Merged revisions 45692 via svnmerge from
[asterisk/asterisk.git] / apps / app_externalivr.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Kevin P. Fleming <kpfleming@digium.com>
7  *
8  * Portions taken from the file-based music-on-hold work
9  * created by Anthony Minessale II in res_musiconhold.c
10  *
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.
16  *
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.
20  */
21
22 /*! \file
23  *
24  * \brief External IVR application interface
25  *
26  * \author Kevin P. Fleming <kpfleming@digium.com>
27  *
28  * \note Portions taken from the file-based music-on-hold work
29  * created by Anthony Minessale II in res_musiconhold.c
30  *
31  * \ingroup applications
32  */
33
34 #include "asterisk.h"
35
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
37
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43
44 #include "asterisk/lock.h"
45 #include "asterisk/file.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/pbx.h"
49 #include "asterisk/module.h"
50 #include "asterisk/linkedlists.h"
51 #include "asterisk/app.h"
52 #include "asterisk/utils.h"
53 #include "asterisk/options.h"
54
55 static const char *app = "ExternalIVR";
56
57 static const char *synopsis = "Interfaces with an external IVR application";
58
59 static const char *descrip = 
60 "  ExternalIVR(command[|arg[|arg...]]): Forks an process to run the supplied command,\n"
61 "and starts a generator on the channel. The generator's play list is\n"
62 "controlled by the external application, which can add and clear entries\n"
63 "via simple commands issued over its stdout. The external application\n"
64 "will receive all DTMF events received on the channel, and notification\n"
65 "if the channel is hung up. The application will not be forcibly terminated\n"
66 "when the channel is hung up.\n"
67 "See doc/externalivr.txt for a protocol specification.\n";
68
69 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
70 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
71
72 struct playlist_entry {
73         AST_LIST_ENTRY(playlist_entry) list;
74         char filename[1];
75 };
76
77 struct ivr_localuser {
78         struct ast_channel *chan;
79         AST_LIST_HEAD(playlist, playlist_entry) playlist;
80         AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
81         int abort_current_sound;
82         int playing_silence;
83         int option_autoclear;
84 };
85
86
87 struct gen_state {
88         struct ivr_localuser *u;
89         struct ast_filestream *stream;
90         struct playlist_entry *current;
91         int sample_queue;
92 };
93
94 static void send_child_event(FILE *handle, const char event, const char *data,
95                              const struct ast_channel *chan)
96 {
97         char tmp[256];
98
99         if (!data) {
100                 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
101         } else {
102                 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
103         }
104
105         fprintf(handle, "%s\n", tmp);
106         if (option_debug)
107                 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
108 }
109
110 static void *gen_alloc(struct ast_channel *chan, void *params)
111 {
112         struct ivr_localuser *u = params;
113         struct gen_state *state;
114         
115         if (!(state = ast_calloc(1, sizeof(*state))))
116                 return NULL;
117
118         state->u = u;
119
120         return state;
121 }
122
123 static void gen_closestream(struct gen_state *state)
124 {
125         if (!state->stream)
126                 return;
127
128         ast_closestream(state->stream);
129         state->u->chan->stream = NULL;
130         state->stream = NULL;
131 }
132
133 static void gen_release(struct ast_channel *chan, void *data)
134 {
135         struct gen_state *state = data;
136
137         gen_closestream(state);
138         free(data);
139 }
140
141 /* caller has the playlist locked */
142 static int gen_nextfile(struct gen_state *state)
143 {
144         struct ivr_localuser *u = state->u;
145         char *file_to_stream;
146         
147         u->abort_current_sound = 0;
148         u->playing_silence = 0;
149         gen_closestream(state);
150
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;
158                 }
159
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;
166                         }
167                 }
168         }
169
170         return (!state->stream);
171 }
172
173 static struct ast_frame *gen_readframe(struct gen_state *state)
174 {
175         struct ast_frame *f = NULL;
176         struct ivr_localuser *u = state->u;
177         
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);
184         }
185
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;
192                 }
193                 if (!gen_nextfile(state))
194                         f = ast_readframe(state->stream);
195         }
196
197         return f;
198 }
199
200 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
201 {
202         struct gen_state *state = data;
203         struct ast_frame *f = NULL;
204         int res = 0;
205
206         state->sample_queue += samples;
207
208         while (state->sample_queue > 0) {
209                 if (!(f = gen_readframe(state)))
210                         return -1;
211
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;
217                 }
218                 state->sample_queue -= f->samples;
219         }
220
221         return res;
222 }
223
224 static struct ast_generator gen =
225 {
226         alloc: gen_alloc,
227         release: gen_release,
228         generate: gen_generate,
229 };
230
231 static struct playlist_entry *make_entry(const char *filename)
232 {
233         struct playlist_entry *entry;
234         
235         if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
236                 return NULL;
237
238         strcpy(entry->filename, filename);
239
240         return entry;
241 }
242
243 static int app_exec(struct ast_channel *chan, void *data)
244 {
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 gen_active = 0;
253         int pid;
254         char *argv[32];
255         int argc = 1;
256         char *buf, *command;
257         FILE *child_commands = NULL;
258         FILE *child_errors = NULL;
259         FILE *child_events = NULL;
260         struct ivr_localuser foo = {
261                 .playlist = AST_LIST_HEAD_INIT_VALUE,
262                 .finishlist = AST_LIST_HEAD_INIT_VALUE,
263         };
264         struct ivr_localuser *u = &foo;
265
266         lu = ast_module_user_add(chan);
267         
268         u->abort_current_sound = 0;
269         u->chan = chan;
270         
271         if (ast_strlen_zero(args)) {
272                 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
273                 ast_module_user_remove(lu);
274                 return -1;      
275         }
276
277         buf = ast_strdupa(data);
278
279         argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
280
281         if (pipe(child_stdin)) {
282                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
283                 goto exit;
284         }
285
286         if (pipe(child_stdout)) {
287                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
288                 goto exit;
289         }
290
291         if (pipe(child_stderr)) {
292                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
293                 goto exit;
294         }
295
296         if (chan->_state != AST_STATE_UP) {
297                 ast_answer(chan);
298         }
299
300         if (ast_activate_generator(chan, &gen, u) < 0) {
301                 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
302                 goto exit;
303         } else
304                 gen_active = 1;
305
306         pid = fork();
307         if (pid < 0) {
308                 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
309                 goto exit;
310         }
311
312         if (!pid) {
313                 /* child process */
314                 int i;
315
316                 if (ast_opt_high_priority)
317                         ast_set_priority(0);
318
319                 dup2(child_stdin[0], STDIN_FILENO);
320                 dup2(child_stdout[1], STDOUT_FILENO);
321                 dup2(child_stderr[1], STDERR_FILENO);
322                 for (i = STDERR_FILENO + 1; i < 1024; i++)
323                         close(i);
324                 execv(argv[0], argv);
325                 fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
326                 exit(1);
327         } else {
328                 /* parent process */
329                 int child_events_fd = child_stdin[1];
330                 int child_commands_fd = child_stdout[0];
331                 int child_errors_fd = child_stderr[0];
332                 struct ast_frame *f;
333                 int ms;
334                 int exception;
335                 int ready_fd;
336                 int waitfds[2] = { child_errors_fd, child_commands_fd };
337                 struct ast_channel *rchan;
338
339                 close(child_stdin[0]);
340                 child_stdin[0] = 0;
341                 close(child_stdout[1]);
342                 child_stdout[1] = 0;
343                 close(child_stderr[1]);
344                 child_stderr[1] = 0;
345
346                 if (!(child_events = fdopen(child_events_fd, "w"))) {
347                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
348                         goto exit;
349                 }
350
351                 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
352                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
353                         goto exit;
354                 }
355
356                 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
357                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
358                         goto exit;
359                 }
360
361                 setvbuf(child_events, NULL, _IONBF, 0);
362                 setvbuf(child_commands, NULL, _IONBF, 0);
363                 setvbuf(child_errors, NULL, _IONBF, 0);
364
365                 res = 0;
366
367                 while (1) {
368                         if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
369                                 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
370                                 res = -1;
371                                 break;
372                         }
373
374                         if (ast_check_hangup(chan)) {
375                                 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
376                                 send_child_event(child_events, 'H', NULL, chan);
377                                 res = -1;
378                                 break;
379                         }
380
381                         ready_fd = 0;
382                         ms = 100;
383                         errno = 0;
384                         exception = 0;
385
386                         rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
387
388                         if (!AST_LIST_EMPTY(&u->finishlist)) {
389                                 AST_LIST_LOCK(&u->finishlist);
390                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
391                                         send_child_event(child_events, 'F', entry->filename, chan);
392                                         free(entry);
393                                 }
394                                 AST_LIST_UNLOCK(&u->finishlist);
395                         }
396
397                         if (rchan) {
398                                 /* the channel has something */
399                                 f = ast_read(chan);
400                                 if (!f) {
401                                         ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
402                                         send_child_event(child_events, 'H', NULL, chan);
403                                         res = -1;
404                                         break;
405                                 }
406
407                                 if (f->frametype == AST_FRAME_DTMF) {
408                                         send_child_event(child_events, f->subclass, NULL, chan);
409                                         if (u->option_autoclear) {
410                                                 if (!u->abort_current_sound && !u->playing_silence)
411                                                         send_child_event(child_events, 'T', NULL, chan);
412                                                 AST_LIST_LOCK(&u->playlist);
413                                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
414                                                         send_child_event(child_events, 'D', entry->filename, chan);
415                                                         free(entry);
416                                                 }
417                                                 if (!u->playing_silence)
418                                                         u->abort_current_sound = 1;
419                                                 AST_LIST_UNLOCK(&u->playlist);
420                                         }
421                                 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
422                                         ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
423                                         send_child_event(child_events, 'H', NULL, chan);
424                                         ast_frfree(f);
425                                         res = -1;
426                                         break;
427                                 }
428                                 ast_frfree(f);
429                         } else if (ready_fd == child_commands_fd) {
430                                 char input[1024];
431
432                                 if (exception || feof(child_commands)) {
433                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
434                                         res = -1;
435                                         break;
436                                 }
437
438                                 if (!fgets(input, sizeof(input), child_commands))
439                                         continue;
440
441                                 command = ast_strip(input);
442
443                                 if (option_debug)
444                                         ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
445
446                                 if (strlen(input) < 4)
447                                         continue;
448
449                                 if (input[0] == 'S') {
450                                         if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
451                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
452                                                 send_child_event(child_events, 'Z', NULL, chan);
453                                                 strcpy(&input[2], "exception");
454                                         }
455                                         if (!u->abort_current_sound && !u->playing_silence)
456                                                 send_child_event(child_events, 'T', NULL, chan);
457                                         AST_LIST_LOCK(&u->playlist);
458                                         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
459                                                 send_child_event(child_events, 'D', entry->filename, chan);
460                                                 free(entry);
461                                         }
462                                         if (!u->playing_silence)
463                                                 u->abort_current_sound = 1;
464                                         entry = make_entry(&input[2]);
465                                         if (entry)
466                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
467                                         AST_LIST_UNLOCK(&u->playlist);
468                                 } else if (input[0] == 'A') {
469                                         if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
470                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
471                                                 send_child_event(child_events, 'Z', NULL, chan);
472                                                 strcpy(&input[2], "exception");
473                                         }
474                                         entry = make_entry(&input[2]);
475                                         if (entry) {
476                                                 AST_LIST_LOCK(&u->playlist);
477                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
478                                                 AST_LIST_UNLOCK(&u->playlist);
479                                         }
480                                 } else if (input[0] == 'H') {
481                                         ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
482                                         send_child_event(child_events, 'H', NULL, chan);
483                                         break;
484                                 } else if (input[0] == 'O') {
485                                         if (!strcasecmp(&input[2], "autoclear"))
486                                                 u->option_autoclear = 1;
487                                         else if (!strcasecmp(&input[2], "noautoclear"))
488                                                 u->option_autoclear = 0;
489                                         else
490                                                 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
491                                 }
492                         } else if (ready_fd == child_errors_fd) {
493                                 char input[1024];
494
495                                 if (exception || feof(child_errors)) {
496                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
497                                         res = -1;
498                                         break;
499                                 }
500
501                                 if (fgets(input, sizeof(input), child_errors)) {
502                                         command = ast_strip(input);
503                                         ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
504                                 }
505                         } else if ((ready_fd < 0) && ms) { 
506                                 if (errno == 0 || errno == EINTR)
507                                         continue;
508
509                                 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
510                                 break;
511                         }
512                 }
513         }
514
515  exit:
516         if (gen_active)
517                 ast_deactivate_generator(chan);
518
519         if (child_events)
520                 fclose(child_events);
521
522         if (child_commands)
523                 fclose(child_commands);
524
525         if (child_errors)
526                 fclose(child_errors);
527
528         if (child_stdin[0])
529                 close(child_stdin[0]);
530
531         if (child_stdin[1])
532                 close(child_stdin[1]);
533
534         if (child_stdout[0])
535                 close(child_stdout[0]);
536
537         if (child_stdout[1])
538                 close(child_stdout[1]);
539
540         if (child_stderr[0])
541                 close(child_stderr[0]);
542
543         if (child_stderr[1])
544                 close(child_stderr[1]);
545
546         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
547                 free(entry);
548
549         ast_module_user_remove(lu);
550
551         return res;
552 }
553
554 static int unload_module(void)
555 {
556         int res;
557
558         res = ast_unregister_application(app);
559
560         ast_module_user_hangup_all();
561
562         return res;
563 }
564
565 static int load_module(void)
566 {
567         return ast_register_application(app, app_exec, synopsis, descrip);
568 }
569
570 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");