7.3. The Asterisk Manager Interface (AMI)

Activate the Asterisk Manager Interface by setting enabled=yes in the [general] section in manager.conf.

Caution

Never do this on a publicly accessible server unless you have taken steps to protect it with packet filters such as iptables, ipfw, an external firewall, or an SSH tunnel!
We add a user entry called admin at the end of the file:
[admin]
secret = secret5
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.255
read = all,system,call,log,verbose,command,agent,user,config
write = all,system,call,log,verbose,command,agent,user,config
The options following read and write define the allowed command types for this user.[40]

Caution

This generous rights assignment is only for test purposes! The command rights level means the user can stop Asterisk. As of Asterisk 1.4, it is even possible to make dialplan changes through the AMI - which also means it is possible to run shell commands with root privileges using System()!
After restarting Asterisk we can connect to the AMI on port 5038 from the system shell using telnet[41]:
$ telnet 127.0.0.1 5038
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Asterisk Call Manager/1.0
Now you can enter commands, usually consisting of multiple lines, by hand. For example:
Action: Login
ActionID: 1
Username: admin
Secret: secret5

Note

All command packets are closed with two carriage returns.
Response:
Response: Success
ActionID: 1
Message: Authentication accepted
Of course, we are most interested in automating this interaction with scripts.

Note

The Manager API is not exactly famous for its ability to handle multiple simultaneous connections gracefully (even though this has improved immensely in version 1.4). If you anticipate this kind of load, it is worth considering an AMI proxy such as the "Simple Asterisk Manager Proxy"[42] (a Perl script), which can handle many connections and bundles them in a single connection. This is completely transparent to the script accessing the AMI. Of course, for the purposes of playing around, it isn't strictly necessary.
Following a successful authentication, packets can be sent in both directions. The packet type is always determined by the first line. The client sends Action packets, the server answers with Response or can send Event packets. Otherwise the order of the lines in a packet is irrelevant. Lines are terminated with a CR LF[43]combination. The entire packet is terminated with an additional CR LF combination. An AMI client normally sends a randomized but unique ActionID with every Action,[44] which the server uses in its response for the purpose of managing overlapping packet streams.
The server sends the client Event packets, which can refer to any events; ther are also events that occur as the result of a client-initiated Action. In this case, the server sends Response: Follows followed by the events (which will contain the ActionID of the initiating action) and a closing event (usually actionnameComplete).
If your client has no need for events, it can turn off these notifications by including Events: off in the authentication packet. Once set, the AMI sends only responses to actions initiated by the client.
The list of available commands can be called up in the CLI with manager show commands (or show manager commands), while information about a specific command can be obtained with manager show command command (or show manager command command):
mos-eisley*CLI> show manager commands
  Action           Privilege        Synopsis                                               
  ------           ---------        --------                                               
  AbsoluteTimeout  call,all         Set Absolute Timeout                                   
  AgentCallbackLo  agent,all        Sets an agent as logged in by callback                 
  AgentLogoff      agent,all        Sets an agent as no longer logged in                   
  Agents           agent,all        Lists agents and their status                          
  ChangeMonitor    call,all         Change monitoring filename of a channel                
  Command          command,all      Execute Asterisk CLI Command                           
  DBGet            system,all       Get DB Entry                                           
  DBPut            system,all       Put DB Entry                                           
  Events           <none>           Control Event Flow                                     
  ExtensionState   call,all         Check Extension Status                                 
  GetConfig        config,all       Retrieve configuration                                 
  Getvar           call,all         Gets a Channel Variable                                
  Hangup           call,all         Hangup Channel                                         
  IAXnetstats      <none>           Show IAX Netstats                                      
  IAXpeers         <none>           List IAX Peers                                         
  ListCommands     <none>           List available manager commands                        
  Logoff           <none>           Logoff Manager                                         
  MailboxCount     call,all         Check Mailbox Message Count                            
  MailboxStatus    call,all         Check Mailbox                                          
  Monitor          call,all         Monitor a channel                                      
  Originate        call,all         Originate Call                                         
  Park             call,all         Park a channel                                         
  ParkedCalls      <none>           List parked calls                                      
  PauseMonitor     call,all         Pause monitoring of a channel                          
  Ping             <none>           Keepalive command                                      
  PlayDTMF         call,all         Play DTMF signal on a specific channel.                
  QueueAdd         agent,all        Add interface to queue.                                
  QueuePause       agent,all        Makes a queue member temporarily unavailable           
  QueueRemove      agent,all        Remove interface from queue.                           
  Queues           <none>           Queues                                                 
  QueueStatus      <none>           Queue Status                                           
  Redirect         call,all         Redirect (transfer) a call                             
  SetCDRUserField  call,all         Set the CDR UserField                                  
  Setvar           call,all         Set Channel Variable                                   
  SIPpeers         system,all       List SIP peers (text format)                           
  SIPshowpeer      system,all       Show SIP peer (text format)                            
  Status           call,all         Lists channel status                                   
  StopMonitor      call,all         Stop monitoring a channel                              
  UnpauseMonitor   call,all         Unpause monitoring of a channel                        
  UpdateConfig     config,all       Update basic configuration                             
  UserEvent        user,all         Send an arbitrary event                                
  WaitEvent        <none>           Wait for an event to occur
