Asterisk 1.6 ast-rad-acc.pl

Hi,
I’ve been using asterisk 1.4 and when I upgraded to asterisk 1.6 ast-rad-acc.pl was no longer doing what I wanted.
Basically since some of the events changed names (link to bridge, and accountcode was an event itself, stuff like that)
So I tweaked a couple of things and is now working the way it used to work with asterisk 1.4
The code has many comments and writes to the log file more than it should do, sorry about it :stuck_out_tongue:
I wanted to share it, since it may be helpful for others, though it’s for an old version of asterisk

#!/usr/bin/perl

Based on mail.newmmc.com/~wsmith/astcdr author is unknown,

found here voip-info.org/wiki-Asterisk+AGI

RADIUS Accounting By

©2004 Porta Software Ltd. portaone.com

Oleksandr Kapitanenko kapitan@portaone.com

use strict;
use Asterisk::Manager;
use Sys::Syslog;
use POSIX;
use Config::IniFiles;
use Authen::Radius;
use Date::Manip;
use Data::Dumper;
Authen::Radius->load_dictionary;

Lock file

my $lock_file = ‘/var/run/ast-rad-acc.pid’;

Config vars

my $runas_user = ‘nobody’;

my $ast_hostname = ‘localhost’;
my $ast_username = ‘*****’;
my $ast_password = ‘****’;

my $monitor_dir = ‘/var/spool/asterisk/monitor’;

Log del script

my $log_file = ‘/var/log/asterisk/radius/ast-rad-acc.log’;
my $log_file_packets = ‘/var/log/asterisk/radius/ast-rad-acc_packets.log’;

Read global RADIUS configuratuin from extensions.conf

I havte doing that but Asterisk manager interface can not read global variables

my $config_dir = ‘/etc/asterisk’;

Globals

my %channels;
my ($rad_serv, $rad_sec, $nas_ip);

Check if already running

if( -e $lock_file ) {
open(PID,$lock_file);
my $pid=;
close PID;
chomp $pid;
if( !-e “/proc/$pid” ) {
print STDERR “Lock file present, but no process with pid=$pid.\n”;
die “Can’t delete lock file $lock_file\n” if !unlink $lock_file;
print STDERR “Lock file has been removed.\n”;
} else {
die “Lockfile present, another copy is running pid=$pid\n”;
}
}

load_config();
my ($name, $passwd, $uid, $gid) = getpwnam($runas_user) or die “$runas_user not in passwd file”;;

Become daemon

&debug_acc(“Starting”);
my $pid;

Create lockfile, and finish parent process

open(PID, “> $lock_file”) || die “ast-rad-acc.pl: Unable to create lockfile $lock_file\n”;
print PID “$$”;
close PID;
chown $uid, $gid, $lock_file;
setpgrp();
select(STDERR); $| = 1;
select(STDOUT); $| = 1;
openlog(‘ast-rad-acc’, ‘cons,pid’, ‘daemon’);
&debug_acc(“Daemon started”);
syslog(‘notice’, “RADIUS accounting for Asterisk started”);

&debug_acc(“Install handlers”);

Install signall handler

$SIG{INT} = &safe_exit;
$SIG{QUIT} = &safe_exit;
$SIG{TERM} = &safe_exit;
$SIG{HUP} = &load_config;

Drop privileges

&debug_acc(“Drop privileges $uid”);

setuid($uid);
&debug_acc(“UID set”);

$< = $uid;
&debug_acc("< set");
$> = $uid;
&debug_acc(“Creating Asterisk::Manager”);
my $astman = new Asterisk::Manager;
&debug_acc(“Created”);
$astman->user($ast_username);
$astman->secret($ast_password);
$astman->host($ast_hostname);
&debug_acc(“Connecting”);

my $ast_connected = 1;
while( 1 ) {
if( $astman->connect ) {
$ast_connected = 1;
syslog(‘info’, ‘Connected to Asterisk!’);
&debug_acc(“Connected to asterisk”);
$astman->setcallback(‘DEFAULT’, &status_callback);
eval { $astman->eventloop; };
} else {
syslog(‘err’, ‘Could not connect to Asterisk!’) if $ast_connected;
$ast_connected = 0;
&debug_acc(“Could not connect to Asterisk”) if $ast_connected;

}
sleep 1;

}

