package Asterisk::AstMan; $|=1; use IO::Socket::INET; use Data::Dumper; use Digest::MD5; =head1 NAME Asterisk::AstMan "I wanna talk to a manager!" =head1 SYNOPSIS use Asterisk::Astman; my $astman = new Asterisk::Manager; my $man = init Asterisk::AstMan ({ -user => "astuser", -secret => "1234", -host => "localhost", -events => "yes" }); =head1 DESCRIPTION This module trys to communicate with the manager interface. =head1 METHODS . =head2 action(<$action>,[$id]) Call specified Action and provide optional ID returns the next item $hashref = $man->action("Queues","1234") =head2 command(<$command>) Call Specified Command $ouput $man->command("Show Channels"); =head2 reg_event(<$id>,<$coderef>) Register code to execute when an event has been received. $newext = sub { my $ref = shift; print "Got a Bite!\n"; print Dumper $ref; }; $man->reg_event("Newexten",$newext); =head2 reg_regex_event(<$regex>,<$coderef>) Register code to execute when an event name matches a regular Expression. $man->reg_regex_event("New.*",$new); =head2 reg_actionid(<$id>,<$coderef>) Register to execute certian code when a certian action ID is detected $todo = sub { my $ref = shift; print "Got a Bite!\n"; print Dumper $ref; }; $hashref = $man->action("Queues","1234") $man->reg_actionid("1234",$todo); =head2 listen([$timeout]) listen for a certian amount of time (undef=forever) for above events and process them. =cut my %TRUE = ( yes => 1, YES => 1, no => 0, NO => 0, 1 => 1, 0 => 0 ); sub init($;$) { my $proto = shift; my $args = shift; my $class = ref($proto) || $proto; my $self = { _events => undef , _connected => undef, _timeout => 1 }; $self->{_host} = $args->{-host} || "localhost"; $self->{_port} = $args->{-port} || 5038; $self->{_user} = $args->{-user} || undef; $self->{_secret} = $args->{-secret} || undef; $self->{_events} = $TRUE{$args->{-events}}; $self->{_timeout} = ($args->{-timeout} > 0) ? $args->{-timeout} : 1; my $me = bless $self,$class; $me->connect; $me; } sub timeout($;$) { my($self,$timeout) = @_; $self->{_timeout} = $timeout if($timeout); return $self->{_timeout}; } sub DESTROY($) { my $self = shift; $self->disconnect; } sub error($$) { my($self,$error) = @_; die $error; } sub chomp($$) { my ($self,$str) = @_; $str =~ s/[\r\n]//g; $str; } sub input($;$) { my ($self,$m) = @_; my $i; my @r; my $s = $self->{_sock}; if($m) { $i = <$s>; return $self->chomp($i); } $SIG{ALRM} = sub {die}; alarm($self->{_timeout}); eval { while($i = <$s>) { #print $i; last if($i eq "\r\n"); $i =~ s/[\r\n]+$//g; push @r,$i; } alarm(0); }; alarm(0); $SIG{ALRM} = "IGNORE"; @r; } sub output($$) { my ($self,$data) = @_; my $s = $self->{_sock}; print $s $data; } sub format($$;$) { my ($self,$data) = @_; my $ret; if(ref $data eq "HASH" ) { foreach my $k (sort keys %$data) { $ret .= "$k: $data->{$k}\r\n"; } $ret .= "\r\n"; } else { $data =~ s/: [\n\r]+/: 0\r\n/g; my %h = $data =~ /^([^:]+)\s*:\s*([^\r\n]*)/mg; $ret = \%h; } $ret; } sub send($$;$$) { my ($self,$param,$reply,$id) = @_; # 0 hash 1 array 2 scalar $reply ||= 0; $self->output($self->format($param)); my $r; my @in; my $done = 0; do { @in = $self->input; if($id) { foreach(@in) { $done++ if(/ActionID: $id/); } } } while($id and ! $done and scalar @in); if($reply == 0) { $r = $self->format(join "\n",@in); } elsif($reply == 1) { $r = [@in]; } elsif($reply == 2) { $r = join "\n",@in; } $r; } sub connect($) { my $self = shift; $self->{_sock} = new IO::Socket::INET( Proto => 'tcp', PeerAddr => $self->{_host}, PeerPort => $self->{_port} ) or return $self->error("Connection refused $self->{_host} port $self->{_port}"); $self->{_sock}->autoflush(1); my $title = undef; ($title,$self->{_version}) = $self->input(1) =~ /^([^\/]+)\/(.*)/; if ($title !~ /Asterisk Call Manager/) { return $self->error("Com Error"); } my $reply = $self->send({ Action => 'Challenge', AuthType => 'MD5'}); my $auth; if($reply->{Response} eq 'Success') { my $md5 = new Digest::MD5; $md5->add($reply->{Challenge}); $md5->add($self->{_secret}); $auth = $self->send({Action => 'Login', AuthType => 'MD5', Username => $self->{_user}, Key => $md5->hexdigest, Events => $self->{_events} }); } else { $auth = $self->send({ Action => 'Login', Username => $user, Secret => $secret, Events => $self->{_events} }); } unless ($auth->{Response} eq 'Success' and $auth->{Message} eq 'Authentication accepted' ) { return $self->error("Connection Failed"); } } sub action($$;$) { my ($self,$action,$id) = @_; $id ||= $$ unless($action eq "Logoff"); $id = undef if($id == -1); my $h = { Action => $action }; $h->{ActionID} = $id if($id); $self->send($h,2,$id); } sub command($$) { my($self,$command) = @_; my $reply = $self->send({'Action' => 'Command', 'Command' => $command},2 ); $reply =~ s/Response: Follows|--END COMMAND--//gs; #my ($ret) = $reply =~ /Response: Follows(.*?)--END COMMAND--/s; $reply; } sub disconnect($) { my ($self) = @_; $self->action("Logoff"); } sub reg_event($$$) { my ($self,$event,$code) = @_; $self->{_events}->{$event} = $code; } sub reg_regex_event($$$) { my ($self,$event,$code) = @_; push @{$self->{_rx_events}},[$event,$code]; } sub reg_actionid($$$) { my ($self,$actionid,$code) = @_; $self->{_actionids}->{$actionid} = $code; } sub listen($;$) { my ($self,$timeout) = @_; my $done = 0; my $target; $target = time + $timeout if($timeout); my $count = 0; while(! $done and my $r = $self->format(join "\n",$self->input)) { if($timeout and time >= $target) { $done = 1; } #print Dumper $r; #print "$r->{Event}\n"; my $func = $self->{_events}->{$r->{Event}} || $self->{_events}->{ALL} || $self->{_actionids}->{$r->{ActionID}} || undef; if(! $func ) { foreach my $k (@{$self->{_rx_events}}) { $func = $k->[1] if($r->{Event} =~ /$k->[0]/); } } (ref $func) and &$func($r); } }