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]);
520 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
)))
526 static int eivr_comm(struct ast_channel
*chan
, struct ivr_localuser
*u
,
527 int eivr_events_fd
, int eivr_commands_fd
, int eivr_errors_fd
,
528 const struct ast_str
*args
, const struct ast_flags flags
)
530 struct playlist_entry
*entry
;
535 int waitfds
[2] = { eivr_commands_fd
, eivr_errors_fd
};
536 struct ast_channel
*rchan
;
539 int test_available_fd
= -1;
540 int hangup_info_sent
= 0;
542 FILE *eivr_commands
= NULL
;
543 FILE *eivr_errors
= NULL
;
544 FILE *eivr_events
= NULL
;
546 if (!(eivr_events
= fdopen(eivr_events_fd
, "w"))) {
547 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to send events\n");
550 if (!(eivr_commands
= fdopen(eivr_commands_fd
, "r"))) {
551 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to receive commands\n");
554 if (eivr_errors_fd
> -1) { /* if opening a socket connection, error stream will not be used */
555 if (!(eivr_errors
= fdopen(eivr_errors_fd
, "r"))) {
556 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to receive errors\n");
561 test_available_fd
= open("/dev/null", O_RDONLY
);
563 setvbuf(eivr_events
, NULL
, _IONBF
, 0);
564 setvbuf(eivr_commands
, NULL
, _IONBF
, 0);
566 setvbuf(eivr_errors
, NULL
, _IONBF
, 0);
572 if (ast_test_flag(chan
, AST_FLAG_ZOMBIE
)) {
573 ast_chan_log(LOG_NOTICE
, chan
, "Is a zombie\n");
577 if (!hangup_info_sent
&& !(ast_test_flag(&flags
, run_dead
)) && ast_check_hangup(chan
)) {
578 if (ast_test_flag(&flags
, ignore_hangup
)) {
579 ast_chan_log(LOG_NOTICE
, chan
, "Got check_hangup, but ignore_hangup set so sending 'I' command\n");
580 send_eivr_event(eivr_events
, 'I', "HANGUP", chan
);
581 hangup_info_sent
= 1;
583 ast_chan_log(LOG_NOTICE
, chan
, "Got check_hangup\n");
584 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
595 rchan
= ast_waitfor_nandfds(&chan
, 1, waitfds
, (eivr_errors_fd
< 0) ? 1 : 2, &exception
, &ready_fd
, &ms
);
597 if (chan
->_state
== AST_STATE_UP
&& !AST_LIST_EMPTY(&u
->finishlist
)) {
598 AST_LIST_LOCK(&u
->finishlist
);
599 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->finishlist
, list
))) {
600 send_eivr_event(eivr_events
, 'F', entry
->filename
, chan
);
603 AST_LIST_UNLOCK(&u
->finishlist
);
606 if (chan
->_state
== AST_STATE_UP
&& !(ast_check_hangup(chan
)) && rchan
) {
607 /* the channel has something */
610 ast_chan_log(LOG_NOTICE
, chan
, "Returned no frame\n");
611 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
615 if (f
->frametype
== AST_FRAME_DTMF
) {
616 send_eivr_event(eivr_events
, f
->subclass
, NULL
, chan
);
617 if (u
->option_autoclear
) {
618 if (!u
->abort_current_sound
&& !u
->playing_silence
)
619 send_eivr_event(eivr_events
, 'T', NULL
, chan
);
620 AST_LIST_LOCK(&u
->playlist
);
621 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
))) {
622 send_eivr_event(eivr_events
, 'D', entry
->filename
, chan
);
625 if (!u
->playing_silence
)
626 u
->abort_current_sound
= 1;
627 AST_LIST_UNLOCK(&u
->playlist
);
629 } else if ((f
->frametype
== AST_FRAME_CONTROL
) && (f
->subclass
== AST_CONTROL_HANGUP
)) {
630 ast_chan_log(LOG_NOTICE
, chan
, "Got AST_CONTROL_HANGUP\n");
631 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
632 if (f
->data
.uint32
) {
633 chan
->hangupcause
= f
->data
.uint32
;
640 } else if (ready_fd
== eivr_commands_fd
) {
643 if (exception
|| (dup2(eivr_commands_fd
, test_available_fd
) == -1) || feof(eivr_commands
)) {
644 ast_chan_log(LOG_WARNING
, chan
, "Child process went away\n");
649 if (!fgets(input
, sizeof(input
), eivr_commands
))
652 command
= ast_strip(input
);
655 ast_debug(1, "got command '%s'\n", input
);
657 if (strlen(input
) < 4)
660 if (input
[0] == 'P') {
661 send_eivr_event(eivr_events
, 'P', args
->str
, chan
);
662 } else if ( input
[0] == 'T' ) {
663 ast_chan_log(LOG_WARNING
, chan
, "Answering channel if needed and starting generator\n");
664 if (chan
->_state
!= AST_STATE_UP
) {
665 if (ast_test_flag(&flags
, run_dead
)) {
666 ast_chan_log(LOG_WARNING
, chan
, "Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
667 send_eivr_event(eivr_events
, 'Z', "ANSWER_FAILURE", chan
);
672 if (!(u
->gen_active
)) {
673 if (ast_activate_generator(chan
, &gen
, u
) < 0) {
674 ast_chan_log(LOG_WARNING
, chan
, "Failed to activate generator\n");
675 send_eivr_event(eivr_events
, 'Z', "GENERATOR_FAILURE", chan
);
680 } else if (input
[0] == 'S') {
681 if (chan
->_state
!= AST_STATE_UP
|| ast_check_hangup(chan
)) {
682 ast_chan_log(LOG_WARNING
, chan
, "Queue 'S'et called on unanswered channel\n");
683 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
686 if (ast_fileexists(&input
[2], NULL
, u
->chan
->language
) == -1) {
687 ast_chan_log(LOG_WARNING
, chan
, "Unknown file requested '%s'\n", &input
[2]);
688 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
689 strcpy(&input
[2], "exception");
691 if (!u
->abort_current_sound
&& !u
->playing_silence
)
692 send_eivr_event(eivr_events
, 'T', NULL
, chan
);
693 AST_LIST_LOCK(&u
->playlist
);
694 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
))) {
695 send_eivr_event(eivr_events
, 'D', entry
->filename
, chan
);
698 if (!u
->playing_silence
)
699 u
->abort_current_sound
= 1;
700 entry
= make_entry(&input
[2]);
702 AST_LIST_INSERT_TAIL(&u
->playlist
, entry
, list
);
703 AST_LIST_UNLOCK(&u
->playlist
);
704 } else if (input
[0] == 'A') {
705 if (chan
->_state
!= AST_STATE_UP
|| ast_check_hangup(chan
)) {
706 ast_chan_log(LOG_WARNING
, chan
, "Queue 'A'ppend called on unanswered channel\n");
707 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
710 if (ast_fileexists(&input
[2], NULL
, u
->chan
->language
) == -1) {
711 ast_chan_log(LOG_WARNING
, chan
, "Unknown file requested '%s'\n", &input
[2]);
712 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
713 strcpy(&input
[2], "exception");
715 entry
= make_entry(&input
[2]);
717 AST_LIST_LOCK(&u
->playlist
);
718 AST_LIST_INSERT_TAIL(&u
->playlist
, entry
, list
);
719 AST_LIST_UNLOCK(&u
->playlist
);
721 } else if (input
[0] == 'G') {
722 /* A get variable message: "G,variable1,variable2,..." */
725 ast_chan_log(LOG_NOTICE
, chan
, "Getting a Variable out of the channel: %s\n", &input
[2]);
726 ast_eivr_getvariable(chan
, &input
[2], response
, sizeof(response
));
727 send_eivr_event(eivr_events
, 'G', response
, chan
);
728 } else if (input
[0] == 'V') {
729 /* A set variable message: "V,variablename=foo" */
730 ast_chan_log(LOG_NOTICE
, chan
, "Setting a Variable up: %s\n", &input
[2]);
731 ast_eivr_setvariable(chan
, &input
[2]);
732 } else if (input
[0] == 'L') {
733 ast_chan_log(LOG_NOTICE
, chan
, "Log message from EIVR: %s\n", &input
[2]);
734 } else if (input
[0] == 'X') {
735 ast_chan_log(LOG_NOTICE
, chan
, "Exiting ExternalIVR: %s\n", &input
[2]);
736 /*! \todo add deprecation debug message for X command here */
739 } else if (input
[0] == 'E') {
740 ast_chan_log(LOG_NOTICE
, chan
, "Exiting: %s\n", &input
[2]);
741 send_eivr_event(eivr_events
, 'E', NULL
, chan
);
744 } else if (input
[0] == 'H') {
745 ast_chan_log(LOG_NOTICE
, chan
, "Hanging up: %s\n", &input
[2]);
746 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
749 } else if (input
[0] == 'O') {
750 if (chan
->_state
!= AST_STATE_UP
|| ast_check_hangup(chan
)) {
751 ast_chan_log(LOG_WARNING
, chan
, "Option called on unanswered channel\n");
752 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
755 if (!strcasecmp(&input
[2], "autoclear"))
756 u
->option_autoclear
= 1;
757 else if (!strcasecmp(&input
[2], "noautoclear"))
758 u
->option_autoclear
= 0;
760 ast_chan_log(LOG_WARNING
, chan
, "Unknown option requested '%s'\n", &input
[2]);
762 } else if (eivr_errors_fd
&& ready_fd
== eivr_errors_fd
) {
765 if (exception
|| feof(eivr_errors
)) {
766 ast_chan_log(LOG_WARNING
, chan
, "Child process went away\n");
770 if (fgets(input
, sizeof(input
), eivr_errors
)) {
771 command
= ast_strip(input
);
772 ast_chan_log(LOG_NOTICE
, chan
, "stderr: %s\n", command
);
774 } else if ((ready_fd
< 0) && ms
) {
775 if (errno
== 0 || errno
== EINTR
)
778 ast_chan_log(LOG_WARNING
, chan
, "Wait failed (%s)\n", strerror(errno
));
786 if (test_available_fd
> -1) {
787 close(test_available_fd
);
794 fclose(eivr_commands
);
803 static int unload_module(void)
805 return ast_unregister_application(app
);
808 static int load_module(void)
810 return ast_register_application(app
, app_exec
, synopsis
, descrip
);
813 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "External IVR Interface Application");