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