sub status_callback {
my (%event) = @_;

return unless defined(%event);

    foreach (keys %event) {
            syslog('debug', "$_:== ". $event{$_} . "\n" );
    }
    syslog('debug', "\n");

my ($i,$j);

foreach $i (sort keys %event) {
    &debug_acc("$i ==> $event{$i}");
}
&debug_acc(""); # separo con un \n
&debug_acc(""); # separo con un \n

for ($event{'Event'}) {

Variable read example

print STDERR $astman->sendcommand( Action => ‘Getvar’, Channel => $event{‘Channel’}, Variable => ‘DNID’ );

	$event{'CallerIDNum'} = $1 if defined $event{'CallerIDNum'}  && $event{'CallerIDNum'} =~ /(\d*)/;
	/newchannel/i && do {
	        my $call_origin = "originate";
		$call_origin = "answer" if $event{'State'} =~ /^Ring$/i;

		my $call_type = "VoIP";
                    $call_type = "Telephony" if $event{'Channel'} =~ /^(Zap)|(VPB)|(phone)|(Modem)|(CAPI)|(mISDN)|(Console)/;

session-protocol

other, cisco, h323, multicast, sipv2, sdp, frf11-trunk, cisco-switched, MarsAnalog, C1000Isdn, aal2-trunk

		my $protocol = 'other';
		$protocol = 'sipv2' if $event{'Channel'} =~ /^SIP/i;
		$protocol = 'h323' if $event{'Channel'} =~ /^h323/i;

      syslog('crit', "Event\n");
  foreach (keys %event) {
      syslog('crit', "$_ => $event{$_}\n");
  }

		$channels{$event{'Channel'}} = { 
			'CHANNEL' => $event{'Channel'},
			'CALLERID' => $event{'CallerIDNum'},
			'DNID' => $event{'DNID'},
			'UNIQUEID' => $event{'Uniqueid'},
			'CALL_START' => time(),
			'LINK_START' => time(),
			'LINK_END' => time(),
			'CALL_ORIGIN' => $call_origin,
			'CALL_TYPE' => $call_type,
			'CALL_PROTOCOL' => $protocol,
			'CALL_ID' => $event{'Uniqueid'},
			'RADIUS_Server' => $rad_serv,
			'RADIUS_Secret' => $rad_sec,
			'NAS_IP_Address' => $nas_ip
		};
		$channels{$event{'Channel'}}{'Remoteip'} = $event{'Remoteip'} if defined $event{'Remoteip'};
	};

	/newaccountcode/i && do {

		$channels{$event{'Channel'}}{'ACCOUNTCODE'} = $event{'AccountCode'};
		# Al setear el CDR, mando el answer start
		$channels{$event{'Channel'}}{'CALL_ORIGIN'}='answer';
		$channels{$event{'Channel'}}{'Status_Type'} = 'Start';
		
	};

	/^dial$/i && do {
		
                    if( defined $event{'SubEvent'} && $event{'SubEvent'} eq 'Begin') {
			$event{'Dialstring'} =~ /^(\d+)/;
			my $dnid = $1;
			print "EL DNID es $dnid\n";
			$channels{$event{'Channel'}}{'DNID'} = $dnid;
			&send_acc(%{$channels{$event{'Channel'}}});
		}
	};

	/newexten/i && do {
		$channels{$event{'Channel'}}{'DNID'} = $event{'DNID'} if defined $event{'DNID'};

                    if( defined $event{'Application'} && $event{'Application'} eq 'Set') {
			my ( $_var, $_val ) = split(/=/, $event{'AppData'});
			if($_var eq 'CDR(accountcode)') {
				$channels{$event{'Channel'}}{'ACCOUNTCODE'} = $_val;
				# Al setear el CDR, mando el answer start
				$channels{$event{'Channel'}}{'CALL_ORIGIN'}='answer';
				$channels{$event{'Channel'}}{'Status_Type'} = 'Start';
				&send_acc(%{$channels{$event{'Channel'}}});


			} else {
				$channels{$event{'Channel'}}{$_var} = $_val;
			}
		}
	};

            /Newcallerid/i && do {
                    $channels{$event{'Channel'}}{'CALLERID'} = $event{'CallerIDNum'} if defined $event{'CallerIDNum'};
            };

            /newstate/i && do {
                    $channels{$event{'Channel'}}{'CALLERID'} = $event{'CallerIDNum'} if defined $event{'CallerIDNum'};
		$channels{$event{'Channel'}}{'DNID'} = $event{'DNID'} if defined $event{'DNID'};
            };

#	/^link$/i && do {
	/^bridge/i && do {  # cambio frank

	    my $channel = $event{'Channel1'};
		return unless $channels{$channel};
		$channels{$channel}{'DSTCHANNEL'} = $event{'Channel2'};
		$channels{$channel}{'LINK_START'} = time();
		$channels{$event{'Channel2'}}{'LINK_START'} = time();
		$channels{$event{'Channel1'}}{'CALL_ID'} = $event{'Uniqueid1'};
		$channels{$event{'Channel2'}}{'CALL_ID'} = $event{'Uniqueid1'};
		$channels{$event{'Channel1'}}{'ACCOUNTCODE'} = $channels{$event{'Channel1'}}{'CALLERID'}
			if !defined $channels{$event{'Channel1'}}{'ACCOUNTCODE'}; 
		$channels{$event{'Channel2'}}{'ACCOUNTCODE'} = $channels{$event{'Channel1'}}{'ACCOUNTCODE'};


		syslog('info', 'Link without uniqueid') unless $channels{$channel}{'UNIQUE'};
		syslog('info', 'Link without channeld') unless $channel;
	        syslog('info', 'Link on undefined channel') unless $channels{$channel};

	    foreach $j (keys %{$channels{$event{'Channel1'}}}) {
		&debug_acc("link1: $j => $channels{$event{'Channel1'}}{$j}");
	    }
	    &debug_acc("\n"); # separo con un \n
	    foreach $j (keys %{$channels{$event{'Channel2'}}}) {
		&debug_acc("link2: $j => $channels{$event{'Channel2'}}{$j}");
	    }
	    &debug_acc("\n"); # separo con un \n

	    # Sergio - Copio el callerid del canal 1 al canal 2
	    $channels{$event{'Channel2'}}{'CALLERID'} =
		$channels{$event{'Channel1'}}{'CALLERID'} = 
		$event{'CallerID1'};

	    # Frank - Copio el DNID del canal 1 al canal 2			
	    $channels{$event{'Channel2'}}{'DNID'} = $channels{$event{'Channel1'}}{'DNID'};

	    $channels{$event{'Channel1'}}{'CALL_ORIGIN'}='answer';
	    $channels{$event{'Channel2'}}{'CALL_ORIGIN'}='originate';


	    # Mando Acct Start (VoIP answer y VoIP originate)
	    $channels{$event{'Channel1'}}{'Status_Type'} = $channels{$event{'Channel2'}}{'Status_Type'} = 'Start';
	    # &send_acc(%{$channels{$event{'Channel1'}}});
	    &send_acc(%{$channels{$event{'Channel2'}}});

	    return unless($channel && $channels{$channel} && $channels{$channel}{'UNIQUEID'});
	};

	/^unlink$/i && do {
		my $channel = $event{'Channel1'};
		return unless $channels{$channel};
		$channels{$event{'Channel1'}}{'LINK_END'} = time();
		$channels{$event{'Channel2'}}{'LINK_END'} = time();

		syslog('info', 'UnLink without uniqueid') unless $channels{$channel}{'UNIQUE'};
		syslog('info', 'UnLink without channeld') unless $channel;
		syslog('info', 'UnLink on undefined channel') unless $channels{$channel};

		return unless($channel && $channels{$channel} && $channels{$channel}{'UNIQUEID'});
	};

	/hangup/i && do {
		my $channel = $event{'Channel'};
		return unless $channels{$channel};
		&debug_acc("Hangup channel");

		$channels{$event{'Channel'}}{'CALL_END'} = time();
		$channels{$event{'Channel'}}{'CAUSE'} = 16; # 0x10 = 16 es Normal call Clearing
		$channels{$event{'Channel'}}{'CAUSE'} = $event{'Cause'} if defined $event{'Cause'};

		$channels{$event{'Channel'}}{'Status_Type'} = 'Stop';
		&send_acc(%{$channels{$event{'Channel'}}});
		delete $channels{$event{'Channel'}};
	};

            /shutdown/i && do {
		die 'ast-rad-acc: Asterisk disconnect';	
            };

            /Reload/i && do {
                    load_config();
            };
}

}

