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 #include "asterisk.h"
00035
00036 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 116296 $")
00037
00038 #include <stdlib.h>
00039 #include <stdio.h>
00040 #include <string.h>
00041 #include <unistd.h>
00042 #include <errno.h>
00043 #include <signal.h>
00044
00045 #include "asterisk/lock.h"
00046 #include "asterisk/file.h"
00047 #include "asterisk/logger.h"
00048 #include "asterisk/channel.h"
00049 #include "asterisk/pbx.h"
00050 #include "asterisk/module.h"
00051 #include "asterisk/linkedlists.h"
00052 #include "asterisk/app.h"
00053 #include "asterisk/utils.h"
00054 #include "asterisk/options.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 void send_child_event(FILE *handle, const char event, const char *data,
00096 const struct ast_channel *chan)
00097 {
00098 char tmp[256];
00099
00100 if (!data) {
00101 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
00102 } else {
00103 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
00104 }
00105
00106 fprintf(handle, "%s\n", tmp);
00107 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
00108 }
00109
00110 static void *gen_alloc(struct ast_channel *chan, void *params)
00111 {
00112 struct ivr_localuser *u = params;
00113 struct gen_state *state;
00114
00115 if (!(state = ast_calloc(1, sizeof(*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 ivr_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 ivr_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 if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10)))
00236 return NULL;
00237
00238 strcpy(entry->filename, filename);
00239
00240 return entry;
00241 }
00242
00243 static int app_exec(struct ast_channel *chan, void *data)
00244 {
00245 struct ast_module_user *lu;
00246 struct playlist_entry *entry;
00247 const char *args = data;
00248 int child_stdin[2] = { 0,0 };
00249 int child_stdout[2] = { 0,0 };
00250 int child_stderr[2] = { 0,0 };
00251 int res = -1;
00252 int test_available_fd = -1;
00253 int gen_active = 0;
00254 int pid;
00255 char *argv[32];
00256 int argc = 1;
00257 char *buf, *command;
00258 FILE *child_commands = NULL;
00259 FILE *child_errors = NULL;
00260 FILE *child_events = NULL;
00261 struct ivr_localuser foo = {
00262 .playlist = AST_LIST_HEAD_INIT_VALUE,
00263 .finishlist = AST_LIST_HEAD_INIT_VALUE,
00264 };
00265 struct ivr_localuser *u = &foo;
00266 sigset_t fullset, oldset;
00267
00268 lu = ast_module_user_add(chan);
00269
00270 sigfillset(&fullset);
00271 pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
00272
00273 u->abort_current_sound = 0;
00274 u->chan = chan;
00275
00276 if (ast_strlen_zero(args)) {
00277 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
00278 ast_module_user_remove(lu);
00279 return -1;
00280 }
00281
00282 buf = ast_strdupa(data);
00283
00284 argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
00285
00286 if (pipe(child_stdin)) {
00287 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
00288 goto exit;
00289 }
00290
00291 if (pipe(child_stdout)) {
00292 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
00293 goto exit;
00294 }
00295
00296 if (pipe(child_stderr)) {
00297 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
00298 goto exit;
00299 }
00300
00301 if (chan->_state != AST_STATE_UP) {
00302 ast_answer(chan);
00303 }
00304
00305 if (ast_activate_generator(chan, &gen, u) < 0) {
00306 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
00307 goto exit;
00308 } else
00309 gen_active = 1;
00310
00311 pid = fork();
00312 if (pid < 0) {
00313 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
00314 goto exit;
00315 }
00316
00317 if (!pid) {
00318
00319 int i;
00320
00321 signal(SIGPIPE, SIG_DFL);
00322 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
00323
00324 if (ast_opt_high_priority)
00325 ast_set_priority(0);
00326
00327 dup2(child_stdin[0], STDIN_FILENO);
00328 dup2(child_stdout[1], STDOUT_FILENO);
00329 dup2(child_stderr[1], STDERR_FILENO);
00330 for (i = STDERR_FILENO + 1; i < 1024; i++)
00331 close(i);
00332 execv(argv[0], argv);
00333 fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
00334 _exit(1);
00335 } else {
00336
00337 int child_events_fd = child_stdin[1];
00338 int child_commands_fd = child_stdout[0];
00339 int child_errors_fd = child_stderr[0];
00340 struct ast_frame *f;
00341 int ms;
00342 int exception;
00343 int ready_fd;
00344 int waitfds[2] = { child_errors_fd, child_commands_fd };
00345 struct ast_channel *rchan;
00346
00347 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
00348
00349 close(child_stdin[0]);
00350 child_stdin[0] = 0;
00351 close(child_stdout[1]);
00352 child_stdout[1] = 0;
00353 close(child_stderr[1]);
00354 child_stderr[1] = 0;
00355
00356 if (!(child_events = fdopen(child_events_fd, "w"))) {
00357 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
00358 goto exit;
00359 }
00360
00361 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
00362 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
00363 goto exit;
00364 }
00365
00366 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
00367 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
00368 goto exit;
00369 }
00370
00371 test_available_fd = open("/dev/null", O_RDONLY);
00372
00373 setvbuf(child_events, NULL, _IONBF, 0);
00374 setvbuf(child_commands, NULL, _IONBF, 0);
00375 setvbuf(child_errors, NULL, _IONBF, 0);
00376
00377 res = 0;
00378
00379 while (1) {
00380 if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
00381 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
00382 res = -1;
00383 break;
00384 }
00385
00386 if (ast_check_hangup(chan)) {
00387 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
00388 send_child_event(child_events, 'H', NULL, chan);
00389 res = -1;
00390 break;
00391 }
00392
00393 ready_fd = 0;
00394 ms = 100;
00395 errno = 0;
00396 exception = 0;
00397
00398 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
00399
00400 if (!AST_LIST_EMPTY(&u->finishlist)) {
00401 AST_LIST_LOCK(&u->finishlist);
00402 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
00403 send_child_event(child_events, 'F', entry->filename, chan);
00404 free(entry);
00405 }
00406 AST_LIST_UNLOCK(&u->finishlist);
00407 }
00408
00409 if (rchan) {
00410
00411 f = ast_read(chan);
00412 if (!f) {
00413 ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
00414 send_child_event(child_events, 'H', NULL, chan);
00415 res = -1;
00416 break;
00417 }
00418
00419 if (f->frametype == AST_FRAME_DTMF) {
00420 send_child_event(child_events, f->subclass, NULL, chan);
00421 if (u->option_autoclear) {
00422 if (!u->abort_current_sound && !u->playing_silence)
00423 send_child_event(child_events, 'T', NULL, chan);
00424 AST_LIST_LOCK(&u->playlist);
00425 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00426 send_child_event(child_events, 'D', entry->filename, chan);
00427 free(entry);
00428 }
00429 if (!u->playing_silence)
00430 u->abort_current_sound = 1;
00431 AST_LIST_UNLOCK(&u->playlist);
00432 }
00433 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
00434 ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
00435 send_child_event(child_events, 'H', NULL, chan);
00436 ast_frfree(f);
00437 res = -1;
00438 break;
00439 }
00440 ast_frfree(f);
00441 } else if (ready_fd == child_commands_fd) {
00442 char input[1024];
00443
00444 if (exception || feof(child_commands)) {
00445 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00446 res = -1;
00447 break;
00448 }
00449
00450 if (!fgets(input, sizeof(input), child_commands))
00451 continue;
00452
00453 command = ast_strip(input);
00454
00455 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
00456
00457 if (strlen(input) < 4)
00458 continue;
00459
00460 if (input[0] == 'S') {
00461 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00462 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00463 send_child_event(child_events, 'Z', NULL, chan);
00464 strcpy(&input[2], "exception");
00465 }
00466 if (!u->abort_current_sound && !u->playing_silence)
00467 send_child_event(child_events, 'T', NULL, chan);
00468 AST_LIST_LOCK(&u->playlist);
00469 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00470 send_child_event(child_events, 'D', entry->filename, chan);
00471 free(entry);
00472 }
00473 if (!u->playing_silence)
00474 u->abort_current_sound = 1;
00475 entry = make_entry(&input[2]);
00476 if (entry)
00477 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00478 AST_LIST_UNLOCK(&u->playlist);
00479 } else if (input[0] == 'A') {
00480 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00481 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00482 send_child_event(child_events, 'Z', NULL, chan);
00483 strcpy(&input[2], "exception");
00484 }
00485 entry = make_entry(&input[2]);
00486 if (entry) {
00487 AST_LIST_LOCK(&u->playlist);
00488 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00489 AST_LIST_UNLOCK(&u->playlist);
00490 }
00491 } else if (input[0] == 'H') {
00492 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
00493 send_child_event(child_events, 'H', NULL, chan);
00494 break;
00495 } else if (input[0] == 'O') {
00496 if (!strcasecmp(&input[2], "autoclear"))
00497 u->option_autoclear = 1;
00498 else if (!strcasecmp(&input[2], "noautoclear"))
00499 u->option_autoclear = 0;
00500 else
00501 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
00502 }
00503 } else if (ready_fd == child_errors_fd) {
00504 char input[1024];
00505
00506 if (exception || (dup2(child_commands_fd, test_available_fd) == -1) || feof(child_errors)) {
00507 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00508 res = -1;
00509 break;
00510 }
00511
00512 if (fgets(input, sizeof(input), child_errors)) {
00513 command = ast_strip(input);
00514 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
00515 }
00516 } else if ((ready_fd < 0) && ms) {
00517 if (errno == 0 || errno == EINTR)
00518 continue;
00519
00520 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
00521 break;
00522 }
00523 }
00524 }
00525
00526 exit:
00527 if (gen_active)
00528 ast_deactivate_generator(chan);
00529
00530 if (child_events)
00531 fclose(child_events);
00532
00533 if (child_commands)
00534 fclose(child_commands);
00535
00536 if (child_errors)
00537 fclose(child_errors);
00538
00539 if (test_available_fd > -1) {
00540 close(test_available_fd);
00541 }
00542
00543 if (child_stdin[0])
00544 close(child_stdin[0]);
00545
00546 if (child_stdin[1])
00547 close(child_stdin[1]);
00548
00549 if (child_stdout[0])
00550 close(child_stdout[0]);
00551
00552 if (child_stdout[1])
00553 close(child_stdout[1]);
00554
00555 if (child_stderr[0])
00556 close(child_stderr[0]);
00557
00558 if (child_stderr[1])
00559 close(child_stderr[1]);
00560
00561 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
00562 free(entry);
00563
00564 ast_module_user_remove(lu);
00565
00566 return res;
00567 }
00568
00569 static int unload_module(void)
00570 {
00571 int res;
00572
00573 res = ast_unregister_application(app);
00574
00575 ast_module_user_hangup_all();
00576
00577 return res;
00578 }
00579
00580 static int load_module(void)
00581 {
00582 return ast_register_application(app, app_exec, synopsis, descrip);
00583 }
00584
00585 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");