2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Kevin P. Fleming <kpfleming@digium.com>
8 * Portions taken from the file-based music-on-hold work
9 * created by Anthony Minessale II in res_musiconhold.c
11 * See http://www.asterisk.org for more information about
12 * the Asterisk project. Please do not directly contact
13 * any of the maintainers of this project for assistance;
14 * the project provides a web site, mailing lists and IRC
15 * channels for your use.
17 * This program is free software, distributed under the terms of
18 * the GNU General Public License Version 2. See the LICENSE file
19 * at the top of the source tree.
24 * \brief External IVR application interface
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38 #include "asterisk/lock.h"
39 #include "asterisk/file.h"
40 #include "asterisk/logger.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/linkedlists.h"
46 static const char *tdesc = "External IVR Interface Application";
48 static const char *app = "ExternalIVR";
50 static const char *synopsis = "Interfaces with an external IVR application";
52 static const char *descrip =
53 " ExternalIVR(command[|arg[|arg...]]): Forks an process to run the supplied command,\n"
54 "and starts a generator on the channel. The generator's play list is\n"
55 "controlled by the external application, which can add and clear entries\n"
56 "via simple commands issued over its stdout. The external application\n"
57 "will receive all DTMF events received on the channel, and notification\n"
58 "if the channel is hung up. The application will not be forcibly terminated\n"
59 "when the channel is hung up.\n"
60 "See doc/README.externalivr for a protocol specification.\n";
62 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name, ## __VA_ARGS__)
64 struct playlist_entry {
65 AST_LIST_ENTRY(playlist_entry) list;
70 struct ast_channel *chan;
71 struct localuser *next;
72 AST_LIST_HEAD(playlist, playlist_entry) playlist;
73 AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
74 int abort_current_sound;
83 struct ast_filestream *stream;
84 struct playlist_entry *current;
88 static void send_child_event(FILE *handle, const char event, const char *data,
89 const struct ast_channel *chan)
94 snprintf(tmp, sizeof(tmp), "%c,%10ld", event, time(NULL));
96 snprintf(tmp, sizeof(tmp), "%c,%10ld,%s", event, time(NULL), data);
99 fprintf(handle, "%s\n", tmp);
100 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
103 static void *gen_alloc(struct ast_channel *chan, void *params)
105 struct localuser *u = params;
106 struct gen_state *state;
108 state = calloc(1, sizeof(*state));
118 static void gen_closestream(struct gen_state *state)
123 ast_closestream(state->stream);
124 state->u->chan->stream = NULL;
125 state->stream = NULL;
128 static void gen_release(struct ast_channel *chan, void *data)
130 struct gen_state *state = data;
132 gen_closestream(state);
136 /* caller has the playlist locked */
137 static int gen_nextfile(struct gen_state *state)
139 struct localuser *u = state->u;
140 char *file_to_stream;
142 u->abort_current_sound = 0;
143 u->playing_silence = 0;
144 gen_closestream(state);
146 while (!state->stream) {
147 state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
148 if (state->current) {
149 file_to_stream = state->current->filename;
151 file_to_stream = "silence-10";
152 u->playing_silence = 1;
155 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
156 ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
157 if (!u->playing_silence) {
165 return (!state->stream);
168 static struct ast_frame *gen_readframe(struct gen_state *state)
170 struct ast_frame *f = NULL;
171 struct localuser *u = state->u;
173 if (u->abort_current_sound ||
174 (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
175 gen_closestream(state);
176 AST_LIST_LOCK(&u->playlist);
178 AST_LIST_UNLOCK(&u->playlist);
181 if (!(state->stream && (f = ast_readframe(state->stream)))) {
182 if (state->current) {
183 AST_LIST_LOCK(&u->finishlist);
184 AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
185 AST_LIST_UNLOCK(&u->finishlist);
186 state->current = NULL;
188 if (!gen_nextfile(state))
189 f = ast_readframe(state->stream);
195 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
197 struct gen_state *state = data;
198 struct ast_frame *f = NULL;
201 state->sample_queue += samples;
203 while (state->sample_queue > 0) {
204 if (!(f = gen_readframe(state)))
207 res = ast_write(chan, f);
210 ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
213 state->sample_queue -= f->samples;
219 static struct ast_generator gen =
222 release: gen_release,
223 generate: gen_generate,
226 static struct playlist_entry *make_entry(const char *filename)
228 struct playlist_entry *entry;
230 entry = calloc(1, sizeof(*entry) + strlen(filename) + 10);
235 strcpy(entry->filename, filename);
240 static int app_exec(struct ast_channel *chan, void *data)
242 struct localuser *u = NULL;
243 struct playlist_entry *entry;
244 const char *args = data;
245 int child_stdin[2] = { 0,0 };
246 int child_stdout[2] = { 0,0 };
247 int child_stderr[2] = { 0,0 };
255 FILE *child_commands = NULL;
256 FILE *child_errors = NULL;
257 FILE *child_events = NULL;
261 AST_LIST_HEAD_INIT(&u->playlist);
262 AST_LIST_HEAD_INIT(&u->finishlist);
263 u->abort_current_sound = 0;
265 if (ast_strlen_zero(args)) {
266 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
270 buf = ast_strdupa(data);
271 command = strsep(&buf, "|");
272 memset(argv, 0, sizeof(argv) / sizeof(argv[0]));
274 while ((argc < 31) && (argv[argc++] = strsep(&buf, "|")));
277 if (pipe(child_stdin)) {
278 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
282 if (pipe(child_stdout)) {
283 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
287 if (pipe(child_stderr)) {
288 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
292 if (chan->_state != AST_STATE_UP) {
296 if (ast_activate_generator(chan, &gen, u) < 0) {
297 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
304 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
312 dup2(child_stdin[0], STDIN_FILENO);
313 dup2(child_stdout[1], STDOUT_FILENO);
314 dup2(child_stderr[1], STDERR_FILENO);
315 for (i = STDERR_FILENO + 1; i < 1024; i++)
317 execv(command, argv);
318 fprintf(stderr, "Failed to execute '%s': %s\n", command, strerror(errno));
322 int child_events_fd = child_stdin[1];
323 int child_commands_fd = child_stdout[0];
324 int child_errors_fd = child_stderr[0];
329 int waitfds[2] = { child_errors_fd, child_commands_fd };
330 struct ast_channel *rchan;
332 close(child_stdin[0]);
334 close(child_stdout[1]);
336 close(child_stderr[1]);
339 if (!(child_events = fdopen(child_events_fd, "w"))) {
340 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
344 setvbuf(child_events, NULL, _IONBF, 0);
346 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
347 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
351 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
352 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
359 if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
360 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
365 if (ast_check_hangup(chan)) {
366 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
367 send_child_event(child_events, 'H', NULL, chan);
377 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
379 if (!AST_LIST_EMPTY(&u->finishlist)) {
380 AST_LIST_LOCK(&u->finishlist);
381 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
382 send_child_event(child_events, 'F', entry->filename, chan);
385 AST_LIST_UNLOCK(&u->finishlist);
389 /* the channel has something */
392 ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
393 send_child_event(child_events, 'H', NULL, chan);
398 if (f->frametype == AST_FRAME_DTMF) {
399 send_child_event(child_events, f->subclass, NULL, chan);
400 if (u->option_autoclear) {
401 if (!u->abort_current_sound && !u->playing_silence)
402 send_child_event(child_events, 'T', NULL, chan);
403 AST_LIST_LOCK(&u->playlist);
404 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
405 send_child_event(child_events, 'D', entry->filename, chan);
408 if (!u->playing_silence)
409 u->abort_current_sound = 1;
410 AST_LIST_UNLOCK(&u->playlist);
412 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
413 ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
414 send_child_event(child_events, 'H', NULL, chan);
420 } else if (ready_fd == child_commands_fd) {
423 if (exception || feof(child_commands)) {
424 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
429 if (!fgets(input, sizeof(input), child_commands))
432 command = ast_strip(input);
434 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
436 if (strlen(input) < 4)
439 if (input[0] == 'S') {
440 if (ast_fileexists(&input[2], NULL, NULL) == -1) {
441 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
442 send_child_event(child_events, 'Z', NULL, chan);
443 strcpy(&input[2], "exception");
445 if (!u->abort_current_sound && !u->playing_silence)
446 send_child_event(child_events, 'T', NULL, chan);
447 AST_LIST_LOCK(&u->playlist);
448 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
449 send_child_event(child_events, 'D', entry->filename, chan);
452 if (!u->playing_silence)
453 u->abort_current_sound = 1;
454 entry = make_entry(&input[2]);
456 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
457 AST_LIST_UNLOCK(&u->playlist);
458 } else if (input[0] == 'A') {
459 if (ast_fileexists(&input[2], NULL, NULL) == -1) {
460 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
461 send_child_event(child_events, 'Z', NULL, chan);
462 strcpy(&input[2], "exception");
464 entry = make_entry(&input[2]);
466 AST_LIST_LOCK(&u->playlist);
467 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
468 AST_LIST_UNLOCK(&u->playlist);
470 } else if (input[0] == 'H') {
471 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
472 send_child_event(child_events, 'H', NULL, chan);
474 } else if (input[0] == 'O') {
475 if (!strcasecmp(&input[2], "autoclear"))
476 u->option_autoclear = 1;
477 else if (!strcasecmp(&input[2], "noautoclear"))
478 u->option_autoclear = 0;
480 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
482 } else if (ready_fd == child_errors_fd) {
485 if (exception || feof(child_errors)) {
486 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
491 if (fgets(input, sizeof(input), child_errors)) {
492 command = ast_strip(input);
493 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
495 } else if ((ready_fd < 0) && ms) {
496 if (errno == 0 || errno == EINTR)
499 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
507 ast_deactivate_generator(chan);
510 fclose(child_events);
513 fclose(child_commands);
516 fclose(child_errors);
519 close(child_stdin[0]);
522 close(child_stdin[1]);
525 close(child_stdout[0]);
528 close(child_stdout[1]);
531 close(child_stderr[0]);
534 close(child_stderr[1]);
536 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
539 LOCAL_USER_REMOVE(u);
544 int unload_module(void)
548 res = ast_unregister_application(app);
550 STANDARD_HANGUP_LOCALUSERS;
555 int load_module(void)
557 return ast_register_application(app, app_exec, synopsis, descrip);
560 char *description(void)
562 return (char *) tdesc;
569 STANDARD_USECOUNT(res);
576 return ASTERISK_GPL_KEY;