sub debug_acc {
my ($msg) = @_;
open O, “>>$log_file” || die(“no pude abrir archivo log $!”);
my $d=&UnixDate(“today”,"%Y-%m-%d %H:%M");
print O “$d $msg\n”;
close O;

}

sub send_acc {
my (%cdr) = @_;
# Manda un paquete de Acct al servidor radius
# en $cdr{‘Status_Type’} tengo si es un Start o un Stop

&debug_acc("status_Type me vino: ".$cdr{'Status_Type'});
&debug_acc("CALL_ORIGIN me vino: ".$cdr{'CALL_ORIGIN'});

my $k;
open O, ">>$log_file_packets";
foreach $k (sort keys %cdr) {
    print O "$k => $cdr{$k}\n";
}
print O "\n";
close O;
&debug_acc("Sending RADIUS msg");

my $r = new Authen::Radius(Host => $cdr{‘RADIUS_Server’}, Secret => $cdr{‘RADIUS_Secret’}, Service => “radius-acct”);
if( !defined $r ) {
syslog(‘crit’, “RADIUS host ‘$cdr{‘RADIUS_Server’}’ ERROR”);
&debug_acc(“RADIUS host ‘$cdr{‘RADIUS_Server’}’ ERROR”);
return;
}

$r->clear_attributes();
my $called_number=$cdr{‘DNID’};
if ($called_number =~ /^0011/) {
$called_number =~ s/^0011…/00/;
syslog(‘debug’, “called_number is now $called_number”);
}
if ($called_number =~ /^11/) {
$called_number =~ s/^11…/00/;
syslog(‘debug’, “called_number is now $called_number”);
}
$r->add_attributes (
{ Name => ‘NAS-IP-Address’, Value => $cdr{‘NAS_IP_Address’} },
{ Name => ‘NAS-Port-Name’, Value => $cdr{‘CHANNEL’} },
{ Name => ‘User-Name’, Value => $cdr{‘ACCOUNTCODE’} },
{ Name => ‘Password’, Value => ‘123’ },
{ Name => ‘Calling-Station-Id’, Value => $cdr{‘CALLERID’} },
{ Name => ‘Called-Station-Id’, Value => $called_number },
{ Name => ‘Acct-Status-Type’, Value => $cdr{‘Status_Type’} },
{ Name => ‘h323-call-type’, Value => $cdr{‘CALL_TYPE’} },
{ Name => ‘h323-call-origin’, Value => $cdr{‘CALL_ORIGIN’} },
{ Name => ‘h323-setup-time’, Value => format_date($cdr{‘CALL_START’}) },
{ Name => ‘h323-connect-time’, Value => format_date($cdr{‘LINK_START’}) },
{ Name => ‘h323-disconnect-time’, Value => format_date($cdr{‘LINK_END’}) },
{ Name => ‘h323-disconnect-cause’, Value => sprintf(’%x’, $cdr{‘CAUSE’}) }, # El RADIUS espera este valor en hexa
{ Name => ‘h323-voice-quality’, Value => ‘0’ },
{ Name => ‘Cisco-AVPair’, Value => “session-protocol=$cdr{‘CALL_PROTOCOL’}” },
{ Name => ‘Cisco-AVPair’, Value => “call-id=$cdr{‘CALL_ID’}” },
{ Name => ‘Acct-Session-Id’, Value => ( ($cdr{‘CALL_ORIGIN’} eq ‘originate’) ? ‘2’:’’ ) . $cdr{‘CALL_ID’} },
{ Name => ‘h323-conf-id’, Value => “$cdr{‘CALL_ID’} D A C Id3” },

{ Name => 'Acct-Session-Time', Value => $cdr{'LINK_END'} - $cdr{'LINK_START'} },

);
$r->add_attributes ( { Name => ‘h323-remote-address’, Value => $cdr{‘Remoteip’} } ) if defined $cdr{‘Remoteip’};

$r->send_packet (ACCOUNTING_REQUEST) and my $type = $r->recv_packet;
if (!defined $type) {
syslog(‘crit’, “No response from RADIUS server”) ;
sleep(1);
$r->send_packet (ACCOUNTING_REQUEST) and my $type = $r->recv_packet;
syslog(‘crit’, “No response from RADIUS server (2nd attempt)”) ;
}
if (0) {
$r = new Authen::Radius(Host => ‘localhost’, Secret => ‘mysecret’, Service => “radius-acct”);
$r->clear_attributes();
$r->add_attributes (
{ Name => ‘NAS-IP-Address’, Value => $cdr{‘NAS_IP_Address’} },
{ Name => ‘NAS-Port-Name’, Value => $cdr{‘CHANNEL’} },
{ Name => ‘User-Name’, Value => $cdr{‘ACCOUNTCODE’} },
{ Name => ‘Password’, Value => ‘123’ },
{ Name => ‘Calling-Station-Id’, Value => $cdr{‘CALLERID’} },
{ Name => ‘Called-Station-Id’, Value => $called_number },
{ Name => ‘Acct-Status-Type’, Value => $cdr{‘Status_Type’} },
{ Name => ‘h323-call-type’, Value => $cdr{‘CALL_TYPE’} },
{ Name => ‘h323-call-origin’, Value => $cdr{‘CALL_ORIGIN’} },
{ Name => ‘h323-setup-time’, Value => format_date($cdr{‘CALL_START’}) },
{ Name => ‘h323-connect-time’, Value => format_date($cdr{‘LINK_START’}) },
{ Name => ‘h323-disconnect-time’, Value => format_date($cdr{‘LINK_END’}) },
{ Name => ‘h323-disconnect-cause’, Value => sprintf(’%x’, $cdr{‘CAUSE’}) }, # El RADIUS espera este valor en hexa
{ Name => ‘h323-voice-quality’, Value => ‘0’ },
{ Name => ‘Cisco-AVPair’, Value => “session-protocol=$cdr{‘CALL_PROTOCOL’}” },
{ Name => ‘Cisco-AVPair’, Value => “call-id=$cdr{‘CALL_ID’}” },
{ Name => ‘Acct-Session-Id’, Value => ( ($cdr{‘CALL_ORIGIN’} eq ‘originate’) ? ‘2’:’’ ) . $cdr{‘CALL_ID’} },
{ Name => ‘h323-conf-id’, Value => “$cdr{‘CALL_ID’} D A C Id3” },

			{ Name => 'Acct-Session-Time', Value => $cdr{'LINK_END'} - $cdr{'LINK_START'} },
			);
    $r->add_attributes ( { Name => 'h323-remote-address', Value => $cdr{'Remoteip'} } ) if defined $cdr{'Remoteip'};
    
    $r->send_packet (ACCOUNTING_REQUEST) and my $type = $r->recv_packet;
}
}

sample ‘09:16:05 GMT Sat Dec 11 2004’

sub format_date {
my ($date) = @_;
return strftime “%H:%M:%S GMT %a %b %e %Y”, gmtime($date);
}

Signal Handlers

sub safe_exit {
my($sig) = @_;
syslog(‘crit’, “Caught a SIG$sig - shutting down”);

$astman->disconnect if $ast_connected;
unlink $lock_file or syslog(‘crit’, “Unable to create lockfile $lock_file\n”);
closelog();
exit;
}

sub load_config {
my $conf=Config::IniFiles->new(-file => “$config_dir/extensions.conf”);
print “@Config::IniFiles::errors”;
syslog(‘crit’, “Config file error $config_dir/extensions.conf!”) if !defined $conf;
$rad_serv = $conf->val(‘globals’,‘RADIUS_Server’);
$rad_sec = $conf->val(‘globals’,‘RADIUS_Secret’);
$nas_ip = $conf->val(‘globals’,‘NAS_IP_Address’);
syslog(‘notice’, “extensions.conf loaded”);
}

I used Dumper to debug the code, so you should probably erase those lines…