These commands are almost always a direct translation of dialplan applications, except in the case of Originate, used to initiate an outgoing call, and Command, which executes a command directly on the CLI. Because our test user admin has all the rights levels (see above), he can execute all commands. The following example shows how we learn how a command is used:
mos-eisley*CLI> manager show command Command
Action: Command 
Synopsis: Execute Asterisk CLI Command
Privilege: command,a
Description: Run a CLI command.
Variables: (Names marked with * are required)
        *Command: Asterisk CLI command to run
        ActionID: Optional Action id for message matching.
The events that Asterisk sends are, as of this writing, effectively undocumented. You may find a list with sparse details at http://www.voip-info.org/wiki/view/asterisk+manager+events. A few additional explanations may be found at http://asterisk-java.sourceforge.net/apidocs/net/sf/asterisk/manager/event/package-frame.html.[45]

Example: Getting the number of voicemail messages with expect

Say we wanted to get the number of messages in a given voice mailbox via the Manager interface. This is easily done using an expect script.
The following expect script connects to the AMI, logs in, then returns the number of new and old messages in the specified mailbox:
#!/usr/bin/expect
#
# Usage: ./vmcount.exp 1234@default

# The user account from manager.conf:
set username "admin"
set secret "secret5"
set host "127.0.0.1"
set port "5038"

if {[llength $argv] != 1} {
    send_user "Error: You must specify a mailbox!\n"
    exit 1
}

# First argument is the mailbox:
set mailbox [lindex $argv 0]
send_user "Mailbox: $mailbox\n"

# Mute output to stdout:
log_user 0

# Open connection to AMI:
spawn telnet $host $port

# Just in case telnet aborts because it cannot connect:
expect_before eof {
    send_user "Failed to connect.\n"
    exit 1
}

# Wait for the text "Manager"; once received, send a login packet:
#
expect "Manager" {
    send_user "Connected.\n"
    send "Action: Login\nUsername: $username\nSecret: $secret\n\n"
    # Please note that telnet automatically converts line feeds
    # (\n) to CR LF (\r\n) - so you must not write \r\n here.
}

# Login successful?:
#
expect {
    -re "Response:\\s*Error" {
        send_user "Login failed.\n"
        exit 1
    }
    -re "Response:\\s*Success" {
        send_user "Logged in.\n"
        # Query the number of messages in the mailbox:
        send "Action: MailboxCount\nMailbox: $mailbox\n\n"
    }
}

expect {
    -re "Response:\\s*Error" {
        send_user "Query of mailbox failed.\n"
        exit 1
    }
    -re "Response:\\s*Success" {}
}
expect {
    -re "NewMessages:\\s*(\[\\d]*)" {
        send_user "New messages: $expect_out(1,string)\n"
    }
}
expect {
    -re "OldMessages:\\s*(\[\\d]*)" {
        send_user "Old messages: $expect_out(1,string)\n"
    }
}

# Log out -- not strictly necessary, but cleaner:
send "Action: Logoff\n\n"
We save the script as vmcount.exp and set it executable with chmod a+x vmcount.exp.
Sample output:
$ ./vmcount.exp 123@default
Mailbox: 123@default
Connected.
Logged in.
New messages: 0
Old messages: 0

StarAstAPI for PHP

A disclaimer: keep your expectations modest. StarAstAPI has room for improvement :-)
There are now numerous, more-or-less good APIs for the AMI in a variety of programming languages (PHP, Perl, Python, Ruby etc.) which we, because of space and time limitations, can't explore here[47]. If the API for your favorite language doesn't work, we're confident you can figure it out. It's doubtful that anybody without programming experience has read this far :)
In this short example, we test the StarAstAPI[48] in PHP, which assumes a PHP 5[49] that was compiled with --enable-sockets.[50] Unfortunately, the StarAstAPI files still contain the obsolete "short open tags" (<?). If you encounter them, replace them with the correct syntax (<?php). Four demo scripts are included with the API: sLogin.php attempts a login[51], sCommand.php executes reload on the CLI, sDial.php tries a connection to SIP/120 and sEvents.php receives events. If we connect to Asterisk using asterisk -vvvr and simultaneously run php -q sLogin.php to open a connection to the AMI[52], watching the CLI, we see:
mos-eisley*CLI> 
  == Parsing '/etc/asterisk/manager.conf': Found
