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