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