#!/usr/bin/perl 

use strict;
use warnings;

use Sys::Syslog;
use POSIX;
use Config::IniFiles;
use Authen::Radius;
Authen::Radius->load_dictionary;
use DBI;
use Date::Manip;
use threads;


my $row=0;
my @select_id;
my $dbh_local;


#
# Connection  a la base de donnees MySQL local
#
sub mysql_connect {
	my $DB_NAME="asterisk";
        my $DB_HOST="localhost";
        my $DB_USER="root";
        my $DB_PASS="password";

        $dbh_local = DBI->connect_cached("DBI:mysql:$DB_NAME:$DB_HOST", $DB_USER, $DB_PASS);
	syslog('crit', "RADIUS Could not connect to database $DB_NAME on $DB_HOST from script Resending") if !defined $dbh_local;
}


#
# Charge tous les paquets RADIUS a traiter de MySQL
#
sub load_radius_packet {
        if (defined $dbh_local) {
                my $id_request="SELECT id FROM radius_packets WHERE status='0' and send_counter<=10 and Unix_Timestamp(now())-Unix_Timestamp(creation_date)>=POW(2,i_delay) and sending_state='0'";

                my $sth = $dbh_local->prepare($id_request);
                $sth->execute();

		while (my $result = $sth->fetchrow_hashref) {
			$select_id[$row]=$result->{id};
			$row++;
		}
		$sth->finish;
	} else {
		syslog('crit', "MySQL Could not load radius packets from local database");
		mysql_connect();
		load_radius_packet();
	}
}


#
# Verrouille ou deverrouille une ligne dans la base de donnees
#
sub lock_row {
	my $id=shift;

	if (defined $dbh_local) {
		my $lock_request="UPDATE radius_packets SET sending_state='1' WHERE id='$id'";
		my $sth = $dbh_local->prepare($lock_request);
		$sth->execute();
		$sth->finish;
	} else {
		syslog('crit', "MySQL Could not lock row where id=$id to the local database");
		mysql_connect();
		lock_row($id);
	}
}


#
# Envoi les paquets RADIUS
#
sub send_packet {
	my $id=shift;
	my $request;
        my $DB_NAME="asterisk";
        my $DB_HOST="localhost";
        my $DB_USER="root";
        my $DB_PASS="password";
	my $RADIUS_Server='Master.adress.ip';
        my $RADIUS_Secret='password';

	my $dbh = DBI->connect_cached("DBI:mysql:$DB_NAME:$DB_HOST", $DB_USER, $DB_PASS);

	if (defined $dbh) {
		$request = "SELECT * FROM radius_packets WHERE id='$id'";
		my $sth = $dbh->prepare($request);
                $sth->execute();
		my $data = $sth->fetchrow_hashref;
		$sth->finish;

        	my $r = new Authen::Radius(Host => $RADIUS_Server, Secret => $RADIUS_Secret, Service => 'radius-acct');

        	if( !defined $r ) {
                	syslog('crit', "RADIUS host $RADIUS_Server ERROR for Resending");
                	return;
        	}

        	$r->clear_attributes();

        	$r->add_attributes (
                        { Name => 'Acct-Status-Type', Value => 'Stop' },
			{ Name => 'h323-call-origin', Value => 'originate' },
                        { Name => 'h323-connect-time', Value => format_date_gmt($data->{'h323_connect_time'}) },
                        { Name => 'h323-disconnect-time', Value => format_date_gmt($data->{'h323_disconnect_time'}) },
                        { Name => 'Acct-Session-Time', Value => $data->{'acct_session_time'} },
                        { Name => 'h323-disconnect-cause', Value => $data->{'h323_disconnect_cause'} },
                        { Name => 'h323-call-type', Value => 'VoIP' },
                        { Name => 'Cisco-AVPair', Value => 'session-protocol=sipv2' },
                        { Name => 'NAS-IP-Address', Value => $data->{'nas_ip_address'} },
                        { Name => 'User-Name', Value => $data->{'user_name'} },
                        { Name => 'Calling-Station-Id', Value => $data->{'calling_station_id'} },
                        { Name => 'Called-Station-Id', Value => $data->{'called_station_id'} },
                        { Name => 'Cisco-AVPair', Value => 'call-id='.$data->{'call_id'} },
                        { Name => 'h323-setup-time', Value => format_date_gmt($data->{'h323_setup_time'}) },
                        { Name => 'Cisco-AVPair', Value => $data->{'h323_conf_id'} },
                        { Name => 'h323-remote-address', Value => $data->{'h323_remote_address'} }
		);

		$r->send_packet (ACCOUNTING_REQUEST) and my $type = $r->recv_packet;
		$data->{'send_counter'}++;
		$data->{'i_delay'}++;
		$data->{'last_send'}=time;

		if (!defined $type) {
			$request="UPDATE radius_packets SET last_send='".format_date_sql($data->{'last_send'})."', i_delay='$data->{'i_delay'}', send_counter='$data->{'send_counter'}', sending_state='0' WHERE id='$data->{'id'}'";
		} else {
			$data->{'reception_date'}=time;
			$data->{'status'}=1;
			$request="UPDATE radius_packets SET reception_date='".format_date_sql($data->{'reception_date'})."', last_send='".format_date_sql($data->{'last_send'})."', i_delay='$data->{'i_delay'}', send_counter='$data->{'send_counter'}', sending_state='0', status='$data->{'status'}' WHERE id='$data->{'id'}'";
		}
		$sth = $dbh->prepare($request);
               	$sth->execute();
		$sth->finish;
	} else {
		syslog('crit', "MySQL Could not send radius packet for row where id=$id");	
	}
}


