a few syntax changes and safer code
[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 <signal.h>
39
40 #include "asterisk/lock.h"
41 #include "asterisk/file.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 #include "asterisk/utils.h"
48
49 static const char *app = "ExternalIVR";
50
51 static const char *synopsis = "Interfaces with an external IVR application";
52
53 static const char *descrip =
54 "  ExternalIVR(command[,arg[,arg...]]): Forks a process to run the supplied command,\n"
55 "and starts a generator on the channel. The generator's play list is\n"
56 "controlled by the external application, which can add and clear entries\n"
57 "via simple commands issued over its stdout. The external application\n"
58 "will receive all DTMF events received on the channel, and notification\n"
59 "if the channel is hung up. The application will not be forcibly terminated\n"
60 "when the channel is hung up.\n"
61 "See doc/externalivr.txt for a protocol specification.\n";
62
63 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
64 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
65
66 struct playlist_entry {
67         AST_LIST_ENTRY(playlist_entry) list;
68         char filename[1];
69 };
70
71 struct ivr_localuser {
72         struct ast_channel *chan;
73         AST_LIST_HEAD(playlist, playlist_entry) playlist;
74         AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
75         int abort_current_sound;
76         int playing_silence;
77         int option_autoclear;
78 };
79
80
81 struct gen_state {
82         struct ivr_localuser *u;
83         struct ast_filestream *stream;
84         struct playlist_entry *current;
85         int sample_queue;
86 };
87
88 static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u, 
89               int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd, 
90               const char *args);
91
92 static void send_eivr_event(FILE *handle, const char event, const char *data,
93         const struct ast_channel *chan)
94 {
95         char tmp[256];
96
97         if (!data) {
98                 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
99         } else {
100                 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
101         }
102
103         fprintf(handle, "%s\n", tmp);
104         if (option_debug)
105                 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
106 }
107
108 static void *gen_alloc(struct ast_channel *chan, void *params)
109 {
110         struct ivr_localuser *u = params;
111         struct gen_state *state;
112
113         if (!(state = ast_calloc(1, sizeof(*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         ast_free(data);
137 }
138
139 /* caller has the playlist locked */
140 static int gen_nextfile(struct gen_state *state)
141 {
142         struct ivr_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 ivr_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 void ast_eivr_getvariable(struct ast_channel *chan, char *data, char *outbuf, int outbuflen)
230 {
231         /* original input data: "G,var1,var2," */
232         /* data passed as "data":  "var1,var2" */
233
234         char *inbuf, *variable;
235         const char *value;
236         int j;
237         struct ast_str *newstring = ast_str_alloca(outbuflen); 
238
239         outbuf[0] = '\0';
240
241         for (j = 1, inbuf = data; ; j++) {
242                 variable = strsep(&inbuf, ",");
243                 if (variable == NULL) {
244                         int outstrlen = strlen(outbuf);
245                         if(outstrlen && outbuf[outstrlen - 1] == ',') {
246                                 outbuf[outstrlen - 1] = 0;
247                         }
248                         break;
249                 }
250                 
251                 value = pbx_builtin_getvar_helper(chan, variable);
252                 if(!value)
253                         value = "";
254                 ast_str_append(&newstring, 0, "%s=%s,", variable, value);
255                 ast_copy_string(outbuf, newstring->str, outbuflen);
256         }
257 };
258
259 static void ast_eivr_setvariable(struct ast_channel *chan, char *data)
260 {
261         char buf[1024];
262         char *value;
263
264         char *inbuf, *variable;
265
266         int j;
267
268         for (j = 1, inbuf = data; ; j++, inbuf = NULL) {
269                 variable = strsep(&inbuf, ",");
270                 ast_chan_log(LOG_DEBUG, chan, "Setting up a variable: %s\n", variable);
271                 if(variable) {
272                         /* variable contains "varname=value" */
273                         ast_copy_string(buf, variable, sizeof(buf));
274                         value = strchr(buf, '=');
275                         if(!value) 
276                                 value="";
277                         else
278                                 *value++ = '\0';
279                         pbx_builtin_setvar_helper(chan, buf, value);
280                 }
281                 else
282                         break;
283         }
284 };
285
286 static struct playlist_entry *make_entry(const char *filename)
287 {
288         struct playlist_entry *entry;
289
290         if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
291                 return NULL;
292
293         strcpy(entry->filename, filename);
294
295         return entry;
296 }
297
298 static int app_exec(struct ast_channel *chan, void *data)
299 {
300         struct playlist_entry *entry;
301         int child_stdin[2] = { 0,0 };
302         int child_stdout[2] = { 0,0 };
303         int child_stderr[2] = { 0,0 };
304         int res = -1;
305         int gen_active = 0;
306         int pid;
307         char *buf, *pipe_delim_argbuf, *pdargbuf_ptr;
308         struct ivr_localuser foo = {
309                 .playlist = AST_LIST_HEAD_INIT_VALUE,
310                 .finishlist = AST_LIST_HEAD_INIT_VALUE,
311         };
312         struct ivr_localuser *u = &foo;
313         sigset_t fullset, oldset;
314         AST_DECLARE_APP_ARGS(args,
315                 AST_APP_ARG(cmd)[32];
316         );
317
318         sigfillset(&fullset);
319         pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
320
321         u->abort_current_sound = 0;
322         u->chan = chan;
323
324         if (ast_strlen_zero(data)) {
325                 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
326                 return -1;
327         }
328
329         buf = ast_strdupa(data);
330         AST_STANDARD_APP_ARGS(args, buf);
331
332         /* copy args and replace commas with pipes */
333         pipe_delim_argbuf = ast_strdupa(data);
334         while((pdargbuf_ptr = strchr(pipe_delim_argbuf, ',')) != NULL)
335                 pdargbuf_ptr[0] = '|';
336         
337         if (pipe(child_stdin)) {
338                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
339                 goto exit;
340         }
341         if (pipe(child_stdout)) {
342                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
343                 goto exit;
344         }
345         if (pipe(child_stderr)) {
346                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
347                 goto exit;
348         }
349         if (chan->_state != AST_STATE_UP) {
350                 ast_answer(chan);
351         }
352         if (ast_activate_generator(chan, &gen, u) < 0) {
353                 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
354                 goto exit;
355         } else
356                 gen_active = 1;
357
358         pid = fork();
359         if (pid < 0) {
360                 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
361                 goto exit;
362         }
363
364         if (!pid) {
365                 /* child process */
366                 int i;
367
368                 signal(SIGPIPE, SIG_DFL);
369                 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
370
371                 if (ast_opt_high_priority)
372                         ast_set_priority(0);
373
374                 dup2(child_stdin[0], STDIN_FILENO);
375                 dup2(child_stdout[1], STDOUT_FILENO);
376                 dup2(child_stderr[1], STDERR_FILENO);
377                 for (i = STDERR_FILENO + 1; i < 1024; i++)
378                         close(i);
379                 execv(args.cmd[0], args.cmd);
380                 fprintf(stderr, "Failed to execute '%s': %s\n", args.cmd[0], strerror(errno));
381                 _exit(1);
382         } else {
383                 /* parent process */
384
385                 close(child_stdin[0]);
386                 child_stdin[0] = 0;
387                 close(child_stdout[1]);
388                 child_stdout[1] = 0;
389                 close(child_stderr[1]);
390                 child_stderr[1] = 0;
391                 res = eivr_comm(chan, u, child_stdin[1], child_stdout[0], child_stderr[0], pipe_delim_argbuf);
392
393                 exit:
394                 if (gen_active)
395                         ast_deactivate_generator(chan);
396
397                 if (child_stdin[0])
398                         close(child_stdin[0]);
399
400                 if (child_stdin[1])
401                         close(child_stdin[1]);
402
403                 if (child_stdout[0])
404                         close(child_stdout[0]);
405
406                 if (child_stdout[1])
407                         close(child_stdout[1]);
408
409                 if (child_stderr[0])
410                         close(child_stderr[0]);
411
412                 if (child_stderr[1])
413                         close(child_stderr[1]);
414
415                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
416                         ast_free(entry);
417
418                 return res;
419         }
420 }
421
422 static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u, 
423                                 int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd, 
424                                 const char *args)
425 {
426         struct playlist_entry *entry;
427         struct ast_frame *f;
428         int ms;
429         int exception;
430         int ready_fd;
431         int waitfds[2] = { eivr_commands_fd, eivr_errors_fd };
432         struct ast_channel *rchan;
433         char *command;
434         int res = -1;
435   
436         FILE *eivr_commands = NULL;
437         FILE *eivr_errors = NULL;
438         FILE *eivr_events = NULL;
439  
440         if (!(eivr_events = fdopen(eivr_events_fd, "w"))) {
441                 ast_chan_log(LOG_WARNING, chan, "Could not open stream to send events\n");
442                 goto exit;
443         }
444         if (!(eivr_commands = fdopen(eivr_commands_fd, "r"))) {
445                 ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive commands\n");
446                 goto exit;
447         }
448         if(eivr_errors_fd) {  /*if opening a socket connection, error stream will not be used*/
449                 if (!(eivr_errors = fdopen(eivr_errors_fd, "r"))) {
450                         ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive errors\n");
451                         goto exit;
452                 }
453         }
454  
455         setvbuf(eivr_events, NULL, _IONBF, 0);
456         setvbuf(eivr_commands, NULL, _IONBF, 0);
457         if(eivr_errors)
458                 setvbuf(eivr_errors, NULL, _IONBF, 0);
459
460         res = 0;
461  
462         while (1) {
463                 if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
464                         ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
465                         res = -1;
466                         break;
467                 }
468                 if (ast_check_hangup(chan)) {
469                         ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
470                         send_eivr_event(eivr_events, 'H', NULL, chan);
471                         res = -1;
472                         break;
473                 }
474  
475                 ready_fd = 0;
476                 ms = 100;
477                 errno = 0;
478                 exception = 0;
479  
480                 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
481  
482                 if (!AST_LIST_EMPTY(&u->finishlist)) {
483                         AST_LIST_LOCK(&u->finishlist);
484                         while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
485                                 send_eivr_event(eivr_events, 'F', entry->filename, chan);
486                                 ast_free(entry);
487                         }
488                         AST_LIST_UNLOCK(&u->finishlist);
489                 }
490  
491                 if (rchan) {
492                         /* the channel has something */
493                         f = ast_read(chan);
494                         if (!f) {
495                                 ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
496                                 send_eivr_event(eivr_events, 'H', NULL, chan);
497                                 res = -1;
498                                 break;
499                         }
500                         if (f->frametype == AST_FRAME_DTMF) {
501                                 send_eivr_event(eivr_events, f->subclass, NULL, chan);
502                                 if (u->option_autoclear) {
503                                         if (!u->abort_current_sound && !u->playing_silence)
504                                                 send_eivr_event(eivr_events, 'T', NULL, chan);
505                                         AST_LIST_LOCK(&u->playlist);
506                                         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
507                                                 send_eivr_event(eivr_events, 'D', entry->filename, chan);
508                                                 ast_free(entry);
509                                         }
510                                         if (!u->playing_silence)
511                                                 u->abort_current_sound = 1;
512                                         AST_LIST_UNLOCK(&u->playlist);
513                                 }
514                         } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
515                                 ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
516                                 send_eivr_event(eivr_events, 'H', NULL, chan);
517                                 ast_frfree(f);
518                                 res = -1;
519                                 break;
520                         }
521                         ast_frfree(f);
522                 } else if (ready_fd == eivr_commands_fd) {
523                         char input[1024];
524  
525                         if (exception || feof(eivr_commands)) {
526                                 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
527                                 res = -1;
528                                 break;
529                         }
530   
531                         if (!fgets(input, sizeof(input), eivr_commands))
532                                 continue;
533  
534                         command = ast_strip(input);
535   
536                         if (option_debug)
537                                 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
538   
539                         if (strlen(input) < 4)
540                                 continue;
541   
542                         if (input[0] == 'P') {
543                                 send_eivr_event(eivr_events, 'P', args, chan);
544  
545                         } else if (input[0] == 'S') {
546                                 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
547                                         ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
548                                         send_eivr_event(eivr_events, 'Z', NULL, chan);
549                                         strcpy(&input[2], "exception");
550                                 }
551                                 if (!u->abort_current_sound && !u->playing_silence)
552                                         send_eivr_event(eivr_events, 'T', NULL, chan);
553                                 AST_LIST_LOCK(&u->playlist);
554                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
555                                         send_eivr_event(eivr_events, 'D', entry->filename, chan);
556                                         ast_free(entry);
557                                 }
558                                 if (!u->playing_silence)
559                                         u->abort_current_sound = 1;
560                                 entry = make_entry(&input[2]);
561                                 if (entry)
562                                         AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
563                                 AST_LIST_UNLOCK(&u->playlist);
564                         } else if (input[0] == 'A') {
565                                 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
566                                         ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
567                                         send_eivr_event(eivr_events, 'Z', NULL, chan);
568                                         strcpy(&input[2], "exception");
569                                 }
570                                 entry = make_entry(&input[2]);
571                                 if (entry) {
572                                         AST_LIST_LOCK(&u->playlist);
573                                         AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
574                                         AST_LIST_UNLOCK(&u->playlist);
575                                 }
576                         } else if (input[0] == 'G') {
577                                 /* A get variable message:  "G,variable1,variable2,..." */
578                                 char response[2048];
579
580                                 ast_chan_log(LOG_NOTICE, chan, "Getting a Variable out of the channel: %s\n", &input[2]);
581                                 ast_eivr_getvariable(chan, &input[2], response, sizeof(response));
582                                 send_eivr_event(eivr_events, 'G', response, chan);
583                         } else if (input[0] == 'V') {
584                                 /* A set variable message:  "V,variablename=foo" */
585                                 ast_chan_log(LOG_NOTICE, chan, "Setting a Variable up: %s\n", &input[2]);
586                                 ast_eivr_setvariable(chan, &input[2]);
587                         } else if (input[0] == 'L') {
588                                 ast_chan_log(LOG_NOTICE, chan, "Log message from EIVR: %s\n", &input[2]);
589                         } else if (input[0] == 'X') {
590                                 ast_chan_log(LOG_NOTICE, chan, "Exiting ExternalIVR: %s\n", &input[2]);
591                                 /*! \todo add deprecation debug message for X command here */
592                                 res = 0;
593                                 break;
594                         } else if (input[0] == 'E') {
595                                 ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
596                                 send_eivr_event(eivr_events, 'E', NULL, chan);
597                                 res = 0;
598                                 break;
599                         } else if (input[0] == 'H') {
600                                 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
601                                 send_eivr_event(eivr_events, 'H', NULL, chan);
602                                 res = -1;
603                                 break;
604                         } else if (input[0] == 'O') {
605                                 if (!strcasecmp(&input[2], "autoclear"))
606                                         u->option_autoclear = 1;
607                                 else if (!strcasecmp(&input[2], "noautoclear"))
608                                         u->option_autoclear = 0;
609                                 else
610                                         ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
611                         }
612                 } else if (eivr_errors_fd && ready_fd == eivr_errors_fd) {
613                         char input[1024];
614   
615                         if (exception || feof(eivr_errors)) {
616                                 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
617                                 res = -1;
618                                 break;
619                         }
620                         if (fgets(input, sizeof(input), eivr_errors)) {
621                                 command = ast_strip(input);
622                                 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
623                         }
624                 } else if ((ready_fd < 0) && ms) { 
625                         if (errno == 0 || errno == EINTR)
626                                 continue;
627  
628                         ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
629                         break;
630                 }
631         }
632   
633  
634 exit:
635  
636         if (eivr_events)
637                 fclose(eivr_events);
638  
639         if (eivr_commands)
640                 fclose(eivr_commands);
641
642         if (eivr_errors)
643                 fclose(eivr_errors);
644   
645         return res;
646  
647   }
648
649 static int unload_module(void)
650 {
651         return ast_unregister_application(app);
652 }
653
654 static int load_module(void)
655 {
656         return ast_register_application(app, app_exec, synopsis, descrip);
657 }
658
659 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");