[Jan 26 20:08:09] NOTICE[10352]: manager.c:961 authenticate: 127.0.0.1 tried to authenticate with nonexistent user 'mark'
  == Connect attempt from '127.0.0.1' unable to authenticate
mos-eisley*CLI> 
This failed because the user did not exist, yet the demo script still reports success:
$  php -q sLogin.php 
Login Sucessful 

followed by the response packet:
Response: Error
ActionID: 1
Message: Authentication failed
The StarAstAPI is, as you can see, not completely clean, but is simple enough that it can be improved easily. If we call php -q sEvents.php - this time with the correct user - we see:
mos-eisley*CLI> 
  == Parsing '/etc/asterisk/manager.conf': Found
  == Manager 'admin' logged on from 127.0.0.1
mos-eisley*CLI> 
As a test, we execute a reload in the CLI, which is reflected in the PHP script output:
Event: Reload
Privilege: system,all
Message: Reload Requested

Event: ChannelReload
Privilege: system,all
Channel: SIP
ReloadReason: RELOAD (Channel module reload)
Registry_Count: 0
Peer_Count: 0
User_Count: 0

Give your creativity free-reign! Write a small script that calls all your friends - in the middle of the night, of course!

Example: Getting the number of mailbox messages with PHP

Here's how we would accomplish the same objective as the section called “Example: Getting the number of voicemail messages with expect” in PHP using StarAstAPI:
#!/usr/bin/php -q
<?php
# option -q turns off the header output when executing CGI-PHP

if ($argc != 2) {
    echo "Error: You must specify a mailbox!\n";
    exit(1);
}
# The first argument after the program name is the mailbox:
$mailbox = $argv[1];
echo "Mailbox: $mailbox\n\n";

# Include StarAstAPI:
require_once './StarAstAPI/StarAstAPI.php';

# Connect and log in:
#
$ami = new AstClientConnection();
if ($ami->Login( 'admin', 'secret5', '127.0.0.1', 5038 )) {
    $rp = $ami->GetResponse('1');
    //echo $rp->ToString();
} else {
    exit(1);
}

# Send the following packet:
#     Action: MailboxCount
#     Mailbox: $mailbox
#     ActionID: 2
#
$data = new AstPacketData;
$data->AddKVPair( 'Action'  , 'MailboxCount' );
$data->AddKVPair( 'Mailbox' , $mailbox );
$data->AddKVPair( 'ActionID', '2' );
$packet = new AstPacket;
$packet->SetAstPacketType( 'Action' );
$packet->SetAstPacketData( $data );
$ami->SendPacket( $packet );

# Read the response packet bearing ActionID 2:
#
$rPacket = $ami->GetResponse('2');
//echo $rp->ToString();
$rData = $rPacket->GetAstPacketData();
$r = $rData->GetAll();

echo "New messages: ", (int)trim($r['NewMessages:']), "\n";
echo "Old messages: ", (int)trim($r['OldMessages:']), "\n";
echo "\n";

# Log out -- not strictly necessary, but cleaner:
#
$ami->Logoff();
# Unfortunately, StarAstAPI isn't totally discreet.
# It does this:
#echo "Logoff Called from somewhere ...";
#socket_close($this->mSocket);

echo "\n";
?>
We save this script as vmcount.php and make it executable with chmod a+x vmcount.exp.
Sample output:
$ ./vmcount.php 123@default
Mailbox: 123123123

New messages: 0
Old messages: 0

Logoff Called from somewhere ...


Learn the rights levels needed for commands by entering manager show commands (or show manager commands in Asterisk 1.2) in the CLI.
Here we only use telnet as an interface, and not in the traditional, interactive fashion.
Carriage Return (decimal ASCII 13) and Line Feed (decimal ASCII 10)
This can be, for example, the name of the script, a timestamp and a sequence number, e.g. testscript.php-1169405408-1.
Don't be confused: this is primarily Asterisk-Java documentation.
The API is easily ported to PHP 4, though the code is cluttered and poorly formatted. When in doubt, just remedy the parse errors :)

[50] You can check this from the shell with php -m.

If you have followed the examples above, you will need to adapt the user name and password.
The user and password are deliberately incorrect.