Version 0.1.9 from FTP
authorMark Spencer <markster@digium.com>
Fri, 28 Sep 2001 13:19:43 +0000 (13:19 +0000)
committerMark Spencer <markster@digium.com>
Fri, 28 Sep 2001 13:19:43 +0000 (13:19 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@360 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_agi.c [new file with mode: 0755]

diff --git a/apps/app_agi.c b/apps/app_agi.c
new file mode 100755 (executable)
index 0000000..672a04d
--- /dev/null
@@ -0,0 +1,809 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Asterisk Gateway Interface
+ * 
+ * Copyright (C) 1999, Mark Spencer
+ *
+ * Mark Spencer <markster@linux-support.net>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/signal.h>
+#include <sys/time.h>
+#include <asterisk/cli.h>
+#include <asterisk/logger.h>
+#include <asterisk/options.h>
+#include <asterisk/image.h>
+#include <asterisk/say.h>
+#include "../asterisk.h"
+
+#include <pthread.h>
+
+#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_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;
+       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_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 (!chan->softhangup)
+               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]);
+       
+       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, 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));
+       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));
+       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,wf;
+       struct timeval tv, start, lastout, now, notime = { 0,0 } ;
+       fd_set readfds;
+       unsigned char tone_block[TONE_BLOCK_SIZE];
+       int res = -1;
+       int ms,i,j;
+
+       if (argc < 6)
+               return RESULT_SHOWUSAGE;
+       if (sscanf(argv[5], "%i", &ms) != 1)
+               return RESULT_SHOWUSAGE;
+
+       if (argc > 6) { /* if to beep */
+               i = 0;
+               lastout.tv_sec = lastout.tv_usec = 0;
+               for(j = 0; j < 13; j++)
+                  {
+                       do gettimeofday(&now,NULL);
+                       while (lastout.tv_sec && 
+                               (ms_diff(&now,&lastout) < 25));
+                       lastout.tv_sec = now.tv_sec;
+                       lastout.tv_usec = now.tv_usec;
+                       wf.frametype = AST_FRAME_VOICE;
+                       wf.subclass = AST_FORMAT_ULAW;
+                       wf.offset = AST_FRIENDLY_OFFSET;
+                       wf.mallocd = 0;
+                       wf.data = tone_block;
+                       wf.datalen = TONE_BLOCK_SIZE;                           
+                       /* make this tone block */
+                       make_tone_block(tone_block,1000.0,&i);
+                       wf.timelen = wf.datalen / 8;
+                       if (ast_write(chan, &wf)) {
+                               fdprintf(fd, "200 result=%d (hangup)\n", 0);
+                               return RESULT_FAILURE;
+                       }
+                       FD_ZERO(&readfds);
+                       FD_SET(chan->fds[0],&readfds);
+                         /* if no read avail, do send again */
+                       if (select(chan->fds[0] + 1,&readfds,NULL,
+                               NULL,&notime) < 1) continue;
+                       f = ast_read(chan);
+                       if (!f) {
+                               fdprintf(fd, "200 result=%d (hangup)\n", 0);
+                               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_frfree(f);
+                                       return RESULT_SUCCESS;
+                               }
+                               break;
+                       case AST_FRAME_VOICE:
+                               break;  /* throw it away */
+                       }
+                       ast_frfree(f);
+                  }
+                 /* suck in 5 voice frames to make up for echo of beep, etc */
+               for(i = 0; i < 5; i++) {
+                       f = ast_read(chan);
+                       if (!f) {
+                               fdprintf(fd, "200 result=%d (hangup)\n", 0);
+                               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_frfree(f);
+                                       return RESULT_SUCCESS;
+                               }
+                               break;
+                       case AST_FRAME_VOICE:
+                               break;  /* throw it away */
+                       }
+                       ast_frfree(f);
+               }
+
+       }
+
+       fs = ast_writefile(argv[2], argv[3], NULL, O_CREAT | O_TRUNC | O_WRONLY, 0, 0644);
+       if (!fs) {
+               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", 0);
+       ast_closestream(fs);
+       return RESULT_SUCCESS;
+}
+
+static char usage_waitfordigit[] = 
+" Usage: WAIT FOR DIGIT <timeout>\n"
+"        Waits up to 'timeout' seconds 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 \"<text to send>\"\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_sendimage[] =
+" Usage: SEND IMAGE <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 <filename> <escape digits>\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 <number> <escape digits>\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_getdata[] =
+" Usage: GET DATA <file to be streamed> [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 <desired context>\n"
+"       Sets the context for continuation upon exiting the application.\n";
+
+static char usage_setextension[] =
+" Usage: SET EXTENSION <new extension>\n"
+"       Changes the extension for continuation upon exiting the application.\n";
+
+static char usage_setpriority[] =
+" Usage: SET PRIORITY <num>\n"
+"       Changes the priority for continuation upon exiting the application.\n";
+
+static char usage_recordfile[] =
+" Usage: RECORD FILE <filename> <format> <escape digits> <timeout> [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";
+
+agi_command commands[] = {
+       { { "wait", "for", "digit", NULL }, handle_waitfordigit, "Waits for a digit to be pressed", usage_waitfordigit },
+       { { "send", "text", NULL }, handle_sendtext, "Sends text to channels supporting it", usage_sendtext },
+       { { "stream", "file", NULL }, handle_streamfile, "Sends audio file on channel", usage_streamfile },
+       { { "send", "image", NULL }, handle_sendimage, "Sends images to channels supporting it", usage_sendimage },
+       { { "say", "number", NULL }, handle_saynumber, "Says a given number", usage_saynumber },
+       { { "get", "data", NULL }, handle_getdata, "Gets data on a channel", usage_getdata },
+       { { "set", "context", NULL }, handle_setcontext, "Sets channel context", usage_setcontext },
+       { { "set", "extension", NULL }, handle_setextension, "Changes channel extension", usage_setextension },
+       { { "set", "priority", NULL }, handle_setpriority, "Prioritizes the channel", usage_setpriority },
+       { { "record", "file", NULL }, handle_recordfile, "Records to a given file", usage_recordfile }
+};
+
+static agi_command *find_command(char *cmds[])
+{
+       int x;
+       int y;
+       int match;
+       for (x=0;x < sizeof(commands) / sizeof(commands[0]);x++) {
+               /* start optimistic */
+               match = 1;
+               for (y=0;match && cmds[y]; y++) {
+                       /* If there are no more words in the command (and we're looking for
+                          an exact match) or there is a difference between the two words,
+                          then this is not a match */
+                       if (!commands[x].cmda[y])
+                               break;
+                       if (strcasecmp(commands[x].cmda[y], cmds[y]))
+                               match = 0;
+               }
+               /* If more words are needed to complete the command then this is not
+                  a candidate (unless we're looking for a really inexact answer  */
+               if (commands[x].cmda[y])
+                       match = 0;
+               if (match)
+                       return &commands[x];
+       }
+       return NULL;
+}
+
+
+static int parse_args(char *s, int *max, char *argv[])
+{
+       int x=0;
+       int quoted=0;
+       int escaped=0;
+       int whitespace=1;
+       char *cur;
+
+       cur = s;
+       while(*s) {
+               switch(*s) {
+               case '"':
+                       /* If it's escaped, put a literal quote */
+                       if (escaped) 
+                               goto normal;
+                       else 
+                               quoted = !quoted;
+                       if (quoted && whitespace) {
+                               /* If we're starting a quote, coming off white space start a new word, too */
+                               argv[x++] = cur;
+                               whitespace=0;
+                       }
+                       escaped = 0;
+               break;
+               case ' ':
+               case '\t':
+                       if (!quoted && !escaped) {
+                               /* If we're not quoted, mark this as whitespace, and
+                                  end the previous argument */
+                               whitespace = 1;
+                               *(cur++) = '\0';
+                       } else
+                               /* Otherwise, just treat it as anything else */ 
+                               goto normal;
+                       break;
+               case '\\':
+                       /* If we're escaped, print a literal, otherwise enable escaping */
+                       if (escaped) {
+                               goto normal;
+                       } else {
+                               escaped=1;
+                       }
+                       break;
+               default:
+normal:
+                       if (whitespace) {
+                               if (x >= MAX_ARGS -1) {
+                                       ast_log(LOG_WARNING, "Too many arguments, truncating\n");
+                                       break;
+                               }
+                               /* Coming off of whitespace, start the next argument */
+                               argv[x++] = cur;
+                               whitespace=0;
+                       }
+                       *(cur++) = *s;
+                       escaped=0;
+               }
+               s++;
+       }
+       /* Null terminate */
+       *(cur++) = '\0';
+       argv[x] = NULL;
+       *max = x;
+       return 0;
+}
+
+static int agi_handle_command(struct ast_channel *chan, int fd, char *buf)
+{
+       char *argv[MAX_ARGS];
+       int argc = 0;
+       int res;
+       agi_command *c;
+       argc = MAX_ARGS;
+       parse_args(buf, &argc, argv);
+#if    0
+       { int x;
+       for (x=0;x<argc;x++) 
+               fprintf(stderr, "Got Arg%d: %s\n", x, argv[x]); }
+#endif
+       c = find_command(argv);
+       if (c) {
+               res = c->handler(chan, fd, argc, argv);
+               switch(res) {
+               case RESULT_SHOWUSAGE:
+                       fdprintf(fd, "520-Invalid command syntax.  Proper usage follows:\n");
+                       fdprintf(fd, c->usage);
+                       fdprintf(fd, "520 End of proper usage.\n");
+                       break;
+               case RESULT_FAILURE:
+                       /* They've already given the failure.  We've been hung up on so handle this
+                          appropriately */
+                       return -1;
+               }
+       } else {
+               fdprintf(fd, "510 Invalid or unknown command\n");
+       }
+       return 0;
+}
+
+static int run_agi(struct ast_channel *chan, char *request, int *fds, int pid)
+{
+       struct ast_channel *c;
+       int outfd;
+       int ms;
+       int returnstatus = 0;
+       struct ast_frame *f;
+       char buf[2048];
+       FILE *readf;
+       if (!(readf = fdopen(fds[0], "r"))) {
+               ast_log(LOG_WARNING, "Unable to fdopen file descriptor\n");
+               kill(pid, SIGHUP);
+               return -1;
+       }
+       setlinebuf(readf);
+       setup_env(chan, request, fds[1]);
+       for (;;) {
+               ms = -1;
+               c = ast_waitfor_nandfds(&chan, 1, &fds[0], 1, NULL, &outfd, &ms);
+               if (c) {
+                       /* Idle the channel until we get a command */
+                       f = ast_read(c);
+                       if (!f) {
+                               ast_log(LOG_DEBUG, "%s hungup\n", chan->name);
+                               returnstatus = -1;
+                               break;
+                       } else {
+                               ast_frfree(f);
+                       }
+               } else if (outfd > -1) {
+                       if (!fgets(buf, sizeof(buf), readf)) {
+                               /* Program terminated */
+                               if (returnstatus)
+                                       returnstatus = -1;
+                               if (option_verbose > 2) 
+                                       ast_verbose(VERBOSE_PREFIX_3 "AGI Script %s completed, returning %d\n", request, returnstatus);
+                               /* No need to kill the pid anymore, since they closed us */
+                               pid = -1;
+                               break;
+                       }
+                       returnstatus |= agi_handle_command(chan, fds[1], buf);
+                       /* If the handle_command returns -1, we need to stop */
+                       if (returnstatus < 0) {
+                               break;
+                       }
+               } else {
+                       ast_log(LOG_WARNING, "No channel, no fd?\n");
+                       returnstatus = -1;
+                       break;
+               }
+       }
+       /* Notify process */
+       if (pid > -1)
+               kill(pid, SIGHUP);
+       fclose(readf);
+       return returnstatus;
+}
+
+static int agi_exec(struct ast_channel *chan, void *data)
+{
+       int res=0;
+       struct localuser *u;
+       char *args,*ringy;
+       char tmp[256];
+       int fds[2];
+       int pid;
+       if (!data || !strlen(data)) {
+               ast_log(LOG_WARNING, "AGI requires an argument (script)\n");
+               return -1;
+       }
+
+
+       strncpy(tmp, data, sizeof(tmp));
+       strtok(tmp, "|");
+       args = strtok(NULL, "|");
+       ringy = strtok(NULL,"|");
+       if (!args)
+               args = "";
+       LOCAL_USER_ADD(u);
+        /* Answer if need be */
+        if (chan->state != AST_STATE_UP) {
+               if (ringy) { /* if for ringing first */
+                       /* a little ringy-dingy first */
+                       ast_indicate(chan, AST_CONTROL_RINGING);  
+                       sleep(3); 
+               }
+               if (ast_answer(chan)) {
+                       LOCAL_USER_REMOVE(u);
+                       return -1;
+               }
+       }
+       res = launch_script(tmp, args, fds, &pid);
+       if (!res) {
+               res = run_agi(chan, tmp, fds, pid);
+               close(fds[0]);
+               close(fds[1]);
+       }
+       LOCAL_USER_REMOVE(u);
+       return res;
+}
+
+int unload_module(void)
+{
+       STANDARD_HANGUP_LOCALUSERS;
+       return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+       return ast_register_application(app, agi_exec, synopsis, descrip);
+}
+
+char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       int res;
+       STANDARD_USECOUNT(res);
+       return res;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
+
+#define CLIP 32635
+#define BIAS 0x84
+
+unsigned char
+linear2ulaw(sample)
+short sample; {
+  static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
+                             4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
+                             5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+                             5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+                             6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+                             6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+                             6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+                             6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};
+  int sign, exponent, mantissa;
+  unsigned char ulawbyte;
+  /* Get the sample into sign-magnitude. */
+  sign = (sample >> 8) & 0x80;          /* set aside the sign */
+  if (sign != 0) sample = -sample;              /* get magnitude */
+  if (sample > CLIP) sample = CLIP;             /* clip the magnitude */
+  /* Convert from 16 bit linear to ulaw. */
+  sample = sample + BIAS;
+  exponent = exp_lut[(sample >> 7) & 0xFF];
+  mantissa = (sample >> (exponent + 3)) & 0x0F;
+  ulawbyte = ~(sign | (exponent << 4) | mantissa);
+#ifdef ZEROTRAP
+  if (ulawbyte == 0) ulawbyte = 0x02;   /* optional CCITT trap */
+#endif
+  return(ulawbyte);
+}