Asterisk - The Open Source Telephony Project GIT-master-7e7a603
app_externalivr.c
Go to the documentation of this file.
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/*** MODULEINFO
35 <support_level>extended</support_level>
36 ***/
37
38#include "asterisk.h"
39
40#include <signal.h>
41
42#include "asterisk/lock.h"
43#include "asterisk/file.h"
44#include "asterisk/channel.h"
45#include "asterisk/pbx.h"
46#include "asterisk/module.h"
48#include "asterisk/app.h"
49#include "asterisk/utils.h"
50#include "asterisk/tcptls.h"
51#include "asterisk/astobj2.h"
52
53/*** DOCUMENTATION
54 <application name="ExternalIVR" language="en_US">
55 <synopsis>
56 Interfaces with an external IVR application.
57 </synopsis>
58 <syntax>
59 <parameter name="command|ivr://host" required="true" hasparams="true">
60 <argument name="arg1" />
61 <argument name="arg2" multiple="yes" />
62 </parameter>
63 <parameter name="options">
64 <optionlist>
65 <option name="n">
66 <para>Tells ExternalIVR() not to answer the channel.</para>
67 </option>
68 <option name="i">
69 <para>Tells ExternalIVR() not to send a hangup and exit when the
70 channel receives a hangup, instead it sends an <literal>I</literal>
71 informative message meaning that the external application MUST hang
72 up the call with an <literal>H</literal> command.</para>
73 </option>
74 <option name="d">
75 <para>Tells ExternalIVR() to run on a channel that has been hung up
76 and will not look for hangups. The external application must exit with
77 an <literal>E</literal> command.</para>
78 </option>
79 </optionlist>
80 </parameter>
81 </syntax>
82 <description>
83 <para>Either forks a process to run given command or makes a socket to connect
84 to given host and starts a generator on the channel. The generator's play list
85 is controlled by the external application, which can add and clear entries via
86 simple commands issued over its stdout. The external application will receive
87 all DTMF events received on the channel, and notification if the channel is
88 hung up. The received on the channel, and notification if the channel is hung
89 up. The application will not be forcibly terminated when the channel is hung up.
90 For more information see <filename>doc/AST.pdf</filename>.</para>
91 </description>
92 </application>
93 ***/
94
95static const char app[] = "ExternalIVR";
96
97/* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
98#define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, ast_channel_name(channel) , ## __VA_ARGS__)
99
100/* Commands */
101#define EIVR_CMD_APND 'A' /* append to prompt queue */
102#define EIVR_CMD_DTMF 'D' /* send DTMF */
103#define EIVR_CMD_EXIT 'E' /* exit */
104#define EIVR_CMD_GET 'G' /* get channel varable(s) */
105#define EIVR_CMD_HGUP 'H' /* hangup */
106#define EIVR_CMD_IRPT 'I' /* interrupt */
107#define EIVR_CMD_LOG 'L' /* log message */
108#define EIVR_CMD_OPT 'O' /* option */
109#define EIVR_CMD_PARM 'P' /* return supplied params */
110#define EIVR_CMD_SQUE 'S' /* (re)set prompt queue */
111#define EIVR_CMD_ANS 'T' /* answer channel */
112#define EIVR_CMD_SVAR 'V' /* set channel varable(s) */
113#define EIVR_CMD_XIT 'X' /* exit **depricated** */
114
115#define EXTERNALIVR_PORT 2949
116
118 noanswer = (1 << 0),
119 ignore_hangup = (1 << 1),
120 run_dead = (1 << 2),
121};
122
127});
128
131 char filename[1];
132};
133
142};
143
144
145struct gen_state {
150};
151
152static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
153 struct ast_iostream *eivr_events,
154 struct ast_iostream *eivr_commands,
155 struct ast_iostream *eivr_errors,
156 const struct ast_str *args, const struct ast_flags flags);
157
158static void send_eivr_event(struct ast_iostream *stream, const char event, const char *data,
159 const struct ast_channel *chan)
160{
161 struct ast_str *tmp = ast_str_create(12);
162
163 ast_str_append(&tmp, 0, "%c,%10d", event, (int)time(NULL));
164 if (data) {
165 ast_str_append(&tmp, 0, ",%s", data);
166 }
167 ast_str_append(&tmp, 0, "\n");
170
171 ast_debug(1, "sent '%s'", ast_str_buffer(tmp));
172 ast_free(tmp);
173}
174
175static void *gen_alloc(struct ast_channel *chan, void *params)
176{
177 struct ivr_localuser *u = params;
178 struct gen_state *state;
179
180 if (!(state = ast_calloc(1, sizeof(*state))))
181 return NULL;
182
183 state->u = u;
184
185 return state;
186}
187
188static void gen_closestream(struct gen_state *state)
189{
190 if (!state->stream)
191 return;
192
193 ast_closestream(state->stream);
195 state->stream = NULL;
196}
197
198static void gen_release(struct ast_channel *chan, void *data)
199{
200 struct gen_state *state = data;
201
203 ast_free(data);
204}
205
206/* caller has the playlist locked */
207static int gen_nextfile(struct gen_state *state)
208{
209 struct ivr_localuser *u = state->u;
210 char *file_to_stream;
211
212 u->abort_current_sound = 0;
213 u->playing_silence = 0;
215
216 while (!state->stream) {
217 state->current = AST_LIST_FIRST(&u->playlist);
218 if (state->current) {
219 file_to_stream = state->current->filename;
220 } else {
221 file_to_stream = "silence/10";
222 u->playing_silence = 1;
223 }
224
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) {
231 continue;
232 } else {
233 break;
234 }
235 }
236 }
237
238 return (!state->stream);
239}
240
241static struct ast_frame *gen_readframe(struct gen_state *state)
242{
243 struct ast_frame *f = NULL;
244 struct ivr_localuser *u = state->u;
245
246 if (u->abort_current_sound ||
252 }
253
254 if (!(state->stream && (f = ast_readframe(state->stream)))) {
255 if (state->current) {
256 /* remove finished file from playlist */
260 /* add finished file to finishlist */
262 AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
264 state->current = NULL;
265 }
266 if (!gen_nextfile(state))
267 f = ast_readframe(state->stream);
268 }
269
270 return f;
271}
272
273static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
274{
275 struct gen_state *state = data;
276 struct ast_frame *f = NULL;
277 int res = 0;
278
279 state->sample_queue += samples;
280
281 while (state->sample_queue > 0) {
282 if (!(f = gen_readframe(state)))
283 return -1;
284
285 res = ast_write(chan, f);
286 ast_frfree(f);
287 if (res < 0) {
288 ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
289 return -1;
290 }
291 state->sample_queue -= f->samples;
292 }
293
294 return res;
295}
296
297static struct ast_generator gen =
298{
299 .alloc = gen_alloc,
300 .release = gen_release,
301 .generate = gen_generate,
302};
303
304static void ast_eivr_getvariable(struct ast_channel *chan, char *data, char *outbuf, int outbuflen)
305{
306 /* original input data: "G,var1,var2," */
307 /* data passed as "data": "var1,var2" */
308
309 char *inbuf, *variable;
310 const char *value;
311 int j;
312 struct ast_str *newstring = ast_str_alloca(outbuflen);
313
314 outbuf[0] = '\0';
315
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;
322 }
323 break;
324 }
325
326 ast_channel_lock(chan);
327 if (!(value = pbx_builtin_getvar_helper(chan, variable))) {
328 value = "";
329 }
330
331 ast_str_append(&newstring, 0, "%s=%s,", variable, value);
332 ast_channel_unlock(chan);
333 ast_copy_string(outbuf, ast_str_buffer(newstring), outbuflen);
334 }
335}
336
337static void ast_eivr_setvariable(struct ast_channel *chan, char *data)
338{
339 char *value;
340
341 char *inbuf = ast_strdupa(data), *variable;
342
343 for (variable = strsep(&inbuf, ","); variable; variable = strsep(&inbuf, ",")) {
344 ast_debug(1, "Setting up a variable: %s\n", variable);
345 /* variable contains "varname=value" */
346 value = strchr(variable, '=');
347 if (!value) {
348 value = "";
349 } else {
350 *value++ = '\0';
351 }
352 pbx_builtin_setvar_helper(chan, variable, value);
353 }
354}
355
356static void ast_eivr_senddtmf(struct ast_channel *chan, char *vdata)
357{
358
359 char *data;
360 int dinterval = 0, duration = 0;
362 AST_APP_ARG(digits);
363 AST_APP_ARG(dinterval);
364 AST_APP_ARG(duration);
365 );
366
367 data = ast_strdupa(vdata);
369
370 if (!ast_strlen_zero(args.dinterval)) {
371 ast_app_parse_timelen(args.dinterval, &dinterval, TIMELEN_MILLISECONDS);
372 }
373 if (!ast_strlen_zero(args.duration)) {
374 ast_app_parse_timelen(args.duration, &duration, TIMELEN_MILLISECONDS);
375 }
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);
378}
379
380static struct playlist_entry *make_entry(const char *filename)
381{
382 struct playlist_entry *entry;
383
384 if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
385 return NULL;
386
387 strcpy(entry->filename, filename);
388
389 return entry;
390}
391
392static int app_exec(struct ast_channel *chan, const char *data)
393{
394 struct ast_flags flags = { 0, };
395 char *opts[0];
396 struct playlist_entry *entry;
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;
402 int res = -1;
403 int pid;
404
405 struct ast_tcptls_session_instance *ser = NULL;
406
407 struct ivr_localuser foo = {
409 .finishlist = AST_LIST_HEAD_INIT_VALUE,
410 .gen_active = 0,
411 .playing_silence = 1,
412 };
413 struct ivr_localuser *u = &foo;
414
415 char *buf;
416 int j;
417 char *s, **app_args, *e;
418 struct ast_str *comma_delim_args = ast_str_alloca(100);
419
420 AST_DECLARE_APP_ARGS(eivr_args,
421 AST_APP_ARG(application);
423 );
424 AST_DECLARE_APP_ARGS(application_args,
425 AST_APP_ARG(cmd)[32];
426 );
427
428 u->abort_current_sound = 0;
429 u->chan = chan;
430
431 if (ast_strlen_zero(data)) {
432 ast_log(LOG_ERROR, "ExternalIVR requires a command to execute\n");
433 goto exit;
434 }
435
436 buf = ast_strdupa(data);
437 AST_STANDARD_APP_ARGS(eivr_args, buf);
438
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);
441
442 /* Parse out any application arguments */
443 if ((s = strchr(eivr_args.application, '('))) {
444 s[0] = ',';
445 if ((e = strrchr(s, ')'))) {
446 *e = '\0';
447 } else {
448 ast_log(LOG_ERROR, "Parse error, missing closing parenthesis\n");
449 goto exit;
450 }
451 }
452
453 AST_STANDARD_APP_ARGS(application_args, eivr_args.application);
454 app_args = application_args.argv;
455
456 /* Put the application + the arguments in a , delimited list */
457 ast_str_reset(comma_delim_args);
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]);
460 }
461
462 /* Get rid of any extraneous arguments */
463 if (eivr_args.options && (s = strchr(eivr_args.options, ','))) {
464 *s = '\0';
465 }
466
467 /* Parse the ExternalIVR() arguments */
468 ast_verb(4, "Parsing options from: [%s]\n", eivr_args.options);
469 ast_app_parse_options(app_opts, &flags, opts, eivr_args.options);
470 if (ast_test_flag(&flags, noanswer)) {
471 ast_verb(4, "noanswer is set\n");
472 }
473 if (ast_test_flag(&flags, ignore_hangup)) {
474 ast_verb(4, "ignore_hangup is set\n");
475 }
476 if (ast_test_flag(&flags, run_dead)) {
477 ast_verb(4, "run_dead is set\n");
478 }
479
480 if (!(ast_test_flag(&flags, noanswer))) {
481 ast_verb(3, "Answering channel and starting generator\n");
482 if (ast_channel_state(chan) != AST_STATE_UP) {
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");
485 goto exit;
486 }
487 ast_answer(chan);
488 }
489 if (ast_activate_generator(chan, &gen, u) < 0) {
490 ast_chan_log(LOG_ERROR, chan, "Failed to activate generator\n");
491 goto exit;
492 } else {
493 u->gen_active = 1;
494 }
495 }
496
497 if (!strncmp(app_args[0], "ivr://", sizeof("ivr://") - 1)) {
498 struct ast_tcptls_session_args ivr_desc = {
499 .accept_fd = -1,
500 .name = "IVR",
501 };
502 struct ast_sockaddr *addrs;
503 int num_addrs = 0, i = 0;
504 char *host = app_args[0] + sizeof("ivr://") - 1;
505
506 /* Communicate through socket to server */
507 ast_debug(1, "Parsing hostname/port for socket connect from \"%s\"\n", host);
508
509 if (!(num_addrs = ast_sockaddr_resolve(&addrs, host, 0, AST_AF_UNSPEC))) {
510 ast_chan_log(LOG_ERROR, chan, "Unable to locate host '%s'\n", host);
511 goto exit;
512 }
513
514 for (i = 0; i < num_addrs; i++) {
515 if (!ast_sockaddr_port(&addrs[i])) {
516 /* Default port if not specified */
518 }
519 ast_sockaddr_copy(&ivr_desc.remote_address, &addrs[i]);
520 if (!(ser = ast_tcptls_client_create(&ivr_desc)) || !(ser = ast_tcptls_client_start(ser))) {
521 continue;
522 }
523 break;
524 }
525
526 ast_free(addrs);
527
528 if (i == num_addrs) {
529 ast_chan_log(LOG_ERROR, chan, "Could not connect to any host. ExternalIVR failed.\n");
530 goto exit;
531 }
532
533 res = eivr_comm(chan, u, ser->stream, ser->stream, NULL, comma_delim_args, flags);
534
535 } else {
536 if (pipe(child_stdin)) {
537 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child input: %s\n", strerror(errno));
538 goto exit;
539 }
540 if (pipe(child_stdout)) {
541 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child output: %s\n", strerror(errno));
542 goto exit;
543 }
544 if (pipe(child_stderr)) {
545 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
546 goto exit;
547 }
548
549 pid = ast_safe_fork(0);
550 if (pid < 0) {
551 ast_log(LOG_ERROR, "Failed to fork(): %s\n", strerror(errno));
552 goto exit;
553 }
554
555 if (!pid) {
556 /* child process */
559
560 dup2(child_stdin[0], STDIN_FILENO);
561 dup2(child_stdout[1], STDOUT_FILENO);
562 dup2(child_stderr[1], STDERR_FILENO);
563 ast_close_fds_above_n(STDERR_FILENO);
564 execv(app_args[0], app_args);
565 fprintf(stderr, "Failed to execute '%s': %s\n", app_args[0], strerror(errno));
566 _exit(1);
567 } else {
568 /* parent process */
569 close(child_stdin[0]);
570 child_stdin[0] = -1;
571 close(child_stdout[1]);
572 child_stdout[1] = -1;
573 close(child_stderr[1]);
574 child_stderr[1] = -1;
575
576 stream_stdin = ast_iostream_from_fd(&child_stdin[1]);
577 stream_stdout = ast_iostream_from_fd(&child_stdout[0]);
578 stream_stderr = ast_iostream_from_fd(&child_stderr[0]);
579
580 res = eivr_comm(chan, u, stream_stdin, stream_stdout, stream_stderr, comma_delim_args, flags);
581 }
582 }
583
584 exit:
585 if (u->gen_active) {
587 }
588 if (stream_stdin) {
589 ast_iostream_close(stream_stdin);
590 }
591 if (stream_stdout) {
592 ast_iostream_close(stream_stdout);
593 }
594 if (stream_stderr) {
595 ast_iostream_close(stream_stderr);
596 }
597 if (child_stdin[0] > -1) {
598 close(child_stdin[0]);
599 }
600 if (child_stdin[1] > -1) {
601 close(child_stdin[1]);
602 }
603 if (child_stdout[0] > -1) {
604 close(child_stdout[0]);
605 }
606 if (child_stdout[1] > -1) {
607 close(child_stdout[1]);
608 }
609 if (child_stderr[0] > -1) {
610 close(child_stderr[0]);
611 }
612 if (child_stderr[1] > -1) {
613 close(child_stderr[1]);
614 }
615 if (ser) {
616 ao2_ref(ser, -1);
617 }
618 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
620 }
621 return res;
622}
623
624static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
625 struct ast_iostream *eivr_events,
626 struct ast_iostream *eivr_commands,
627 struct ast_iostream *eivr_errors,
628 const struct ast_str *args, const struct ast_flags flags)
629{
630 char input[1024];
631 struct playlist_entry *entry;
632 struct ast_frame *f;
633 int ms;
634 int exception;
635 int ready_fd;
636 int waitfds[2];
637 int r;
638 struct ast_channel *rchan;
639 int res = -1;
640 int hangup_info_sent = 0;
641
642 waitfds[0] = ast_iostream_get_fd(eivr_commands);
643 waitfds[1] = eivr_errors ? ast_iostream_get_fd(eivr_errors) : -1;
644
645 while (1) {
647 ast_chan_log(LOG_ERROR, chan, "Is a zombie\n");
648 break;
649 }
650 if (!hangup_info_sent && !(ast_test_flag(&flags, run_dead)) && ast_check_hangup(chan)) {
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;
655 } else {
656 ast_verb(3, "Got check_hangup\n");
657 send_eivr_event(eivr_events, 'H', NULL, chan);
658 break;
659 }
660 }
661
662 ready_fd = 0;
663 ms = 100;
664 errno = 0;
665 exception = 0;
666
667 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, (eivr_errors) ? 2 : 1, &exception, &ready_fd, &ms);
668
671 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
672 send_eivr_event(eivr_events, 'F', entry->filename, chan);
674 }
676 }
677
678 if (ast_channel_state(chan) == AST_STATE_UP && !(ast_check_hangup(chan)) && rchan) {
679 /* the channel has something */
680 f = ast_read(chan);
681 if (!f) {
682 ast_verb(3, "Returned no frame\n");
683 send_eivr_event(eivr_events, 'H', NULL, chan);
684 break;
685 }
686 if (f->frametype == AST_FRAME_DTMF) {
687 send_eivr_event(eivr_events, f->subclass.integer, NULL, chan);
688 if (u->option_autoclear) {
690 if (!u->abort_current_sound && !u->playing_silence) {
691 /* send interrupted file as T data */
692 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
693 send_eivr_event(eivr_events, 'T', entry->filename, chan);
695 }
696 }
697 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
698 send_eivr_event(eivr_events, 'D', entry->filename, chan);
700 }
701 if (!u->playing_silence)
702 u->abort_current_sound = 1;
704 }
705 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass.integer == AST_CONTROL_HANGUP)) {
706 ast_verb(3, "Got AST_CONTROL_HANGUP\n");
707 send_eivr_event(eivr_events, 'H', NULL, chan);
708 if (f->data.uint32) {
710 }
711 ast_frfree(f);
712 break;
713 }
714 ast_frfree(f);
715 } else if (ready_fd == waitfds[0]) {
716 if (exception) {
717 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
718 break;
719 }
720
721 r = ast_iostream_gets(eivr_commands, input, sizeof(input));
722 if (r <= 0) {
723 if (r == 0) {
724 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
725 break;
726 }
727 continue;
728 }
729
731 ast_verb(4, "got command '%s'\n", input);
732
733 if (strlen(input) < 3) {
734 continue;
735 }
736
737 if (input[0] == EIVR_CMD_PARM) {
738 struct ast_str *tmp = (struct ast_str *) args;
739 send_eivr_event(eivr_events, 'P', ast_str_buffer(tmp), chan);
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");
745 if (ast_channel_state(chan) != AST_STATE_UP) {
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);
749 continue;
750 }
751 if (ast_answer(chan)) {
752 ast_chan_log(LOG_WARNING, chan, "Failed to answer channel\n");
753 send_eivr_event(eivr_events, 'Z', "ANSWER_FAILURE", chan);
754 continue;
755 }
756 }
757 if (!(u->gen_active)) {
758 if (ast_activate_generator(chan, &gen, u) < 0) {
759 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
760 send_eivr_event(eivr_events, 'Z', "GENERATOR_FAILURE", chan);
761 } else {
762 u->gen_active = 1;
763 }
764 }
765 } else if (input[0] == EIVR_CMD_IRPT) {
766 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
767 ast_chan_log(LOG_WARNING, chan, "Queue 'I'nterrupt called on unanswered channel\n");
768 send_eivr_event(eivr_events, 'Z', NULL, chan);
769 continue;
770 }
772 if (!u->abort_current_sound && !u->playing_silence) {
773 /* send interrupted file as T data */
774 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
775 send_eivr_event(eivr_events, 'T', entry->filename, chan);
777 }
778 }
779 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
780 send_eivr_event(eivr_events, 'D', entry->filename, chan);
782 }
783 if (!u->playing_silence) {
784 u->abort_current_sound = 1;
785 }
787 } else if (input[0] == EIVR_CMD_SQUE) {
788 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
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);
791 continue;
792 }
794 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
795 send_eivr_event(eivr_events, 'Z', &input[2], chan);
796 } else {
798 if (!u->abort_current_sound && !u->playing_silence) {
799 /* send interrupted file as T data */
800 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
801 send_eivr_event(eivr_events, 'T', entry->filename, chan);
803 }
804 }
805 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
806 send_eivr_event(eivr_events, 'D', entry->filename, chan);
808 }
809 if (!u->playing_silence) {
810 u->abort_current_sound = 1;
811 }
812 entry = make_entry(&input[2]);
813 if (entry) {
815 }
817 }
818 } else if (input[0] == EIVR_CMD_APND) {
819 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
820 ast_chan_log(LOG_WARNING, chan, "Queue 'A'ppend called on unanswered channel\n");
821 send_eivr_event(eivr_events, 'Z', NULL, chan);
822 continue;
823 }
825 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
826 send_eivr_event(eivr_events, 'Z', &input[2], chan);
827 } else {
828 entry = make_entry(&input[2]);
829 if (entry) {
833 }
834 }
835 } else if (input[0] == EIVR_CMD_GET) {
836 char response[2048];
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");
848 res = 0;
849 break;
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);
853 res = 0;
854 break;
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);
858 break;
859 } else if (input[0] == EIVR_CMD_OPT) {
860 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
861 ast_chan_log(LOG_WARNING, chan, "Option called on unanswered channel\n");
862 send_eivr_event(eivr_events, 'Z', NULL, chan);
863 continue;
864 }
865 if (!strcasecmp(&input[2], "autoclear"))
866 u->option_autoclear = 1;
867 else if (!strcasecmp(&input[2], "noautoclear"))
868 u->option_autoclear = 0;
869 else
870 ast_chan_log(LOG_WARNING, chan, "Unknown option requested: %s\n", &input[2]);
871 }
872 } else if (ready_fd == waitfds[1]) {
873 if (exception) {
874 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
875 break;
876 }
877
878 r = ast_iostream_gets(eivr_errors, input, sizeof(input));
879 if (r > 0) {
880 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", ast_strip(input));
881 } else if (r == 0) {
882 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
883 break;
884 }
885 } else if ((ready_fd < 0) && ms) {
886 if (errno == 0 || errno == EINTR)
887 continue;
888
889 ast_chan_log(LOG_ERROR, chan, "Wait failed (%s)\n", strerror(errno));
890 break;
891 }
892 }
893
894 return res;
895}
896
897static int unload_module(void)
898{
900}
901
902static int load_module(void)
903{
905}
906
907AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "External IVR Interface Application");
#define EIVR_CMD_EXIT
#define EIVR_CMD_XIT
#define EIVR_CMD_SVAR
#define EIVR_CMD_DTMF
#define EIVR_CMD_OPT
static const char app[]
#define EXTERNALIVR_PORT
static void ast_eivr_getvariable(struct ast_channel *chan, char *data, char *outbuf, int outbuflen)
static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u, struct ast_iostream *eivr_events, struct ast_iostream *eivr_commands, struct ast_iostream *eivr_errors, const struct ast_str *args, const struct ast_flags flags)
static void send_eivr_event(struct ast_iostream *stream, const char event, const char *data, const struct ast_channel *chan)
static struct playlist_entry * make_entry(const char *filename)
static struct ast_frame * gen_readframe(struct gen_state *state)
#define EIVR_CMD_ANS
#define EIVR_CMD_GET
#define ast_chan_log(level, channel, format,...)
static void ast_eivr_setvariable(struct ast_channel *chan, char *data)
#define EIVR_CMD_APND
static const struct ast_app_option app_opts[128]
static int app_exec(struct ast_channel *chan, const char *data)
static int gen_nextfile(struct gen_state *state)
#define EIVR_CMD_IRPT
static void gen_release(struct ast_channel *chan, void *data)
static struct ast_generator gen
static int load_module(void)
static void * gen_alloc(struct ast_channel *chan, void *params)
#define EIVR_CMD_LOG
#define EIVR_CMD_HGUP
static int unload_module(void)
#define EIVR_CMD_PARM
options_flags
@ run_dead
@ ignore_hangup
@ noanswer
static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
static void ast_eivr_senddtmf(struct ast_channel *chan, char *vdata)
#define EIVR_CMD_SQUE
AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "External IVR Interface Application")
static void gen_closestream(struct gen_state *state)
static int input(yyscan_t yyscanner)
Definition: ast_expr2f.c:1570
Asterisk main include file. File version handling, generic pbx functions.
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...
Definition: asterisk.c:1837
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
static int tmp()
Definition: bt_open.c:389
enum cc_state state
Definition: ccss.c:393
General Asterisk PBX channel definitions.
int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen, void *params)
Definition: channel.c:2951
void ast_channel_stream_set(struct ast_channel *chan, struct ast_filestream *value)
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.
Definition: channel.c:2988
#define ast_channel_lock(chan)
Definition: channel.h:2922
struct ast_flags * ast_channel_flags(struct ast_channel *chan)
void ast_deactivate_generator(struct ast_channel *chan)
Definition: channel.c:2893
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.
Definition: channel.c:5144
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition: channel.c:4257
int ast_check_hangup(struct ast_channel *chan)
Check to see if a channel is needing hang up.
Definition: channel.c:445
const char * ast_channel_language(const struct ast_channel *chan)
@ AST_FLAG_ZOMBIE
Definition: channel.h:987
void ast_channel_hangupcause_set(struct ast_channel *chan, int value)
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:2805
#define ast_channel_unlock(chan)
Definition: channel.h:2923
ast_channel_state
ast_channel states
Definition: channelstate.h:35
@ AST_STATE_UP
Definition: channelstate.h:42
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Generic File Format Support. Should be included by clients of the file handling routines....
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.
Definition: file.c:795
struct ast_frame * ast_readframe(struct ast_filestream *s)
Read a frame from a filestream.
Definition: file.c:936
int ast_closestream(struct ast_filestream *f)
Closes a stream.
Definition: file.c:1111
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
Definition: file.c:1129
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application 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.
Definition: main/app.c:3263
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
int ast_safe_fork(int stop_reaper)
Common routine to safely fork without a chance of a signal handler firing badly in the child.
Definition: main/app.c:3197
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
@ TIMELEN_MILLISECONDS
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
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.
Definition: main/app.c:1127
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.
Definition: main/app.c:3056
void ast_close_fds_above_n(int n)
Common routine for child processes, to close all fds prior to exec(2)
Definition: main/app.c:3192
char * strsep(char **str, const char *delims)
#define AST_FRAME_DTMF
#define ast_frfree(fr)
@ AST_FRAME_CONTROL
@ AST_CONTROL_HANGUP
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define ast_verb(level,...)
#define LOG_NOTICE
#define LOG_WARNING
ssize_t ast_iostream_gets(struct ast_iostream *stream, char *buffer, size_t size)
Read a LF-terminated string from an iostream.
Definition: iostream.c:300
struct ast_iostream * ast_iostream_from_fd(int *fd)
Create an iostream from a file descriptor.
Definition: iostream.c:604
ssize_t ast_iostream_write(struct ast_iostream *stream, const void *buffer, size_t count)
Write data to an iostream.
Definition: iostream.c:374
int ast_iostream_get_fd(struct ast_iostream *stream)
Get an iostream's file descriptor.
Definition: iostream.c:84
int ast_iostream_close(struct ast_iostream *stream)
Close an iostream.
Definition: iostream.c:528
A set of macros to manage forward-linked lists.
#define AST_LIST_HEAD_INIT_VALUE
Defines initial values for a declaration of AST_LIST_HEAD.
Definition: linkedlists.h:234
#define AST_LIST_EMPTY(head)
Checks whether the specified list contains any entries.
Definition: linkedlists.h:450
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
Definition: linkedlists.h:173
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
Definition: linkedlists.h:421
Asterisk locking-related definitions:
int errno
Asterisk module definitions.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:626
@ AST_AF_UNSPEC
Definition: netsock2.h:54
#define ast_sockaddr_port(addr)
Get the port number of a socket address.
Definition: netsock2.h:517
static void ast_sockaddr_copy(struct ast_sockaddr *dst, const struct ast_sockaddr *src)
Copies the data from one ast_sockaddr to another.
Definition: netsock2.h:167
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.
Definition: netsock2.c:280
#define ast_sockaddr_set_port(addr, port)
Sets the port number of a socket address.
Definition: netsock2.h:532
#define ast_opt_high_priority
Definition: options.h:112
Core PBX routines and definitions.
const char * pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
Return a pointer to the value of the corresponding channel variable.
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.
#define NULL
Definition: resample.c:96
char * ast_str_truncate(struct ast_str *buf, ssize_t len)
Truncates the enclosed string to the given length.
Definition: strings.h:786
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#define ast_str_alloca(init_len)
Definition: strings.h:848
void ast_str_reset(struct ast_str *buf)
Reset the content of a dynamic string. Useful before a series of ast_str_append.
Definition: strings.h:693
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223
Main Channel structure associated with a channel.
struct ast_flags flags
This structure is allocated by file.c in one chunk, together with buf_size and desc_size bytes of mem...
Definition: mod_format.h:101
Structure used to handle boolean flags.
Definition: utils.h:199
unsigned int flags
Definition: utils.h:200
Data structure associated with a single frame of data.
struct ast_frame_subclass subclass
union ast_frame::@226 data
enum ast_frame_type frametype
void *(* alloc)(struct ast_channel *chan, void *params)
Definition: channel.h:226
Socket address structure.
Definition: netsock2.h:97
Support for dynamic strings.
Definition: strings.h:623
arguments for the accepting thread
Definition: tcptls.h:130
struct ast_sockaddr remote_address
Definition: tcptls.h:133
describes a server instance
Definition: tcptls.h:150
struct ast_iostream * stream
Definition: tcptls.h:161
Definition: search.h:40
Definition: astman.c:222
struct ast_filestream * stream
struct playlist_entry * current
struct ivr_localuser * u
struct ast_channel * chan
struct ivr_localuser::playlist playlist
struct ivr_localuser::finishlist finishlist
struct playlist_entry::@21 list
char filename[1]
int value
Definition: syslog.c:37
Generic support for tcp/tls servers in Asterisk.
struct ast_tcptls_session_instance * ast_tcptls_client_create(struct ast_tcptls_session_args *desc)
Creates a client connection's ast_tcptls_session_instance.
Definition: tcptls.c:678
struct ast_tcptls_session_instance * ast_tcptls_client_start(struct ast_tcptls_session_instance *tcptls_session)
Attempt to connect and start a tcptls session.
Definition: tcptls.c:673
const char * args
static struct test_options options
static int inbuf(struct baseio *bio, FILE *fi)
utility used by inchar(), for base_encode()
Definition: utils.c:590
Utility functions.
#define ast_test_flag(p, flag)
Definition: utils.h:63