Activate the Asterisk Manager Interface by setting
enabled=yes in the [general] section in
manager.conf.
|
|
|
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.[28]
|
|
|
This generous rights assignment is only for test purposes! The
|
After restarting Asterisk we can connect to the AMI on port 5038 from the system shell using telnet[29]:
$ 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
|
|
|
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.
|
|
|
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"[30] (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[31]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,[32] 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 callba
ck
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 chan
nel
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 chann
el.
QueueAdd agent,all Add interface to queue.
QueuePause agent,all Makes a queue member temporarily una
vailable
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.[33]
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
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[35]. 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[36] in PHP, which assumes a PHP 5[37] that was compiled with
--enable-sockets.[38] 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[39], 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[40], 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 t ried 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!
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 ...
[28] Learn the rights levels needed for commands by entering manager show commands (or show manager commands in Asterisk 1.2) in the CLI.
[29] Here we only use telnet as an interface, and not in the traditional, interactive fashion.
[31] Carriage Return (decimal ASCII 13) and Line Feed (decimal ASCII 10)
[32] This can be, for example, the name of the script, a timestamp
and a sequence number, e.g.
testscript.php-1169405408-1.
[33] Don't be confused: this is primarily Asterisk-Java documentation.
[35] Examples with comments may be found at http://www.voip-info.org/wiki/view/Asterisk+manager+Examples
[36] from http://www.starutilities.com/
[37] The API is easily ported to PHP 4, though the code is cluttered and poorly formatted. When in doubt, just remedy the parse errors :)
[38] You can check this from the shell with php -m.
[39] If you have followed the examples above, you will need to adapt the user name and password.
[40] The user and password are deliberately incorrect.