Using PHP with the Asterisk Gateway Interface (AGI)
PHP Tips and Examples
Script Location
Asterisk expects to find scripts in the /var/lib/asterisk/agi-bin/
directory for AGI execution.
Page Contents
- File Permissions
- The Shebang
- Read AGI Inputs
- Sending commands and receiving responses
- Debugging with VERBOSE
- Debugging with PHP
- Exiting cleanly
- Kill zombie AGI scripts while debugging
- Putting it all together
- Old Samples
- 12. sample.php
- 13. another sample, ANI
- 14. yet another sample, read()
- 15. Simple example for GET VARIABLE
- See also
File Permissions
Remember to make your files executable with chmod 755 /var/lib/asterisk/agi-bin/*.php
.
The Shebang
The first line in your script will, of course, determine which executable runs it. Be aware of the different versions of PHP, such as /usr/bin/php
and /usr/bin/php-cgi
. In most cases, life will be easier if you use the CLI version — typically at /usr/bin/php
. (Use /usr/bin/php -v
to confirm that you are using the CLI version.) This will make $argv
and $argc
available, disable buffering, disable HTTP header output, and pipe error messages to STDERR instead of STDOUT. All these are desirable for working with Asterisk. If you use the CGI executable, you will have to use some command-line options and edit your scripts to ensure proper behavior.
The shebang should be followed immediately by the <?php
script opener, with only a newline between them. Any whitespace could get sent to Asterisk, causing problems.
Read AGI Inputs
All communication with the AGI occurs over STDIN and STDOUT; these IO streams are predefined in PHP and do not need to be opened or closed. When your program starts, Asterisk will stream a number of variables to it, which it’s in your interest to capture.
$agivars = array();
while (!feof(STDIN)) {
$agivar = trim(fgets(STDIN));
if ($agivar === '') {
break;
}
$agivar = explode(':', $agivar);
$agivars[$agivar[0]] = trim($agivar[1]);
}
extract($agivars);
echo $agi_callerid;
The info passed by Asterisk is:
- agi_request – The filename of your script
- agi_channel – The originating channel (your phone)
- agi_language – The language code (e.g. “en”)
- agi_type – The originating channel type (e.g. “SIP” or “ZAP”)
- agi_uniqueid – A unique ID for the call
- agi_callerid – The caller ID number (or “unknown”)
- agi_calleridname – The caller ID number (or “unknown”)
- agi_callingpres – The presentation for the callerid in a ZAP channel
- agi_callingani2 – The number which is defined in ANI2 see Asterisk Detailed Variable List (only for PRI Channels)
- agi_callington – The type of number used in PRI Channels see Asterisk Detailed Variable List
- agi_callingtns – An optional 4 digit number (Transit Network Selector) used in PRI Channels see Asterisk Detailed Variable List
- agi_dnid – The dialed number id
- agi_rdnis – The referring DNIS number
- agi_context – Origin context in extensions.conf
- agi_extension – The called number
- agi_priority – The priority it was executed as in the dial plan
- agi_enhanced – The flag value is 1.0 if started as an EAGI script
- agi_accountcode – Account code of the origin channel
Sending commands and receiving responses
Sending Commands
To send commands to Asterisk, use fwrite
(or it’s alias fputs
) to send AGI commands to standard ouput.
fwrite(STDOUT, "SAY NUMBER 1234567 '79#'\n");
fflush($stdout);
Notes
- Use
fflush
after an output, just to be safe. If output buffering is somehow enabled, the command may not be sent properly, leaving you to wait for your script to timeout. - Most AGI command options are required, even if empty. Some options must be quoted, and some must not be quoted. Check the list of AGI commands to be sure.
- Follow each command with a carriage return (
\n
).
Receiving Responses
To receive responses from Asterisk, simply read standard input with fgets
$msg = fgets(STDIN);
The response is in the form <code> result=<result> [extra]
where code
is a numeric HTTP-like response code (200 for success, 5xx for errors) result
is the result of the command and extra
is data that is sometimes appended to the results. If a command times out, extra
will contain the value (timeout)
.
Sample Function
function execute_agi($command) {
fwrite(STDOUT, "$command\n");
fflush(STDOUT);
$result = fgets(STDIN);
$ret = array('code'=> -1, 'result'=> -1, 'timeout'=> false, 'data'=> '');
if (preg_match("/^([0-9]{1,3}) (.*)/", $result, $matches)) {
$ret['code'] = $matches[1];
$ret['result'] = 0;
if (preg_match('/^result=([0-9a-zA-Z]*)(?:\s?\((.*?)\))?$/', $matches[2], $match)) {
$ret['result'] = $match[1];
$ret['timeout'] = ($match[2] === 'timeout') ? true : false;
$ret['data'] = $match[2];
}
}
return $ret;
}
$result = agi_execute('WAIT FOR DIGIT 5000');
print_r($result);
/*
If # is pressed, output will be:
Array
(
[code] => 200
[result] => 35
[timeout] => false
[data] =>
)
*/
Debugging with VERBOSE
VERBOSE is your friend if you’d like to display debugging information in the Asterisk CLI. But make sure to also read its return as otherwise, you will screw up your script badly! Using the above execute_agi
example it’s simple to build a logging function:
function log_agi($entry, $level = 1) {
if (!is_numeric($level)) {
$level = 1;
}
$result = execute_agi("VERBOSE \"$entry\" $level");
}
log_agi('Log message');
Debugging with PHP
To view PHP’s error messages, you need to view STDERR. Fortunately, STDERR from an AGI script is output to the Asterisk console. Unfortunately, it is only output to the original instance of Asterisk. Connecting to an already-running instance with asterisk -r
will not work; you’ll need to connect to the original Asterisk instance or stop Asterisk and start your own instance. On most systems, Asterisk will have a console running on TTY9; connect to that console and you’ll see the messages from STDERR. If that doesn’t work, you can always stop the running Asterisk and start your own with (for example) asterisk -fgc -U asterisk -G asterisk
. Obviously this is not an option on a live server! Note that no verbosity is required, output from STDERR is always displayed in the original Asterisk console.
Another way to see errors of your scripts live is to edit php.ini and set error_log = /var/log/mylogfile
Make sure that display_errors is set to “Off” as well. This will prevent PHP printing out notices and error messages as output of your script.
After that tail -f /var/log/mylogfile will show the errors of your script.
Exiting cleanly
On hangup from the caller, Asterisk will kill your script with SIGHUP; you can use pcntl_signal
to run a user-defined function at this time.
pcntl_signal(SIGHUP, "agi_hangup_handler");
function agi_hangup_handler($signo) {
//this function is run when Asterisk kills your script ($signo is always 1)
//close file handles, write database records, etc.
}
If you call HANGUP from within your script, you must then terminate your script with exit or the script will not stop, since Asterisk 1.6. Prior versions terminated the script after a hangup regardless of its origin.
Kill zombie AGI scripts while debugging
If your script failed for some reason, the process could be hung; remember to kill your script process with killproc my_script.php
before testing it again.
Putting it all together
#!/usr/bin/php
<?php
/*
This script answers the call and prompts for an extension, provided your caller ID is approved.
When it receives 4 digits it will read them back to you and hang up.
*/
$debug_mode = false; //debug mode writes extra data to the log file below whenever an AGI command is executed
$log_file = '/tmp/agitest.log'; //log file to use in debug mode
$allowed_ext = array('1234', '0'); //who's allowed to access this script
//get the AGI variables; we will check caller id
$agivars = array();
while (!feof(STDIN)) {
$agivar = trim(fgets(STDIN));
if ($agivar === '') {
break;
}
else {
$agivar = explode(':', $agivar);
$agivars[$agivar[0]] = trim($agivar[1]);
}
}
foreach($agivars as $k=>$v) {
log_agi("Got $k=$v");
}
extract($agivars);
//hangup if it's not an allowed extension
if (!empty($allowed_ext)) {
if (!in_array($agi_callerid, $allowed_ext)) {
log_agi("Call rejected from $agi_calleridname <$agi_callerid>");
execute_agi('STREAM FILE goodbye ""');
execute_agi('HANGUP');
exit;
}
}
//ask for an extension
$ext = '';
$result = execute_agi('STREAM FILE please-enter-the "0123456789"');
if ($result['result'] == 0) {
$result = execute_agi('STREAM FILE users "0123456789"');
if ($result['result'] == 0) {
//they haven't entered anything yet
$result = execute_agi("GET DATA extension 5000 4");
if ($result['result'] > 0) {
$ext = $result['result'];
}
}
else {
$ext = chr($result['result']);
}
}
else {
$ext = chr($result['result']);
}
//we haven't got 4 digits yet
if (strlen($ext) < 4) {
//still no input after GET DATA timeout
if (empty($ext)) {
execute_agi('STREAM FILE please-enter-the ""');
execute_agi('STREAM FILE users ""');
execute_agi('STREAM FILE extension ""');
}
//we got a single digit during playback of 'please enter the' or 'users'
while (strlen($ext) < 4) {
$result = execute_agi('WAIT FOR DIGIT -1');
//look for digits only
if ($result['result'] >= 48 && $result['result'] <= 57) {
$ext .= chr($result['result']);
}
//ignore * or # (or a, b, c, d)
else {
continue;
}
}
}
log_agi("Got extension $ext");
//say back the extension; at this point you could certainly do something more productive but that is left to you
execute_agi('STREAM FILE you-entered ""');
execute_agi("SAY DIGITS $ext \"\"");
execute_agi('STREAM FILE goodbye ""');
execute_agi('HANGUP');
exit;
function execute_agi($command) {
global $debug_mode, $log_file;
fwrite(STDOUT, "$command\n");
fflush(STDOUT);
$result = trim(fgets(STDIN));
$ret = array('code'=> -1, 'result'=> -1, 'timeout'=> false, 'data'=> '');
if (preg_match("/^([0-9]{1,3}) (.*)/", $result, $matches)) {
$ret['code'] = $matches[1];
$ret['result'] = 0;
if (preg_match('/^result=([0-9a-zA-Z]*)\s?(?:\(?(.*?)\)?)?$/', $matches[2], $match)) {
$ret['result'] = $match[1];
$ret['timeout'] = ($match[2] === 'timeout') ? true : false;
$ret['data'] = $match[2];
}
}
if ($debug_mode && !empty($logfile)) {
$fh = fopen($logfile, 'a');
if ($fh !== false) {
$res = $ret['result'] . (empty($ret['data']) ? '' : " / $ret[data]");
fwrite($fh, "-------\n>> $command\n<< $result\n<< parsed $res\n");
fclose($fh);
}
}
return $ret;
}
function log_agi($entry, $level = 1) {
if (!is_numeric($level)) {
$level = 1;
}
$result = execute_agi("VERBOSE \"$entry\" $level");
}
?>
Old Samples
Note the following samples are all 4-5 years old at this point, and are written for earlier versions of PHP and Asterisk. They should be approached with caution.
12. sample.php
If you copy and paste this sample, be sure to remove the leading spaces before the first two lines or you will get an error message talking about an “Exec format error”. You may also get this message if your text file is not encoded in ANSI format. Also check your php version in the first line. You may need “#!/usr/bin/php5 -q” or simply “#!/usr/bin/php -q”.
- !/usr/bin/php4 -q
<?php
ob_implicit_flush(true);
set_time_limit(6);
$in = fopen(“php://stdin”,”r”);
$stdlog = fopen(“/var/log/asterisk/my_agi.log”, “w”);
// toggle debugging output (more verbose)
$debug = false;
// Do function definitions before we start the main loop
function read() {
global $in, $debug, $stdlog;
$input = str_replace(“\n”, “”, fgets($in, 4096));
if ($debug) fputs($stdlog, “read: $input\n”);
return $input;
}
function errlog($line) {
global $err;
echo “VERBOSE \”$line\”\n”;
}
function write($line) {
global $debug, $stdlog;
if ($debug) fputs($stdlog, “write: $line\n”);
echo $line.”\n”;
}
// parse agi headers into array
while ($env=read()) {
$s = split(“: “,$env);
$agi[str_replace(“agi_”,””,$s[0])] = trim($s[1]);
if (($env == “”) || ($env == “\n”)) {
break;
}
}
// main program
echo “VERBOSE \”Here we go!\” 2\n”;
read();
errlog(“Call from “.$agi[‘channel’].” – Calling phone”);
read();
write(“SAY DIGITS 22 X”); // X is the escape digit. since X is not DTMF, no exit is possible
read();
write(“SAY NUMBER 2233 X”); // X is the escape digit. since X is not DTMF, no exit is possible
read();
// clean up file handlers etc.
fclose($in);
fclose($stdlog);
exit;
?>
13. another sample, ANI
ani.agi
- !/usr/local/bin/php -q
<?php
ob_implicit_flush(true);
set_time_limit(6);
$in = fopen(“php://stdin”,”r”);
$stdlog = fopen(“/var/log/asterisk/my_agi.log”, “w”);
// Do function definitions before we start the main loop
function read() {
global $in, $debug;
$input = str_replace(“\n”, “”, fgets($in, 4096));
return $input;
}
function errlog($line) {
global $err;
echo “VERBOSE \”$line\”\n”;
}
function write($line) {
global $debug;
echo $line.”\n”;
}
// parse agi headers into array
while ($env=read()) {
$env = str_replace(“\””,””,$env);
$s = split(“: “,$env);
$agi[str_replace(“agi_”,””,$s[0])] = trim($s[1]);
if (($env == “”) || ($env == “\n”)) {
break;
}
}
function connect_db() {
$db_connection = mysql_connect (‘localhost’, ‘admin’, ‘admincdr’) or die (mysql_error());
$db_select = mysql_select_db(‘cdrdb’) or die (mysql_error());
}
// main program
$cli = $agi[[callerid];
$exten= $agi[extension];
//errlog(“Call from “.$agi[callerid].” – Calling phone”);
connect_db();
$query1 = “SELECT pin_no FROM accounts WHERE clid = ‘$cli’ “;
$query_result1 = @mysql_query($query1);
$row_count = mysql_num_rows($query_result1);
$row1 = @mysql_fetch_array ($query_result1);
If ($row_count !=0 ) { // caller is authenticated based on ANI
$pin = $row1[pin_no];
write (“SET CONTEXT did”);
write (“EXEC SETACCOUNT $pin”);
write (“EXEC GoTO s|2”); // ask for the number to call, no authentication
}
Else { // clid does not exist so ask for PIN
write (“SET CONTEXT did”);
write (“EXEC GoTO 866XXXXXXX|7”);
}
// clean up file handlers etc.
fclose($in);
fclose($stdlog);
exit;
?>
Extensions.conf
[did]
;for did callers
exten => 866XXXXXXX,1,Ringing
exten => 866XXXXXXX,2,Wait,4
exten => 866XXXXXXX,3,Answer
exten => 866XXXXXXX,4,SetCIDname()
exten => 866XXXXXXX,5,agi,ani.agi
exten => 866XXXXXXX,6,Authenticate(/etc/asterisk/authenticate.txt|a)
exten => 866XXXXXXX,7,GoTO(s|2)
exten => s,2,BackGround(pls-entr-num-uwish2-call)
exten => s,3,DigitTimeout,5
exten => s,4,ResponseTimeout,10
exten => _011XXXXXXXX.,1,Playback(pls-wait-connect-call)
exten => _011XXXXXXXX.,2,AbsoluteTimeout(3600)
exten => _011XXXXXXXX.,3,ResetCDR(w)
exten => _011XXXXXXXX.,4,Dial(H323/${EXTEN}@a.b.c.d,90)
exten => _011XXXXXXXX.,5,NoCDR() ; if no answer
exten => _011XXXXXXXX.,6,Busy
exten => _011XXXXXXXX.,105,NoCDR() ; if line is busy
exten => _011XXXXXXXX.,106,Busy
exten => t,1,Playback(vm-goodbye) ; if timeout
exten => t,2,NoCDR()
exten => t,3,Hangup
exten => i,1,Playback,invalid ; if any number other than 011…..
exten => i,2,Goto,s|3
;exten => h,1,Hangup
mysql database
database name – cdrdb ( user – admin, login – admincdr )
table name – accounts ( two fields, pin_no and clid)
14. yet another sample, read()
Purpose – Get info received from READ command
call_start.agi
- !/usr/local/bin/php -q
<?php
ob_implicit_flush(true);
set_time_limit(6);
error_reporting(0); //added by user: democritus
$in = fopen(“php://stdin”,”r”);
$stdlog = fopen(“/var/log/asterisk/my_agi.log”, “w”);
// toggle debugging output (more verbose)
$debug = true;
// Do function definitions before we start the main loop
function read() {
global $in, $debug, $stdlog;
$input = str_replace(“\n”, “”, fgets($in, 4096));
if ($debug) fputs($stdlog, “read: $input\n”);
return $input;
}
function write($line) {
global $debug, $stdlog;
if ($debug) fputs($stdlog, “write: $line\n”);
echo $line.”\n”;
}
// parse agi headers into array
while ($env=read()) {
$s = split(“: “,$env);
$agi[str_replace(“agi_”,””,$s[0])] = trim($s[1]);
if (($env == “”) || ($env == “\n”)) {
break;
}
}
write(“EXEC READ dialed|IVR/en_enter_destination|0 X”); // Custom Sound File (So make your own!)
read();
write(“GET VARIABLE dialed X”);
$a = read();
$num = substr($a,14);
$num = substr($num,0,-1);
echo “VERBOSE \”$num\” \n”;
read();
// clean up file handlers etc.
fclose($in);
fclose($stdlog);
exit;
?>
By Mike Roberts (manipura) Nov 30 ’04
15. Simple example for GET VARIABLE
function __read__() {
global $in, $debug;
$input = str_replace(“\n”, “”, fgets($in, 4096));
if ($debug) echo “VERBOSE \”read: $input\”\n”;
return $input;
}
function __write__($line) {
global $debug;
if ($debug) echo “VERBOSE \”write: $line\”\n”;
print $line.”\n”;
}
//get the variable and strip off all the extra stuff around it
__write__(“GET VARIABLE MYDIALPLANVAR”);
$res = substr(strrchr(__read__(),”(“),1,-1);
__write__(“EXEC NOOP ‘======??? MYDIALPLANVAR: “.$res.” ???======’\n”);
__read__();
By hkirrc.patrick 2003-11-11, some changes in source code by Florian Overkamp and WipeOut and Philipp von Klitzing and democritus
See also
- Asterisk AGI: The Asterisk Gateway Interface, Overview, pointers to AGI documentation
- Example: peer-status.phps uses AMI to get the status of SIP peer
- MSN PHP: Instant messaging for MSN
- http://eder.us/projects/asterisk_php/: PHP scripting within the dialplan – better than using AGI
- http://eder.us/projects/phpagi/: phpAGI2 :: a php class for AGI
- http://phpagi.sourceforge.net/: phpagi :: a php class for AGI
- https://wisedonkey.livejournal.com/98142.html: PHP ASTLIB : A PHP 5 class for AGI. Built for easy extensibility for handing Fast AGI
- http://www.php.net: The PHP scripting language: home page, documentation, downloads
Go back to Asterisk