95 static const char app[] =
"ExternalIVR";
98 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, ast_channel_name(channel) , ## __VA_ARGS__)
101 #define EIVR_CMD_APND 'A'
102 #define EIVR_CMD_DTMF 'D'
103 #define EIVR_CMD_EXIT 'E'
104 #define EIVR_CMD_GET 'G'
105 #define EIVR_CMD_HGUP 'H'
106 #define EIVR_CMD_IRPT 'I'
107 #define EIVR_CMD_LOG 'L'
108 #define EIVR_CMD_OPT 'O'
109 #define EIVR_CMD_PARM 'P'
110 #define EIVR_CMD_SQUE 'S'
111 #define EIVR_CMD_ANS 'T'
112 #define EIVR_CMD_SVAR 'V'
113 #define EIVR_CMD_XIT 'X'
115 #define EXTERNALIVR_PORT 2949
119 ignore_hangup = (1 << 1),
138 int abort_current_sound;
140 int option_autoclear;
158 static void send_eivr_event(
struct ast_iostream *stream,
const char event,
const char *data,
175 static void *gen_alloc(
struct ast_channel *chan,
void *params)
188 static void gen_closestream(
struct gen_state *state)
194 ast_channel_stream_set(state->u->chan, NULL);
195 state->stream = NULL;
198 static void gen_release(
struct ast_channel *chan,
void *data)
202 gen_closestream(state);
207 static int gen_nextfile(
struct gen_state *state)
210 char *file_to_stream;
212 u->abort_current_sound = 0;
213 u->playing_silence = 0;
214 gen_closestream(state);
216 while (!state->stream) {
218 if (state->current) {
219 file_to_stream = state->current->filename;
221 file_to_stream =
"silence/10";
222 u->playing_silence = 1;
225 if (!(state->stream =
ast_openstream_full(u->chan, file_to_stream, ast_channel_language(u->chan), 1))) {
226 ast_chan_log(LOG_WARNING, u->chan,
"File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
230 if (!u->playing_silence) {
238 return (!state->stream);
246 if (u->abort_current_sound ||
248 gen_closestream(state);
254 if (!(state->stream && (f =
ast_readframe(state->stream)))) {
255 if (state->current) {
264 state->current = NULL;
266 if (!gen_nextfile(state))
273 static int gen_generate(
struct ast_channel *chan,
void *data,
int len,
int samples)
279 state->sample_queue +=
samples;
281 while (state->sample_queue > 0) {
282 if (!(f = gen_readframe(state)))
288 ast_chan_log(LOG_WARNING, chan,
"Failed to write frame: %s\n", strerror(errno));
291 state->sample_queue -= f->
samples;
300 .release = gen_release,
301 .generate = gen_generate,
304 static void ast_eivr_getvariable(
struct ast_channel *chan,
char *data,
char *outbuf,
int outbuflen)
309 char *
inbuf, *variable;
312 struct ast_str *newstring = ast_str_alloca(outbuflen);
316 for (j = 1, inbuf = data; ; j++) {
317 variable = strsep(&inbuf,
",");
318 if (variable == NULL) {
319 int outstrlen = strlen(outbuf);
320 if (outstrlen && outbuf[outstrlen - 1] ==
',') {
321 outbuf[outstrlen - 1] = 0;
326 ast_channel_lock(chan);
332 ast_channel_unlock(chan);
337 static void ast_eivr_setvariable(
struct ast_channel *chan,
char *data)
343 for (variable = strsep(&inbuf,
","); variable; variable = strsep(&inbuf,
",")) {
344 ast_debug(1,
"Setting up a variable: %s\n", variable);
346 value = strchr(variable,
'=');
356 static void ast_eivr_senddtmf(
struct ast_channel *chan,
char *vdata)
360 int dinterval = 0, duration = 0;
370 if (!ast_strlen_zero(args.dinterval)) {
373 if (!ast_strlen_zero(args.duration)) {
376 ast_verb(4,
"Sending DTMF: %s %d %d\n", args.digits, dinterval <= 0 ? 250 : dinterval, duration);
377 ast_dtmf_stream(chan, NULL, args.digits, dinterval <= 0 ? 250 : dinterval, duration);
384 if (!(entry =
ast_calloc(1,
sizeof(*entry) + strlen(filename) + 10)))
387 strcpy(entry->filename, filename);
392 static int app_exec(
struct ast_channel *chan,
const char *data)
397 int child_stdin[2] = { -1, -1 };
398 int child_stdout[2] = { -1, -1 };
399 int child_stderr[2] = { -1, -1 };
400 struct ast_iostream *stream_stdin = NULL, *stream_stdout = NULL,
401 *stream_stderr = NULL;
411 .playing_silence = 1,
417 char *s, **app_args, *e;
418 struct ast_str *comma_delim_args = ast_str_alloca(100);
428 u->abort_current_sound = 0;
431 if (ast_strlen_zero(data)) {
432 ast_log(LOG_ERROR,
"ExternalIVR requires a command to execute\n");
439 ast_verb(4,
"ExternalIVR received application and arguments: %s\n", eivr_args.application);
440 ast_verb(4,
"ExternalIVR received options: %s\n", eivr_args.options);
443 if ((s = strchr(eivr_args.application,
'('))) {
445 if ((e = strrchr(s,
')'))) {
448 ast_log(LOG_ERROR,
"Parse error, missing closing parenthesis\n");
454 app_args = application_args.argv;
458 for (j = 0; application_args.cmd[j] != NULL; j++) {
459 ast_str_append(&comma_delim_args, 0,
"%s%s", j == 0 ?
"" :
",", application_args.cmd[j]);
463 if (eivr_args.options && (s = strchr(eivr_args.options,
','))) {
468 ast_verb(4,
"Parsing options from: [%s]\n", eivr_args.options);
470 if (ast_test_flag(&flags, noanswer)) {
471 ast_verb(4,
"noanswer is set\n");
473 if (ast_test_flag(&flags, ignore_hangup)) {
474 ast_verb(4,
"ignore_hangup is set\n");
476 if (ast_test_flag(&flags, run_dead)) {
477 ast_verb(4,
"run_dead is set\n");
480 if (!(ast_test_flag(&flags, noanswer))) {
481 ast_verb(3,
"Answering channel and starting generator\n");
483 if (ast_test_flag(&flags, run_dead)) {
484 ast_chan_log(LOG_ERROR, chan,
"Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
490 ast_chan_log(LOG_ERROR, chan,
"Failed to activate generator\n");
497 if (!strncmp(app_args[0],
"ivr://",
sizeof(
"ivr://") - 1)) {
503 int num_addrs = 0, i = 0;
504 char *host = app_args[0] +
sizeof(
"ivr://") - 1;
507 ast_debug(1,
"Parsing hostname/port for socket connect from \"%s\"\n", host);
510 ast_chan_log(LOG_ERROR, chan,
"Unable to locate host '%s'\n", host);
514 for (i = 0; i < num_addrs; i++) {
528 if (i == num_addrs) {
529 ast_chan_log(LOG_ERROR, chan,
"Could not connect to any host. ExternalIVR failed.\n");
533 res = eivr_comm(chan, u, ser->
stream, ser->
stream, NULL, comma_delim_args, flags);
536 if (pipe(child_stdin)) {
537 ast_chan_log(LOG_ERROR, chan,
"Could not create pipe for child input: %s\n", strerror(errno));
540 if (pipe(child_stdout)) {
541 ast_chan_log(LOG_ERROR, chan,
"Could not create pipe for child output: %s\n", strerror(errno));
544 if (pipe(child_stderr)) {
545 ast_chan_log(LOG_ERROR, chan,
"Could not create pipe for child errors: %s\n", strerror(errno));
551 ast_log(LOG_ERROR,
"Failed to fork(): %s\n", strerror(errno));
557 if (ast_opt_high_priority)
560 dup2(child_stdin[0], STDIN_FILENO);
561 dup2(child_stdout[1], STDOUT_FILENO);
562 dup2(child_stderr[1], STDERR_FILENO);
564 execv(app_args[0], app_args);
565 fprintf(stderr,
"Failed to execute '%s': %s\n", app_args[0], strerror(errno));
569 close(child_stdin[0]);
571 close(child_stdout[1]);
572 child_stdout[1] = -1;
573 close(child_stderr[1]);
574 child_stderr[1] = -1;
580 res = eivr_comm(chan, u, stream_stdin, stream_stdout, stream_stderr, comma_delim_args, flags);
597 if (child_stdin[0] > -1) {
598 close(child_stdin[0]);
600 if (child_stdin[1] > -1) {
601 close(child_stdin[1]);
603 if (child_stdout[0] > -1) {
604 close(child_stdout[0]);
606 if (child_stdout[1] > -1) {
607 close(child_stdout[1]);
609 if (child_stderr[0] > -1) {
610 close(child_stderr[0]);
612 if (child_stderr[1] > -1) {
613 close(child_stderr[1]);
640 int hangup_info_sent = 0;
647 ast_chan_log(LOG_ERROR, chan,
"Is a zombie\n");
650 if (!hangup_info_sent && !(ast_test_flag(&flags, run_dead)) &&
ast_check_hangup(chan)) {
651 if (ast_test_flag(&flags, ignore_hangup)) {
652 ast_verb(3,
"Got check_hangup, but ignore_hangup set so sending 'I' command\n");
653 send_eivr_event(eivr_events,
'I',
"HANGUP", chan);
654 hangup_info_sent = 1;
656 ast_verb(3,
"Got check_hangup\n");
657 send_eivr_event(eivr_events,
'H', NULL, chan);
667 rchan =
ast_waitfor_nandfds(&chan, 1, waitfds, (eivr_errors) ? 2 : 1, &exception, &ready_fd, &ms);
672 send_eivr_event(eivr_events,
'F', entry->filename, chan);
682 ast_verb(3,
"Returned no frame\n");
683 send_eivr_event(eivr_events,
'H', NULL, chan);
688 if (u->option_autoclear) {
690 if (!u->abort_current_sound && !u->playing_silence) {
693 send_eivr_event(eivr_events,
'T', entry->filename, chan);
698 send_eivr_event(eivr_events,
'D', entry->filename, chan);
701 if (!u->playing_silence)
702 u->abort_current_sound = 1;
706 ast_verb(3,
"Got AST_CONTROL_HANGUP\n");
707 send_eivr_event(eivr_events,
'H', NULL, chan);
708 if (f->
data.uint32) {
709 ast_channel_hangupcause_set(chan, f->
data.uint32);
715 }
else if (ready_fd == waitfds[0]) {
717 ast_chan_log(LOG_ERROR, chan,
"Child process went away\n");
724 ast_chan_log(LOG_ERROR, chan,
"Child process went away\n");
731 ast_verb(4,
"got command '%s'\n", input);
733 if (strlen(input) < 3) {
737 if (input[0] == EIVR_CMD_PARM) {
740 }
else if (input[0] == EIVR_CMD_DTMF) {
741 ast_verb(4,
"Sending DTMF: %s\n", &input[2]);
742 ast_eivr_senddtmf(chan, &input[2]);
743 }
else if (input[0] == EIVR_CMD_ANS) {
744 ast_verb(3,
"Answering channel if needed and starting generator\n");
746 if (ast_test_flag(&flags, run_dead)) {
747 ast_chan_log(LOG_WARNING, chan,
"Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
748 send_eivr_event(eivr_events,
'Z',
"ANSWER_FAILURE", chan);
752 ast_chan_log(LOG_WARNING, chan,
"Failed to answer channel\n");
753 send_eivr_event(eivr_events,
'Z',
"ANSWER_FAILURE", chan);
757 if (!(u->gen_active)) {
759 ast_chan_log(LOG_WARNING, chan,
"Failed to activate generator\n");
760 send_eivr_event(eivr_events,
'Z',
"GENERATOR_FAILURE", chan);
765 }
else if (input[0] == EIVR_CMD_IRPT) {
767 ast_chan_log(LOG_WARNING, chan,
"Queue 'I'nterrupt called on unanswered channel\n");
768 send_eivr_event(eivr_events,
'Z', NULL, chan);
772 if (!u->abort_current_sound && !u->playing_silence) {
775 send_eivr_event(eivr_events,
'T', entry->filename, chan);
780 send_eivr_event(eivr_events,
'D', entry->filename, chan);
783 if (!u->playing_silence) {
784 u->abort_current_sound = 1;
787 }
else if (input[0] == EIVR_CMD_SQUE) {
789 ast_chan_log(LOG_WARNING, chan,
"Queue re'S'et called on unanswered channel\n");
790 send_eivr_event(eivr_events,
'Z', NULL, chan);
793 if (!
ast_fileexists(&input[2], NULL, ast_channel_language(u->chan))) {
794 ast_chan_log(LOG_WARNING, chan,
"Unknown file requested '%s'\n", &input[2]);
795 send_eivr_event(eivr_events,
'Z', &input[2], chan);
798 if (!u->abort_current_sound && !u->playing_silence) {
801 send_eivr_event(eivr_events,
'T', entry->filename, chan);
806 send_eivr_event(eivr_events,
'D', entry->filename, chan);
809 if (!u->playing_silence) {
810 u->abort_current_sound = 1;
812 entry = make_entry(&input[2]);
818 }
else if (input[0] == EIVR_CMD_APND) {
820 ast_chan_log(LOG_WARNING, chan,
"Queue 'A'ppend called on unanswered channel\n");
821 send_eivr_event(eivr_events,
'Z', NULL, chan);
824 if (!
ast_fileexists(&input[2], NULL, ast_channel_language(u->chan))) {
825 ast_chan_log(LOG_WARNING, chan,
"Unknown file requested '%s'\n", &input[2]);
826 send_eivr_event(eivr_events,
'Z', &input[2], chan);
828 entry = make_entry(&input[2]);
835 }
else if (input[0] == EIVR_CMD_GET) {
837 ast_verb(4,
"Retriving Variables from channel: %s\n", &input[2]);
838 ast_eivr_getvariable(chan, &input[2], response,
sizeof(response));
839 send_eivr_event(eivr_events,
'G', response, chan);
840 }
else if (input[0] == EIVR_CMD_SVAR) {
841 ast_verb(4,
"Setting Variables in channel: %s\n", &input[2]);
842 ast_eivr_setvariable(chan, &input[2]);
843 }
else if (input[0] == EIVR_CMD_LOG) {
844 ast_chan_log(LOG_NOTICE, chan,
"Log message from EIVR: %s\n", &input[2]);
845 }
else if (input[0] == EIVR_CMD_XIT) {
846 ast_chan_log(LOG_NOTICE, chan,
"Exiting: %s\n", &input[2]);
847 ast_chan_log(LOG_WARNING, chan,
"e'X'it command is depricated, use 'E'xit instead\n");
850 }
else if (input[0] == EIVR_CMD_EXIT) {
851 ast_chan_log(LOG_NOTICE, chan,
"Exiting: %s\n", &input[2]);
852 send_eivr_event(eivr_events,
'E', NULL, chan);
855 }
else if (input[0] == EIVR_CMD_HGUP) {
856 ast_chan_log(LOG_NOTICE, chan,
"Hanging up: %s\n", &input[2]);
857 send_eivr_event(eivr_events,
'H', NULL, chan);
859 }
else if (input[0] == EIVR_CMD_OPT) {
861 ast_chan_log(LOG_WARNING, chan,
"Option called on unanswered channel\n");
862 send_eivr_event(eivr_events,
'Z', NULL, chan);
865 if (!strcasecmp(&input[2],
"autoclear"))
866 u->option_autoclear = 1;
867 else if (!strcasecmp(&input[2],
"noautoclear"))
868 u->option_autoclear = 0;
870 ast_chan_log(LOG_WARNING, chan,
"Unknown option requested: %s\n", &input[2]);
872 }
else if (ready_fd == waitfds[1]) {
874 ast_chan_log(LOG_ERROR, chan,
"Child process went away\n");
880 ast_chan_log(LOG_NOTICE, chan,
"stderr: %s\n",
ast_strip(input));
882 ast_chan_log(LOG_ERROR, chan,
"Child process went away\n");
885 }
else if ((ready_fd < 0) && ms) {
886 if (errno == 0 || errno == EINTR)
889 ast_chan_log(LOG_ERROR, chan,
"Wait failed (%s)\n", strerror(errno));
897 static int unload_module(
void)
902 static int load_module(
void)
907 AST_MODULE_INFO_STANDARD_EXTENDED(
ASTERISK_GPL_KEY,
"External IVR Interface Application");
int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const char *digits, int between, unsigned int duration)
Send a string of DTMF digits to a channel.
Main Channel structure associated with a channel.
#define AST_LIST_LOCK(head)
Locks a list.
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen, void *params)
static void ast_sockaddr_copy(struct ast_sockaddr *dst, const struct ast_sockaddr *src)
Copies the data from one ast_sockaddr to another.
ssize_t ast_iostream_write(struct ast_iostream *stream, const void *buffer, size_t count)
Write data to an iostream.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
void ast_close_fds_above_n(int n)
Common routine for child processes, to close all fds prior to exec(2)
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
ast_channel_state
ast_channel states
struct ast_iostream * ast_iostream_from_fd(int *fd)
Create an iostream from a file descriptor.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
int ast_iostream_get_fd(struct ast_iostream *stream)
Get an iostream's file descriptor.
arguments for the accepting thread
struct ast_filestream * ast_openstream_full(struct ast_channel *chan, const char *filename, const char *preflang, int asis)
Opens stream for use in seeking, playing.
#define AST_LIST_EMPTY(head)
Checks whether the specified list contains any entries.
struct ast_channel * ast_waitfor_nandfds(struct ast_channel **c, int n, int *fds, int nfds, int *exception, int *outfd, int *ms)
Waits for activity on a group of channels.
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
Generic support for tcp/tls servers in Asterisk.
int ast_unregister_application(const char *app)
Unregister an application.
Socket address structure.
static int inbuf(struct baseio *bio, FILE *fi)
utility used by inchar(), for base_encode()
const char * pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
Return a pointer to the value of the corresponding channel variable.
struct ast_frame_subclass subclass
char * ast_str_truncate(struct ast_str *buf, ssize_t len)
Truncates the enclosed string to the given length.
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
#define ast_sockaddr_port(addr)
Get the port number of a socket address.
int ast_iostream_close(struct ast_iostream *stream)
Close an iostream.
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
A set of macros to manage forward-linked lists.
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Core PBX routines and definitions.
describes a server instance
int ast_check_hangup(struct ast_channel *chan)
Check to see if a channel is needing hang up.
int ast_set_priority(int)
We set ourselves to a high priority, that we might pre-empt everything else. If your PBX has heavy ac...
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Support for dynamic strings.
#define ast_sockaddr_set_port(addr, port)
Sets the port number of a socket address.
struct ast_frame * ast_readframe(struct ast_filestream *s)
Read a frame from a filestream.
int ast_safe_fork(int stop_reaper)
Common routine to safely fork without a chance of a signal handler firing badly in the child...
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
union ast_frame::@224 data
#define ast_calloc(num, len)
A wrapper for calloc()
int ast_closestream(struct ast_filestream *f)
Closes a stream.
int ast_write(struct ast_channel *chan, struct ast_frame *frame)
Write a frame to a channel This function writes the given frame to the indicated channel.
Structure used to handle boolean flags.
struct ast_iostream * stream
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name...
ssize_t ast_iostream_gets(struct ast_iostream *stream, char *buffer, size_t size)
Read a LF-terminated string from an iostream.
void ast_str_reset(struct ast_str *buf)
Reset the content of a dynamic string. Useful before a series of ast_str_append.
void ast_deactivate_generator(struct ast_channel *chan)
This structure is allocated by file.c in one chunk, together with buf_size and desc_size bytes of mem...
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
struct ast_tcptls_session_instance * ast_tcptls_client_create(struct ast_tcptls_session_args *desc)
Creates a client connection's ast_tcptls_session_instance.
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
int ast_answer(struct ast_channel *chan)
Answer a channel.
Data structure associated with a single frame of data.
struct ast_tcptls_session_instance * ast_tcptls_client_start(struct ast_tcptls_session_instance *tcptls_session)
Attempt to connect and start a tcptls session.
enum ast_frame_type frametype
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
int ast_app_parse_timelen(const char *timestr, int *result, enum ast_timelen defunit)
Common routine to parse time lengths, with optional time unit specifier.
#define AST_LIST_HEAD_INIT_VALUE
Defines initial values for a declaration of AST_LIST_HEAD.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
int ast_sockaddr_resolve(struct ast_sockaddr **addrs, const char *str, int flags, int family)
Parses a string with an IPv4 or IPv6 address and place results into an array.
#define AST_APP_ARG(name)
Define an application argument.