Mon Jul 14 17:25:00 2008

Asterisk developer's documentation


res_agi.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2006, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * Copyright (C) 2005 Junghanns.NET GmbH
00015  * Klaus-Peter Junghanns <kpj@junghanns.net>
00016  *
00017  * This program is free software, distributed under the terms of
00018  * the GNU General Public License Version 2. See the LICENSE file
00019  * at the top of the source tree.
00020  */
00021 
00022 /*! \file
00023  *
00024  * \brief AGI - the Asterisk Gateway Interface
00025  *
00026  * \author Mark Spencer <markster@digium.com> 
00027  */
00028 
00029 #include "asterisk.h"
00030 
00031 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 116466 $")
00032 
00033 #include <sys/types.h>
00034 #include <netdb.h>
00035 #include <sys/socket.h>
00036 #include <netinet/in.h>
00037 #include <netinet/tcp.h>
00038 #include <arpa/inet.h>
00039 #include <math.h>
00040 #include <stdlib.h>
00041 #include <unistd.h>
00042 #include <string.h>
00043 #include <stdlib.h>
00044 #include <signal.h>
00045 #include <sys/time.h>
00046 #include <stdio.h>
00047 #include <fcntl.h>
00048 #include <errno.h>
00049 #include <sys/wait.h>
00050 
00051 #include "asterisk/file.h"
00052 #include "asterisk/logger.h"
00053 #include "asterisk/channel.h"
00054 #include "asterisk/pbx.h"
00055 #include "asterisk/module.h"
00056 #include "asterisk/astdb.h"
00057 #include "asterisk/callerid.h"
00058 #include "asterisk/cli.h"
00059 #include "asterisk/logger.h"
00060 #include "asterisk/options.h"
00061 #include "asterisk/image.h"
00062 #include "asterisk/say.h"
00063 #include "asterisk/app.h"
00064 #include "asterisk/dsp.h"
00065 #include "asterisk/musiconhold.h"
00066 #include "asterisk/utils.h"
00067 #include "asterisk/lock.h"
00068 #include "asterisk/strings.h"
00069 #include "asterisk/agi.h"
00070 
00071 #define MAX_ARGS 128
00072 #define MAX_COMMANDS 128
00073 #define AGI_NANDFS_RETRY 3
00074 #define AGI_BUF_LEN 2048
00075 
00076 /* Recycle some stuff from the CLI interface */
00077 #define fdprintf agi_debug_cli
00078 
00079 static char *app = "AGI";
00080 
00081 static char *xapp = "XAGI";
00082 
00083 static char *eapp = "EAGI";
00084 
00085 static char *deadapp = "DeadAGI";
00086 
00087 static char *synopsis = "Executes an AGI compliant application";
00088 static char *xsynopsis = "Executes an XAGI compliant application";
00089 static char *esynopsis = "Executes an EAGI compliant application";
00090 static char *deadsynopsis = "Executes AGI on a hungup channel";
00091 
00092 static char *descrip =
00093 "  [E|Dead|X]AGI(command|args): Executes an Asterisk Gateway Interface compliant\n"
00094 "program on a channel. AGI allows Asterisk to launch external programs\n"
00095 "written in any language to control a telephony channel, play audio,\n"
00096 "read DTMF digits, etc. by communicating with the AGI protocol on stdin\n"
00097 "and stdout.\n"
00098 "  This channel will stop dialplan execution on hangup inside of this\n"
00099 "application, except when using DeadAGI.  Otherwise, dialplan execution\n"
00100 "will continue normally.\n"
00101 "  A locally executed AGI script will receive SIGHUP on hangup from the channel\n"
00102 "except when using DeadAGI. This can be disabled by setting the AGISIGHUP channel\n"
00103 "variable to \"no\" before executing the AGI application.\n"
00104 "  Using 'EAGI' provides enhanced AGI, with incoming audio available out of band\n"
00105 "on file descriptor 3\n\n"
00106 "Using 'XAGI' provides enhanced AGI, with incoming audio available out of band"
00107 " on file descriptor 3 and outgoing audio available out of band on file descriptor 4\n\n"
00108 "  Use the CLI command 'agi show' to list available agi commands\n"
00109 "  This application sets the following channel variable upon completion:\n"
00110 "     AGISTATUS      The status of the attempt to the run the AGI script\n"
00111 "                    text string, one of SUCCESS | FAILURE | HANGUP\n";
00112 
00113 static int agidebug = 0;
00114 
00115 #define TONE_BLOCK_SIZE 200
00116 
00117 /* Max time to connect to an AGI remote host */
00118 #define MAX_AGI_CONNECT 2000
00119 
00120 #define AGI_PORT 4573
00121 
00122 enum agi_result {
00123    AGI_RESULT_SUCCESS,
00124    AGI_RESULT_SUCCESS_FAST,
00125    AGI_RESULT_FAILURE,
00126    AGI_RESULT_HANGUP
00127 };
00128 
00129 static int agi_debug_cli(int fd, char *fmt, ...)
00130 {
00131    char *stuff;
00132    int res = 0;
00133 
00134    va_list ap;
00135    va_start(ap, fmt);
00136    res = vasprintf(&stuff, fmt, ap);
00137    va_end(ap);
00138    if (res == -1) {
00139       ast_log(LOG_ERROR, "Out of memory\n");
00140    } else {
00141       if (agidebug)
00142          ast_verbose("AGI Tx >> %s", stuff); /* \n provided by caller */
00143       res = ast_carefulwrite(fd, stuff, strlen(stuff), 100);
00144       free(stuff);
00145    }
00146 
00147    return res;
00148 }
00149 
00150 /* launch_netscript: The fastagi handler.
00151    FastAGI defaults to port 4573 */
00152 static enum agi_result launch_netscript(char *agiurl, char *argv[], int *fds, int *efd, int *opid)
00153 {
00154    int s;
00155    int flags;
00156    struct pollfd pfds[1];
00157    char *host;
00158    char *c; int port = AGI_PORT;
00159    char *script="";
00160    struct sockaddr_in sin;
00161    struct hostent *hp;
00162    struct ast_hostent ahp;
00163    int res;
00164 
00165    /* agiusl is "agi://host.domain[:port][/script/name]" */
00166    host = ast_strdupa(agiurl + 6);  /* Remove agi:// */
00167    /* Strip off any script name */
00168    if ((c = strchr(host, '/'))) {
00169       *c = '\0';
00170       c++;
00171       script = c;
00172    }
00173    if ((c = strchr(host, ':'))) {
00174       *c = '\0';
00175       c++;
00176       port = atoi(c);
00177    }
00178    if (efd) {
00179       ast_log(LOG_WARNING, "AGI URI's don't support Enhanced AGI yet\n");
00180       return -1;
00181    }
00182    hp = ast_gethostbyname(host, &ahp);
00183    if (!hp) {
00184       ast_log(LOG_WARNING, "Unable to locate host '%s'\n", host);
00185       return -1;
00186    }
00187    s = socket(AF_INET, SOCK_STREAM, 0);
00188    if (s < 0) {
00189       ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno));
00190       return -1;
00191    }
00192    flags = fcntl(s, F_GETFL);
00193    if (flags < 0) {
00194       ast_log(LOG_WARNING, "Fcntl(F_GETFL) failed: %s\n", strerror(errno));
00195       close(s);
00196       return -1;
00197    }
00198    if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
00199       ast_log(LOG_WARNING, "Fnctl(F_SETFL) failed: %s\n", strerror(errno));
00200       close(s);
00201       return -1;
00202    }
00203    memset(&sin, 0, sizeof(sin));
00204    sin.sin_family = AF_INET;
00205    sin.sin_port = htons(port);
00206    memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
00207    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) && (errno != EINPROGRESS)) {
00208       ast_log(LOG_WARNING, "Connect failed with unexpected error: %s\n", strerror(errno));
00209       close(s);
00210       return AGI_RESULT_FAILURE;
00211    }
00212 
00213    pfds[0].fd = s;
00214    pfds[0].events = POLLOUT;
00215    while ((res = poll(pfds, 1, MAX_AGI_CONNECT)) != 1) {
00216       if (errno != EINTR) {
00217          if (!res) {
00218             ast_log(LOG_WARNING, "FastAGI connection to '%s' timed out after MAX_AGI_CONNECT (%d) milliseconds.\n",
00219                agiurl, MAX_AGI_CONNECT);
00220          } else
00221             ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", agiurl, strerror(errno));
00222          close(s);
00223          return AGI_RESULT_FAILURE;
00224       }
00225    }
00226 
00227    if (fdprintf(s, "agi_network: yes\n") < 0) {
00228       if (errno != EINTR) {
00229          ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", agiurl, strerror(errno));
00230          close(s);
00231          return AGI_RESULT_FAILURE;
00232       }
00233    }
00234 
00235    /* If we have a script parameter, relay it to the fastagi server */
00236    if (!ast_strlen_zero(script))
00237       fdprintf(s, "agi_network_script: %s\n", script);
00238 
00239    if (option_debug > 3)
00240       ast_log(LOG_DEBUG, "Wow, connected!\n");
00241    fds[0] = s;
00242    fds[1] = s;
00243    *opid = -1;
00244    return AGI_RESULT_SUCCESS_FAST;
00245 }
00246 
00247 static enum agi_result launch_script(char *script, char *argv[], int *fds, int *efd, int *efd2, int *opid)
00248 {
00249    char tmp[256];
00250    int pid;
00251    int toast[2];
00252    int fromast[2];
00253    int audio[2];
00254    int audio2[2];
00255    int x;
00256    int res;
00257    sigset_t signal_set, old_set;
00258    
00259    if (!strncasecmp(script, "agi://", 6))
00260       return launch_netscript(script, argv, fds, efd, opid);
00261    
00262    if (script[0] != '/') {
00263       snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_AGI_DIR, script);
00264       script = tmp;
00265    }
00266    if (pipe(toast)) {
00267       ast_log(LOG_WARNING, "Unable to create toast pipe: %s\n",strerror(errno));
00268       return AGI_RESULT_FAILURE;
00269    }
00270    if (pipe(fromast)) {
00271       ast_log(LOG_WARNING, "unable to create fromast pipe: %s\n", strerror(errno));
00272       close(toast[0]);
00273       close(toast[1]);
00274       return AGI_RESULT_FAILURE;
00275    }
00276    if (efd) {
00277       if (pipe(audio)) {
00278          ast_log(LOG_WARNING, "unable to create audio pipe: %s\n", strerror(errno));
00279          close(fromast[0]);
00280          close(fromast[1]);
00281          close(toast[0]);
00282          close(toast[1]);
00283          return AGI_RESULT_FAILURE;
00284       }
00285       res = fcntl(audio[1], F_GETFL);
00286       if (res > -1) 
00287          res = fcntl(audio[1], F_SETFL, res | O_NONBLOCK);
00288       if (res < 0) {
00289          ast_log(LOG_WARNING, "unable to set audio pipe parameters: %s\n", strerror(errno));
00290          close(fromast[0]);
00291          close(fromast[1]);
00292          close(toast[0]);
00293          close(toast[1]);
00294          close(audio[0]);
00295          close(audio[1]);
00296          return AGI_RESULT_FAILURE;
00297       }
00298    }
00299    if (efd2) {
00300       if (pipe(audio2)) {
00301          ast_log(LOG_WARNING, "unable to create audio pipe: %s\n", strerror(errno));
00302          close(fromast[0]);
00303          close(fromast[1]);
00304          close(toast[0]);
00305          close(toast[1]);
00306          close(audio[0]);
00307          close(audio[1]);
00308          return AGI_RESULT_FAILURE;
00309       }
00310       res = fcntl(audio2[0], F_GETFL);
00311       if (res > -1)
00312          res = fcntl(audio2[0], F_SETFL, res | O_NONBLOCK);
00313       if (res < 0) {
00314          ast_log(LOG_WARNING, "unable to set audio pipe parameters: %s\n", strerror(errno));
00315          close(fromast[0]);
00316          close(fromast[1]);
00317          close(toast[0]);
00318          close(toast[1]);
00319          close(audio[0]);
00320          close(audio[1]);
00321          close(audio2[0]);
00322          close(audio2[1]);
00323          return AGI_RESULT_FAILURE;
00324       }
00325    }
00326 
00327    /* Block SIGHUP during the fork - prevents a race */
00328    sigfillset(&signal_set);
00329    pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
00330    pid = fork();
00331    if (pid < 0) {
00332       ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
00333       pthread_sigmask(SIG_SETMASK, &old_set, NULL);
00334       return AGI_RESULT_FAILURE;
00335    }
00336    if (!pid) {
00337       /* Pass paths to AGI via environmental variables */
00338       setenv("AST_CONFIG_DIR", ast_config_AST_CONFIG_DIR, 1);
00339       setenv("AST_CONFIG_FILE", ast_config_AST_CONFIG_FILE, 1);
00340       setenv("AST_MODULE_DIR", ast_config_AST_MODULE_DIR, 1);
00341       setenv("AST_SPOOL_DIR", ast_config_AST_SPOOL_DIR, 1);
00342       setenv("AST_MONITOR_DIR", ast_config_AST_MONITOR_DIR, 1);
00343       setenv("AST_VAR_DIR", ast_config_AST_VAR_DIR, 1);
00344       setenv("AST_DATA_DIR", ast_config_AST_DATA_DIR, 1);
00345       setenv("AST_LOG_DIR", ast_config_AST_LOG_DIR, 1);
00346       setenv("AST_AGI_DIR", ast_config_AST_AGI_DIR, 1);
00347       setenv("AST_KEY_DIR", ast_config_AST_KEY_DIR, 1);
00348       setenv("AST_RUN_DIR", ast_config_AST_RUN_DIR, 1);
00349 
00350       /* Don't run AGI scripts with realtime priority -- it causes audio stutter */
00351       ast_set_priority(0);
00352 
00353       /* Redirect stdin and out, provide enhanced audio channel if desired */
00354       dup2(fromast[0], STDIN_FILENO);
00355       dup2(toast[1], STDOUT_FILENO);
00356       if (efd) {
00357          dup2(audio[0], STDERR_FILENO + 1);
00358       } else {
00359          close(STDERR_FILENO + 1);
00360       }
00361       if (efd2) {
00362          dup2(audio2[1], STDERR_FILENO + 2);
00363       } else {
00364          close(STDERR_FILENO + 2);
00365       }
00366 
00367       /* Before we unblock our signals, return our trapped signals back to the defaults */
00368       signal(SIGHUP, SIG_DFL);
00369       signal(SIGCHLD, SIG_DFL);
00370       signal(SIGINT, SIG_DFL);
00371       signal(SIGURG, SIG_DFL);
00372       signal(SIGTERM, SIG_DFL);
00373       signal(SIGPIPE, SIG_DFL);
00374       signal(SIGXFSZ, SIG_DFL);
00375 
00376       /* unblock important signal handlers */
00377       if (pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL)) {
00378          ast_log(LOG_WARNING, "unable to unblock signals for AGI script: %s\n", strerror(errno));
00379          _exit(1);
00380       }
00381 
00382       /* Close everything but stdin/out/error */
00383       for (x=STDERR_FILENO + 3;x<1024;x++)
00384          close(x);
00385 
00386       /* Execute script */
00387       execv(script, argv);
00388       /* Can't use ast_log since FD's are closed */
00389       fprintf(stdout, "verbose \"Failed to execute '%s': %s\" 2\n", script, strerror(errno));
00390       /* Special case to set status of AGI to failure */
00391       fprintf(stdout, "failure\n");
00392       fflush(stdout);
00393       _exit(1);
00394    }
00395    pthread_sigmask(SIG_SETMASK, &old_set, NULL);
00396    if (option_verbose > 2) 
00397       ast_verbose(VERBOSE_PREFIX_3 "Launched AGI Script %s\n", script);
00398    fds[0] = toast[0];
00399    fds[1] = fromast[1];
00400    if (efd) {
00401       *efd = audio[1];
00402    }
00403    if (efd2) {
00404       *efd2 = audio2[0];
00405    }
00406    /* close what we're not using in the parent */
00407    close(toast[1]);
00408    close(fromast[0]);
00409 
00410    if (efd) {
00411       close(audio[0]);
00412    }
00413    if (efd2) {
00414       close(audio2[1]);
00415    }
00416 
00417    *opid = pid;
00418    return AGI_RESULT_SUCCESS;
00419 }
00420 
00421 static void setup_env(struct ast_channel *chan, char *request, int fd, int enhanced)
00422 {
00423    /* Print initial environment, with agi_request always being the first
00424       thing */
00425    fdprintf(fd, "agi_request: %s\n", request);
00426    fdprintf(fd, "agi_channel: %s\n", chan->name);
00427    fdprintf(fd, "agi_language: %s\n", chan->language);
00428    fdprintf(fd, "agi_type: %s\n", chan->tech->type);
00429    fdprintf(fd, "agi_uniqueid: %s\n", chan->uniqueid);
00430 
00431    /* ANI/DNIS */
00432    fdprintf(fd, "agi_callerid: %s\n", S_OR(chan->cid.cid_num, "unknown"));
00433    fdprintf(fd, "agi_calleridname: %s\n", S_OR(chan->cid.cid_name, "unknown"));
00434    fdprintf(fd, "agi_callingpres: %d\n", chan->cid.cid_pres);
00435    fdprintf(fd, "agi_callingani2: %d\n", chan->cid.cid_ani2);
00436    fdprintf(fd, "agi_callington: %d\n", chan->cid.cid_ton);
00437    fdprintf(fd, "agi_callingtns: %d\n", chan->cid.cid_tns);
00438    fdprintf(fd, "agi_dnid: %s\n", S_OR(chan->cid.cid_dnid, "unknown"));
00439    fdprintf(fd, "agi_rdnis: %s\n", S_OR(chan->cid.cid_rdnis, "unknown"));
00440 
00441    /* Context information */
00442    fdprintf(fd, "agi_context: %s\n", chan->context);
00443    fdprintf(fd, "agi_extension: %s\n", chan->exten);
00444    fdprintf(fd, "agi_priority: %d\n", chan->priority);
00445    fdprintf(fd, "agi_enhanced: %d%s\n", enhanced, ".0");
00446 
00447    /* User information */
00448    fdprintf(fd, "agi_accountcode: %s\n", chan->accountcode ? chan->accountcode : "");
00449     
00450    /* End with empty return */
00451    fdprintf(fd, "\n");
00452 }
00453 
00454 static int handle_answer(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00455 {
00456    int res;
00457    res = 0;
00458    if (chan->_state != AST_STATE_UP) {
00459       /* Answer the chan */
00460       res = ast_answer(chan);
00461    }
00462    fdprintf(agi->fd, "200 result=%d\n", res);
00463    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00464 }
00465 
00466 static int handle_waitfordigit(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00467 {
00468    int res;
00469    int to;
00470    if (argc != 4)
00471       return RESULT_SHOWUSAGE;
00472    if (sscanf(argv[3], "%d", &to) != 1)
00473       return RESULT_SHOWUSAGE;
00474    res = ast_waitfordigit_full(chan, to, agi->audio, agi->ctrl);
00475    fdprintf(agi->fd, "200 result=%d\n", res);
00476    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00477 }
00478 
00479 static int handle_sendtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00480 {
00481    int res;
00482    if (argc != 3)
00483       return RESULT_SHOWUSAGE;
00484    /* At the moment, the parser (perhaps broken) returns with
00485       the last argument PLUS the newline at the end of the input
00486       buffer. This probably needs to be fixed, but I wont do that
00487       because other stuff may break as a result. The right way
00488       would probably be to strip off the trailing newline before
00489       parsing, then here, add a newline at the end of the string
00490       before sending it to ast_sendtext --DUDE */
00491    res = ast_sendtext(chan, argv[2]);
00492    fdprintf(agi->fd, "200 result=%d\n", res);
00493    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00494 }
00495 
00496 static int handle_recvchar(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00497 {
00498    int res;
00499    if (argc != 3)
00500       return RESULT_SHOWUSAGE;
00501    res = ast_recvchar(chan,atoi(argv[2]));
00502    if (res == 0) {
00503       fdprintf(agi->fd, "200 result=%d (timeout)\n", res);
00504       return RESULT_SUCCESS;
00505    }
00506    if (res > 0) {
00507       fdprintf(agi->fd, "200 result=%d\n", res);
00508       return RESULT_SUCCESS;
00509    }
00510    else {
00511       fdprintf(agi->fd, "200 result=%d (hangup)\n", res);
00512       return RESULT_FAILURE;
00513    }
00514 }
00515 
00516 static int handle_recvtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00517 {
00518    char *buf;
00519    
00520    if (argc != 3)
00521       return RESULT_SHOWUSAGE;
00522    buf = ast_recvtext(chan,atoi(argv[2]));
00523    if (buf) {
00524       fdprintf(agi->fd, "200 result=1 (%s)\n", buf);
00525       free(buf);
00526    } else { 
00527       fdprintf(agi->fd, "200 result=-1\n");
00528    }
00529    return RESULT_SUCCESS;
00530 }
00531 
00532 static int handle_tddmode(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00533 {
00534    int res,x;
00535    if (argc != 3)
00536       return RESULT_SHOWUSAGE;
00537    if (!strncasecmp(argv[2],"on",2)) 
00538       x = 1; 
00539    else 
00540       x = 0;
00541    if (!strncasecmp(argv[2],"mate",4)) 
00542       x = 2;
00543    if (!strncasecmp(argv[2],"tdd",3))
00544       x = 1;
00545    res = ast_channel_setoption(chan, AST_OPTION_TDD, &x, sizeof(char), 0);
00546    if (res != RESULT_SUCCESS)
00547       fdprintf(agi->fd, "200 result=0\n");
00548    else
00549       fdprintf(agi->fd, "200 result=1\n");
00550    return RESULT_SUCCESS;
00551 }
00552 
00553 static int handle_sendimage(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00554 {
00555    int res;
00556    if (argc != 3)
00557       return RESULT_SHOWUSAGE;
00558    res = ast_send_image(chan, argv[2]);
00559    if (!ast_check_hangup(chan))
00560       res = 0;
00561    fdprintf(agi->fd, "200 result=%d\n", res);
00562    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00563 }
00564 
00565 static int handle_controlstreamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00566 {
00567    int res = 0;
00568    int skipms = 3000;
00569    char *fwd = NULL;
00570    char *rev = NULL;
00571    char *pause = NULL;
00572    char *stop = NULL;
00573 
00574    if (argc < 5 || argc > 9)
00575       return RESULT_SHOWUSAGE;
00576 
00577    if (!ast_strlen_zero(argv[4]))
00578       stop = argv[4];
00579    else
00580       stop = NULL;
00581    
00582    if ((argc > 5) && (sscanf(argv[5], "%d", &skipms) != 1))
00583       return RESULT_SHOWUSAGE;
00584 
00585    if (argc > 6 && !ast_strlen_zero(argv[6]))
00586       fwd = argv[6];
00587    else
00588       fwd = "#";
00589 
00590    if (argc > 7 && !ast_strlen_zero(argv[7]))
00591       rev = argv[7];
00592    else
00593       rev = "*";
00594    
00595    if (argc > 8 && !ast_strlen_zero(argv[8]))
00596       pause = argv[8];
00597    else
00598       pause = NULL;
00599    
00600    res = ast_control_streamfile(chan, argv[3], fwd, rev, stop, pause, NULL, skipms);
00601    
00602    fdprintf(agi->fd, "200 result=%d\n", res);
00603 
00604    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00605 }
00606 
00607 static int handle_streamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00608 {
00609    int res;
00610    int vres;   
00611    struct ast_filestream *fs;
00612    struct ast_filestream *vfs;
00613    long sample_offset = 0;
00614    long max_length;
00615    char *edigits = "";
00616 
00617    if (argc < 4 || argc > 5)
00618       return RESULT_SHOWUSAGE;
00619 
00620    if (argv[3]) 
00621       edigits = argv[3];
00622 
00623    if ((argc > 4) && (sscanf(argv[4], "%ld", &sample_offset) != 1))
00624       return RESULT_SHOWUSAGE;
00625    
00626    fs = ast_openstream(chan, argv[2], chan->language);   
00627    
00628    if (!fs) {
00629       fdprintf(agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset);
00630       return RESULT_SUCCESS;
00631    }  
00632    vfs = ast_openvstream(chan, argv[2], chan->language);
00633    if (vfs)
00634       ast_log(LOG_DEBUG, "Ooh, found a video stream, too\n");
00635       
00636    if (option_verbose > 2)
00637       ast_verbose(VERBOSE_PREFIX_3 "Playing '%s' (escape_digits=%s) (sample_offset %ld)\n", argv[2], edigits, sample_offset);
00638 
00639    ast_seekstream(fs, 0, SEEK_END);
00640    max_length = ast_tellstream(fs);
00641    ast_seekstream(fs, sample_offset, SEEK_SET);
00642    res = ast_applystream(chan, fs);
00643    if (vfs)
00644       vres = ast_applystream(chan, vfs);
00645    ast_playstream(fs);
00646    if (vfs)
00647       ast_playstream(vfs);
00648    
00649    res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl);
00650    /* this is to check for if ast_waitstream closed the stream, we probably are at
00651     * the end of the stream, return that amount, else check for the amount */
00652    sample_offset = (chan->stream) ? ast_tellstream(fs) : max_length;
00653    ast_stopstream(chan);
00654    if (res == 1) {
00655       /* Stop this command, don't print a result line, as there is a new command */
00656       return RESULT_SUCCESS;
00657    }
00658    fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset);
00659    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00660 }
00661 
00662 /* get option - really similar to the handle_streamfile, but with a timeout */
00663 static int handle_getoption(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00664 {
00665    int res;
00666    int vres;   
00667    struct ast_filestream *fs;
00668    struct ast_filestream *vfs;
00669    long sample_offset = 0;
00670    long max_length;
00671    int timeout = 0;
00672    char *edigits = "";
00673 
00674    if ( argc < 4 || argc > 5 )
00675       return RESULT_SHOWUSAGE;
00676 
00677    if ( argv[3] ) 
00678       edigits = argv[3];
00679 
00680    if ( argc == 5 )
00681       timeout = atoi(argv[4]);
00682    else if (chan->pbx->dtimeout) {
00683       /* by default dtimeout is set to 5sec */
00684       timeout = chan->pbx->dtimeout * 1000; /* in msec */
00685    }
00686 
00687    fs = ast_openstream(chan, argv[2], chan->language);
00688    if (!fs) {
00689       fdprintf(agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset);
00690       ast_log(LOG_WARNING, "Unable to open %s\n", argv[2]);
00691       return RESULT_SUCCESS;
00692    }
00693    vfs = ast_openvstream(chan, argv[2], chan->language);
00694    if (vfs)
00695       ast_log(LOG_DEBUG, "Ooh, found a video stream, too\n");
00696    
00697    if (option_verbose > 2)
00698       ast_verbose(VERBOSE_PREFIX_3 "Playing '%s' (escape_digits=%s) (timeout %d)\n", argv[2], edigits, timeout);
00699 
00700    ast_seekstream(fs, 0, SEEK_END);
00701    max_length = ast_tellstream(fs);
00702    ast_seekstream(fs, sample_offset, SEEK_SET);
00703    res = ast_applystream(chan, fs);
00704    if (vfs)
00705       vres = ast_applystream(chan, vfs);
00706    ast_playstream(fs);
00707    if (vfs)
00708       ast_playstream(vfs);
00709 
00710    res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl);
00711    /* this is to check for if ast_waitstream closed the stream, we probably are at
00712     * the end of the stream, return that amount, else check for the amount */
00713    sample_offset = (chan->stream)?ast_tellstream(fs):max_length;
00714    ast_stopstream(chan);
00715    if (res == 1) {
00716       /* Stop this command, don't print a result line, as there is a new command */
00717       return RESULT_SUCCESS;
00718    }
00719 
00720    /* If the user didnt press a key, wait for digitTimeout*/
00721    if (res == 0 ) {
00722       res = ast_waitfordigit_full(chan, timeout, agi->audio, agi->ctrl);
00723       /* Make sure the new result is in the escape digits of the GET OPTION */
00724       if ( !strchr(edigits,res) )
00725          res=0;
00726    }
00727 
00728         fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset);
00729    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00730 }
00731 
00732 
00733 
00734 
00735 /*--- handle_saynumber: Say number in various language syntaxes ---*/
00736 /* Need to add option for gender here as well. Coders wanted */
00737 /* While waiting, we're sending a (char *) NULL.  */
00738 static int handle_saynumber(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00739 {
00740    int res;
00741    int num;
00742    if (argc != 4)
00743       return RESULT_SHOWUSAGE;
00744    if (sscanf(argv[2], "%d", &num) != 1)
00745       return RESULT_SHOWUSAGE;
00746    res = ast_say_number_full(chan, num, argv[3], chan->language, (char *) NULL, agi->audio, agi->ctrl);
00747    if (res == 1)
00748       return RESULT_SUCCESS;
00749    fdprintf(agi->fd, "200 result=%d\n", res);
00750    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00751 }
00752 
00753 static int handle_saydigits(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00754 {
00755    int res;
00756    int num;
00757 
00758    if (argc != 4)
00759       return RESULT_SHOWUSAGE;
00760    if (sscanf(argv[2], "%d", &num) != 1)
00761       return RESULT_SHOWUSAGE;
00762 
00763    res = ast_say_digit_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl);
00764    if (res == 1) /* New command */
00765       return RESULT_SUCCESS;
00766    fdprintf(agi->fd, "200 result=%d\n", res);
00767    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00768 }
00769 
00770 static int handle_sayalpha(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00771 {
00772    int res;
00773 
00774    if (argc != 4)
00775       return RESULT_SHOWUSAGE;
00776 
00777    res = ast_say_character_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl);
00778    if (res == 1) /* New command */
00779       return RESULT_SUCCESS;
00780    fdprintf(agi->fd, "200 result=%d\n", res);
00781    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00782 }
00783 
00784 static int handle_saydate(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00785 {
00786    int res;
00787    int num;
00788    if (argc != 4)
00789       return RESULT_SHOWUSAGE;
00790    if (sscanf(argv[2], "%d", &num) != 1)
00791       return RESULT_SHOWUSAGE;
00792    res = ast_say_date(chan, num, argv[3], chan->language);
00793    if (res == 1)
00794       return RESULT_SUCCESS;
00795    fdprintf(agi->fd, "200 result=%d\n", res);
00796    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00797 }
00798 
00799 static int handle_saytime(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00800 {
00801    int res;
00802    int num;
00803    if (argc != 4)
00804       return RESULT_SHOWUSAGE;
00805    if (sscanf(argv[2], "%d", &num) != 1)
00806       return RESULT_SHOWUSAGE;
00807    res = ast_say_time(chan, num, argv[3], chan->language);
00808    if (res == 1)
00809       return RESULT_SUCCESS;
00810    fdprintf(agi->fd, "200 result=%d\n", res);
00811    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00812 }
00813 
00814 static int handle_saydatetime(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00815 {
00816    int res=0;
00817    time_t unixtime;
00818    char *format, *zone=NULL;
00819    
00820    if (argc < 4)
00821       return RESULT_SHOWUSAGE;
00822 
00823    if (argc > 4) {
00824       format = argv[4];
00825    } else {
00826       /* XXX this doesn't belong here, but in the 'say' module */
00827       if (!strcasecmp(chan->language, "de")) {
00828          format = "A dBY HMS";
00829       } else {
00830          format = "ABdY 'digits/at' IMp"; 
00831       }
00832    }
00833 
00834    if (argc > 5 && !ast_strlen_zero(argv[5]))
00835       zone = argv[5];
00836 
00837    if (ast_get_time_t(argv[2], &unixtime, 0, NULL))
00838       return RESULT_SHOWUSAGE;
00839 
00840    res = ast_say_date_with_format(chan, unixtime, argv[3], chan->language, format, zone);
00841    if (res == 1)
00842       return RESULT_SUCCESS;
00843 
00844    fdprintf(agi->fd, "200 result=%d\n", res);
00845    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00846 }
00847 
00848 static int handle_sayphonetic(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00849 {
00850    int res;
00851 
00852    if (argc != 4)
00853       return RESULT_SHOWUSAGE;
00854 
00855    res = ast_say_phonetic_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl);
00856    if (res == 1) /* New command */
00857       return RESULT_SUCCESS;
00858    fdprintf(agi->fd, "200 result=%d\n", res);
00859    return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
00860 }
00861 
00862 static int handle_getdata(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00863 {
00864    int res;
00865    char data[1024];
00866    int max;
00867    int timeout;
00868 
00869    if (argc < 3)
00870       return RESULT_SHOWUSAGE;
00871    if (argc >= 4)
00872       timeout = atoi(argv[3]); 
00873    else
00874       timeout = 0;
00875    if (argc >= 5) 
00876       max = atoi(argv[4]); 
00877    else
00878       max = 1024;
00879    res = ast_app_getdata_full(chan, argv[2], data, max, timeout, agi->audio, agi->ctrl);
00880    if (res == 2)        /* New command */
00881       return RESULT_SUCCESS;
00882    else if (res == 1)
00883       fdprintf(agi->fd, "200 result=%s (timeout)\n", data);
00884    else if (res < 0 )
00885       fdprintf(agi->fd, "200 result=-1\n");
00886    else
00887       fdprintf(agi->fd, "200 result=%s\n", data);
00888    return RESULT_SUCCESS;
00889 }
00890 
00891 static int handle_setcontext(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00892 {
00893 
00894    if (argc != 3)
00895       return RESULT_SHOWUSAGE;
00896    ast_copy_string(chan->context, argv[2], sizeof(chan->context));
00897    fdprintf(agi->fd, "200 result=0\n");
00898    return RESULT_SUCCESS;
00899 }
00900    
00901 static int handle_setextension(struct ast_channel *chan, AGI *agi, int argc, char **argv)
00902 {
00903    if (argc != 3)
00904       return RESULT_SHOWUSAGE;
00905    ast_copy_string(chan->exten, argv[2], sizeof(chan->exten));
00906    fdprintf(agi->fd, "200 result=0\n");
00907    return RESULT_SUCCESS;
00908 }
00909 
00910 static int handle_setpriority(struct ast_channel *chan, AGI *agi, int argc, char **argv)
00911 {
00912    int pri;
00913    if (argc != 3)
00914       return RESULT_SHOWUSAGE;   
00915 
00916    if (sscanf(argv[2], "%d", &pri) != 1) {
00917       if ((pri = ast_findlabel_extension(chan, chan->context, chan->exten, argv[2], chan->cid.cid_num)) < 1)
00918          return RESULT_SHOWUSAGE;
00919    }
00920 
00921    ast_explicit_goto(chan, NULL, NULL, pri);
00922    fdprintf(agi->fd, "200 result=0\n");
00923    return RESULT_SUCCESS;
00924 }
00925       
00926 static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
00927 {
00928    struct ast_filestream *fs;
00929    struct ast_frame *f;
00930    struct timeval start;
00931    long sample_offset = 0;
00932    int res = 0;
00933    int ms;
00934 
00935         struct ast_dsp *sildet=NULL;         /* silence detector dsp */
00936         int totalsilence = 0;
00937         int dspsilence = 0;
00938         int silence = 0;                /* amount of silence to allow */
00939         int gotsilence = 0;             /* did we timeout for silence? */
00940         char *silencestr=NULL;
00941         int rfmt=0;
00942 
00943 
00944    /* XXX EAGI FIXME XXX */
00945 
00946    if (argc < 6)
00947       return RESULT_SHOWUSAGE;
00948    if (sscanf(argv[5], "%d", &ms) != 1)
00949       return RESULT_SHOWUSAGE;
00950 
00951    if (argc > 6)
00952       silencestr = strchr(argv[6],'s');
00953    if ((argc > 7) && (!silencestr))
00954       silencestr = strchr(argv[7],'s');
00955    if ((argc > 8) && (!silencestr))
00956       silencestr = strchr(argv[8],'s');
00957 
00958    if (silencestr) {
00959       if (strlen(silencestr) > 2) {
00960          if ((silencestr[0] == 's') && (silencestr[1] == '=')) {
00961             silencestr++;
00962             silencestr++;
00963             if (silencestr)
00964                         silence = atoi(silencestr);
00965                if (silence > 0)
00966                         silence *= 1000;
00967             }
00968       }
00969    }
00970 
00971         if (silence > 0) {
00972          rfmt = chan->readformat;
00973                 res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
00974                 if (res < 0) {
00975                   ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
00976                         return -1;
00977                 }
00978                   sildet = ast_dsp_new();
00979                 if (!sildet) {
00980                   ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
00981                         return -1;
00982                 }
00983                   ast_dsp_set_threshold(sildet, 256);
00984          }
00985 
00986    /* backward compatibility, if no offset given, arg[6] would have been
00987     * caught below and taken to be a beep, else if it is a digit then it is a
00988     * offset */
00989    if ((argc >6) && (sscanf(argv[6], "%ld", &sample_offset) != 1) && (!strchr(argv[6], '=')))
00990       res = ast_streamfile(chan, "beep", chan->language);
00991 
00992    if ((argc > 7) && (!strchr(argv[7], '=')))
00993       res = ast_streamfile(chan, "beep", chan->language);
00994 
00995    if (!res)
00996       res = ast_waitstream(chan, argv[4]);
00997    if (res) {
00998       fdprintf(agi->fd, "200 result=%d (randomerror) endpos=%ld\n", res, sample_offset);
00999    } else {
01000       fs = ast_writefile(argv[2], argv[3], NULL, O_CREAT | O_WRONLY | (sample_offset ? O_APPEND : 0), 0, 0644);
01001       if (!fs) {
01002          res = -1;
01003          fdprintf(agi->fd, "200 result=%d (writefile)\n", res);
01004          if (sildet)
01005             ast_dsp_free(sildet);
01006          return RESULT_FAILURE;
01007       }
01008       
01009       /* Request a video update */
01010       ast_indicate(chan, AST_CONTROL_VIDUPDATE);
01011    
01012       chan->stream = fs;
01013       ast_applystream(chan,fs);
01014       /* really should have checks */
01015       ast_seekstream(fs, sample_offset, SEEK_SET);
01016       ast_truncstream(fs);
01017       
01018       start = ast_tvnow();
01019       while ((ms < 0) || ast_tvdiff_ms(ast_tvnow(), start) < ms) {
01020          res = ast_waitfor(chan, -1);
01021          if (res < 0) {
01022             ast_closestream(fs);
01023             fdprintf(agi->fd, "200 result=%d (waitfor) endpos=%ld\n", res,sample_offset);
01024             if (sildet)
01025                ast_dsp_free(sildet);
01026             return RESULT_FAILURE;
01027          }
01028          f = ast_read(chan);
01029          if (!f) {
01030             fdprintf(agi->fd, "200 result=%d (hangup) endpos=%ld\n", -1, sample_offset);
01031             ast_closestream(fs);
01032             if (sildet)
01033                ast_dsp_free(sildet);
01034             return RESULT_FAILURE;
01035          }
01036          switch(f->frametype) {
01037          case AST_FRAME_DTMF:
01038             if (strchr(argv[4], f->subclass)) {
01039                /* This is an interrupting chracter, so rewind to chop off any small
01040                   amount of DTMF that may have been recorded
01041                */
01042                ast_stream_rewind(fs, 200);
01043                ast_truncstream(fs);
01044                sample_offset = ast_tellstream(fs);
01045                fdprintf(agi->fd, "200 result=%d (dtmf) endpos=%ld\n", f->subclass, sample_offset);
01046                ast_closestream(fs);
01047                ast_frfree(f);
01048                if (sildet)
01049                   ast_dsp_free(sildet);
01050                return RESULT_SUCCESS;
01051             }
01052             break;
01053          case AST_FRAME_VOICE:
01054             ast_writestream(fs, f);
01055             /* this is a safe place to check progress since we know that fs
01056              * is valid after a write, and it will then have our current
01057              * location */
01058             sample_offset = ast_tellstream(fs);
01059                                 if (silence > 0) {
01060                                  dspsilence = 0;
01061                                         ast_dsp_silence(sildet, f, &dspsilence);
01062                                         if (dspsilence) {
01063                                              totalsilence = dspsilence;
01064                                         } else {
01065                                                 totalsilence = 0;
01066                                         }
01067                                         if (totalsilence > silence) {
01068                                              /* Ended happily with silence */
01069                                                 gotsilence = 1;
01070                                                 break;
01071                                         }
01072                               }
01073             break;
01074          case AST_FRAME_VIDEO:
01075             ast_writestream(fs, f);
01076          default:
01077             /* Ignore all other frames */
01078             break;
01079          }
01080          ast_frfree(f);
01081          if (gotsilence)
01082             break;
01083          }
01084 
01085                if (gotsilence) {
01086                         ast_stream_rewind(fs, silence-1000);
01087                   ast_truncstream(fs);
01088          sample_offset = ast_tellstream(fs);
01089       }     
01090       fdprintf(agi->fd, "200 result=%d (timeout) endpos=%ld\n", res, sample_offset);
01091       ast_closestream(fs);
01092    }
01093 
01094         if (silence > 0) {
01095                 res = ast_set_read_format(chan, rfmt);
01096                 if (res)
01097                         ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name);
01098                 ast_dsp_free(sildet);
01099         }
01100    return RESULT_SUCCESS;
01101 }
01102 
01103 static int handle_autohangup(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
01104 {
01105    int timeout;
01106 
01107    if (argc != 3)
01108       return RESULT_SHOWUSAGE;
01109    if (sscanf(argv[2], "%d", &timeout) != 1)
01110       return RESULT_SHOWUSAGE;
01111    if (timeout < 0)
01112       timeout = 0;
01113    if (timeout)
01114       chan->whentohangup = time(NULL) + timeout;
01115    else
01116       chan->whentohangup = 0;
01117    fdprintf(agi->fd, "200 result=0\n");
01118    return RESULT_SUCCESS;
01119 }
01120 
01121 static int handle_hangup(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01122 {
01123    struct ast_channel *c;
01124    if (argc == 1) {
01125       /* no argument: hangup the current channel */
01126       ast_softhangup(chan,AST_SOFTHANGUP_EXPLICIT);
01127       fdprintf(agi->fd, "200 result=1\n");
01128       return RESULT_SUCCESS;
01129    } else if (argc == 2) {
01130       /* one argument: look for info on the specified channel */
01131       c = ast_get_channel_by_name_locked(argv[1]);
01132       if (c) {
01133          /* we have a matching channel */
01134          ast_softhangup(c,AST_SOFTHANGUP_EXPLICIT);
01135          fdprintf(agi->fd, "200 result=1\n");
01136          ast_channel_unlock(c);
01137          return RESULT_SUCCESS;
01138       }
01139       /* if we get this far no channel name matched the argument given */
01140       fdprintf(agi->fd, "200 result=-1\n");
01141       return RESULT_SUCCESS;
01142    } else {
01143       return RESULT_SHOWUSAGE;
01144    }
01145 }
01146 
01147 static int handle_exec(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01148 {
01149    int res;
01150    struct ast_app *app;
01151 
01152    if (argc < 2)
01153       return RESULT_SHOWUSAGE;
01154 
01155    if (option_verbose > 2)
01156       ast_verbose(VERBOSE_PREFIX_3 "AGI Script Executing Application: (%s) Options: (%s)\n", argv[1], argv[2]);
01157 
01158    app = pbx_findapp(argv[1]);
01159 
01160    if (app) {
01161       res = pbx_exec(chan, app, argv[2]);
01162    } else {
01163       ast_log(LOG_WARNING, "Could not find application (%s)\n", argv[1]);
01164       res = -2;
01165    }
01166    fdprintf(agi->fd, "200 result=%d\n", res);
01167 
01168    /* Even though this is wrong, users are depending upon this result. */
01169    return res;
01170 }
01171 
01172 static int handle_setcallerid(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01173 {
01174    char tmp[256]="";
01175    char *l = NULL, *n = NULL;
01176 
01177    if (argv[2]) {
01178       ast_copy_string(tmp, argv[2], sizeof(tmp));
01179       ast_callerid_parse(tmp, &n, &l);
01180       if (l)
01181          ast_shrink_phone_number(l);
01182       else
01183          l = "";
01184       if (!n)
01185          n = "";
01186       ast_set_callerid(chan, l, n, NULL);
01187    }
01188 
01189    fdprintf(agi->fd, "200 result=1\n");
01190    return RESULT_SUCCESS;
01191 }
01192 
01193 static int handle_channelstatus(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01194 {
01195    struct ast_channel *c;
01196    if (argc == 2) {
01197       /* no argument: supply info on the current channel */
01198       fdprintf(agi->fd, "200 result=%d\n", chan->_state);
01199       return RESULT_SUCCESS;
01200    } else if (argc == 3) {
01201       /* one argument: look for info on the specified channel */
01202       c = ast_get_channel_by_name_locked(argv[2]);
01203       if (c) {
01204          fdprintf(agi->fd, "200 result=%d\n", c->_state);
01205          ast_channel_unlock(c);
01206          return RESULT_SUCCESS;
01207       }
01208       /* if we get this far no channel name matched the argument given */
01209       fdprintf(agi->fd, "200 result=-1\n");
01210       return RESULT_SUCCESS;
01211    } else {
01212       return RESULT_SHOWUSAGE;
01213    }
01214 }
01215 
01216 static int handle_setvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01217 {
01218    if (argv[3])
01219       pbx_builtin_setvar_helper(chan, argv[2], argv[3]);
01220 
01221    fdprintf(agi->fd, "200 result=1\n");
01222    return RESULT_SUCCESS;
01223 }
01224 
01225 static int handle_getvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01226 {
01227    char *ret;
01228    char tempstr[1024];
01229 
01230    if (argc != 3)
01231       return RESULT_SHOWUSAGE;
01232 
01233    /* check if we want to execute an ast_custom_function */
01234    if (!ast_strlen_zero(argv[2]) && (argv[2][strlen(argv[2]) - 1] == ')')) {
01235       ret = ast_func_read(chan, argv[2], tempstr, sizeof(tempstr)) ? NULL : tempstr;
01236    } else {
01237       pbx_retrieve_variable(chan, argv[2], &ret, tempstr, sizeof(tempstr), NULL);
01238    }
01239 
01240    if (ret)
01241       fdprintf(agi->fd, "200 result=1 (%s)\n", ret);
01242    else
01243       fdprintf(agi->fd, "200 result=0\n");
01244 
01245    return RESULT_SUCCESS;
01246 }
01247 
01248 static int handle_getvariablefull(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01249 {
01250    char tmp[4096] = "";
01251    struct ast_channel *chan2=NULL;
01252 
01253    if ((argc != 4) && (argc != 5))
01254       return RESULT_SHOWUSAGE;
01255    if (argc == 5) {
01256       chan2 = ast_get_channel_by_name_locked(argv[4]);
01257    } else {
01258       chan2 = chan;
01259    }
01260    if (chan2) {
01261       pbx_substitute_variables_helper(chan2, argv[3], tmp, sizeof(tmp) - 1);
01262       fdprintf(agi->fd, "200 result=1 (%s)\n", tmp);
01263    } else {
01264       fdprintf(agi->fd, "200 result=0\n");
01265    }
01266    if (chan2 && (chan2 != chan))
01267       ast_channel_unlock(chan2);
01268    return RESULT_SUCCESS;
01269 }
01270 
01271 static int handle_verbose(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01272 {
01273    int level = 0;
01274    char *prefix;
01275 
01276    if (argc < 2)
01277       return RESULT_SHOWUSAGE;
01278 
01279    if (argv[2])
01280       sscanf(argv[2], "%d", &level);
01281 
01282    switch (level) {
01283       case 4:
01284          prefix = VERBOSE_PREFIX_4;
01285          break;
01286       case 3:
01287          prefix = VERBOSE_PREFIX_3;
01288          break;
01289       case 2:
01290          prefix = VERBOSE_PREFIX_2;
01291          break;
01292       case 1:
01293       default:
01294          prefix = VERBOSE_PREFIX_1;
01295          break;
01296    }
01297 
01298    if (level <= option_verbose)
01299       ast_verbose("%s %s: %s\n", prefix, chan->data, argv[1]);
01300    
01301    fdprintf(agi->fd, "200 result=1\n");
01302    
01303    return RESULT_SUCCESS;
01304 }
01305 
01306 static int handle_dbget(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01307 {
01308    int res;
01309    char tmp[256];
01310 
01311    if (argc != 4)
01312       return RESULT_SHOWUSAGE;
01313    res = ast_db_get(argv[2], argv[3], tmp, sizeof(tmp));
01314    if (res) 
01315       fdprintf(agi->fd, "200 result=0\n");
01316    else
01317       fdprintf(agi->fd, "200 result=1 (%s)\n", tmp);
01318 
01319    return RESULT_SUCCESS;
01320 }
01321 
01322 static int handle_dbput(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01323 {
01324    int res;
01325 
01326    if (argc != 5)
01327       return RESULT_SHOWUSAGE;
01328    res = ast_db_put(argv[2], argv[3], argv[4]);
01329    fdprintf(agi->fd, "200 result=%c\n", res ? '0' : '1');
01330    return RESULT_SUCCESS;
01331 }
01332 
01333 static int handle_dbdel(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01334 {
01335    int res;
01336 
01337    if (argc != 4)
01338       return RESULT_SHOWUSAGE;
01339    res = ast_db_del(argv[2], argv[3]);
01340    fdprintf(agi->fd, "200 result=%c\n", res ? '0' : '1');
01341    return RESULT_SUCCESS;
01342 }
01343 
01344 static int handle_dbdeltree(struct ast_channel *chan, AGI *agi, int argc, char **argv)
01345 {
01346    int res;
01347    if ((argc < 3) || (argc > 4))
01348       return RESULT_SHOWUSAGE;
01349    if (argc == 4)
01350       res = ast_db_deltree(argv[2], argv[3]);
01351    else
01352       res = ast_db_deltree(argv[2], NULL);
01353 
01354    fdprintf(agi->fd, "200 result=%c\n", res ? '0' : '1');
01355    return RESULT_SUCCESS;
01356 }
01357 
01358 static char debug_usage[] = 
01359 "Usage: agi debug\n"
01360 "       Enables dumping of AGI transactions for debugging purposes\n";
01361 
01362 static char no_debug_usage[] = 
01363 "Usage: agi debug off\n"
01364 "       Disables dumping of AGI transactions for debugging purposes\n";
01365 
01366 static int agi_do_debug(int fd, int argc, char *argv[])
01367 {
01368    if (argc != 2)
01369       return RESULT_SHOWUSAGE;
01370    agidebug = 1;
01371    ast_cli(fd, "AGI Debugging Enabled\n");
01372    return RESULT_SUCCESS;
01373 }
01374 
01375 static int agi_no_debug_deprecated(int fd, int argc, char *argv[])
01376 {
01377    if (argc != 3)
01378       return RESULT_SHOWUSAGE;
01379    agidebug = 0;
01380    ast_cli(fd, "AGI Debugging Disabled\n");
01381    return RESULT_SUCCESS;
01382 }
01383 
01384 static int agi_no_debug(int fd, int argc, char *argv[])
01385 {
01386    if (argc != 3)
01387       return RESULT_SHOWUSAGE;
01388    agidebug = 0;
01389    ast_cli(fd, "AGI Debugging Disabled\n");
01390    return RESULT_SUCCESS;
01391 }
01392 
01393 static int handle_noop(struct ast_channel *chan, AGI *agi, int arg, char *argv[])
01394 {
01395    fdprintf(agi->fd, "200 result=0\n");
01396    return RESULT_SUCCESS;
01397 }
01398 
01399 static int handle_setmusic(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
01400 {
01401    if (!strncasecmp(argv[2], "on", 2))
01402       ast_moh_start(chan, argc > 3 ? argv[3] : NULL, NULL);
01403    else if (!strncasecmp(argv[2], "off", 3))
01404       ast_moh_stop(chan);
01405    fdprintf(agi->fd, "200 result=0\n");
01406    return RESULT_SUCCESS;
01407 }
01408 
01409 static char usage_setmusic[] =
01410 " Usage: SET MUSIC ON <on|off> <class>\n"
01411 "  Enables/Disables the music on hold generator.  If <class> is\n"
01412 " not specified, then the default music on hold class will be used.\n"
01413 " Always returns 0.\n";
01414 
01415 static char usage_dbput[] =
01416 " Usage: DATABASE PUT <family> <key> <value>\n"
01417 "  Adds or updates an entry in the Asterisk database for a\n"
01418 " given family, key, and value.\n"
01419 " Returns 1 if successful, 0 otherwise.\n";
01420 
01421 static char usage_dbget[] =
01422 " Usage: DATABASE GET <family> <key>\n"
01423 "  Retrieves an entry in the Asterisk database for a\n"
01424 " given family and key.\n"
01425 " Returns 0 if <key> is not set.  Returns 1 if <key>\n"
01426 " is set and returns the variable in parentheses.\n"
01427 " Example return code: 200 result=1 (testvariable)\n";
01428 
01429 static char usage_dbdel[] =
01430 " Usage: DATABASE DEL <family> <key>\n"
01431 "  Deletes an entry in the Asterisk database for a\n"
01432 " given family and key.\n"
01433 " Returns 1 if successful, 0 otherwise.\n";
01434 
01435 static char usage_dbdeltree[] =
01436 " Usage: DATABASE DELTREE <family> [keytree]\n"
01437 "  Deletes a family or specific keytree within a family\n"
01438 " in the Asterisk database.\n"
01439 " Returns 1 if successful, 0 otherwise.\n";
01440 
01441 static char usage_verbose[] =
01442 " Usage: VERBOSE <message> <level>\n"
01443 "  Sends <message> to the console via verbose message system.\n"
01444 " <level> is the the verbose level (1-4)\n"
01445 " Always returns 1.\n";
01446 
01447 static char usage_getvariable[] =
01448 " Usage: GET VARIABLE <variablename>\n"
01449 "  Returns 0 if <variablename> is not set.  Returns 1 if <variablename>\n"
01450 " is set and returns the variable in parentheses.\n"
01451 " example return code: 200 result=1 (testvariable)\n";
01452 
01453 static char usage_getvariablefull[] =
01454 " Usage: GET FULL VARIABLE <variablename> [<channel name>]\n"
01455 "  Returns 0 if <variablename> is not set or channel does not exist.  Returns 1\n"
01456 "if <variablename>  is set and returns the variable in parenthesis.  Understands\n"
01457 "complex variable names and builtin variables, unlike GET VARIABLE.\n"
01458 " example return code: 200 result=1 (testvariable)\n";
01459 
01460 static char usage_setvariable[] =
01461 " Usage: SET VARIABLE <variablename> <value>\n";
01462 
01463 static char usage_channelstatus[] =
01464 " Usage: CHANNEL STATUS [<channelname>]\n"
01465 "  Returns the status of the specified channel.\n" 
01466 " If no channel name is given the returns the status of the\n"
01467 " current channel.  Return values:\n"
01468 "  0 Channel is down and available\n"
01469 "  1 Channel is down, but reserved\n"
01470 "  2 Channel is off hook\n"
01471 "  3 Digits (or equivalent) have been dialed\n"
01472 "  4 Line is ringing\n"
01473 "  5 Remote end is ringing\n"
01474 "  6 Line is up\n"
01475 "  7 Line is busy\n";
01476 
01477 static char usage_setcallerid[] =
01478 " Usage: SET CALLERID <number>\n"
01479 "  Changes the callerid of the current channel.\n";
01480 
01481 static char usage_exec[] =
01482 " Usage: EXEC <application> <options>\n"
01483 "  Executes <application> with given <options>.\n"
01484 " Returns whatever the application returns, or -2 on failure to find application\n";
01485 
01486 static char usage_hangup[] =
01487 " Usage: HANGUP [<channelname>]\n"
01488 "  Hangs up the specified channel.\n"
01489 " If no channel name is given, hangs up the current channel\n";
01490 
01491 static char usage_answer[] = 
01492 " Usage: ANSWER\n"
01493 "  Answers channel if not already in answer state. Returns -1 on\n"
01494 " channel failure, or 0 if successful.\n";
01495 
01496 static char usage_waitfordigit[] = 
01497 " Usage: WAIT FOR DIGIT <timeout>\n"
01498 "  Waits up to 'timeout' milliseconds for channel to receive a DTMF digit.\n"
01499 " Returns -1 on channel failure, 0 if no digit is received in the timeout, or\n"
01500 " the numerical value of the ascii of the digit if one is received.  Use -1\n"
01501 " for the timeout value if you desire the call to block indefinitely.\n";
01502 
01503 static char usage_sendtext[] =
01504 " Usage: SEND TEXT \"<text to send>\"\n"
01505 "  Sends the given text on a channel. Most channels do not support the\n"
01506 " transmission of text.  Returns 0 if text is sent, or if the channel does not\n"
01507 " support text transmission.  Returns -1 only on error/hangup.  Text\n"
01508 " consisting of greater than one word should be placed in quotes since the\n"
01509 " command only accepts a single argument.\n";
01510 
01511 static char usage_recvchar[] =
01512 " Usage: RECEIVE CHAR <timeout>\n"
01513 "  Receives a character of text on a channel. Specify timeout to be the\n"
01514 " maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n"
01515 " do not support the reception of text. Returns the decimal value of the character\n"
01516 " if one is received, or 0 if the channel does not support text reception.  Returns\n"
01517 " -1 only on error/hangup.\n";
01518 
01519 static char usage_recvtext[] =
01520 " Usage: RECEIVE TEXT <timeout>\n"
01521 "  Receives a string of text on a channel. Specify timeout to be the\n"
01522 " maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n"
01523 " do not support the reception of text. Returns -1 for failure or 1 for success, and the string in parentheses.\n";
01524 
01525 static char usage_tddmode[] =
01526 " Usage: TDD MODE <on|off>\n"
01527 "  Enable/Disable TDD transmission/reception on a channel. Returns 1 if\n"
01528 " successful, or 0 if channel is not TDD-capable.\n";
01529 
01530 static char usage_sendimage[] =
01531 " Usage: SEND IMAGE <image>\n"
01532 "  Sends the given image on a channel. Most channels do not support the\n"
01533 " transmission of images. Returns 0 if image is sent, or if the channel does not\n"
01534 " support image transmission.  Returns -1 only on error/hangup. Image names\n"
01535 " should not include extensions.\n";
01536 
01537 static char usage_streamfile[] =
01538 " Usage: STREAM FILE <filename> <escape digits> [sample offset]\n"
01539 "  Send the given file, allowing playback to be interrupted by the given\n"
01540 " digits, if any. Use double quotes for the digits if you wish none to be\n"
01541 " permitted. If sample offset is provided then the audio will seek to sample\n"
01542 " offset before play starts.  Returns 0 if playback completes without a digit\n"
01543 " being pressed, or the ASCII numerical value of the digit if one was pressed,\n"
01544 " or -1 on error or if the channel was disconnected. Remember, the file\n"
01545 " extension must not be included in the filename.\n";
01546 
01547 static char usage_controlstreamfile[] =
01548 " Usage: CONTROL STREAM FILE <filename> <escape digits> [skipms] [ffchar] [rewchr] [pausechr]\n"
01549 "  Send the given file, allowing playback to be controled by the given\n"
01550 " digits, if any. Use double quotes for the digits if you wish none to be\n"
01551 " permitted.  Returns 0 if playback completes without a digit\n"
01552 " being pressed, or the ASCII numerical value of the digit if one was pressed,\n"
01553 " or -1 on error or if the channel was disconnected. Remember, the file\n"
01554 " extension must not be included in the filename.\n\n"
01555 " Note: ffchar and rewchar default to * and # respectively.\n";
01556 
01557 static char usage_getoption[] = 
01558 " Usage: GET OPTION <filename> <escape_digits> [timeout]\n"
01559 "  Behaves similar to STREAM FILE but used with a timeout option.\n";
01560 
01561 static char usage_saynumber[] =
01562 " Usage: SAY NUMBER <number> <escape digits>\n"
01563 "  Say a given number, returning early if any of the given DTMF digits\n"
01564 " are received on the channel.  Returns 0 if playback completes without a digit\n"
01565 " being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
01566 " -1 on error/hangup.\n";
01567 
01568 static char usage_saydigits[] =
01569 " Usage: SAY DIGITS <number> <escape digits>\n"
01570 "  Say a given digit string, returning early if any of the given DTMF digits\n"
01571 " are received on the channel. Returns 0 if playback completes without a digit\n"
01572 " being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
01573 " -1 on error/hangup.\n";
01574 
01575 static char usage_sayalpha[] =
01576 " Usage: SAY ALPHA <number> <escape digits>\n"
01577 "  Say a given character string, returning early if any of the given DTMF digits\n"
01578 " are received on the channel. Returns 0 if playback completes without a digit\n"
01579 " being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
01580 " -1 on error/hangup.\n";
01581 
01582 static char usage_saydate[] =
01583 " Usage: SAY DATE <date> <escape digits>\n"
01584 "  Say a given date, returning early if any of the given DTMF digits are\n"
01585 " received on the channel.  <date> is number of seconds elapsed since 00:00:00\n"
01586 " on January 1, 1970, Coordinated Universal Time (UTC). Returns 0 if playback\n"
01587 " completes without a digit being pressed, or the ASCII numerical value of the\n"
01588 " digit if one was pressed or -1 on error/hangup.\n";
01589 
01590 static char usage_saytime[] =
01591 " Usage: SAY TIME <time> <escape digits>\n"
01592 "  Say a given time, returning early if any of the given DTMF digits are\n"
01593 " received on the channel.  <time> is number of seconds elapsed since 00:00:00\n"
01594 " on January 1, 1970, Coordinated Universal Time (UTC). Returns 0 if playback\n"
01595 " completes without a digit being pressed, or the ASCII numerical value of the\n"
01596 " digit if one was pressed or -1 on error/hangup.\n";
01597 
01598 static char usage_saydatetime[] =
01599 " Usage: SAY DATETIME <time> <escape digits> [format] [timezone]\n"
01600 "  Say a given time, returning early if any of the given DTMF digits are\n"
01601 " received on the channel.  <time> is number of seconds elapsed since 00:00:00\n"
01602 " on January 1, 1970, Coordinated Universal Time (UTC). [format] is the format\n"
01603 " the time should be said in.  See voicemail.conf (defaults to \"ABdY\n"
01604 " 'digits/at' IMp\").  Acceptable values for [timezone] can be found in\n"
01605 " /usr/share/zoneinfo.  Defaults to machine default. Returns 0 if playback\n"
01606 " completes without a digit being pressed, or the ASCII numerical value of the\n"
01607 " digit if one was pressed or -1 on error/hangup.\n";
01608 
01609 static char usage_sayphonetic[] =
01610 " Usage: SAY PHONETIC <string> <escape digits>\n"
01611 "  Say a given character string with phonetics, returning early if any of the\n"
01612 " given DTMF digits are received on the channel. Returns 0 if playback\n"
01613 " completes without a digit pressed, the ASCII numerical value of the digit\n"
01614 " if one was pressed, or -1 on error/hangup.\n";
01615 
01616 static char usage_getdata[] =
01617 " Usage: GET DATA <file to be streamed> [timeout] [max digits]\n"
01618 "  Stream the given file, and recieve DTMF data. Returns the digits received\n"
01619 "from the channel at the other end.\n";
01620 
01621 static char usage_setcontext[] =
01622 " Usage: SET CONTEXT <desired context>\n"
01623 "  Sets the context for continuation upon exiting the application.\n";
01624 
01625 static char usage_setextension[] =
01626 " Usage: SET EXTENSION <new extension>\n"
01627 "  Changes the extension for continuation upon exiting the application.\n";
01628 
01629 static char usage_setpriority[] =
01630 " Usage: SET PRIORITY <priority>\n"
01631 "  Changes the priority for continuation upon exiting the application.\n"
01632 " The priority must be a valid priority or label.\n";
01633 
01634 static char usage_recordfile[] =
01635 " Usage: RECORD FILE <filename> <format> <escape digits> <timeout> \\\n"
01636 "                                          [offset samples] [BEEP] [s=silence]\n"
01637 "  Record to a file until a given dtmf digit in the sequence is received\n"
01638 " Returns -1 on hangup or error.  The format will specify what kind of file\n"
01639 " will be recorded.  The timeout is the maximum record time in milliseconds, or\n"
01640 " -1 for no timeout. \"Offset samples\" is optional, and, if provided, will seek\n"
01641 " to the offset without exceeding the end of the file.  \"silence\" is the number\n"
01642 " of seconds of silence allowed before the function returns despite the\n"
01643 " lack of dtmf digits or reaching timeout.  Silence value must be\n"
01644 " preceeded by \"s=\" and is also optional.\n";
01645 
01646 static char usage_autohangup[] =
01647 " Usage: SET AUTOHANGUP <time>\n"
01648 "  Cause the channel to automatically hangup at <time> seconds in the\n"
01649 " future.  Of course it can be hungup before then as well. Setting to 0 will\n"
01650 " cause the autohangup feature to be disabled on this channel.\n";
01651 
01652 static char usage_noop[] =
01653 " Usage: NoOp\n"
01654 "  Does nothing.\n";
01655 
01656 static agi_command commands[MAX_COMMANDS] = {
01657    { { "answer", NULL }, handle_answer, "Answer channel", usage_answer },
01658    { { "channel", "status", NULL }, handle_channelstatus, "Returns status of the connected channel", usage_channelstatus },
01659    { { "database", "del", NULL }, handle_dbdel, "Removes database key/value", usage_dbdel },
01660    { { "database", "deltree", NULL }, handle_dbdeltree, "Removes database keytree/value", usage_dbdeltree },
01661    { { "database", "get", NULL }, handle_dbget, "Gets database value", usage_dbget },
01662    { { "database", "put", NULL }, handle_dbput, "Adds/updates database value", usage_dbput },
01663    { { "exec", NULL }, handle_exec, "Executes a given Application", usage_exec },
01664    { { "get", "data", NULL }, handle_getdata, "Prompts for DTMF on a channel", usage_getdata },
01665    { { "get", "full", "variable", NULL }, handle_getvariablefull, "Evaluates a channel expression", usage_getvariablefull },
01666    { { "get", "option", NULL }, handle_getoption, "Stream file, prompt for DTMF, with timeout", usage_getoption },
01667    { { "get", "variable", NULL }, handle_getvariable, "Gets a channel variable", usage_getvariable },
01668    { { "hangup", NULL }, handle_hangup, "Hangup the current channel", usage_hangup },
01669    { { "noop", NULL }, handle_noop, "Does nothing", usage_noop },
01670    { { "receive", "char", NULL }, handle_recvchar, "Receives one character from channels supporting it", usage_recvchar },
01671    { { "receive", "text", NULL }, handle_recvtext, "Receives text from channels supporting it", usage_recvtext },
01672    { { "record", "file", NULL }, handle_recordfile, "Records to a given file", usage_recordfile },
01673    { { "say", "alpha", NULL }, handle_sayalpha, "Says a given character string", usage_sayalpha },
01674    { { "say", "digits", NULL }, handle_saydigits, "Says a given digit string", usage_saydigits },
01675    { { "say", "number", NULL }, handle_saynumber, "Says a given number", usage_saynumber },
01676    { { "say", "phonetic", NULL }, handle_sayphonetic, "Says a given character string with phonetics", usage_sayphonetic },
01677    { { "say", "date", NULL }, handle_saydate, "Says a given date", usage_saydate },
01678    { { "say", "time", NULL }, handle_saytime, "Says a given time", usage_saytime },
01679    { { "say", "datetime", NULL }, handle_saydatetime, "Says a given time as specfied by the format given", usage_saydatetime },
01680    { { "send", "image", NULL }, handle_sendimage, "Sends images to channels supporting it", usage_sendimage },
01681    { { "send", "text", NULL }, handle_sendtext, "Sends text to channels supporting it", usage_sendtext },
01682    { { "set", "autohangup", NULL }, handle_autohangup, "Autohangup channel in some time", usage_autohangup },
01683    { { "set", "callerid", NULL }, handle_setcallerid, "Sets callerid for the current channel", usage_setcallerid },
01684    { { "set", "context", NULL }, handle_setcontext, "Sets channel context", usage_setcontext },
01685    { { "set", "extension", NULL }, handle_setextension, "Changes channel extension", usage_setextension },
01686    { { "set", "music", NULL }, handle_setmusic, "Enable/Disable Music on hold generator", usage_setmusic },
01687    { { "set", "priority", NULL }, handle_setpriority, "Set channel dialplan priority", usage_setpriority },
01688    { { "set", "variable", NULL }, handle_setvariable, "Sets a channel variable", usage_setvariable },
01689    { { "stream", "file", NULL }, handle_streamfile, "Sends audio file on channel", usage_streamfile },
01690    { { "control", "stream", "file", NULL }, handle_controlstreamfile, "Sends audio file on channel and allows the listner to control the stream", usage_controlstreamfile },
01691    { { "tdd", "mode", NULL }, handle_tddmode, "Toggles TDD mode (for the deaf)", usage_tddmode },
01692    { { "verbose", NULL }, handle_verbose, "Logs a message to the asterisk verbose log", usage_verbose },
01693    { { "wait", "for", "digit", NULL }, handle_waitfordigit, "Waits for a digit to be pressed", usage_waitfordigit },
01694 };
01695 
01696 static int help_workhorse(int fd, char *match[])
01697 {
01698    char fullcmd[80];
01699    char matchstr[80];
01700    int x;
01701    struct agi_command *e;
01702    if (match)
01703       ast_join(matchstr, sizeof(matchstr), match);
01704    for (x=0;x<sizeof(commands)/sizeof(commands[0]);x++) {
01705       e = &commands[x]; 
01706       if (!e->cmda[0])
01707          break;
01708       /* Hide commands that start with '_' */
01709       if ((e->cmda[0])[0] == '_')
01710          continue;
01711       ast_join(fullcmd, sizeof(fullcmd), e->cmda);
01712       if (match && strncasecmp(matchstr, fullcmd, strlen(matchstr)))
01713          continue;
01714       ast_cli(fd, "%20.20s   %s\n", fullcmd, e->summary);
01715    }
01716    return 0;
01717 }
01718 
01719 int ast_agi_register(agi_command *agi)
01720 {
01721    int x;
01722    for (x=0; x<MAX_COMMANDS - 1; x++) {
01723       if (commands[x].cmda[0] == agi->cmda[0]) {
01724          ast_log(LOG_WARNING, "Command already registered!\n");
01725          return -1;
01726       }
01727    }
01728    for (x=0; x<MAX_COMMANDS - 1; x++) {
01729       if (!commands[x].cmda[0]) {
01730          commands[x] = *agi;
01731          return 0;
01732       }
01733    }
01734    ast_log(LOG_WARNING, "No more room for new commands!\n");
01735    return -1;
01736 }
01737 
01738 void ast_agi_unregister(agi_command *agi)
01739 {
01740    int x;
01741    for (x=0; x<MAX_COMMANDS - 1; x++) {
01742       if (commands[x].cmda[0] == agi->cmda[0]) {
01743          memset(&commands[x], 0, sizeof(agi_command));
01744       }
01745    }
01746 }
01747 
01748 static agi_command *find_command(char *cmds[], int exact)
01749 {
01750    int x;
01751    int y;
01752    int match;
01753 
01754    for (x=0; x < sizeof(commands) / sizeof(commands[0]); x++) {
01755       if (!commands[x].cmda[0])
01756          break;
01757       /* start optimistic */
01758       match = 1;
01759       for (y=0; match && cmds[y]; y++) {
01760          /* If there are no more words in the command (and we're looking for
01761             an exact match) or there is a difference between the two words,
01762             then this is not a match */
01763          if (!commands[x].cmda[y] && !exact)
01764             break;
01765          /* don't segfault if the next part of a command doesn't exist */
01766          if (!commands[x].cmda[y])
01767             return NULL;
01768          if (strcasecmp(commands[x].cmda[y], cmds[y]))
01769             match = 0;
01770       }
01771       /* If more words are needed to complete the command then this is not
01772          a candidate (unless we're looking for a really inexact answer  */
01773       if ((exact > -1) && commands[x].cmda[y])
01774          match = 0;
01775       if (match)
01776          return &commands[x];
01777    }
01778    return NULL;
01779 }
01780 
01781 
01782 static int parse_args(char *s, int *max, char *argv[])
01783 {
01784    int x=0;
01785    int quoted=0;
01786    int escaped=0;
01787    int whitespace=1;
01788    char *cur;
01789 
01790    cur = s;
01791    while(*s) {
01792       switch(*s) {
01793       case '"':
01794          /* If it's escaped, put a literal quote */
01795          if (escaped) 
01796             goto normal;
01797          else 
01798             quoted = !quoted;
01799          if (quoted && whitespace) {
01800             /* If we're starting a quote, coming off white space start a new word, too */
01801             argv[x++] = cur;
01802             whitespace=0;
01803          }
01804          escaped = 0;
01805       break;
01806       case ' ':
01807       case '\t':
01808          if (!quoted && !escaped) {
01809             /* If we're not quoted, mark this as whitespace, and
01810                end the previous argument */
01811             whitespace = 1;
01812             *(cur++) = '\0';
01813          } else
01814             /* Otherwise, just treat it as anything else */ 
01815             goto normal;
01816          break;
01817       case '\\':
01818          /* If we're escaped, print a literal, otherwise enable escaping */
01819          if (escaped) {
01820             goto normal;
01821          } else {
01822             escaped=1;
01823          }
01824          break;
01825       default:
01826 normal:
01827          if (whitespace) {
01828             if (x >= MAX_ARGS -1) {
01829                ast_log(LOG_WARNING, "Too many arguments, truncating\n");
01830                break;
01831             }
01832             /* Coming off of whitespace, start the next argument */
01833             argv[x++] = cur;
01834             whitespace=0;
01835          }
01836          *(cur++) = *s;
01837          escaped=0;
01838       }
01839       s++;
01840    }
01841    /* Null terminate */
01842    *(cur++) = '\0';
01843    argv[x] = NULL;
01844    *max = x;
01845    return 0;
01846 }
01847 
01848 static int agi_handle_command(struct ast_channel *chan, AGI *agi, char *buf)
01849 {
01850    char *argv[MAX_ARGS];
01851    int argc = MAX_ARGS;
01852    int res;
01853    agi_command *c;
01854 
01855    parse_args(buf, &argc, argv);
01856    c = find_command(argv, 0);
01857    if (c) {
01858       res = c->handler(chan, agi, argc, argv);
01859       switch(res) {
01860       case RESULT_SHOWUSAGE:
01861          fdprintf(agi->fd, "520-Invalid command syntax.  Proper usage follows:\n");
01862          fdprintf(agi->fd, c->usage);
01863          fdprintf(agi->fd, "520 End of proper usage.\n");
01864          break;
01865       case AST_PBX_KEEPALIVE:
01866          /* We've been asked to keep alive, so do so */
01867          return AST_PBX_KEEPALIVE;
01868          break;
01869       case RESULT_FAILURE:
01870          /* They've already given the failure.  We've been hung up on so handle this
01871             appropriately */
01872          return -1;
01873       }
01874    } else {
01875       fdprintf(agi->fd, "510 Invalid or unknown command\n");
01876    }
01877    return 0;
01878 }
01879 static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi, int pid, int *status, int dead)
01880 {
01881    struct ast_channel *c;
01882    int outfd;
01883    int ms;
01884    enum agi_result returnstatus = AGI_RESULT_SUCCESS;
01885    struct ast_frame *f;
01886    struct ast_frame fr;
01887    char buf[AGI_BUF_LEN];
01888    char audiobuf[AGI_BUF_LEN];
01889    char *res = NULL;
01890    int audiobytes;
01891    int fds[2];
01892    int enhanced = 0;
01893    FILE *readf;
01894    /* how many times we'll retry if ast_waitfor_nandfs will return without either 
01895      channel or file descriptor in case select is interrupted by a system call (EINTR) */
01896    int retry = AGI_NANDFS_RETRY;
01897 
01898    if (!(readf = fdopen(agi->ctrl, "r"))) {
01899       ast_log(LOG_WARNING, "Unable to fdopen file descriptor\n");
01900       if (pid > -1)
01901          kill(pid, SIGHUP);
01902       close(agi->ctrl);
01903       return AGI_RESULT_FAILURE;
01904    }
01905    setlinebuf(readf);
01906    if (agi->audio > -1) {
01907        enhanced = 1;
01908    }
01909    if (agi->audio_in > -1) {
01910        enhanced++;
01911    }
01912    setup_env(chan, request, agi->fd, enhanced);
01913    fds[0] = agi->ctrl;
01914    fds[1] = agi->audio_in;
01915    for (;;) {
01916       ms = -1;
01917       if (agi->audio_in > -1) {
01918           c = ast_waitfor_nandfds(&chan, dead ? 0 : 1, fds, 2, NULL, &outfd, &ms);
01919       } else {
01920           c = ast_waitfor_nandfds(&chan, dead ? 0 : 1, &agi->ctrl, 1, NULL, &outfd, &ms);
01921       }
01922       if (c) {
01923          retry = AGI_NANDFS_RETRY;
01924          /* Idle the channel until we get a command */
01925          f = ast_read(c);
01926          if (!f) {
01927             ast_log(LOG_DEBUG, "%s hungup\n", chan->name);
01928             returnstatus = AGI_RESULT_HANGUP;
01929             break;
01930          } else {
01931             /* If it's voice, write it to the audio pipe */
01932             if ((agi->audio > -1) && (f->frametype == AST_FRAME_VOICE)) {
01933                /* Write, ignoring errors */
01934                write(agi->audio, f->data, f->datalen);
01935             }
01936             ast_frfree(f);
01937          }
01938       } else if (outfd > -1) {
01939           if ((agi->audio_in > -1) && (outfd == agi->audio_in)) {
01940          audiobytes = read(agi->audio_in, audiobuf, sizeof(audiobuf));
01941          if (audiobytes > 0) {
01942              fr.frametype = AST_FRAME_VOICE;
01943              fr.subclass = AST_FORMAT_SLINEAR;
01944              fr.datalen = audiobytes;
01945              fr.data = audiobuf;
01946              ast_write(chan, &fr);
01947          }
01948           } else {
01949          size_t len = sizeof(buf);
01950          size_t buflen = 0;
01951 
01952          retry = AGI_NANDFS_RETRY;
01953          buf[0] = '\0';
01954 
01955          while (buflen < (len - 1)) {
01956             res = fgets(buf + buflen, len, readf);
01957             if (feof(readf)) 
01958                break;
01959             if (ferror(readf) && ((errno != EINTR) && (errno != EAGAIN))) 
01960                break;
01961             if (res != NULL && !agi->fast)
01962                break;
01963             buflen = strlen(buf);
01964             if (buflen && buf[buflen - 1] == '\n')
01965                break;
01966             len -= buflen;
01967             if (agidebug)
01968                ast_verbose( "AGI Rx << temp buffer %s - errno %s\n", buf, strerror(errno));
01969          }
01970 
01971          if (!buf[0]) {
01972             /* Program terminated */
01973             if (returnstatus)
01974                returnstatus = -1;
01975             if (option_verbose > 2) 
01976                ast_verbose(VERBOSE_PREFIX_3 "AGI Script %s completed, returning %d\n", request, returnstatus);
01977             if (pid > 0)
01978                waitpid(pid, status, 0);
01979             /* No need to kill the pid anymore, since they closed us */
01980             pid = -1;
01981             break;
01982          }
01983 
01984          /* Special case for inability to execute child process */
01985          if (*buf && strncasecmp(buf, "failure", 7) == 0) {
01986             returnstatus = AGI_RESULT_FAILURE;
01987             break;
01988          }
01989 
01990          /* get rid of trailing newline, if any */
01991          if (*buf && buf[strlen(buf) - 1] == '\n')
01992             buf[strlen(buf) - 1] = 0;
01993          if (agidebug)
01994             ast_verbose("AGI Rx << %s\n", buf);
01995          returnstatus |= agi_handle_command(chan, agi, buf);
01996          /* If the handle_command returns -1, we need to stop */
01997          if ((returnstatus < 0) || (returnstatus == AST_PBX_KEEPALIVE)) {
01998             break;
01999          }
02000           }
02001       } else {
02002          if (--retry <= 0) {
02003             ast_log(LOG_WARNING, "No channel, no fd?\n");
02004             returnstatus = AGI_RESULT_FAILURE;
02005             break;
02006          }
02007       }
02008    }
02009    /* Notify process */
02010    if (pid > -1) {
02011       const char *sighup = pbx_builtin_getvar_helper(chan, "AGISIGHUP");
02012       if (ast_strlen_zero(sighup) || !ast_false(sighup)) {
02013          if (kill(pid, SIGHUP)) {
02014             ast_log(LOG_WARNING, "unable to send SIGHUP to AGI process %d: %s\n", pid, strerror(errno));
02015          } else { /* Give the process a chance to die */
02016             usleep(1);
02017          }
02018       }
02019       waitpid(pid, status, WNOHANG);
02020    }
02021    fclose(readf);
02022    return returnstatus;
02023 }
02024 
02025 static int handle_showagi(int fd, int argc, char *argv[])
02026 {
02027    struct agi_command *e;
02028    char fullcmd[80];
02029    if ((argc < 2))
02030       return RESULT_SHOWUSAGE;
02031    if (argc > 2) {
02032       e = find_command(argv + 2, 1);
02033       if (e) 
02034          ast_cli(fd, e->usage);
02035       else {
02036          if (find_command(argv + 2, -1)) {
02037             return help_workhorse(fd, argv + 1);
02038          } else {
02039             ast_join(fullcmd, sizeof(fullcmd), argv+1);
02040             ast_cli(fd, "No such command '%s'.\n", fullcmd);
02041          }
02042       }
02043    } else {
02044       return help_workhorse(fd, NULL);
02045    }
02046    return RESULT_SUCCESS;
02047 }
02048 
02049 static int handle_agidumphtml(int fd, int argc, char *argv[])
02050 {
02051    struct agi_command *e;
02052    char fullcmd[80];
02053    int x;
02054    FILE *htmlfile;
02055 
02056    if ((argc < 3))
02057       return RESULT_SHOWUSAGE;
02058 
02059    if (!(htmlfile = fopen(argv[2], "wt"))) {
02060       ast_cli(fd, "Could not create file '%s'\n", argv[2]);
02061       return RESULT_SHOWUSAGE;
02062    }
02063 
02064    fprintf(htmlfile, "<HTML>\n<HEAD>\n<TITLE>AGI Commands</TITLE>\n</HEAD>\n");
02065    fprintf(htmlfile, "<BODY>\n<CENTER><B><H1>AGI Commands</H1></B></CENTER>\n\n");
02066 
02067 
02068    fprintf(htmlfile, "<TABLE BORDER=\"0\" CELLSPACING=\"10\">\n");
02069 
02070    for (x=0;x<sizeof(commands)/sizeof(commands[0]);x++) {
02071       char *stringp, *tempstr;
02072 
02073       e = &commands[x]; 
02074       if (!e->cmda[0])  /* end ? */
02075          break;
02076       /* Hide commands that start with '_' */
02077       if ((e->cmda[0])[0] == '_')
02078          continue;
02079       ast_join(fullcmd, sizeof(fullcmd), e->cmda);
02080 
02081       fprintf(htmlfile, "<TR><TD><TABLE BORDER=\"1\" CELLPADDING=\"5\" WIDTH=\"100%%\">\n");
02082       fprintf(htmlfile, "<TR><TH ALIGN=\"CENTER\"><B>%s - %s</B></TH></TR>\n", fullcmd,e->summary);
02083 
02084       stringp=e->usage;
02085       tempstr = strsep(&stringp, "\n");
02086 
02087       fprintf(htmlfile, "<TR><TD ALIGN=\"CENTER\">%s</TD></TR>\n", tempstr);
02088       
02089       fprintf(htmlfile, "<TR><TD ALIGN=\"CENTER\">\n");
02090       while ((tempstr = strsep(&stringp, "\n")) != NULL)
02091          fprintf(htmlfile, "%s<BR>\n",tempstr);
02092       fprintf(htmlfile, "</TD></TR>\n");
02093       fprintf(htmlfile, "</TABLE></TD></TR>\n\n");
02094 
02095    }
02096 
02097    fprintf(htmlfile, "</TABLE>\n</BODY>\n</HTML>\n");
02098    fclose(htmlfile);
02099    ast_cli(fd, "AGI HTML Commands Dumped to: %s\n", argv[2]);
02100    return RESULT_SUCCESS;
02101 }
02102 
02103 static int agi_exec_full(struct ast_channel *chan, void *data, int enhanced, int dead)
02104 {
02105    enum agi_result res;
02106    struct ast_module_user *u;
02107    char *argv[MAX_ARGS];
02108    char buf[AGI_BUF_LEN] = "";
02109    char *tmp = (char *)buf;
02110    int argc = 0;
02111    int fds[2];
02112    int efd = -1;
02113    int efd2 = -1;
02114    int pid;
02115         char *stringp;
02116    AGI agi;
02117 
02118    if (ast_strlen_zero(data)) {
02119       ast_log(LOG_WARNING, "AGI requires an argument (script)\n");
02120       return -1;
02121    }
02122    ast_copy_string(buf, data, sizeof(buf));
02123 
02124    memset(&agi, 0, sizeof(agi));
02125         while ((stringp = strsep(&tmp, "|")) && argc < MAX_ARGS-1)
02126       argv[argc++] = stringp;
02127    argv[argc] = NULL;
02128 
02129    u = ast_module_user_add(chan);
02130 #if 0
02131     /* Answer if need be */
02132         if (chan->_state != AST_STATE_UP) {
02133       if (ast_answer(chan)) {
02134          LOCAL_USER_REMOVE(u);
02135          return -1;
02136       }
02137    }
02138 #endif
02139    ast_replace_sigchld();
02140    res = launch_script(argv[0], argv, fds, enhanced ? &efd : NULL, (enhanced == 2) ? &efd2 : NULL, &pid);
02141    if (res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) {
02142       int status = 0;
02143       agi.fd = fds[1];
02144       agi.ctrl = fds[0];
02145       agi.audio = efd;
02146       agi.audio_in = efd2;
02147       agi.fast = (res == AGI_RESULT_SUCCESS_FAST) ? 1 : 0;
02148       res = run_agi(chan, argv[0], &agi, pid, &status, dead);
02149       /* If the fork'd process returns non-zero, set AGISTATUS to FAILURE */
02150       if ((res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) && status)
02151          res = AGI_RESULT_FAILURE;
02152       if (fds[1] != fds[0])
02153          close(fds[1]);
02154       if (efd > -1)
02155          close(efd);
02156       if (efd2 > -1)
02157          close(efd2);
02158    }
02159    ast_unreplace_sigchld();
02160    ast_module_user_remove(u);
02161 
02162    switch (res) {
02163    case AGI_RESULT_SUCCESS:
02164    case AGI_RESULT_SUCCESS_FAST:
02165       pbx_builtin_setvar_helper(chan, "AGISTATUS", "SUCCESS");
02166       break;
02167    case AGI_RESULT_FAILURE:
02168       pbx_builtin_setvar_helper(chan, "AGISTATUS", "FAILURE");
02169       break;
02170    case AGI_RESULT_HANGUP:
02171       pbx_builtin_setvar_helper(chan, "AGISTATUS", "HANGUP");
02172       return -1;
02173    }
02174 
02175    return 0;
02176 }
02177 
02178 static int agi_exec(struct ast_channel *chan, void *data)
02179 {
02180    if (chan->_softhangup)
02181       ast_log(LOG_WARNING, "If you want to run AGI on hungup channels you should use DeadAGI!\n");
02182    return agi_exec_full(chan, data, 0, 0);
02183 }
02184 
02185 static int eagi_exec(struct ast_channel *chan, void *data)
02186 {
02187    int readformat;
02188    int res;
02189 
02190    if (chan->_softhangup)
02191       ast_log(LOG_WARNING, "If you want to run AGI on hungup channels you should use DeadAGI!\n");
02192    readformat = chan->readformat;
02193    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
02194       ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", chan->name);
02195       return -1;
02196    }
02197    res = agi_exec_full(chan, data, 1, 0);
02198    if (!res) {
02199       if (ast_set_read_format(chan, readformat)) {
02200          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(readformat));
02201       }
02202    }
02203    return res;
02204 }
02205 
02206 static int xagi_exec(struct ast_channel *chan, void *data)
02207 {
02208    int readformat, writeformat;
02209    int res;
02210 
02211    if (chan->_softhangup)
02212       ast_log(LOG_WARNING, "If you want to run AGI on hungup channels you should use DeadAGI!\n");
02213    readformat = chan->readformat;
02214    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
02215       ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", chan->name);
02216       return -1;
02217    }
02218    writeformat = chan->writeformat;
02219    if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
02220       ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", chan->name);
02221       return -1;
02222    }
02223    res = agi_exec_full(chan, data, 2, 0);
02224    if (!res) {
02225       if (ast_set_read_format(chan, readformat)) {
02226          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(readformat));
02227       }
02228       if (ast_set_write_format(chan, writeformat)) {
02229          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(writeformat));
02230       }
02231    }
02232    return res;
02233 }
02234 
02235 static int deadagi_exec(struct ast_channel *chan, void *data)
02236 {
02237    if (!ast_check_hangup(chan))
02238       ast_log(LOG_WARNING,"Running DeadAGI on a live channel will cause problems, please use AGI\n");
02239    return agi_exec_full(chan, data, 0, 1);
02240 }
02241 
02242 static char showagi_help[] =
02243 "Usage: agi show [topic]\n"
02244 "       When called with a topic as an argument, displays usage\n"
02245 "       information on the given command.  If called without a\n"
02246 "       topic, it provides a list of AGI commands.\n";
02247 
02248 
02249 static char dumpagihtml_help[] =
02250 "Usage: agi dumphtml <filename>\n"
02251 "  Dumps the agi command list in html format to given filename\n";
02252 
02253 static struct ast_cli_entry cli_show_agi_deprecated = {
02254    { "show", "agi", NULL },
02255    handle_showagi, NULL,
02256    NULL };
02257 
02258 static struct ast_cli_entry cli_dump_agihtml_deprecated = {
02259    { "dump", "agihtml", NULL },
02260    handle_agidumphtml, NULL,
02261    NULL };
02262 
02263 static struct ast_cli_entry cli_agi_no_debug_deprecated = {
02264    { "agi", "no", "debug", NULL },
02265    agi_no_debug_deprecated, NULL,
02266    NULL };
02267 
02268 static struct ast_cli_entry cli_agi[] = {
02269    { { "agi", "debug", NULL },
02270    agi_do_debug, "Enable AGI debugging",
02271    debug_usage },
02272 
02273    { { "agi", "debug", "off", NULL },
02274    agi_no_debug, "Disable AGI debugging",
02275    no_debug_usage, NULL, &cli_agi_no_debug_deprecated },
02276 
02277    { { "agi", "show", NULL },
02278    handle_showagi, "List AGI commands or specific help",
02279    showagi_help, NULL, &cli_show_agi_deprecated },
02280 
02281    { { "agi", "dumphtml", NULL },
02282    handle_agidumphtml, "Dumps a list of agi commands in html format",
02283    dumpagihtml_help, NULL, &cli_dump_agihtml_deprecated },
02284 };
02285 
02286 static int unload_module(void)
02287 {
02288    ast_module_user_hangup_all();
02289    ast_cli_unregister_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry));
02290    ast_unregister_application(xapp);
02291    ast_unregister_application(eapp);
02292    ast_unregister_application(deadapp);
02293    return ast_unregister_application(app);
02294 }
02295 
02296 static int load_module(void)
02297 {
02298    ast_cli_register_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry));
02299    ast_register_application(deadapp, deadagi_exec, deadsynopsis, descrip);
02300    ast_register_application(eapp, eagi_exec, esynopsis, descrip);
02301    ast_register_application(xapp, xagi_exec, xsynopsis, descrip);
02302    return ast_register_application(app, agi_exec, synopsis, descrip);
02303 }
02304 
02305 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Asterisk Gateway Interface (AGI)",
02306                 .load = load_module,
02307                 .unload = unload_module,
02308       );

Generated on Mon Jul 14 17:25:00 2008 for Asterisk - the Open Source PBX by  doxygen 1.5.1