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