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