Thu Dec 17 23:51:19 2009

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

Generated on Thu Dec 17 23:51:19 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7