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