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