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