Tag Archives: perl

A crash course in Perl

Found in a posting on the unofficial Penn State intranet (Yammer):

for those who already know the basic building blocks of programming or scripting (statements, blocks, conditionals, variables, operations, and so on).
Perl is always in my toolbox. Real programming languages are for Real Programmers. Perl is for systems administrators and hackers. You can do anything with it; it will probably just take longer to run (but if you don’t care about that, it will probably be quicker to write).

Appendix: analog remix debugging and notes

Part 1: Intro, Part 2: Tropo/PHP, Part 3: Asterisk Manager/Perl

These notes are from a weekend of hacking and debugging with Michigan Telephone, the first guinea pig to try out the “analog remix” scripts. Thanks for the extra challenges. 🙂

Asterisk 1.4, 1.8 and the Asterisk Manager Interface

  • Asterisk 1.4’s Manager calls the channel that is bridged to the current channel “Link.” Newer versions, more sensibly, call it “BridgedChannel.” I updated the Perl CGI to use whichever is defined, so now the CGI will work with newer versions of Asterisk, and not just 1.4.
  • A bug exists on specific versions of Asterisk 1.4, 1.6 and 1.8 that breaks call transfer from the Manager (as well as SIP blind transfers). The current -rc1 versions, as of this writing, have the fix: 1.4.39-rc1, 1.6.2.16-rc1, and 1.8.2-rc1. This bug has been in 1.4 since 1.4.38-rc1; in 1.6 since 1.6.2.15-rc1; and in 1.8 since 1.8.0-rc4. If you’re on a buggy version, the transfer will just hang up the channel.
  • How to set up the Manager user may not be obvious. Just make a new user block in manager.conf or manager_custom.conf (FreePBX) with the following permissions:

    [tropousername]
    secret = tropopassword
    deny=0.0.0.0/0.0.0.0
    permit=127.0.0.1/255.255.255.0
    read = call
    write = call

    Set the username and password to whatever you want, and then use these in the Perl CGI in the AMI::Common::new() function. Your manager user needs only “call” permissions to perform the actions in the CGI.

Perl

  • MT notes that you may have to install the AnyEvent and parent Perl modules before Asterisk::AMI. Use CPAN’s installer if you can: perl -MCPAN -e shell and install parent, AnyEvent, and Asterisk::AMI
  • The Data::Dumper module can be really helpful if you find that things aren’t going well with your CGI. Just use Data::Dumper at the top of the CGI, then print STDERR Dumper($variablename) wherever you need it. Try this on a hash, such as $currchan, and you’ll get a dump of the whole thing.

Firewall/ACL

You may want to set up a separate Apache VirtualHost with access controls (username/password and/or IP access list) for your Tropo CGI. Or, if your web server is firewalled, you’ll need to let Tropo in to make the request. Tropo’s support team indicated that requests will come from 66.193.54.21, so this IP can be allowed through your firewall. They will announce any changes to this on changes.tropo.com.

Analog remix part 3: some Perl CGI to route the call

Part 3 of the analog remix ties it all together. We need a way for Tropo to tell Asterisk to transfer the call, since we’re not going to use SIP REFER. A simple Perl CGI that connects to Asterisk Manager Interface (AMI) will do the trick.

I’m sticking with old-school Perl for this one because I really like Ryan Bullock’s Asterisk::AMI modules. His code is easy to use and he keeps it up to date.

Referring back to part 2, you can see where the curl function calls tropo.cgi (the Perl script below) with parameters f=xfer (the only function for now), callid=the SIP Call-ID, and dest=the number the caller requested. The Perl script finds the Call-ID by iterating through the channel list, finding one that’s connected to Tropo, and then checking the Call-ID to make sure it’s the exact one. (You could have more than one channel open to Tropo.) Once it finds that, it transfers the second leg of that call, which is the caller. Output is to STDERR so that I can review the transaction in the Apache error log.

Updated 12/19/2010: This line

my $link = $currchan->{'Link'} || $currchan->{'BridgedChannel'};

is updated to use either the key returned by Asterisk 1.4 (“Link”) or by later versions (“BridgedChannel”) in the channel listing.

The code:

#!/usr/bin/perl

use strict;
use Asterisk::AMI::Common;
use CGI;
use URI::Escape;

select(STDERR);
local $| = 1;
select(STDOUT);
local $| = 1;

my $cgi = CGI->new;
my $func = $cgi->param('f');
my $callid = uri_unescape($cgi->param('callid'));
my $dest = $cgi->param('dest');

print $cgi->header("text/plain");

