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