Fix a few things I missed to ensure zt_chan_conf structure is not modified in mkintf
[asterisk-bristuff.git] / apps / app_externalivr.c
blob8004fecd143ee875681d44b12719e6c90b90c4bb
1 /*
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.
22 /*! \file
24 * \brief External IVR application interface
26 * \author Kevin P. Fleming <kpfleming@digium.com>
28 * \note Portions taken from the file-based music-on-hold work
29 * created by Anthony Minessale II in res_musiconhold.c
31 * \ingroup applications
34 #include "asterisk.h"
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <signal.h>
45 #include "asterisk/lock.h"
46 #include "asterisk/file.h"
47 #include "asterisk/logger.h"
48 #include "asterisk/channel.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/module.h"
51 #include "asterisk/linkedlists.h"
52 #include "asterisk/app.h"
53 #include "asterisk/utils.h"
54 #include "asterisk/options.h"
56 static const char *app = "ExternalIVR";
58 static const char *synopsis = "Interfaces with an external IVR application";
60 static const char *descrip =
61 " ExternalIVR(command[|arg[|arg...]]): Forks a process to run the supplied command,\n"
62 "and starts a generator on the channel. The generator's play list is\n"
63 "controlled by the external application, which can add and clear entries\n"
64 "via simple commands issued over its stdout. The external application\n"
65 "will receive all DTMF events received on the channel, and notification\n"
66 "if the channel is hung up. The application will not be forcibly terminated\n"
67 "when the channel is hung up.\n"
68 "See doc/externalivr.txt for a protocol specification.\n";
70 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
71 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
73 struct playlist_entry {
74 AST_LIST_ENTRY(playlist_entry) list;
75 char filename[1];
78 struct ivr_localuser {
79 struct ast_channel *chan;
80 AST_LIST_HEAD(playlist, playlist_entry) playlist;
81 AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
82 int abort_current_sound;
83 int playing_silence;
84 int option_autoclear;
88 struct gen_state {
89 struct ivr_localuser *u;
90 struct ast_filestream *stream;
91 struct playlist_entry *current;
92 int sample_queue;
95 static void send_child_event(FILE *handle, const char event, const char *data,
96 const struct ast_channel *chan)
98 char tmp[256];
100 if (!data) {
101 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
102 } else {
103 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
106 fprintf(handle, "%s\n", tmp);
107 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
110 static void *gen_alloc(struct ast_channel *chan, void *params)
112 struct ivr_localuser *u = params;
113 struct gen_state *state;
115 if (!(state = ast_calloc(1, sizeof(*state))))
116 return NULL;
118 state->u = u;
120 return state;
123 static void gen_closestream(struct gen_state *state)
125 if (!state->stream)
126 return;
128 ast_closestream(state->stream);
129 state->u->chan->stream = NULL;
130 state->stream = NULL;
133 static void gen_release(struct ast_channel *chan, void *data)
135 struct gen_state *state = data;
137 gen_closestream(state);
138 free(data);
141 /* caller has the playlist locked */
142 static int gen_nextfile(struct gen_state *state)
144 struct ivr_localuser *u = state->u;
145 char *file_to_stream;
147 u->abort_current_sound = 0;
148 u->playing_silence = 0;
149 gen_closestream(state);
151 while (!state->stream) {
152 state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
153 if (state->current) {
154 file_to_stream = state->current->filename;
155 } else {
156 file_to_stream = "silence/10";
157 u->playing_silence = 1;
160 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
161 ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
162 if (!u->playing_silence) {
163 continue;
164 } else {
165 break;
170 return (!state->stream);
173 static struct ast_frame *gen_readframe(struct gen_state *state)
175 struct ast_frame *f = NULL;
176 struct ivr_localuser *u = state->u;
178 if (u->abort_current_sound ||
179 (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
180 gen_closestream(state);
181 AST_LIST_LOCK(&u->playlist);
182 gen_nextfile(state);
183 AST_LIST_UNLOCK(&u->playlist);
186 if (!(state->stream && (f = ast_readframe(state->stream)))) {
187 if (state->current) {
188 AST_LIST_LOCK(&u->finishlist);
189 AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
190 AST_LIST_UNLOCK(&u->finishlist);
191 state->current = NULL;
193 if (!gen_nextfile(state))
194 f = ast_readframe(state->stream);
197 return f;
200 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
202 struct gen_state *state = data;
203 struct ast_frame *f = NULL;
204 int res = 0;
206 state->sample_queue += samples;
208 while (state->sample_queue > 0) {
209 if (!(f = gen_readframe(state)))
210 return -1;
212 res = ast_write(chan, f);
213 ast_frfree(f);
214 if (res < 0) {
215 ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
216 return -1;
218 state->sample_queue -= f->samples;
221 return res;
224 static struct ast_generator gen =
226 alloc: gen_alloc,
227 release: gen_release,
228 generate: gen_generate,
231 static struct playlist_entry *make_entry(const char *filename)
233 struct playlist_entry *entry;
235 if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
236 return NULL;
238 strcpy(entry->filename, filename);
240 return entry;
243 static int app_exec(struct ast_channel *chan, void *data)
245 struct ast_module_user *lu;
246 struct playlist_entry *entry;
247 const char *args = data;
248 int child_stdin[2] = { 0,0 };
249 int child_stdout[2] = { 0,0 };
250 int child_stderr[2] = { 0,0 };
251 int res = -1;
252 int test_available_fd = -1;
253 int gen_active = 0;
254 int pid;
255 char *argv[32];
256 int argc = 1;
257 char *buf, *command;
258 FILE *child_commands = NULL;
259 FILE *child_errors = NULL;
260 FILE *child_events = NULL;
261 struct ivr_localuser foo = {
262 .playlist = AST_LIST_HEAD_INIT_VALUE,
263 .finishlist = AST_LIST_HEAD_INIT_VALUE,
265 struct ivr_localuser *u = &foo;
266 sigset_t fullset, oldset;
268 lu = ast_module_user_add(chan);
270 sigfillset(&fullset);
271 pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
273 u->abort_current_sound = 0;
274 u->chan = chan;
276 if (ast_strlen_zero(args)) {
277 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
278 ast_module_user_remove(lu);
279 return -1;
282 buf = ast_strdupa(data);
284 argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
286 if (pipe(child_stdin)) {
287 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
288 goto exit;
291 if (pipe(child_stdout)) {
292 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
293 goto exit;
296 if (pipe(child_stderr)) {
297 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
298 goto exit;
301 if (chan->_state != AST_STATE_UP) {
302 ast_answer(chan);
305 if (ast_activate_generator(chan, &gen, u) < 0) {
306 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
307 goto exit;
308 } else
309 gen_active = 1;
311 pid = fork();
312 if (pid < 0) {
313 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
314 goto exit;
317 if (!pid) {
318 /* child process */
319 int i;
321 signal(SIGPIPE, SIG_DFL);
322 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
324 if (ast_opt_high_priority)
325 ast_set_priority(0);
327 dup2(child_stdin[0], STDIN_FILENO);
328 dup2(child_stdout[1], STDOUT_FILENO);
329 dup2(child_stderr[1], STDERR_FILENO);
330 for (i = STDERR_FILENO + 1; i < 1024; i++)
331 close(i);
332 execv(argv[0], argv);
333 fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
334 _exit(1);
335 } else {
336 /* parent process */
337 int child_events_fd = child_stdin[1];
338 int child_commands_fd = child_stdout[0];
339 int child_errors_fd = child_stderr[0];
340 struct ast_frame *f;
341 int ms;
342 int exception;
343 int ready_fd;
344 int waitfds[2] = { child_errors_fd, child_commands_fd };
345 struct ast_channel *rchan;
347 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
349 close(child_stdin[0]);
350 child_stdin[0] = 0;
351 close(child_stdout[1]);
352 child_stdout[1] = 0;
353 close(child_stderr[1]);
354 child_stderr[1] = 0;
356 if (!(child_events = fdopen(child_events_fd, "w"))) {
357 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
358 goto exit;
361 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
362 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
363 goto exit;
366 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
367 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
368 goto exit;
371 test_available_fd = open("/dev/null", O_RDONLY);
373 setvbuf(child_events, NULL, _IONBF, 0);
374 setvbuf(child_commands, NULL, _IONBF, 0);
375 setvbuf(child_errors, NULL, _IONBF, 0);
377 res = 0;
379 while (1) {
380 if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
381 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
382 res = -1;
383 break;
386 if (ast_check_hangup(chan)) {
387 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
388 send_child_event(child_events, 'H', NULL, chan);
389 res = -1;
390 break;
393 ready_fd = 0;
394 ms = 100;
395 errno = 0;
396 exception = 0;
398 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
400 if (!AST_LIST_EMPTY(&u->finishlist)) {
401 AST_LIST_LOCK(&u->finishlist);
402 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
403 send_child_event(child_events, 'F', entry->filename, chan);
404 free(entry);
406 AST_LIST_UNLOCK(&u->finishlist);
409 if (rchan) {
410 /* the channel has something */
411 f = ast_read(chan);
412 if (!f) {
413 ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
414 send_child_event(child_events, 'H', NULL, chan);
415 res = -1;
416 break;
419 if (f->frametype == AST_FRAME_DTMF) {
420 send_child_event(child_events, f->subclass, NULL, chan);
421 if (u->option_autoclear) {
422 if (!u->abort_current_sound && !u->playing_silence)
423 send_child_event(child_events, 'T', NULL, chan);
424 AST_LIST_LOCK(&u->playlist);
425 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
426 send_child_event(child_events, 'D', entry->filename, chan);
427 free(entry);
429 if (!u->playing_silence)
430 u->abort_current_sound = 1;
431 AST_LIST_UNLOCK(&u->playlist);
433 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
434 ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
435 send_child_event(child_events, 'H', NULL, chan);
436 ast_frfree(f);
437 res = -1;
438 break;
440 ast_frfree(f);
441 } else if (ready_fd == child_commands_fd) {
442 char input[1024];
444 if (exception || feof(child_commands)) {
445 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
446 res = -1;
447 break;
450 if (!fgets(input, sizeof(input), child_commands))
451 continue;
453 command = ast_strip(input);
455 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
457 if (strlen(input) < 4)
458 continue;
460 if (input[0] == 'S') {
461 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
462 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
463 send_child_event(child_events, 'Z', NULL, chan);
464 strcpy(&input[2], "exception");
466 if (!u->abort_current_sound && !u->playing_silence)
467 send_child_event(child_events, 'T', NULL, chan);
468 AST_LIST_LOCK(&u->playlist);
469 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
470 send_child_event(child_events, 'D', entry->filename, chan);
471 free(entry);
473 if (!u->playing_silence)
474 u->abort_current_sound = 1;
475 entry = make_entry(&input[2]);
476 if (entry)
477 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
478 AST_LIST_UNLOCK(&u->playlist);
479 } else if (input[0] == 'A') {
480 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
481 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
482 send_child_event(child_events, 'Z', NULL, chan);
483 strcpy(&input[2], "exception");
485 entry = make_entry(&input[2]);
486 if (entry) {
487 AST_LIST_LOCK(&u->playlist);
488 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
489 AST_LIST_UNLOCK(&u->playlist);
491 } else if (input[0] == 'H') {
492 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
493 send_child_event(child_events, 'H', NULL, chan);
494 break;
495 } else if (input[0] == 'O') {
496 if (!strcasecmp(&input[2], "autoclear"))
497 u->option_autoclear = 1;
498 else if (!strcasecmp(&input[2], "noautoclear"))
499 u->option_autoclear = 0;
500 else
501 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
503 } else if (ready_fd == child_errors_fd) {
504 char input[1024];
506 if (exception || (dup2(child_commands_fd, test_available_fd) == -1) || feof(child_errors)) {
507 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
508 res = -1;
509 break;
512 if (fgets(input, sizeof(input), child_errors)) {
513 command = ast_strip(input);
514 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
516 } else if ((ready_fd < 0) && ms) {
517 if (errno == 0 || errno == EINTR)
518 continue;
520 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
521 break;
526 exit:
527 if (gen_active)
528 ast_deactivate_generator(chan);
530 if (child_events)
531 fclose(child_events);
533 if (child_commands)
534 fclose(child_commands);
536 if (child_errors)
537 fclose(child_errors);
539 if (test_available_fd > -1) {
540 close(test_available_fd);
543 if (child_stdin[0])
544 close(child_stdin[0]);
546 if (child_stdin[1])
547 close(child_stdin[1]);
549 if (child_stdout[0])
550 close(child_stdout[0]);
552 if (child_stdout[1])
553 close(child_stdout[1]);
555 if (child_stderr[0])
556 close(child_stderr[0]);
558 if (child_stderr[1])
559 close(child_stderr[1]);
561 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
562 free(entry);
564 ast_module_user_remove(lu);
566 return res;
569 static int unload_module(void)
571 int res;
573 res = ast_unregister_application(app);
575 ast_module_user_hangup_all();
577 return res;
580 static int load_module(void)
582 return ast_register_application(app, app_exec, synopsis, descrip);
585 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");