📄 cagi.c
字号:
// // Written by: David Cornewell (david.cornewell@gmail.com)// Date: 03-16-2005// // Purpose: This is a C library for interfacing with Asterisks' AGI. It is based on PHPAGI. // Actually, it is almost completely copied from it. I just changed the code to C.// The in depth documentation is the same.//// License: LGPL. //// MODS: // $Id: cagi.c,v 1.14 2006/11/13 16:14:19 dcornewell Exp $// $Log: cagi.c,v $// Revision 1.14 2006/11/13 16:14:19 dcornewell// Fixed bug in AGITool_sendcmd(); when parsing data from response it was getting// the length of the string inside the (). it did -1. it should have.//// Revision 1.13 2006/10/06 20:39:45 dcornewell// put checkes in Init for the return of fgets. Just in case the stdin pipe// gets lost, but app is blocking SIGPIPE//// Revision 1.12 2006/10/04 18:49:17 dcornewell// Changed AGITool_ListAddItem() to malloc field and value rather than use// static buffers with them. uses memory necessary and allows for more than 50// bytes each.//// Revision 1.11 2006/03/10 19:20:32 dcornewell// Fixed a bug when trying to get the status of a dead channel. if program handles// the SIGHUP and tries to clean up, it was checking the status of a channel// hoping to find out that it was down. would get SIGPIPE cause stdin/out/err are// not gone and would try to parse a NULL from fgets(stdin). checking for that// null now.//// also implemented some exec functions. basically wrappers that call exec with// something.//// Revision 1.10 2006/02/21 19:20:53 dcornewell// Checking value for null before doing anything with it.//// Revision 1.9 2005/10/04 16:36:27 dcornewell// Updated AGITool_exec_dial prototype. added check to make sure res->data isn't// over filled. would take 2048 bytes, but you never know//// Revision 1.8 2005/10/04 16:19:18 dcornewell// Fixed bug with exec dial. needed a \n//// Revision 1.7 2005/10/03 13:50:39 dcornewell// Added AGITool_get_variable2() that takes a dest variable. it will fill that// field with the variable. More self explainatory.//// Revision 1.6 2005/10/03 13:36:46 dcornewell// Fixed bug in send_cmd. I was parsing data from asterisk and I bombed a zero// into my buffer rather than res->result. this stopped the parsing and I never// filled res->data. Also added a raw field to the result structure that will// contain the raw data from asterisk. This would have helped some in debugging.//// Revision 1.5 2005/09/29 20:02:49 dcornewell// Added AGITool_exec_dial() will dial. Thanks to Raymond Chen for testing and// working on this.//// Revision 1.4 2005/07/07 15:15:33 dcornewell// Added a check for null in strim. Anything that stops core dumps. not likely,// but good to check.//// Revision 1.3 2005/06/05 23:31:09 dcornewell// Changed the command AUTOHANGUP to "SET AUTOHANGUP". I am not sure if I messed// this up, or if it just changed. hope it works//// Revision 1.2 2005/04/14 00:13:29 dcornewell// Changed License to LGPL. Not sure why, people just like it better//// Revision 1.1.1.1 2005/03/22 15:45:15 dcornewell// initial checkin of CAGI. Most needed features are implemented.//#include <stdio.h>#include <stdarg.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <ctype.h>#include "cagi.h"int FileExists(char *path){ struct stat file_stat; if(stat(path, &file_stat)) return 0; return 1;}int strim(char *src){ int i=0; if (src) { i=strlen(src)-1; while (i>0 && isspace(src[i])) { src[i]=0; i--; } } return i;}AGI_VAL_LIST *AGITool_ListAddItem(AGI_VAL_LIST *l, char *field, char *value){ AGI_VAL_LIST *newl; newl = (AGI_VAL_LIST *)malloc(sizeof(AGI_VAL_LIST)); if (newl) { memset(newl,0,sizeof(AGI_VAL_LIST)); if (field!=NULL) { newl->field = (char*)malloc(strlen(field)+1); strlcpy(newl->field,field,strlen(field)+1); } if (value!=NULL) { newl->value = (char*)malloc(strlen(value)+1); strlcpy(newl->value,value,strlen(value)+1); } newl->next=l; } return newl;}void AGITool_ListDestroy(AGI_VAL_LIST *l){ AGI_VAL_LIST *l2; while (l) { l2=l->next; free(l->field);l->field=NULL; free(l->value);l->value=NULL; free(l);l=NULL; l=l2; } return;}char *AGITool_ListGetVal(AGI_VAL_LIST *l, char *field){ while (l) { if (!strcmp(l->field, field)) { return l->value; } l=l->next; } return "";}int AGITool_Init(AGI_TOOLS *tool){ char buffer[1024], *field, *value; // open stdin tool->in = stdin; // open stdout tool->out = stdout; /* * Often contains any/all of the following: * agi_network - value is yes if this is a fastagi * agi_network_script - name of the script to execute * agi_request - name of agi script * agi_channel - current channel * agi_language - current language * agi_type - channel type (SIP, ZAP, IAX, ...) * agi_uniqueid - unique id based on unix time * agi_callerid - callerID string * agi_dnid - dialed number id * agi_rdnis - referring DNIS number * agi_context - current context * agi_extension - extension dialed * agi_priority - current priority * agi_enhanced - value is 1.0 if started as an EAGI script * agi_accountcode - set by SetAccount in the dialplan */ // read the request tool->agi_vars=tool->settings=NULL; if ( fgets(buffer,sizeof(buffer),stdin) ) { while(strcmp(buffer, "\n") != 0) { field=buffer; value=strchr(buffer,':'); if (value) { value[0]=0; value+=2; strim(value); tool->agi_vars = AGITool_ListAddItem(tool->agi_vars, field,value); } if ( !fgets(buffer,sizeof(buffer),stdin) ) { // if failed, may have lost pipe, but sigpipe is blocked. break; } } } // These could be configured in an ini... tool->settings = AGITool_ListAddItem(tool->settings, "tmpdir","/tmp/"); tool->settings = AGITool_ListAddItem(tool->settings, "festival_text2wave","text2wave"); tool->settings = AGITool_ListAddItem(tool->settings, "cepstral_swift","swift"); return 0;}void AGITool_Destroy(AGI_TOOLS *tool){ AGITool_ListDestroy(tool->agi_vars); AGITool_ListDestroy(tool->settings);}int AGITool_sendcmd(AGI_TOOLS *tool, AGI_CMD_RESULT *res, char *command, ...){ va_list ap; char buffer[4096], *str, *ptr; int count,ret; res->code=500; strlcpy(res->result,"-1",sizeof(res->result)); res->data[0]=0; // write command va_start(ap, command); ret = vfprintf(tool->out, command, ap); va_end(ap); fflush(tool->out); if (ret<0) { return 0; } // Read result. Occasionally, a command return a string followed by an extra new line. // When this happens, our script will ignore the new line, but it will still be in the // buffer. So, if we get a blank line, it is probably the result of a previous // command. We read until we get a valid result or asterisk hangs up. One offending // command is SEND TEXT. buffer[0]=0; str=buffer; for (count=0; str && !strlen(str) && count<5; count++) { str = fgets(buffer,sizeof(buffer),tool->in); } if (!str) { return atoi(res->result); } if(count >= 5) { return 0; } // // Let's save what asterisk sent in case caller needs raw data string. Good for debug too. // This will only do us good with single line responses though. // strlcpy(res->raw,buffer,sizeof(res->raw)); strim(buffer); // parse result res->code = atoi(str); str+=4; if(str[0] == '-') // we have a multiline response! Like Usage { char junkit[2048]; fgets(junkit,sizeof(junkit),tool->in); while (atoi(junkit) != res->code) { fgets(junkit,sizeof(junkit),tool->in); } } if(res->code != AGIRES_OK) // some sort of error { strlcpy(res->data, str,sizeof(res->data)); } else {// normal AGIRES_OK response ptr=strstr(str,"result="); if (ptr) { strlcpy(res->result, ptr+7, sizeof(res->result)); ptr=strchr(res->result,' '); if (ptr) { ptr[0]=0; }// res->result = atoi(strstr(str,"result=")+7); } if (strstr(str, "endpos=")) { res->endpos = atoul(strstr(str,"endpos=")+7); } if (strchr(str, '(')) { if (strchr(str, ')')) { int dlen=strchr(str, ')')-strchr(str,'('); if (dlen>sizeof(res->data)) dlen=sizeof(res->data); strlcpy(res->data, strchr(str,'(')+1, dlen); } else { strlcpy(res->data, strchr(str,'(')+1, sizeof(res->data)); } } } return atoi(res->result);}//****************************// Commands to be sent//****************************//// Answer channel if not already in answer state.//int AGITool_answer(AGI_TOOLS *tool, AGI_CMD_RESULT *res){ return AGITool_sendcmd(tool, res, "ANSWER\n");}//// Cause the channel to automatically hangup at $time seconds in the future.// If $time is 0 then the autohangup feature is disabled on this channel.//// If the channel is hungup prior to $time seconds, this setting has no effect.//// @param integer $time until automatic hangup// @return array, see evaluate for return information.//int AGITool_autohangup(AGI_TOOLS *tool, AGI_CMD_RESULT *res, int time){ return AGITool_sendcmd(tool, res, "SET AUTOHANGUP %d\n", time);}//// Get the status of the specified channel. If no channel name is specified, return the status of the current channel.//// @param string $channel//int AGITool_channel_status(AGI_TOOLS *tool, AGI_CMD_RESULT *res, char *channel){ int ret = AGITool_sendcmd(tool, res, "CHANNEL STATUS %s\n", channel); switch(atoi(res->result)) { case -1: snprintf(res->data, sizeof(res->data), "There is no channel that matches %s",channel); break; case AST_STATE_DOWN: snprintf(res->data, sizeof(res->data), "Channel is down and available"); break; case AST_STATE_RESERVED: snprintf(res->data, sizeof(res->data), "Channel is down, but reserved"); break; case AST_STATE_OFFHOOK: snprintf(res->data, sizeof(res->data), "Channel is off hook"); break; case AST_STATE_DIALING: snprintf(res->data, sizeof(res->data), "Digits (or equivalent) have been dialed"); break; case AST_STATE_RING: snprintf(res->data, sizeof(res->data), "Line is ringing"); break; case AST_STATE_RINGING: snprintf(res->data, sizeof(res->data), "Remote end is ringing"); break; case AST_STATE_UP: snprintf(res->data, sizeof(res->data), "Line is up"); break; case AST_STATE_BUSY: snprintf(res->data, sizeof(res->data), "Line is busy"); break; case AST_STATE_DIALING_OFFHOOK: snprintf(res->data, sizeof(res->data), "Digits (or equivalent) have been dialed while offhook"); break; case AST_STATE_PRERING: snprintf(res->data, sizeof(res->data), "Channel has detected an incoming call and is waiting for ring"); break; default: snprintf(res->data, sizeof(res->data), "Unknown result: %s", res->result); break; } return ret;}//// Executes the specified Asterisk application with given options//// @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands//int AGITool_exec(AGI_TOOLS *tool, AGI_CMD_RESULT *res, char *application, char *options){ return AGITool_sendcmd(tool, res, "EXEC %s %s\n", application, options);}//// Plays the given file and receives DTMF data.//// This is similar to STREAM FILE, but this command can accept and return many DTMF digits,// while STREAM FILE returns immediately after the first DTMF digit is detected.//// Asterisk looks for the file to play in /var/lib/asterisk/sounds //// If the user doesn't press any keys when the message plays, there is $timeout milliseconds// of silence then the command ends. //// The user has the opportunity to press a key at any time during the message or the// post-message silence. If the user presses a key while the message is playing, the// message stops playing. When the first key is pressed a timer starts counting for// $timeout milliseconds. Every time the user presses another key the timer is restarted.// The command ends when the counter goes to zero or the maximum number of digits is entered,// whichever happens first. //
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -