don't re-define the localuser struct for custom use inside the module (issue #6216)
[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 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 LOCAL_USER_DECL;
88 STANDARD_LOCAL_USER;
89
90 struct gen_state {
91         struct ivr_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 ivr_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 ivr_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 ivr_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))) /* XXX why 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 *lu = 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         struct ivr_localuser foo, *u = &foo;
263
264         bzero(u, sizeof(*u));
265
266         LOCAL_USER_ADD(lu);
267         
268         AST_LIST_HEAD_INIT(&u->playlist);
269         AST_LIST_HEAD_INIT(&u->finishlist);
270         u->abort_current_sound = 0;
271         
272         if (ast_strlen_zero(args)) {
273                 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
274                 LOCAL_USER_REMOVE(lu);
275                 return -1;      
276         }
277
278         if (!(buf = ast_strdupa(data))) {
279                 LOCAL_USER_REMOVE(lu);
280                 return -1;
281         }
282
283         argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
284
285         if (pipe(child_stdin)) {
286                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
287                 goto exit;
288         }
289
290         if (pipe(child_stdout)) {
291                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
292                 goto exit;
293         }
294
295         if (pipe(child_stderr)) {
296                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
297                 goto exit;
298         }
299
300         if (chan->_state != AST_STATE_UP) {
301                 ast_answer(chan);
302         }
303
304         if (ast_activate_generator(chan, &gen, u) < 0) {
305                 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
306                 goto exit;
307         } else
308                 gen_active = 1;
309
310         pid = fork();
311         if (pid < 0) {
312                 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
313                 goto exit;
314         }
315
316         if (!pid) {
317                 /* child process */
318                 int i;
319
320                 dup2(child_stdin[0], STDIN_FILENO);
321                 dup2(child_stdout[1], STDOUT_FILENO);
322                 dup2(child_stderr[1], STDERR_FILENO);
323                 for (i = STDERR_FILENO + 1; i < 1024; i++)
324                         close(i);
325                 execv(argv[0], argv);
326                 fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
327                 exit(1);
328         } else {
329                 /* parent process */
330                 int child_events_fd = child_stdin[1];
331                 int child_commands_fd = child_stdout[0];
332                 int child_errors_fd = child_stderr[0];
333                 struct ast_frame *f;
334                 int ms;
335                 int exception;
336                 int ready_fd;
337                 int waitfds[2] = { child_errors_fd, child_commands_fd };
338                 struct ast_channel *rchan;
339
340                 close(child_stdin[0]);
341                 child_stdin[0] = 0;
342                 close(child_stdout[1]);
343                 child_stdout[1] = 0;
344                 close(child_stderr[1]);
345                 child_stderr[1] = 0;
346
347                 if (!(child_events = fdopen(child_events_fd, "w"))) {
348                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
349                         goto exit;
350                 }
351
352                 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
353                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
354                         goto exit;
355                 }
356
357                 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
358                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
359                         goto exit;
360                 }
361
362                 setvbuf(child_events, NULL, _IONBF, 0);
363                 setvbuf(child_commands, NULL, _IONBF, 0);
364                 setvbuf(child_errors, NULL, _IONBF, 0);
365
366                 res = 0;
367
368                 while (1) {
369                         if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
370                                 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
371                                 res = -1;
372                                 break;
373                         }
374
375                         if (ast_check_hangup(chan)) {
376                                 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
377                                 send_child_event(child_events, 'H', NULL, chan);
378                                 res = -1;
379                                 break;
380                         }
381
382                         ready_fd = 0;
383                         ms = 100;
384                         errno = 0;
385                         exception = 0;
386
387                         rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
388
389                         if (!AST_LIST_EMPTY(&u->finishlist)) {
390                                 AST_LIST_LOCK(&u->finishlist);
391                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
392                                         send_child_event(child_events, 'F', entry->filename, chan);
393                                         free(entry);
394                                 }
395                                 AST_LIST_UNLOCK(&u->finishlist);
396                         }
397
398                         if (rchan) {
399                                 /* the channel has something */
400                                 f = ast_read(chan);
401                                 if (!f) {
402                                         ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
403                                         send_child_event(child_events, 'H', NULL, chan);
404                                         res = -1;
405                                         break;
406                                 }
407
408                                 if (f->frametype == AST_FRAME_DTMF) {
409                                         send_child_event(child_events, f->subclass, NULL, chan);
410                                         if (u->option_autoclear) {
411                                                 if (!u->abort_current_sound && !u->playing_silence)
412                                                         send_child_event(child_events, 'T', NULL, chan);
413                                                 AST_LIST_LOCK(&u->playlist);
414                                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
415                                                         send_child_event(child_events, 'D', entry->filename, chan);
416                                                         free(entry);
417                                                 }
418                                                 if (!u->playing_silence)
419                                                         u->abort_current_sound = 1;
420                                                 AST_LIST_UNLOCK(&u->playlist);
421                                         }
422                                 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
423                                         ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
424                                         send_child_event(child_events, 'H', NULL, chan);
425                                         ast_frfree(f);
426                                         res = -1;
427                                         break;
428                                 }
429                                 ast_frfree(f);
430                         } else if (ready_fd == child_commands_fd) {
431                                 char input[1024];
432
433                                 if (exception || feof(child_commands)) {
434                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
435                                         res = -1;
436                                         break;
437                                 }
438
439                                 if (!fgets(input, sizeof(input), child_commands))
440                                         continue;
441
442                                 command = ast_strip(input);
443
444                                 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
445
446                                 if (strlen(input) < 4)
447                                         continue;
448
449                                 if (input[0] == 'S') {
450                                         if (ast_fileexists(&input[2], NULL, NULL) == -1) {
451                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
452                                                 send_child_event(child_events, 'Z', NULL, chan);
453                                                 strcpy(&input[2], "exception");
454                                         }
455                                         if (!u->abort_current_sound && !u->playing_silence)
456                                                 send_child_event(child_events, 'T', NULL, chan);
457                                         AST_LIST_LOCK(&u->playlist);
458                                         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
459                                                 send_child_event(child_events, 'D', entry->filename, chan);
460                                                 free(entry);
461                                         }
462                                         if (!u->playing_silence)
463                                                 u->abort_current_sound = 1;
464                                         entry = make_entry(&input[2]);
465                                         if (entry)
466                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
467                                         AST_LIST_UNLOCK(&u->playlist);
468                                 } else if (input[0] == 'A') {
469                                         if (ast_fileexists(&input[2], NULL, NULL) == -1) {
470                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
471                                                 send_child_event(child_events, 'Z', NULL, chan);
472                                                 strcpy(&input[2], "exception");
473                                         }
474                                         entry = make_entry(&input[2]);
475                                         if (entry) {
476                                                 AST_LIST_LOCK(&u->playlist);
477                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
478                                                 AST_LIST_UNLOCK(&u->playlist);
479                                         }
480                                 } else if (input[0] == 'H') {
481                                         ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
482                                         send_child_event(child_events, 'H', NULL, chan);
483                                         break;
484                                 } else if (input[0] == 'O') {
485                                         if (!strcasecmp(&input[2], "autoclear"))
486                                                 u->option_autoclear = 1;
487                                         else if (!strcasecmp(&input[2], "noautoclear"))
488                                                 u->option_autoclear = 0;
489                                         else
490                                                 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
491                                 }
492                         } else if (ready_fd == child_errors_fd) {
493                                 char input[1024];
494
495                                 if (exception || feof(child_errors)) {
496                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
497                                         res = -1;
498                                         break;
499                                 }
500
501                                 if (fgets(input, sizeof(input), child_errors)) {
502                                         command = ast_strip(input);
503                                         ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
504                                 }
505                         } else if ((ready_fd < 0) && ms) { 
506                                 if (errno == 0 || errno == EINTR)
507                                         continue;
508
509                                 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
510                                 break;
511                         }
512                 }
513         }
514
515  exit:
516         if (gen_active)
517                 ast_deactivate_generator(chan);
518
519         if (child_events)
520                 fclose(child_events);
521
522         if (child_commands)
523                 fclose(child_commands);
524
525         if (child_errors)
526                 fclose(child_errors);
527
528         if (child_stdin[0])
529                 close(child_stdin[0]);
530
531         if (child_stdin[1])
532                 close(child_stdin[1]);
533
534         if (child_stdout[0])
535                 close(child_stdout[0]);
536
537         if (child_stdout[1])
538                 close(child_stdout[1]);
539
540         if (child_stderr[0])
541                 close(child_stderr[0]);
542
543         if (child_stderr[1])
544                 close(child_stderr[1]);
545
546         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
547                 free(entry);
548
549         LOCAL_USER_REMOVE(lu);
550
551         return res;
552 }
553
554 int unload_module(void)
555 {
556         int res;
557
558         res = ast_unregister_application(app);
559
560         STANDARD_HANGUP_LOCALUSERS;
561
562         return res;
563 }
564
565 int load_module(void)
566 {
567         return ast_register_application(app, app_exec, synopsis, descrip);
568 }
569
570 char *description(void)
571 {
572         return (char *) tdesc;
573 }
574
575 int usecount(void)
576 {
577         int res;
578
579         STANDARD_USECOUNT(res);
580
581         return res;
582 }
583
584 char *key()
585 {
586         return ASTERISK_GPL_KEY;
587 }