Merged revisions 24019 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 <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <errno.h>
39
40 #include "asterisk.h"
41
42 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
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
56 static const char *app = "ExternalIVR";
57
58 static const char *synopsis = "Interfaces with an external IVR application";
59
60 static const char *descrip = 
61 "  ExternalIVR(command[|arg[|arg...]]): Forks an 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";
69
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__)
72
73 struct playlist_entry {
74         AST_LIST_ENTRY(playlist_entry) list;
75         char filename[1];
76 };
77
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;
85 };
86
87 LOCAL_USER_DECL;
88
89 struct gen_state {
90         struct ivr_localuser *u;
91         struct ast_filestream *stream;
92         struct playlist_entry *current;
93         int sample_queue;
94 };
95
96 static void send_child_event(FILE *handle, const char event, const char *data,
97                              const struct ast_channel *chan)
98 {
99         char tmp[256];
100
101         if (!data) {
102                 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
103         } else {
104                 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
105         }
106
107         fprintf(handle, "%s\n", tmp);
108         ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
109 }
110
111 static void *gen_alloc(struct ast_channel *chan, void *params)
112 {
113         struct ivr_localuser *u = params;
114         struct gen_state *state;
115         
116         if (!(state = ast_calloc(1, sizeof(*state))))
117                 return NULL;
118
119         state->u = u;
120
121         return state;
122 }
123
124 static void gen_closestream(struct gen_state *state)
125 {
126         if (!state->stream)
127                 return;
128
129         ast_closestream(state->stream);
130         state->u->chan->stream = NULL;
131         state->stream = NULL;
132 }
133
134 static void gen_release(struct ast_channel *chan, void *data)
135 {
136         struct gen_state *state = data;
137
138         gen_closestream(state);
139         free(data);
140 }
141
142 /* caller has the playlist locked */
143 static int gen_nextfile(struct gen_state *state)
144 {
145         struct ivr_localuser *u = state->u;
146         char *file_to_stream;
147         
148         u->abort_current_sound = 0;
149         u->playing_silence = 0;
150         gen_closestream(state);
151
152         while (!state->stream) {
153                 state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
154                 if (state->current) {
155                         file_to_stream = state->current->filename;
156                 } else {
157                         file_to_stream = "silence-10";
158                         u->playing_silence = 1;
159                 }
160
161                 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
162                         ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
163                         if (!u->playing_silence) {
164                                 continue;
165                         } else { 
166                                 break;
167                         }
168                 }
169         }
170
171         return (!state->stream);
172 }
173
174 static struct ast_frame *gen_readframe(struct gen_state *state)
175 {
176         struct ast_frame *f = NULL;
177         struct ivr_localuser *u = state->u;
178         
179         if (u->abort_current_sound ||
180             (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
181                 gen_closestream(state);
182                 AST_LIST_LOCK(&u->playlist);
183                 gen_nextfile(state);
184                 AST_LIST_UNLOCK(&u->playlist);
185         }
186
187         if (!(state->stream && (f = ast_readframe(state->stream)))) {
188                 if (state->current) {
189                         AST_LIST_LOCK(&u->finishlist);
190                         AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
191                         AST_LIST_UNLOCK(&u->finishlist);
192                         state->current = NULL;
193                 }
194                 if (!gen_nextfile(state))
195                         f = ast_readframe(state->stream);
196         }
197
198         return f;
199 }
200
201 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
202 {
203         struct gen_state *state = data;
204         struct ast_frame *f = NULL;
205         int res = 0;
206
207         state->sample_queue += samples;
208
209         while (state->sample_queue > 0) {
210                 if (!(f = gen_readframe(state)))
211                         return -1;
212
213                 res = ast_write(chan, f);
214                 ast_frfree(f);
215                 if (res < 0) {
216                         ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
217                         return -1;
218                 }
219                 state->sample_queue -= f->samples;
220         }
221
222         return res;
223 }
224
225 static struct ast_generator gen =
226 {
227         alloc: gen_alloc,
228         release: gen_release,
229         generate: gen_generate,
230 };
231
232 static struct playlist_entry *make_entry(const char *filename)
233 {
234         struct playlist_entry *entry;
235         
236         if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
237                 return NULL;
238
239         strcpy(entry->filename, filename);
240
241         return entry;
242 }
243
244 static int app_exec(struct ast_channel *chan, void *data)
245 {
246         struct localuser *lu = NULL;
247         struct playlist_entry *entry;
248         const char *args = data;
249         int child_stdin[2] = { 0,0 };
250         int child_stdout[2] = { 0,0 };
251         int child_stderr[2] = { 0,0 };
252         int res = -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, *u = &foo;
262
263         bzero(u, sizeof(*u));
264
265         LOCAL_USER_ADD(lu);
266         
267         AST_LIST_HEAD_INIT(&u->playlist);
268         AST_LIST_HEAD_INIT(&u->finishlist);
269         u->abort_current_sound = 0;
270         u->chan = chan;
271         
272         if (ast_strlen_zero(args)) {
273                 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
274                 LOCAL_USER_REMOVE(lu);
275                 return -1;      
276         }
277
278         if (!(buf = ast_strdupa(data))) {
279                 LOCAL_USER_REMOVE(lu);
280                 return -1;
281         }
282
283         argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
284
285         if (pipe(child_stdin)) {
286                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
287                 goto exit;
288         }
289
290         if (pipe(child_stdout)) {
291                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
292                 goto exit;
293         }
294
295         if (pipe(child_stderr)) {
296                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
297                 goto exit;
298         }
299
300         if (chan->_state != AST_STATE_UP) {
301                 ast_answer(chan);
302         }
303
304         if (ast_activate_generator(chan, &gen, u) < 0) {
305                 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
306                 goto exit;
307         } else
308                 gen_active = 1;
309
310         pid = fork();
311         if (pid < 0) {
312                 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
313                 goto exit;
314         }
315
316         if (!pid) {
317                 /* child process */
318                 int i;
319
320                 if (ast_opt_high_priority)
321                         ast_set_priority(0);
322
323                 dup2(child_stdin[0], STDIN_FILENO);
324                 dup2(child_stdout[1], STDOUT_FILENO);
325                 dup2(child_stderr[1], STDERR_FILENO);
326                 for (i = STDERR_FILENO + 1; i < 1024; i++)
327                         close(i);
328                 execv(argv[0], argv);
329                 fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
330                 exit(1);
331         } else {
332                 /* parent process */
333                 int child_events_fd = child_stdin[1];
334                 int child_commands_fd = child_stdout[0];
335                 int child_errors_fd = child_stderr[0];
336                 struct ast_frame *f;
337                 int ms;
338                 int exception;
339                 int ready_fd;
340                 int waitfds[2] = { child_errors_fd, child_commands_fd };
341                 struct ast_channel *rchan;
342
343                 close(child_stdin[0]);
344                 child_stdin[0] = 0;
345                 close(child_stdout[1]);
346                 child_stdout[1] = 0;
347                 close(child_stderr[1]);
348                 child_stderr[1] = 0;
349
350                 if (!(child_events = fdopen(child_events_fd, "w"))) {
351                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
352                         goto exit;
353                 }
354
355                 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
356                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
357                         goto exit;
358                 }
359
360                 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
361                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
362                         goto exit;
363                 }
364
365                 setvbuf(child_events, NULL, _IONBF, 0);
366                 setvbuf(child_commands, NULL, _IONBF, 0);
367                 setvbuf(child_errors, NULL, _IONBF, 0);
368
369                 res = 0;
370
371                 while (1) {
372                         if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
373                                 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
374                                 res = -1;
375                                 break;
376                         }
377
378                         if (ast_check_hangup(chan)) {
379                                 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
380                                 send_child_event(child_events, 'H', NULL, chan);
381                                 res = -1;
382                                 break;
383                         }
384
385                         ready_fd = 0;
386                         ms = 100;
387                         errno = 0;
388                         exception = 0;
389
390                         rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
391
392                         if (!AST_LIST_EMPTY(&u->finishlist)) {
393                                 AST_LIST_LOCK(&u->finishlist);
394                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
395                                         send_child_event(child_events, 'F', entry->filename, chan);
396                                         free(entry);
397                                 }
398                                 AST_LIST_UNLOCK(&u->finishlist);
399                         }
400
401                         if (rchan) {
402                                 /* the channel has something */
403                                 f = ast_read(chan);
404                                 if (!f) {
405                                         ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
406                                         send_child_event(child_events, 'H', NULL, chan);
407                                         res = -1;
408                                         break;
409                                 }
410
411                                 if (f->frametype == AST_FRAME_DTMF) {
412                                         send_child_event(child_events, f->subclass, NULL, chan);
413                                         if (u->option_autoclear) {
414                                                 if (!u->abort_current_sound && !u->playing_silence)
415                                                         send_child_event(child_events, 'T', NULL, chan);
416                                                 AST_LIST_LOCK(&u->playlist);
417                                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
418                                                         send_child_event(child_events, 'D', entry->filename, chan);
419                                                         free(entry);
420                                                 }
421                                                 if (!u->playing_silence)
422                                                         u->abort_current_sound = 1;
423                                                 AST_LIST_UNLOCK(&u->playlist);
424                                         }
425                                 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
426                                         ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
427                                         send_child_event(child_events, 'H', NULL, chan);
428                                         ast_frfree(f);
429                                         res = -1;
430                                         break;
431                                 }
432                                 ast_frfree(f);
433                         } else if (ready_fd == child_commands_fd) {
434                                 char input[1024];
435
436                                 if (exception || feof(child_commands)) {
437                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
438                                         res = -1;
439                                         break;
440                                 }
441
442                                 if (!fgets(input, sizeof(input), child_commands))
443                                         continue;
444
445                                 command = ast_strip(input);
446
447                                 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
448
449                                 if (strlen(input) < 4)
450                                         continue;
451
452                                 if (input[0] == 'S') {
453                                         if (ast_fileexists(&input[2], NULL, NULL) == -1) {
454                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
455                                                 send_child_event(child_events, 'Z', NULL, chan);
456                                                 strcpy(&input[2], "exception");
457                                         }
458                                         if (!u->abort_current_sound && !u->playing_silence)
459                                                 send_child_event(child_events, 'T', NULL, chan);
460                                         AST_LIST_LOCK(&u->playlist);
461                                         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
462                                                 send_child_event(child_events, 'D', entry->filename, chan);
463                                                 free(entry);
464                                         }
465                                         if (!u->playing_silence)
466                                                 u->abort_current_sound = 1;
467                                         entry = make_entry(&input[2]);
468                                         if (entry)
469                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
470                                         AST_LIST_UNLOCK(&u->playlist);
471                                 } else if (input[0] == 'A') {
472                                         if (ast_fileexists(&input[2], NULL, NULL) == -1) {
473                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
474                                                 send_child_event(child_events, 'Z', NULL, chan);
475                                                 strcpy(&input[2], "exception");
476                                         }
477                                         entry = make_entry(&input[2]);
478                                         if (entry) {
479                                                 AST_LIST_LOCK(&u->playlist);
480                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
481                                                 AST_LIST_UNLOCK(&u->playlist);
482                                         }
483                                 } else if (input[0] == 'H') {
484                                         ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
485                                         send_child_event(child_events, 'H', NULL, chan);
486                                         break;
487                                 } else if (input[0] == 'O') {
488                                         if (!strcasecmp(&input[2], "autoclear"))
489                                                 u->option_autoclear = 1;
490                                         else if (!strcasecmp(&input[2], "noautoclear"))
491                                                 u->option_autoclear = 0;
492                                         else
493                                                 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
494                                 }
495                         } else if (ready_fd == child_errors_fd) {
496                                 char input[1024];
497
498                                 if (exception || feof(child_errors)) {
499                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
500                                         res = -1;
501                                         break;
502                                 }
503
504                                 if (fgets(input, sizeof(input), child_errors)) {
505                                         command = ast_strip(input);
506                                         ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
507                                 }
508                         } else if ((ready_fd < 0) && ms) { 
509                                 if (errno == 0 || errno == EINTR)
510                                         continue;
511
512                                 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
513                                 break;
514                         }
515                 }
516         }
517
518  exit:
519         if (gen_active)
520                 ast_deactivate_generator(chan);
521
522         if (child_events)
523                 fclose(child_events);
524
525         if (child_commands)
526                 fclose(child_commands);
527
528         if (child_errors)
529                 fclose(child_errors);
530
531         if (child_stdin[0])
532                 close(child_stdin[0]);
533
534         if (child_stdin[1])
535                 close(child_stdin[1]);
536
537         if (child_stdout[0])
538                 close(child_stdout[0]);
539
540         if (child_stdout[1])
541                 close(child_stdout[1]);
542
543         if (child_stderr[0])
544                 close(child_stderr[0]);
545
546         if (child_stderr[1])
547                 close(child_stderr[1]);
548
549         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
550                 free(entry);
551
552         LOCAL_USER_REMOVE(lu);
553
554         return res;
555 }
556
557 static int unload_module(void *mod)
558 {
559         int res;
560
561         res = ast_unregister_application(app);
562
563         STANDARD_HANGUP_LOCALUSERS;
564
565         return res;
566 }
567
568 static int load_module(void *mod)
569 {
570         return ast_register_application(app, app_exec, synopsis, descrip);
571 }
572
573 static const char *description(void)
574 {
575         return "External IVR Interface Application";
576 }
577
578 static const char *key(void)
579 {
580         return ASTERISK_GPL_KEY;
581 }
582
583 STD_MOD1;