add ExternalIVR() application
[asterisk/asterisk.git] / apps / app_externalivr.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * External IVR application interface
5  * 
6  * Copyright (C) 2005, Digium, Inc.
7  *
8  * Kevin P. Fleming <kpfleming@digium.com>
9  *
10  * Portions taken from the file-based music-on-hold work
11  * created by Anthony Minessale II in res_musiconhold.c
12  *
13  * This program is free software, distributed under the terms of
14  * the GNU General Public License
15  */
16
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <string.h>
20 #include <stdlib.h>
21 #include <errno.h>
22
23 #include "asterisk.h"
24
25 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
26
27 #include "asterisk/lock.h"
28 #include "asterisk/file.h"
29 #include "asterisk/logger.h"
30 #include "asterisk/channel.h"
31 #include "asterisk/pbx.h"
32 #include "asterisk/module.h"
33 #include "asterisk/linkedlists.h"
34
35 static char *tdesc = "External IVR Interface Application";
36
37 static char *app = "ExternalIVR";
38
39 static char *synopsis = "Interfaces with an external IVR application";
40
41 static char *descrip = 
42 "  ExternalIVR(command[|arg[|arg...]]): Forks an process to run the supplied command,\n"
43 "and starts a generator on the channel. The generator's play list is\n"
44 "controlled by the external application, which can add and clear entries\n"
45 "via simple commands issued over its stdout. The external application\n"
46 "will receive all DTMF events received on the channel, and notification\n"
47 "if the channel is hung up. The application will not be forcibly terminated\n"
48 "when the channel is hung up.\n"
49 "See doc/README.externalivr for a protocol specification.\n";
50
51 struct playlist_entry {
52         AST_LIST_ENTRY(playlist_entry) list;
53         char filename[1];
54 };
55
56 struct localuser {
57         struct ast_channel *chan;
58         struct localuser *next;
59         AST_LIST_HEAD(playlist, playlist_entry) playlist;
60         int list_cleared;
61 };
62
63 LOCAL_USER_DECL;
64
65 struct gen_state {
66         struct localuser *u;
67         struct ast_filestream *stream;
68         int sample_queue;
69         int playing_silence;
70 };
71
72 static void *gen_alloc(struct ast_channel *chan, void *params)
73 {
74         struct localuser *u = params;
75         struct gen_state *state;
76
77         state = calloc(1, sizeof(*state));
78
79         if (!state)
80                 return NULL;
81
82         state->u = u;
83
84         return state;
85 }
86
87 static void gen_closestream(struct gen_state *state)
88 {
89         if (!state->stream)
90                 return;
91
92         ast_closestream(state->stream);
93         state->u->chan->stream = NULL;
94         state->stream = NULL;
95 }
96
97 static void gen_release(struct ast_channel *chan, void *data)
98 {
99         struct gen_state *state = data;
100
101         gen_closestream(state);
102         free(data);
103 }
104
105 /* caller has the playlist locked */
106 static int gen_nextfile(struct gen_state *state)
107 {
108         struct playlist_entry *entry;
109         struct localuser *u = state->u;
110         char *file_to_stream;
111         
112         state->u->list_cleared = 0;
113         state->playing_silence = 0;
114         gen_closestream(state);
115
116         while (!state->stream) {
117                 if (AST_LIST_FIRST(&u->playlist))
118                         entry = AST_LIST_REMOVE_HEAD(&u->playlist, list);
119                 else
120                         entry = NULL;
121
122                 if (entry) {
123                         file_to_stream = ast_strdupa(entry->filename);
124                         free(entry);
125                 } else {
126                         file_to_stream = "silence-10";
127                         state->playing_silence = 1;
128                 }
129
130                 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
131                         ast_log(LOG_WARNING, "File '%s' could not be opened for channel '%s': %s\n", file_to_stream, u->chan->name, strerror(errno));
132                         if (!state->playing_silence)
133                                 continue;
134                         else
135                                 break;
136                 }
137         }
138
139         return (!state->stream);
140 }
141
142 static struct ast_frame *gen_readframe(struct gen_state *state)
143 {
144         struct ast_frame *f = NULL;
145         
146         if (state->u->list_cleared ||
147             (state->playing_silence && AST_LIST_FIRST(&state->u->playlist))) {
148                 gen_closestream(state);
149                 AST_LIST_LOCK(&state->u->playlist);
150                 gen_nextfile(state);
151                 AST_LIST_UNLOCK(&state->u->playlist);
152         }
153
154         if (!(state->stream && (f = ast_readframe(state->stream)))) {
155                 if (!gen_nextfile(state))
156                         f = ast_readframe(state->stream);
157         }
158
159         return f;
160 }
161
162 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
163 {
164         struct gen_state *state = data;
165         struct ast_frame *f = NULL;
166         int res = 0;
167
168         state->sample_queue += samples;
169
170         while (state->sample_queue > 0) {
171                 if (!(f = gen_readframe(state)))
172                         return -1;
173
174                 res = ast_write(chan, f);
175                 ast_frfree(f);
176                 if (res < 0) {
177                         ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
178                         return -1;
179                 }
180                 state->sample_queue -= f->samples;
181         }
182
183         return res;
184 }
185
186 static struct ast_generator gen =
187 {
188         alloc: gen_alloc,
189         release: gen_release,
190         generate: gen_generate,
191 };
192
193 static struct playlist_entry *make_entry(const char *filename)
194 {
195         struct playlist_entry *entry;
196
197         entry = calloc(1, sizeof(*entry) + strlen(filename));
198
199         if (!entry)
200                 return NULL;
201
202         strcpy(entry->filename, filename);
203
204         return entry;
205 }
206
207 static int app_exec(struct ast_channel *chan, void *data)
208 {
209         struct localuser *u = NULL;
210         struct playlist_entry *entry;
211         const char *args = data;
212         int child_stdin[2] = { 0,0 };
213         int child_stdout[2] = { 0,0 };
214         int child_stderr[2] = { 0,0 };
215         int res = -1;
216         int gen_active = 0;
217         int pid;
218         char *command;
219         char *argv[32];
220         int argc = 1;
221         char *buf;
222         FILE *child_commands = NULL;
223         FILE *child_errors = NULL;
224         FILE *child_events = NULL;
225
226         if (!args || ast_strlen_zero(args)) {
227                 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
228                 goto exit;
229         }
230
231         buf = ast_strdupa(data);
232         command = strsep(&buf, "|");
233         argv[0] = command;
234         while ((argc < 31) && (argv[argc++] = strsep(&buf, "|")));
235         argv[argc] = NULL;
236
237         LOCAL_USER_ADD(u);
238
239         if (pipe(child_stdin)) {
240                 ast_log(LOG_WARNING, "Could not create pipe for child input on channel '%s': %s\n", chan->name, strerror(errno));
241                 goto exit;
242         }
243
244         if (pipe(child_stdout)) {
245                 ast_log(LOG_WARNING, "Could not create pipe for child output on channel '%s': %s\n", chan->name, strerror(errno));
246                 goto exit;
247         }
248
249         if (pipe(child_stderr)) {
250                 ast_log(LOG_WARNING, "Could not create pipe for child errors on channel '%s': %s\n", chan->name, strerror(errno));
251                 goto exit;
252         }
253
254         u->list_cleared = 0;
255         AST_LIST_HEAD_INIT(&u->playlist);
256
257         if (chan->_state != AST_STATE_UP) {
258                 ast_answer(chan);
259         }
260
261         if (ast_activate_generator(chan, &gen, u) < 0) {
262                 ast_log(LOG_WARNING,"Failed to activate generator on '%s'\n", chan->name);
263                 goto exit;
264         } else
265                 gen_active = 1;
266
267         pid = fork();
268         if (pid < 0) {
269                 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
270                 goto exit;
271         }
272
273         if (!pid) {
274                 /* child process */
275                 int i;
276
277                 dup2(child_stdin[0], STDIN_FILENO);
278                 dup2(child_stdout[1], STDOUT_FILENO);
279                 dup2(child_stderr[1], STDERR_FILENO);
280                 close(child_stdin[1]);
281                 close(child_stdout[0]);
282                 close(child_stderr[0]);
283                 for (i = STDERR_FILENO + 1; i < 1024; i++)
284                         close(i);
285                 execv(command, argv);
286                 fprintf(stderr, "Failed to execute '%s': %s\n", command, strerror(errno));
287                 exit(1);
288         } else {
289                 /* parent process */
290                 int child_events_fd = child_stdin[1];
291                 int child_commands_fd = child_stdout[0];
292                 int child_errors_fd = child_stderr[0];
293                 struct ast_frame *f;
294                 int ms;
295                 int exception;
296                 int ready_fd;
297                 int waitfds[2] = { child_errors_fd, child_commands_fd };
298                 struct ast_channel *rchan;
299
300                 close(child_stdin[0]);
301                 close(child_stdout[1]);
302                 close(child_stderr[1]);
303
304                 if (!(child_events = fdopen(child_events_fd, "w"))) {
305                         ast_log(LOG_WARNING, "Could not open stream for child events for channel '%s'\n", chan->name);
306                         goto exit;
307                 }
308
309                 setvbuf(child_events, NULL, _IONBF, 0);
310
311                 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
312                         ast_log(LOG_WARNING, "Could not open stream for child commands for channel '%s'\n", chan->name);
313                         goto exit;
314                 }
315
316                 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
317                         ast_log(LOG_WARNING, "Could not open stream for child errors for channel '%s'\n", chan->name);
318                         goto exit;
319                 }
320
321                 res = 0;
322
323                 while (1) {
324                         if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
325                                 ast_log(LOG_NOTICE, "Channel '%s' is a zombie\n", chan->name);
326                                 res = -1;
327                                 break;
328                         }
329
330                         if (ast_check_hangup(chan)) {
331                                 ast_log(LOG_NOTICE, "Channel '%s' got check_hangup\n", chan->name);
332                                 fprintf(child_events, "H,%10ld\n", time(NULL));
333                                 res = -1;
334                                 break;
335                         }
336
337                         ready_fd = 0;
338                         ms = 1000;
339                         errno = 0;
340                         exception = 0;
341
342                         rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
343
344                         if (rchan) {
345                                 /* the channel has something */
346                                 f = ast_read(chan);
347                                 if (!f) {
348                                         fprintf(child_events, "H,%10ld\n", time(NULL));
349                                         ast_log(LOG_NOTICE, "Channel '%s' returned no frame\n", chan->name);
350                                         res = -1;
351                                         break;
352                                 }
353
354                                 if (f->frametype == AST_FRAME_DTMF) {
355                                         fprintf(child_events, "%c,%10ld\n", f->subclass, time(NULL));
356                                 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
357                                         ast_log(LOG_NOTICE, "Channel '%s' got AST_CONTROL_HANGUP\n", chan->name);
358                                         fprintf(child_events, "H,%10ld\n", time(NULL));
359                                         ast_frfree(f);
360                                         res = -1;
361                                         break;
362                                 }
363                                 ast_frfree(f);
364                         } else if (ready_fd == child_commands_fd) {
365                                 char input[1024];
366
367                                 if (exception || feof(child_commands)) {
368                                         ast_log(LOG_WARNING, "Child process went away for channel '%s'\n", chan->name);
369                                         res = -1;
370                                         break;
371                                 }
372
373                                 if (!fgets(input, sizeof(input), child_commands))
374                                         continue;
375
376                                 command = ast_strip(input);
377
378                                 if (strlen(input) < 4)
379                                         continue;
380
381                                 if (input[0] == 'S') {
382                                         if (ast_fileexists(&input[2], NULL, NULL) == -1) {
383                                                 fprintf(child_events, "Z,%10ld\n", time(NULL));
384                                                 ast_log(LOG_WARNING, "Unknown file requested '%s' for channel '%s'\n", &input[2], chan->name);
385                                                 strcpy(&input[2], "exception");
386                                         }
387                                         AST_LIST_LOCK(&u->playlist);
388                                         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
389                                                 free(entry);
390                                         u->list_cleared = 1;
391                                         entry = make_entry(&input[2]);
392                                         if (entry)
393                                                 AST_LIST_UNLOCK(&u->playlist);
394                                         AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
395                                 } else if (input[0] == 'A') {
396                                         if (ast_fileexists(&input[2], NULL, NULL) == -1) {
397                                                 fprintf(child_events, "Z,%10ld\n", time(NULL));
398                                                 ast_log(LOG_WARNING, "Unknown file requested '%s' for channel '%s'\n", &input[2], chan->name);
399                                                 strcpy(&input[2], "exception");
400                                         }
401                                         entry = make_entry(&input[2]);
402                                         if (entry) {
403                                                 AST_LIST_LOCK(&u->playlist);
404                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
405                                                 AST_LIST_UNLOCK(&u->playlist);
406                                         }
407                                 } else if (input[0] == 'H') {
408                                         ast_log(LOG_NOTICE, "Hanging up: %s\n", &input[2]);
409                                         fprintf(child_events, "H,%10ld\n", time(NULL));
410                                         break;
411                                 }
412                         } else if (ready_fd == child_errors_fd) {
413                                 char input[1024];
414
415                                 if (exception || feof(child_errors)) {
416                                         ast_log(LOG_WARNING, "Child process went away for channel '%s'\n", chan->name);
417                                         res = -1;
418                                         break;
419                                 }
420
421                                 if (fgets(input, sizeof(input), child_errors)) {
422                                         command = ast_strip(input);
423                                         ast_log(LOG_NOTICE, "%s\n", command);
424                                 }
425                         } else if ((ready_fd < 0) && ms) { 
426                                 if (errno == 0 || errno == EINTR)
427                                         continue;
428
429                                 ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno));
430                                 break;
431                         }
432                 }
433         }
434
435  exit:
436         if (gen_active)
437                 ast_deactivate_generator(chan);
438
439         if (child_events)
440                 fclose(child_events);
441
442         if (child_commands)
443                 fclose(child_commands);
444
445         if (child_errors)
446                 fclose(child_errors);
447
448         if (child_stdin[0]) {
449                 close(child_stdin[0]);
450                 close(child_stdin[1]);
451         }
452
453         if (child_stdout[0]) {
454                 close(child_stdout[0]);
455                 close(child_stdout[1]);
456         }
457
458         if (child_stderr[0]) {
459                 close(child_stderr[0]);
460                 close(child_stderr[1]);
461         }
462
463         if (u) {
464                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
465                         free(entry);
466
467                 LOCAL_USER_REMOVE(u);
468         }
469
470         return res;
471 }
472
473 int unload_module(void)
474 {
475         STANDARD_HANGUP_LOCALUSERS;
476
477         return ast_unregister_application(app);
478 }
479
480 int load_module(void)
481 {
482         return ast_register_application(app, app_exec, synopsis, descrip);
483 }
484
485 char *description(void)
486 {
487         return tdesc;
488 }
489
490 int usecount(void)
491 {
492         int res;
493
494         STANDARD_USECOUNT(res);
495
496         return res;
497 }
498
499 char *key()
500 {
501         return ASTERISK_GPL_KEY;
502 }