(closes issue #10381)
[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 playlist_entry *entry;
247         int child_stdin[2] = { 0,0 };
248         int child_stdout[2] = { 0,0 };
249         int child_stderr[2] = { 0,0 };
250         int res = -1;
251         int gen_active = 0;
252         int pid;
253         char *buf, *command;
254         FILE *child_commands = NULL;
255         FILE *child_errors = NULL;
256         FILE *child_events = NULL;
257         struct ivr_localuser foo = {
258                 .playlist = AST_LIST_HEAD_INIT_VALUE,
259                 .finishlist = AST_LIST_HEAD_INIT_VALUE,
260         };
261         struct ivr_localuser *u = &foo;
262         sigset_t fullset, oldset;
263         AST_DECLARE_APP_ARGS(args,
264                 AST_APP_ARG(cmd)[32];
265         );
266
267         sigfillset(&fullset);
268         pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
269
270         u->abort_current_sound = 0;
271         u->chan = chan;
272         
273         if (ast_strlen_zero(data)) {
274                 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
275                 return -1;      
276         }
277
278         buf = ast_strdupa(data);
279         AST_STANDARD_APP_ARGS(args, buf);
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                 signal(SIGPIPE, SIG_DFL);
317                 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
318
319                 if (ast_opt_high_priority)
320                         ast_set_priority(0);
321
322                 dup2(child_stdin[0], STDIN_FILENO);
323                 dup2(child_stdout[1], STDOUT_FILENO);
324                 dup2(child_stderr[1], STDERR_FILENO);
325                 for (i = STDERR_FILENO + 1; i < 1024; i++)
326                         close(i);
327                 execv(args.cmd[0], args.cmd);
328                 fprintf(stderr, "Failed to execute '%s': %s\n", args.cmd[0], strerror(errno));
329                 _exit(1);
330         } else {
331                 /* parent process */
332                 int child_events_fd = child_stdin[1];
333                 int child_commands_fd = child_stdout[0];
334                 int child_errors_fd = child_stderr[0];
335                 struct ast_frame *f;
336                 int ms;
337                 int exception;
338                 int ready_fd;
339                 int waitfds[2] = { child_errors_fd, child_commands_fd };
340                 struct ast_channel *rchan;
341
342                 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
343
344                 close(child_stdin[0]);
345                 child_stdin[0] = 0;
346                 close(child_stdout[1]);
347                 child_stdout[1] = 0;
348                 close(child_stderr[1]);
349                 child_stderr[1] = 0;
350
351                 if (!(child_events = fdopen(child_events_fd, "w"))) {
352                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
353                         goto exit;
354                 }
355
356                 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
357                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
358                         goto exit;
359                 }
360
361                 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
362                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
363                         goto exit;
364                 }
365
366                 setvbuf(child_events, NULL, _IONBF, 0);
367                 setvbuf(child_commands, NULL, _IONBF, 0);
368                 setvbuf(child_errors, NULL, _IONBF, 0);
369
370                 res = 0;
371
372                 while (1) {
373                         if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
374                                 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
375                                 res = -1;
376                                 break;
377                         }
378
379                         if (ast_check_hangup(chan)) {
380                                 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
381                                 send_child_event(child_events, 'H', NULL, chan);
382                                 res = -1;
383                                 break;
384                         }
385
386                         ready_fd = 0;
387                         ms = 100;
388                         errno = 0;
389                         exception = 0;
390
391                         rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
392
393                         if (!AST_LIST_EMPTY(&u->finishlist)) {
394                                 AST_LIST_LOCK(&u->finishlist);
395                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
396                                         send_child_event(child_events, 'F', entry->filename, chan);
397                                         ast_free(entry);
398                                 }
399                                 AST_LIST_UNLOCK(&u->finishlist);
400                         }
401
402                         if (rchan) {
403                                 /* the channel has something */
404                                 f = ast_read(chan);
405                                 if (!f) {
406                                         ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
407                                         send_child_event(child_events, 'H', NULL, chan);
408                                         res = -1;
409                                         break;
410                                 }
411
412                                 if (f->frametype == AST_FRAME_DTMF) {
413                                         send_child_event(child_events, f->subclass, NULL, chan);
414                                         if (u->option_autoclear) {
415                                                 if (!u->abort_current_sound && !u->playing_silence)
416                                                         send_child_event(child_events, 'T', NULL, chan);
417                                                 AST_LIST_LOCK(&u->playlist);
418                                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
419                                                         send_child_event(child_events, 'D', entry->filename, chan);
420                                                         ast_free(entry);
421                                                 }
422                                                 if (!u->playing_silence)
423                                                         u->abort_current_sound = 1;
424                                                 AST_LIST_UNLOCK(&u->playlist);
425                                         }
426                                 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
427                                         ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
428                                         send_child_event(child_events, 'H', NULL, chan);
429                                         ast_frfree(f);
430                                         res = -1;
431                                         break;
432                                 }
433                                 ast_frfree(f);
434                         } else if (ready_fd == child_commands_fd) {
435                                 char input[1024];
436
437                                 if (exception || feof(child_commands)) {
438                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
439                                         res = -1;
440                                         break;
441                                 }
442
443                                 if (!fgets(input, sizeof(input), child_commands))
444                                         continue;
445
446                                 command = ast_strip(input);
447
448                                 if (option_debug)
449                                         ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
450
451                                 if (strlen(input) < 4)
452                                         continue;
453
454                                 if (input[0] == 'S') {
455                                         if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
456                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
457                                                 send_child_event(child_events, 'Z', NULL, chan);
458                                                 strcpy(&input[2], "exception");
459                                         }
460                                         if (!u->abort_current_sound && !u->playing_silence)
461                                                 send_child_event(child_events, 'T', NULL, chan);
462                                         AST_LIST_LOCK(&u->playlist);
463                                         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
464                                                 send_child_event(child_events, 'D', entry->filename, chan);
465                                                 ast_free(entry);
466                                         }
467                                         if (!u->playing_silence)
468                                                 u->abort_current_sound = 1;
469                                         entry = make_entry(&input[2]);
470                                         if (entry)
471                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
472                                         AST_LIST_UNLOCK(&u->playlist);
473                                 } else if (input[0] == 'A') {
474                                         if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
475                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
476                                                 send_child_event(child_events, 'Z', NULL, chan);
477                                                 strcpy(&input[2], "exception");
478                                         }
479                                         entry = make_entry(&input[2]);
480                                         if (entry) {
481                                                 AST_LIST_LOCK(&u->playlist);
482                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
483                                                 AST_LIST_UNLOCK(&u->playlist);
484                                         }
485                                 } else if (input[0] == 'E') {
486                                         ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
487                                         send_child_event(child_events, 'E', NULL, chan);
488                                         res = 0;
489                                         break;
490                                 } else if (input[0] == 'H') {
491                                         ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
492                                         send_child_event(child_events, 'H', NULL, chan);
493                                         res = -1;
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]);
502                                 } else if (input[0] == 'V') {
503                                         char *c;
504                                         c = strchr(&input[2], '=');
505                                         if (!c) {
506                                                 send_child_event(child_events, 'Z', NULL, chan);
507                                         } else {
508                                                 *c++ = '\0';
509                                                 pbx_builtin_setvar_helper(chan, &input[2], c);
510                                         }
511                                 }
512                         } else if (ready_fd == child_errors_fd) {
513                                 char input[1024];
514
515                                 if (exception || feof(child_errors)) {
516                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
517                                         res = -1;
518                                         break;
519                                 }
520
521                                 if (fgets(input, sizeof(input), child_errors)) {
522                                         command = ast_strip(input);
523                                         ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
524                                 }
525                         } else if ((ready_fd < 0) && ms) { 
526                                 if (errno == 0 || errno == EINTR)
527                                         continue;
528
529                                 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
530                                 break;
531                         }
532                 }
533         }
534
535  exit:
536         if (gen_active)
537                 ast_deactivate_generator(chan);
538
539         if (child_events)
540                 fclose(child_events);
541
542         if (child_commands)
543                 fclose(child_commands);
544
545         if (child_errors)
546                 fclose(child_errors);
547
548         if (child_stdin[0])
549                 close(child_stdin[0]);
550
551         if (child_stdin[1])
552                 close(child_stdin[1]);
553
554         if (child_stdout[0])
555                 close(child_stdout[0]);
556
557         if (child_stdout[1])
558                 close(child_stdout[1]);
559
560         if (child_stderr[0])
561                 close(child_stderr[0]);
562
563         if (child_stderr[1])
564                 close(child_stderr[1]);
565
566         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
567                 ast_free(entry);
568
569         return res;
570 }
571
572 static int unload_module(void)
573 {
574         return ast_unregister_application(app);
575 }
576
577 static int load_module(void)
578 {
579         return ast_register_application(app, app_exec, synopsis, descrip);
580 }
581
582 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");