/* * Asterisk -- A telephony toolkit for Linux. * * Asterisk Gateway Interface * * Copyright (C) 1999, Mark Spencer * * Mark Spencer * * This program is free software, distributed under the terms of * the GNU General Public License */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../asterisk.h" #include #define MAX_ARGS 128 /* Recycle some stuff from the CLI interface */ #define fdprintf ast_cli typedef struct agi_command { /* Null terminated list of the words of the command */ char *cmda[AST_MAX_CMD_LEN]; /* Handler for the command (fd for output, # of arguments, argument list). Returns RESULT_SHOWUSAGE for improper arguments */ int (*handler)(struct ast_channel *chan, int fd, int argc, char *argv[]); /* Summary of the command (< 60 characters) */ char *summary; /* Detailed usage information */ char *usage; } agi_command; static char *tdesc = "Asterisk Gateway Interface (AGI)"; static char *app = "AGI"; static char *synopsis = "Executes an AGI compliant application"; static char *descrip = " 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. Returns -1 on hangup or if application requested hangup, or\n" "0 on non-hangup exit.\n"; STANDARD_LOCAL_USER; LOCAL_USER_DECL; #define TONE_BLOCK_SIZE 200 static float loudness = 8192.0; unsigned char linear2ulaw(short sample); static void make_tone_block(unsigned char *data, float f1, int *x); static void make_tone_block(unsigned char *data, float f1, int *x) { int i; float val; for(i = 0; i < TONE_BLOCK_SIZE; i++) { val = loudness * sin((f1 * 2.0 * M_PI * (*x)++)/8000.0); data[i] = linear2ulaw((int)val); } /* wrap back around from 8000 */ if (*x >= 8000) *x = 0; return; } static int launch_script(char *script, char *args, int *fds, int *opid) { char tmp[256]; int pid; int toast[2]; int fromast[2]; int x; if (script[0] != '/') { snprintf(tmp, sizeof(tmp), "%s/%s", 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; } pid = fork(); if (pid < 0) { ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno)); return -1; } if (!pid) { /* Redirect stdin and out */ dup2(fromast[0], STDIN_FILENO); dup2(toast[1], STDOUT_FILENO); /* Close everything but stdin/out/error */ for (x=STDERR_FILENO + 1;x<1024;x++) close(x); /* Execute script */ execl(script, script, args, NULL); ast_log(LOG_WARNING, "Failed to execute '%s': %s\n", script, strerror(errno)); exit(1); } if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Launched AGI Script %s\n", script); fds[0] = toast[0]; fds[1] = fromast[1]; /* close what we're not using in the parent */ close(toast[1]); close(fromast[0]); *opid = pid; return 0; } static void setup_env(struct ast_channel *chan, char *request, int fd) { /* 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); /* ANI/DNIS */ fdprintf(fd, "agi_callerid: %s\n", chan->callerid ? chan->callerid : ""); fdprintf(fd, "agi_dnid: %s\n", chan->dnid ? chan->dnid : ""); /* 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); /* End with empty return */ fdprintf(fd, "\n"); } static int handle_answer(struct ast_channel *chan, int fd, int argc, char *argv[]) { int res; res = 0; if (chan->_state != AST_STATE_UP) { /* Answer the chan */ res = ast_answer(chan); } fdprintf(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_waitfordigit(struct ast_channel *chan, int fd, int argc, char *argv[]) { int res; int to; if (argc != 4) return RESULT_SHOWUSAGE; if (sscanf(argv[3], "%i", &to) != 1) return RESULT_SHOWUSAGE; res = ast_waitfordigit(chan, to); fdprintf(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_sendtext(struct ast_channel *chan, int fd, 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(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_recvchar(struct ast_channel *chan, int fd, int argc, char *argv[]) { int res; if (argc != 3) return RESULT_SHOWUSAGE; res = ast_recvchar(chan,atoi(argv[2])); if (res == 0) { fdprintf(fd, "200 result=%d (timeout)\n", res); return RESULT_SUCCESS; } if (res > 0) { fdprintf(fd, "200 result=%d\n", res); return RESULT_SUCCESS; } else { fdprintf(fd, "200 result=%d (hangup)\n", res); return RESULT_FAILURE; } } static int handle_tddmode(struct ast_channel *chan, int fd, 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); fdprintf(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_sendimage(struct ast_channel *chan, int fd, 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(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_streamfile(struct ast_channel *chan, int fd, int argc, char *argv[]) { int res; if (argc != 4) return RESULT_SHOWUSAGE; res = ast_streamfile(chan, argv[2],chan->language); if (res) { fdprintf(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SHOWUSAGE; else return RESULT_FAILURE; } res = ast_waitstream(chan, argv[3]); ast_stopstream(chan); fdprintf(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_saynumber(struct ast_channel *chan, int fd, int argc, char *argv[]) { int res; int num; if (argc != 4) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%i", &num) != 1) return RESULT_SHOWUSAGE; res = ast_say_number(chan, num, argv[3], chan->language); fdprintf(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_saydigits(struct ast_channel *chan, int fd, int argc, char *argv[]) { int res; int num; if (argc != 4) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%i", &num) != 1) return RESULT_SHOWUSAGE; res = ast_say_digit_str(chan, argv[2], argv[3], chan->language); fdprintf(fd, "200 result=%d\n", res); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } int ast_app_getdata(struct ast_channel *c, char *prompt, char *s, int maxlen, int timeout); static int handle_getdata(struct ast_channel *chan, int fd, int argc, char *argv[]) { int res; char data[50]; 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 = 50; res = ast_app_getdata(chan, argv[2], data, max, timeout); if (res == 1) fdprintf(fd, "200 result=%s (timeout)\n", data); else fdprintf(fd, "200 result=%s\n", data); if (res >= 0) return RESULT_SUCCESS; else return RESULT_FAILURE; } static int handle_setcontext(struct ast_channel *chan, int fd, int argc, char *argv[]) { if (argc != 3) return RESULT_SHOWUSAGE; strncpy(chan->context, argv[2], sizeof(chan->context)-1); fdprintf(fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_setextension(struct ast_channel *chan, int fd, int argc, char **argv) { if (argc != 3) return RESULT_SHOWUSAGE; strncpy(chan->exten, argv[2], sizeof(chan->exten)-1); fdprintf(fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_setpriority(struct ast_channel *chan, int fd, int argc, char **argv) { int pri; if (argc != 3) return RESULT_SHOWUSAGE; if (sscanf(argv[2], "%i", &pri) != 1) return RESULT_SHOWUSAGE; chan->priority = pri - 1; fdprintf(fd, "200 result=0\n"); return RESULT_SUCCESS; } static int ms_diff(struct timeval *tv1, struct timeval *tv2) { int ms; ms = (tv1->tv_sec - tv2->tv_sec) * 1000; ms += (tv1->tv_usec - tv2->tv_usec) / 1000; return(ms); } static int handle_recordfile(struct ast_channel *chan, int fd, int argc, char *argv[]) { struct ast_filestream *fs; struct ast_frame *f; struct timeval tv, start; int res = 0; int ms; if (argc < 6) return RESULT_SHOWUSAGE; if (sscanf(argv[5], "%i", &ms) != 1) return RESULT_SHOWUSAGE; if (argc > 6) res = ast_streamfile(chan, "beep", chan->language); if (!res) res = ast_waitstream(chan, argv[4]); if (!res) { fs = ast_writefile(argv[2], argv[3], NULL, O_CREAT | O_TRUNC | O_WRONLY, 0, 0644); if (!fs) { res = -1; fdprintf(fd, "200 result=%d (writefile)\n", res); return RESULT_FAILURE; } gettimeofday(&start, NULL); gettimeofday(&tv, NULL); while ((ms < 0) || (((tv.tv_sec - start.tv_sec) * 1000 + (tv.tv_usec - start.tv_usec)/1000) < ms)) { res = ast_waitfor(chan, -1); if (res < 0) { ast_closestream(fs); fdprintf(fd, "200 result=%d (waitfor)\n", res); return RESULT_FAILURE; } f = ast_read(chan); if (!f) { fdprintf(fd, "200 result=%d (hangup)\n", 0); ast_closestream(fs); return RESULT_FAILURE; } switch(f->frametype) { case AST_FRAME_DTMF: if (strchr(argv[4], f->subclass)) { /* This is an interrupting chracter */ fdprintf(fd, "200 result=%d (dtmf)\n", f->subclass); ast_closestream(fs); ast_frfree(f); return RESULT_SUCCESS; } break; case AST_FRAME_VOICE: ast_writestream(fs, f); break; } ast_frfree(f); } gettimeofday(&tv, NULL); fdprintf(fd, "200 result=%d (timeout)\n", res); ast_closestream(fs); } else fdprintf(fd, "200 result=%d (randomerror)\n", res); return RESULT_SUCCESS; } static int handle_autohangup(struct ast_channel *chan, int fd, 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(fd, "200 result=0\n"); return RESULT_SUCCESS; } static int handle_hangup(struct ast_channel *chan, int fd, int argc, char **argv) { ast_softhangup(chan,AST_SOFTHANGUP_EXPLICIT); fdprintf(fd, "200 result=1\n"); return RESULT_SUCCESS; } static int handle_exec(struct ast_channel *chan, int fd, 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(fd, "200 result=%d\n", res); return res; } static int handle_setcallerid(struct ast_channel *chan, int fd, int argc, char **argv) { if (argv[2]) ast_set_callerid(chan, argv[2]); /* strncpy(chan->callerid, argv[2], sizeof(chan->callerid)-1); */ fdprintf(fd, "200 result=1\n"); return RESULT_SUCCESS; } static int handle_channelstatus(struct ast_channel *chan, int fd, int argc, char **argv) { fdprintf(fd, "200 result=%d\n", chan->_state); return RESULT_SUCCESS; } static char usage_channelstatus[] = " Usage: CHANNEL STATUS\n" " Returns the status of the connected 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 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_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 \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. Returns 0 if playback completes without a digit being pressed, or\n" " the ASCII numerical value of the digit if one was pressed, or -1 on error or\n" " if the channel was disconnected. Remember, the file extension must not be\n" " included in the filename.\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_getdata[] = " Usage: GET DATA [timeout] [max digits]\n" " Stream the given file, and recieve DTMF data. Returns the digits recieved\n" "from the channel at the other end.\n"; static char usage_setcontext[] = " Usage: SET CONTEXT \n" " Sets the context for continuation upon exiting the application.\n"; static char usage_setextension[] = " Usage: SET EXTENSION \n" " Changes the extension for continuation upon exiting the application.\n"; static char usage_setpriority[] = " Usage: SET PRIORITY \n" " Changes the priority for continuation upon exiting the application.\n"; static char usage_recordfile[] = " Usage: RECORD FILE [BEEP]\n" " Record to a file until a given dtmf digit in the sequence is received\n" " Returns -1 on hangup or error. The format will specify what kind of file\n" " will be recorded. The timeout is the maximum record time in milliseconds, or\n" " -1 for no timeout\n"; static char usage_autohangup[] = " Usage: SET AUTOHANGUP