/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2006, Digium, Inc. * * Mark Spencer * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \brief AGI - the Asterisk Gateway Interface * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/file.h" #include "asterisk/logger.h" #include "asterisk/channel.h" #include "asterisk/pbx.h" #include "asterisk/module.h" #include "asterisk/astdb.h" #include "asterisk/callerid.h" #include "asterisk/cli.h" #include "asterisk/logger.h" #include "asterisk/options.h" #include "asterisk/image.h" #include "asterisk/say.h" #include "asterisk/app.h" #include "asterisk/dsp.h" #include "asterisk/musiconhold.h" #include "asterisk/manager.h" #include "asterisk/utils.h" #include "asterisk/lock.h" #include "asterisk/strings.h" #include "asterisk/agi.h" #define MAX_ARGS 128 #define MAX_COMMANDS 128 /* Recycle some stuff from the CLI interface */ #define fdprintf agi_debug_cli static char *tdesc = "Asterisk Gateway Interface (AGI)"; static char *app = "AGI"; static char *eapp = "EAGI"; static char *deadapp = "DeadAGI"; static char *synopsis = "Executes an AGI compliant application"; static char *esynopsis = "Executes an EAGI compliant application"; static char *deadsynopsis = "Executes AGI on a hungup channel"; static char *descrip = " [E|Dead]AGI(command|args): Executes an Asterisk Gateway Interface compliant\n" "program on a channel. AGI allows Asterisk to launch external programs\n" "written in any language to control a telephony channel, play audio,\n" "read DTMF digits, etc. by communicating with the AGI protocol on stdin\n" "and stdout.\n" "Returns -1 on hangup (except for DeadAGI) or if application requested\n" " hangup, or 0 on non-hangup exit. \n" "Using 'EAGI' provides enhanced AGI, with incoming audio available out of band\n" "on file descriptor 3\n\n" "Use the CLI command 'show agi' to list available agi commands\n"; static int agidebug = 0; STANDARD_LOCAL_USER; LOCAL_USER_DECL; #define TONE_BLOCK_SIZE 200 /* Max time to connect to an AGI remote host */ #define MAX_AGI_CONNECT 2000 #define AGI_PORT 4573 static void agi_debug_cli(int fd, char *fmt, ...) { char *stuff; int res = 0; va_list ap; va_start(ap, fmt); res = vasprintf(&stuff, fmt, ap); va_end(ap); if (res == -1) { ast_log(LOG_ERROR, "Out of memory\n"); } else { if (agidebug) ast_verbose("AGI Tx >> %s", stuff); ast_carefulwrite(fd, stuff, strlen(stuff), 100); free(stuff); } } /* launch_netscript: The fastagi handler. FastAGI defaults to port 4573 */ static int launch_netscript(char *agiurl, char *argv[], int *fds, int *efd, int *opid) { int s; int flags; struct pollfd pfds[1]; char *host; char *c; int port = AGI_PORT; char *script=""; struct sockaddr_in sin; struct hostent *hp; struct ast_hostent ahp; int res; host = ast_strdupa(agiurl + 6); /* Remove agi:// */ if (!host) return -1; /* Strip off any script name */ if ((c = strchr(host, '/'))) { *c = '\0'; c++; script = c; } if ((c = strchr(host, ':'))) { *c = '\0'; c++; port = atoi(c); } if (efd) { ast_log(LOG_WARNING, "AGI URI's don't support Enhanced AGI yet\n"); return -1; } hp = ast_gethostbyname(host, &ahp); if (!hp) { ast_log(LOG_WARNING, "Unable to locate host '%s'\n", host); return -1; } s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno)); return -1; } flags = fcntl(s, F_GETFL); if (flags < 0) { ast_log(LOG_WARNING, "Fcntl(F_GETFL) failed: %s\n", strerror(errno)); close(s); return -1; } if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) { ast_log(LOG_WARNING, "Fnctl(F_SETFL) failed: %s\n", strerror(errno)); close(s); return -1; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(port); memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr)); if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) && (errno != EINPROGRESS)) { ast_log(LOG_WARNING, "Connect failed with unexpected error: %s\n", strerror(errno)); close(s); return -1; } pfds[0].fd = s; pfds[0].events = POLLOUT; while ((res = poll(pfds, 1, MAX_AGI_CONNECT)) != 1) { if (errno != EINTR) { if (!res) { ast_log(LOG_WARNING, "FastAGI connection to '%s' timed out after MAX_AGI_CONNECT (%d) milliseconds.\n", agiurl, MAX_AGI_CONNECT); } else ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", agiurl, strerror(errno)); close(s); return -1; } } while (write(s, "agi_network: yes\n", strlen("agi_network: yes\n")) < 0) { if (errno != EINTR) { ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", agiurl, strerror(errno)); close(s); return -1; } } /* If we have a script parameter, relay it to the fastagi server */ if (!ast_strlen_zero(script)) fdprintf(s, "agi_network_script: %s\n", script); if (option_debug > 3) ast_log(LOG_DEBUG, "Wow, connected!\n"); fds[0] = s; fds[1] = s; *opid = -1; return 0; } static int launch_script(char *script, char *argv[], int *fds, int *efd, int *opid) { char tmp[256]; int pid; int toast[2]; int fromast[2]; int audio[2]; int x; int res; sigset_t signal_set, old_set; if (!strncasecmp(script, "agi://", 6)) return launch_netscript(script, argv, fds, efd, opid); if (script[0] != '/') { snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_AGI_DIR, script); script = tmp; } if (pipe(toast)) { ast_log(LOG_WARNING, "Unable to create toast pipe: %s\n",strerror(errno)); return -1; } if (pipe(fromast)) { ast_log(LOG_WARNING, "unable to create fromast pipe: %s\n", strerror(errno)); close(toast[0]); close(toast[1]); return -1; } if (efd) { if (pipe(audio)) { ast_log(LOG_WARNING, "unable to create audio pipe: %s\n", strerror(errno)); close(fromast[0]); close(fromast[1]); close(toast[0]); close(toast[1]); return -1; } res = fcntl(audio[1], F_GETFL); if (res > -1) res = fcntl(audio[1], F_SETFL, res | O_NONBLOCK); if (res < 0) { ast_log(LOG_WARNING, "unable to set audio pipe parameters: %s\n", strerror(errno)); close(fromast[0]); close(fromast[1]); close(toast[0]); close(toast[1]); close(audio[0]); close(audio[1]); return -1; } } /* Block SIGHUP during the fork - prevents a race */ sigfillset(&signal_set); pthread_sigmask(SIG_BLOCK, &signal_set, &old_set); pid = fork(); if (pid < 0) { ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno)); return -1; } if (!pid) { /* Don't run AGI scripts with realtime priority -- it causes audio stutter */ ast_set_priority(0); /* Redirect stdin and out, provide enhanced audio channel if desired */ dup2(fromast[0], STDIN_FILENO); dup2(toast[1], STDOUT_FILENO); if (efd) { dup2(audio[0], STDERR_FILENO + 1); } else { close(STDERR_FILENO + 1); } /* Before we unblock our signals, return our trapped signals back to the defaults */ signal(SIGHUP, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGURG, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGPIPE, SIG_DFL); signal(SIGXFSZ, SIG_DFL); /* unblock important signal handlers */ if (pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL)) { ast_log(LOG_WARNING, "unable to unblock signals for AGI script: %s\n", strerror(errno)); _exit(1); } /* Close everything but stdin/out/error */ for (x=STDERR_FILENO + 2;x<1024;x++) close(x); /* Execute script */ execv(script, argv); /* Can't use ast_log since FD's are closed */ fprintf(stderr, "Failed to execute '%s': %s\n", script, strerror(errno)); _exit(1); } pthread_sigmask(SIG_SETMASK, &old_set, NULL); if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Launched AGI Script %s\n", script); fds[0] = toast[0]; fds[1] = fromast[1]; if (efd) { *efd = audio[1]; } /* close what we're not using in the parent */ close(toast[1]); close(fromast[0]); if (efd) { /* [PHM 12/18/03] */ close(audio[0]); } *opid = pid; return 0; } static void setup_env(struct ast_channel *chan, char *request, int fd, int enhanced) { /* Print initial environment, with agi_request always being the first thing */ fdprintf(fd, "agi_request: %s\n", request); fdprintf(fd, "agi_channel: %s\n", chan->name); fdprintf(fd, "agi_language: %s\n", chan->language); fdprintf(fd, "agi_type: %s\n", chan->type); fdprintf(fd, "agi_uniqueid: %s\n", chan->uniqueid); /* ANI/DNIS */ fdprintf(fd, "agi_callerid: %s\n", chan->cid.cid_num ? chan->cid.cid_num : "unknown"); fdprintf(fd, "agi_calleridname: %s\n", chan->cid.cid_name ? chan->cid.cid_name : "unknown"); fdprintf(fd, "agi_callingpres: %d\n", chan->cid.cid_pres); fdprintf(fd, "agi_callingani2: %d\n", chan->cid.cid_ani2); fdprintf(fd, "agi_callington: %d\n", chan->cid.cid_ton); fdprintf(fd, "agi_callingtns: %d\n", chan->cid.cid_tns); fdprintf(fd, "agi_dnid: %s\n", chan->cid.cid_dnid ? chan->cid.cid_dnid : "unknown"); fdprintf(fd, "agi_rdnis: %s\n", chan->cid.cid_rdnis ? chan->cid.cid_rdnis : "unknown"); /* Context information */ fdprintf(fd, "agi_context: %s\n", chan->context); fdprintf(fd, "agi_extension: %s\n", chan->exten); fdprintf(fd, "agi_priority: %d\n", chan->priority); fdprintf(fd, "agi_enhanced: %s\n", enhanced ? "1.0" : "0.0"); /* User information */ fdprintf(fd, "agi_accountcode: %s\n", chan->accountcode ? chan->accountcode : ""); /* End with empty return */ fdprintf(fd, "\n"); } static int handle_answer(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; res = 0; if (chan->_state != AST_STATE_UP) { /* Answer the chan */ res = ast_answer(chan); } fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_waitfordigit(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; int to; if (argc != 4) return RESULT_SHOWUSAGE; if (sscanf(argv[3], "%d", &to) != 1) return RESULT_SHOWUSAGE; res = ast_waitfordigit_full(chan, to, agi->audio, agi->ctrl); fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_sendtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; if (argc != 3) return RESULT_SHOWUSAGE; /* At the moment, the parser (perhaps broken) returns with the last argument PLUS the newline at the end of the input buffer. This probably needs to be fixed, but I wont do that because other stuff may break as a result. The right way would probably be to strip off the trailing newline before parsing, then here, add a newline at the end of the string before sending it to ast_sendtext --DUDE */ res = ast_sendtext(chan, argv[2]); fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_recvchar(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; if (argc != 3) return RESULT_SHOWUSAGE; res = ast_recvchar(chan,atoi(argv[2])); if (res == 0) { fdprintf(agi->fd, "200 result=%d (timeout)\n", res); return RESULT_SUCCESS; } if (res > 0) { fdprintf(agi->fd, "200 result=%d\n", res); return RESULT_SUCCESS; } else { fdprintf(agi->fd, "200 result=%d (hangup)\n", res); return RESULT_FAILURE; } } static int handle_recvtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { char *buf; if (argc != 3) return RESULT_SHOWUSAGE; buf = ast_recvtext(chan,atoi(argv[2])); if (buf) { fdprintf(agi->fd, "200 result=1 (%s)\n", buf); free(buf); } else { fdprintf(agi->fd, "200 result=-1\n"); } return RESULT_SUCCESS; } static int handle_tddmode(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res,x; if (argc != 3) return RESULT_SHOWUSAGE; if (!strncasecmp(argv[2],"on",2)) x = 1; else x = 0; if (!strncasecmp(argv[2],"mate",4)) x = 2; if (!strncasecmp(argv[2],"tdd",3)) x = 1; res = ast_channel_setoption(chan, AST_OPTION_TDD, &x, sizeof(char), 0); if (res != RESULT_SUCCESS) fdprintf(agi->fd, "200 result=0\n"); else fdprintf(agi->fd, "200 result=1\n"); return RESULT_SUCCESS; } static int handle_sendimage(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; if (argc != 3) return RESULT_SHOWUSAGE; res = ast_send_image(chan, argv[2]); if (!ast_check_hangup(chan)) res = 0; fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_controlstreamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res = 0; int skipms = 3000; char *fwd = NULL; char *rev = NULL; char *pause = NULL; char *stop = NULL; if (argc < 5 || argc > 9) return RESULT_SHOWUSAGE; if (!ast_strlen_zero(argv[4])) stop = argv[4]; else stop = NULL; if ((argc > 5) && (sscanf(argv[5], "%d", &skipms) != 1)) return RESULT_SHOWUSAGE; if (argc > 6 && !ast_strlen_zero(argv[6])) fwd = argv[6]; else fwd = "#"; if (argc > 7 && !ast_strlen_zero(argv[7])) rev = argv[7]; else rev = "*"; if (argc > 8 && !ast_strlen_zero(argv[8])) pause = argv[8]; else pause = NULL; res = ast_control_streamfile(chan, argv[3], fwd, rev, stop, pause, NULL, skipms); fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_streamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; struct ast_filestream *fs; long sample_offset = 0; long max_length; if (argc < 4) return RESULT_SHOWUSAGE; if (argc > 5) return RESULT_SHOWUSAGE; if ((argc > 4) && (sscanf(argv[4], "%ld", &sample_offset) != 1)) return RESULT_SHOWUSAGE; fs = ast_openstream(chan, argv[2], chan->language); if (!fs){ fdprintf(agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset); return RESULT_SUCCESS; } ast_seekstream(fs, 0, SEEK_END); max_length = ast_tellstream(fs); ast_seekstream(fs, sample_offset, SEEK_SET); res = ast_applystream(chan, fs); res = ast_playstream(fs); if (res) { fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset); if (res >= 0) return RESULT_SHOWUSAGE; else return RESULT_FAILURE; } res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl); /* this is to check for if ast_waitstream closed the stream, we probably are at * the end of the stream, return that amount, else check for the amount */ sample_offset = (chan->stream) ? ast_tellstream(fs) : max_length; ast_stopstream(chan); if (res == 1) { /* Stop this command, don't print a result line, as there is a new command */ return RESULT_SUCCESS; } fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } /* get option - really similar to the handle_streamfile, but with a timeout */ static int handle_getoption(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; struct ast_filestream *fs; long sample_offset = 0; long max_length; int timeout = 0; char *edigits = NULL; if ( argc < 4 || argc > 5 ) return RESULT_SHOWUSAGE; if ( argv[3] ) edigits = argv[3]; if ( argc == 5 ) timeout = atoi(argv[4]); else if (chan->pbx->dtimeout) { /* by default dtimeout is set to 5sec */ timeout = chan->pbx->dtimeout * 1000; /* in msec */ } fs = ast_openstream(chan, argv[2], chan->language); if (!fs){ fdprintf(agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset); ast_log(LOG_WARNING, "Unable to open %s\n", argv[2]); return RESULT_SUCCESS; } if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Playing '%s' (escape_digits=%s) (timeout %d)\n", argv[2], edigits, timeout); ast_seekstream(fs, 0, SEEK_END); max_length = ast_tellstream(fs); ast_seekstream(fs, sample_offset, SEEK_SET); res = ast_applystream(chan, fs); res = ast_playstream(fs); if (res) { fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset); if (res >= 0) return RESULT_SHOWUSAGE; else return RESULT_FAILURE; } res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl); /* this is to check for if ast_waitstream closed the stream, we probably are at * the end of the stream, return that amount, else check for the amount */ sample_offset = (chan->stream)?ast_tellstream(fs):max_length; ast_stopstream(chan); if (res == 1) { /* Stop this command, don't print a result line, as there is a new command */ return RESULT_SUCCESS; } /* If the user didnt press a key, wait for digitTimeout*/ if (res == 0 ) { res = ast_waitfordigit_full(chan, timeout, agi->audio, agi->ctrl); /* Make sure the new result is in the escape digits of the GET OPTION */ if ( !strchr(edigits,res) ) res=0; } fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } /*--- handle_saynumber: Say number in various language syntaxes ---*/ /* Need to add option for gender here as well. Coders wanted */ /* While waiting, we're sending a (char *) NULL. */ static int handle_saynumber(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; int num; if (argc != 4) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%d", &num) != 1) return RESULT_SHOWUSAGE; res = ast_say_number_full(chan, num, argv[3], chan->language, (char *) NULL, agi->audio, agi->ctrl); if (res == 1) return RESULT_SUCCESS; fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_saydigits(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; int num; if (argc != 4) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%d", &num) != 1) return RESULT_SHOWUSAGE; res = ast_say_digit_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl); if (res == 1) /* New command */ return RESULT_SUCCESS; fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_sayalpha(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; if (argc != 4) return RESULT_SHOWUSAGE; res = ast_say_character_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl); if (res == 1) /* New command */ return RESULT_SUCCESS; fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_saydate(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; int num; if (argc != 4) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%d", &num) != 1) return RESULT_SHOWUSAGE; res = ast_say_date(chan, num, argv[3], chan->language); if (res == 1) return RESULT_SUCCESS; fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_saytime(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; int num; if (argc != 4) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%d", &num) != 1) return RESULT_SHOWUSAGE; res = ast_say_time(chan, num, argv[3], chan->language); if (res == 1) return RESULT_SUCCESS; fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_saydatetime(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res=0; long unixtime; char *format, *zone=NULL; if (argc < 4) return RESULT_SHOWUSAGE; if (argc > 4) { format = argv[4]; } else { if (!strcasecmp(chan->language, "de")) { format = "A dBY HMS"; } else { format = "ABdY 'digits/at' IMp"; } } if (argc > 5 && !ast_strlen_zero(argv[5])) zone = argv[5]; if (sscanf(argv[2], "%ld", &unixtime) != 1) return RESULT_SHOWUSAGE; res = ast_say_date_with_format(chan, (time_t) unixtime, argv[3], chan->language, format, zone); if (res == 1) return RESULT_SUCCESS; fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_sayphonetic(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; if (argc != 4) return RESULT_SHOWUSAGE; res = ast_say_phonetic_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl); if (res == 1) /* New command */ return RESULT_SUCCESS; fdprintf(agi->fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_getdata(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int res; char data[1024]; int max; int timeout; if (argc < 3) return RESULT_SHOWUSAGE; if (argc >= 4) timeout = atoi(argv[3]); else timeout = 0; if (argc >= 5) max = atoi(argv[4]); else max = 1024; res = ast_app_getdata_full(chan, argv[2], data, max, timeout, agi->audio, agi->ctrl); if (res == 2) /* New command */ return RESULT_SUCCESS; else if (res == 1) fdprintf(agi->fd, "200 result=%s (timeout)\n", data); else if (res < 0 ) fdprintf(agi->fd, "200 result=-1\n"); else fdprintf(agi->fd, "200 result=%s\n", data); return RESULT_SUCCESS; } static int handle_setcontext(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { if (argc != 3) return RESULT_SHOWUSAGE; ast_copy_string(chan->context, argv[2], sizeof(chan->context)); fdprintf(agi->fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_setextension(struct ast_channel *chan, AGI *agi, int argc, char **argv) { if (argc != 3) return RESULT_SHOWUSAGE; ast_copy_string(chan->exten, argv[2], sizeof(chan->exten)); fdprintf(agi->fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_setpriority(struct ast_channel *chan, AGI *agi, int argc, char **argv) { int pri; if (argc != 3) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%d", &pri) != 1) { if ((pri = ast_findlabel_extension(chan, chan->context, chan->exten, argv[2], chan->cid.cid_num)) < 1) return RESULT_SHOWUSAGE; } ast_explicit_goto(chan, NULL, NULL, pri); fdprintf(agi->fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { struct ast_filestream *fs; struct ast_frame *f; struct timeval start; long sample_offset = 0; int res = 0; int ms; struct ast_dsp *sildet=NULL; /* silence detector dsp */ int totalsilence = 0; int dspsilence = 0; int silence = 0; /* amount of silence to allow */ int gotsilence = 0; /* did we timeout for silence? */ char *silencestr=NULL; int rfmt=0; /* XXX EAGI FIXME XXX */ if (argc < 6) return RESULT_SHOWUSAGE; if (sscanf(argv[5], "%d", &ms) != 1) return RESULT_SHOWUSAGE; if (argc > 6) silencestr = strchr(argv[6],'s'); if ((argc > 7) && (!silencestr)) silencestr = strchr(argv[7],'s'); if ((argc > 8) && (!silencestr)) silencestr = strchr(argv[8],'s'); if (silencestr) { if (strlen(silencestr) > 2) { if ((silencestr[0] == 's') && (silencestr[1] == '=')) { silencestr++; silencestr++; if (silencestr) silence = atoi(silencestr); if (silence > 0) silence *= 1000; } } } if (silence > 0) { rfmt = chan->readformat; res = ast_set_read_format(chan, AST_FORMAT_SLINEAR); if (res < 0) { ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n"); return -1; } sildet = ast_dsp_new(); if (!sildet) { ast_log(LOG_WARNING, "Unable to create silence detector :(\n"); return -1; } ast_dsp_set_threshold(sildet, 256); } /* backward compatibility, if no offset given, arg[6] would have been * caught below and taken to be a beep, else if it is a digit then it is a * offset */ if ((argc >6) && (sscanf(argv[6], "%ld", &sample_offset) != 1) && (!strchr(argv[6], '='))) res = ast_streamfile(chan, "beep", chan->language); if ((argc > 7) && (!strchr(argv[7], '='))) res = ast_streamfile(chan, "beep", chan->language); if (!res) res = ast_waitstream(chan, argv[4]); if (res) { fdprintf(agi->fd, "200 result=%d (randomerror) endpos=%ld\n", res, sample_offset); } else { fs = ast_writefile(argv[2], argv[3], NULL, O_CREAT | O_WRONLY | (sample_offset ? O_APPEND : 0), 0, 0644); if (!fs) { res = -1; fdprintf(agi->fd, "200 result=%d (writefile)\n", res); if (sildet) ast_dsp_free(sildet); return RESULT_FAILURE; } /* Request a video update */ ast_indicate(chan, AST_CONTROL_VIDUPDATE); chan->stream = fs; ast_applystream(chan,fs); /* really should have checks */ ast_seekstream(fs, sample_offset, SEEK_SET); ast_truncstream(fs); start = ast_tvnow(); while ((ms < 0) || ast_tvdiff_ms(ast_tvnow(), start) < ms) { res = ast_waitfor(chan, -1); if (res < 0) { ast_closestream(fs); fdprintf(agi->fd, "200 result=%d (waitfor) endpos=%ld\n", res,sample_offset); if (sildet) ast_dsp_free(sildet); return RESULT_FAILURE; } f = ast_read(chan); if (!f) { fdprintf(agi->fd, "200 result=%d (hangup) endpos=%ld\n", 0, sample_offset); ast_closestream(fs); if (sildet) ast_dsp_free(sildet); return RESULT_FAILURE; } switch(f->frametype) { case AST_FRAME_DTMF: if (strchr(argv[4], f->subclass)) { /* This is an interrupting chracter, so rewind to chop off any small amount of DTMF that may have been recorded */ ast_stream_rewind(fs, 200); ast_truncstream(fs); sample_offset = ast_tellstream(fs); fdprintf(agi->fd, "200 result=%d (dtmf) endpos=%ld\n", f->subclass, sample_offset); ast_closestream(fs); ast_frfree(f); if (sildet) ast_dsp_free(sildet); return RESULT_SUCCESS; } break; case AST_FRAME_VOICE: ast_writestream(fs, f); /* this is a safe place to check progress since we know that fs * is valid after a write, and it will then have our current * location */ sample_offset = ast_tellstream(fs); if (silence > 0) { dspsilence = 0; ast_dsp_silence(sildet, f, &dspsilence); if (dspsilence) { totalsilence = dspsilence; } else { totalsilence = 0; } if (totalsilence > silence) { /* Ended happily with silence */ gotsilence = 1; break; } } break; case AST_FRAME_VIDEO: ast_writestream(fs, f); break; } ast_frfree(f); if (gotsilence) break; } if (gotsilence) { ast_stream_rewind(fs, silence-1000); ast_truncstream(fs); sample_offset = ast_tellstream(fs); } fdprintf(agi->fd, "200 result=%d (timeout) endpos=%ld\n", res, sample_offset); ast_closestream(fs); } if (silence > 0) { res = ast_set_read_format(chan, rfmt); if (res) ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name); ast_dsp_free(sildet); } return RESULT_SUCCESS; } static int handle_autohangup(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { int timeout; if (argc != 3) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%d", &timeout) != 1) return RESULT_SHOWUSAGE; if (timeout < 0) timeout = 0; if (timeout) chan->whentohangup = time(NULL) + timeout; else chan->whentohangup = 0; fdprintf(agi->fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_hangup(struct ast_channel *chan, AGI *agi, int argc, char **argv) { struct ast_channel *c; if (argc == 1) { /* no argument: hangup the current channel */ ast_softhangup(chan,AST_SOFTHANGUP_EXPLICIT); fdprintf(agi->fd, "200 result=1\n"); return RESULT_SUCCESS; } else if (argc == 2) { /* one argument: look for info on the specified channel */ c = ast_get_channel_by_name_locked(argv[1]); if (c) { /* we have a matching channel */ ast_softhangup(c,AST_SOFTHANGUP_EXPLICIT); fdprintf(agi->fd, "200 result=1\n"); ast_mutex_unlock(&c->lock); return RESULT_SUCCESS; } /* if we get this far no channel name matched the argument given */ fdprintf(agi->fd, "200 result=-1\n"); return RESULT_SUCCESS; } else { return RESULT_SHOWUSAGE; } } static int handle_exec(struct ast_channel *chan, AGI *agi, int argc, char **argv) { int res; struct ast_app *app; if (argc < 2) return RESULT_SHOWUSAGE; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "AGI Script Executing Application: (%s) Options: (%s)\n", argv[1], argv[2]); app = pbx_findapp(argv[1]); if (app) { res = pbx_exec(chan, app, argv[2], 1); } else { ast_log(LOG_WARNING, "Could not find application (%s)\n", argv[1]); res = -2; } fdprintf(agi->fd, "200 result=%d\n", res); return res; } static int handle_setcallerid(struct ast_channel *chan, AGI *agi, int argc, char **argv) { char tmp[256]=""; char *l = NULL, *n = NULL; if (argv[2]) { ast_copy_string(tmp, argv[2], sizeof(tmp)); ast_callerid_parse(tmp, &n, &l); if (l) ast_shrink_phone_number(l); else l = ""; if (!n) n = ""; ast_set_callerid(chan, l, n, NULL); } fdprintf(agi->fd, "200 result=1\n"); return RESULT_SUCCESS; } static int handle_channelstatus(struct ast_channel *chan, AGI *agi, int argc, char **argv) { struct ast_channel *c; if (argc == 2) { /* no argument: supply info on the current channel */ fdprintf(agi->fd, "200 result=%d\n", chan->_state); return RESULT_SUCCESS; } else if (argc == 3) { /* one argument: look for info on the specified channel */ c = ast_get_channel_by_name_locked(argv[2]); if (c) { fdprintf(agi->fd, "200 result=%d\n", c->_state); ast_mutex_unlock(&c->lock); return RESULT_SUCCESS; } /* if we get this far no channel name matched the argument given */ fdprintf(agi->fd, "200 result=-1\n"); return RESULT_SUCCESS; } else { return RESULT_SHOWUSAGE; } } static int handle_setvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv) { if (argv[3]) pbx_builtin_setvar_helper(chan, argv[2], argv[3]); fdprintf(agi->fd, "200 result=1\n"); return RESULT_SUCCESS; } static int handle_getvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv) { char *ret; char tempstr[1024]; if (argc != 3) return RESULT_SHOWUSAGE; /* check if we want to execute an ast_custom_function */ if (!ast_strlen_zero(argv[2]) && (argv[2][strlen(argv[2]) - 1] == ')')) { ret = ast_func_read(chan, argv[2], tempstr, sizeof(tempstr)); } else { pbx_retrieve_variable(chan, argv[2], &ret, tempstr, sizeof(tempstr), NULL); } if (ret) fdprintf(agi->fd, "200 result=1 (%s)\n", ret); else fdprintf(agi->fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_getvariablefull(struct ast_channel *chan, AGI *agi, int argc, char **argv) { char tmp[4096] = ""; struct ast_channel *chan2=NULL; if ((argc != 4) && (argc != 5)) return RESULT_SHOWUSAGE; if (argc == 5) { chan2 = ast_get_channel_by_name_locked(argv[4]); } else { chan2 = chan; } if (chan) { /* XXX isn't this chan2 ? */ pbx_substitute_variables_helper(chan2, argv[3], tmp, sizeof(tmp) - 1); fdprintf(agi->fd, "200 result=1 (%s)\n", tmp); } else { fdprintf(agi->fd, "200 result=0\n"); } if (chan2 && (chan2 != chan)) ast_mutex_unlock(&chan2->lock); return RESULT_SUCCESS; } static int handle_verbose(struct ast_channel *chan, AGI *agi, int argc, char **argv) { int level = 0; char *prefix; if (argc < 2) return RESULT_SHOWUSAGE; if (argv[2]) sscanf(argv[2], "%d", &level); switch (level) { case 4: prefix = VERBOSE_PREFIX_4; break; case 3: prefix = VERBOSE_PREFIX_3; break; case 2: prefix = VERBOSE_PREFIX_2; break; case 1: default: prefix = VERBOSE_PREFIX_1; break; } if (level <= option_verbose) ast_verbose("%s %s: %s\n", prefix, chan->data, argv[1]); fdprintf(agi->fd, "200 result=1\n"); return RESULT_SUCCESS; } static int handle_dbget(struct ast_channel *chan, AGI *agi, int argc, char **argv) { int res; char tmp[256]; if (argc != 4) return RESULT_SHOWUSAGE; res = ast_db_get(argv[2], argv[3], tmp, sizeof(tmp)); if (res) fdprintf(agi->fd, "200 result=0\n"); else fdprintf(agi->fd, "200 result=1 (%s)\n", tmp); return RESULT_SUCCESS; } static int handle_dbput(struct ast_channel *chan, AGI *agi, int argc, char **argv) { int res; if (argc != 5) return RESULT_SHOWUSAGE; res = ast_db_put(argv[2], argv[3], argv[4]); if (res) fdprintf(agi->fd, "200 result=0\n"); else fdprintf(agi->fd, "200 result=1\n"); return RESULT_SUCCESS; } static int handle_dbdel(struct ast_channel *chan, AGI *agi, int argc, char **argv) { int res; if (argc != 4) return RESULT_SHOWUSAGE; res = ast_db_del(argv[2], argv[3]); if (res) fdprintf(agi->fd, "200 result=0\n"); else fdprintf(agi->fd, "200 result=1\n"); return RESULT_SUCCESS; } static int handle_dbdeltree(struct ast_channel *chan, AGI *agi, int argc, char **argv) { int res; if ((argc < 3) || (argc > 4)) return RESULT_SHOWUSAGE; if (argc == 4) res = ast_db_deltree(argv[2], argv[3]); else res = ast_db_deltree(argv[2], NULL); if (res) fdprintf(agi->fd, "200 result=0\n"); else fdprintf(agi->fd, "200 result=1\n"); return RESULT_SUCCESS; } static char debug_usage[] = "Usage: agi debug\n" " Enables dumping of AGI transactions for debugging purposes\n"; static char no_debug_usage[] = "Usage: agi no debug\n" " Disables dumping of AGI transactions for debugging purposes\n"; static int agi_do_debug(int fd, int argc, char *argv[]) { if (argc != 2) return RESULT_SHOWUSAGE; agidebug = 1; ast_cli(fd, "AGI Debugging Enabled\n"); return RESULT_SUCCESS; } static int agi_no_debug(int fd, int argc, char *argv[]) { if (argc != 3) return RESULT_SHOWUSAGE; agidebug = 0; ast_cli(fd, "AGI Debugging Disabled\n"); return RESULT_SUCCESS; } static struct ast_cli_entry cli_debug = { { "agi", "debug", NULL }, agi_do_debug, "Enable AGI debugging", debug_usage }; static struct ast_cli_entry cli_no_debug = { { "agi", "no", "debug", NULL }, agi_no_debug, "Disable AGI debugging", no_debug_usage }; static int handle_noop(struct ast_channel *chan, AGI *agi, int arg, char *argv[]) { fdprintf(agi->fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_setmusic(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) { if (!strncasecmp(argv[2],"on",2)) { if (argc > 3) ast_moh_start(chan, argv[3]); else ast_moh_start(chan, NULL); } if (!strncasecmp(argv[2],"off",3)) { ast_moh_stop(chan); } fdprintf(agi->fd, "200 result=0\n"); return RESULT_SUCCESS; } static char usage_setmusic[] = " Usage: SET MUSIC ON \n" " Enables/Disables the music on hold generator. If is\n" " not specified, then the default music on hold class will be used.\n" " Always returns 0.\n"; static char usage_dbput[] = " Usage: DATABASE PUT \n" " Adds or updates an entry in the Asterisk database for a\n" " given family, key, and value.\n" " Returns 1 if successful, 0 otherwise.\n"; static char usage_dbget[] = " Usage: DATABASE GET \n" " Retrieves an entry in the Asterisk database for a\n" " given family and key.\n" " Returns 0 if is not set. Returns 1 if \n" " is set and returns the variable in parentheses.\n" " Example return code: 200 result=1 (testvariable)\n"; static char usage_dbdel[] = " Usage: DATABASE DEL \n" " Deletes an entry in the Asterisk database for a\n" " given family and key.\n" " Returns 1 if successful, 0 otherwise.\n"; static char usage_dbdeltree[] = " Usage: DATABASE DELTREE [keytree]\n" " Deletes a family or specific keytree within a family\n" " in the Asterisk database.\n" " Returns 1 if successful, 0 otherwise.\n"; static char usage_verbose[] = " Usage: VERBOSE \n" " Sends to the console via verbose message system.\n" " is the the verbose level (1-4)\n" " Always returns 1.\n"; static char usage_getvariable[] = " Usage: GET VARIABLE \n" " Returns 0 if is not set. Returns 1 if \n" " is set and returns the variable in parentheses.\n" " example return code: 200 result=1 (testvariable)\n"; static char usage_getvariablefull[] = " Usage: GET FULL VARIABLE []\n" " Returns 0 if is not set or channel does not exist. Returns 1\n" "if is set and returns the variable in parenthesis. Understands\n" "complex variable names and builtin variables, unlike GET VARIABLE.\n" " example return code: 200 result=1 (testvariable)\n"; static char usage_setvariable[] = " Usage: SET VARIABLE \n"; static char usage_channelstatus[] = " Usage: CHANNEL STATUS []\n" " Returns the status of the specified channel.\n" " If no channel name is given the returns the status of the\n" " current channel. Return values:\n" " 0 Channel is down and available\n" " 1 Channel is down, but reserved\n" " 2 Channel is off hook\n" " 3 Digits (or equivalent) have been dialed\n" " 4 Line is ringing\n" " 5 Remote end is ringing\n" " 6 Line is up\n" " 7 Line is busy\n"; static char usage_setcallerid[] = " Usage: SET CALLERID \n" " Changes the callerid of the current channel.\n"; static char usage_exec[] = " Usage: EXEC \n" " Executes with given .\n" " Returns whatever the application returns, or -2 on failure to find application\n"; static char usage_hangup[] = " Usage: HANGUP []\n" " Hangs up the specified channel.\n" " If no channel name is given, hangs up the current channel\n"; static char usage_answer[] = " Usage: ANSWER\n" " Answers channel if not already in answer state. Returns -1 on\n" " channel failure, or 0 if successful.\n"; static char usage_waitfordigit[] = " Usage: WAIT FOR DIGIT \n" " Waits up to 'timeout' milliseconds for channel to receive a DTMF digit.\n" " Returns -1 on channel failure, 0 if no digit is received in the timeout, or\n" " the numerical value of the ascii of the digit if one is received. Use -1\n" " for the timeout value if you desire the call to block indefinitely.\n"; static char usage_sendtext[] = " Usage: SEND TEXT \"\"\n" " Sends the given text on a channel. Most channels do not support the\n" " transmission of text. Returns 0 if text is sent, or if the channel does not\n" " support text transmission. Returns -1 only on error/hangup. Text\n" " consisting of greater than one word should be placed in quotes since the\n" " command only accepts a single argument.\n"; static char usage_recvchar[] = " Usage: RECEIVE CHAR \n" " Receives a character of text on a channel. Specify timeout to be the\n" " maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n" " do not support the reception of text. Returns the decimal value of the character\n" " if one is received, or 0 if the channel does not support text reception. Returns\n" " -1 only on error/hangup.\n"; static char usage_recvtext[] = " Usage: RECEIVE TEXT \n" " Receives a string of text on a channel. Specify timeout to be the\n" " maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n" " do not support the reception of text. Returns -1 for failure or 1 for success, and the string in parentheses.\n"; static char usage_tddmode[] = " Usage: TDD MODE \n" " Enable/Disable TDD transmission/reception on a channel. Returns 1 if\n" " successful, or 0 if channel is not TDD-capable.\n"; static char usage_sendimage[] = " Usage: SEND IMAGE \n" " Sends the given image on a channel. Most channels do not support the\n" " transmission of images. Returns 0 if image is sent, or if the channel does not\n" " support image transmission. Returns -1 only on error/hangup. Image names\n" " should not include extensions.\n"; static char usage_streamfile[] = " Usage: STREAM FILE [sample offset]\n" " Send the given file, allowing playback to be interrupted by the given\n" " digits, if any. Use double quotes for the digits if you wish none to be\n" " permitted. If sample offset is provided then the audio will seek to sample\n" " offset before play starts. Returns 0 if playback completes without a digit\n" " being pressed, or the ASCII numerical value of the digit if one was pressed,\n" " or -1 on error or if the channel was disconnected. Remember, the file\n" " extension must not be included in the filename.\n"; static char usage_controlstreamfile[] = " Usage: CONTROL STREAM FILE [skipms] [ffchar] [rewchr] [pausechr]\n" " Send the given file, allowing playback to be controled by the given\n" " digits, if any. Use double quotes for the digits if you wish none to be\n" " permitted. Returns 0 if playback completes without a digit\n" " being pressed, or the ASCII numerical value of the digit if one was pressed,\n" " or -1 on error or if the channel was disconnected. Remember, the file\n" " extension must not be included in the filename.\n\n" " Note: ffchar and rewchar default to * and # respectively.\n"; static char usage_getoption[] = " Usage: GET OPTION [timeout]\n" " Behaves similar to STREAM FILE but used with a timeout option.\n"; static char usage_saynumber[] = " Usage: SAY NUMBER \n" " Say a given number, returning early if any of the given DTMF digits\n" " are received on the channel. Returns 0 if playback completes without a digit\n" " being pressed, or the ASCII numerical value of the digit if one was pressed or\n" " -1 on error/hangup.\n"; static char usage_saydigits[] = " Usage: SAY DIGITS \n" " Say a given digit string, returning early if any of the given DTMF digits\n" " are received on the channel. Returns 0 if playback completes without a digit\n" " being pressed, or the ASCII numerical value of the digit if one was pressed or\n" " -1 on error/hangup.\n"; static char usage_sayalpha[] = " Usage: SAY ALPHA \n" " Say a given character string, returning early if any of the given DTMF digits\n" " are received on the channel. Returns 0 if playback completes without a digit\n" " being pressed, or the ASCII numerical value of the digit if one was pressed or\n" " -1 on error/hangup.\n"; static char usage_saydate[] = " Usage: SAY DATE \n" " Say a given date, returning early if any of the given DTMF digits are\n" " received on the channel. is number of seconds elapsed since 00:00:00\n" " on January 1, 1970, Coordinated Universal Time (UTC). Returns 0 if playback\n" " completes without a digit being pressed, or the ASCII numerical value of the\n" " digit if one was pressed or -1 on error/hangup.\n"; static char usage_saytime[] = " Usage: SAY TIME