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