2 * Asterisk -- A telephony toolkit for Linux.
4 * Asterisk Gateway Interface
6 * Copyright (C) 1999, Mark Spencer
8 * Mark Spencer <markster@linux-support.net>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 #include <sys/types.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/module.h>
20 #include <asterisk/astdb.h>
31 #include <asterisk/cli.h>
32 #include <asterisk/logger.h>
33 #include <asterisk/options.h>
34 #include <asterisk/image.h>
35 #include <asterisk/say.h>
36 #include <asterisk/app.h>
37 #include <asterisk/dsp.h>
38 #include <asterisk/musiconhold.h>
39 #include <asterisk/utils.h>
40 #include <asterisk/lock.h>
41 #include <asterisk/agi.h>
42 #include "../asterisk.h"
43 #include "../astconf.h"
46 #define MAX_COMMANDS 128
48 /* Recycle some stuff from the CLI interface */
49 #define fdprintf ast_cli
51 static char *tdesc = "Asterisk Gateway Interface (AGI)";
53 static char *app = "AGI";
55 static char *eapp = "EAGI";
57 static char *deadapp = "DeadAGI";
59 static char *synopsis = "Executes an AGI compliant application";
60 static char *esynopsis = "Executes an EAGI compliant application";
61 static char *deadsynopsis = "Executes AGI on a hungup channel";
63 static char *descrip =
64 " [E|Dead]AGI(command|args): Executes an Asterisk Gateway Interface compliant\n"
65 "program on a channel. AGI allows Asterisk to launch external programs\n"
66 "written in any language to control a telephony channel, play audio,\n"
67 "read DTMF digits, etc. by communicating with the AGI protocol on stdin\n"
69 "Returns -1 on hangup (except for DeadAGI) or if application requested\n"
70 " hangup, or 0 on non-hangup exit. \n"
71 "Using 'EAGI' provides enhanced AGI, with incoming audio available out of band"
72 "on file descriptor 3\n\n"
73 "Use the CLI command 'show agi' to list available agi commands\n";
80 #define TONE_BLOCK_SIZE 200
82 static int launch_script(char *script, char *argv[], int *fds, int *efd, int *opid)
91 if (script[0] != '/') {
92 snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_AGI_DIR, script);
96 ast_log(LOG_WARNING, "Unable to create toast pipe: %s\n",strerror(errno));
100 ast_log(LOG_WARNING, "unable to create fromast pipe: %s\n", strerror(errno));
107 ast_log(LOG_WARNING, "unable to create audio pipe: %s\n", strerror(errno));
114 res = fcntl(audio[1], F_GETFL);
116 res = fcntl(audio[1], F_SETFL, res | O_NONBLOCK);
118 ast_log(LOG_WARNING, "unable to set audio pipe parameters: %s\n", strerror(errno));
130 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
134 /* Redirect stdin and out, provide enhanced audio channel if desired */
135 dup2(fromast[0], STDIN_FILENO);
136 dup2(toast[1], STDOUT_FILENO);
138 dup2(audio[0], STDERR_FILENO + 1);
140 close(STDERR_FILENO + 1);
142 /* Close everything but stdin/out/error */
143 for (x=STDERR_FILENO + 2;x<1024;x++)
147 /* Can't use ast_log since FD's are closed */
148 fprintf(stderr, "Failed to execute '%s': %s\n", script, strerror(errno));
151 if (option_verbose > 2)
152 ast_verbose(VERBOSE_PREFIX_3 "Launched AGI Script %s\n", script);
158 /* close what we're not using in the parent */
172 static void setup_env(struct ast_channel *chan, char *request, int fd, int enhanced)
174 /* Print initial environment, with agi_request always being the first
176 fdprintf(fd, "agi_request: %s\n", request);
177 fdprintf(fd, "agi_channel: %s\n", chan->name);
178 fdprintf(fd, "agi_language: %s\n", chan->language);
179 fdprintf(fd, "agi_type: %s\n", chan->type);
180 fdprintf(fd, "agi_uniqueid: %s\n", chan->uniqueid);
183 fdprintf(fd, "agi_callerid: %s\n", chan->callerid ? chan->callerid : "unknown");
184 fdprintf(fd, "agi_dnid: %s\n", chan->dnid ? chan->dnid : "unknown");
185 fdprintf(fd, "agi_rdnis: %s\n", chan->rdnis ? chan->rdnis : "unknown");
187 /* Context information */
188 fdprintf(fd, "agi_context: %s\n", chan->context);
189 fdprintf(fd, "agi_extension: %s\n", chan->exten);
190 fdprintf(fd, "agi_priority: %d\n", chan->priority);
191 fdprintf(fd, "agi_enhanced: %s\n", enhanced ? "1.0" : "0.0");
193 /* User information */
194 fdprintf(fd, "agi_accountcode: %s\n", chan->accountcode ? chan->accountcode : "");
196 /* End with empty return */
200 static int handle_answer(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
204 if (chan->_state != AST_STATE_UP) {
205 /* Answer the chan */
206 res = ast_answer(chan);
208 fdprintf(agi->fd, "200 result=%d\n", res);
210 return RESULT_SUCCESS;
212 return RESULT_FAILURE;
215 static int handle_waitfordigit(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
220 return RESULT_SHOWUSAGE;
221 if (sscanf(argv[3], "%i", &to) != 1)
222 return RESULT_SHOWUSAGE;
223 res = ast_waitfordigit_full(chan, to, agi->audio, agi->ctrl);
224 fdprintf(agi->fd, "200 result=%d\n", res);
226 return RESULT_SUCCESS;
228 return RESULT_FAILURE;
231 static int handle_sendtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
235 return RESULT_SHOWUSAGE;
236 /* At the moment, the parser (perhaps broken) returns with
237 the last argument PLUS the newline at the end of the input
238 buffer. This probably needs to be fixed, but I wont do that
239 because other stuff may break as a result. The right way
240 would probably be to strip off the trailing newline before
241 parsing, then here, add a newline at the end of the string
242 before sending it to ast_sendtext --DUDE */
243 res = ast_sendtext(chan, argv[2]);
244 fdprintf(agi->fd, "200 result=%d\n", res);
246 return RESULT_SUCCESS;
248 return RESULT_FAILURE;
251 static int handle_recvchar(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
255 return RESULT_SHOWUSAGE;
256 res = ast_recvchar(chan,atoi(argv[2]));
258 fdprintf(agi->fd, "200 result=%d (timeout)\n", res);
259 return RESULT_SUCCESS;
262 fdprintf(agi->fd, "200 result=%d\n", res);
263 return RESULT_SUCCESS;
266 fdprintf(agi->fd, "200 result=%d (hangup)\n", res);
267 return RESULT_FAILURE;
271 static int handle_tddmode(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
275 return RESULT_SHOWUSAGE;
276 if (!strncasecmp(argv[2],"on",2)) x = 1; else x = 0;
277 if (!strncasecmp(argv[2],"mate",4)) x = 2;
278 if (!strncasecmp(argv[2],"tdd",3)) x = 1;
279 res = ast_channel_setoption(chan,AST_OPTION_TDD,&x,sizeof(char),0);
280 fdprintf(agi->fd, "200 result=%d\n", res);
282 return RESULT_SUCCESS;
284 return RESULT_FAILURE;
287 static int handle_sendimage(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
291 return RESULT_SHOWUSAGE;
292 res = ast_send_image(chan, argv[2]);
293 if (!ast_check_hangup(chan))
295 fdprintf(agi->fd, "200 result=%d\n", res);
297 return RESULT_SUCCESS;
299 return RESULT_FAILURE;
302 static int handle_streamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
305 struct ast_filestream *fs;
306 long sample_offset = 0;
310 return RESULT_SHOWUSAGE;
312 return RESULT_SHOWUSAGE;
313 if ((argc > 4) && (sscanf(argv[4], "%ld", &sample_offset) != 1))
314 return RESULT_SHOWUSAGE;
316 fs = ast_openstream(chan, argv[2], chan->language);
318 fdprintf(agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset);
319 ast_log(LOG_WARNING, "Unable to open %s\n", argv[2]);
320 return RESULT_FAILURE;
322 ast_seekstream(fs, 0, SEEK_END);
323 max_length = ast_tellstream(fs);
324 ast_seekstream(fs, sample_offset, SEEK_SET);
325 res = ast_applystream(chan, fs);
326 res = ast_playstream(fs);
328 fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset);
330 return RESULT_SHOWUSAGE;
332 return RESULT_FAILURE;
334 res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl);
335 /* this is to check for if ast_waitstream closed the stream, we probably are at
336 * the end of the stream, return that amount, else check for the amount */
337 sample_offset = (chan->stream)?ast_tellstream(fs):max_length;
338 ast_stopstream(chan);
340 /* Stop this command, don't print a result line, as there is a new command */
341 return RESULT_SUCCESS;
343 fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset);
345 return RESULT_SUCCESS;
347 return RESULT_FAILURE;
350 /*--- handle_saynumber: Say number in various language syntaxes ---*/
351 /* Need to add option for gender here as well. Coders wanted */
352 /* While waiting, we're sending a (char *) NULL. */
353 static int handle_saynumber(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
358 return RESULT_SHOWUSAGE;
359 if (sscanf(argv[2], "%i", &num) != 1)
360 return RESULT_SHOWUSAGE;
361 res = ast_say_number_full(chan, num, argv[3], chan->language, (char *) NULL, agi->audio, agi->ctrl);
363 return RESULT_SUCCESS;
364 fdprintf(agi->fd, "200 result=%d\n", res);
366 return RESULT_SUCCESS;
368 return RESULT_FAILURE;
371 static int handle_saydigits(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
377 return RESULT_SHOWUSAGE;
378 if (sscanf(argv[2], "%i", &num) != 1)
379 return RESULT_SHOWUSAGE;
381 res = ast_say_digit_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl);
382 if (res == 1) /* New command */
383 return RESULT_SUCCESS;
384 fdprintf(agi->fd, "200 result=%d\n", res);
386 return RESULT_SUCCESS;
388 return RESULT_FAILURE;
391 static int handle_saytime(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
396 return RESULT_SHOWUSAGE;
397 if (sscanf(argv[2], "%i", &num) != 1)
398 return RESULT_SHOWUSAGE;
399 res = ast_say_time(chan, num, argv[3], chan->language);
401 return RESULT_SUCCESS;
402 fdprintf(agi->fd, "200 result=%d\n", res);
404 return RESULT_SUCCESS;
406 return RESULT_FAILURE;
409 static int handle_sayphonetic(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
414 return RESULT_SHOWUSAGE;
416 res = ast_say_phonetic_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl);
417 if (res == 1) /* New command */
418 return RESULT_SUCCESS;
419 fdprintf(agi->fd, "200 result=%d\n", res);
421 return RESULT_SUCCESS;
423 return RESULT_FAILURE;
426 static int handle_getdata(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
434 return RESULT_SHOWUSAGE;
435 if (argc >= 4) timeout = atoi(argv[3]); else timeout = 0;
436 if (argc >= 5) max = atoi(argv[4]); else max = 1024;
437 res = ast_app_getdata_full(chan, argv[2], data, max, timeout, agi->audio, agi->ctrl);
438 if (res == 2) /* New command */
439 return RESULT_SUCCESS;
441 fdprintf(agi->fd, "200 result=%s (timeout)\n", data);
443 fdprintf(agi->fd, "200 result=-1\n");
445 fdprintf(agi->fd, "200 result=%s\n", data);
447 return RESULT_SUCCESS;
449 return RESULT_FAILURE;
452 static int handle_setcontext(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
456 return RESULT_SHOWUSAGE;
457 strncpy(chan->context, argv[2], sizeof(chan->context)-1);
458 fdprintf(agi->fd, "200 result=0\n");
459 return RESULT_SUCCESS;
462 static int handle_setextension(struct ast_channel *chan, AGI *agi, int argc, char **argv)
465 return RESULT_SHOWUSAGE;
466 strncpy(chan->exten, argv[2], sizeof(chan->exten)-1);
467 fdprintf(agi->fd, "200 result=0\n");
468 return RESULT_SUCCESS;
471 static int handle_setpriority(struct ast_channel *chan, AGI *agi, int argc, char **argv)
475 return RESULT_SHOWUSAGE;
476 if (sscanf(argv[2], "%i", &pri) != 1)
477 return RESULT_SHOWUSAGE;
478 chan->priority = pri - 1;
479 fdprintf(agi->fd, "200 result=0\n");
480 return RESULT_SUCCESS;
483 static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
485 struct ast_filestream *fs;
487 struct timeval tv, start;
488 long sample_offset = 0;
492 struct ast_dsp *sildet=NULL; /* silence detector dsp */
493 int totalsilence = 0;
495 int silence = 0; /* amount of silence to allow */
496 int gotsilence = 0; /* did we timeout for silence? */
497 char *silencestr=NULL;
501 /* XXX EAGI FIXME XXX */
504 return RESULT_SHOWUSAGE;
505 if (sscanf(argv[5], "%i", &ms) != 1)
506 return RESULT_SHOWUSAGE;
509 silencestr = strchr(argv[6],'s');
510 if ((argc > 7) && (!silencestr))
511 silencestr = strchr(argv[7],'s');
512 if ((argc > 8) && (!silencestr))
513 silencestr = strchr(argv[8],'s');
516 if (strlen(silencestr) > 2) {
517 if ((silencestr[0] == 's') && (silencestr[1] == '=')) {
521 silence = atoi(silencestr);
529 rfmt = chan->readformat;
530 res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
532 ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
535 sildet = ast_dsp_new();
537 ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
540 ast_dsp_set_threshold(sildet, 256);
543 /* backward compatibility, if no offset given, arg[6] would have been
544 * caught below and taken to be a beep, else if it is a digit then it is a
546 if ((argc >6) && (sscanf(argv[6], "%ld", &sample_offset) != 1) && (!strchr(argv[6], '=')))
547 res = ast_streamfile(chan, "beep", chan->language);
549 if ((argc > 7) && (!strchr(argv[7], '=')))
550 res = ast_streamfile(chan, "beep", chan->language);
553 res = ast_waitstream(chan, argv[4]);
555 fs = ast_writefile(argv[2], argv[3], NULL, O_CREAT | O_WRONLY | (sample_offset ? O_APPEND : 0), 0, 0644);
558 fdprintf(agi->fd, "200 result=%d (writefile)\n", res);
560 ast_dsp_free(sildet);
561 return RESULT_FAILURE;
565 ast_applystream(chan,fs);
566 /* really should have checks */
567 ast_seekstream(fs, sample_offset, SEEK_SET);
570 gettimeofday(&start, NULL);
571 gettimeofday(&tv, NULL);
572 while ((ms < 0) || (((tv.tv_sec - start.tv_sec) * 1000 + (tv.tv_usec - start.tv_usec)/1000) < ms)) {
573 res = ast_waitfor(chan, -1);
576 fdprintf(agi->fd, "200 result=%d (waitfor) endpos=%ld\n", res,sample_offset);
578 ast_dsp_free(sildet);
579 return RESULT_FAILURE;
583 fdprintf(agi->fd, "200 result=%d (hangup) endpos=%ld\n", 0, sample_offset);
586 ast_dsp_free(sildet);
587 return RESULT_FAILURE;
589 switch(f->frametype) {
591 if (strchr(argv[4], f->subclass)) {
592 /* This is an interrupting chracter */
593 sample_offset = ast_tellstream(fs);
594 fdprintf(agi->fd, "200 result=%d (dtmf) endpos=%ld\n", f->subclass, sample_offset);
598 ast_dsp_free(sildet);
599 return RESULT_SUCCESS;
602 case AST_FRAME_VOICE:
603 ast_writestream(fs, f);
604 /* this is a safe place to check progress since we know that fs
605 * is valid after a write, and it will then have our current
607 sample_offset = ast_tellstream(fs);
610 ast_dsp_silence(sildet, f, &dspsilence);
612 totalsilence = dspsilence;
616 if (totalsilence > silence) {
617 /* Ended happily with silence */
626 gettimeofday(&tv, NULL);
632 ast_stream_rewind(fs, silence-1000);
634 sample_offset = ast_tellstream(fs);
636 fdprintf(agi->fd, "200 result=%d (timeout) endpos=%ld\n", res, sample_offset);
639 fdprintf(agi->fd, "200 result=%d (randomerror) endpos=%ld\n", res, sample_offset);
642 res = ast_set_read_format(chan, rfmt);
644 ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name);
645 ast_dsp_free(sildet);
647 return RESULT_SUCCESS;
650 static int handle_autohangup(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
655 return RESULT_SHOWUSAGE;
656 if (sscanf(argv[2], "%d", &timeout) != 1)
657 return RESULT_SHOWUSAGE;
661 chan->whentohangup = time(NULL) + timeout;
663 chan->whentohangup = 0;
664 fdprintf(agi->fd, "200 result=0\n");
665 return RESULT_SUCCESS;
668 static int handle_hangup(struct ast_channel *chan, AGI *agi, int argc, char **argv)
670 struct ast_channel *c;
672 /* no argument: hangup the current channel */
673 ast_softhangup(chan,AST_SOFTHANGUP_EXPLICIT);
674 fdprintf(agi->fd, "200 result=1\n");
675 return RESULT_SUCCESS;
676 } else if (argc==2) {
677 /* one argument: look for info on the specified channel */
678 c = ast_channel_walk_locked(NULL);
680 if (strcasecmp(argv[1],c->name)==0) {
681 /* we have a matching channel */
682 ast_softhangup(c,AST_SOFTHANGUP_EXPLICIT);
683 fdprintf(agi->fd, "200 result=1\n");
684 ast_mutex_unlock(&c->lock);
685 return RESULT_SUCCESS;
687 ast_mutex_unlock(&c->lock);
688 c = ast_channel_walk_locked(c);
690 /* if we get this far no channel name matched the argument given */
691 fdprintf(agi->fd, "200 result=-1\n");
692 return RESULT_SUCCESS;
694 return RESULT_SHOWUSAGE;
698 static int handle_exec(struct ast_channel *chan, AGI *agi, int argc, char **argv)
704 return RESULT_SHOWUSAGE;
706 if (option_verbose > 2)
707 ast_verbose(VERBOSE_PREFIX_3 "AGI Script Executing Application: (%s) Options: (%s)\n", argv[1], argv[2]);
709 app = pbx_findapp(argv[1]);
712 res = pbx_exec(chan, app, argv[2], 1);
714 ast_log(LOG_WARNING, "Could not find application (%s)\n", argv[1]);
717 fdprintf(agi->fd, "200 result=%d\n", res);
722 static int handle_setcallerid(struct ast_channel *chan, AGI *agi, int argc, char **argv)
725 ast_set_callerid(chan, argv[2], 0);
727 fdprintf(agi->fd, "200 result=1\n");
728 return RESULT_SUCCESS;
731 static int handle_channelstatus(struct ast_channel *chan, AGI *agi, int argc, char **argv)
733 struct ast_channel *c;
735 /* no argument: supply info on the current channel */
736 fdprintf(agi->fd, "200 result=%d\n", chan->_state);
737 return RESULT_SUCCESS;
738 } else if (argc==3) {
739 /* one argument: look for info on the specified channel */
740 c = ast_channel_walk_locked(NULL);
742 if (strcasecmp(argv[2],c->name)==0) {
743 fdprintf(agi->fd, "200 result=%d\n", c->_state);
744 ast_mutex_unlock(&c->lock);
745 return RESULT_SUCCESS;
747 ast_mutex_unlock(&c->lock);
748 c = ast_channel_walk_locked(c);
750 /* if we get this far no channel name matched the argument given */
751 fdprintf(agi->fd, "200 result=-1\n");
752 return RESULT_SUCCESS;
754 return RESULT_SHOWUSAGE;
758 static int handle_setvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv)
761 pbx_builtin_setvar_helper(chan, argv[2], argv[3]);
763 fdprintf(agi->fd, "200 result=1\n");
764 return RESULT_SUCCESS;
767 static int handle_getvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv)
771 if ((tempstr = pbx_builtin_getvar_helper(chan, argv[2])))
772 fdprintf(agi->fd, "200 result=1 (%s)\n", tempstr);
774 fdprintf(agi->fd, "200 result=0\n");
776 return RESULT_SUCCESS;
779 static int handle_verbose(struct ast_channel *chan, AGI *agi, int argc, char **argv)
785 return RESULT_SHOWUSAGE;
788 sscanf(argv[2], "%d", &level);
792 prefix = VERBOSE_PREFIX_4;
795 prefix = VERBOSE_PREFIX_3;
798 prefix = VERBOSE_PREFIX_2;
802 prefix = VERBOSE_PREFIX_1;
806 if (level <= option_verbose)
807 ast_verbose("%s %s: %s\n", prefix, chan->data, argv[1]);
809 fdprintf(agi->fd, "200 result=1\n");
811 return RESULT_SUCCESS;
814 static int handle_dbget(struct ast_channel *chan, AGI *agi, int argc, char **argv)
819 return RESULT_SHOWUSAGE;
820 res = ast_db_get(argv[2], argv[3], tmp, sizeof(tmp));
822 fdprintf(agi->fd, "200 result=0\n");
824 fdprintf(agi->fd, "200 result=1 (%s)\n", tmp);
826 return RESULT_SUCCESS;
829 static int handle_dbput(struct ast_channel *chan, AGI *agi, int argc, char **argv)
833 return RESULT_SHOWUSAGE;
834 res = ast_db_put(argv[2], argv[3], argv[4]);
836 fdprintf(agi->fd, "200 result=0\n");
838 fdprintf(agi->fd, "200 result=1\n");
840 return RESULT_SUCCESS;
843 static int handle_dbdel(struct ast_channel *chan, AGI *agi, int argc, char **argv)
847 return RESULT_SHOWUSAGE;
848 res = ast_db_del(argv[2], argv[3]);
850 fdprintf(agi->fd, "200 result=0\n");
852 fdprintf(agi->fd, "200 result=1\n");
854 return RESULT_SUCCESS;
857 static int handle_dbdeltree(struct ast_channel *chan, AGI *agi, int argc, char **argv)
860 if ((argc < 3) || (argc > 4))
861 return RESULT_SHOWUSAGE;
863 res = ast_db_deltree(argv[2], argv[3]);
865 res = ast_db_deltree(argv[2], NULL);
868 fdprintf(agi->fd, "200 result=0\n");
870 fdprintf(agi->fd, "200 result=1\n");
871 return RESULT_SUCCESS;
874 static int handle_noop(struct ast_channel *chan, AGI *agi, int arg, char *argv[])
876 fdprintf(agi->fd, "200 result=0\n");
877 return RESULT_SUCCESS;
880 static int handle_setmusic(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
882 if (!strncasecmp(argv[2],"on",2)) {
884 ast_moh_start(chan, argv[3]);
886 ast_moh_start(chan, NULL);
888 if (!strncasecmp(argv[2],"off",3)) {
891 fdprintf(agi->fd, "200 result=0\n");
892 return RESULT_SUCCESS;
895 static char usage_setmusic[] =
896 " Usage: SET MUSIC ON <on|off> <class>\n"
897 " Enables/Disables the music on hold generator. If <class> is\n"
898 " not specified then the default music on hold class will be used.\n"
899 " Always returns 0\n";
901 static char usage_dbput[] =
902 " Usage: DATABASE PUT <family> <key> <value>\n"
903 " Adds or updates an entry in the Asterisk database for a\n"
904 " given family, key, and value.\n"
905 " Returns 1 if succesful, 0 otherwise\n";
907 static char usage_dbget[] =
908 " Usage: DATABASE GET <family> <key>\n"
909 " Retrieves an entry in the Asterisk database for a\n"
910 " given family and key.\n"
911 " Returns 0 if <key> is not set. Returns 1 if <key>\n"
912 " is set and returns the variable in parenthesis\n"
913 " example return code: 200 result=1 (testvariable)\n";
915 static char usage_dbdel[] =
916 " Usage: DATABASE DEL <family> <key>\n"
917 " Deletes an entry in the Asterisk database for a\n"
918 " given family and key.\n"
919 " Returns 1 if succesful, 0 otherwise\n";
921 static char usage_dbdeltree[] =
922 " Usage: DATABASE DELTREE <family> [keytree]\n"
923 " Deletes a family or specific keytree withing a family\n"
924 " in the Asterisk database.\n"
925 " Returns 1 if succesful, 0 otherwise\n";
927 static char usage_verbose[] =
928 " Usage: VERBOSE <message> <level>\n"
929 " Sends <message> to the console via verbose message system.\n"
930 " <level> is the the verbose level (1-4)\n"
931 " Always returns 1\n";
933 static char usage_getvariable[] =
934 " Usage: GET VARIABLE <variablename>\n"
935 " Returns 0 if <variablename> is not set. Returns 1 if <variablename>\n"
936 " is set and returns the variable in parenthesis\n"
937 " example return code: 200 result=1 (testvariable)\n";
939 static char usage_setvariable[] =
940 " Usage: SET VARIABLE <variablename> <value>\n";
942 static char usage_channelstatus[] =
943 " Usage: CHANNEL STATUS [<channelname>]\n"
944 " Returns the status of the specified channel.\n"
945 " If no channel name is given the returns the status of the\n"
946 " current channel.\n"
948 " 0 Channel is down and available\n"
949 " 1 Channel is down, but reserved\n"
950 " 2 Channel is off hook\n"
951 " 3 Digits (or equivalent) have been dialed\n"
952 " 4 Line is ringing\n"
953 " 5 Remote end is ringing\n"
957 static char usage_setcallerid[] =
958 " Usage: SET CALLERID <number>\n"
959 " Changes the callerid of the current channel.\n";
961 static char usage_exec[] =
962 " Usage: EXEC <application> <options>\n"
963 " Executes <application> with given <options>.\n"
964 " Returns whatever the application returns, or -2 on failure to find application\n";
966 static char usage_hangup[] =
967 " Usage: HANGUP [<channelname>]\n"
968 " Hangs up the specified channel.\n"
969 " If no channel name is given, hangs up the current channel\n";
971 static char usage_answer[] =
973 " Answers channel if not already in answer state. Returns -1 on\n"
974 " channel failure, or 0 if successful.\n";
976 static char usage_waitfordigit[] =
977 " Usage: WAIT FOR DIGIT <timeout>\n"
978 " Waits up to 'timeout' milliseconds for channel to receive a DTMF digit.\n"
979 " Returns -1 on channel failure, 0 if no digit is received in the timeout, or\n"
980 " the numerical value of the ascii of the digit if one is received. Use -1\n"
981 " for the timeout value if you desire the call to block indefinitely.\n";
983 static char usage_sendtext[] =
984 " Usage: SEND TEXT \"<text to send>\"\n"
985 " Sends the given text on a channel. Most channels do not support the\n"
986 " transmission of text. Returns 0 if text is sent, or if the channel does not\n"
987 " support text transmission. Returns -1 only on error/hangup. Text\n"
988 " consisting of greater than one word should be placed in quotes since the\n"
989 " command only accepts a single argument.\n";
991 static char usage_recvchar[] =
992 " Usage: RECEIVE CHAR <timeout>\n"
993 " Receives a character of text on a channel. Specify timeout to be the\n"
994 " maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n"
995 " do not support the reception of text. Returns the decimal value of the character\n"
996 " if one is received, or 0 if the channel does not support text reception. Returns\n"
997 " -1 only on error/hangup.\n";
999 static char usage_tddmode[] =
1000 " Usage: TDD MODE <on|off>\n"
1001 " Enable/Disable TDD transmission/reception on a channel. Returns 1 if\n"
1002 " successful, or 0 if channel is not TDD-capable.\n";
1004 static char usage_sendimage[] =
1005 " Usage: SEND IMAGE <image>\n"
1006 " Sends the given image on a channel. Most channels do not support the\n"
1007 " transmission of images. Returns 0 if image is sent, or if the channel does not\n"
1008 " support image transmission. Returns -1 only on error/hangup. Image names\n"
1009 " should not include extensions.\n";
1011 static char usage_streamfile[] =
1012 " Usage: STREAM FILE <filename> <escape digits> [sample offset]\n"
1013 " Send the given file, allowing playback to be interrupted by the given\n"
1014 " digits, if any. Use double quotes for the digits if you wish none to be\n"
1015 " permitted. If sample offset is provided then the audio will seek to sample\n"
1016 " offset before play starts. Returns 0 if playback completes without a digit\n"
1017 " being pressed, or the ASCII numerical value of the digit if one was pressed,\n"
1018 " or -1 on error or if the channel was disconnected. Remember, the file\n"
1019 " extension must not be included in the filename.\n";
1021 static char usage_saynumber[] =
1022 " Usage: SAY NUMBER <number> <escape digits>\n"
1023 " Say a given number, returning early if any of the given DTMF digits\n"
1024 " are received on the channel. Returns 0 if playback completes without a digit\n"
1025 " being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
1026 " -1 on error/hangup.\n";
1028 static char usage_saydigits[] =
1029 " Usage: SAY DIGITS <number> <escape digits>\n"
1030 " Say a given digit string, returning early if any of the given DTMF digits\n"
1031 " are received on the channel. Returns 0 if playback completes without a digit\n"
1032 " being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
1033 " -1 on error/hangup.\n";
1035 static char usage_saytime[] =
1036 " Usage: SAY TIME <time> <escape digits>\n"
1037 " Say a given time, returning early if any of the given DTMF digits are\n"
1038 " received on the channel. <time> is number of seconds elapsed since 00:00:00\n"
1039 " on January 1, 1970, Coordinated Universal Time (UTC). Returns 0 if playback\n"
1040 " completes without a digit being pressed, or the ASCII numerical value of the\n"
1041 " digit if one was pressed or -1 on error/hangup.\n";
1043 static char usage_sayphonetic[] =
1044 " Usage: SAY PHONETIC <string> <escape digits>\n"
1045 " Say a given character string with phonetics, returning early if any of the given DTMF digits\n"
1046 " are received on the channel. Returns 0 if playback completes without a digit\n"
1047 " being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
1048 " -1 on error/hangup.\n";
1050 static char usage_getdata[] =
1051 " Usage: GET DATA <file to be streamed> [timeout] [max digits]\n"
1052 " Stream the given file, and recieve DTMF data. Returns the digits recieved\n"
1053 "from the channel at the other end.\n";
1055 static char usage_setcontext[] =
1056 " Usage: SET CONTEXT <desired context>\n"
1057 " Sets the context for continuation upon exiting the application.\n";
1059 static char usage_setextension[] =
1060 " Usage: SET EXTENSION <new extension>\n"
1061 " Changes the extension for continuation upon exiting the application.\n";
1063 static char usage_setpriority[] =
1064 " Usage: SET PRIORITY <num>\n"
1065 " Changes the priority for continuation upon exiting the application.\n";
1067 static char usage_recordfile[] =
1068 " Usage: RECORD FILE <filename> <format> <escape digits> <timeout> [offset samples] [BEEP] [s=silence]\n"
1069 " Record to a file until a given dtmf digit in the sequence is received\n"
1070 " Returns -1 on hangup or error. The format will specify what kind of file\n"
1071 " will be recorded. The timeout is the maximum record time in milliseconds, or\n"
1072 " -1 for no timeout. Offset samples is optional, and if provided will seek to\n"
1073 " the offset without exceeding the end of the file. \"silence\" is the number\n"
1074 " of seconds of silence allowed before the function returns despite the\n"
1075 " lack of dtmf digits or reaching timeout. Silence value must be\n"
1076 " preceeded by \"s=\" and is optional.\n";
1079 static char usage_autohangup[] =
1080 " Usage: SET AUTOHANGUP <time>\n"
1081 " Cause the channel to automatically hangup at <time> seconds in the\n"
1082 "future. Of course it can be hungup before then as well. Setting to\n"
1083 "0 will cause the autohangup feature to be disabled on this channel.\n";
1085 static char usage_noop[] =
1089 static agi_command commands[MAX_COMMANDS] = {
1090 { { "answer", NULL }, handle_answer, "Asserts answer", usage_answer },
1091 { { "wait", "for", "digit", NULL }, handle_waitfordigit, "Waits for a digit to be pressed", usage_waitfordigit },
1092 { { "send", "text", NULL }, handle_sendtext, "Sends text to channels supporting it", usage_sendtext },
1093 { { "receive", "char", NULL }, handle_recvchar, "Receives text from channels supporting it", usage_recvchar },
1094 { { "tdd", "mode", NULL }, handle_tddmode, "Sends text to channels supporting it", usage_tddmode },
1095 { { "stream", "file", NULL }, handle_streamfile, "Sends audio file on channel", usage_streamfile },
1096 { { "send", "image", NULL }, handle_sendimage, "Sends images to channels supporting it", usage_sendimage },
1097 { { "say", "digits", NULL }, handle_saydigits, "Says a given digit string", usage_saydigits },
1098 { { "say", "number", NULL }, handle_saynumber, "Says a given number", usage_saynumber },
1099 { { "say", "phonetic", NULL }, handle_sayphonetic, "Says a given character string with phonetics", usage_sayphonetic },
1100 { { "say", "time", NULL }, handle_saytime, "Says a given time", usage_saytime },
1101 { { "get", "data", NULL }, handle_getdata, "Gets data on a channel", usage_getdata },
1102 { { "set", "context", NULL }, handle_setcontext, "Sets channel context", usage_setcontext },
1103 { { "set", "extension", NULL }, handle_setextension, "Changes channel extension", usage_setextension },
1104 { { "set", "priority", NULL }, handle_setpriority, "Prioritizes the channel", usage_setpriority },
1105 { { "record", "file", NULL }, handle_recordfile, "Records to a given file", usage_recordfile },
1106 { { "set", "autohangup", NULL }, handle_autohangup, "Autohangup channel in some time", usage_autohangup },
1107 { { "hangup", NULL }, handle_hangup, "Hangup the current channel", usage_hangup },
1108 { { "exec", NULL }, handle_exec, "Executes a given Application", usage_exec },
1109 { { "set", "callerid", NULL }, handle_setcallerid, "Sets callerid for the current channel", usage_setcallerid },
1110 { { "channel", "status", NULL }, handle_channelstatus, "Returns status of the connected channel", usage_channelstatus },
1111 { { "set", "variable", NULL }, handle_setvariable, "Sets a channel variable", usage_setvariable },
1112 { { "get", "variable", NULL }, handle_getvariable, "Gets a channel variable", usage_getvariable },
1113 { { "verbose", NULL }, handle_verbose, "Logs a message to the asterisk verbose log", usage_verbose },
1114 { { "database", "get", NULL }, handle_dbget, "Gets database value", usage_dbget },
1115 { { "database", "put", NULL }, handle_dbput, "Adds/updates database value", usage_dbput },
1116 { { "database", "del", NULL }, handle_dbdel, "Removes database key/value", usage_dbdel },
1117 { { "database", "deltree", NULL }, handle_dbdeltree, "Removes database keytree/value", usage_dbdeltree },
1118 { { "noop", NULL }, handle_noop, "Does nothing", usage_noop },
1119 { { "set", "music", NULL }, handle_setmusic, "Enable/Disable Music on hold generator", usage_setmusic }
1122 static void join(char *s, size_t len, char *w[])
1125 /* Join words into a string */
1130 for (x=0;w[x];x++) {
1132 strncat(s, " ", len - strlen(s) - 1);
1133 strncat(s, w[x], len - strlen(s) - 1);
1137 static int help_workhorse(int fd, char *match[])
1142 struct agi_command *e;
1144 join(matchstr, sizeof(matchstr), match);
1145 for (x=0;x<sizeof(commands)/sizeof(commands[0]);x++) {
1146 if (!commands[x].cmda[0]) break;
1149 join(fullcmd, sizeof(fullcmd), e->cmda);
1150 /* Hide commands that start with '_' */
1151 if (fullcmd[0] == '_')
1154 if (strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
1158 ast_cli(fd, "%20.20s %s\n", fullcmd, e->summary);
1163 int agi_register(agi_command *agi)
1166 for (x=0;x<MAX_COMMANDS - 1;x++) {
1167 if (commands[x].cmda[0] == agi->cmda[0]) {
1168 ast_log(LOG_WARNING, "Command already registered!\n");
1172 for (x=0;x<MAX_COMMANDS - 1;x++) {
1173 if (!commands[x].cmda[0]) {
1178 ast_log(LOG_WARNING, "No more room for new commands!\n");
1182 void agi_unregister(agi_command *agi)
1185 for (x=0;x<MAX_COMMANDS - 1;x++) {
1186 if (commands[x].cmda[0] == agi->cmda[0]) {
1187 memset(&commands[x], 0, sizeof(agi_command));
1192 static agi_command *find_command(char *cmds[], int exact)
1197 for (x=0;x < sizeof(commands) / sizeof(commands[0]);x++) {
1198 if (!commands[x].cmda[0]) break;
1199 /* start optimistic */
1201 for (y=0;match && cmds[y]; y++) {
1202 /* If there are no more words in the command (and we're looking for
1203 an exact match) or there is a difference between the two words,
1204 then this is not a match */
1205 if (!commands[x].cmda[y] && !exact)
1207 /* don't segfault if the next part of a command doesn't exist */
1208 if (!commands[x].cmda[y]) return NULL;
1209 if (strcasecmp(commands[x].cmda[y], cmds[y]))
1212 /* If more words are needed to complete the command then this is not
1213 a candidate (unless we're looking for a really inexact answer */
1214 if ((exact > -1) && commands[x].cmda[y])
1217 return &commands[x];
1223 static int parse_args(char *s, int *max, char *argv[])
1235 /* If it's escaped, put a literal quote */
1240 if (quoted && whitespace) {
1241 /* If we're starting a quote, coming off white space start a new word, too */
1249 if (!quoted && !escaped) {
1250 /* If we're not quoted, mark this as whitespace, and
1251 end the previous argument */
1255 /* Otherwise, just treat it as anything else */
1259 /* If we're escaped, print a literal, otherwise enable escaping */
1269 if (x >= MAX_ARGS -1) {
1270 ast_log(LOG_WARNING, "Too many arguments, truncating\n");
1273 /* Coming off of whitespace, start the next argument */
1282 /* Null terminate */
1289 static int agi_handle_command(struct ast_channel *chan, AGI *agi, char *buf)
1291 char *argv[MAX_ARGS];
1296 parse_args(buf, &argc, argv);
1299 for (x=0;x<argc;x++)
1300 fprintf(stderr, "Got Arg%d: %s\n", x, argv[x]); }
1302 c = find_command(argv, 0);
1304 res = c->handler(chan, agi, argc, argv);
1306 case RESULT_SHOWUSAGE:
1307 fdprintf(agi->fd, "520-Invalid command syntax. Proper usage follows:\n");
1308 fdprintf(agi->fd, c->usage);
1309 fdprintf(agi->fd, "520 End of proper usage.\n");
1311 case AST_PBX_KEEPALIVE:
1312 /* We've been asked to keep alive, so do so */
1313 return AST_PBX_KEEPALIVE;
1315 case RESULT_FAILURE:
1316 /* They've already given the failure. We've been hung up on so handle this
1321 fdprintf(agi->fd, "510 Invalid or unknown command\n");
1326 static int run_agi(struct ast_channel *chan, char *request, AGI *agi, int pid, int dead)
1328 struct ast_channel *c;
1331 int returnstatus = 0;
1332 struct ast_frame *f;
1335 /* how many times we'll retry if ast_waitfor_nandfs will return without either
1336 channel or file descriptor in case select is interrupted by a system call (EINTR) */
1339 if (!(readf = fdopen(agi->ctrl, "r"))) {
1340 ast_log(LOG_WARNING, "Unable to fdopen file descriptor\n");
1346 setup_env(chan, request, agi->fd, (agi->audio > -1));
1349 c = ast_waitfor_nandfds(&chan, dead ? 0 : 1, &agi->ctrl, 1, NULL, &outfd, &ms);
1352 /* Idle the channel until we get a command */
1355 ast_log(LOG_DEBUG, "%s hungup\n", chan->name);
1359 /* If it's voice, write it to the audio pipe */
1360 if ((agi->audio > -1) && (f->frametype == AST_FRAME_VOICE)) {
1361 /* Write, ignoring errors */
1362 write(agi->audio, f->data, f->datalen);
1366 } else if (outfd > -1) {
1368 if (!fgets(buf, sizeof(buf), readf)) {
1369 /* Program terminated */
1372 if (option_verbose > 2)
1373 ast_verbose(VERBOSE_PREFIX_3 "AGI Script %s completed, returning %d\n", request, returnstatus);
1374 /* No need to kill the pid anymore, since they closed us */
1378 /* get rid of trailing newline, if any */
1379 if (*buf && buf[strlen(buf) - 1] == '\n')
1380 buf[strlen(buf) - 1] = 0;
1382 returnstatus |= agi_handle_command(chan, agi, buf);
1383 /* If the handle_command returns -1, we need to stop */
1384 if ((returnstatus < 0) || (returnstatus == AST_PBX_KEEPALIVE)) {
1389 ast_log(LOG_WARNING, "No channel, no fd?\n");
1395 /* Notify process */
1399 return returnstatus;
1402 static int handle_showagi(int fd, int argc, char *argv[]) {
1403 struct agi_command *e;
1406 return RESULT_SHOWUSAGE;
1408 e = find_command(argv + 2, 1);
1410 ast_cli(fd, e->usage);
1412 if (find_command(argv + 2, -1)) {
1413 return help_workhorse(fd, argv + 1);
1415 join(fullcmd, sizeof(fullcmd), argv+1);
1416 ast_cli(fd, "No such command '%s'.\n", fullcmd);
1420 return help_workhorse(fd, NULL);
1422 return RESULT_SUCCESS;
1425 static int handle_dumpagihtml(int fd, int argc, char *argv[]) {
1426 struct agi_command *e;
1433 return RESULT_SHOWUSAGE;
1435 if (!(htmlfile = fopen(argv[2], "wt"))) {
1436 ast_cli(fd, "Could not create file '%s'\n", argv[2]);
1437 return RESULT_SHOWUSAGE;
1440 fprintf(htmlfile, "<HTML>\n<HEAD>\n<TITLE>AGI Commands</TITLE>\n</HEAD>\n");
1441 fprintf(htmlfile, "<BODY>\n<CENTER><B><H1>AGI Commands</H1></B></CENTER>\n\n");
1444 fprintf(htmlfile, "<TABLE BORDER=\"0\" CELLSPACING=\"10\">\n");
1446 for (x=0;x<sizeof(commands)/sizeof(commands[0]);x++) {
1448 if (!commands[x].cmda[0]) break;
1451 join(fullcmd, sizeof(fullcmd), e->cmda);
1452 /* Hide commands that start with '_' */
1453 if (fullcmd[0] == '_')
1456 fprintf(htmlfile, "<TR><TD><TABLE BORDER=\"1\" CELLPADDING=\"5\" WIDTH=\"100%%\">\n");
1457 fprintf(htmlfile, "<TR><TH ALIGN=\"CENTER\"><B>%s - %s</B></TD></TR>\n", fullcmd,e->summary);
1461 tempstr = strsep(&stringp, "\n");
1463 fprintf(htmlfile, "<TR><TD ALIGN=\"CENTER\">%s</TD></TR>\n", tempstr);
1465 fprintf(htmlfile, "<TR><TD ALIGN=\"CENTER\">\n");
1466 while ((tempstr = strsep(&stringp, "\n")) != NULL) {
1467 fprintf(htmlfile, "%s<BR>\n",tempstr);
1470 fprintf(htmlfile, "</TD></TR>\n");
1471 fprintf(htmlfile, "</TABLE></TD></TR>\n\n");
1475 fprintf(htmlfile, "</TABLE>\n</BODY>\n</HTML>\n");
1477 ast_cli(fd, "AGI HTML Commands Dumped to: %s\n", argv[2]);
1478 return RESULT_SUCCESS;
1481 static int agi_exec_full(struct ast_channel *chan, void *data, int enhanced, int dead)
1484 struct localuser *u;
1485 char *argv[MAX_ARGS];
1487 char *tmp = (char *)buf;
1494 if (!data || ast_strlen_zero(data)) {
1495 ast_log(LOG_WARNING, "AGI requires an argument (script)\n");
1498 strncpy(buf, data, sizeof(buf) - 1);
1500 memset(&agi, 0, sizeof(agi));
1501 while ((stringp = strsep(&tmp, "|"))) {
1502 argv[argc++] = stringp;
1508 /* Answer if need be */
1509 if (chan->_state != AST_STATE_UP) {
1510 if (ast_answer(chan)) {
1511 LOCAL_USER_REMOVE(u);
1516 res = launch_script(argv[0], argv, fds, enhanced ? &efd : NULL, &pid);
1521 res = run_agi(chan, argv[0], &agi, pid, dead);
1526 LOCAL_USER_REMOVE(u);
1530 static int agi_exec(struct ast_channel *chan, void *data)
1532 if (chan->_softhangup)
1533 ast_log(LOG_WARNING, "If you want to run AGI on hungup channels you should use DeadAGI!\n");
1534 return agi_exec_full(chan, data, 0, 0);
1537 static int eagi_exec(struct ast_channel *chan, void *data)
1541 if (chan->_softhangup)
1542 ast_log(LOG_WARNING, "If you want to run AGI on hungup channels you should use DeadAGI!\n");
1543 readformat = chan->readformat;
1544 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
1545 ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", chan->name);
1548 res = agi_exec_full(chan, data, 1, 0);
1550 if (ast_set_read_format(chan, readformat)) {
1551 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(readformat));
1557 static int deadagi_exec(struct ast_channel *chan, void *data)
1559 return agi_exec_full(chan, data, 0, 1);
1562 static char showagi_help[] =
1563 "Usage: show agi [topic]\n"
1564 " When called with a topic as an argument, displays usage\n"
1565 " information on the given command. If called without a\n"
1566 " topic, it provides a list of AGI commands.\n";
1569 static char dumpagihtml_help[] =
1570 "Usage: dump agihtml <filename>\n"
1571 " Dumps the agi command list in html format to given filename\n";
1573 static struct ast_cli_entry showagi =
1574 { { "show", "agi", NULL }, handle_showagi, "Show AGI commands or specific help", showagi_help };
1576 static struct ast_cli_entry dumpagihtml =
1577 { { "dump", "agihtml", NULL }, handle_dumpagihtml, "Dumps a list of agi command in html format", dumpagihtml_help };
1579 int unload_module(void)
1581 STANDARD_HANGUP_LOCALUSERS;
1582 ast_cli_unregister(&showagi);
1583 ast_cli_unregister(&dumpagihtml);
1584 ast_unregister_application(eapp);
1585 ast_unregister_application(deadapp);
1586 return ast_unregister_application(app);
1589 int load_module(void)
1591 ast_cli_register(&showagi);
1592 ast_cli_register(&dumpagihtml);
1593 ast_register_application(deadapp, deadagi_exec, deadsynopsis, descrip);
1594 ast_register_application(eapp, eagi_exec, esynopsis, descrip);
1595 return ast_register_application(app, agi_exec, synopsis, descrip);
1598 char *description(void)
1606 STANDARD_USECOUNT(res);
1612 return ASTERISK_GPL_KEY;