diff --git a/acct-filter b/acct-filter deleted file mode 100755 index 0d6061ed683369cc8f8e56ab25b9b6c6fea060a3..0000000000000000000000000000000000000000 --- a/acct-filter +++ /dev/null @@ -1,893 +0,0 @@ -#!/usr/bin/perl -w -# Author: Nigel Kukard <nkukard@lbsd.net> -# Date: 17/04/2007 -# Desc: Accounting filter for GNU Radius -# License: GPL - -use strict; -use Benchmark; -use Getopt::Long; -use DateTime; -use Time::HiRes qw( gettimeofday tv_interval ); - -use MIME::Lite; - -# Set library directory -use lib qw(../../); - -use sm::config; -use sm::dblayer; - -# Radius stuff -use Authen::Radius; - -# Common stuff -require("common.pm"); - -use Data::Dumper; - - -# Notify constants -use constant { - NOTIFY_CHECK => 1, - NOTIFY_RESET => 2, -}; - - -my %optctl = (); - -GetOptions(\%optctl, "help"); - - -# Check if user wants usage -if (defined($optctl{'help'})) { - displayUsage(); -} - -# Open up logfile -my $logfile = "/var/log/radius/acct-filter"; -open(FH,">> $logfile") or die "Failed to open '$logfile': $!"; - - -# Load radius dictionaries -Authen::Radius->load_dictionary("raddb/dictionary"); - - -# Databases -my $dbh; # Authentication -my $dbh_log; # Logs - -# Get db handle -$dbh = sm::dbilayer->new($cfg_db_DSN, $cfg_db_Username, $cfg_db_Password); -if (!$dbh) { - print(STDERR "Error creating database object: ".sm::dbilayer->internalErr()); - exit 1; -} - -# Connect to database -if ($dbh->connect() != 0) { - print(STDERR "Error connecting to database: ".$dbh->err); - exit 1; -} - -# Check if we must use split db's -if (defined($cfg_radiuslog_db_DSN)) { - # Get log db handle - $dbh_log = sm::dbilayer->new($cfg_radiuslog_db_DSN, $cfg_radiuslog_db_Username, $cfg_radiuslog_db_Password); - if (!$dbh_log) { - print(STDERR "Error creating database object: ".sm::dbilayer->internalErr()); - exit 1; - } - - # Connect to database - if ($dbh_log->connect() != 0) { - print(STDERR "Error connecting to database: ".$dbh_log->err); - exit 1; - } -# If not use the main DB -} else { - $dbh_log = $dbh; -} - - - -# Signal handler for dead children -use POSIX "WNOHANG"; -sub REAPER { - my $child; - # If a second child dies while in the signal handler caused by the - # first death, we won't get another signal. So must loop here else - # we will leave the unreaped child as a zombie. And the next time - # two children die we get another zombie. And so on. - while (($child = waitpid(-1,WNOHANG)) > 0) { -# $Kid_Status{$child} = $?; - 1; - } - $SIG{CHLD} = \&REAPER; # still loathe sysV -} -$SIG{CHLD} = \&REAPER; - - - -# No buffering -select((select(FH), $| = 1)[0]); -select((select(STDOUT), $| = 1)[0]); - - -# Handled requests -my $requests = 0; - -# Loop with input -while (my $line = <STDIN>) { - my %acct; - my @acct; - - - # Inc number of requests - $requests++; - - # Munch off \n - chomp($line); - - # Check number of results - if ((@acct = split /:/, $line) != 21) { - print(FH "ERROR: Number of params from radiusd: ".(@acct)."/$line\n"); - goto END; - } - - # Pull in request - ($acct{'User-Name'},$acct{'Status-Type'},$acct{'Acct-Session-Id'},$acct{'NAS-IP-Address'},$acct{'NAS-Port-Type'},$acct{'NAS-Port'}, - $acct{'Called-Station-Id'},$acct{'Calling-Station-Id'},$acct{'Delay'},$acct{'Acct-Session-Time'}, - $acct{'Acct-Input-Octets'},$acct{'Acct-Output-Octets'},$acct{'Acct-Input-Gigawords'},$acct{'Acct-Output-Gigawords'}, - $acct{'Ascend-Xmit-Rate'},$acct{'Ascend-Data-Rate'},$acct{'Framed-IP-Address'},$acct{'Connect-Info'},$acct{'Service-Type'}, - $acct{'Class'},$acct{'Acct-Terminate-Cause'}) = @acct; - - # Pull timestamp - my $dt = DateTime->from_epoch( epoch => time() ); - $acct{'Timestamp'} = $dt->strftime('%Y-%m-%d %H:%M:%S'); - - my $timer0 = [gettimeofday]; - - # Grab user details - my $userData = getUser($dbh,$acct{'User-Name'}); - if (ref $userData ne "HASH") { - print(FH "ERROR: $userData\n"); - $userData->{'AgentID'} = 0; - $userData->{'RadiusClassID'} = 0; - $userData->{'UsageCap'} = 0; - } - - printf(FH 'ACCT - AgentID: %s, ClassID: %s, User-Name: %s, Status-Type: %s, Timestamp: %s, Acct-Session-Id: %s, NAS-IP-Address: %s, NAS-Port-Type: %s, NAS-Port: %s, Called-Station-Id: %s, Calling-Station-Id: %s, Delay: %s, Acct-Session-Time: %s, Acct-Input-Octets: %s, Acct-Output-Octets: %s, Acct-Input-Gigawords: %s, Acct-Output-Gigawords: %s, Ascend-Xmit-Rate: %s, Ascend-Data-Rate: %s, Framed-IP-Address: %s, Connect-Info: %s, Service-Type: %s, Class: %s, Acct-Terminate-Cause: %s'."\n",$userData->{'AgentID'},$userData->{'RadiusClassID'},$acct{'User-Name'},$acct{'Status-Type'},$acct{'Timestamp'},$acct{'Acct-Session-Id'},$acct{'NAS-IP-Address'},$acct{'NAS-Port-Type'},$acct{'NAS-Port'},$acct{'Called-Station-Id'},$acct{'Calling-Station-Id'},$acct{'Delay'},$acct{'Acct-Session-Time'},$acct{'Acct-Input-Octets'},$acct{'Acct-Output-Octets'},$acct{'Acct-Input-Gigawords'},$acct{'Acct-Output-Gigawords'},$acct{'Ascend-Xmit-Rate'},$acct{'Ascend-Data-Rate'},$acct{'Framed-IP-Address'},$acct{'Connect-Info'},$acct{'Service-Type'},$acct{'Class'},$acct{'Acct-Terminate-Cause'}); - - # IF ADSL (ADSL specific stuff) - if ($acct{'NAS-Port-Type'} eq "5") { - # Calculate dates - my $date = DateTime->from_epoch( epoch => time() ); - my $today = $date->ymd(); - $date->set_day(1); - my $thismonth = $date->ymd(); - $date->add( months => 1); - my $nextmonth = $date->ymd(); - - my $extraQuery = ""; - - - # NULL - uncapped, no limits - if (!defined($userData->{'UsageCap'})) { - $extraQuery = "AND Timestamp > ".$dbh->quote($thismonth)." AND Timestamp < ".$dbh->quote($nextmonth); - - # > 0 - normal cap, check usage for this month, check topups for this month - # Calculate cap user has (acctinputoctets + (2^32 * gigawords)) /1024 / 1024 - # Calculate usage user has - } elsif ($userData->{'UsageCap'} > 0) { - $extraQuery = "AND Timestamp > ".$dbh->quote($thismonth)." AND Timestamp < ".$dbh->quote($nextmonth); - - # 0 - topup account - } elsif ($userData->{'UsageCap'} == 0) { - - } - - # Grab users usage - my $usageData = getUsage($dbh_log,$acct{'User-Name'},$extraQuery); - if (ref $usageData ne "HASH") { - print(FH "ERROR: $usageData\n"); -# print(STDOUT "1\n"); - goto END; - } - $userData->{'TotalUsage'} = $usageData->{'Total'}; - - # Only total up this month - $extraQuery = ""; - if (defined($userData->{'UsageCap'}) && $userData->{'UsageCap'} > 0) { - $extraQuery = "AND ValidFrom <= ".$dbh->quote($today)." AND ValidTo > ".$dbh->quote($today); - } - # Get how much we've been topped up - my $topupData = getTopups($dbh,$acct{'User-Name'},$extraQuery); - if (ref $topupData ne "HASH") { - print(FH "ERROR: $topupData\n"); -# print(STDOUT "1\n"); - goto END; - } - $userData->{'Topups'} = $topupData->{'Total'}; - - $userData->{'SessUsage'} = 0; - # If we updating or disconnecting, calculate the session usage so far - if ($acct{'Status-Type'} == 2 || $acct{'Status-Type'} == 3) { - # Check how much data we used in this session - my $sessUsage = 0; - if (defined($acct{'Acct-Input-Octets'}) && $acct{'Acct-Input-Octets'} > 0) { - $sessUsage += $acct{'Acct-Input-Octets'} / 1024 / 1024; - } - if (defined($acct{'Acct-Input-Gigawords'}) && $acct{'Acct-Input-Gigawords'} > 0) { - $sessUsage += $acct{'Acct-Input-Gigawords'} * 4096; - } - # Add up output - if (defined($acct{'Acct-Output-Octets'}) && $acct{'Acct-Output-Octets'} > 0) { - $sessUsage += $acct{'Acct-Output-Octets'} / 1024 / 1024; - } - if (defined($acct{'Acct-Output-Gigawords'}) && $acct{'Acct-Output-Gigawords'} > 0) { - $sessUsage += $acct{'Acct-Output-Gigawords'} * 4096; - } - $userData->{'SessUsage'} = ceil($sessUsage); - } - - # Print usage stats - printf(FH ' - Usage => Total: %s, Session: %s, Cap: %s, Topups: %s'."\n", - $userData->{'TotalUsage'}, - $userData->{'SessUsage'}, - defined($userData->{'UsageCap'}) ? $userData->{'UsageCap'} : "uncapped", - $userData->{'Topups'}, - ); - - # Capping & usage predictions - my $totalCap = !defined($userData->{'UsageCap'}) ? 0 : $userData->{'UsageCap'} + $userData->{'Topups'}; - if (defined($userData->{'UsageCap'}) && $userData->{'UsageCap'} >= 0) { - my $exceeded = 0; - - # Checking if we updating or stopping the session, if we are, check capping - if ($totalCap <= $userData->{'TotalUsage'} && ($acct{'Status-Type'} == 2 || $acct{'Status-Type'} == 3)) { - print(FH " - TEST: User has exceeded cap by ".($userData->{'TotalUsage'} - $totalCap)."Mbyte\n"); - - # If this is an update, user is still logged in, so disconnect them - if ($userData->{'CappingType'} == 1 && $acct{'Status-Type'} == 3) { - print(FH " - TEST: User is still logged in, disconnecting\n"); - disconnectUser($dbh,\*FH,$userData,\%acct); - - # If user just got kicked off, notify them - } elsif ($userData->{'CappingType'} == 1 && $acct{'Status-Type'} == 2) { - print(FH " - TEST: Notifying user\n"); - # We reset notifications, cause the user MUST get this and we dont mind updating him in future - notifyUser($dbh,$dbh_log,\*FH,$userData,NOTIFY_RESET, - sprintf('Username %s has been capped, please contact your ISP should you need a topup',$userData->{'Username'}), - ); - } - - $exceeded = 1; - } - - # Check if we may exceed the cap in the next hour, we need at least 1hrs of data for accuracy - if (!$exceeded && $acct{'Acct-Session-Time'} > 3600) { - my $perSecUsage = $userData->{'SessUsage'} / $acct{'Acct-Session-Time'}; - my $hrPredict = sprintf('%.2f',($perSecUsage * 3600)); - - print(FH " - User is predicted to use ${hrPredict}Mb in the next hour\n"); - # If user is infact predicted to exceed, notify them - if ($totalCap <= $userData->{'TotalUsage'} + $hrPredict) { - print(FH " - This will exceed users cap, notifying user\n"); - notifyUser($dbh,$dbh_log,\*FH,$userData,NOTIFY_CHECK, - sprintf('Username %s may be capped in the next hour based on current usage stats, please contact your ISP should you need a topup', - $userData->{'Username'}), - ); - } - } - } - } - - # START - if ($acct{'Status-Type'} == 1) { - # Start accounting - my $res = $dbh_log->do(" - INSERT INTO radiusLogs - ( - AgentID, - Username, - RadiusClassID, - UsageCap, - Topups, - Timestamp, - AcctDelayTime, - AcctSessionID, - NASIPAddress, - NASPortType, - NASPort, - CalledStationID, - CallingStationID, - ConnectInfo, - ServiceType, - Class, - FramedIPAddress, - Status - ) - VALUES - ( - ".$dbh_log->quote($userData->{'AgentID'}).", - ".$dbh_log->quote($acct{'User-Name'}).", - ".$dbh_log->quote($userData->{'RadiusClassID'}).", - ".$dbh_log->quote($userData->{'UsageCap'}).", - ".$dbh_log->quote($userData->{'Topups'}).", - ".$dbh_log->quote($acct{'Timestamp'}).", - ".$dbh_log->quote($acct{'Delay'}).", - ".$dbh_log->quote($acct{'Acct-Session-Id'}).", - ".$dbh_log->quote($acct{'NAS-IP-Address'}).", - ".$dbh_log->quote($acct{'NAS-Port-Type'}).", - ".$dbh_log->quote($acct{'NAS-Port'}).", - ".$dbh_log->quote($acct{'Called-Station-Id'}).", - ".$dbh_log->quote($acct{'Calling-Station-Id'}).", - ".$dbh_log->quote($acct{'Connect-Info'}).", - ".$dbh_log->quote($acct{'Service-Type'}).", - ".$dbh_log->quote($acct{'Class'}).", - ".$dbh_log->quote($acct{'Framed-IP-Address'}).", - ".$dbh_log->quote(1)." - ) - "); - if (!$res) { - print(FH "ERROR: Failed to insert radius auth fail data: ".$dbh_log->err."\n"); -# print(STDOUT "1\n"); - goto END; - } - - # STOP - } elsif ($acct{'Status-Type'} == 2) { - # Stop accounting - my $res = $dbh_log->do(" - UPDATE radiusLogs - SET - Status = ".$dbh_log->quote(3).", - NASTransmitRate = ".$dbh_log->quote($acct{'Ascend-Xmit-Rate'}).", - NASReceiveRate = ".$dbh_log->quote($acct{'Ascend-Data-Rate'}).", - AcctSessionTime = ".$dbh_log->quote($acct{'Acct-Session-Time'}).", - AcctInputOctets = ".$dbh_log->quote($acct{'Acct-Input-Octets'}).", - AcctOutputOctets = ".$dbh_log->quote($acct{'Acct-Output-Octets'}).", - AcctInputGigawords = ".$dbh_log->quote($acct{'Acct-Input-Gigawords'}).", - AcctOutputGigawords = ".$dbh_log->quote($acct{'Acct-Output-Gigawords'}).", - ConnectTermReason = ".$dbh_log->quote($acct{'Acct-Terminate-Cause'}).", - LastAccUpdate = ".$dbh_log->quote($acct{'Timestamp'})." - WHERE - Username = ".$dbh_log->quote($acct{'User-Name'})." - AND AcctSessionID = ".$dbh_log->quote($acct{'Acct-Session-Id'})." - AND NASIPAddress = ".$dbh_log->quote($acct{'NAS-IP-Address'})." - "); - if (!$res) { - print(FH "ERROR: Failed to update stop accounting data: ".$dbh_log->err."\n"); -# print(STDOUT "1\n"); - goto END; - } - - $res = 0 if ($res eq "0E0"); - - print(FH " - Rows updated: $res\n"); - - # Check if we updated duplicates, if we did, fix them - if ($res > 1) { - fixDuplicates(\%acct); - } - - # UPDATE - } elsif ($acct{'Status-Type'} == 3) { - # Update accounting - my $res = $dbh_log->do(" - UPDATE radiusLogs - SET - Status = ".$dbh_log->quote(2).", - NASTransmitRate = ".$dbh_log->quote($acct{'Ascend-Xmit-Rate'}).", - NASReceiveRate = ".$dbh_log->quote($acct{'Ascend-Data-Rate'}).", - AcctSessionTime = ".$dbh_log->quote($acct{'Acct-Session-Time'}).", - AcctInputOctets = ".$dbh_log->quote($acct{'Acct-Input-Octets'}).", - AcctOutputOctets = ".$dbh_log->quote($acct{'Acct-Output-Octets'}).", - AcctInputGigawords = ".$dbh_log->quote($acct{'Acct-Input-Gigawords'}).", - AcctOutputGigawords = ".$dbh_log->quote($acct{'Acct-Output-Gigawords'}).", - ConnectTermReason = ".$dbh_log->quote($acct{'Acct-Terminate-Cause'}).", - LastAccUpdate = ".$dbh_log->quote($acct{'Timestamp'})." - WHERE - Username = ".$dbh_log->quote($acct{'User-Name'})." - AND AcctSessionID = ".$dbh_log->quote($acct{'Acct-Session-Id'})." - AND NASIPAddress = ".$dbh_log->quote($acct{'NAS-IP-Address'})." - "); - if (!$res) { - print(FH "ERROR: Failed to update accounting data: ".$dbh_log->err."\n"); -# print(STDOUT "1\n"); - goto END; - } - - $res = 0 if ($res eq "0E0"); - - print(FH " - Rows updated: $res\n"); - - # Create record as it doesn't exist! - if ($res == 0) { - # Start accounting - my $res = $dbh_log->do(" - INSERT INTO radiusLogs - ( - AgentID, - Username, - RadiusClassID, - UsageCap, - Topups, - Timestamp, - AcctDelayTime, - AcctSessionID, - NASIPAddress, - NASPortType, - NASPort, - CalledStationID, - CallingStationID, - ConnectInfo, - ServiceType, - Class, - FramedIPAddress, - NASTransmitRate, - NASReceiveRate, - AcctSessionTime, - AcctInputOctets, - AcctOutputOctets, - AcctInputGigawords, - AcctOutputGigawords, - ConnectTermReason, - Status - ) - VALUES - ( - ".$dbh_log->quote($userData->{'AgentID'}).", - ".$dbh_log->quote($acct{'User-Name'}).", - ".$dbh_log->quote($userData->{'RadiusClassID'}).", - ".$dbh_log->quote($userData->{'UsageCap'}).", - ".$dbh_log->quote($userData->{'Topups'}).", - ".$dbh_log->quote( - $acct{'Timestamp'} - defined($acct{'Acct-Session-Time'}) ? $acct{'Acct-Session-Time'} : 0 - ).", - ".$dbh_log->quote($acct{'Delay'}).", - ".$dbh_log->quote($acct{'Acct-Session-Id'}).", - ".$dbh_log->quote($acct{'NAS-IP-Address'}).", - ".$dbh_log->quote($acct{'NAS-Port-Type'}).", - ".$dbh_log->quote($acct{'NAS-Port'}).", - ".$dbh_log->quote($acct{'Called-Station-Id'}).", - ".$dbh_log->quote($acct{'Calling-Station-Id'}).", - ".$dbh_log->quote($acct{'Connect-Info'}).", - ".$dbh_log->quote($acct{'Service-Type'}).", - ".$dbh_log->quote($acct{'Class'}).", - ".$dbh_log->quote($acct{'Framed-IP-Address'}).", - ".$dbh_log->quote($acct{'Ascend-Xmit-Rate'}).", - ".$dbh_log->quote($acct{'Ascend-Data-Rate'}).", - ".$dbh_log->quote($acct{'Acct-Session-Time'}).", - ".$dbh_log->quote($acct{'Acct-Input-Octets'}).", - ".$dbh_log->quote($acct{'Acct-Output-Octets'}).", - ".$dbh_log->quote($acct{'Acct-Input-Gigawords'}).", - ".$dbh_log->quote($acct{'Acct-Output-Gigawords'}).", - ".$dbh_log->quote(0).", - ".$dbh_log->quote(1)." - ) - "); - if (!$res) { - print(FH "ERROR: Failed to insert radius auth fail data: ".$dbh_log->err."\n"); -# print(STDOUT "1\n"); - goto END; - } - print(FH " - Lost accounting record created\n"); - } - - # Check if we updated duplicates, if we did, fix them - if ($res > 1) { - fixDuplicates(\%acct); - } - } - - my $timer1 = [gettimeofday]; - my $timediff = tv_interval($timer0,$timer1); - print(FH "Code execution took: ${timediff}s\n"); - -END: - # Check if we've handled enough requests - if ($requests >= 1000) { - print(FH "Handled enough request, terminating\n"); - last; - } -# print(STDOUT "0\n"); -} -close(FH); - - -# Function to resolve duplicates -sub fixDuplicates -{ - my ($acct) = @_; - - - # Select duplicates - my $sth = $dbh_log->select(" - SELECT ID - FROM - radiusLogs - WHERE - Username = ".$dbh_log->quote($acct->{'User-Name'})." - AND NASIPAddress = ".$dbh_log->quote($acct->{'NAS-IP-Address'})." - AND AcctSessionID = ".$dbh_log->quote($acct->{'Acct-Session-Id'})." - ORDER BY ID - LIMIT 99 OFFSET 1 - "); - if (!$sth) { - print(FH "ERROR: Selecting duplicates: ".$dbh_log->err."\n"); - print(STDOUT "1\n"); - return; - } - # Return if no rows returned - return if ($sth->rows < 1); - - my @IDs = (); - # Pull in duplicates - while (my $dup = $sth->fetchrow_hashref()) { - push(@IDs,$dup->{'ID'}); - } - $sth->finish(); - # Remove duplicates - my $res = $dbh_log->do(" - DELETE FROM radiusLogs - WHERE - ID IN (".join(',',@IDs).") - "); - if (!$res) { - print(FH "ERROR: Failed to remove duplicates: ".$dbh_log->err."\n"); - } else { - $res = 0 if ($res eq "0E0"); - print(FH " - Duplicates removed: $res\n"); - } -} - - -# Disconnect user -sub disconnectUser -{ - my ($dbh,$fh,$userData,$acct) = @_; - - - # If radius classID == 0, means we don't know about this user, just return, nothing we can do - if ($userData->{'RadiusClassID'} == 0) { - print($fh " - (D) Radius class ID is zero, cannot disconnect\n"); - return; - } - - # Grab class data - my $classData = getClass($dbh,$userData->{'RadiusClassID'}); - - # Check if we got a hash, if not just return, error already reported in common.pm - if (ref $classData ne "HASH") { - print($fh " - (D) No class data, cannot disconnect: $classData\n"); - return; - } - - # Check if we have POD server list, if not ... return - if (!defined($classData->{'PODServers'}) || $classData->{'PODServers'} eq "") { - print($fh " - (D) No POD servers, cannot disconnect\n"); - return; - } - - # Loop with POD servers and add to list - my @podServers; - foreach my $i (split(/,/,$classData->{'PODServers'})) { - my %server; - # Pull out data we need - if ($i =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/([^:]+):(\d+)$/) { - ($server{'IP'},$server{'Secret'},$server{'Port'}) = ($1,$2,$3); - # If we didn't understand it, bitch - } else { - print($fh " - (D) Did not understand POD server definition '$i'\n"); - return; - } - # Add to list - push(@podServers,\%server); - } - - # Fork off notification handler - my $child_pid; - if (!defined($child_pid = fork())) { - print($fh " - (D) ERROR: Cannot fork: $!\n"); - } elsif ($child_pid) { - # I'm the parent - return; - } else { - # I'm the child - sleep(5); - - # Grab a server - foreach my $server (@podServers) - { - # Fire up radius - my $r = new Authen::Radius(Host => $server->{'IP'}.':'.$server->{'Port'}, Secret => $server->{'Secret'}, Debug => 1); - if (!$r) { - print($fh "FORK - (D) ".$userData->{'Username'}.", failed to create radius object\n"); - exit; - } - # Set attributes - $r->add_attributes( - { Name => 'User-Name', Value => $userData->{'Username'} }, - { Name => 'Framed-IP-Address', Value => $acct->{'Framed-IP-Address'} }, - { Name => 'NAS-IP-Address', Value => $acct->{'NAS-IP-Address'} }, - ); - # Send packet - my $res = $r->send_packet(DISCONNECT_REQUEST); - print($fh "FORK - (D) ".$userData->{'Username'}.", Result1: ".Dumper($res)."\n"); - # Clear & grab result - $r->clear_attributes(); - $res = $r->recv_packet(); - # Disect packet and see whats going on - print($fh "FORK - (D) ".$userData->{'Username'}.", Result2: ".Dumper($res)."\n"); - my $value = $r->{'attributes'}; - if (defined($value)) { - my ($v1,$v2,$v3,$v4) = unpack('C C N',$value); - printf($fh "FORK - (D) ".$userData->{'Username'}.", Got reply: v1=%s, v2=%s, v3=%s, v4=%s\n", - defined($v1) ? $v1 : '<undef>', - defined($v2) ? $v2 : '<undef>', - defined($v3) ? $v3 : '<undef>', - defined($v4) ? $v4 : '<undef>' - ); - # Last it, as we got a reply - last; - # This would mean a timeout? - } else { - print($fh "FORK - (D) ".$userData->{'Username'}.", Got reply: undefined\n"); - } - } - } -} - - - -# Notify user -sub notifyUser -{ - my ($dbh,$dbh_log,$fh,$userData,$action,$message) = @_; - - - # If no notification method is specified, just return - return if (!defined($userData->{'NotifyMethod'}) || $userData->{'NotifyMethod'} eq ""); - - # Get current notification status - my $notifyStatus = getNotifyStatus($dbh_log,$userData); - if (ref $notifyStatus ne "HASH") { - print($fh " - (N) No notify status: $notifyStatus\n"); - return; - } - printf($fh ' - (N) Notify status, id = %s, lastupdate = %s, updateinterval = %s'."\n", - defined($notifyStatus->{'ID'}) ? $notifyStatus->{'ID'} : 'new', - $notifyStatus->{'LastUpdate'}, - $notifyStatus->{'UpdateInterval'}); - - # Time now - my $now = time(); - - # Check if we should reset - if ($action & NOTIFY_RESET == NOTIFY_RESET) { - $notifyStatus->{'UpdateInterval'} = 86400; - - # If we not resetting, check if we should do a check - } else { - # Calculate delta - my $delta = (($now - $notifyStatus->{'LastUpdate'}) + $notifyStatus->{'UpdateInterval'}) / 2; - $delta = 86400 if ($delta > 86400); # 1 day window only - - print($fh " - (N) Notify delta = $delta\n"); - - # Check actions - if ($action & NOTIFY_CHECK == NOTIFY_CHECK) { - # Return if delta is less than half a day - return if ($delta < 43200); - print($fh " - (N) Notify check, succeeded\n"); - } - - $notifyStatus->{'UpdateInterval'} = $delta; - } - - $notifyStatus->{'LastUpdate'} = $now; - my $res = updateNotifyStatus($dbh_log,$userData,$notifyStatus); - if (!$res) { - print($fh " - (N) Notify data updated\n"); - } else { - print($fh " - (N) Notify update error: $res\n"); - } - - # Get agent data - my $agentData = getAgent($dbh,$userData->{'AgentID'}); - # Check if we got a hash, if not just return, error already reported in common.pm - if (ref $agentData ne "HASH") { - print($fh " - (N) No agent data, cannot notify: $agentData\n"); - return; - } - - # Fork off notification handler - my $child_pid; - if (!defined($child_pid = fork())) { - print($fh " - (N) ERROR: Cannot fork: $!\n"); - } elsif ($child_pid) { - # I'm the parent - return; - } else { - # I'm the child - sleep(5); - - # Pull out notification email addy - my @methods = split /,/, $userData->{'NotifyMethod'}; - - print($fh "FORK - (N) Resume for: ".$userData->{'Username'}."\n"); - # Loop with notification methods - foreach my $method (@methods) { - - # Check for email addy - if ($method =~ /^\S+@\S+$/) { - print($fh "FORK - (N) Its an email addy: $method\n"); - - # Create message - my $msg = MIME::Lite->new( - From => $agentData->{'ContactEmail'}, - To => $method, - Bcc => $agentData->{'ContactEmail'}, - Subject => "ADSL user ".$userData->{'Username'}, - Type => 'multipart/mixed' - ); - - # Attach body - $msg->attach( - Type => 'TEXT', - Encoding => 'quoted-printable', - Data => $message, - ); - - # Send - if (!(my $res = $msg->send())) { - print($fh "FORK - (N) Failed to send email\n"); - } - - - # First character can only be 1-9, next char is second part of country code, then 9 digit phone number (without 0) - } elsif ($method =~ /^\+([1-9][0-9]{0,1})([1-9][0-9]{8})$/) { - my $country = $1; - my $number = $2; - - use URI::Escape; - my $cmd = "echo '".uri_escape($message,"\x00-\x1f\x7f-\xff\"\'")."' | /usr/local/sitemanager-trunk/scripts/sms/sendsms --send-to='$country$number'"; - - - if ($country eq "27") { - print($fh "FORK - (N) Its a cellphone in allowed country $country: $method\n"); - - open(LOG,">> /var/log/radius/sms.log"); - printf(LOG '%s:SENT:%s:%s:%s%s', $userData->{'AgentID'}, $country . $number, $userData->{'Username'}, $cmd, "\n"); - close(LOG); - - print($fh "FORK - (N) Going to run command: $cmd\n"); - system($cmd); - if ($? == -1) { - print($fh "FORK - (N) Failed to execute: $!\n"); - } elsif ($? & 127) { - printf($fh "FORK - (N) Child died with signal %d\n",($? & 127)); - } else { - printf($fh "FORK - (N) Child exited with value %d\n", $? >> 8); - } - - } else { - print($fh "FORK - (N) Its a cellphone in dis-allowed country $country: $method\n"); - - open(LOG,">> /var/log/radius/sms.log"); - printf(LOG '%s:REJECT:%s:%s:%s%s', $userData->{'AgentID'}, $country . $number, $userData->{'Username'}, $cmd, "\n"); - close(LOG); - } - - # Not understood - } else { - print($fh "FORK - (N) I DO NOT UNDERSTAND NOTIFY METHOD => '$method'\n"); - } - } - exit; - } -} - - -# Function to get a notification status -sub getNotifyStatus -{ - my ($dbh,$userData) = @_; - - - my %notifyStatus; - - # Select tracking information - my $sth = $dbh->select(" - SELECT - ID, Username, LastUpdate, UpdateInterval, LastValue - FROM - radiusNotifyTrack - WHERE - Username = ".$dbh->quote($userData->{'Username'})." - "); - if (!$sth) { - return "Database error: ".$dbh->err; - } - - # Tracking info exists - if ($sth->rows == 1) { - # Pull data - my $data = $sth->fetchrow_hashref(); - $sth->finish(); - # Sanity check - return "Undefined data!" if (ref $data ne "HASH"); - - $notifyStatus{'ID'} = $data->{'ID'}; - $notifyStatus{'LastUpdate'} = $data->{'LastUpdate'}; - $notifyStatus{'UpdateInterval'} = $data->{'UpdateInterval'}; - $notifyStatus{'LastValue'} = $data->{'LastValue'}; - - # No tracking info - } elsif ($sth->rows == 0) { - $sth->finish(); - $notifyStatus{'LastUpdate'} = 0; - $notifyStatus{'UpdateInterval'} = 86400; - - # Insert record seeing as it doesn't exist - my $res = $dbh->do(" - INSERT INTO radiusNotifyTrack - ( - Username, - LastUpdate, - UpdateInterval - ) - VALUES - ( - ".$dbh->quote($userData->{'Username'}).", - ".$dbh->quote($notifyStatus{'LastUpdate'}).", - ".$dbh->quote($notifyStatus{'UpdateInterval'})." - ) - "); - return "Failed to insert radius tracking data: ".$dbh->err if (!$res); - - # Wth happened here? - } else { - my $msg = "Unknown number of rows returned for radius tracking: ".$sth->rows; - $sth->finish(); - return $msg; - } - - return \%notifyStatus; -} - - -# Function to update users notify status -sub updateNotifyStatus -{ - my ($dbh,$userData,$notifyStatus) = @_; - - - # Update accounting - my $res = $dbh->do(" - UPDATE radiusNotifyTrack - SET - LastUpdate = ".$dbh->quote($notifyStatus->{'LastUpdate'}).", - UpdateInterval = ".$dbh->quote($notifyStatus->{'UpdateInterval'})." - WHERE - Username = ".$dbh->quote($userData->{'Username'})." - "); - if (!$res) { - return "Failed to update notify tracking data: ".$dbh->err; - } - - return; -} - - - -# Display usage -sub displayUsage { - print("Usage: $0 [--quiet]\n"); - exit 0; -} - - -# vim: ts=4 diff --git a/auth-filter b/auth-filter deleted file mode 100755 index 2bdc7cd924ee6caa85ccb4dff531e225887e1f99..0000000000000000000000000000000000000000 --- a/auth-filter +++ /dev/null @@ -1,459 +0,0 @@ -#!/usr/bin/perl -w -# Author: Nigel Kukard <nkukard@lbsd.net> -# Date: 12/04/2007 -# Desc: Authentication filter for GNU Radius -# License: GPL - -use strict; -use Benchmark; -use Getopt::Long; -use DateTime; -use Time::HiRes qw( gettimeofday tv_interval ); - -# Set library directory -use lib qw(../../); - -use sm::config; -use sm::dblayer; - -require("common.pm"); - - -my %optctl = (); - -GetOptions(\%optctl, "help"); - - -# Check if user wants usage -if (defined($optctl{'help'})) { - displayUsage(); -} - -# Open up logfile -my $logfile = "/var/log/radius/auth-filter"; -open(FH,">> $logfile") or die "Failed to open '$logfile': $!"; - - -# Databases -my $dbh; # Authentication -my $dbh_log; # Logs - -# Get db handle -$dbh = sm::dbilayer->new($cfg_db_DSN, $cfg_db_Username, $cfg_db_Password); -if (!$dbh) { - print(STDERR "Error creating database object: ".sm::dbilayer->internalErr()); - exit 1; -} - -# Connect to database -if ($dbh->connect() != 0) { - print(STDERR "Error connecting to database: ".$dbh->err); - exit 1; -} - -# Check if we must use split db's -if (defined($cfg_radiuslog_db_DSN)) { - # Get log db handle - $dbh_log = sm::dbilayer->new($cfg_radiuslog_db_DSN, $cfg_radiuslog_db_Username, $cfg_radiuslog_db_Password); - if (!$dbh_log) { - print(STDERR "Error creating database object: ".sm::dbilayer->internalErr()); - exit 1; - } - - # Connect to database - if ($dbh_log->connect() != 0) { - print(STDERR "Error connecting to database: ".$dbh_log->err); - exit 1; - } -# If not use the main DB -} else { - $dbh_log = $dbh; -} - - -# No buffering -select((select(FH), $| = 1)[0]); -select((select(STDOUT), $| = 1)[0]); - -# Loop with input -while (my $line = <STDIN>) { - my %request; - my @request; - my %accessAttribs; - my %replyAttribs; - - - # Munch off \n - chomp($line); - - # Check number of results - if ((@request = split /:/, $line) < 6) { - print(FH "ERROR: Number of params from radiusd: ".(@request)."/$line\n"); - print(STDOUT "1\n"); - next; - } - - # Pull in request - ($request{'User-Name'},$request{'NAS-IP-Address'},$request{'NAS-Port-Type'},$request{'NAS-Port'},$request{'Connect-Info'}, - $request{'Service-Type'}) = @request; - my $dt = DateTime->from_epoch( epoch => time() ); - $request{'Timestamp'} = $dt->strftime('%Y-%m-%d %H:%M:%S'); - - # If this is a auth mechanism that called us, allow - if ($request{'NAS-Port-Type'} eq "0" && $request{'NAS-Port'} eq "0" && $request{'Service-Type'} eq "0") { - print(STDOUT "0\n"); - next; - } - - my $timer0 = [gettimeofday]; - - # Grab user details - my $userData = getUser($dbh,$request{'User-Name'}); - if (ref $userData ne "HASH") { - print(FH "ERROR: $userData\n"); - print(STDOUT "1\n"); - next; - } - - printf(FH 'INFO: User-Name: %s, Timestamp: %s, NAS-IP-Address: %s, NAS-Port-Type: %s, NAS-Port: %s, Connect-Info: %s, Service-Type: %s, CappingType: %s, UsageCap: %s, AgentDisabled: %s'."\n", - $request{'User-Name'}, - $request{'Timestamp'}, - $request{'NAS-IP-Address'}, - $request{'NAS-Port-Type'}, - $request{'NAS-Port'}, - $request{'Connect-Info'}, - $request{'Service-Type'}, - $userData->{'CappingType'}, - defined($userData->{'UsageCap'}) ? $userData->{'UsageCap'} : "uncapped", - $userData->{'AgentDisabled'} - ); - - - # Check user active, else insert into auth fail - if ($userData->{'AgentDisabled'} eq "1") { - print(FH " - Agent disabled\n"); - print(STDOUT "1 Reply-Message = \"Your account is currently deactivated. Please, contact your ISP.\"\n"); - authFail(\%request,1); - next; - } - - # Pull in class attribs & check - my $sth = $dbh->select(" - SELECT - Attr, OP, Value - FROM - radiusClassAttribs - WHERE - RadiusClassID = ".$dbh->quote($userData->{'RadiusClassID'})." - AND OP IS NOT NULL - "); - if (!$sth) { - print(FH "ERROR: Selecting class attributes: ".$dbh->err."\n"); - print(STDOUT "1\n"); - next; - } - # Loop with class attribs and push - while (my $item = $sth->fetchrow_hashref()) { - push(@{$accessAttribs{$item->{'Attr'}}{$item->{'OP'}}},$item->{'Value'}); - } - $sth->finish(); - - # Pull in user attribs & check - $sth = $dbh->select(" - SELECT - Attr, OP, Value - FROM - radiusAttribs - WHERE - RadiusUserID = ".$dbh->quote($userData->{'ID'})." - AND OP IS NOT NULL - "); - if (!$sth) { - print(FH "ERROR: Selecting class attributes: ".$dbh->err."\n"); - print(STDOUT "1\n"); - next; - } - # Loop with class attribs and push - while (my $item = $sth->fetchrow_hashref()) { - push(@{$accessAttribs{$item->{'Attr'}}{$item->{'OP'}}},$item->{'Value'}); - } - $sth->finish(); - - # Loop with access attribs and push - my $rejectAttrs = 0; - foreach my $attr (keys %accessAttribs) { - my $ok = 0; - - # Check if we missing something in the request - if (!defined($request{$attr})) { - printf(FH " - WARNING: Attribute '$attr' was in accessAttribs, but not request\n"); - next; - } - - # Loop with attrib op's and check them out - foreach my $op (keys %{$accessAttribs{$attr}}) { - printf(FH ' - Checking %s, request="%s", op="%s", attr="%s": ', - $attr, - $request{$attr}, - $op, - join(',',@{$accessAttribs{$attr}{$op}}) - ); - # Check value against operator - foreach my $val (@{$accessAttribs{$attr}{$op}}) { - # Equal - if ($op eq "=") { - if ($request{$attr} eq $val) { - print(FH "matched '$val'\n"); - $ok = 1; - last; - } - } - } - # Check if we ok, if not continue - if ($ok == 0) { - print(FH "no match\n"); - } else { - last - } - } - # Check if we ok, if not we've been violated - if ($ok == 0) { - print(FH " - Class attribute violation: '$attr'\n"); - $rejectAttrs = 1; - last; - } - } - # Check if something didn't match up - if ($rejectAttrs == 1) { - print(STDOUT "1 Reply-Message = \"Connection attribute mismatch. Please, contact your ISP.\"\n"); - authFail(\%request,5); - next; - } - - # Check user type vs. adsl & analogue/isdn - -# IF ADSL - if ($request{'NAS-Port-Type'} eq "5") { - # Calculate dates - my $date = DateTime->from_epoch( epoch => time() ); - my $today = $date->ymd(); - $date->set_day(1); - my $thismonth = $date->ymd(); - $date->add( months => 1); - my $nextmonth = $date->ymd(); - - - # Extra query for selects - my $extraQuery = ""; - - # Check port locking, else insert into auth fail - $sth = $dbh->select(" - SELECT - NASPort - FROM - radiusPortLocks - WHERE - RadiusUserID = ".$dbh->quote($userData->{'ID'})." - AND AgentDisabled = 0 - "); - if (!$sth) { - print(FH "ERROR: Selecting NAS ports: ".$dbh->err."\n"); - print(STDOUT "1\n"); - next; - } - # Check rows - if ($sth->rows > 0) { - my $found = 0; - # Loop with port locks - while (my $portLock = $sth->fetchrow_hashref()) { - # Check if we found port locking - if ($request{'NAS-Port'} eq $portLock->{'NASPort'}) { - $found = 1; - last; - } - } - # Check if we found it - if ($found == 0) { - print(FH " - Port locked\n"); - print(STDOUT "1 Reply-Message = \"Connection from unauthorized port. Please, contact your ISP.\"\n"); - authFail(\%request,4); - $sth->finish(); - next; - } - } - $sth->finish(); - - - - # NULL - uncapped, no limits - if (!defined($userData->{'UsageCap'})) { - $extraQuery = "AND Timestamp > ".$dbh->quote($thismonth)." AND Timestamp < ".$dbh->quote($nextmonth); - - # > 0 - normal cap, check usage for this month, check topups for this month - # Calculate cap user has (acctinputoctets + (2^32 * gigawords)) /1024 / 1024 - # Calculate usage user has - } elsif ($userData->{'UsageCap'} > 0) { - $extraQuery = "AND Timestamp > ".$dbh->quote($thismonth)." AND Timestamp < ".$dbh->quote($nextmonth); - - # 0 - topup account - } elsif ($userData->{'UsageCap'} == 0) { - - } - - # Grab users usage - my $usageData = getUsage($dbh_log,$request{'User-Name'},$extraQuery); - if (ref $usageData ne "HASH") { - print(FH "ERROR: $usageData\n"); - print(STDOUT "1\n"); - next; - } - my $totalUsage = $usageData->{'Total'}; - - # If we a normal or topup, check topups - if (defined($userData->{'UsageCap'}) && ($userData->{'UsageCap'} > 0 || $userData->{'UsageCap'} == 0)) { - # Prepare - $extraQuery = ""; - - # Only total up this month - if ($userData->{'UsageCap'} > 0) { - $extraQuery = "AND ValidFrom <= ".$dbh->quote($today)." AND ValidTo >= ".$dbh->quote($today); - } - - # Get how much we've been topped up - my $topupData = getTopups($dbh,$request{'User-Name'},$extraQuery); - if (ref $topupData ne "HASH") { - print(FH "ERROR: $topupData\n"); - print(STDOUT "1\n"); - next; - } - my $topupBw = $topupData->{'Total'}; - - # Check capping, else insert into auth fail - print(FH " - Usage $totalUsage (Cap:".$userData->{'UsageCap'}."+Topup:$topupBw)\n"); - - # Check capping - if ($userData->{'CappingType'} == 1 && ($userData->{'UsageCap'} + $topupBw) <= $totalUsage) { - print(FH " - User capped\n"); - print(STDOUT "1 Reply-Message = \"Your account is has been capped. Please, contact your ISP.\"\n"); - authFail(\%request,2); - next; - } - } - - -# IF ANALOGUE - } elsif ($request{'NAS-Port-Type'} eq "0") { - -# IF ISDN - } elsif ($request{'NAS-Port-Type'} eq "2") { - - } else { - print(FH "ERROR: Unknown NAS-Port-Type: ".$request{'NAS-Port-Type'}."\n"); - } - - # Pull in class attribs - $sth = $dbh->select(" - SELECT - Attr, Value - FROM - radiusClassAttribs - WHERE - RadiusClassID = ".$dbh->quote($userData->{'RadiusClassID'})." - AND OP IS NULL - "); - if (!$sth) { - print(FH "ERROR: Selecting class attributes: ".$dbh->err."\n"); - print(STDOUT "1\n"); - next; - } - # Loop with class attribs - while (my $item = $sth->fetchrow_hashref()) { - $replyAttribs{$item->{'Attr'}} = $item->{'Value'}; - } - $sth->finish(); - - # Pull in user attribs - $sth = $dbh->select(" - SELECT - Attr, Value - FROM - radiusAttribs - WHERE - RadiusUserID = ".$dbh->quote($userData->{'ID'})." - AND OP IS NULL - "); - if (!$sth) { - print(FH "ERROR: Selecting user attributes: ".$dbh->err."\n"); - print(STDOUT "1\n"); - next; - } - # Loop with user attribs - while (my $item = $sth->fetchrow_hashref()) { - $replyAttribs{$item->{'Attr'}} = $item->{'Value'}; - } - $sth->finish(); - - # Build up our attrib pairs - my @replyAttribs; - foreach my $key (keys %replyAttribs) { - push(@replyAttribs,sprintf('%s = %s',$key,$replyAttribs{$key})); - } - - my $timer1 = [gettimeofday]; - my $timediff = tv_interval($timer0,$timer1); - printf(FH ' - Attributes => %s'."\n",join(", ",@replyAttribs)); - print(FH "Code execution took: ${timediff}s\n"); - - # Reply with positive status plus , separated list of attribs - printf(STDOUT '0 %s'."\n",join(", ",@replyAttribs)); -} -close(FH); - - -# Log failed authentication -sub authFail -{ - my ($request,$reason) = @_; - - - # Insert entry into auth fail table - my $sth = $dbh_log->do(" - INSERT INTO radiusAuthFail - ( - Username, - Timestamp, - NASIPAddress, - NASPortType, - NASPort, - ConnectInfo, - ServiceType, - Reason - ) - VALUES - ( - ".$dbh->quote($request->{'User-Name'}).", - ".$dbh->quote($request->{'Timestamp'}).", - ".$dbh->quote($request->{'NAS-IP-Address'}).", - ".$dbh->quote($request->{'NAS-Port-Type'}).", - ".$dbh->quote($request->{'NAS-Port'}).", - ".$dbh->quote($request->{'Connect-Info'}).", - ".$dbh->quote($request->{'Service-Type'}).", - ".$dbh->quote($request->{'Reason'})." - - ) - "); - if (!$sth) { - print(FH "ERROR: Failed to insert radius auth fail data: ".$dbh_log->err."\n"); - } -} - - - -# Display usage -sub displayUsage { - print("Usage: $0 [--quiet]\n"); - exit 0; -} - - -# vim: ts=4