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