#
# sample '09:16:05 GMT Sat Dec 11 2004'
#
sub format_date_gmt {
        my ($date) = @_;
	return &UnixDate($date, "%H:%M:%S GMT %a %b %e %Y");
}


#
# sample '2004-12-11 09:16:05'
#
sub format_date_sql {
        my ($date) = @_;
        return strftime("%Y-%m-%d %H:%M:%S", localtime($date));
}

		
#
# main
#
my $j;
my $thr;
my $lock_file = '/var/run/ast-resend-lost.pid';

if( -e $lock_file ) {
	open(PID,$lock_file);
	my $pid=<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 punning pid=$pid\n";
	}
}

# set C locale
POSIX::setlocale(LC_ALL, "C");
my $runas_user = 'asterisk';
my ($name, $passwd, $uid, $gid) = getpwnam($runas_user) or die "$runas_user not in passwd file";;


# Become daemon
#
my $pid;
if( !defined($pid = fork()) ) {
	die "cannot fork: $!";
} elsif ($pid) {
# Create lockfile, and finish parent process
#
	open(PID, "> $lock_file") || die "ast-resend-lost.pl: Unable to create lockfile $lock_file\n";
	print PID "$pid";
	close PID;
	chown $uid, $gid, $lock_file;
	exit;
} else {
# daemon
	setpgrp();
	select(STDERR); $| = 1;
	select(STDOUT); $| = 1;
	openlog('ast-resend-lost', 'cons,pid', 'daemon');
	syslog('notice', "RADIUS Resend for Asterisk started");
}


# Install signall handler
#
$SIG{QUIT} = \&safe_exit;

# Drop privileges
#
setuid($uid);
$< = $uid;
$> = $uid;

while(1) {
	mysql_connect();
	load_radius_packet();
	for ($j=0;$j<$row;$j++) {
		lock_row($select_id[$j]);
		$thr=threads->new(\&send_packet, $select_id[$j]);
	}
	undef @select_id;
	$row=0;
	sleep 1;
	}

# Signal Handlers
#

sub safe_exit {
	my($sig) = @_;
	syslog('crit', "Caught a SIG$sig - shutting down");
	$dbh_local->disconnect;
	unlink $lock_file or syslog('crit', "Unable to create lockfile $lock_file\n");
	closelog();     
	exit;
}
