Asterisk - The Open Source Telephony Project GIT-master-27fb039
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
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 <since>
56 <version>1.2.0</version>
57 </since>
58 <synopsis>
59 Interfaces with an external IVR application.
60 </synopsis>
61 <syntax>
62 <parameter name="command|ivr://host" required="true" hasparams="true">
63 <argument name="arg1" />
64 <argument name="arg2" multiple="yes" />
65 </parameter>
66 <parameter name="options">
67 <optionlist>
68 <option name="n">
69 <para>Tells ExternalIVR() not to answer the channel.</para>
70 </option>
71 <option name="i">
72 <para>Tells ExternalIVR() not to send a hangup and exit when the
73 channel receives a hangup, instead it sends an <literal>I</literal>
74 informative message meaning that the external application MUST hang
75 up the call with an <literal>H</literal> command.</para>
76 </option>
77 <option name="d">
78 <para>Tells ExternalIVR() to run on a channel that has been hung up
79 and will not look for hangups. The external application must exit with
80 an <literal>E</literal> command.</para>
81 </option>
82 </optionlist>
83 </parameter>
84 </syntax>
85 <description>
86 <para>Either forks a process to run given command or makes a socket to connect
87 to given host and starts a generator on the channel. The generator's play list
88 is controlled by the external application, which can add and clear entries via
89 simple commands issued over its stdout. The external application will receive
90 all DTMF events received on the channel, and notification if the channel is
91 hung up. The received on the channel, and notification if the channel is hung
92 up. The application will not be forcibly terminated when the channel is hung up.
93 For more information see <filename>doc/AST.pdf</filename>.</para>
94 </description>
95 </application>
96 ***/
97
98static const char app[] = "ExternalIVR";
99
100/* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
101#define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, ast_channel_name(channel) , ## __VA_ARGS__)
102
103/* Commands */
104#define EIVR_CMD_APND 'A' /* append to prompt queue */
105#define EIVR_CMD_DTMF 'D' /* send DTMF */
106#define EIVR_CMD_EXIT 'E' /* exit */
107#define EIVR_CMD_GET 'G' /* get channel varable(s) */
108#define EIVR_CMD_HGUP 'H' /* hangup */
109#define EIVR_CMD_IRPT 'I' /* interrupt */
110#define EIVR_CMD_LOG 'L' /* log message */
111#define EIVR_CMD_OPT 'O' /* option */
112#define EIVR_CMD_PARM 'P' /* return supplied params */
113#define EIVR_CMD_SQUE 'S' /* (re)set prompt queue */
114#define EIVR_CMD_ANS 'T' /* answer channel */
115#define EIVR_CMD_SVAR 'V' /* set channel varable(s) */
116#define EIVR_CMD_XIT 'X' /* exit **depricated** */
117
118#define EXTERNALIVR_PORT 2949
119
121 noanswer = (1 << 0),
122 ignore_hangup = (1 << 1),
123 run_dead = (1 << 2),
124};
125
130});
131
136
146
147
154
155static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
156 struct ast_iostream *eivr_events,
157 struct ast_iostream *eivr_commands,
158 struct ast_iostream *eivr_errors,
159 const struct ast_str *args, const struct ast_flags flags);
160
161static void send_eivr_event(struct ast_iostream *stream, const char event, const char *data,
162 const struct ast_channel *chan)
163{
164 struct ast_str *tmp = ast_str_create(12);
165
166 ast_str_append(&tmp, 0, "%c,%10d", event, (int)time(NULL));
167 if (data) {
168 ast_str_append(&tmp, 0, ",%s", data);
169 }
170 ast_str_append(&tmp, 0, "\n");
171 ast_iostream_write(stream, ast_str_buffer(tmp), strlen(ast_str_buffer(tmp)));
172 ast_str_truncate(tmp, -1);
173
174 ast_debug(1, "sent '%s'", ast_str_buffer(tmp));
175 ast_free(tmp);
176}
177
178static void *gen_alloc(struct ast_channel *chan, void *params)
179{
180 struct ivr_localuser *u = params;
181 struct gen_state *state;
182
183 if (!(state = ast_calloc(1, sizeof(*state))))
184 return NULL;
185
186 state->u = u;
187
188 return state;
189}
190
191static void gen_closestream(struct gen_state *state)
192{
193 if (!state->stream)
194 return;
195
196 ast_closestream(state->stream);
198 state->stream = NULL;
199}
200
201static void gen_release(struct ast_channel *chan, void *data)
202{
203 struct gen_state *state = data;
204
206 ast_free(data);
207}
208
209/* caller has the playlist locked */
210static int gen_nextfile(struct gen_state *state)
211{
212 struct ivr_localuser *u = state->u;
213 char *file_to_stream;
214
215 u->abort_current_sound = 0;
216 u->playing_silence = 0;
218
219 while (!state->stream) {
220 state->current = AST_LIST_FIRST(&u->playlist);
221 if (state->current) {
222 file_to_stream = state->current->filename;
223 } else {
224 file_to_stream = "silence/10";
225 u->playing_silence = 1;
226 }
227
228 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, ast_channel_language(u->chan), 1))) {
229 ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
233 if (!u->playing_silence) {
234 continue;
235 } else {
236 break;
237 }
238 }
239 }
240
241 return (!state->stream);
242}
243
244static struct ast_frame *gen_readframe(struct gen_state *state)
245{
246 struct ast_frame *f = NULL;
247 struct ivr_localuser *u = state->u;
248
249 if (u->abort_current_sound ||
255 }
256
257 if (!(state->stream && (f = ast_readframe(state->stream)))) {
258 if (state->current) {
259 /* remove finished file from playlist */
263 /* add finished file to finishlist */
265 AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
267 state->current = NULL;
268 }
269 if (!gen_nextfile(state))
270 f = ast_readframe(state->stream);
271 }
272
273 return f;
274}
275
276static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
277{
278 struct gen_state *state = data;
279 struct ast_frame *f = NULL;
280 int res = 0;
281
282 state->sample_queue += samples;
283
284 while (state->sample_queue > 0) {
285 if (!(f = gen_readframe(state)))
286 return -1;
287
288 res = ast_write(chan, f);
289 ast_frfree(f);
290 if (res < 0) {
291 ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
292 return -1;
293 }
294 state->sample_queue -= f->samples;
295 }
296
297 return res;
298}
299
300static struct ast_generator gen =
301{
302 .alloc = gen_alloc,
303 .release = gen_release,
304 .generate = gen_generate,
305};
306
307static void ast_eivr_getvariable(struct ast_channel *chan, char *data, char *outbuf, int outbuflen)
308{
309 /* original input data: "G,var1,var2," */
310 /* data passed as "data": "var1,var2" */
311
312 char *inbuf, *variable;
313 const char *value;
314 int j;
315 struct ast_str *newstring = ast_str_alloca(outbuflen);
316
317 outbuf[0] = '\0';
318
319 for (j = 1, inbuf = data; ; j++) {
320 variable = strsep(&inbuf, ",");
321 if (variable == NULL) {
322 int outstrlen = strlen(outbuf);
323 if (outstrlen && outbuf[outstrlen - 1] == ',') {
324 outbuf[outstrlen - 1] = 0;
325 }
326 break;
327 }
328
329 ast_channel_lock(chan);
330 if (!(value = pbx_builtin_getvar_helper(chan, variable))) {
331 value = "";
332 }
333
334 ast_str_append(&newstring, 0, "%s=%s,", variable, value);
335 ast_channel_unlock(chan);
336 ast_copy_string(outbuf, ast_str_buffer(newstring), outbuflen);
337 }
338}
339
340static void ast_eivr_setvariable(struct ast_channel *chan, char *data)
341{
342 char *value;
343
344 char *inbuf = ast_strdupa(data), *variable;
345
346 for (variable = strsep(&inbuf, ","); variable; variable = strsep(&inbuf, ",")) {
347 ast_debug(1, "Setting up a variable: %s\n", variable);
348 /* variable contains "varname=value" */
349 value = strchr(variable, '=');
350 if (!value) {
351 value = "";
352 } else {
353 *value++ = '\0';
354 }
355 pbx_builtin_setvar_helper(chan, variable, value);
356 }
357}
358
359static void ast_eivr_senddtmf(struct ast_channel *chan, char *vdata)
360{
361
362 char *data;
363 int dinterval = 0, duration = 0;
365 AST_APP_ARG(digits);
366 AST_APP_ARG(dinterval);
367 AST_APP_ARG(duration);
368 );
369
370 data = ast_strdupa(vdata);
372
373 if (!ast_strlen_zero(args.dinterval)) {
374 ast_app_parse_timelen(args.dinterval, &dinterval, TIMELEN_MILLISECONDS);
375 }
376 if (!ast_strlen_zero(args.duration)) {
377 ast_app_parse_timelen(args.duration, &duration, TIMELEN_MILLISECONDS);
378 }
379 ast_verb(4, "Sending DTMF: %s %d %d\n", args.digits, dinterval <= 0 ? 250 : dinterval, duration);
380 ast_dtmf_stream(chan, NULL, args.digits, dinterval <= 0 ? 250 : dinterval, duration);
381}
382
383static struct playlist_entry *make_entry(const char *filename)
384{
385 struct playlist_entry *entry;
386
387 if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
388 return NULL;
389
390 strcpy(entry->filename, filename);
391
392 return entry;
393}
394
395static int app_exec(struct ast_channel *chan, const char *data)
396{
397 struct ast_flags flags = { 0, };
398 char *opts[0];
399 struct playlist_entry *entry;
400 int child_stdin[2] = { -1, -1 };
401 int child_stdout[2] = { -1, -1 };
402 int child_stderr[2] = { -1, -1 };
403 struct ast_iostream *stream_stdin = NULL, *stream_stdout = NULL,
404 *stream_stderr = NULL;
405 int res = -1;
406 int pid;
407
408 struct ast_tcptls_session_instance *ser = NULL;
409
410 struct ivr_localuser foo = {
412 .finishlist = AST_LIST_HEAD_INIT_VALUE,
413 .gen_active = 0,
414 .playing_silence = 1,
415 };
416 struct ivr_localuser *u = &foo;
417
418 char *buf;
419 int j;
420 char *s, **app_args, *e;
421 struct ast_str *comma_delim_args = ast_str_alloca(100);
422
423 AST_DECLARE_APP_ARGS(eivr_args,
424 AST_APP_ARG(application);
426 );
427
428#define MAX_EIVR_APPLICATION_ARGS 32
429
430 AST_DECLARE_APP_ARGS(application_args,
432 );
433
434 u->abort_current_sound = 0;
435 u->chan = chan;
436
437 if (ast_strlen_zero(data)) {
438 ast_log(LOG_ERROR, "ExternalIVR requires a command to execute\n");
439 goto exit;
440 }
441
442 buf = ast_strdupa(data);
443 AST_STANDARD_APP_ARGS(eivr_args, buf);
444
445 ast_verb(4, "ExternalIVR received application and arguments: %s\n", eivr_args.application);
446 ast_verb(4, "ExternalIVR received options: %s\n", eivr_args.options);
447
448 /* Parse out any application arguments */
449 if ((s = strchr(eivr_args.application, '('))) {
450 s[0] = ',';
451 if ((e = strrchr(s, ')'))) {
452 *e = '\0';
453 } else {
454 ast_log(LOG_ERROR, "Parse error, missing closing parenthesis\n");
455 goto exit;
456 }
457 }
458
459 AST_STANDARD_APP_ARGS(application_args, eivr_args.application);
460 app_args = application_args.argv;
461
462 /* Put the application + the arguments in a , delimited list */
463 ast_str_reset(comma_delim_args);
464 for (j = 0; j < MAX_EIVR_APPLICATION_ARGS && application_args.cmd[j]; j++) {
465 ast_str_append(&comma_delim_args, 0, "%s%s", j == 0 ? "" : ",", application_args.cmd[j]);
466 }
467
468 /* Get rid of any extraneous arguments */
469 if (eivr_args.options && (s = strchr(eivr_args.options, ','))) {
470 *s = '\0';
471 }
472
473 /* Parse the ExternalIVR() arguments */
474 ast_verb(4, "Parsing options from: [%s]\n", eivr_args.options);
475 ast_app_parse_options(app_opts, &flags, opts, eivr_args.options);
476 if (ast_test_flag(&flags, noanswer)) {
477 ast_verb(4, "noanswer is set\n");
478 }
479 if (ast_test_flag(&flags, ignore_hangup)) {
480 ast_verb(4, "ignore_hangup is set\n");
481 }
482 if (ast_test_flag(&flags, run_dead)) {
483 ast_verb(4, "run_dead is set\n");
484 }
485
486 if (!(ast_test_flag(&flags, noanswer))) {
487 ast_verb(3, "Answering channel and starting generator\n");
488 if (ast_channel_state(chan) != AST_STATE_UP) {
489 if (ast_test_flag(&flags, run_dead)) {
490 ast_chan_log(LOG_ERROR, chan, "Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
491 goto exit;
492 }
493 ast_answer(chan);
494 }
495 if (ast_activate_generator(chan, &gen, u) < 0) {
496 ast_chan_log(LOG_ERROR, chan, "Failed to activate generator\n");
497 goto exit;
498 } else {
499 u->gen_active = 1;
500 }
501 }
502
503 if (!strncmp(app_args[0], "ivr://", sizeof("ivr://") - 1)) {
504 struct ast_tcptls_session_args ivr_desc = {
505 .accept_fd = -1,
506 .name = "IVR",
507 };
508 struct ast_sockaddr *addrs;
509 int num_addrs = 0, i = 0;
510 char *host = app_args[0] + sizeof("ivr://") - 1;
511
512 /* Communicate through socket to server */
513 ast_debug(1, "Parsing hostname/port for socket connect from \"%s\"\n", host);
514
515 if (!(num_addrs = ast_sockaddr_resolve(&addrs, host, 0, AST_AF_UNSPEC))) {
516 ast_chan_log(LOG_ERROR, chan, "Unable to locate host '%s'\n", host);
517 goto exit;
518 }
519
520 for (i = 0; i < num_addrs; i++) {
521 if (!ast_sockaddr_port(&addrs[i])) {
522 /* Default port if not specified */
524 }
525 ast_sockaddr_copy(&ivr_desc.remote_address, &addrs[i]);
526 if (!(ser = ast_tcptls_client_create(&ivr_desc)) || !(ser = ast_tcptls_client_start(ser))) {
527 continue;
528 }
529 break;
530 }
531
532 ast_free(addrs);
533
534 if (i == num_addrs) {
535 ast_chan_log(LOG_ERROR, chan, "Could not connect to any host. ExternalIVR failed.\n");
536 goto exit;
537 }
538
539 res = eivr_comm(chan, u, ser->stream, ser->stream, NULL, comma_delim_args, flags);
540
541 } else {
542 if (pipe(child_stdin)) {
543 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child input: %s\n", strerror(errno));
544 goto exit;
545 }
546 if (pipe(child_stdout)) {
547 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child output: %s\n", strerror(errno));
548 goto exit;
549 }
550 if (pipe(child_stderr)) {
551 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
552 goto exit;
553 }
554
555 pid = ast_safe_fork(0);
556 if (pid < 0) {
557 ast_log(LOG_ERROR, "Failed to fork(): %s\n", strerror(errno));
558 goto exit;
559 }
560
561 if (!pid) {
562 /* child process */
565
566 dup2(child_stdin[0], STDIN_FILENO);
567 dup2(child_stdout[1], STDOUT_FILENO);
568 dup2(child_stderr[1], STDERR_FILENO);
569 ast_close_fds_above_n(STDERR_FILENO);
570 execv(app_args[0], app_args);
571 fprintf(stderr, "Failed to execute '%s': %s\n", app_args[0], strerror(errno));
572 _exit(1);
573 } else {
574 /* parent process */
575 close(child_stdin[0]);
576 child_stdin[0] = -1;
577 close(child_stdout[1]);
578 child_stdout[1] = -1;
579 close(child_stderr[1]);
580 child_stderr[1] = -1;
581
582 stream_stdin = ast_iostream_from_fd(&child_stdin[1]);
583 stream_stdout = ast_iostream_from_fd(&child_stdout[0]);
584 stream_stderr = ast_iostream_from_fd(&child_stderr[0]);
585
586 res = eivr_comm(chan, u, stream_stdin, stream_stdout, stream_stderr, comma_delim_args, flags);
587 }
588 }
589
590 exit:
591 if (u->gen_active) {
593 }
594 if (stream_stdin) {
595 ast_iostream_close(stream_stdin);
596 }
597 if (stream_stdout) {
598 ast_iostream_close(stream_stdout);
599 }
600 if (stream_stderr) {
601 ast_iostream_close(stream_stderr);
602 }
603 if (child_stdin[0] > -1) {
604 close(child_stdin[0]);
605 }
606 if (child_stdin[1] > -1) {
607 close(child_stdin[1]);
608 }
609 if (child_stdout[0] > -1) {
610 close(child_stdout[0]);
611 }
612 if (child_stdout[1] > -1) {
613 close(child_stdout[1]);
614 }
615 if (child_stderr[0] > -1) {
616 close(child_stderr[0]);
617 }
618 if (child_stderr[1] > -1) {
619 close(child_stderr[1]);
620 }
621 if (ser) {
622 ao2_ref(ser, -1);
623 }
624 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
625 ast_free(entry);
626 }
627 return res;
628}
629
630static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
631 struct ast_iostream *eivr_events,
632 struct ast_iostream *eivr_commands,
633 struct ast_iostream *eivr_errors,
634 const struct ast_str *args, const struct ast_flags flags)
635{
636 char input[1024];
637 struct playlist_entry *entry;
638 struct ast_frame *f;
639 int ms;
640 int exception;
641 int ready_fd;
642 int waitfds[2];
643 int r;
644 struct ast_channel *rchan;
645 int res = -1;
646 int hangup_info_sent = 0;
647
648 waitfds[0] = ast_iostream_get_fd(eivr_commands);
649 waitfds[1] = eivr_errors ? ast_iostream_get_fd(eivr_errors) : -1;
650
651 while (1) {
653 ast_chan_log(LOG_ERROR, chan, "Is a zombie\n");
654 break;
655 }
656 if (!hangup_info_sent && !(ast_test_flag(&flags, run_dead)) && ast_check_hangup(chan)) {
658 ast_verb(3, "Got check_hangup, but ignore_hangup set so sending 'I' command\n");
659 send_eivr_event(eivr_events, 'I', "HANGUP", chan);
660 hangup_info_sent = 1;
661 } else {
662 ast_verb(3, "Got check_hangup\n");
663 send_eivr_event(eivr_events, 'H', NULL, chan);
664 break;
665 }
666 }
667
668 ready_fd = 0;
669 ms = 100;
670 errno = 0;
671 exception = 0;
672
673 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, (eivr_errors) ? 2 : 1, &exception, &ready_fd, &ms);
674
677 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
678 send_eivr_event(eivr_events, 'F', entry->filename, chan);
679 ast_free(entry);
680 }
682 }
683
684 if (ast_channel_state(chan) == AST_STATE_UP && !(ast_check_hangup(chan)) && rchan) {
685 /* the channel has something */
686 f = ast_read(chan);
687 if (!f) {
688 ast_verb(3, "Returned no frame\n");
689 send_eivr_event(eivr_events, 'H', NULL, chan);
690 break;
691 }
692 if (f->frametype == AST_FRAME_DTMF) {
693 send_eivr_event(eivr_events, f->subclass.integer, NULL, chan);
694 if (u->option_autoclear) {
696 if (!u->abort_current_sound && !u->playing_silence) {
697 /* send interrupted file as T data */
698 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
699 send_eivr_event(eivr_events, 'T', entry->filename, chan);
700 ast_free(entry);
701 }
702 }
703 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
704 send_eivr_event(eivr_events, 'D', entry->filename, chan);
705 ast_free(entry);
706 }
707 if (!u->playing_silence)
708 u->abort_current_sound = 1;
710 }
711 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass.integer == AST_CONTROL_HANGUP)) {
712 ast_verb(3, "Got AST_CONTROL_HANGUP\n");
713 send_eivr_event(eivr_events, 'H', NULL, chan);
714 if (f->data.uint32) {
716 }
717 ast_frfree(f);
718 break;
719 }
720 ast_frfree(f);
721 } else if (ready_fd == waitfds[0]) {
722 if (exception) {
723 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
724 break;
725 }
726
727 r = ast_iostream_gets(eivr_commands, input, sizeof(input));
728 if (r <= 0) {
729 if (r == 0) {
730 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
731 break;
732 }
733 continue;
734 }
735
736 ast_strip(input);
737 ast_verb(4, "got command '%s'\n", input);
738
739 if (strlen(input) < 3) {
740 continue;
741 }
742
743 if (input[0] == EIVR_CMD_PARM) {
744 struct ast_str *tmp = (struct ast_str *) args;
745 send_eivr_event(eivr_events, 'P', ast_str_buffer(tmp), chan);
746 } else if (input[0] == EIVR_CMD_DTMF) {
747 ast_verb(4, "Sending DTMF: %s\n", &input[2]);
748 ast_eivr_senddtmf(chan, &input[2]);
749 } else if (input[0] == EIVR_CMD_ANS) {
750 ast_verb(3, "Answering channel if needed and starting generator\n");
751 if (ast_channel_state(chan) != AST_STATE_UP) {
752 if (ast_test_flag(&flags, run_dead)) {
753 ast_chan_log(LOG_WARNING, chan, "Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
754 send_eivr_event(eivr_events, 'Z', "ANSWER_FAILURE", chan);
755 continue;
756 }
757 if (ast_answer(chan)) {
758 ast_chan_log(LOG_WARNING, chan, "Failed to answer channel\n");
759 send_eivr_event(eivr_events, 'Z', "ANSWER_FAILURE", chan);
760 continue;
761 }
762 }
763 if (!(u->gen_active)) {
764 if (ast_activate_generator(chan, &gen, u) < 0) {
765 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
766 send_eivr_event(eivr_events, 'Z', "GENERATOR_FAILURE", chan);
767 } else {
768 u->gen_active = 1;
769 }
770 }
771 } else if (input[0] == EIVR_CMD_IRPT) {
772 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
773 ast_chan_log(LOG_WARNING, chan, "Queue 'I'nterrupt called on unanswered channel\n");
774 send_eivr_event(eivr_events, 'Z', NULL, chan);
775 continue;
776 }
778 if (!u->abort_current_sound && !u->playing_silence) {
779 /* send interrupted file as T data */
780 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
781 send_eivr_event(eivr_events, 'T', entry->filename, chan);
782 ast_free(entry);
783 }
784 }
785 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
786 send_eivr_event(eivr_events, 'D', entry->filename, chan);
787 ast_free(entry);
788 }
789 if (!u->playing_silence) {
790 u->abort_current_sound = 1;
791 }
793 } else if (input[0] == EIVR_CMD_SQUE) {
794 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
795 ast_chan_log(LOG_WARNING, chan, "Queue re'S'et called on unanswered channel\n");
796 send_eivr_event(eivr_events, 'Z', NULL, chan);
797 continue;
798 }
799 if (!ast_fileexists(&input[2], NULL, ast_channel_language(u->chan))) {
800 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
801 send_eivr_event(eivr_events, 'Z', &input[2], chan);
802 } else {
804 if (!u->abort_current_sound && !u->playing_silence) {
805 /* send interrupted file as T data */
806 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
807 send_eivr_event(eivr_events, 'T', entry->filename, chan);
808 ast_free(entry);
809 }
810 }
811 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
812 send_eivr_event(eivr_events, 'D', entry->filename, chan);
813 ast_free(entry);
814 }
815 if (!u->playing_silence) {
816 u->abort_current_sound = 1;
817 }
818 entry = make_entry(&input[2]);
819 if (entry) {
820 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
821 }
823 }
824 } else if (input[0] == EIVR_CMD_APND) {
825 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
826 ast_chan_log(LOG_WARNING, chan, "Queue 'A'ppend called on unanswered channel\n");
827 send_eivr_event(eivr_events, 'Z', NULL, chan);
828 continue;
829 }
830 if (!ast_fileexists(&input[2], NULL, ast_channel_language(u->chan))) {
831 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
832 send_eivr_event(eivr_events, 'Z', &input[2], chan);
833 } else {
834 entry = make_entry(&input[2]);
835 if (entry) {
837 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
839 }
840 }
841 } else if (input[0] == EIVR_CMD_GET) {
842 char response[2048];
843 ast_verb(4, "Retrieving Variables from channel: %s\n", &input[2]);
844 ast_eivr_getvariable(chan, &input[2], response, sizeof(response));
845 send_eivr_event(eivr_events, 'G', response, chan);
846 } else if (input[0] == EIVR_CMD_SVAR) {
847 ast_verb(4, "Setting Variables in channel: %s\n", &input[2]);
848 ast_eivr_setvariable(chan, &input[2]);
849 } else if (input[0] == EIVR_CMD_LOG) {
850 ast_chan_log(LOG_NOTICE, chan, "Log message from EIVR: %s\n", &input[2]);
851 } else if (input[0] == EIVR_CMD_XIT) {
852 ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
853 ast_chan_log(LOG_WARNING, chan, "e'X'it command is deprecated, use 'E'xit instead\n");
854 res = 0;
855 break;
856 } else if (input[0] == EIVR_CMD_EXIT) {
857 ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
858 send_eivr_event(eivr_events, 'E', NULL, chan);
859 res = 0;
860 break;
861 } else if (input[0] == EIVR_CMD_HGUP) {
862 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
863 send_eivr_event(eivr_events, 'H', NULL, chan);
864 break;
865 } else if (input[0] == EIVR_CMD_OPT) {
866 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
867 ast_chan_log(LOG_WARNING, chan, "Option called on unanswered channel\n");
868 send_eivr_event(eivr_events, 'Z', NULL, chan);
869 continue;
870 }
871 if (!strcasecmp(&input[2], "autoclear"))
872 u->option_autoclear = 1;
873 else if (!strcasecmp(&input[2], "noautoclear"))
874 u->option_autoclear = 0;
875 else
876 ast_chan_log(LOG_WARNING, chan, "Unknown option requested: %s\n", &input[2]);
877 }
878 } else if (ready_fd == waitfds[1]) {
879 if (exception) {
880 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
881 break;
882 }
883
884 r = ast_iostream_gets(eivr_errors, input, sizeof(input));
885 if (r > 0) {
886 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", ast_strip(input));
887 } else if (r == 0) {
888 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
889 break;
890 }
891 } else if ((ready_fd < 0) && ms) {
892 if (errno == 0 || errno == EINTR)
893 continue;
894
895 ast_chan_log(LOG_ERROR, chan, "Wait failed (%s)\n", strerror(errno));
896 break;
897 }
898 }
899
900 return res;
901}
902
903static int unload_module(void)
904{
906}
907
908static int load_module(void)
909{
911}
912
913AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "External IVR Interface Application");
static const char app_exec[]
Definition app_exec.c:138
#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 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
static void gen_closestream(struct gen_state *state)
#define MAX_EIVR_APPLICATION_ARGS
char * strsep(char **str, const char *delims)
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:1853
#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
General Asterisk PBX channel definitions.
int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen, void *params)
Definition channel.c:2948
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:2985
#define ast_channel_lock(chan)
Definition channel.h:2982
struct ast_flags * ast_channel_flags(struct ast_channel *chan)
void ast_deactivate_generator(struct ast_channel *chan)
Definition channel.c:2890
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:5139
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition channel.c:4250
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:1007
void ast_channel_hangupcause_set(struct ast_channel *chan, int value)
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition channel.c:2803
#define ast_channel_unlock(chan)
Definition channel.h:2983
ast_channel_state
ast_channel states
@ AST_STATE_UP
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:861
struct ast_frame * ast_readframe(struct ast_filestream *s)
Read a frame from a filestream.
Definition file.c:955
int ast_closestream(struct ast_filestream *f)
Closes a stream.
Definition file.c:1130
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
Definition file.c:1148
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:3273
#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:3207
#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:3066
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:3202
#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:311
struct ast_iostream * ast_iostream_from_fd(int *fd)
Create an iostream from a file descriptor.
Definition iostream.c:611
ssize_t ast_iostream_write(struct ast_iostream *stream, const void *buffer, size_t count)
Write data to an iostream.
Definition iostream.c:385
int ast_iostream_get_fd(struct ast_iostream *stream)
Get an iostream's file descriptor.
Definition iostream.c:85
int ast_iostream_close(struct ast_iostream *stream)
Close an iostream.
Definition iostream.c:539
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.
#define AST_LIST_EMPTY(head)
Checks whether the specified list contains any entries.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#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.
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
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_MODULE_INFO_STANDARD_EXTENDED(keystr, desc)
Definition module.h:589
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition module.h:640
@ 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:122
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.
static struct @519 args
#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
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
char *attribute_pure ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition strings.h:761
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:220
unsigned int flags
Definition utils.h:221
Data structure associated with a single frame of data.
struct ast_frame_subclass subclass
enum ast_frame_type frametype
union ast_frame::@239 data
void *(* alloc)(struct ast_channel *chan, void *params)
Definition channel.h:228
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:151
struct ast_iostream * stream
Definition tcptls.h:162
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::@22 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:686
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:681
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:64