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