2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Kevin P. Fleming <kpfleming@digium.com>
8 * Portions taken from the file-based music-on-hold work
9 * created by Anthony Minessale II in res_musiconhold.c
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.
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.
24 * \brief External IVR application interface
26 * \author Kevin P. Fleming <kpfleming@digium.com>
28 * \note Portions taken from the file-based music-on-hold work
29 * created by Anthony Minessale II in res_musiconhold.c
31 * \ingroup applications
36 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
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"
50 static const char *app
= "ExternalIVR";
52 static const char *synopsis
= "Interfaces with an external IVR application";
53 static const char *descrip
=
54 " ExternalIVR(command|ivr://ivrhosti([,arg[,arg...]])[,options]): Either forks a process\n"
55 "to run given command or makes a socket to connect to given host and starts\n"
56 "a generator on the channel. The generator's play list is controlled by the\n"
57 "external application, which can add and clear entries via simple commands\n"
58 "issued over its stdout. The external application will receive all DTMF events\n"
59 "received on the channel, and notification if the channel is hung up. The\n"
60 "application will not be forcibly terminated when the channel is hung up.\n"
61 "See doc/externalivr.txt for a protocol specification.\n"
62 "The 'n' option tells ExternalIVR() not to answer the channel. \n"
63 "The 'i' option tells ExternalIVR() not to send a hangup and exit when the\n"
64 " channel receives a hangup, instead it sends an 'I' informative message\n"
65 " meaning that the external application MUST hang up the call with an H command\n"
66 "The 'd' option tells ExternalIVR() to run on a channel that has been hung up\n"
67 " and will not look for hangups. The external application must exit with\n"
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__)
75 ignore_hangup
= (1 << 1),
79 AST_APP_OPTIONS(app_opts
, {
80 AST_APP_OPTION('n', noanswer
),
81 AST_APP_OPTION('i', ignore_hangup
),
82 AST_APP_OPTION('d', run_dead
),
85 struct playlist_entry
{
86 AST_LIST_ENTRY(playlist_entry
) list
;
90 struct ivr_localuser
{
91 struct ast_channel
*chan
;
92 AST_LIST_HEAD(playlist
, playlist_entry
) playlist
;
93 AST_LIST_HEAD(finishlist
, playlist_entry
) finishlist
;
94 int abort_current_sound
;
102 struct ivr_localuser
*u
;
103 struct ast_filestream
*stream
;
104 struct playlist_entry
*current
;
108 static int eivr_comm(struct ast_channel
*chan
, struct ivr_localuser
*u
,
109 int eivr_events_fd
, int eivr_commands_fd
, int eivr_errors_fd
,
110 const struct ast_str
*args
, const struct ast_flags flags
);
112 int eivr_connect_socket(struct ast_channel
*chan
, const char *host
, int port
);
114 static void send_eivr_event(FILE *handle
, const char event
, const char *data
,
115 const struct ast_channel
*chan
)
117 struct ast_str
*tmp
= ast_str_create(12);
119 ast_str_append(&tmp
, 0, "%c,%10d", event
, (int)time(NULL
));
121 ast_str_append(&tmp
, 0, "%s", data
);
124 fprintf(handle
, "%s\n", tmp
->str
);
125 ast_debug(1, "sent '%s'\n", tmp
->str
);
128 static void *gen_alloc(struct ast_channel
*chan
, void *params
)
130 struct ivr_localuser
*u
= params
;
131 struct gen_state
*state
;
133 if (!(state
= ast_calloc(1, sizeof(*state
))))
141 static void gen_closestream(struct gen_state
*state
)
146 ast_closestream(state
->stream
);
147 state
->u
->chan
->stream
= NULL
;
148 state
->stream
= NULL
;
151 static void gen_release(struct ast_channel
*chan
, void *data
)
153 struct gen_state
*state
= data
;
155 gen_closestream(state
);
159 /* caller has the playlist locked */
160 static int gen_nextfile(struct gen_state
*state
)
162 struct ivr_localuser
*u
= state
->u
;
163 char *file_to_stream
;
165 u
->abort_current_sound
= 0;
166 u
->playing_silence
= 0;
167 gen_closestream(state
);
169 while (!state
->stream
) {
170 state
->current
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
);
171 if (state
->current
) {
172 file_to_stream
= state
->current
->filename
;
174 file_to_stream
= "silence/10";
175 u
->playing_silence
= 1;
178 if (!(state
->stream
= ast_openstream_full(u
->chan
, file_to_stream
, u
->chan
->language
, 1))) {
179 ast_chan_log(LOG_WARNING
, u
->chan
, "File '%s' could not be opened: %s\n", file_to_stream
, strerror(errno
));
180 if (!u
->playing_silence
) {
188 return (!state
->stream
);
191 static struct ast_frame
*gen_readframe(struct gen_state
*state
)
193 struct ast_frame
*f
= NULL
;
194 struct ivr_localuser
*u
= state
->u
;
196 if (u
->abort_current_sound
||
197 (u
->playing_silence
&& AST_LIST_FIRST(&u
->playlist
))) {
198 gen_closestream(state
);
199 AST_LIST_LOCK(&u
->playlist
);
201 AST_LIST_UNLOCK(&u
->playlist
);
204 if (!(state
->stream
&& (f
= ast_readframe(state
->stream
)))) {
205 if (state
->current
) {
206 AST_LIST_LOCK(&u
->finishlist
);
207 AST_LIST_INSERT_TAIL(&u
->finishlist
, state
->current
, list
);
208 AST_LIST_UNLOCK(&u
->finishlist
);
209 state
->current
= NULL
;
211 if (!gen_nextfile(state
))
212 f
= ast_readframe(state
->stream
);
218 static int gen_generate(struct ast_channel
*chan
, void *data
, int len
, int samples
)
220 struct gen_state
*state
= data
;
221 struct ast_frame
*f
= NULL
;
224 state
->sample_queue
+= samples
;
226 while (state
->sample_queue
> 0) {
227 if (!(f
= gen_readframe(state
)))
230 res
= ast_write(chan
, f
);
233 ast_chan_log(LOG_WARNING
, chan
, "Failed to write frame: %s\n", strerror(errno
));
236 state
->sample_queue
-= f
->samples
;
242 static struct ast_generator gen
=
245 release
: gen_release
,
246 generate
: gen_generate
,
249 static void ast_eivr_getvariable(struct ast_channel
*chan
, char *data
, char *outbuf
, int outbuflen
)
251 /* original input data: "G,var1,var2," */
252 /* data passed as "data": "var1,var2" */
254 char *inbuf
, *variable
;
257 struct ast_str
*newstring
= ast_str_alloca(outbuflen
);
261 for (j
= 1, inbuf
= data
; ; j
++) {
262 variable
= strsep(&inbuf
, ",");
263 if (variable
== NULL
) {
264 int outstrlen
= strlen(outbuf
);
265 if (outstrlen
&& outbuf
[outstrlen
- 1] == ',') {
266 outbuf
[outstrlen
- 1] = 0;
271 ast_channel_lock(chan
);
272 if (!(value
= pbx_builtin_getvar_helper(chan
, variable
))) {
276 ast_str_append(&newstring
, 0, "%s=%s,", variable
, value
);
277 ast_channel_unlock(chan
);
278 ast_copy_string(outbuf
, newstring
->str
, outbuflen
);
282 static void ast_eivr_setvariable(struct ast_channel
*chan
, char *data
)
287 char *inbuf
, *variable
;
291 for (j
= 1, inbuf
= data
; ; j
++, inbuf
= NULL
) {
292 variable
= strsep(&inbuf
, ",");
293 ast_debug(1, "Setting up a variable: %s\n", variable
);
295 /* variable contains "varname=value" */
296 ast_copy_string(buf
, variable
, sizeof(buf
));
297 value
= strchr(buf
, '=');
303 pbx_builtin_setvar_helper(chan
, buf
, value
);
310 static struct playlist_entry
*make_entry(const char *filename
)
312 struct playlist_entry
*entry
;
314 if (!(entry
= ast_calloc(1, sizeof(*entry
) + strlen(filename
) + 10))) /* XXX why 10 ? */
317 strcpy(entry
->filename
, filename
);
322 static int app_exec(struct ast_channel
*chan
, void *data
)
324 struct ast_flags flags
;
326 struct playlist_entry
*entry
;
327 int child_stdin
[2] = { 0, 0 };
328 int child_stdout
[2] = { 0, 0 };
329 int child_stderr
[2] = { 0, 0 };
334 char *port_str
= NULL
;
336 struct ast_tcptls_session_instance
*ser
= NULL
;
338 struct ivr_localuser foo
= {
339 .playlist
= AST_LIST_HEAD_INIT_VALUE
,
340 .finishlist
= AST_LIST_HEAD_INIT_VALUE
,
343 struct ivr_localuser
*u
= &foo
;
347 char *s
, **app_args
, *e
;
348 struct ast_str
*pipe_delim_args
= ast_str_create(100);
350 AST_DECLARE_APP_ARGS(eivr_args
,
351 AST_APP_ARG(cmd
)[32];
353 AST_DECLARE_APP_ARGS(application_args
,
354 AST_APP_ARG(cmd
)[32];
357 u
->abort_current_sound
= 0;
360 buf
= ast_strdupa(data
);
361 AST_STANDARD_APP_ARGS(eivr_args
, buf
);
363 if ((s
= strchr(eivr_args
.cmd
[0], '('))) {
365 if (( e
= strrchr(s
, ')')) ) {
368 ast_log(LOG_ERROR
, "Parse error, no closing paren?\n");
370 AST_STANDARD_APP_ARGS(application_args
, eivr_args
.cmd
[0]);
371 app_args
= application_args
.argv
;
373 /* Put the application + the arguments in a | delimited list */
374 ast_str_reset(pipe_delim_args
);
375 for (j
= 0; application_args
.cmd
[j
] != NULL
; j
++) {
376 ast_str_append(&pipe_delim_args
, 0, "%s%s", j
== 0 ? "" : "|", application_args
.cmd
[j
]);
379 /* Parse the ExternalIVR() arguments */
381 ast_debug(1, "Parsing options from: [%s]\n", eivr_args
.cmd
[1]);
382 ast_app_parse_options(app_opts
, &flags
, opts
, eivr_args
.cmd
[1]);
384 if (ast_test_flag(&flags
, noanswer
))
385 ast_debug(1, "noanswer is set\n");
386 if (ast_test_flag(&flags
, ignore_hangup
))
387 ast_debug(1, "ignore_hangup is set\n");
388 if (ast_test_flag(&flags
, run_dead
))
389 ast_debug(1, "run_dead is set\n");
393 app_args
= eivr_args
.argv
;
394 for (j
= 0; eivr_args
.cmd
[j
] != NULL
; j
++) {
395 ast_str_append(&pipe_delim_args
, 0, "%s%s", j
== 0 ? "" : "|", eivr_args
.cmd
[j
]);
399 if (ast_strlen_zero(data
)) {
400 ast_log(LOG_WARNING
, "ExternalIVR requires a command to execute\n");
404 if (!(ast_test_flag(&flags
, noanswer
))) {
405 ast_chan_log(LOG_WARNING
, chan
, "Answering channel and starting generator\n");
406 if (chan
->_state
!= AST_STATE_UP
) {
407 if (ast_test_flag(&flags
, run_dead
)) {
408 ast_chan_log(LOG_WARNING
, chan
, "Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
413 if (ast_activate_generator(chan
, &gen
, u
) < 0) {
414 ast_chan_log(LOG_WARNING
, chan
, "Failed to activate generator\n");
421 if (!strncmp(app_args
[0], "ivr://", 6)) {
422 struct server_args ivr_desc
= {
426 struct ast_hostent hp
;
428 /*communicate through socket to server*/
429 ast_debug(1, "Parsing hostname:port for socket connect from \"%s\"\n", app_args
[0]);
430 ast_copy_string(hostname
, app_args
[0] + 6, sizeof(hostname
));
431 if ((port_str
= strchr(hostname
, ':')) != NULL
) {
434 port
= atoi(port_str
);
437 port
= 2949; /* default port, if one is not provided */
440 ast_gethostbyname(hostname
, &hp
);
441 ivr_desc
.sin
.sin_family
= AF_INET
;
442 ivr_desc
.sin
.sin_port
= htons(port
);
443 memmove(&ivr_desc
.sin
.sin_addr
.s_addr
, hp
.hp
.h_addr
, hp
.hp
.h_length
);
444 ser
= ast_tcptls_client_start(&ivr_desc
);
449 res
= eivr_comm(chan
, u
, ser
->fd
, ser
->fd
, -1, pipe_delim_args
, flags
);
453 if (pipe(child_stdin
)) {
454 ast_chan_log(LOG_WARNING
, chan
, "Could not create pipe for child input: %s\n", strerror(errno
));
457 if (pipe(child_stdout
)) {
458 ast_chan_log(LOG_WARNING
, chan
, "Could not create pipe for child output: %s\n", strerror(errno
));
461 if (pipe(child_stderr
)) {
462 ast_chan_log(LOG_WARNING
, chan
, "Could not create pipe for child errors: %s\n", strerror(errno
));
466 pid
= ast_safe_fork(0);
468 ast_log(LOG_WARNING
, "Failed to fork(): %s\n", strerror(errno
));
474 if (ast_opt_high_priority
)
477 dup2(child_stdin
[0], STDIN_FILENO
);
478 dup2(child_stdout
[1], STDOUT_FILENO
);
479 dup2(child_stderr
[1], STDERR_FILENO
);
480 ast_close_fds_above_n(STDERR_FILENO
);
481 execv(app_args
[0], app_args
);
482 fprintf(stderr
, "Failed to execute '%s': %s\n", app_args
[0], strerror(errno
));
486 close(child_stdin
[0]);
488 close(child_stdout
[1]);
490 close(child_stderr
[1]);
492 res
= eivr_comm(chan
, u
, child_stdin
[1], child_stdout
[0], child_stderr
[0], pipe_delim_args
, flags
);
498 ast_deactivate_generator(chan
);
501 close(child_stdin
[0]);
504 close(child_stdin
[1]);
507 close(child_stdout
[0]);
510 close(child_stdout
[1]);
513 close(child_stderr
[0]);
516 close(child_stderr
[1]);
519 ast_tcptls_session_instance_destroy(ser
);
521 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
)))
527 static int eivr_comm(struct ast_channel
*chan
, struct ivr_localuser
*u
,
528 int eivr_events_fd
, int eivr_commands_fd
, int eivr_errors_fd
,
529 const struct ast_str
*args
, const struct ast_flags flags
)
531 struct playlist_entry
*entry
;
536 int waitfds
[2] = { eivr_commands_fd
, eivr_errors_fd
};
537 struct ast_channel
*rchan
;
540 int test_available_fd
= -1;
541 int hangup_info_sent
= 0;
543 FILE *eivr_commands
= NULL
;
544 FILE *eivr_errors
= NULL
;
545 FILE *eivr_events
= NULL
;
547 if (!(eivr_events
= fdopen(eivr_events_fd
, "w"))) {
548 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to send events\n");
551 if (!(eivr_commands
= fdopen(eivr_commands_fd
, "r"))) {
552 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to receive commands\n");
555 if (eivr_errors_fd
> -1) { /* if opening a socket connection, error stream will not be used */
556 if (!(eivr_errors
= fdopen(eivr_errors_fd
, "r"))) {
557 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to receive errors\n");
562 test_available_fd
= open("/dev/null", O_RDONLY
);
564 setvbuf(eivr_events
, NULL
, _IONBF
, 0);
565 setvbuf(eivr_commands
, NULL
, _IONBF
, 0);
567 setvbuf(eivr_errors
, NULL
, _IONBF
, 0);
573 if (ast_test_flag(chan
, AST_FLAG_ZOMBIE
)) {
574 ast_chan_log(LOG_NOTICE
, chan
, "Is a zombie\n");
578 if (!hangup_info_sent
&& !(ast_test_flag(&flags
, run_dead
)) && ast_check_hangup(chan
)) {
579 if (ast_test_flag(&flags
, ignore_hangup
)) {
580 ast_chan_log(LOG_NOTICE
, chan
, "Got check_hangup, but ignore_hangup set so sending 'I' command\n");
581 send_eivr_event(eivr_events
, 'I', "HANGUP", chan
);
582 hangup_info_sent
= 1;
584 ast_chan_log(LOG_NOTICE
, chan
, "Got check_hangup\n");
585 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
596 rchan
= ast_waitfor_nandfds(&chan
, 1, waitfds
, (eivr_errors_fd
< 0) ? 1 : 2, &exception
, &ready_fd
, &ms
);
598 if (chan
->_state
== AST_STATE_UP
&& !AST_LIST_EMPTY(&u
->finishlist
)) {
599 AST_LIST_LOCK(&u
->finishlist
);
600 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->finishlist
, list
))) {
601 send_eivr_event(eivr_events
, 'F', entry
->filename
, chan
);
604 AST_LIST_UNLOCK(&u
->finishlist
);
607 if (chan
->_state
== AST_STATE_UP
&& !(ast_check_hangup(chan
)) && rchan
) {
608 /* the channel has something */
611 ast_chan_log(LOG_NOTICE
, chan
, "Returned no frame\n");
612 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
616 if (f
->frametype
== AST_FRAME_DTMF
) {
617 send_eivr_event(eivr_events
, f
->subclass
, NULL
, chan
);
618 if (u
->option_autoclear
) {
619 if (!u
->abort_current_sound
&& !u
->playing_silence
)
620 send_eivr_event(eivr_events
, 'T', NULL
, chan
);
621 AST_LIST_LOCK(&u
->playlist
);
622 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
))) {
623 send_eivr_event(eivr_events
, 'D', entry
->filename
, chan
);
626 if (!u
->playing_silence
)
627 u
->abort_current_sound
= 1;
628 AST_LIST_UNLOCK(&u
->playlist
);
630 } else if ((f
->frametype
== AST_FRAME_CONTROL
) && (f
->subclass
== AST_CONTROL_HANGUP
)) {
631 ast_chan_log(LOG_NOTICE
, chan
, "Got AST_CONTROL_HANGUP\n");
632 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
633 if (f
->data
.uint32
) {
634 chan
->hangupcause
= f
->data
.uint32
;
641 } else if (ready_fd
== eivr_commands_fd
) {
644 if (exception
|| (dup2(eivr_commands_fd
, test_available_fd
) == -1) || feof(eivr_commands
)) {
645 ast_chan_log(LOG_WARNING
, chan
, "Child process went away\n");
650 if (!fgets(input
, sizeof(input
), eivr_commands
))
653 command
= ast_strip(input
);
656 ast_debug(1, "got command '%s'\n", input
);
658 if (strlen(input
) < 4)
661 if (input
[0] == 'P') {
662 send_eivr_event(eivr_events
, 'P', args
->str
, chan
);
663 } else if ( input
[0] == 'T' ) {
664 ast_chan_log(LOG_WARNING
, chan
, "Answering channel if needed and starting generator\n");
665 if (chan
->_state
!= AST_STATE_UP
) {
666 if (ast_test_flag(&flags
, run_dead
)) {
667 ast_chan_log(LOG_WARNING
, chan
, "Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
668 send_eivr_event(eivr_events
, 'Z', "ANSWER_FAILURE", chan
);
673 if (!(u
->gen_active
)) {
674 if (ast_activate_generator(chan
, &gen
, u
) < 0) {
675 ast_chan_log(LOG_WARNING
, chan
, "Failed to activate generator\n");
676 send_eivr_event(eivr_events
, 'Z', "GENERATOR_FAILURE", chan
);
681 } else if (input
[0] == 'S') {
682 if (chan
->_state
!= AST_STATE_UP
|| ast_check_hangup(chan
)) {
683 ast_chan_log(LOG_WARNING
, chan
, "Queue 'S'et called on unanswered channel\n");
684 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
687 if (ast_fileexists(&input
[2], NULL
, u
->chan
->language
) == -1) {
688 ast_chan_log(LOG_WARNING
, chan
, "Unknown file requested '%s'\n", &input
[2]);
689 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
690 strcpy(&input
[2], "exception");
692 if (!u
->abort_current_sound
&& !u
->playing_silence
)
693 send_eivr_event(eivr_events
, 'T', NULL
, chan
);
694 AST_LIST_LOCK(&u
->playlist
);
695 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
))) {
696 send_eivr_event(eivr_events
, 'D', entry
->filename
, chan
);
699 if (!u
->playing_silence
)
700 u
->abort_current_sound
= 1;
701 entry
= make_entry(&input
[2]);
703 AST_LIST_INSERT_TAIL(&u
->playlist
, entry
, list
);
704 AST_LIST_UNLOCK(&u
->playlist
);
705 } else if (input
[0] == 'A') {
706 if (chan
->_state
!= AST_STATE_UP
|| ast_check_hangup(chan
)) {
707 ast_chan_log(LOG_WARNING
, chan
, "Queue 'A'ppend called on unanswered channel\n");
708 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
711 if (ast_fileexists(&input
[2], NULL
, u
->chan
->language
) == -1) {
712 ast_chan_log(LOG_WARNING
, chan
, "Unknown file requested '%s'\n", &input
[2]);
713 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
714 strcpy(&input
[2], "exception");
716 entry
= make_entry(&input
[2]);
718 AST_LIST_LOCK(&u
->playlist
);
719 AST_LIST_INSERT_TAIL(&u
->playlist
, entry
, list
);
720 AST_LIST_UNLOCK(&u
->playlist
);
722 } else if (input
[0] == 'G') {
723 /* A get variable message: "G,variable1,variable2,..." */
726 ast_chan_log(LOG_NOTICE
, chan
, "Getting a Variable out of the channel: %s\n", &input
[2]);
727 ast_eivr_getvariable(chan
, &input
[2], response
, sizeof(response
));
728 send_eivr_event(eivr_events
, 'G', response
, chan
);
729 } else if (input
[0] == 'V') {
730 /* A set variable message: "V,variablename=foo" */
731 ast_chan_log(LOG_NOTICE
, chan
, "Setting a Variable up: %s\n", &input
[2]);
732 ast_eivr_setvariable(chan
, &input
[2]);
733 } else if (input
[0] == 'L') {
734 ast_chan_log(LOG_NOTICE
, chan
, "Log message from EIVR: %s\n", &input
[2]);
735 } else if (input
[0] == 'X') {
736 ast_chan_log(LOG_NOTICE
, chan
, "Exiting ExternalIVR: %s\n", &input
[2]);
737 /*! \todo add deprecation debug message for X command here */
740 } else if (input
[0] == 'E') {
741 ast_chan_log(LOG_NOTICE
, chan
, "Exiting: %s\n", &input
[2]);
742 send_eivr_event(eivr_events
, 'E', NULL
, chan
);
745 } else if (input
[0] == 'H') {
746 ast_chan_log(LOG_NOTICE
, chan
, "Hanging up: %s\n", &input
[2]);
747 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
750 } else if (input
[0] == 'O') {
751 if (chan
->_state
!= AST_STATE_UP
|| ast_check_hangup(chan
)) {
752 ast_chan_log(LOG_WARNING
, chan
, "Option called on unanswered channel\n");
753 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
756 if (!strcasecmp(&input
[2], "autoclear"))
757 u
->option_autoclear
= 1;
758 else if (!strcasecmp(&input
[2], "noautoclear"))
759 u
->option_autoclear
= 0;
761 ast_chan_log(LOG_WARNING
, chan
, "Unknown option requested '%s'\n", &input
[2]);
763 } else if (eivr_errors_fd
&& ready_fd
== eivr_errors_fd
) {
766 if (exception
|| feof(eivr_errors
)) {
767 ast_chan_log(LOG_WARNING
, chan
, "Child process went away\n");
771 if (fgets(input
, sizeof(input
), eivr_errors
)) {
772 command
= ast_strip(input
);
773 ast_chan_log(LOG_NOTICE
, chan
, "stderr: %s\n", command
);
775 } else if ((ready_fd
< 0) && ms
) {
776 if (errno
== 0 || errno
== EINTR
)
779 ast_chan_log(LOG_WARNING
, chan
, "Wait failed (%s)\n", strerror(errno
));
787 if (test_available_fd
> -1) {
788 close(test_available_fd
);
795 fclose(eivr_commands
);
804 static int unload_module(void)
806 return ast_unregister_application(app
);
809 static int load_module(void)
811 return ast_register_application(app
, app_exec
, synopsis
, descrip
);
814 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "External IVR Interface Application");