Optionally display the value of several variables within the Status command.
[asterisk-bristuff.git] / apps / app_externalivr.c
bloba93abb4a8b695602406a8c4179300d6c109c69fb
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Kevin P. Fleming <kpfleming@digium.com>
8 * Portions taken from the file-based music-on-hold work
9 * created by Anthony Minessale II in res_musiconhold.c
11 * See http://www.asterisk.org for more information about
12 * the Asterisk project. Please do not directly contact
13 * any of the maintainers of this project for assistance;
14 * the project provides a web site, mailing lists and IRC
15 * channels for your use.
17 * This program is free software, distributed under the terms of
18 * the GNU General Public License Version 2. See the LICENSE file
19 * at the top of the source tree.
22 /*! \file
24 * \brief External IVR application interface
26 * \author Kevin P. Fleming <kpfleming@digium.com>
28 * \note Portions taken from the file-based music-on-hold work
29 * created by Anthony Minessale II in res_musiconhold.c
31 * \ingroup applications
34 #include "asterisk.h"
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38 #include <signal.h>
40 #include "asterisk/lock.h"
41 #include "asterisk/file.h"
42 #include "asterisk/channel.h"
43 #include "asterisk/pbx.h"
44 #include "asterisk/module.h"
45 #include "asterisk/linkedlists.h"
46 #include "asterisk/app.h"
47 #include "asterisk/utils.h"
48 #include "asterisk/tcptls.h"
50 static const char *app = "ExternalIVR";
52 static const char *synopsis = "Interfaces with an external IVR application";
54 static const char *descrip =
55 " ExternalIVR(command|ivr://ivrhost[,arg[,arg...]]): Either forks a process\n"
56 "to run given command or makes a socket to connect to given host and starts\n"
57 "a generator on the channel. The generator's play list is controlled by the\n"
58 "external application, which can add and clear entries via simple commands\n"
59 "issued over its stdout. The external application will receive all DTMF events\n"
60 "received on the channel, and notification if the channel is hung up. The\n"
61 "application will not be forcibly terminated when the channel is hung up.\n"
62 "See doc/externalivr.txt for a protocol specification.\n";
64 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
65 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
67 struct playlist_entry {
68 AST_LIST_ENTRY(playlist_entry) list;
69 char filename[1];
72 struct ivr_localuser {
73 struct ast_channel *chan;
74 AST_LIST_HEAD(playlist, playlist_entry) playlist;
75 AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
76 int abort_current_sound;
77 int playing_silence;
78 int option_autoclear;
82 struct gen_state {
83 struct ivr_localuser *u;
84 struct ast_filestream *stream;
85 struct playlist_entry *current;
86 int sample_queue;
89 static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
90 int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd,
91 const char *args);
93 int eivr_connect_socket(struct ast_channel *chan, const char *host, int port);
95 static void send_eivr_event(FILE *handle, const char event, const char *data,
96 const struct ast_channel *chan)
98 char tmp[256];
100 if (!data) {
101 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
102 } else {
103 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
106 fprintf(handle, "%s\n", tmp);
107 if (option_debug)
108 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
111 static void *gen_alloc(struct ast_channel *chan, void *params)
113 struct ivr_localuser *u = params;
114 struct gen_state *state;
116 if (!(state = ast_calloc(1, sizeof(*state))))
117 return NULL;
119 state->u = u;
121 return state;
124 static void gen_closestream(struct gen_state *state)
126 if (!state->stream)
127 return;
129 ast_closestream(state->stream);
130 state->u->chan->stream = NULL;
131 state->stream = NULL;
134 static void gen_release(struct ast_channel *chan, void *data)
136 struct gen_state *state = data;
138 gen_closestream(state);
139 ast_free(data);
142 /* caller has the playlist locked */
143 static int gen_nextfile(struct gen_state *state)
145 struct ivr_localuser *u = state->u;
146 char *file_to_stream;
148 u->abort_current_sound = 0;
149 u->playing_silence = 0;
150 gen_closestream(state);
152 while (!state->stream) {
153 state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
154 if (state->current) {
155 file_to_stream = state->current->filename;
156 } else {
157 file_to_stream = "silence/10";
158 u->playing_silence = 1;
161 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
162 ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
163 if (!u->playing_silence) {
164 continue;
165 } else {
166 break;
171 return (!state->stream);
174 static struct ast_frame *gen_readframe(struct gen_state *state)
176 struct ast_frame *f = NULL;
177 struct ivr_localuser *u = state->u;
179 if (u->abort_current_sound ||
180 (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
181 gen_closestream(state);
182 AST_LIST_LOCK(&u->playlist);
183 gen_nextfile(state);
184 AST_LIST_UNLOCK(&u->playlist);
187 if (!(state->stream && (f = ast_readframe(state->stream)))) {
188 if (state->current) {
189 AST_LIST_LOCK(&u->finishlist);
190 AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
191 AST_LIST_UNLOCK(&u->finishlist);
192 state->current = NULL;
194 if (!gen_nextfile(state))
195 f = ast_readframe(state->stream);
198 return f;
201 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
203 struct gen_state *state = data;
204 struct ast_frame *f = NULL;
205 int res = 0;
207 state->sample_queue += samples;
209 while (state->sample_queue > 0) {
210 if (!(f = gen_readframe(state)))
211 return -1;
213 res = ast_write(chan, f);
214 ast_frfree(f);
215 if (res < 0) {
216 ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
217 return -1;
219 state->sample_queue -= f->samples;
222 return res;
225 static struct ast_generator gen =
227 alloc: gen_alloc,
228 release: gen_release,
229 generate: gen_generate,
232 static void ast_eivr_getvariable(struct ast_channel *chan, char *data, char *outbuf, int outbuflen)
234 /* original input data: "G,var1,var2," */
235 /* data passed as "data": "var1,var2" */
237 char *inbuf, *variable;
238 const char *value;
239 int j;
240 struct ast_str *newstring = ast_str_alloca(outbuflen);
242 outbuf[0] = '\0';
244 for (j = 1, inbuf = data; ; j++) {
245 variable = strsep(&inbuf, ",");
246 if (variable == NULL) {
247 int outstrlen = strlen(outbuf);
248 if(outstrlen && outbuf[outstrlen - 1] == ',') {
249 outbuf[outstrlen - 1] = 0;
251 break;
254 ast_channel_lock(chan);
255 if (!(value = pbx_builtin_getvar_helper(chan, variable))) {
256 value = "";
259 ast_str_append(&newstring, 0, "%s=%s,", variable, value);
260 ast_channel_unlock(chan);
261 ast_copy_string(outbuf, newstring->str, outbuflen);
265 static void ast_eivr_setvariable(struct ast_channel *chan, char *data)
267 char buf[1024];
268 char *value;
270 char *inbuf, *variable;
272 int j;
274 for (j = 1, inbuf = data; ; j++, inbuf = NULL) {
275 variable = strsep(&inbuf, ",");
276 ast_chan_log(LOG_DEBUG, chan, "Setting up a variable: %s\n", variable);
277 if(variable) {
278 /* variable contains "varname=value" */
279 ast_copy_string(buf, variable, sizeof(buf));
280 value = strchr(buf, '=');
281 if(!value)
282 value="";
283 else
284 *value++ = '\0';
285 pbx_builtin_setvar_helper(chan, buf, value);
287 else
288 break;
292 static struct playlist_entry *make_entry(const char *filename)
294 struct playlist_entry *entry;
296 if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
297 return NULL;
299 strcpy(entry->filename, filename);
301 return entry;
304 static int app_exec(struct ast_channel *chan, void *data)
306 struct playlist_entry *entry;
307 int child_stdin[2] = { 0,0 };
308 int child_stdout[2] = { 0,0 };
309 int child_stderr[2] = { 0,0 };
310 int res = -1;
311 int gen_active = 0;
312 int pid;
313 char *buf, *pipe_delim_argbuf, *pdargbuf_ptr;
315 char hostname[1024];
316 char *port_str = NULL;
317 int port = 0;
318 struct ast_tcptls_session_instance *ser = NULL;
320 struct ivr_localuser foo = {
321 .playlist = AST_LIST_HEAD_INIT_VALUE,
322 .finishlist = AST_LIST_HEAD_INIT_VALUE,
324 struct ivr_localuser *u = &foo;
325 AST_DECLARE_APP_ARGS(args,
326 AST_APP_ARG(cmd)[32];
329 u->abort_current_sound = 0;
330 u->chan = chan;
332 if (ast_strlen_zero(data)) {
333 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
334 return -1;
337 buf = ast_strdupa(data);
338 AST_STANDARD_APP_ARGS(args, buf);
340 /* copy args and replace commas with pipes */
341 pipe_delim_argbuf = ast_strdupa(data);
342 while((pdargbuf_ptr = strchr(pipe_delim_argbuf, ',')) != NULL)
343 pdargbuf_ptr[0] = '|';
345 if(!strncmp(args.cmd[0], "ivr://", 6)) {
346 struct server_args ivr_desc = {
347 .accept_fd = -1,
348 .name = "IVR",
350 struct ast_hostent hp;
352 /*communicate through socket to server*/
353 if (chan->_state != AST_STATE_UP) {
354 ast_answer(chan);
356 if (ast_activate_generator(chan, &gen, u) < 0) {
357 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
358 goto exit;
359 } else {
360 gen_active = 1;
363 ast_chan_log(LOG_DEBUG, chan, "Parsing hostname:port for socket connect from \"%s\"\n", args.cmd[0]);
364 strncpy(hostname, args.cmd[0] + 6, sizeof(hostname));
365 if((port_str = strchr(hostname, ':')) != NULL) {
366 port_str[0] = 0;
367 port_str += 1;
368 port = atoi(port_str);
370 if(!port)
371 port = 2949; /*default port, if one is not provided*/
373 ast_gethostbyname(hostname, &hp);
374 ivr_desc.sin.sin_family = AF_INET;
375 ivr_desc.sin.sin_port = htons(port);
376 memmove(&ivr_desc.sin.sin_addr.s_addr, hp.hp.h_addr, hp.hp.h_length);
377 ser = ast_tcptls_client_start(&ivr_desc);
379 if (!ser) {
380 goto exit;
382 res = eivr_comm(chan, u, ser->fd, ser->fd, 0, pipe_delim_argbuf);
383 } else {
385 if (pipe(child_stdin)) {
386 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
387 goto exit;
389 if (pipe(child_stdout)) {
390 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
391 goto exit;
393 if (pipe(child_stderr)) {
394 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
395 goto exit;
397 if (chan->_state != AST_STATE_UP) {
398 ast_answer(chan);
400 if (ast_activate_generator(chan, &gen, u) < 0) {
401 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
402 goto exit;
403 } else {
404 gen_active = 1;
407 pid = ast_safe_fork(0);
408 if (pid < 0) {
409 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
410 goto exit;
413 if (!pid) {
414 /* child process */
415 if (ast_opt_high_priority)
416 ast_set_priority(0);
418 dup2(child_stdin[0], STDIN_FILENO);
419 dup2(child_stdout[1], STDOUT_FILENO);
420 dup2(child_stderr[1], STDERR_FILENO);
421 ast_close_fds_above_n(STDERR_FILENO);
422 execv(args.cmd[0], args.cmd);
423 fprintf(stderr, "Failed to execute '%s': %s\n", args.cmd[0], strerror(errno));
424 _exit(1);
425 } else {
426 /* parent process */
428 close(child_stdin[0]);
429 child_stdin[0] = 0;
430 close(child_stdout[1]);
431 child_stdout[1] = 0;
432 close(child_stderr[1]);
433 child_stderr[1] = 0;
434 res = eivr_comm(chan, u, child_stdin[1], child_stdout[0], child_stderr[0], pipe_delim_argbuf);
438 exit:
439 if (gen_active)
440 ast_deactivate_generator(chan);
442 if (child_stdin[0])
443 close(child_stdin[0]);
445 if (child_stdin[1])
446 close(child_stdin[1]);
448 if (child_stdout[0])
449 close(child_stdout[0]);
451 if (child_stdout[1])
452 close(child_stdout[1]);
454 if (child_stderr[0])
455 close(child_stderr[0]);
457 if (child_stderr[1])
458 close(child_stderr[1]);
460 if (ser) {
461 fclose(ser->f);
462 ast_tcptls_session_instance_destroy(ser);
465 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
466 ast_free(entry);
468 return res;
471 static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u,
472 int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd,
473 const char *args)
475 struct playlist_entry *entry;
476 struct ast_frame *f;
477 int ms;
478 int exception;
479 int ready_fd;
480 int waitfds[2] = { eivr_commands_fd, eivr_errors_fd };
481 struct ast_channel *rchan;
482 char *command;
483 int res = -1;
485 FILE *eivr_commands = NULL;
486 FILE *eivr_errors = NULL;
487 FILE *eivr_events = NULL;
489 if (!(eivr_events = fdopen(eivr_events_fd, "w"))) {
490 ast_chan_log(LOG_WARNING, chan, "Could not open stream to send events\n");
491 goto exit;
493 if (!(eivr_commands = fdopen(eivr_commands_fd, "r"))) {
494 ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive commands\n");
495 goto exit;
497 if(eivr_errors_fd) { /* if opening a socket connection, error stream will not be used */
498 if (!(eivr_errors = fdopen(eivr_errors_fd, "r"))) {
499 ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive errors\n");
500 goto exit;
504 setvbuf(eivr_events, NULL, _IONBF, 0);
505 setvbuf(eivr_commands, NULL, _IONBF, 0);
506 if(eivr_errors)
507 setvbuf(eivr_errors, NULL, _IONBF, 0);
509 res = 0;
511 while (1) {
512 if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
513 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
514 res = -1;
515 break;
517 if (ast_check_hangup(chan)) {
518 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
519 send_eivr_event(eivr_events, 'H', NULL, chan);
520 res = -1;
521 break;
524 ready_fd = 0;
525 ms = 100;
526 errno = 0;
527 exception = 0;
529 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
531 if (!AST_LIST_EMPTY(&u->finishlist)) {
532 AST_LIST_LOCK(&u->finishlist);
533 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
534 send_eivr_event(eivr_events, 'F', entry->filename, chan);
535 ast_free(entry);
537 AST_LIST_UNLOCK(&u->finishlist);
540 if (rchan) {
541 /* the channel has something */
542 f = ast_read(chan);
543 if (!f) {
544 ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
545 send_eivr_event(eivr_events, 'H', NULL, chan);
546 res = -1;
547 break;
549 if (f->frametype == AST_FRAME_DTMF) {
550 send_eivr_event(eivr_events, f->subclass, NULL, chan);
551 if (u->option_autoclear) {
552 if (!u->abort_current_sound && !u->playing_silence)
553 send_eivr_event(eivr_events, 'T', NULL, chan);
554 AST_LIST_LOCK(&u->playlist);
555 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
556 send_eivr_event(eivr_events, 'D', entry->filename, chan);
557 ast_free(entry);
559 if (!u->playing_silence)
560 u->abort_current_sound = 1;
561 AST_LIST_UNLOCK(&u->playlist);
563 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
564 ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
565 send_eivr_event(eivr_events, 'H', NULL, chan);
566 if (f->seqno) {
567 chan->hangupcause = f->seqno;
569 ast_frfree(f);
570 res = -1;
571 break;
573 ast_frfree(f);
574 } else if (ready_fd == eivr_commands_fd) {
575 char input[1024];
577 if (exception || feof(eivr_commands)) {
578 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
579 res = -1;
580 break;
583 if (!fgets(input, sizeof(input), eivr_commands))
584 continue;
586 command = ast_strip(input);
588 if (option_debug)
589 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
591 if (strlen(input) < 4)
592 continue;
594 if (input[0] == 'P') {
595 send_eivr_event(eivr_events, 'P', args, chan);
597 } else if (input[0] == 'S') {
598 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
599 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
600 send_eivr_event(eivr_events, 'Z', NULL, chan);
601 strcpy(&input[2], "exception");
603 if (!u->abort_current_sound && !u->playing_silence)
604 send_eivr_event(eivr_events, 'T', NULL, chan);
605 AST_LIST_LOCK(&u->playlist);
606 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
607 send_eivr_event(eivr_events, 'D', entry->filename, chan);
608 ast_free(entry);
610 if (!u->playing_silence)
611 u->abort_current_sound = 1;
612 entry = make_entry(&input[2]);
613 if (entry)
614 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
615 AST_LIST_UNLOCK(&u->playlist);
616 } else if (input[0] == 'A') {
617 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
618 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
619 send_eivr_event(eivr_events, 'Z', NULL, chan);
620 strcpy(&input[2], "exception");
622 entry = make_entry(&input[2]);
623 if (entry) {
624 AST_LIST_LOCK(&u->playlist);
625 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
626 AST_LIST_UNLOCK(&u->playlist);
628 } else if (input[0] == 'G') {
629 /* A get variable message: "G,variable1,variable2,..." */
630 char response[2048];
632 ast_chan_log(LOG_NOTICE, chan, "Getting a Variable out of the channel: %s\n", &input[2]);
633 ast_eivr_getvariable(chan, &input[2], response, sizeof(response));
634 send_eivr_event(eivr_events, 'G', response, chan);
635 } else if (input[0] == 'V') {
636 /* A set variable message: "V,variablename=foo" */
637 ast_chan_log(LOG_NOTICE, chan, "Setting a Variable up: %s\n", &input[2]);
638 ast_eivr_setvariable(chan, &input[2]);
639 } else if (input[0] == 'L') {
640 ast_chan_log(LOG_NOTICE, chan, "Log message from EIVR: %s\n", &input[2]);
641 } else if (input[0] == 'X') {
642 ast_chan_log(LOG_NOTICE, chan, "Exiting ExternalIVR: %s\n", &input[2]);
643 /*! \todo add deprecation debug message for X command here */
644 res = 0;
645 break;
646 } else if (input[0] == 'E') {
647 ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
648 send_eivr_event(eivr_events, 'E', NULL, chan);
649 res = 0;
650 break;
651 } else if (input[0] == 'H') {
652 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
653 send_eivr_event(eivr_events, 'H', NULL, chan);
654 res = -1;
655 break;
656 } else if (input[0] == 'O') {
657 if (!strcasecmp(&input[2], "autoclear"))
658 u->option_autoclear = 1;
659 else if (!strcasecmp(&input[2], "noautoclear"))
660 u->option_autoclear = 0;
661 else
662 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
664 } else if (eivr_errors_fd && ready_fd == eivr_errors_fd) {
665 char input[1024];
667 if (exception || feof(eivr_errors)) {
668 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
669 res = -1;
670 break;
672 if (fgets(input, sizeof(input), eivr_errors)) {
673 command = ast_strip(input);
674 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
676 } else if ((ready_fd < 0) && ms) {
677 if (errno == 0 || errno == EINTR)
678 continue;
680 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
681 break;
686 exit:
688 if (eivr_events)
689 fclose(eivr_events);
691 if (eivr_commands)
692 fclose(eivr_commands);
694 if (eivr_errors)
695 fclose(eivr_errors);
697 return res;
701 static int unload_module(void)
703 return ast_unregister_application(app);
706 static int load_module(void)
708 return ast_register_application(app, app_exec, synopsis, descrip);
711 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");