Asterisk - The Open Source Telephony Project GIT-master-754dea3
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
134 char filename[1];
135};
136
145};
146
147
148struct gen_state {
153};
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 AST_DECLARE_APP_ARGS(application_args,
428 AST_APP_ARG(cmd)[32];
429 );
430
431 u->abort_current_sound = 0;
432 u->chan = chan;
433
434 if (ast_strlen_zero(data)) {
435 ast_log(LOG_ERROR, "ExternalIVR requires a command to execute\n");
436 goto exit;
437 }
438
439 buf = ast_strdupa(data);
440 AST_STANDARD_APP_ARGS(eivr_args, buf);
441
442 ast_verb(4, "ExternalIVR received application and arguments: %s\n", eivr_args.application);
443 ast_verb(4, "ExternalIVR received options: %s\n", eivr_args.options);
444
445 /* Parse out any application arguments */
446 if ((s = strchr(eivr_args.application, '('))) {
447 s[0] = ',';
448 if ((e = strrchr(s, ')'))) {
449 *e = '\0';
450 } else {
451 ast_log(LOG_ERROR, "Parse error, missing closing parenthesis\n");
452 goto exit;
453 }
454 }
455
456 AST_STANDARD_APP_ARGS(application_args, eivr_args.application);
457 app_args = application_args.argv;
458
459 /* Put the application + the arguments in a , delimited list */
460 ast_str_reset(comma_delim_args);
461 for (j = 0; application_args.cmd[j] != NULL; j++) {
462 ast_str_append(&comma_delim_args, 0, "%s%s", j == 0 ? "" : ",", application_args.cmd[j]);
463 }
464
465 /* Get rid of any extraneous arguments */
466 if (eivr_args.options && (s = strchr(eivr_args.options, ','))) {
467 *s = '\0';
468 }
469
470 /* Parse the ExternalIVR() arguments */
471 ast_verb(4, "Parsing options from: [%s]\n", eivr_args.options);
472 ast_app_parse_options(app_opts, &flags, opts, eivr_args.options);
473 if (ast_test_flag(&flags, noanswer)) {
474 ast_verb(4, "noanswer is set\n");
475 }
476 if (ast_test_flag(&flags, ignore_hangup)) {
477 ast_verb(4, "ignore_hangup is set\n");
478 }
479 if (ast_test_flag(&flags, run_dead)) {
480 ast_verb(4, "run_dead is set\n");
481 }
482
483 if (!(ast_test_flag(&flags, noanswer))) {
484 ast_verb(3, "Answering channel and starting generator\n");
485 if (ast_channel_state(chan) != AST_STATE_UP) {
486 if (ast_test_flag(&flags, run_dead)) {
487 ast_chan_log(LOG_ERROR, chan, "Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
488 goto exit;
489 }
490 ast_answer(chan);
491 }
492 if (ast_activate_generator(chan, &gen, u) < 0) {
493 ast_chan_log(LOG_ERROR, chan, "Failed to activate generator\n");
494 goto exit;
495 } else {
496 u->gen_active = 1;
497 }
498 }
499
500 if (!strncmp(app_args[0], "ivr://", sizeof("ivr://") - 1)) {
501 struct ast_tcptls_session_args ivr_desc = {
502 .accept_fd = -1,
503 .name = "IVR",
504 };
505 struct ast_sockaddr *addrs;
506 int num_addrs = 0, i = 0;
507 char *host = app_args[0] + sizeof("ivr://") - 1;
508
509 /* Communicate through socket to server */
510 ast_debug(1, "Parsing hostname/port for socket connect from \"%s\"\n", host);
511
512 if (!(num_addrs = ast_sockaddr_resolve(&addrs, host, 0, AST_AF_UNSPEC))) {
513 ast_chan_log(LOG_ERROR, chan, "Unable to locate host '%s'\n", host);
514 goto exit;
515 }
516
517 for (i = 0; i < num_addrs; i++) {
518 if (!ast_sockaddr_port(&addrs[i])) {
519 /* Default port if not specified */
521 }
522 ast_sockaddr_copy(&ivr_desc.remote_address, &addrs[i]);
523 if (!(ser = ast_tcptls_client_create(&ivr_desc)) || !(ser = ast_tcptls_client_start(ser))) {
524 continue;
525 }
526 break;
527 }
528
529 ast_free(addrs);
530
531 if (i == num_addrs) {
532 ast_chan_log(LOG_ERROR, chan, "Could not connect to any host. ExternalIVR failed.\n");
533 goto exit;
534 }
535
536 res = eivr_comm(chan, u, ser->stream, ser->stream, NULL, comma_delim_args, flags);
537
538 } else {
539 if (pipe(child_stdin)) {
540 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child input: %s\n", strerror(errno));
541 goto exit;
542 }
543 if (pipe(child_stdout)) {
544 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child output: %s\n", strerror(errno));
545 goto exit;
546 }
547 if (pipe(child_stderr)) {
548 ast_chan_log(LOG_ERROR, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
549 goto exit;
550 }
551
552 pid = ast_safe_fork(0);
553 if (pid < 0) {
554 ast_log(LOG_ERROR, "Failed to fork(): %s\n", strerror(errno));
555 goto exit;
556 }
557
558 if (!pid) {
559 /* child process */
562
563 dup2(child_stdin[0], STDIN_FILENO);
564 dup2(child_stdout[1], STDOUT_FILENO);
565 dup2(child_stderr[1], STDERR_FILENO);
566 ast_close_fds_above_n(STDERR_FILENO);
567 execv(app_args[0], app_args);
568 fprintf(stderr, "Failed to execute '%s': %s\n", app_args[0], strerror(errno));
569 _exit(1);
570 } else {
571 /* parent process */
572 close(child_stdin[0]);
573 child_stdin[0] = -1;
574 close(child_stdout[1]);
575 child_stdout[1] = -1;
576 close(child_stderr[1]);
577 child_stderr[1] = -1;
578
579 stream_stdin = ast_iostream_from_fd(&child_stdin[1]);
580 stream_stdout = ast_iostream_from_fd(&child_stdout[0]);
581 stream_stderr = ast_iostream_from_fd(&child_stderr[0]);
582
583 res = eivr_comm(chan, u, stream_stdin, stream_stdout, stream_stderr, comma_delim_args, flags);
584 }
585 }
586
587 exit:
588 if (u->gen_active) {
590 }
591 if (stream_stdin) {
592 ast_iostream_close(stream_stdin);
593 }
594 if (stream_stdout) {
595 ast_iostream_close(stream_stdout);
596 }
597 if (stream_stderr) {
598 ast_iostream_close(stream_stderr);
599 }
600 if (child_stdin[0] > -1) {
601 close(child_stdin[0]);
602 }
603 if (child_stdin[1] > -1) {
604 close(child_stdin[1]);
605 }
606 if (child_stdout[0] > -1) {
607 close(child_stdout[0]);
608 }
609 if (child_stdout[1] > -1) {
610 close(child_stdout[1]);
611 }
612 if (child_stderr[0] > -1) {
613 close(child_stderr[0]);
614 }
615 if (child_stderr[1] > -1) {
616 close(child_stderr[1]);
617 }
618 if (ser) {
619 ao2_ref(ser, -1);
620 }
621 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
622 ast_free(entry);
623 }
624 return res;
625}
626
627static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
628 struct ast_iostream *eivr_events,
629 struct ast_iostream *eivr_commands,
630 struct ast_iostream *eivr_errors,
631 const struct ast_str *args, const struct ast_flags flags)
632{
633 char input[1024];
634 struct playlist_entry *entry;
635 struct ast_frame *f;
636 int ms;
637 int exception;
638 int ready_fd;
639 int waitfds[2];
640 int r;
641 struct ast_channel *rchan;
642 int res = -1;
643 int hangup_info_sent = 0;
644
645 waitfds[0] = ast_iostream_get_fd(eivr_commands);
646 waitfds[1] = eivr_errors ? ast_iostream_get_fd(eivr_errors) : -1;
647
648 while (1) {
650 ast_chan_log(LOG_ERROR, chan, "Is a zombie\n");
651 break;
652 }
653 if (!hangup_info_sent && !(ast_test_flag(&flags, run_dead)) && ast_check_hangup(chan)) {
655 ast_verb(3, "Got check_hangup, but ignore_hangup set so sending 'I' command\n");
656 send_eivr_event(eivr_events, 'I', "HANGUP", chan);
657 hangup_info_sent = 1;
658 } else {
659 ast_verb(3, "Got check_hangup\n");
660 send_eivr_event(eivr_events, 'H', NULL, chan);
661 break;
662 }
663 }
664
665 ready_fd = 0;
666 ms = 100;
667 errno = 0;
668 exception = 0;
669
670 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, (eivr_errors) ? 2 : 1, &exception, &ready_fd, &ms);
671
674 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
675 send_eivr_event(eivr_events, 'F', entry->filename, chan);
676 ast_free(entry);
677 }
679 }
680
681 if (ast_channel_state(chan) == AST_STATE_UP && !(ast_check_hangup(chan)) && rchan) {
682 /* the channel has something */
683 f = ast_read(chan);
684 if (!f) {
685 ast_verb(3, "Returned no frame\n");
686 send_eivr_event(eivr_events, 'H', NULL, chan);
687 break;
688 }
689 if (f->frametype == AST_FRAME_DTMF) {
690 send_eivr_event(eivr_events, f->subclass.integer, NULL, chan);
691 if (u->option_autoclear) {
693 if (!u->abort_current_sound && !u->playing_silence) {
694 /* send interrupted file as T data */
695 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
696 send_eivr_event(eivr_events, 'T', entry->filename, chan);
697 ast_free(entry);
698 }
699 }
700 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
701 send_eivr_event(eivr_events, 'D', entry->filename, chan);
702 ast_free(entry);
703 }
704 if (!u->playing_silence)
705 u->abort_current_sound = 1;
707 }
708 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass.integer == AST_CONTROL_HANGUP)) {
709 ast_verb(3, "Got AST_CONTROL_HANGUP\n");
710 send_eivr_event(eivr_events, 'H', NULL, chan);
711 if (f->data.uint32) {
713 }
714 ast_frfree(f);
715 break;
716 }
717 ast_frfree(f);
718 } else if (ready_fd == waitfds[0]) {
719 if (exception) {
720 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
721 break;
722 }
723
724 r = ast_iostream_gets(eivr_commands, input, sizeof(input));
725 if (r <= 0) {
726 if (r == 0) {
727 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
728 break;
729 }
730 continue;
731 }
732
734 ast_verb(4, "got command '%s'\n", input);
735
736 if (strlen(input) < 3) {
737 continue;
738 }
739
740 if (input[0] == EIVR_CMD_PARM) {
741 struct ast_str *tmp = (struct ast_str *) args;
742 send_eivr_event(eivr_events, 'P', ast_str_buffer(tmp), chan);
743 } else if (input[0] == EIVR_CMD_DTMF) {
744 ast_verb(4, "Sending DTMF: %s\n", &input[2]);
745 ast_eivr_senddtmf(chan, &input[2]);
746 } else if (input[0] == EIVR_CMD_ANS) {
747 ast_verb(3, "Answering channel if needed and starting generator\n");
748 if (ast_channel_state(chan) != AST_STATE_UP) {
749 if (ast_test_flag(&flags, run_dead)) {
750 ast_chan_log(LOG_WARNING, chan, "Running ExternalIVR with 'd'ead flag on non-hungup channel isn't supported\n");
751 send_eivr_event(eivr_events, 'Z', "ANSWER_FAILURE", chan);
752 continue;
753 }
754 if (ast_answer(chan)) {
755 ast_chan_log(LOG_WARNING, chan, "Failed to answer channel\n");
756 send_eivr_event(eivr_events, 'Z', "ANSWER_FAILURE", chan);
757 continue;
758 }
759 }
760 if (!(u->gen_active)) {
761 if (ast_activate_generator(chan, &gen, u) < 0) {
762 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
763 send_eivr_event(eivr_events, 'Z', "GENERATOR_FAILURE", chan);
764 } else {
765 u->gen_active = 1;
766 }
767 }
768 } else if (input[0] == EIVR_CMD_IRPT) {
769 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
770 ast_chan_log(LOG_WARNING, chan, "Queue 'I'nterrupt called on unanswered channel\n");
771 send_eivr_event(eivr_events, 'Z', NULL, chan);
772 continue;
773 }
775 if (!u->abort_current_sound && !u->playing_silence) {
776 /* send interrupted file as T data */
777 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
778 send_eivr_event(eivr_events, 'T', entry->filename, chan);
779 ast_free(entry);
780 }
781 }
782 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
783 send_eivr_event(eivr_events, 'D', entry->filename, chan);
784 ast_free(entry);
785 }
786 if (!u->playing_silence) {
787 u->abort_current_sound = 1;
788 }
790 } else if (input[0] == EIVR_CMD_SQUE) {
791 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
792 ast_chan_log(LOG_WARNING, chan, "Queue re'S'et called on unanswered channel\n");
793 send_eivr_event(eivr_events, 'Z', NULL, chan);
794 continue;
795 }
797 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
798 send_eivr_event(eivr_events, 'Z', &input[2], chan);
799 } else {
801 if (!u->abort_current_sound && !u->playing_silence) {
802 /* send interrupted file as T data */
803 if ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
804 send_eivr_event(eivr_events, 'T', entry->filename, chan);
805 ast_free(entry);
806 }
807 }
808 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
809 send_eivr_event(eivr_events, 'D', entry->filename, chan);
810 ast_free(entry);
811 }
812 if (!u->playing_silence) {
813 u->abort_current_sound = 1;
814 }
815 entry = make_entry(&input[2]);
816 if (entry) {
817 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
818 }
820 }
821 } else if (input[0] == EIVR_CMD_APND) {
822 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
823 ast_chan_log(LOG_WARNING, chan, "Queue 'A'ppend called on unanswered channel\n");
824 send_eivr_event(eivr_events, 'Z', NULL, chan);
825 continue;
826 }
828 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
829 send_eivr_event(eivr_events, 'Z', &input[2], chan);
830 } else {
831 entry = make_entry(&input[2]);
832 if (entry) {
834 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
836 }
837 }
838 } else if (input[0] == EIVR_CMD_GET) {
839 char response[2048];
840 ast_verb(4, "Retrieving Variables from channel: %s\n", &input[2]);
841 ast_eivr_getvariable(chan, &input[2], response, sizeof(response));
842 send_eivr_event(eivr_events, 'G', response, chan);
843 } else if (input[0] == EIVR_CMD_SVAR) {
844 ast_verb(4, "Setting Variables in channel: %s\n", &input[2]);
845 ast_eivr_setvariable(chan, &input[2]);
846 } else if (input[0] == EIVR_CMD_LOG) {
847 ast_chan_log(LOG_NOTICE, chan, "Log message from EIVR: %s\n", &input[2]);
848 } else if (input[0] == EIVR_CMD_XIT) {
849 ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
850 ast_chan_log(LOG_WARNING, chan, "e'X'it command is deprecated, use 'E'xit instead\n");
851 res = 0;
852 break;
853 } else if (input[0] == EIVR_CMD_EXIT) {
854 ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
855 send_eivr_event(eivr_events, 'E', NULL, chan);
856 res = 0;
857 break;
858 } else if (input[0] == EIVR_CMD_HGUP) {
859 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
860 send_eivr_event(eivr_events, 'H', NULL, chan);
861 break;
862 } else if (input[0] == EIVR_CMD_OPT) {
863 if (ast_channel_state(chan) != AST_STATE_UP || ast_check_hangup(chan)) {
864 ast_chan_log(LOG_WARNING, chan, "Option called on unanswered channel\n");
865 send_eivr_event(eivr_events, 'Z', NULL, chan);
866 continue;
867 }
868 if (!strcasecmp(&input[2], "autoclear"))
869 u->option_autoclear = 1;
870 else if (!strcasecmp(&input[2], "noautoclear"))
871 u->option_autoclear = 0;
872 else
873 ast_chan_log(LOG_WARNING, chan, "Unknown option requested: %s\n", &input[2]);
874 }
875 } else if (ready_fd == waitfds[1]) {
876 if (exception) {
877 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
878 break;
879 }
880
881 r = ast_iostream_gets(eivr_errors, input, sizeof(input));
882 if (r > 0) {
883 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", ast_strip(input));
884 } else if (r == 0) {
885 ast_chan_log(LOG_ERROR, chan, "Child process went away\n");
886 break;
887 }
888 } else if ((ready_fd < 0) && ms) {
889 if (errno == 0 || errno == EINTR)
890 continue;
891
892 ast_chan_log(LOG_ERROR, chan, "Wait failed (%s)\n", strerror(errno));
893 break;
894 }
895 }
896
897 return res;
898}
899
900static int unload_module(void)
901{
903}
904
905static int load_module(void)
906{
908}
909
910AST_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
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:1848
#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
enum cc_state state
Definition: ccss.c:399
General Asterisk PBX channel definitions.
int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen, void *params)
Definition: channel.c:2979
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:3016
#define ast_channel_lock(chan)
Definition: channel.h:2970
struct ast_flags * ast_channel_flags(struct ast_channel *chan)
void ast_deactivate_generator(struct ast_channel *chan)
Definition: channel.c:2921
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:5161
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition: channel.c:4274
int ast_check_hangup(struct ast_channel *chan)
Check to see if a channel is needing hang up.
Definition: channel.c:445
@ AST_FLAG_ZOMBIE
Definition: channel.h:1007
const char * ast_channel_language(const struct ast_channel *chan)
void ast_channel_hangupcause_set(struct ast_channel *chan, int value)
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:2834
#define ast_channel_unlock(chan)
Definition: channel.h:2971
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:850
struct ast_frame * ast_readframe(struct ast_filestream *s)
Read a frame from a filestream.
Definition: file.c:944
int ast_closestream(struct ast_filestream *f)
Closes a stream.
Definition: file.c:1119
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
Definition: file.c:1137
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.
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:640
#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
@ AST_AF_UNSPEC
Definition: netsock2.h:54
#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.
union ast_frame::@228 data
struct ast_frame_subclass subclass
enum ast_frame_type frametype
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
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: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
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