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