e5778edba54444e96d3841c95f46927b8fc229a9
[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 #include <signal.h>
44
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"
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
88 struct gen_state {
89         struct ivr_localuser *u;
90         struct ast_filestream *stream;
91         struct playlist_entry *current;
92         int sample_queue;
93 };
94
95 static void send_child_event(FILE *handle, const char event, const char *data,
96                              const struct ast_channel *chan)
97 {
98         char tmp[256];
99
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);
104         }
105
106         fprintf(handle, "%s\n", tmp);
107         if (option_debug)
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         ast_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 ast_module_user *lu;
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 = {
262                 .playlist = AST_LIST_HEAD_INIT_VALUE,
263                 .finishlist = AST_LIST_HEAD_INIT_VALUE,
264         };
265         struct ivr_localuser *u = &foo;
266         sigset_t fullset, oldset;
267
268         lu = ast_module_user_add(chan);
269
270         sigfillset(&fullset);
271         pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
272
273         u->abort_current_sound = 0;
274         u->chan = chan;
275         
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;      
280         }
281
282         buf = ast_strdupa(data);
283
284         argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
285
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;
289         }
290
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;
294         }
295
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;
299         }
300
301         if (chan->_state != AST_STATE_UP) {
302                 ast_answer(chan);
303         }
304
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;
310
311         pid = fork();
312         if (pid < 0) {
313                 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
314                 goto exit;
315         }
316
317         if (!pid) {
318                 /* child process */
319                 int i;
320
321                 signal(SIGPIPE, SIG_DFL);
322                 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
323
324                 if (ast_opt_high_priority)
325                         ast_set_priority(0);
326
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;
346
347                 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
348
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;
355
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;
359                 }
360
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;
364                 }
365
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;
369                 }
370
371                 setvbuf(child_events, NULL, _IONBF, 0);
372                 setvbuf(child_commands, NULL, _IONBF, 0);
373                 setvbuf(child_errors, NULL, _IONBF, 0);
374
375                 res = 0;
376
377                 while (1) {
378                         if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
379                                 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
380                                 res = -1;
381                                 break;
382                         }
383
384                         if (ast_check_hangup(chan)) {
385                                 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
386                                 send_child_event(child_events, 'H', NULL, chan);
387                                 res = -1;
388                                 break;
389                         }
390
391                         ready_fd = 0;
392                         ms = 100;
393                         errno = 0;
394                         exception = 0;
395
396                         rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
397
398                         if (!AST_LIST_EMPTY(&u->finishlist)) {
399                                 AST_LIST_LOCK(&u->finishlist);
400                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
401                                         send_child_event(child_events, 'F', entry->filename, chan);
402                                         ast_free(entry);
403                                 }
404                                 AST_LIST_UNLOCK(&u->finishlist);
405                         }
406
407                         if (rchan) {
408                                 /* the channel has something */
409                                 f = ast_read(chan);
410                                 if (!f) {
411                                         ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
412                                         send_child_event(child_events, 'H', NULL, chan);
413                                         res = -1;
414                                         break;
415                                 }
416
417                                 if (f->frametype == AST_FRAME_DTMF) {
418                                         send_child_event(child_events, f->subclass, NULL, chan);
419                                         if (u->option_autoclear) {
420                                                 if (!u->abort_current_sound && !u->playing_silence)
421                                                         send_child_event(child_events, 'T', NULL, chan);
422                                                 AST_LIST_LOCK(&u->playlist);
423                                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
424                                                         send_child_event(child_events, 'D', entry->filename, chan);
425                                                         ast_free(entry);
426                                                 }
427                                                 if (!u->playing_silence)
428                                                         u->abort_current_sound = 1;
429                                                 AST_LIST_UNLOCK(&u->playlist);
430                                         }
431                                 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
432                                         ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
433                                         send_child_event(child_events, 'H', NULL, chan);
434                                         ast_frfree(f);
435                                         res = -1;
436                                         break;
437                                 }
438                                 ast_frfree(f);
439                         } else if (ready_fd == child_commands_fd) {
440                                 char input[1024];
441
442                                 if (exception || feof(child_commands)) {
443                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
444                                         res = -1;
445                                         break;
446                                 }
447
448                                 if (!fgets(input, sizeof(input), child_commands))
449                                         continue;
450
451                                 command = ast_strip(input);
452
453                                 if (option_debug)
454                                         ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
455
456                                 if (strlen(input) < 4)
457                                         continue;
458
459                                 if (input[0] == 'S') {
460                                         if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
461                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
462                                                 send_child_event(child_events, 'Z', NULL, chan);
463                                                 strcpy(&input[2], "exception");
464                                         }
465                                         if (!u->abort_current_sound && !u->playing_silence)
466                                                 send_child_event(child_events, 'T', NULL, chan);
467                                         AST_LIST_LOCK(&u->playlist);
468                                         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
469                                                 send_child_event(child_events, 'D', entry->filename, chan);
470                                                 ast_free(entry);
471                                         }
472                                         if (!u->playing_silence)
473                                                 u->abort_current_sound = 1;
474                                         entry = make_entry(&input[2]);
475                                         if (entry)
476                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
477                                         AST_LIST_UNLOCK(&u->playlist);
478                                 } else if (input[0] == 'A') {
479                                         if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
480                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
481                                                 send_child_event(child_events, 'Z', NULL, chan);
482                                                 strcpy(&input[2], "exception");
483                                         }
484                                         entry = make_entry(&input[2]);
485                                         if (entry) {
486                                                 AST_LIST_LOCK(&u->playlist);
487                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
488                                                 AST_LIST_UNLOCK(&u->playlist);
489                                         }
490                                 } else if (input[0] == 'E') {
491                                         ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
492                                         send_child_event(child_events, 'E', NULL, chan);
493                                         res = 0;
494                                         break;
495                                 } else if (input[0] == 'H') {
496                                         ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
497                                         send_child_event(child_events, 'H', NULL, chan);
498                                         res = -1;
499                                         break;
500                                 } else if (input[0] == 'O') {
501                                         if (!strcasecmp(&input[2], "autoclear"))
502                                                 u->option_autoclear = 1;
503                                         else if (!strcasecmp(&input[2], "noautoclear"))
504                                                 u->option_autoclear = 0;
505                                         else
506                                                 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
507                                 } else if (input[0] == 'V') {
508                                         char *c;
509                                         c = strchr(&input[2], '=');
510                                         if (!c) {
511                                                 send_child_event(child_events, 'Z', NULL, chan);
512                                         } else {
513                                                 *c++ = '\0';
514                                                 pbx_builtin_setvar_helper(chan, &input[2], c);
515                                         }
516                                 }
517                         } else if (ready_fd == child_errors_fd) {
518                                 char input[1024];
519
520                                 if (exception || feof(child_errors)) {
521                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
522                                         res = -1;
523                                         break;
524                                 }
525
526                                 if (fgets(input, sizeof(input), child_errors)) {
527                                         command = ast_strip(input);
528                                         ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
529                                 }
530                         } else if ((ready_fd < 0) && ms) { 
531                                 if (errno == 0 || errno == EINTR)
532                                         continue;
533
534                                 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
535                                 break;
536                         }
537                 }
538         }
539
540  exit:
541         if (gen_active)
542                 ast_deactivate_generator(chan);
543
544         if (child_events)
545                 fclose(child_events);
546
547         if (child_commands)
548                 fclose(child_commands);
549
550         if (child_errors)
551                 fclose(child_errors);
552
553         if (child_stdin[0])
554                 close(child_stdin[0]);
555
556         if (child_stdin[1])
557                 close(child_stdin[1]);
558
559         if (child_stdout[0])
560                 close(child_stdout[0]);
561
562         if (child_stdout[1])
563                 close(child_stdout[1]);
564
565         if (child_stderr[0])
566                 close(child_stderr[0]);
567
568         if (child_stderr[1])
569                 close(child_stderr[1]);
570
571         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
572                 ast_free(entry);
573
574         ast_module_user_remove(lu);
575
576         return res;
577 }
578
579 static int unload_module(void)
580 {
581         return ast_unregister_application(app);
582 }
583
584 static int load_module(void)
585 {
586         return ast_register_application(app, app_exec, synopsis, descrip);
587 }
588
589 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");