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