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