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";
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";
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__)
67 struct playlist_entry
{
68 AST_LIST_ENTRY(playlist_entry
) list
;
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
;
83 struct ivr_localuser
*u
;
84 struct ast_filestream
*stream
;
85 struct playlist_entry
*current
;
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
,
93 int eivr_connect_socket(struct ast_channel
*chan
, const char *host
, int port
);
95 static void send_eivr_event(FILE *handle
, const char event
, const char *data
,
96 const struct ast_channel
*chan
)
101 snprintf(tmp
, sizeof(tmp
), "%c,%10d", event
, (int)time(NULL
));
103 snprintf(tmp
, sizeof(tmp
), "%c,%10d,%s", event
, (int)time(NULL
), data
);
106 fprintf(handle
, "%s\n", tmp
);
108 ast_chan_log(LOG_DEBUG
, chan
, "sent '%s'\n", tmp
);
111 static void *gen_alloc(struct ast_channel
*chan
, void *params
)
113 struct ivr_localuser
*u
= params
;
114 struct gen_state
*state
;
116 if (!(state
= ast_calloc(1, sizeof(*state
))))
124 static void gen_closestream(struct gen_state
*state
)
129 ast_closestream(state
->stream
);
130 state
->u
->chan
->stream
= NULL
;
131 state
->stream
= NULL
;
134 static void gen_release(struct ast_channel
*chan
, void *data
)
136 struct gen_state
*state
= data
;
138 gen_closestream(state
);
142 /* caller has the playlist locked */
143 static int gen_nextfile(struct gen_state
*state
)
145 struct ivr_localuser
*u
= state
->u
;
146 char *file_to_stream
;
148 u
->abort_current_sound
= 0;
149 u
->playing_silence
= 0;
150 gen_closestream(state
);
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
;
157 file_to_stream
= "silence/10";
158 u
->playing_silence
= 1;
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
) {
171 return (!state
->stream
);
174 static struct ast_frame
*gen_readframe(struct gen_state
*state
)
176 struct ast_frame
*f
= NULL
;
177 struct ivr_localuser
*u
= state
->u
;
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
);
184 AST_LIST_UNLOCK(&u
->playlist
);
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
;
194 if (!gen_nextfile(state
))
195 f
= ast_readframe(state
->stream
);
201 static int gen_generate(struct ast_channel
*chan
, void *data
, int len
, int samples
)
203 struct gen_state
*state
= data
;
204 struct ast_frame
*f
= NULL
;
207 state
->sample_queue
+= samples
;
209 while (state
->sample_queue
> 0) {
210 if (!(f
= gen_readframe(state
)))
213 res
= ast_write(chan
, f
);
216 ast_chan_log(LOG_WARNING
, chan
, "Failed to write frame: %s\n", strerror(errno
));
219 state
->sample_queue
-= f
->samples
;
225 static struct ast_generator gen
=
228 release
: gen_release
,
229 generate
: gen_generate
,
232 static void ast_eivr_getvariable(struct ast_channel
*chan
, char *data
, char *outbuf
, int outbuflen
)
234 /* original input data: "G,var1,var2," */
235 /* data passed as "data": "var1,var2" */
237 char *inbuf
, *variable
;
240 struct ast_str
*newstring
= ast_str_alloca(outbuflen
);
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;
254 ast_channel_lock(chan
);
255 if (!(value
= pbx_builtin_getvar_helper(chan
, variable
))) {
259 ast_str_append(&newstring
, 0, "%s=%s,", variable
, value
);
260 ast_channel_unlock(chan
);
261 ast_copy_string(outbuf
, newstring
->str
, outbuflen
);
265 static void ast_eivr_setvariable(struct ast_channel
*chan
, char *data
)
270 char *inbuf
, *variable
;
274 for (j
= 1, inbuf
= data
; ; j
++, inbuf
= NULL
) {
275 variable
= strsep(&inbuf
, ",");
276 ast_chan_log(LOG_DEBUG
, chan
, "Setting up a variable: %s\n", variable
);
278 /* variable contains "varname=value" */
279 ast_copy_string(buf
, variable
, sizeof(buf
));
280 value
= strchr(buf
, '=');
285 pbx_builtin_setvar_helper(chan
, buf
, value
);
292 static struct playlist_entry
*make_entry(const char *filename
)
294 struct playlist_entry
*entry
;
296 if (!(entry
= ast_calloc(1, sizeof(*entry
) + strlen(filename
) + 10))) /* XXX why 10 ? */
299 strcpy(entry
->filename
, filename
);
304 static int app_exec(struct ast_channel
*chan
, void *data
)
306 struct playlist_entry
*entry
;
307 int child_stdin
[2] = { 0,0 };
308 int child_stdout
[2] = { 0,0 };
309 int child_stderr
[2] = { 0,0 };
313 char *buf
, *pipe_delim_argbuf
, *pdargbuf_ptr
;
316 char *port_str
= NULL
;
318 struct ast_tcptls_session_instance
*ser
= NULL
;
320 struct ivr_localuser foo
= {
321 .playlist
= AST_LIST_HEAD_INIT_VALUE
,
322 .finishlist
= AST_LIST_HEAD_INIT_VALUE
,
324 struct ivr_localuser
*u
= &foo
;
325 AST_DECLARE_APP_ARGS(args
,
326 AST_APP_ARG(cmd
)[32];
329 u
->abort_current_sound
= 0;
332 if (ast_strlen_zero(data
)) {
333 ast_log(LOG_WARNING
, "ExternalIVR requires a command to execute\n");
337 buf
= ast_strdupa(data
);
338 AST_STANDARD_APP_ARGS(args
, buf
);
340 /* copy args and replace commas with pipes */
341 pipe_delim_argbuf
= ast_strdupa(data
);
342 while((pdargbuf_ptr
= strchr(pipe_delim_argbuf
, ',')) != NULL
)
343 pdargbuf_ptr
[0] = '|';
345 if(!strncmp(args
.cmd
[0], "ivr://", 6)) {
346 struct server_args ivr_desc
= {
350 struct ast_hostent hp
;
352 /*communicate through socket to server*/
353 if (chan
->_state
!= AST_STATE_UP
) {
356 if (ast_activate_generator(chan
, &gen
, u
) < 0) {
357 ast_chan_log(LOG_WARNING
, chan
, "Failed to activate generator\n");
363 ast_chan_log(LOG_DEBUG
, chan
, "Parsing hostname:port for socket connect from \"%s\"\n", args
.cmd
[0]);
364 strncpy(hostname
, args
.cmd
[0] + 6, sizeof(hostname
));
365 if((port_str
= strchr(hostname
, ':')) != NULL
) {
368 port
= atoi(port_str
);
371 port
= 2949; /*default port, if one is not provided*/
373 ast_gethostbyname(hostname
, &hp
);
374 ivr_desc
.sin
.sin_family
= AF_INET
;
375 ivr_desc
.sin
.sin_port
= htons(port
);
376 memmove(&ivr_desc
.sin
.sin_addr
.s_addr
, hp
.hp
.h_addr
, hp
.hp
.h_length
);
377 ser
= ast_tcptls_client_start(&ivr_desc
);
382 res
= eivr_comm(chan
, u
, ser
->fd
, ser
->fd
, 0, pipe_delim_argbuf
);
385 if (pipe(child_stdin
)) {
386 ast_chan_log(LOG_WARNING
, chan
, "Could not create pipe for child input: %s\n", strerror(errno
));
389 if (pipe(child_stdout
)) {
390 ast_chan_log(LOG_WARNING
, chan
, "Could not create pipe for child output: %s\n", strerror(errno
));
393 if (pipe(child_stderr
)) {
394 ast_chan_log(LOG_WARNING
, chan
, "Could not create pipe for child errors: %s\n", strerror(errno
));
397 if (chan
->_state
!= AST_STATE_UP
) {
400 if (ast_activate_generator(chan
, &gen
, u
) < 0) {
401 ast_chan_log(LOG_WARNING
, chan
, "Failed to activate generator\n");
407 pid
= ast_safe_fork(0);
409 ast_log(LOG_WARNING
, "Failed to fork(): %s\n", strerror(errno
));
415 if (ast_opt_high_priority
)
418 dup2(child_stdin
[0], STDIN_FILENO
);
419 dup2(child_stdout
[1], STDOUT_FILENO
);
420 dup2(child_stderr
[1], STDERR_FILENO
);
421 ast_close_fds_above_n(STDERR_FILENO
);
422 execv(args
.cmd
[0], args
.cmd
);
423 fprintf(stderr
, "Failed to execute '%s': %s\n", args
.cmd
[0], strerror(errno
));
428 close(child_stdin
[0]);
430 close(child_stdout
[1]);
432 close(child_stderr
[1]);
434 res
= eivr_comm(chan
, u
, child_stdin
[1], child_stdout
[0], child_stderr
[0], pipe_delim_argbuf
);
440 ast_deactivate_generator(chan
);
443 close(child_stdin
[0]);
446 close(child_stdin
[1]);
449 close(child_stdout
[0]);
452 close(child_stdout
[1]);
455 close(child_stderr
[0]);
458 close(child_stderr
[1]);
462 ast_tcptls_session_instance_destroy(ser
);
465 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
)))
471 static int eivr_comm(struct ast_channel
*chan
, struct ivr_localuser
*u
,
472 int eivr_events_fd
, int eivr_commands_fd
, int eivr_errors_fd
,
475 struct playlist_entry
*entry
;
480 int waitfds
[2] = { eivr_commands_fd
, eivr_errors_fd
};
481 struct ast_channel
*rchan
;
485 FILE *eivr_commands
= NULL
;
486 FILE *eivr_errors
= NULL
;
487 FILE *eivr_events
= NULL
;
489 if (!(eivr_events
= fdopen(eivr_events_fd
, "w"))) {
490 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to send events\n");
493 if (!(eivr_commands
= fdopen(eivr_commands_fd
, "r"))) {
494 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to receive commands\n");
497 if(eivr_errors_fd
) { /* if opening a socket connection, error stream will not be used */
498 if (!(eivr_errors
= fdopen(eivr_errors_fd
, "r"))) {
499 ast_chan_log(LOG_WARNING
, chan
, "Could not open stream to receive errors\n");
504 setvbuf(eivr_events
, NULL
, _IONBF
, 0);
505 setvbuf(eivr_commands
, NULL
, _IONBF
, 0);
507 setvbuf(eivr_errors
, NULL
, _IONBF
, 0);
512 if (ast_test_flag(chan
, AST_FLAG_ZOMBIE
)) {
513 ast_chan_log(LOG_NOTICE
, chan
, "Is a zombie\n");
517 if (ast_check_hangup(chan
)) {
518 ast_chan_log(LOG_NOTICE
, chan
, "Got check_hangup\n");
519 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
529 rchan
= ast_waitfor_nandfds(&chan
, 1, waitfds
, 2, &exception
, &ready_fd
, &ms
);
531 if (!AST_LIST_EMPTY(&u
->finishlist
)) {
532 AST_LIST_LOCK(&u
->finishlist
);
533 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->finishlist
, list
))) {
534 send_eivr_event(eivr_events
, 'F', entry
->filename
, chan
);
537 AST_LIST_UNLOCK(&u
->finishlist
);
541 /* the channel has something */
544 ast_chan_log(LOG_NOTICE
, chan
, "Returned no frame\n");
545 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
549 if (f
->frametype
== AST_FRAME_DTMF
) {
550 send_eivr_event(eivr_events
, f
->subclass
, NULL
, chan
);
551 if (u
->option_autoclear
) {
552 if (!u
->abort_current_sound
&& !u
->playing_silence
)
553 send_eivr_event(eivr_events
, 'T', NULL
, chan
);
554 AST_LIST_LOCK(&u
->playlist
);
555 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
))) {
556 send_eivr_event(eivr_events
, 'D', entry
->filename
, chan
);
559 if (!u
->playing_silence
)
560 u
->abort_current_sound
= 1;
561 AST_LIST_UNLOCK(&u
->playlist
);
563 } else if ((f
->frametype
== AST_FRAME_CONTROL
) && (f
->subclass
== AST_CONTROL_HANGUP
)) {
564 ast_chan_log(LOG_NOTICE
, chan
, "Got AST_CONTROL_HANGUP\n");
565 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
567 chan
->hangupcause
= f
->seqno
;
574 } else if (ready_fd
== eivr_commands_fd
) {
577 if (exception
|| feof(eivr_commands
)) {
578 ast_chan_log(LOG_WARNING
, chan
, "Child process went away\n");
583 if (!fgets(input
, sizeof(input
), eivr_commands
))
586 command
= ast_strip(input
);
589 ast_chan_log(LOG_DEBUG
, chan
, "got command '%s'\n", input
);
591 if (strlen(input
) < 4)
594 if (input
[0] == 'P') {
595 send_eivr_event(eivr_events
, 'P', args
, chan
);
597 } else if (input
[0] == 'S') {
598 if (ast_fileexists(&input
[2], NULL
, u
->chan
->language
) == -1) {
599 ast_chan_log(LOG_WARNING
, chan
, "Unknown file requested '%s'\n", &input
[2]);
600 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
601 strcpy(&input
[2], "exception");
603 if (!u
->abort_current_sound
&& !u
->playing_silence
)
604 send_eivr_event(eivr_events
, 'T', NULL
, chan
);
605 AST_LIST_LOCK(&u
->playlist
);
606 while ((entry
= AST_LIST_REMOVE_HEAD(&u
->playlist
, list
))) {
607 send_eivr_event(eivr_events
, 'D', entry
->filename
, chan
);
610 if (!u
->playing_silence
)
611 u
->abort_current_sound
= 1;
612 entry
= make_entry(&input
[2]);
614 AST_LIST_INSERT_TAIL(&u
->playlist
, entry
, list
);
615 AST_LIST_UNLOCK(&u
->playlist
);
616 } else if (input
[0] == 'A') {
617 if (ast_fileexists(&input
[2], NULL
, u
->chan
->language
) == -1) {
618 ast_chan_log(LOG_WARNING
, chan
, "Unknown file requested '%s'\n", &input
[2]);
619 send_eivr_event(eivr_events
, 'Z', NULL
, chan
);
620 strcpy(&input
[2], "exception");
622 entry
= make_entry(&input
[2]);
624 AST_LIST_LOCK(&u
->playlist
);
625 AST_LIST_INSERT_TAIL(&u
->playlist
, entry
, list
);
626 AST_LIST_UNLOCK(&u
->playlist
);
628 } else if (input
[0] == 'G') {
629 /* A get variable message: "G,variable1,variable2,..." */
632 ast_chan_log(LOG_NOTICE
, chan
, "Getting a Variable out of the channel: %s\n", &input
[2]);
633 ast_eivr_getvariable(chan
, &input
[2], response
, sizeof(response
));
634 send_eivr_event(eivr_events
, 'G', response
, chan
);
635 } else if (input
[0] == 'V') {
636 /* A set variable message: "V,variablename=foo" */
637 ast_chan_log(LOG_NOTICE
, chan
, "Setting a Variable up: %s\n", &input
[2]);
638 ast_eivr_setvariable(chan
, &input
[2]);
639 } else if (input
[0] == 'L') {
640 ast_chan_log(LOG_NOTICE
, chan
, "Log message from EIVR: %s\n", &input
[2]);
641 } else if (input
[0] == 'X') {
642 ast_chan_log(LOG_NOTICE
, chan
, "Exiting ExternalIVR: %s\n", &input
[2]);
643 /*! \todo add deprecation debug message for X command here */
646 } else if (input
[0] == 'E') {
647 ast_chan_log(LOG_NOTICE
, chan
, "Exiting: %s\n", &input
[2]);
648 send_eivr_event(eivr_events
, 'E', NULL
, chan
);
651 } else if (input
[0] == 'H') {
652 ast_chan_log(LOG_NOTICE
, chan
, "Hanging up: %s\n", &input
[2]);
653 send_eivr_event(eivr_events
, 'H', NULL
, chan
);
656 } else if (input
[0] == 'O') {
657 if (!strcasecmp(&input
[2], "autoclear"))
658 u
->option_autoclear
= 1;
659 else if (!strcasecmp(&input
[2], "noautoclear"))
660 u
->option_autoclear
= 0;
662 ast_chan_log(LOG_WARNING
, chan
, "Unknown option requested '%s'\n", &input
[2]);
664 } else if (eivr_errors_fd
&& ready_fd
== eivr_errors_fd
) {
667 if (exception
|| feof(eivr_errors
)) {
668 ast_chan_log(LOG_WARNING
, chan
, "Child process went away\n");
672 if (fgets(input
, sizeof(input
), eivr_errors
)) {
673 command
= ast_strip(input
);
674 ast_chan_log(LOG_NOTICE
, chan
, "stderr: %s\n", command
);
676 } else if ((ready_fd
< 0) && ms
) {
677 if (errno
== 0 || errno
== EINTR
)
680 ast_chan_log(LOG_WARNING
, chan
, "Wait failed (%s)\n", strerror(errno
));
692 fclose(eivr_commands
);
701 static int unload_module(void)
703 return ast_unregister_application(app
);
706 static int load_module(void)
708 return ast_register_application(app
, app_exec
, synopsis
, descrip
);
711 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "External IVR Interface Application");