Forked from
smradius / smradius
707 commits behind the upstream repository.
-
Nigel Kukard authoredNigel Kukard authored
acct-filter 26.48 KiB
#!/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