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