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