#!/usr/bin/perl # # Simple agi example that authenticate caller via RADIUS # # (c)2004 Porta Software Ltd. www.portaone.com # Written by: Oleksandr Kapitanenko # use Crypt::CBC; use Asterisk::AGI; use Authen::Radius; use Sys::Syslog; use Switch; #my modules use List::Util; my $return_code; my $credit_time = -1; my $hangup = 1; my @routes; my @Expire; my @Join_Expire; my $numroutes=0; my $skip_route=0; my $iexpires=0; my %expires; my $auth_cli; $AGI = new Asterisk::AGI; my %input = $AGI->ReadParse(); #$AGI->verbose("AGI Environment Dump:", 4); #foreach my $i (sort keys %input) { # $AGI->verbose(" -- $i = $input{$i}", 4); #} openlog('ast-auth', 'cons,pid', 'daemon'); syslog('notice', "Script Asterisk Authentication Started"); my %params; my(@pairs) = split(/[&;]/,$ARGV[0]); my($param,$value); foreach (@pairs) { ($param,$value) = split('=',$_,2); $param = unescape($param); $value = unescape($value); $params{$param}=$value; } $hangup = 0 if defined $params{'IfFailed'} && $params{'IfFailed'} =~ /DoNotHangup/i; #my $callid = $1 if defined $params{'CallID'} && $params{'CallID'} =~ /(.*)/; my ($a,$b,$c,$d) = ($1,$2,$3,$4) if defined $params{'H323_ID'} && $params{'H323_ID'} =~ /([0-9]+)-([0-9]+)-([0-9]+)-([0-9]+)/; my $h323_id = sprintf("%08X %08X %08X %08X",$a,$b,$c,$d); $AGI->verbose("H323 hex: $h323_id"); my $max_duration = $AGI->get_variable('max_duration'); my $r = new Authen::Radius(Host => $AGI->get_variable('RADIUS_Server'), Secret => $AGI->get_variable('RADIUS_Secret')); if( !defined $r ) { $AGI->verbose('RADIUS server "'.$AGI->get_variable('RADIUS_Server').'" ERROR', 0); $AGI->hangup() if $hangup; exit; } Authen::Radius->load_dictionary; $input{'callerid'} = $1 if defined $input{'callerid'} && $input{'callerid'} =~ /<(\d*)>/; $input{'dnid'} = $1 if defined $input{'dnid'} && $input{'dnid'} =~ /<(\d*)>/; $r->add_attributes ( { Name => 'NAS-IP-Address', Value => $AGI->get_variable('NAS_IP_Address') }, { Name => 'NAS-Port-Name', Value => $input{'channel'} }, { Name => 'Calling-Station-Id', Value => $input{'callerid'} }, { Name => 'Called-Station-Id', Value => $input{'dnid'} }, { Name => 'Cisco-AVPair', Value => "call-id=$input{'uniqueid'}" }, # { Name => 'Cisco-AVPair', Value => "call-id=$callid" }, { Name => 'Cisco-AVPair', Value => "h323-conf-id=$h323_id" } ); if( defined $params{'AuthorizeBy'} && $params{'AuthorizeBy'} =~ /sip/i ) { my $sip_auth_header = $AGI->get_variable('SIP_Authorization'); if( !defined $sip_auth_header ) { $AGI->verbose("ERROR Authorization=SIP requested but no SIP Authorization header provided.", 0); $AGI->set_variable('RADIUS_Status', 'ConfigurationError'); $AGI->hangup() if $hangup; exit; } $sip_auth_header =~ s/\s*Digest\s*//; my %sip_auth; my(@pairs) = split(/,\s?/,$sip_auth_header); my($param,$value); foreach (@pairs) { ($param,$value) = split('=',$_,2); $param = unescape($param); $value = unescape($value); $value =~ s/^"//; $value =~ s/"$//; $sip_auth{$param}=$value; } $r->add_attributes ( { Name => 'User-Name', Value => $sip_auth{'username'} } ); $r->add_attributes ( { Name => 'Digest-Attributes', Value => 'User-Name = "'.$sip_auth{'username'}.'"' } ); $r->add_attributes ( { Name => 'Digest-Attributes', Value => 'Realm = "'.$sip_auth{'realm'}.'"' } ); $r->add_attributes ( { Name => 'Digest-Attributes', Value => 'Nonce = "'.$sip_auth{'nonce'}.'"' } ); $r->add_attributes ( { Name => 'Digest-Attributes', Value => 'URI = "'.$sip_auth{'uri'}.'"' } ); $r->add_attributes ( { Name => 'Digest-Attributes', Value => 'Method = "INVITE"' } ); $r->add_attributes ( { Name => 'Digest-Attributes', Value => 'Algorithm = "'.$sip_auth{'algorithm'}.'"' } ); $r->add_attributes ( { Name => 'Digest-Response', Value => $sip_auth{'response'} } ); $AGI->set_variable('SIP_Username', $sip_auth{'username'}); } else { #TODO looks wrong, check $r->add_attributes ( { Name => 'User-Name', Value => $input{'accountcode'} } ); $r->add_attributes ( { Name => 'Password', Value => $params{'Password'} } ) if defined $params{'Password'}; } $r->add_attributes ( { Name => 'h323-ivr-out', Value => "PortaBilling_Routing:$params{Routing}" } ) if defined $params{'Routing'}; $r->send_packet (ACCESS_REQUEST) and $type = $r->recv_packet; if( !defined $type ) { $AGI->verbose("No responce from RADIUS server", 0); $AGI->set_variable('RADIUS_Status', 'NoResponce'); $AGI->hangup() if $hangup; } my $dial_info = ''; my ($ActiveFolloMe,$TypeFollowMe); my $Pred_expire=0; for $a ($r->get_attributes) { $AGI->verbose("---- attr: name=$a->{'Name'} value=$a->{'Value'}", 4); $AGI->set_variable($a->{'Name'}, $a->{'Value'}); $return_code = $a->{'Value'} if $a->{'Name'} eq 'h323-return-code'; $credit_time = List::Util::min($max_duration,$a->{'Value'}) if $a->{'Name'} eq 'h323-credit-time'; if( $a->{'Name'} eq 'Cisco-AVPair' && $a->{'Value'} =~ /^h323-ivr-in=PortaBilling_Routing:/ ) { $AGI->verbose(" -- AGI full route: $a->{'Value'}", 4); #TODO this regexp is very important, double-check $a->{'Value'} =~ /^h323-ivr-in=PortaBilling_Routing:\s*([^@]*)@([^;]*);?(.*)$/; my ($user, $contact) = ($1, $2); my %route_info; my(@pairs) = split(/[&;]/,$3); my($param,$value); foreach (@pairs) { ($param,$value) = split('=',$_,2); $param = unescape($param); $value = unescape($value); $route_info{$param}=$value; } my $cipher = Crypt::CBC->new({ 'key' => $AGI->get_variable('RADIUS_Secret'), 'cipher' => 'DES', 'iv' => '$KJh#(}q', 'regenerate_key' => 1, # default true 'padding' => 'oneandzeroes', 'prepend_iv' => 0 }); my ($authuser, $secret) = split(/\0/, $cipher->decrypt(pack("H*",$route_info{'auth'})) ); $ActiveFolloMe=1 if ($route_info{'access-code'} eq 'FOLLOWME'); switch ($route_info{'g-hunt'}) { case 'all' { $TypeFollowMe='Simultaneous';} case 'seq' { $TypeFollowMe='Sequential' ;} } if ($route_info{'expires'}) { $skip_route++; $Pred_expire=$route_info{'expires'}; } if($user ne '' and $contact ne '' and $Pred_expire) { @routes[$numroutes]=$params{'Routing'}."/".$user."@".$contact; @clis[$numroutes]=$route_info{'cli'} if defined $route_info{'cli'}; @Join_Expire[$numroutes]=$skip_route; @Expire[$numroutes]=$Pred_expire; $numroutes++; $dial_info .= '&' if $dial_info ne ''; $dial_info .= "$params{Routing}/$user"; $dial_info .= ":$secret" if defined $route_info{'auth'} && defined $secret; $dial_info .= ":$authuser" if defined $route_info{'auth'} && defined $authuser; $dial_info .= '@'.$contact; } else { $AGI->verbose(" -- AGI route skipped",4); } } if( $a->{'Name'} eq 'Cisco-AVPair' && $a->{'Value'} =~ /^h323-ivr-in=PortaBilling_CompleteNumber:/ ) { $routing_info = $a->{'Value'}; $routing_info =~ s/^h323-ivr-in=PortaBilling_CompleteNumber://; $AGI->set_variable('DNID',$routing_info); } if( $a->{'Name'} eq 'Cisco-AVPair' && $a->{'Value'} =~ /^h323-ivr-in=PortaBilling_CLI:/ ) { $routing_info = $a->{'Value'}; $routing_info =~ s/^h323-ivr-in=PortaBilling_CLI://; $auth_cli=$routing_info; } } $TypeFollowMe='List' if ($ActiveFolloMe && !$TypeFollowMe); syslog('info', "FOLLOW ME2: ActiveFolloMe=$ActiveFolloMe -> $TypeFollowMe"); my @XSUM; $AGI->set_variable('NumRoutes', $numroutes); for ($i=0;$i<$numroutes;$i++) { my $cli=$auth_cli; $cli=@clis[$i] if defined @clis[$i]; $AGI->verbose(" -- AGI set route $i: @routes[$i] CLI: $cli",4); if (@Join_Expire[$i]) { @XSUM[@Join_Expire[$i]]=0 if (!defined @XSUM[@Join_Expire[$i]]); $AGI->set_variable("XROUTE_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]", @routes[$i]); $AGI->set_variable("XEXP_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]", @Expire[$i]); $AGI->set_variable("XCLI_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]", $cli); syslog('info', "FOLLOW ME2: XROUTE_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]=@routes[$i] | XEXP_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]=@Expire[$i] | XCLI_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]=$cli") ; @XSUM[@Join_Expire[$i]]++; } } my $k=0; foreach my $vl (@XSUM) { if ($vl) { syslog('info', "FOLLOW ME2: XSUM_$k = $vl"); $AGI->set_variable("XSUM_$k", $vl); } $k++; } $k = $k - 1 ; $AGI->set_variable("XTOT", $k); syslog('info', "FOLLOW ME2: XTOT = $k"); $AGI->set_variable('ActiveFolloMe', $ActiveFolloMe); $AGI->set_variable('RADIUS_Status', 'OK'); $AGI->set_variable('Dial_Info', $dial_info) if $dial_info ne ''; $AGI->verbose(" -- AGI full dial info: $dial_info",4); $AGI->hangup() if $return_code != 0 && $return_code != 13 && $hangup; $AGI->set_autohangup($credit_time) if $credit_time != -1; exit; sub unescape { shift() if ref($_[0]); my $todecode = shift; return undef unless defined($todecode); #$todecode =~ tr/+/ /; # pluses become spaces $todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge; return $todecode; }