00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038 #include "asterisk.h"
00039
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 195317 $")
00041
00042 #include <signal.h>
00043 #ifdef HAVE_CAP
00044 #include <sys/capability.h>
00045 #endif
00046
00047 #include "asterisk/lock.h"
00048 #include "asterisk/file.h"
00049 #include "asterisk/channel.h"
00050 #include "asterisk/pbx.h"
00051 #include "asterisk/module.h"
00052 #include "asterisk/linkedlists.h"
00053 #include "asterisk/app.h"
00054 #include "asterisk/utils.h"
00055
00056 static const char *app = "ExternalIVR";
00057
00058 static const char *synopsis = "Interfaces with an external IVR application";
00059
00060 static const char *descrip =
00061 " ExternalIVR(command[,arg[,arg...]]): Forks a process to run the supplied command,\n"
00062 "and starts a generator on the channel. The generator's play list is\n"
00063 "controlled by the external application, which can add and clear entries\n"
00064 "via simple commands issued over its stdout. The external application\n"
00065 "will receive all DTMF events received on the channel, and notification\n"
00066 "if the channel is hung up. The application will not be forcibly terminated\n"
00067 "when the channel is hung up.\n"
00068 "See doc/externalivr.txt for a protocol specification.\n";
00069
00070
00071 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
00072
00073 struct playlist_entry {
00074 AST_LIST_ENTRY(playlist_entry) list;
00075 char filename[1];
00076 };
00077
00078 struct ivr_localuser {
00079 struct ast_channel *chan;
00080 AST_LIST_HEAD(playlist, playlist_entry) playlist;
00081 AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
00082 int abort_current_sound;
00083 int playing_silence;
00084 int option_autoclear;
00085 };
00086
00087
00088 struct gen_state {
00089 struct ivr_localuser *u;
00090 struct ast_filestream *stream;
00091 struct playlist_entry *current;
00092 int sample_queue;
00093 };
00094
00095 static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
00096 int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd,
00097 const char *args);
00098
00099 static void send_eivr_event(FILE *handle, const char event, const char *data,
00100 const struct ast_channel *chan)
00101 {
00102 char tmp[256];
00103
00104 if (!data) {
00105 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
00106 } else {
00107 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
00108 }
00109
00110 fprintf(handle, "%s\n", tmp);
00111 if (option_debug)
00112 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
00113 }
00114
00115 static void *gen_alloc(struct ast_channel *chan, void *params)
00116 {
00117 struct ivr_localuser *u = params;
00118 struct gen_state *state;
00119
00120 if (!(state = ast_calloc(1, sizeof(*state))))
00121 return NULL;
00122
00123 state->u = u;
00124
00125 return state;
00126 }
00127
00128 static void gen_closestream(struct gen_state *state)
00129 {
00130 if (!state->stream)
00131 return;
00132
00133 ast_closestream(state->stream);
00134 state->u->chan->stream = NULL;
00135 state->stream = NULL;
00136 }
00137
00138 static void gen_release(struct ast_channel *chan, void *data)
00139 {
00140 struct gen_state *state = data;
00141
00142 gen_closestream(state);
00143 ast_free(data);
00144 }
00145
00146
00147 static int gen_nextfile(struct gen_state *state)
00148 {
00149 struct ivr_localuser *u = state->u;
00150 char *file_to_stream;
00151
00152 u->abort_current_sound = 0;
00153 u->playing_silence = 0;
00154 gen_closestream(state);
00155
00156 while (!state->stream) {
00157 state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
00158 if (state->current) {
00159 file_to_stream = state->current->filename;
00160 } else {
00161 file_to_stream = "silence/10";
00162 u->playing_silence = 1;
00163 }
00164
00165 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
00166 ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
00167 if (!u->playing_silence) {
00168 continue;
00169 } else {
00170 break;
00171 }
00172 }
00173 }
00174
00175 return (!state->stream);
00176 }
00177
00178 static struct ast_frame *gen_readframe(struct gen_state *state)
00179 {
00180 struct ast_frame *f = NULL;
00181 struct ivr_localuser *u = state->u;
00182
00183 if (u->abort_current_sound ||
00184 (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
00185 gen_closestream(state);
00186 AST_LIST_LOCK(&u->playlist);
00187 gen_nextfile(state);
00188 AST_LIST_UNLOCK(&u->playlist);
00189 }
00190
00191 if (!(state->stream && (f = ast_readframe(state->stream)))) {
00192 if (state->current) {
00193 AST_LIST_LOCK(&u->finishlist);
00194 AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
00195 AST_LIST_UNLOCK(&u->finishlist);
00196 state->current = NULL;
00197 }
00198 if (!gen_nextfile(state))
00199 f = ast_readframe(state->stream);
00200 }
00201
00202 return f;
00203 }
00204
00205 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
00206 {
00207 struct gen_state *state = data;
00208 struct ast_frame *f = NULL;
00209 int res = 0;
00210
00211 state->sample_queue += samples;
00212
00213 while (state->sample_queue > 0) {
00214 if (!(f = gen_readframe(state)))
00215 return -1;
00216
00217 res = ast_write(chan, f);
00218 ast_frfree(f);
00219 if (res < 0) {
00220 ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
00221 return -1;
00222 }
00223 state->sample_queue -= f->samples;
00224 }
00225
00226 return res;
00227 }
00228
00229 static struct ast_generator gen =
00230 {
00231 alloc: gen_alloc,
00232 release: gen_release,
00233 generate: gen_generate,
00234 };
00235
00236 static void ast_eivr_getvariable(struct ast_channel *chan, char *data, char *outbuf, int outbuflen)
00237 {
00238
00239
00240
00241 char *inbuf, *variable;
00242 const char *value;
00243 int j;
00244 struct ast_str *newstring = ast_str_alloca(outbuflen);
00245
00246 outbuf[0] = '\0';
00247
00248 for (j = 1, inbuf = data; ; j++) {
00249 variable = strsep(&inbuf, ",");
00250 if (variable == NULL) {
00251 int outstrlen = strlen(outbuf);
00252 if(outstrlen && outbuf[outstrlen - 1] == ',') {
00253 outbuf[outstrlen - 1] = 0;
00254 }
00255 break;
00256 }
00257
00258 value = pbx_builtin_getvar_helper(chan, variable);
00259 if(!value)
00260 value = "";
00261 ast_str_append(&newstring, 0, "%s=%s,", variable, value);
00262 ast_copy_string(outbuf, newstring->str, outbuflen);
00263 }
00264 };
00265
00266 static void ast_eivr_setvariable(struct ast_channel *chan, char *data)
00267 {
00268 char *value;
00269
00270 char *inbuf = ast_strdupa(data), *variable;
00271
00272 for (variable = strsep(&inbuf, ","); variable; variable = strsep(&inbuf, ",")) {
00273 ast_debug(1, "Setting up a variable: %s\n", variable);
00274
00275 value = strchr(variable, '=');
00276 if (!value) {
00277 value = "";
00278 } else {
00279 *value++ = '\0';
00280 }
00281 pbx_builtin_setvar_helper(chan, variable, value);
00282 }
00283 };
00284
00285 static struct playlist_entry *make_entry(const char *filename)
00286 {
00287 struct playlist_entry *entry;
00288
00289 if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10)))
00290 return NULL;
00291
00292 strcpy(entry->filename, filename);
00293
00294 return entry;
00295 }
00296
00297 static int app_exec(struct ast_channel *chan, void *data)
00298 {
00299 struct playlist_entry *entry;
00300 int child_stdin[2] = { 0,0 };
00301 int child_stdout[2] = { 0,0 };
00302 int child_stderr[2] = { 0,0 };
00303 int res = -1;
00304 int gen_active = 0;
00305 int pid;
00306 char *buf, *comma_delim_argbuf;
00307 struct ivr_localuser foo = {
00308 .playlist = AST_LIST_HEAD_INIT_VALUE,
00309 .finishlist = AST_LIST_HEAD_INIT_VALUE,
00310 };
00311 struct ivr_localuser *u = &foo;
00312 sigset_t fullset, oldset;
00313 AST_DECLARE_APP_ARGS(args,
00314 AST_APP_ARG(cmd)[32];
00315 );
00316
00317 sigfillset(&fullset);
00318 pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
00319
00320 u->abort_current_sound = 0;
00321 u->chan = chan;
00322
00323 if (ast_strlen_zero(data)) {
00324 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
00325 return -1;
00326 }
00327
00328 buf = ast_strdupa(data);
00329 AST_STANDARD_APP_ARGS(args, buf);
00330
00331
00332 comma_delim_argbuf = ast_strdupa(data);
00333
00334 if (pipe(child_stdin)) {
00335 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
00336 goto exit;
00337 }
00338 if (pipe(child_stdout)) {
00339 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
00340 goto exit;
00341 }
00342 if (pipe(child_stderr)) {
00343 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
00344 goto exit;
00345 }
00346 if (chan->_state != AST_STATE_UP) {
00347 ast_answer(chan);
00348 }
00349 if (ast_activate_generator(chan, &gen, u) < 0) {
00350 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
00351 goto exit;
00352 } else
00353 gen_active = 1;
00354
00355 pid = fork();
00356 if (pid < 0) {
00357 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
00358 goto exit;
00359 }
00360
00361 if (!pid) {
00362
00363 int i;
00364 #ifdef HAVE_CAP
00365 cap_t cap = cap_from_text("cap_net_admin-eip");
00366
00367 if (cap_set_proc(cap)) {
00368
00369 ast_log(LOG_WARNING, "Unable to remove capabilities.\n");
00370 }
00371 cap_free(cap);
00372 #endif
00373
00374 signal(SIGPIPE, SIG_DFL);
00375 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
00376
00377 if (ast_opt_high_priority)
00378 ast_set_priority(0);
00379
00380 dup2(child_stdin[0], STDIN_FILENO);
00381 dup2(child_stdout[1], STDOUT_FILENO);
00382 dup2(child_stderr[1], STDERR_FILENO);
00383 for (i = STDERR_FILENO + 1; i < 1024; i++)
00384 close(i);
00385 execv(args.cmd[0], args.cmd);
00386 fprintf(stderr, "Failed to execute '%s': %s\n", args.cmd[0], strerror(errno));
00387 _exit(1);
00388 } else {
00389
00390
00391 close(child_stdin[0]);
00392 child_stdin[0] = 0;
00393 close(child_stdout[1]);
00394 child_stdout[1] = 0;
00395 close(child_stderr[1]);
00396 child_stderr[1] = 0;
00397 res = eivr_comm(chan, u, child_stdin[1], child_stdout[0], child_stderr[0], comma_delim_argbuf);
00398
00399 exit:
00400 if (gen_active)
00401 ast_deactivate_generator(chan);
00402
00403 if (child_stdin[0])
00404 close(child_stdin[0]);
00405
00406 if (child_stdin[1])
00407 close(child_stdin[1]);
00408
00409 if (child_stdout[0])
00410 close(child_stdout[0]);
00411
00412 if (child_stdout[1])
00413 close(child_stdout[1]);
00414
00415 if (child_stderr[0])
00416 close(child_stderr[0]);
00417
00418 if (child_stderr[1])
00419 close(child_stderr[1]);
00420
00421 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
00422 ast_free(entry);
00423
00424 return res;
00425 }
00426 }
00427
00428 static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
00429 int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd,
00430 const char *args)
00431 {
00432 struct playlist_entry *entry;
00433 struct ast_frame *f;
00434 int ms;
00435 int exception;
00436 int ready_fd;
00437 int waitfds[2] = { eivr_commands_fd, eivr_errors_fd };
00438 struct ast_channel *rchan;
00439 char *command;
00440 int res = -1;
00441 int test_available_fd = -1;
00442
00443 FILE *eivr_commands = NULL;
00444 FILE *eivr_errors = NULL;
00445 FILE *eivr_events = NULL;
00446
00447 if (!(eivr_events = fdopen(eivr_events_fd, "w"))) {
00448 ast_chan_log(LOG_WARNING, chan, "Could not open stream to send events\n");
00449 goto exit;
00450 }
00451 if (!(eivr_commands = fdopen(eivr_commands_fd, "r"))) {
00452 ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive commands\n");
00453 goto exit;
00454 }
00455 if(eivr_errors_fd) {
00456 if (!(eivr_errors = fdopen(eivr_errors_fd, "r"))) {
00457 ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive errors\n");
00458 goto exit;
00459 }
00460 }
00461
00462 test_available_fd = open("/dev/null", O_RDONLY);
00463
00464 setvbuf(eivr_events, NULL, _IONBF, 0);
00465 setvbuf(eivr_commands, NULL, _IONBF, 0);
00466 if(eivr_errors)
00467 setvbuf(eivr_errors, NULL, _IONBF, 0);
00468
00469 res = 0;
00470
00471 while (1) {
00472 if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
00473 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
00474 res = -1;
00475 break;
00476 }
00477 if (ast_check_hangup(chan)) {
00478 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
00479 send_eivr_event(eivr_events, 'H', NULL, chan);
00480 res = -1;
00481 break;
00482 }
00483
00484 ready_fd = 0;
00485 ms = 100;
00486 errno = 0;
00487 exception = 0;
00488
00489 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, (eivr_errors_fd == 0) ? 1 : 2, &exception, &ready_fd, &ms);
00490
00491 if (!AST_LIST_EMPTY(&u->finishlist)) {
00492 AST_LIST_LOCK(&u->finishlist);
00493 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
00494 send_eivr_event(eivr_events, 'F', entry->filename, chan);
00495 ast_free(entry);
00496 }
00497 AST_LIST_UNLOCK(&u->finishlist);
00498 }
00499
00500 if (rchan) {
00501
00502 f = ast_read(chan);
00503 if (!f) {
00504 ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
00505 send_eivr_event(eivr_events, 'H', NULL, chan);
00506 res = -1;
00507 break;
00508 }
00509 if (f->frametype == AST_FRAME_DTMF) {
00510 send_eivr_event(eivr_events, f->subclass, NULL, chan);
00511 if (u->option_autoclear) {
00512 if (!u->abort_current_sound && !u->playing_silence)
00513 send_eivr_event(eivr_events, 'T', NULL, chan);
00514 AST_LIST_LOCK(&u->playlist);
00515 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00516 send_eivr_event(eivr_events, 'D', entry->filename, chan);
00517 ast_free(entry);
00518 }
00519 if (!u->playing_silence)
00520 u->abort_current_sound = 1;
00521 AST_LIST_UNLOCK(&u->playlist);
00522 }
00523 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
00524 ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
00525 send_eivr_event(eivr_events, 'H', NULL, chan);
00526 ast_frfree(f);
00527 res = -1;
00528 break;
00529 }
00530 ast_frfree(f);
00531 } else if (ready_fd == eivr_commands_fd) {
00532 char input[1024];
00533
00534 if (exception || (dup2(eivr_commands_fd, test_available_fd) == -1) || feof(eivr_commands)) {
00535 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00536 res = -1;
00537 break;
00538 }
00539
00540 if (!fgets(input, sizeof(input), eivr_commands))
00541 continue;
00542
00543 command = ast_strip(input);
00544
00545 if (option_debug)
00546 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
00547
00548 if (strlen(input) < 4)
00549 continue;
00550
00551 if (input[0] == 'P') {
00552 send_eivr_event(eivr_events, 'P', args, chan);
00553
00554 } else if (input[0] == 'S') {
00555 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00556 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00557 send_eivr_event(eivr_events, 'Z', NULL, chan);
00558 strcpy(&input[2], "exception");
00559 }
00560 if (!u->abort_current_sound && !u->playing_silence)
00561 send_eivr_event(eivr_events, 'T', NULL, chan);
00562 AST_LIST_LOCK(&u->playlist);
00563 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00564 send_eivr_event(eivr_events, 'D', entry->filename, chan);
00565 ast_free(entry);
00566 }
00567 if (!u->playing_silence)
00568 u->abort_current_sound = 1;
00569 entry = make_entry(&input[2]);
00570 if (entry)
00571 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00572 AST_LIST_UNLOCK(&u->playlist);
00573 } else if (input[0] == 'A') {
00574 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00575 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00576 send_eivr_event(eivr_events, 'Z', NULL, chan);
00577 strcpy(&input[2], "exception");
00578 }
00579 entry = make_entry(&input[2]);
00580 if (entry) {
00581 AST_LIST_LOCK(&u->playlist);
00582 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00583 AST_LIST_UNLOCK(&u->playlist);
00584 }
00585 } else if (input[0] == 'G') {
00586
00587 char response[2048];
00588
00589 ast_chan_log(LOG_NOTICE, chan, "Getting a Variable out of the channel: %s\n", &input[2]);
00590 ast_eivr_getvariable(chan, &input[2], response, sizeof(response));
00591 send_eivr_event(eivr_events, 'G', response, chan);
00592 } else if (input[0] == 'V') {
00593
00594 ast_chan_log(LOG_NOTICE, chan, "Setting a Variable up: %s\n", &input[2]);
00595 ast_eivr_setvariable(chan, &input[2]);
00596 } else if (input[0] == 'L') {
00597 ast_chan_log(LOG_NOTICE, chan, "Log message from EIVR: %s\n", &input[2]);
00598 } else if (input[0] == 'X') {
00599 ast_chan_log(LOG_NOTICE, chan, "Exiting ExternalIVR: %s\n", &input[2]);
00600
00601 res = 0;
00602 break;
00603 } else if (input[0] == 'E') {
00604 ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
00605 send_eivr_event(eivr_events, 'E', NULL, chan);
00606 res = 0;
00607 break;
00608 } else if (input[0] == 'H') {
00609 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
00610 send_eivr_event(eivr_events, 'H', NULL, chan);
00611 res = -1;
00612 break;
00613 } else if (input[0] == 'O') {
00614 if (!strcasecmp(&input[2], "autoclear"))
00615 u->option_autoclear = 1;
00616 else if (!strcasecmp(&input[2], "noautoclear"))
00617 u->option_autoclear = 0;
00618 else
00619 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
00620 }
00621 } else if (eivr_errors_fd && ready_fd == eivr_errors_fd) {
00622 char input[1024];
00623
00624 if (exception || feof(eivr_errors)) {
00625 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00626 res = -1;
00627 break;
00628 }
00629 if (fgets(input, sizeof(input), eivr_errors)) {
00630 command = ast_strip(input);
00631 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
00632 }
00633 } else if ((ready_fd < 0) && ms) {
00634 if (errno == 0 || errno == EINTR)
00635 continue;
00636
00637 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
00638 break;
00639 }
00640 }
00641
00642
00643 exit:
00644
00645 if (test_available_fd > -1) {
00646 close(test_available_fd);
00647 }
00648
00649 if (eivr_events)
00650 fclose(eivr_events);
00651
00652 if (eivr_commands)
00653 fclose(eivr_commands);
00654
00655 if (eivr_errors)
00656 fclose(eivr_errors);
00657
00658 return res;
00659
00660 }
00661
00662 static int unload_module(void)
00663 {
00664 return ast_unregister_application(app);
00665 }
00666
00667 static int load_module(void)
00668 {
00669 return ast_register_application(app, app_exec, synopsis, descrip);
00670 }
00671
00672 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");