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