if(defined $func) {
  if($func eq "xfer") {
    if(defined $dest && $dest ne "") {
      my $ami = Asterisk::AMI::Common->new (PeerAddr => 'localhost',
                                    PeerPort => '5038',
                                    Username => 'USERNAME',
                                    Secret => 'PASSWORD',
                                    Events => 'off',
                                    on_error => &errorHandler
                                   )
      or die "Unable to connect to Asterisk Manager.n";

      my $channels = $ami->channels;
      if (defined($channels)) {
        foreach my $channel (keys %{$channels}) {
          my $currchan = %{$channels}->{"$channel"};
          if ($channel =~ m/tropo.com/) { # find channels connected to Tropo
            my $currchan_callid = $ami->get_var($channel, "SIPCALLID");
            if ($currchan_callid == $callid) { # make sure it's the right call,
              print STDERR "Channel $channel"; # identified by Call-ID
              print STDERR "Callid $callid";

              my $link = $currchan->{'Link'} || $currchan->{'BridgedChannel'};
              if (defined($link)) { # this is the initiating leg of the call
                my $transfer = $ami->transfer($link, $dest, "from-internal");
                if (defined($transfer)) {
                  if ($transfer == 1) {
                    print STDERR "Tropo successfully transferred $link to $dest.";
                  } else {
                    print STDERR "Tropo FAILED to transfer $link to $dest.";
                  }
                } else {
                  print STDERR "System error performing transfer.";
                }
              } else {
                print STDERR "Local channel leg not found.";
              }
            }
          }
        }
      } else {
        print STDERR "Could not get channel list from Asterisk manager.";
      }
      $ami->disconnect; # close Manager connection
    } else {
      print STDERR "xfer requested but no dest specified.";
    }
  } else {
    print STDERR "Invalid function sent in CGI.";
  }
} else {
  print STDERR "No function sent in CGI.";
}

print "Done.n";
exit;

#### Functions ####

sub errorHandler {  # A fatal error occurred
  my ($amiobj, $errmsg) = @_;
  print STDERR "AMI Fatal error: $errmsgn";
  exit 1;
}

Summary

Starting with part 1, configure a hot line or warm line for your old analog device to automatically ring an extension on your system. You’ll define that extension as the SIP URI to the Tropo application you made in part 2. The Tropo application, in turn, will connect back to your Asterisk server running web services (Apache) with a CGI that can take the input and connect to the Asterisk Manager Interface and transfer your call. Simple! 🙂

More Asterisk and XMPP (Jabber) integration

Last month I wrote about how to send an XMPP (Jabber) message with Caller*ID information to one or more users when a call comes in to your Asterisk server. This is a simple, one-way notification and is easy to set up. What if we want to interact more fully with the Asterisk server over XMPP?

Please note that I am referring to administrative interaction, not call-level interaction. Asterisk 1.6 adds a JabberReceive command to the dial plan, which would allow some interactive instant messaging within the context of a call.

It turns out that the Asterisk Manager Interface (AMI) posts an event for every XMPP packet–both outgoing and incoming–so writing a Manager application to interface with XMPP is a good way to go.

Writing a daemon in Perl to listen on the Manager interface for XMPP messages, and then interact with Asterisk through the Manager, ends up being pretty straightforward, especially with the Asterisk::AMI module available on CPAN.

Here’s the general flow:

  1. Set up the AMI connection, with Events => ‘on’ so that we get events. The user this script logs in as must be in manager.conf and have permission to receive events and perform any actions you want to be able to perform (originate calls, write to the database, etc.).

  2. Read in a list of buddies from Asterisk’s jabber.conf from whom Asterisk should accept commands.

  3. Set up a simple event loop that filters out just the Jabber events. Mine looks like this:

    while (my $event = $ami->get_event) {
    if ($event->{'Event'} == 'JabberEvent' && defined($event->{'Packet'} )) {
    validateMessage($event->{'Packet'});
    }
    }

  4. Validate the XMPP message. Make sure it came from a valid buddy (step 2) and contains a command.

  5. Process the command, initiating any actions and sending results back to the XMPP buddy. So far in my script I’ve implemented a “call” command, where you can instruct Asterisk to originate a call to a certain number from an extension (or use the ring-all group as the default extension); a “dnc” command, that adds a number to the FreePBX blacklist (telemarketers), and help text.

How does it work? It’s simple–just add the Asterisk XMPP user (the one that sends you the call pop-ups from the previous posting) to your buddy list, and IM it your commands.

What’s left? I’d like to add some more useful interactive features.

  • When a call comes in and I get the Caller*ID pop-up, typing “cell” in the pop-up window will let me redirect the call to my cell phone. Or “vm” for voicemail or “zap” for Zapateller.
  • Missed/placed/received calls lists by pulling records from the CDR database.
  • Channel status, tech (sip/iax2) status, other statuses.

When is enough enough?

You could implement the entire Manager command set in this way, even implementing a very clunky attendant console or ACD supervision tool. That could be a bit much, but I see this being useful as a tool for common call-control tasks and simple administration tasks that would otherwise have to be performed at the command line (requires you to have an SSH session open to the server) or web interface.

If anyone’s interested in the Perl code in its current form, let me know.