#!/usr/bin/perl
#
# Simple agi example that authenticate caller via RADIUS
#
# (c)2004 Porta Software Ltd. www.portaone.com
# Written by: Oleksandr Kapitanenko <kapitan@portaone.com>
#

use Crypt::CBC;
use Asterisk::AGI;
use Authen::Radius;
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);
#}

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);
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);
		@XSUM[@Join_Expire[$i]]++;
		}	
}
my $k=0;
foreach my $vl (@XSUM)
		{
		if ($vl)
			{
			$AGI->set_variable("XSUM_$k", $vl);
			}
		$k++;
		}

$AGI->set_variable("XTOT", $k);

$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;

# unescape URL-encoded data
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;
}
