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