Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • smradius/smradius
  • centiva-shail/smradius
  • nkukard/smradius
3 results
Show changes
Showing
with 2543 additions and 401 deletions
# SQL accounting database # SQL accounting database
# Copyright (C) 2007-2015, AllWorldIT # Copyright (C) 2007-2019, AllWorldIT
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
...@@ -35,12 +35,10 @@ use Math::BigFloat; ...@@ -35,12 +35,10 @@ use Math::BigFloat;
# Exporter stuff # Exporter stuff
require Exporter; use base qw(Exporter);
our (@ISA,@EXPORT,@EXPORT_OK); our @EXPORT = qw(
@ISA = qw(Exporter);
@EXPORT = qw(
); );
@EXPORT_OK = qw( our @EXPORT_OK = qw(
); );
...@@ -127,26 +125,26 @@ sub init ...@@ -127,26 +125,26 @@ sub init
%{request.NAS-Identifier}, %{request.NAS-Identifier},
%{request.NAS-IP-Address}, %{request.NAS-IP-Address},
%{request.Acct-Delay-Time}, %{request.Acct-Delay-Time},
%{request.SessionTime}, %{request.Acct-Session-Time},
%{request.InputOctets}, %{request.Acct-Input-Octets},
%{request.InputGigawords}, %{request.Acct-Input-Gigawords},
%{request.InputPackets}, %{request.Acct-Input-Packets},
%{request.OutputOctets}, %{request.Acct-Output-Octets},
%{request.OutputGigawords}, %{request.Acct-Output-Gigawords},
%{request.OutputPackets}, %{request.Acct-Output-Packets},
%{query.PeriodKey} %{query.PeriodKey}
) )
'; ';
$config->{'accounting_update_get_records_query'} = ' $config->{'accounting_update_get_records_query'} = '
SELECT SELECT
SUM(AcctInputOctets) AS InputOctets, SUM(AcctInputOctets) AS AcctInputOctets,
SUM(AcctInputPackets) AS InputPackets, SUM(AcctInputPackets) AS AcctInputPackets,
SUM(AcctOutputOctets) AS OutputOctets, SUM(AcctOutputOctets) AS AcctOutputOctets,
SUM(AcctOutputPackets) AS OutputPackets, SUM(AcctOutputPackets) AS AcctOutputPackets,
SUM(AcctInputGigawords) AS InputGigawords, SUM(AcctInputGigawords) AS AcctInputGigawords,
SUM(AcctOutputGigawords) AS OutputGigawords, SUM(AcctOutputGigawords) AS AcctOutputGigawords,
SUM(AcctSessionTime) AS SessionTime, SUM(AcctSessionTime) AS AcctSessionTime,
PeriodKey PeriodKey
FROM FROM
@TP@accounting @TP@accounting
...@@ -165,13 +163,13 @@ sub init ...@@ -165,13 +163,13 @@ sub init
UPDATE UPDATE
@TP@accounting @TP@accounting
SET SET
AcctSessionTime = %{query.SessionTime}, AcctSessionTime = %{query.Acct-Session-Time},
AcctInputOctets = %{query.InputOctets}, AcctInputOctets = %{query.Acct-Input-Octets},
AcctInputGigawords = %{query.InputGigawords}, AcctInputGigawords = %{query.Acct-Input-Gigawords},
AcctInputPackets = %{query.InputPackets}, AcctInputPackets = %{query.Acct-Input-Packets},
AcctOutputOctets = %{query.OutputOctets}, AcctOutputOctets = %{query.Acct-Output-Octets},
AcctOutputGigawords = %{query.OutputGigawords}, AcctOutputGigawords = %{query.Acct-Output-Gigawords},
AcctOutputPackets = %{query.OutputPackets}, AcctOutputPackets = %{query.Acct-Output-Packets},
AcctStatusType = %{request.Acct-Status-Type} AcctStatusType = %{request.Acct-Status-Type}
WHERE WHERE
Username = %{user.Username} Username = %{user.Username}
...@@ -208,6 +206,20 @@ sub init ...@@ -208,6 +206,20 @@ sub init
AND PeriodKey = %{query.PeriodKey} AND PeriodKey = %{query.PeriodKey}
'; ';
$config->{'accounting_usage_query_period'} = '
SELECT
SUM(AcctInputOctets) AS AcctInputOctets,
SUM(AcctOutputOctets) AS AcctOutputOctets,
SUM(AcctInputGigawords) AS AcctInputGigawords,
SUM(AcctOutputGigawords) AS AcctOutputGigawords,
SUM(AcctSessionTime) AS AcctSessionTime
FROM
@TP@accounting
WHERE
Username = %{user.Username}
AND EventTimestamp > %{query.PeriodKey}
';
$config->{'accounting_select_duplicates_query'} = ' $config->{'accounting_select_duplicates_query'} = '
SELECT SELECT
ID ID
...@@ -282,6 +294,15 @@ sub init ...@@ -282,6 +294,15 @@ sub init
$config->{'accounting_usage_query'} = $scfg->{'mod_accounting_sql'}->{'accounting_usage_query'}; $config->{'accounting_usage_query'} = $scfg->{'mod_accounting_sql'}->{'accounting_usage_query'};
} }
} }
if (defined($scfg->{'mod_accounting_sql'}->{'accounting_usage_query_period'}) &&
$scfg->{'mod_accounting_sql'}->{'accounting_usage_query_period'} ne "") {
if (ref($scfg->{'mod_accounting_sql'}->{'accounting_usage_query_period'}) eq "ARRAY") {
$config->{'accounting_usage_query_period'} = join(' ',
@{$scfg->{'mod_accounting_sql'}->{'accounting_usage_query_period'}});
} else {
$config->{'accounting_usage_query_period'} = $scfg->{'mod_accounting_sql'}->{'accounting_usage_query_period'};
}
}
if (defined($scfg->{'mod_accounting_sql'}->{'accounting_select_duplicates_query'}) && if (defined($scfg->{'mod_accounting_sql'}->{'accounting_select_duplicates_query'}) &&
$scfg->{'mod_accounting_sql'}->{'accounting_select_duplicates_query'} ne "") { $scfg->{'mod_accounting_sql'}->{'accounting_select_duplicates_query'} ne "") {
if (ref($scfg->{'mod_accounting_sql'}->{'accounting_select_duplicates_query'}) eq "ARRAY") { if (ref($scfg->{'mod_accounting_sql'}->{'accounting_select_duplicates_query'}) eq "ARRAY") {
...@@ -301,10 +322,15 @@ sub init ...@@ -301,10 +322,15 @@ sub init
} }
} }
if (defined($scfg->{'mod_accounting_sql'}->{'accounting_usage_cache_time'})) { if (defined($scfg->{'mod_accounting_sql'}->{'accounting_usage_cache_time'})) {
if ($scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'} =~ /^\s*(yes|true|1)\s*$/i) { # Check if we're a boolean
# Default? if (defined(my $val = isBoolean($scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'}))) {
} elsif ($scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'} =~ /^\s*(no|false|0)\s*$/i) { # If val is true, we default to the default anyway
$config->{'accounting_usage_cache_time'} = undef;
# We're disabled
if (!$val) {
$config->{'accounting_usage_cache_time'} = undef;
}
# We *could* have a value...
} elsif ($scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'} =~ /^[0-9]+$/) { } elsif ($scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'} =~ /^[0-9]+$/) {
$config->{'accounting_usage_cache_time'} = $scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'}; $config->{'accounting_usage_cache_time'} = $scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'};
} else { } else {
...@@ -324,20 +350,42 @@ sub init ...@@ -324,20 +350,42 @@ sub init
# Function to get radius user data usage # Function to get radius user data usage
# The 'period' parameter is optional and is the number of days to return usage for
sub getUsage sub getUsage
{ {
my ($server,$user,$packet) = @_; my ($server,$user,$packet,$period) = @_;
# Build template # Build template
my $template; my $template;
foreach my $attr ($packet->attributes) { foreach my $attr ($packet->attributes) {
$template->{'request'}->{$attr} = $packet->rawattr($attr) $template->{'request'}->{$attr} = $packet->rawattr($attr)
} }
$template->{'user'} = $user;
# Current PeriodKey # Add user details
$template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'user'}->{'Username'} = $user->{'Username'};
# Current PeriodKey, this is used for non-$period queries
my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'}); my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'});
$template->{'query'}->{'PeriodKey'} = $now->strftime("%Y-%m");
# Query template to use below
my $queryTemplate;
# If we're doing a query for a specific period
if (defined($period)) {
# We need to switch out the query to the period query
$queryTemplate = "accounting_usage_query_period";
# Grab a clone of now, and create the start date DateTime object
my $startDate = $now->clone->subtract( 'days' => $period );
# And we add the start date
$template->{'query'}->{'PeriodKey'} = $startDate->ymd();
# If not, we just use PeriodKey as normal...
} else {
# Set the normal PeriodKey query template to use
$queryTemplate = "accounting_usage_query";
# And set the period key to this month
$template->{'query'}->{'PeriodKey'} = $now->strftime("%Y-%m");
}
# If we using caching, check how old the result is # If we using caching, check how old the result is
if (defined($config->{'accounting_usage_cache_time'})) { if (defined($config->{'accounting_usage_cache_time'})) {
...@@ -349,20 +397,20 @@ sub getUsage ...@@ -349,20 +397,20 @@ sub getUsage
} }
# Replace template entries # Replace template entries
my (@dbDoParams) = templateReplace($config->{'accounting_usage_query'},$template); my (@dbDoParams) = templateReplace($config->{$queryTemplate},$template);
# Fetch data # Fetch data
my $sth = DBSelect(@dbDoParams); my $sth = DBSelect(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: %s",AWITPT::DB::DBLayer::error());
return; return;
} }
# Our usage hash # Our usage hash
my %usageTotals; my %usageTotals;
$usageTotals{'TotalSessionTime'} = Math::BigInt->new(); $usageTotals{'TotalSessionTime'} = Math::BigInt->new(0);
$usageTotals{'TotalDataInput'} = Math::BigInt->new(); $usageTotals{'TotalDataInput'} = Math::BigInt->new(0);
$usageTotals{'TotalDataOutput'} = Math::BigInt->new(); $usageTotals{'TotalDataOutput'} = Math::BigInt->new(0);
# Pull in usage and add up # Pull in usage and add up
while (my $row = hashifyLCtoMC($sth->fetchrow_hashref(), while (my $row = hashifyLCtoMC($sth->fetchrow_hashref(),
...@@ -379,7 +427,7 @@ sub getUsage ...@@ -379,7 +427,7 @@ sub getUsage
} }
if (defined($row->{'AcctInputGigawords'}) && $row->{'AcctInputGigawords'} > 0) { if (defined($row->{'AcctInputGigawords'}) && $row->{'AcctInputGigawords'} > 0) {
my $inputGigawords = Math::BigInt->new($row->{'AcctInputGigawords'}); my $inputGigawords = Math::BigInt->new($row->{'AcctInputGigawords'});
$inputGigawords->bmul(UINT_MAX); $inputGigawords->bmul(GIGAWORD_VALUE);
$usageTotals{'TotalDataInput'}->badd($inputGigawords); $usageTotals{'TotalDataInput'}->badd($inputGigawords);
} }
# Add output usage if we have any # Add output usage if we have any
...@@ -388,16 +436,16 @@ sub getUsage ...@@ -388,16 +436,16 @@ sub getUsage
} }
if (defined($row->{'AcctOutputGigawords'}) && $row->{'AcctOutputGigawords'} > 0) { if (defined($row->{'AcctOutputGigawords'}) && $row->{'AcctOutputGigawords'} > 0) {
my $outputGigawords = Math::BigInt->new($row->{'AcctOutputGigawords'}); my $outputGigawords = Math::BigInt->new($row->{'AcctOutputGigawords'});
$outputGigawords->bmul(UINT_MAX); $outputGigawords->bmul(GIGAWORD_VALUE);
$usageTotals{'TotalDataOutput'}->badd($outputGigawords); $usageTotals{'TotalDataOutput'}->badd($outputGigawords);
} }
} }
DBFreeRes($sth); DBFreeRes($sth);
# Convert to bigfloat for accuracy # Convert to bigfloat for accuracy
my $totalData = Math::BigFloat->new(); my $totalData = Math::BigFloat->new(0);
$totalData->badd($usageTotals{'TotalDataOutput'})->badd($usageTotals{'TotalDataInput'}); $totalData->badd($usageTotals{'TotalDataOutput'})->badd($usageTotals{'TotalDataInput'});
my $totalTime = Math::BigFloat->new(); my $totalTime = Math::BigFloat->new(0);
$totalTime->badd($usageTotals{'TotalSessionTime'}); $totalTime->badd($usageTotals{'TotalSessionTime'});
# Rounding up # Rounding up
...@@ -408,7 +456,7 @@ sub getUsage ...@@ -408,7 +456,7 @@ sub getUsage
# If we using caching and got here, it means that we must cache the result # If we using caching and got here, it means that we must cache the result
if (defined($config->{'accounting_usage_cache_time'})) { if (defined($config->{'accounting_usage_cache_time'})) {
$res{'CachedUntil'} = $user->{'_Internal'}->{'Timestamp-Unix'} + $config->{'accounting_usage_cache_time'}; $res{'CachedUntil'} = $user->{'_Internal'}->{'Timestamp-Unix'} + $config->{'accounting_usage_cache_time'};
# Cache the result # Cache the result
cacheStoreComplexKeyPair('mod_accounting_sql(getUsage)',$user->{'Username'}."/".$template->{'query'}->{'PeriodKey'},\%res); cacheStoreComplexKeyPair('mod_accounting_sql(getUsage)',$user->{'Username'}."/".$template->{'query'}->{'PeriodKey'},\%res);
} }
...@@ -429,6 +477,7 @@ sub acct_log ...@@ -429,6 +477,7 @@ sub acct_log
{ {
my ($server,$user,$packet) = @_; my ($server,$user,$packet) = @_;
# Build template # Build template
my $template; my $template;
foreach my $attr ($packet->attributes) { foreach my $attr ($packet->attributes) {
...@@ -437,8 +486,9 @@ sub acct_log ...@@ -437,8 +486,9 @@ sub acct_log
# Fix event timestamp # Fix event timestamp
$template->{'request'}->{'Timestamp'} = $user->{'_Internal'}->{'Timestamp'}; $template->{'request'}->{'Timestamp'} = $user->{'_Internal'}->{'Timestamp'};
# Add user # Add user details
$template->{'user'} = $user; $template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'user'}->{'Username'} = $user->{'Username'};
# Current PeriodKey # Current PeriodKey
my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'}); my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'});
...@@ -447,11 +497,12 @@ sub acct_log ...@@ -447,11 +497,12 @@ sub acct_log
# For our queries # For our queries
$template->{'query'}->{'PeriodKey'} = $periodKey; $template->{'query'}->{'PeriodKey'} = $periodKey;
# Default to being a new period, only if we update on INTERIM or STOP do we set this to 0
my $newPeriod = 1;
# #
# U P D A T E & S T O P P A C K E T # U P D A T E & S T O P P A C K E T
# #
# If its a new period we're going to trigger START
my $newPeriod;
if ($packet->rawattr('Acct-Status-Type') eq "2" || $packet->rawattr('Acct-Status-Type') eq "3") { if ($packet->rawattr('Acct-Status-Type') eq "2" || $packet->rawattr('Acct-Status-Type') eq "3") {
# Replace template entries # Replace template entries
my @dbDoParams = templateReplace($config->{'accounting_update_get_records_query'},$template); my @dbDoParams = templateReplace($config->{'accounting_update_get_records_query'},$template);
...@@ -459,16 +510,17 @@ sub acct_log ...@@ -459,16 +510,17 @@ sub acct_log
# Fetch previous records of the same session # Fetch previous records of the same session
my $sth = DBSelect(@dbDoParams); my $sth = DBSelect(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: %s",AWITPT::DB::DBLayer::error());
return; return;
} }
# Convert session total gigawords/octets into bytes # Convert session total gigawords into bytes
my $totalInputBytes = Math::BigInt->new(); my $totalInputBytes = Math::BigInt->new($template->{'request'}->{'Acct-Input-Gigawords'});
$totalInputBytes->badd($template->{'request'}->{'Acct-Input-Gigawords'})->bmul(UINT_MAX); my $totalOutputBytes = Math::BigInt->new($template->{'request'}->{'Acct-Output-Gigawords'});
$totalInputBytes->bmul(GIGAWORD_VALUE);
$totalOutputBytes->bmul(GIGAWORD_VALUE);
# Add byte counters
$totalInputBytes->badd($template->{'request'}->{'Acct-Input-Octets'}); $totalInputBytes->badd($template->{'request'}->{'Acct-Input-Octets'});
my $totalOutputBytes = Math::BigInt->new();
$totalOutputBytes->badd($template->{'request'}->{'Acct-Output-Gigawords'})->bmul(UINT_MAX);
$totalOutputBytes->badd($template->{'request'}->{'Acct-Output-Octets'}); $totalOutputBytes->badd($template->{'request'}->{'Acct-Output-Octets'});
# Packets, no conversion # Packets, no conversion
my $totalInputPackets = Math::BigInt->new($template->{'request'}->{'Acct-Input-Packets'}); my $totalInputPackets = Math::BigInt->new($template->{'request'}->{'Acct-Input-Packets'});
...@@ -478,21 +530,31 @@ sub acct_log ...@@ -478,21 +530,31 @@ sub acct_log
# Loop through previous records and subtract them from our session totals # Loop through previous records and subtract them from our session totals
while (my $sessionPart = hashifyLCtoMC($sth->fetchrow_hashref(), while (my $sessionPart = hashifyLCtoMC($sth->fetchrow_hashref(),
qw(InputOctets InputPackets OutputOctets OutputPackets InputGigawords OutputGigawords SessionTime PeriodKey) qw(AcctInputOctets AcctInputPackets AcctOutputOctets AcctOutputPackets AcctInputGigawords AcctOutputGigawords
SessionTime PeriodKey)
)) { )) {
# Make sure we treat undef values sort of sanely
# Convert this session usage to bytes $sessionPart->{'AcctInputGigawords'} //= 0;
my $sessionInputBytes = Math::BigInt->new(); $sessionPart->{'AcctInputOctets'} //= 0;
$sessionInputBytes->badd($sessionPart->{'InputGigawods'})->bmul(UINT_MAX); $sessionPart->{'AcctOutputGigawords'} //= 0;
$sessionInputBytes->badd($sessionPart->{'InputOctets'}); $sessionPart->{'AcctOutputOctets'} //= 0;
my $sessionOutputBytes = Math::BigInt->new(); $sessionPart->{'AcctInputPackets'} //= 0;
$sessionOutputBytes->badd($sessionPart->{'OutputGigawods'})->bmul(UINT_MAX); $sessionPart->{'AcctOutputPackets'} //= 0;
$sessionOutputBytes->badd($sessionPart->{'OutputOctets'}); $sessionPart->{'AcctSessionTime'} //= 0;
# Convert the gigawords into bytes
my $sessionInputBytes = Math::BigInt->new($sessionPart->{'AcctInputGigawords'});
my $sessionOutputBytes = Math::BigInt->new($sessionPart->{'AcctOutputGigawords'});
$sessionInputBytes->bmul(GIGAWORD_VALUE);
$sessionOutputBytes->bmul(GIGAWORD_VALUE);
# Add the byte counters
$sessionInputBytes->badd($sessionPart->{'AcctInputOctets'});
$sessionOutputBytes->badd($sessionPart->{'AcctOutputOctets'});
# And packets # And packets
my $sessionInputPackets = Math::BigInt->new($sessionPart->{'InputPackets'}); my $sessionInputPackets = Math::BigInt->new($sessionPart->{'AcctInputPackets'});
my $sessionOutputPackets = Math::BigInt->new($sessionPart->{'OutputPackets'}); my $sessionOutputPackets = Math::BigInt->new($sessionPart->{'AcctOutputPackets'});
# Finally session time # Finally session time
my $sessionSessionTime = Math::BigInt->new($sessionPart->{'SessionTime'}); my $sessionSessionTime = Math::BigInt->new($sessionPart->{'AcctSessionTime'});
# Check if this record is from an earlier period # Check if this record is from an earlier period
if (defined($sessionPart->{'PeriodKey'}) && $sessionPart->{'PeriodKey'} ne $periodKey) { if (defined($sessionPart->{'PeriodKey'}) && $sessionPart->{'PeriodKey'} ne $periodKey) {
...@@ -511,36 +573,36 @@ sub acct_log ...@@ -511,36 +573,36 @@ sub acct_log
DBFreeRes($sth); DBFreeRes($sth);
# Sanitize # Sanitize
if ($totalInputBytes->is_neg()) { if ($totalInputBytes->is_neg()) {
$totalInputBytes->bzero(); $totalInputBytes->bzero();
} }
if ($totalOutputBytes->is_neg()) { if ($totalOutputBytes->is_neg()) {
$totalOutputBytes->bzero(); $totalOutputBytes->bzero();
} }
if ($totalInputPackets->is_neg()) { if ($totalInputPackets->is_neg()) {
$totalInputPackets->bzero(); $totalInputPackets->bzero();
} }
if ($totalOutputPackets->is_neg()) { if ($totalOutputPackets->is_neg()) {
$totalOutputPackets->bzero(); $totalOutputPackets->bzero();
} }
if ($totalSessionTime->is_neg()) { if ($totalSessionTime->is_neg()) {
$totalSessionTime->bzero(); $totalSessionTime->bzero();
} }
# Re-calculate # Re-calculate
my ($inputGigawordsStr,$inputOctetsStr) = $totalInputBytes->bdiv(UINT_MAX); my ($inputGigawordsStr,$inputOctetsStr) = $totalInputBytes->bdiv(GIGAWORD_VALUE);
my ($outputGigawordsStr,$outputOctetsStr) = $totalOutputBytes->bdiv(UINT_MAX); my ($outputGigawordsStr,$outputOctetsStr) = $totalOutputBytes->bdiv(GIGAWORD_VALUE);
# Conversion to strings # Conversion to strings
$template->{'query'}->{'InputGigawords'} = $inputGigawordsStr->bstr(); $template->{'query'}->{'Acct-Input-Gigawords'} = $inputGigawordsStr->bstr();
$template->{'query'}->{'InputOctets'} = $inputOctetsStr->bstr(); $template->{'query'}->{'Acct-Input-Octets'} = $inputOctetsStr->bstr();
$template->{'query'}->{'OutputGigawords'} = $outputGigawordsStr->bstr(); $template->{'query'}->{'Acct-Output-Gigawords'} = $outputGigawordsStr->bstr();
$template->{'query'}->{'OutputOctets'} = $outputOctetsStr->bstr(); $template->{'query'}->{'Acct-Output-Octets'} = $outputOctetsStr->bstr();
$template->{'query'}->{'InputPackets'} = $totalInputPackets->bstr(); $template->{'query'}->{'Acct-Input-Packets'} = $totalInputPackets->bstr();
$template->{'query'}->{'OutputPackets'} = $totalOutputPackets->bstr(); $template->{'query'}->{'Acct-Output-Packets'} = $totalOutputPackets->bstr();
$template->{'query'}->{'SessionTime'} = $totalSessionTime->bstr(); $template->{'query'}->{'Acct-Session-Time'} = $totalSessionTime->bstr();
# Replace template entries # Replace template entries
...@@ -550,7 +612,7 @@ sub acct_log ...@@ -550,7 +612,7 @@ sub acct_log
$sth = DBDo(@dbDoParams); $sth = DBDo(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to update accounting ALIVE record: ". $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to update accounting ALIVE record: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
...@@ -559,7 +621,7 @@ sub acct_log ...@@ -559,7 +621,7 @@ sub acct_log
# Be very sneaky .... if we updated something, this is obviously NOT a new period # Be very sneaky .... if we updated something, this is obviously NOT a new period
$newPeriod = 0; $newPeriod = 0;
# If we updated a few things ... possibly duplicates? # If we updated a few things ... possibly duplicates?
if ($sth > 1) { if ($sth > 1) {
fixDuplicates($server, $template); fixDuplicates($server, $template);
} }
} }
...@@ -575,12 +637,11 @@ sub acct_log ...@@ -575,12 +637,11 @@ sub acct_log
if ($packet->rawattr('Acct-Status-Type') eq "1" || $newPeriod) { if ($packet->rawattr('Acct-Status-Type') eq "1" || $newPeriod) {
# Replace template entries # Replace template entries
my @dbDoParams = templateReplace($config->{'accounting_start_query'},$template); my @dbDoParams = templateReplace($config->{'accounting_start_query'},$template);
# Insert into database # Insert into database
my $sth = DBDo(@dbDoParams); my $sth = DBDo(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to insert accounting START record: ". $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to insert accounting START record: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
# Update first login? # Update first login?
...@@ -592,7 +653,6 @@ sub acct_log ...@@ -592,7 +653,6 @@ sub acct_log
$user->{'_UserDB'}->{'Users_data_set'}($server,$user,'global','FirstLogin',$user->{'_Internal'}->{'Timestamp-Unix'}); $user->{'_UserDB'}->{'Users_data_set'}($server,$user,'global','FirstLogin',$user->{'_Internal'}->{'Timestamp-Unix'});
} }
} }
} }
...@@ -608,11 +668,12 @@ sub acct_log ...@@ -608,11 +668,12 @@ sub acct_log
# Update database (status) # Update database (status)
my $sth = DBDo(@dbDoParams); my $sth = DBDo(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to update accounting STOP record: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Failed to update accounting STOP record: %s",AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
} }
return MOD_RES_ACK; return MOD_RES_ACK;
} }
...@@ -629,7 +690,7 @@ sub fixDuplicates ...@@ -629,7 +690,7 @@ sub fixDuplicates
# Select duplicates # Select duplicates
my $sth = DBSelect(@dbDoParams); my $sth = DBSelect(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: %s",AWITPT::DB::DBLayer::error());
return; return;
} }
...@@ -652,7 +713,7 @@ sub fixDuplicates ...@@ -652,7 +713,7 @@ sub fixDuplicates
# Delete duplicates # Delete duplicates
$sth = DBDo(@dbDoParams); $sth = DBDo(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Database query failed: %s",AWITPT::DB::DBLayer::error());
DBRollback(); DBRollback();
return; return;
} }
...@@ -682,7 +743,7 @@ sub cleanup ...@@ -682,7 +743,7 @@ sub cleanup
# Last month.. # Last month..
my $lastMonth = $thisMonth->clone()->subtract( months => 1 ); my $lastMonth = $thisMonth->clone()->subtract( months => 1 );
my $prevPeriodKey = $lastMonth->strftime("%Y-%m"); my $prevPeriodKey = $lastMonth->strftime("%Y-%m");
# Begin transaction # Begin transaction
DBBegin(); DBBegin();
...@@ -700,7 +761,7 @@ sub cleanup ...@@ -700,7 +761,7 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to delete accounting summary record: ". $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to delete accounting summary record: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
DBRollback(); DBRollback();
return; return;
} }
...@@ -725,7 +786,7 @@ sub cleanup ...@@ -725,7 +786,7 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to select accounting record: ". $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to select accounting record: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
return; return;
} }
...@@ -747,7 +808,7 @@ sub cleanup ...@@ -747,7 +808,7 @@ sub cleanup
} }
if (defined($row->{'AcctInputGigawords'}) && $row->{'AcctInputGigawords'} > 0) { if (defined($row->{'AcctInputGigawords'}) && $row->{'AcctInputGigawords'} > 0) {
my $inputGigawords = Math::BigInt->new($row->{'AcctInputGigawords'}); my $inputGigawords = Math::BigInt->new($row->{'AcctInputGigawords'});
$inputGigawords->bmul(UINT_MAX); $inputGigawords->bmul(GIGAWORD_VALUE);
$usageTotals{$row->{'Username'}}{'TotalDataInput'}->badd($inputGigawords); $usageTotals{$row->{'Username'}}{'TotalDataInput'}->badd($inputGigawords);
} }
# Add output usage if we have any # Add output usage if we have any
...@@ -756,7 +817,7 @@ sub cleanup ...@@ -756,7 +817,7 @@ sub cleanup
} }
if (defined($row->{'AcctOutputGigawords'}) && $row->{'AcctOutputGigawords'} > 0) { if (defined($row->{'AcctOutputGigawords'}) && $row->{'AcctOutputGigawords'} > 0) {
my $outputGigawords = Math::BigInt->new($row->{'AcctOutputGigawords'}); my $outputGigawords = Math::BigInt->new($row->{'AcctOutputGigawords'});
$outputGigawords->bmul(UINT_MAX); $outputGigawords->bmul(GIGAWORD_VALUE);
$usageTotals{$row->{'Username'}}{'TotalDataOutput'}->badd($outputGigawords); $usageTotals{$row->{'Username'}}{'TotalDataOutput'}->badd($outputGigawords);
} }
...@@ -764,9 +825,9 @@ sub cleanup ...@@ -764,9 +825,9 @@ sub cleanup
} else { } else {
# Make BigInts for this user # Make BigInts for this user
$usageTotals{$row->{'Username'}}{'TotalSessionTime'} = Math::BigInt->new(); $usageTotals{$row->{'Username'}}{'TotalSessionTime'} = Math::BigInt->new(0);
$usageTotals{$row->{'Username'}}{'TotalDataInput'} = Math::BigInt->new(); $usageTotals{$row->{'Username'}}{'TotalDataInput'} = Math::BigInt->new(0);
$usageTotals{$row->{'Username'}}{'TotalDataOutput'} = Math::BigInt->new(); $usageTotals{$row->{'Username'}}{'TotalDataOutput'} = Math::BigInt->new(0);
# Look for session time # Look for session time
if (defined($row->{'AcctSessionTime'}) && $row->{'AcctSessionTime'} > 0) { if (defined($row->{'AcctSessionTime'}) && $row->{'AcctSessionTime'} > 0) {
...@@ -778,7 +839,7 @@ sub cleanup ...@@ -778,7 +839,7 @@ sub cleanup
} }
if (defined($row->{'AcctInputGigawords'}) && $row->{'AcctInputGigawords'} > 0) { if (defined($row->{'AcctInputGigawords'}) && $row->{'AcctInputGigawords'} > 0) {
my $inputGigawords = Math::BigInt->new($row->{'AcctInputGigawords'}); my $inputGigawords = Math::BigInt->new($row->{'AcctInputGigawords'});
$inputGigawords->bmul(UINT_MAX); $inputGigawords->bmul(GIGAWORD_VALUE);
$usageTotals{$row->{'Username'}}{'TotalDataInput'}->badd($inputGigawords); $usageTotals{$row->{'Username'}}{'TotalDataInput'}->badd($inputGigawords);
} }
# Add output usage if we have any # Add output usage if we have any
...@@ -787,7 +848,7 @@ sub cleanup ...@@ -787,7 +848,7 @@ sub cleanup
} }
if (defined($row->{'AcctOutputGigawords'}) && $row->{'AcctOutputGigawords'} > 0) { if (defined($row->{'AcctOutputGigawords'}) && $row->{'AcctOutputGigawords'} > 0) {
my $outputGigawords = Math::BigInt->new($row->{'AcctOutputGigawords'}); my $outputGigawords = Math::BigInt->new($row->{'AcctOutputGigawords'});
$outputGigawords->bmul(UINT_MAX); $outputGigawords->bmul(GIGAWORD_VALUE);
$usageTotals{$row->{'Username'}}{'TotalDataOutput'}->badd($outputGigawords); $usageTotals{$row->{'Username'}}{'TotalDataOutput'}->badd($outputGigawords);
} }
...@@ -832,7 +893,7 @@ sub cleanup ...@@ -832,7 +893,7 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to create accounting summary record: ". $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to create accounting summary record: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
DBRollback(); DBRollback();
return; return;
} }
...@@ -849,5 +910,6 @@ sub cleanup ...@@ -849,5 +910,6 @@ sub cleanup
} }
1; 1;
# vim: ts=4 # vim: ts=4
# Test accounting database # Test accounting database
# Copyright (C) 2007-2015, AllWorldIT # Copyright (C) 2007-2016, AllWorldIT
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
...@@ -99,7 +99,10 @@ Acct-Delay-Time: %{accounting.Acct-Delay-Time} ...@@ -99,7 +99,10 @@ Acct-Delay-Time: %{accounting.Acct-Delay-Time}
foreach my $attr ($packet->attributes) { foreach my $attr ($packet->attributes) {
$template->{'accounting'}->{$attr} = $packet->attr($attr) $template->{'accounting'}->{$attr} = $packet->attr($attr)
} }
$template->{'user'} = $user;
# Add user details
$template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'user'}->{'Username'} = $user->{'Username'};
if ($packet->rawattr('Acct-Status-Type') eq "1") { if ($packet->rawattr('Acct-Status-Type') eq "1") {
$server->log(LOG_DEBUG,"Start Packet: ".$packet->dump()); $server->log(LOG_DEBUG,"Start Packet: ".$packet->dump());
......
# Capping support # Capping support
# Copyright (C) 2007-2016, AllWorldIT # Copyright (C) 2007-2019, AllWorldIT
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -27,11 +27,19 @@ use smradius::logging; ...@@ -27,11 +27,19 @@ use smradius::logging;
use smradius::util; use smradius::util;
use AWITPT::Util; use AWITPT::Util;
use POSIX qw(floor); use List::Util qw( min );
use MIME::Lite;
use POSIX qw( floor );
# Set our version
our $VERSION = "0.0.1";
# Load exporter # Load exporter
use base qw(Exporter); use base qw(Exporter);
our @EXPORT = qw(
);
our @EXPORT_OK = qw( our @EXPORT_OK = qw(
); );
...@@ -57,9 +65,6 @@ my $UPTIME_LIMIT_ATTRIBUTE = 'SMRadius-Capping-Uptime-Limit'; ...@@ -57,9 +65,6 @@ my $UPTIME_LIMIT_ATTRIBUTE = 'SMRadius-Capping-Uptime-Limit';
my $TRAFFIC_TOPUP_ATTRIBUTE = 'SMRadius-Capping-Traffic-Topup'; my $TRAFFIC_TOPUP_ATTRIBUTE = 'SMRadius-Capping-Traffic-Topup';
my $TIME_TOPUP_ATTRIBUTE = 'SMRadius-Capping-Uptime-Topup'; my $TIME_TOPUP_ATTRIBUTE = 'SMRadius-Capping-Uptime-Topup';
my $TRAFFIC_AUTOTOPUP_ATTRIBUTE = 'SMRadius-Capping-Traffic-AutoTopup';
my $TIME_AUTOTOPUP_ATTRIBUTE = 'SMRadius-Capping-Uptime-AutoTopup';
my $config; my $config;
...@@ -72,21 +77,34 @@ sub init ...@@ -72,21 +77,34 @@ sub init
my $scfg = $server->{'inifile'}; my $scfg = $server->{'inifile'};
# Defaults
$config->{'enable_mikrotik'} = 0;
$config->{'caveat_captrafzero'} = 0;
# Setup SQL queries # Setup SQL queries
if (defined($scfg->{'mod_feature_capping'})) { if (defined($scfg->{'mod_feature_capping'})) {
# Check if option exists # Check if option exists
if (defined($scfg->{'mod_feature_capping'}{'enable_mikrotik'})) { if (defined($scfg->{'mod_feature_capping'}{'enable_mikrotik'})) {
# Pull in config # Pull in config
if ($scfg->{'mod_feature_capping'}{'enable_mikrotik'} =~ /^\s*(yes|true|1)\s*$/i) { if (defined(my $val = isBoolean($scfg->{'mod_feature_capping'}{'enable_mikrotik'}))) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] Mikrotik-specific vendor return attributes ENABLED"); if ($val) {
$config->{'enable_mikrotik'} = $scfg->{'mod_feature_capping'}{'enable_mikrotik'}; $server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] Mikrotik-specific vendor return attributes ENABLED");
# Default? $config->{'enable_mikrotik'} = $val;
} elsif ($scfg->{'mod_feature_capping'}{'enable_mikrotik'} =~ /^\s*(no|false|0)\s*$/i) { }
$config->{'enable_mikrotik'} = undef;
} else { } else {
$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] Value for 'enable_mikrotik' is invalid"); $server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] Value for 'enable_mikrotik' is invalid");
} }
} }
# Check if we have the caveat setting
if (defined(my $val = isBoolean($scfg->{'mod_feature_capping'}{'caveat_captrafzero'}))) {
if ($val) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] Caveat to swap '0' and -undef- for ".
"SMRadius-Capping-Traffic-Limit ENABLED");
$config->{'caveat_captrafzero'} = $val;
}
} else {
$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] Value for 'caveat_captrafzero' is invalid");
}
} }
return; return;
...@@ -120,6 +138,20 @@ sub post_auth_hook ...@@ -120,6 +138,20 @@ sub post_auth_hook
my $uptimeLimit = _getAttributeKeyLimit($server,$user,$UPTIME_LIMIT_ATTRIBUTE); my $uptimeLimit = _getAttributeKeyLimit($server,$user,$UPTIME_LIMIT_ATTRIBUTE);
my $trafficLimit = _getAttributeKeyLimit($server,$user,$TRAFFIC_LIMIT_ATTRIBUTE); my $trafficLimit = _getAttributeKeyLimit($server,$user,$TRAFFIC_LIMIT_ATTRIBUTE);
# Swap around 0 and undef if we need to apply the captrafzero caveat
if ($config->{'caveat_captrafzero'}) {
if (!defined($uptimeLimit)) {
$uptimeLimit = 0;
} elsif ($uptimeLimit == 0) {
$uptimeLimit = undef;
}
if (!defined($trafficLimit)) {
$trafficLimit = 0;
} elsif ($trafficLimit == 0) {
$trafficLimit = undef;
}
}
# #
# Get current traffic and uptime usage # Get current traffic and uptime usage
...@@ -135,19 +167,15 @@ sub post_auth_hook ...@@ -135,19 +167,15 @@ sub post_auth_hook
# Get valid traffic and uptime topups # Get valid traffic and uptime topups
# #
# Check if there was any data returned at all # Check if there was any data returned at all
my $uptimeTopupAmount = _getConfigAttributeNumeric($server,$user,$TIME_TOPUP_ATTRIBUTE) // 0; my $uptimeTopupAmount = _getConfigAttributeNumeric($server,$user,$TIME_TOPUP_ATTRIBUTE) // 0;
my $trafficTopupAmount = _getConfigAttributeNumeric($server,$user,$TRAFFIC_TOPUP_ATTRIBUTE) // 0; my $trafficTopupAmount = _getConfigAttributeNumeric($server,$user,$TRAFFIC_TOPUP_ATTRIBUTE) // 0;
my $uptimeAutoTopupAmount = _getConfigAttributeNumeric($server,$user,$TIME_AUTOTOPUP_ATTRIBUTE) // 0;
my $trafficAutoTopupAmount = _getConfigAttributeNumeric($server,$user,$TRAFFIC_AUTOTOPUP_ATTRIBUTE) // 0;
# #
# Set the new uptime and traffic limits (limit, if any.. + topups) # Set the new uptime and traffic limits (limit, if any.. + topups)
# #
# Uptime.. # Uptime..
# // is a defined operator, $a ? defined($a) : $b # // is a defined operator, $a ? defined($a) : $b
my $uptimeLimitWithTopups = ($uptimeLimit // 0) + $uptimeTopupAmount; my $uptimeLimitWithTopups = ($uptimeLimit // 0) + $uptimeTopupAmount;
...@@ -157,24 +185,41 @@ sub post_auth_hook ...@@ -157,24 +185,41 @@ sub post_auth_hook
my $trafficLimitWithTopups = ($trafficLimit // 0) + $trafficTopupAmount; my $trafficLimitWithTopups = ($trafficLimit // 0) + $trafficTopupAmount;
#
# Do auto-topups for both traffic and uptime
#
my $autoTopupTrafficAdded = _doAutoTopup($server,$user,$accountingUsage->{'TotalDataUsage'},"traffic",
$trafficLimitWithTopups,1);
if (defined($autoTopupTrafficAdded)) {
$trafficLimitWithTopups += $autoTopupTrafficAdded;
}
my $autoTopupUptimeAdded = _doAutoTopup($server,$user,$accountingUsage->{'TotalSessionTime'},"uptime",
$uptimeLimitWithTopups,2);
if (defined($autoTopupUptimeAdded)) {
$uptimeLimitWithTopups += $autoTopupUptimeAdded;
}
# #
# Display our usages # Display our usages
# #
_logUptimeUsage($server,$accountingUsage,$uptimeLimit,$uptimeTopupAmount);
_logTrafficUsage($server,$accountingUsage,$trafficLimit,$trafficTopupAmount); _logUsage($server,$accountingUsage->{'TotalDataUsage'},$trafficLimit,$trafficTopupAmount,'traffic');
_logUsage($server,$accountingUsage->{'TotalSessionTime'},$uptimeLimit,$uptimeTopupAmount,'uptime');
# #
# Add conditional variables # Add conditional variables
# #
# Add attribute conditionals BEFORE override
addAttributeConditionalVariable($user,"SMRadius_Capping_TotalDataUsage",$accountingUsage->{'TotalDataUsage'}); addAttributeConditionalVariable($user,"SMRadius_Capping_TotalDataUsage",$accountingUsage->{'TotalDataUsage'});
addAttributeConditionalVariable($user,"SMRadius_Capping_TotalSessionTime",$accountingUsage->{'TotalSessionTime'}); addAttributeConditionalVariable($user,"SMRadius_Capping_TotalSessionTime",$accountingUsage->{'TotalSessionTime'});
# #
# Allow for capping overrides by client attribute # Allow for capping overrides by attribute
# #
if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Uptime-Multiplier'})) { if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Uptime-Multiplier'})) {
...@@ -186,7 +231,7 @@ sub post_auth_hook ...@@ -186,7 +231,7 @@ sub post_auth_hook
$uptimeLimitWithTopups = $newLimit; $uptimeLimitWithTopups = $newLimit;
$accountingUsage->{'TotalSessionTime'} = $newSessionTime; $accountingUsage->{'TotalSessionTime'} = $newSessionTime;
$server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] Client uptime multiplier '$multiplier' changes ". $server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] User uptime multiplier '$multiplier' changes ".
"uptime limit ('$uptimeLimitWithTopups' => '$newLimit'), ". "uptime limit ('$uptimeLimitWithTopups' => '$newLimit'), ".
"uptime usage ('".$accountingUsage->{'TotalSessionTime'}."' => '$newSessionTime')" "uptime usage ('".$accountingUsage->{'TotalSessionTime'}."' => '$newSessionTime')"
); );
...@@ -200,7 +245,7 @@ sub post_auth_hook ...@@ -200,7 +245,7 @@ sub post_auth_hook
$trafficLimitWithTopups = $newLimit; $trafficLimitWithTopups = $newLimit;
$accountingUsage->{'TotalDataUsage'} = $newDataUsage; $accountingUsage->{'TotalDataUsage'} = $newDataUsage;
$server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] Client traffic multiplier '$multiplier' changes ". $server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] User traffic multiplier '$multiplier' changes ".
"traffic limit ('$trafficLimitWithTopups' => '$newLimit'), ". "traffic limit ('$trafficLimitWithTopups' => '$newLimit'), ".
"traffic usage ('".$accountingUsage->{'TotalDataUsage'}."' => '$newDataUsage')" "traffic usage ('".$accountingUsage->{'TotalDataUsage'}."' => '$newDataUsage')"
); );
...@@ -211,8 +256,8 @@ sub post_auth_hook ...@@ -211,8 +256,8 @@ sub post_auth_hook
# Check if we've exceeded our limits # Check if we've exceeded our limits
# #
# Uptime.. # Uptime...
if (!defined($uptimeLimit) || $uptimeLimit > 0) { if (defined($uptimeLimit)) {
# Check session time has not exceeded what we're allowed # Check session time has not exceeded what we're allowed
if ($accountingUsage->{'TotalSessionTime'} >= $uptimeLimitWithTopups) { if ($accountingUsage->{'TotalSessionTime'} >= $uptimeLimitWithTopups) {
...@@ -223,7 +268,7 @@ sub post_auth_hook ...@@ -223,7 +268,7 @@ sub post_auth_hook
} else { } else {
# Check if we returning Mikrotik vattributes # Check if we returning Mikrotik vattributes
# FIXME: NK - this is not mikrotik specific # FIXME: NK - this is not mikrotik specific
if (defined($config->{'enable_mikrotik'})) { if ($config->{'enable_mikrotik'}) {
# FIXME: NK - We should cap the maximum total session time to that which is already set, if something is set # FIXME: NK - We should cap the maximum total session time to that which is already set, if something is set
# Setup reply attributes for Mikrotik HotSpots # Setup reply attributes for Mikrotik HotSpots
my %attribute = ( my %attribute = (
...@@ -237,7 +282,7 @@ sub post_auth_hook ...@@ -237,7 +282,7 @@ sub post_auth_hook
} }
# Traffic # Traffic
if (!defined($trafficLimit) || $trafficLimit > 0) { if (defined($trafficLimit)) {
# Capped # Capped
if ($accountingUsage->{'TotalDataUsage'} >= $trafficLimitWithTopups) { if ($accountingUsage->{'TotalDataUsage'} >= $trafficLimitWithTopups) {
...@@ -247,7 +292,7 @@ sub post_auth_hook ...@@ -247,7 +292,7 @@ sub post_auth_hook
# Setup limits # Setup limits
} else { } else {
# Check if we returning Mikrotik vattributes # Check if we returning Mikrotik vattributes
if (defined($config->{'enable_mikrotik'})) { if ($config->{'enable_mikrotik'}) {
# Get remaining traffic # Get remaining traffic
my $remainingTraffic = $trafficLimitWithTopups - $accountingUsage->{'TotalDataUsage'}; my $remainingTraffic = $trafficLimitWithTopups - $accountingUsage->{'TotalDataUsage'};
my $remainingTrafficLimit = ( $remainingTraffic % 4096 ) * 1024 * 1024; my $remainingTrafficLimit = ( $remainingTraffic % 4096 ) * 1024 * 1024;
...@@ -301,7 +346,7 @@ sub post_acct_hook ...@@ -301,7 +346,7 @@ sub post_acct_hook
# Skip MAC authentication # Skip MAC authentication
return MOD_RES_SKIP if ($user->{'_UserDB'}->{'Name'} eq "SQL User Database (MAC authentication)"); return MOD_RES_SKIP if ($user->{'_UserDB'}->{'Name'} eq "SQL User Database (MAC authentication)");
# Exceeding maximum, must be disconnected # User is either connecting 'START' or disconnecting 'STOP'
return MOD_RES_SKIP if ($packet->rawattr('Acct-Status-Type') ne "1" && $packet->rawattr('Acct-Status-Type') ne "3"); return MOD_RES_SKIP if ($packet->rawattr('Acct-Status-Type') ne "1" && $packet->rawattr('Acct-Status-Type') ne "3");
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] POST ACCT HOOK"); $server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] POST ACCT HOOK");
...@@ -314,6 +359,19 @@ sub post_acct_hook ...@@ -314,6 +359,19 @@ sub post_acct_hook
my $uptimeLimit = _getAttributeKeyLimit($server,$user,$UPTIME_LIMIT_ATTRIBUTE); my $uptimeLimit = _getAttributeKeyLimit($server,$user,$UPTIME_LIMIT_ATTRIBUTE);
my $trafficLimit = _getAttributeKeyLimit($server,$user,$TRAFFIC_LIMIT_ATTRIBUTE); my $trafficLimit = _getAttributeKeyLimit($server,$user,$TRAFFIC_LIMIT_ATTRIBUTE);
# Swap around 0 and undef if we need to apply the captrafzero caveat
if ($config->{'caveat_captrafzero'}) {
if (!defined($uptimeLimit)) {
$uptimeLimit = 0;
} elsif ($uptimeLimit == 0) {
$uptimeLimit = undef;
}
if (!defined($trafficLimit)) {
$trafficLimit = 0;
} elsif ($trafficLimit == 0) {
$trafficLimit = undef;
}
}
# #
# Get current traffic and uptime usage # Get current traffic and uptime usage
...@@ -332,8 +390,6 @@ sub post_acct_hook ...@@ -332,8 +390,6 @@ sub post_acct_hook
# Check if there was any data returned at all # Check if there was any data returned at all
my $uptimeTopupAmount = _getConfigAttributeNumeric($server,$user,$TIME_TOPUP_ATTRIBUTE) // 0; my $uptimeTopupAmount = _getConfigAttributeNumeric($server,$user,$TIME_TOPUP_ATTRIBUTE) // 0;
my $trafficTopupAmount = _getConfigAttributeNumeric($server,$user,$TRAFFIC_TOPUP_ATTRIBUTE) // 0; my $trafficTopupAmount = _getConfigAttributeNumeric($server,$user,$TRAFFIC_TOPUP_ATTRIBUTE) // 0;
my $uptimeAutoTopupAmount = _getConfigAttributeNumeric($server,$user,$TIME_AUTOTOPUP_ATTRIBUTE) // 0;
my $trafficAutoTopupAmount = _getConfigAttributeNumeric($server,$user,$TRAFFIC_AUTOTOPUP_ATTRIBUTE) // 0;
# #
...@@ -348,12 +404,30 @@ sub post_acct_hook ...@@ -348,12 +404,30 @@ sub post_acct_hook
# // is a defined operator, $a ? defined($a) : $b # // is a defined operator, $a ? defined($a) : $b
my $trafficLimitWithTopups = ($trafficLimit // 0) + $trafficTopupAmount; my $trafficLimitWithTopups = ($trafficLimit // 0) + $trafficTopupAmount;
#
# Do auto-topups for both traffic and uptime
#
my $autoTopupTrafficAdded = _doAutoTopup($server,$user,$accountingUsage->{'TotalDataUsage'},"traffic",
$trafficLimitWithTopups,1);
if (defined($autoTopupTrafficAdded)) {
$trafficLimitWithTopups += $autoTopupTrafficAdded;
}
my $autoTopupUptimeAdded = _doAutoTopup($server,$user,$accountingUsage->{'TotalSessionTime'},"uptime",
$uptimeLimitWithTopups,2);
if (defined($autoTopupUptimeAdded)) {
$uptimeLimitWithTopups += $autoTopupUptimeAdded;
}
# #
# Display our usages # Display our usages
# #
_logUptimeUsage($server,$accountingUsage,$uptimeLimit,$uptimeTopupAmount); _logUsage($server,$accountingUsage->{'TotalDataUsage'},$trafficLimit,$trafficTopupAmount,'traffic');
_logTrafficUsage($server,$accountingUsage,$trafficLimit,$trafficTopupAmount); _logUsage($server,$accountingUsage->{'TotalSessionTime'},$uptimeLimit,$uptimeTopupAmount,'uptime');
# #
...@@ -366,20 +440,20 @@ sub post_acct_hook ...@@ -366,20 +440,20 @@ sub post_acct_hook
# #
# Allow for capping overrides by client attribute # Allow for capping overrides by user attribute
# #
if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Uptime-Multiplier'})) { if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Uptime-Multiplier'})) {
my $multiplier = pop(@{$user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Uptime-Multiplier'}}); my $multiplier = pop(@{$user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Uptime-Multiplier'}});
my $newLimit = $uptimeLimitWithTopups * $multiplier; my $newLimit = $uptimeLimitWithTopups * $multiplier;
$server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] Client cap uptime multiplier '$multiplier' changes limit ". $server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] User cap uptime multiplier '$multiplier' changes limit ".
"from '$uptimeLimitWithTopups' to '$newLimit'"); "from '$uptimeLimitWithTopups' to '$newLimit'");
$uptimeLimitWithTopups = $newLimit; $uptimeLimitWithTopups = $newLimit;
} }
if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Traffic-Multiplier'})) { if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Traffic-Multiplier'})) {
my $multiplier = pop(@{$user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Traffic-Multiplier'}}); my $multiplier = pop(@{$user->{'ConfigAttributes'}->{'SMRadius-Config-Capping-Traffic-Multiplier'}});
my $newLimit = $trafficLimitWithTopups * $multiplier; my $newLimit = $trafficLimitWithTopups * $multiplier;
$server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] Client cap traffic multiplier '$multiplier' changes limit ". $server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] User cap traffic multiplier '$multiplier' changes limit ".
"from '$trafficLimitWithTopups' to '$newLimit'"); "from '$trafficLimitWithTopups' to '$newLimit'");
$trafficLimitWithTopups = $newLimit; $trafficLimitWithTopups = $newLimit;
} }
...@@ -390,7 +464,7 @@ sub post_acct_hook ...@@ -390,7 +464,7 @@ sub post_acct_hook
# #
# Uptime.. # Uptime..
if (!defined($uptimeLimit) || $uptimeLimit > 0) { if (defined($uptimeLimit)) {
# Capped # Capped
if ($accountingUsage->{'TotalSessionTime'} >= $uptimeLimitWithTopups) { if ($accountingUsage->{'TotalSessionTime'} >= $uptimeLimitWithTopups) {
...@@ -401,7 +475,7 @@ sub post_acct_hook ...@@ -401,7 +475,7 @@ sub post_acct_hook
} }
# Traffic # Traffic
if (!defined($trafficLimit) || $trafficLimit > 0) { if (defined($trafficLimit)) {
# Capped # Capped
if ($accountingUsage->{'TotalDataUsage'} >= $trafficLimitWithTopups) { if ($accountingUsage->{'TotalDataUsage'} >= $trafficLimitWithTopups) {
...@@ -417,7 +491,7 @@ sub post_acct_hook ...@@ -417,7 +491,7 @@ sub post_acct_hook
## @internal ## @internal
# Code snippet to grab the current uptime limit by processing the user attributes # Code snippet to grab the current attribute key limit by processing the user attributes
sub _getAttributeKeyLimit sub _getAttributeKeyLimit
{ {
my ($server,$user,$attributeKey) = @_; my ($server,$user,$attributeKey) = @_;
...@@ -474,62 +548,30 @@ sub _getAccountingUsage ...@@ -474,62 +548,30 @@ sub _getAccountingUsage
## @internal ## @internal
# Code snippet to log our uptime usage # Code snippet to log our uptime usage
sub _logUptimeUsage sub _logUsage
{ {
my ($server,$accountingUsage,$uptimeLimit,$uptimeTopupAmount) = @_; my ($server,$accountingUsage,$limit,$topupAmount,$type) = @_;
# Check if our limit is defined my $typeKey = ucfirst($type);
if (!defined($uptimeLimit)) {
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Uptime => Usage total: ".$accountingUsage->{'TotalSessionTime'}.
"min (Limit: Prepaid, Topups: ".$uptimeTopupAmount."min)");
return;
}
# If so, check if its > 0, which would depict its capped
if ($uptimeLimit > 0) {
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Uptime => Usage total: ".$accountingUsage->{'TotalSessionTime'}.
"min (Limit: ".$uptimeLimit."min, Topups: ".$uptimeTopupAmount."min)");
} else {
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Uptime => Usage total: ".$accountingUsage->{'TotalSessionTime'}.
"min (Limit: none, Topups: ".$uptimeTopupAmount."min)");
}
return;
}
## @internal
# Code snippet to log our traffic usage
sub _logTrafficUsage
{
my ($server,$accountingUsage,$trafficLimit,$trafficTopupAmount) = @_;
# Check if our limit is defined # Check if our limit is defined
if (!defined($trafficLimit)) { if (defined($limit) && $limit == 0) {
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Bandwidth => Usage total: ".$accountingUsage->{'TotalDataUsage'}. $limit = '-topup-';
"Mbyte (Limit: Prepaid, Topups: ".$trafficTopupAmount."Mbyte)");
return;
}
# If so, check if its > 0, which would depict its capped
if ($trafficLimit > 0) {
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Bandwidth => Usage total: ".$accountingUsage->{'TotalDataUsage'}.
"Mbyte (Limit: ".$trafficLimit."Mbyte, Topups: ".$trafficTopupAmount."Mbyte)");
} else { } else {
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Bandwidth => Usage total: ".$accountingUsage->{'TotalDataUsage'}. $limit = '-none-';
"Mbyte (Limit: none, Topups: ".$trafficTopupAmount."Mbyte)");
} }
$server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] Capping information [type: %s, total: %s, limit: %s, topups: %s]",
$type,$accountingUsage,$limit,$topupAmount);
return; return;
} }
## @internal ## @internal
# Function snippet to return a numeric configuration attribute # Function snippet to return a user attribute
sub _getConfigAttributeNumeric sub _getConfigAttributeNumeric
{ {
my ($server,$user,$attributeName) = @_; my ($server,$user,$attributeName) = @_;
...@@ -557,5 +599,264 @@ sub _getConfigAttributeNumeric ...@@ -557,5 +599,264 @@ sub _getConfigAttributeNumeric
## @internal
# Function snippet to return a attribute
sub _getAttribute
{
my ($server,$user,$attributeName) = @_;
# Check the attribute exists
return if (!defined($user->{'Attributes'}->{$attributeName}));
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] User attribute '".$attributeName."' is defined");
# Check the required operator is present in this case :=
if (!defined($user->{'Attributes'}->{$attributeName}->{':='})) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] User attribute '".$attributeName."' has no ':=' operator");
return;
}
# Check the operator value is defined...
if (!defined($user->{'Attributes'}->{$attributeName}->{':='}->{'Value'})) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] User attribute '".$attributeName."' has no value");
return;
}
return $user->{'Attributes'}->{$attributeName}->{':='}->{'Value'};
}
## @internal
# Function which impelments our auto-topup functionality
sub _doAutoTopup
{
my ($server,$user,$accountingUsage,$type,$usageLimit,$topupType) = @_;
my $scfg = $server->{'inifile'};
# Get the key, which has the first letter uppercased
my $typeKey = ucfirst($type);
# Booleanize the attribute and check if its enabled
if (my $enabled = booleanize(_getAttribute($server,$user,"SMRadius-AutoTopup-$typeKey-Enabled"))) {
$server->log(LOG_INFO,'[MOD_FEATURE_CAPPING] AutoTopups for %s is enabled',$type);
} else {
$server->log(LOG_DEBUG,'[MOD_FEATURE_CAPPING] AutoTopups for %s is not enabled',$type);
return;
}
# Do sanity checks on the auto-topup amount
my $autoTopupAmount = _getAttribute($server,$user,"SMRadius-AutoTopup-$typeKey-Amount");
if (!defined($autoTopupAmount)) {
$server->log(LOG_WARN,'[MOD_FEATURE_CAPPING] SMRadius-AutoTopup-%s-Amount must have a value',$typeKey);
return;
}
if (!isNumber($autoTopupAmount)){
$server->log(LOG_WARN,'[MOD_FEATURE_CAPPING] SMRadius-AutoTopup-%s-Amount must be a number and be > 0, instead it was '.
'\'%s\', IGNORING SMRadius-AutoTopup-%s-Enabled',$typeKey,$autoTopupAmount,$typeKey);
return;
}
# Do sanity checks on the auto-topup threshold
my $autoTopupThreshold = _getAttribute($server,$user,"SMRadius-AutoTopup-$typeKey-Threshold");
if (defined($autoTopupThreshold) && !isNumber($autoTopupThreshold)){
$server->log(LOG_WARN,'[MOD_FEATURE_CAPPING] SMRadius-AutoTopup-%s-Threshold must be a number and be > 0, instead it was '.
'\'%s\', IGNORING SMRadius-AutoTopup-%s-Threshold',$typeKey,$autoTopupAmount,$typeKey);
$autoTopupThreshold = undef;
}
# Check that if the auto-topup limit is defined, that it is > 0
my $autoTopupLimit = _getAttribute($server,$user,"SMRadius-AutoTopup-$typeKey-Limit");
if (defined($autoTopupLimit) && !isNumber($autoTopupLimit)) {
$server->log(LOG_WARN,'[MOD_FEATURE_CAPPING] SMRadius-AutoTopup-%s-Limit must be a number and be > 0, instead it was '.
'\'%s\', IGNORING SMRadius-AutoTopup-%s-Enabled',$typeKey,$autoTopupAmount,$typeKey);
return;
}
# Pull in ahow many auto-topups were already added
my $autoTopupsAdded = _getConfigAttributeNumeric($server,$user,"SMRadius-Capping-$typeKey-AutoTopup") // 0;
# Default to an auto-topup threshold of the topup amount divided by two if none has been provided
$autoTopupThreshold //= floor($autoTopupAmount / 2);
# Check if we're still within our usage limit and return
if (($usageLimit + $autoTopupsAdded - $accountingUsage) > $autoTopupThreshold) {
$server->log(LOG_DEBUG,'[MOD_FEATURE_CAPPING] SMRadius-AutoTopup-%s: CHECK => usageLimit(%s) + autoTopupsAdded(%s) - '.
'accountingUsage(%s) < autoTopupThreshold(%s) = not eligble for auto-topup yet',$typeKey,
$usageLimit,$autoTopupsAdded,$accountingUsage,$autoTopupThreshold);
return;
} else {
$server->log(LOG_DEBUG,'[MOD_FEATURE_CAPPING] SMRadius-AutoTopup-%s: CHECK => usageLimit(%s) + autoTopupsAdded(%s) - '.
'accountingUsage(%s) < autoTopupThreshold(%s) = eligble, processing',$typeKey,
$usageLimit,$autoTopupsAdded,$accountingUsage,$autoTopupThreshold);
}
# Check the difference between our accounting usage and our usage limit
my $usageDelta = $accountingUsage - $usageLimit;
# Make sure our delta is at least 0
$usageDelta = 0 if ($usageDelta < 0);
# Calculate how many topups are needed
my $autoTopupsRequired = floor($usageDelta / $autoTopupAmount) + 1;
# Default the topups to add to the number required
my $autoTopupsToAdd = $autoTopupsRequired;
# If we have an auto-topup limit, recalculate how many we must add... maybe it exceeds
if (defined($autoTopupLimit)) {
my $autoTopupsAllowed = floor(($autoTopupLimit - $autoTopupsAdded) / $autoTopupAmount);
$autoTopupsToAdd = min($autoTopupsRequired,$autoTopupsAllowed);
# We cannot add a negative amount of auto-topups, if we have a negative amount, we have hit our limit
$autoTopupsToAdd = 0 if ($autoTopupsToAdd < 0);
}
# Total topup amount
my $autoTopupsToAddAmount = $autoTopupsToAdd * $autoTopupAmount;
# The datetime now
my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'});
# Use truncate to set all values after 'month' to their default values
my $thisMonth = $now->clone()->truncate( to => "month" );
# This month, in string form
my $thisMonth_str = $thisMonth->strftime("%Y-%m-%d");
# Next month..
my $nextMonth = $thisMonth->clone()->add( months => 1 );
my $nextMonth_str = $nextMonth->strftime("%Y-%m-%d");
# Lets see if a module accepts to add a topup
my $res;
foreach my $module (@{$server->{'module_list'}}) {
# Do we have the correct plugin?
if (defined($module->{'Feature_Config_Topop_add'})) {
$server->log(LOG_INFO,"[MOD_FEATURE_CAPPING] Found plugin: '".$module->{'Name'}."'");
# Try add topup
$res = $module->{'Feature_Config_Topop_add'}($server,$user,$thisMonth_str,$nextMonth_str,
($topupType | 4),$autoTopupAmount);
# Skip to end if we added a topup
if ($res == MOD_RES_ACK) {
my $topupsRemaining = $autoTopupsToAdd - 1;
while ($topupsRemaining > 0) {
# Try add another topup
$res = $module->{'Feature_Config_Topop_add'}($server,$user,$thisMonth_str,$nextMonth_str,
($topupType | 4),$autoTopupAmount);
$topupsRemaining--;
}
last;
}
}
}
# If not, return undef
if (!defined($res) || $res != MOD_RES_ACK) {
$server->log(LOG_WARN,'[MOD_FEATURE_CAPPING] Auto-Topup(s) cannot be added, no module replied with ACK');
return;
}
$server->log(LOG_INFO,'[MOD_FEATURE_CAPPING] Auto-Topups added [type: %s, threshold: %s, amount: %s, required: %s, limit: %s, added: %s]',
$type,$autoTopupThreshold,$autoTopupAmount,$autoTopupsRequired,$autoTopupLimit,$autoTopupsToAdd);
# Grab notify destinations
my $notify;
if (!defined($notify = _getAttribute($server,$user,"SMRadius-AutoTopup-$typeKey-Notify"))) {
$server->log(LOG_INFO,'[MOD_FEATURE_CAPPING] AutoTopups notify destination is not specified, NOT notifying');
goto END;
}
$server->log(LOG_INFO,'[MOD_FEATURE_CAPPING] AutoTopups notify destination is \'%s\'',$notify);
# Grab notify template
my $notifyTemplate;
if (!defined($notifyTemplate = _getAttribute($server,$user,"SMRadius-AutoTopup-$typeKey-NotifyTemplate"))) {
$server->log(LOG_INFO,'[MOD_FEATURE_CAPPING] AutoTopups notify template is not specified, NOT notifying');
goto END;
}
# NOTE: $autoTopupToAdd and autoTopupsToAddAmount will be 0 if no auto-topups were added
# Create variable hash to pass to TT
my $variables = {
'user' => {
'ID' => $user->{'ID'},
'username' => $user->{'Username'},
},
'usage' => {
'total' => $accountingUsage,
'limit' => $usageLimit,
},
'autotopup' => {
'amount' => $autoTopupAmount,
'limit' => $autoTopupLimit,
'added' => $autoTopupsAdded,
'toAdd' => $autoTopupsToAdd,
'toAddAmount' => $autoTopupsToAddAmount,
},
};
# Split off notification targets
my @notificationTargets = split(/[,;\s]+/,$notify);
foreach my $notifyTarget (@notificationTargets) {
# Parse template
my ($notifyMsg,$error) = quickTemplateToolkit($notifyTemplate,{
%{$variables},
'notify' => { 'target' => $notifyTarget }
});
# Check if we have a result, if not, report the error
if (!defined($notifyMsg)) {
my $errorMsg = $error->info();
$errorMsg =~ s/\r?\n/\\n/g;
$server->log(LOG_WARN,'[MOD_FEATURE_CAPPING] AutoTopups notify template parsing failed: %s',$errorMsg);
next;
}
my %messageHeaders = ();
# Split message into lines
my @lines = split(/\r?\n/,$notifyMsg);
while (defined($lines[0]) && (my $line = $lines[0]) =~ /(\S+): (.*)/) {
my ($header,$value) = ($1,$2);
$messageHeaders{$header} = $value;
# Remove line
shift(@lines);
# Last if our next line is undefined
last if (!defined($lines[0]));
# If the next line is blank, remove it, and continue below
if ($lines[0] =~ /^\s*$/) {
# Remove blank line
shift(@lines);
last;
}
}
# Create message
my $msg = MIME::Lite->new(
'Type' => 'multipart/mixed',
'Date' => $now->strftime('%a, %d %b %Y %H:%M:%S %z'),
%messageHeaders
);
# Attach body
$msg->attach(
'Type' => 'TEXT',
'Encoding' => '8bit',
'Data' => join("\n",@lines),
);
# Send email
my $smtpServer = $scfg->{'server'}{'smtp_server'} // 'localhost';
eval { $msg->send("smtp",$smtpServer); };
if (my $error = $@) {
$server->log(LOG_WARN,"[MOD_FEATURE_CAPPING] Email sending failed: '%s'",$error);
}
}
END:
return $autoTopupsToAddAmount;
}
1; 1;
# vim: ts=4 # vim: ts=4
# FUP support
# Copyright (C) 2007-2019, AllWorldIT
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
package smradius::modules::features::mod_feature_fup;
use strict;
use warnings;
# Modules we need
use smradius::attributes;
use smradius::constants;
use smradius::logging;
use smradius::util;
use AWITPT::Util;
use List::Util qw( min );
use MIME::Lite;
use POSIX qw( floor );
# Set our version
our $VERSION = "0.0.1";
# Load exporter
use base qw(Exporter);
our @EXPORT = qw(
);
our @EXPORT_OK = qw(
);
# Plugin info
our $pluginInfo = {
Name => "User FUP Feature",
Init => \&init,
# Authentication hook
'Feature_Post-Authentication_hook' => \&post_auth_hook,
# Accounting hook
'Feature_Post-Accounting_hook' => \&post_acct_hook,
};
# Some constants
my $FUP_PERIOD_ATTRIBUTE = 'SMRadius-FUP-Period';
my $FUP_TRAFFIC_THRESHOLD_ATTRIBUTE = 'SMRadius-FUP-Traffic-Threshold';
my $config;
## @internal
# Initialize module
sub init
{
my $server = shift;
my $scfg = $server->{'inifile'};
# Defaults
$config->{'enable_mikrotik'} = 0;
# Setup SQL queries
if (defined($scfg->{'mod_feature_fup'})) {
# Check if option exists
if (defined($scfg->{'mod_feature_fup'}{'enable_mikrotik'})) {
# Pull in config
if (defined(my $val = isBoolean($scfg->{'mod_feature_fup'}{'enable_mikrotik'}))) {
if ($val) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_FUP] Mikrotik-specific vendor return attributes ENABLED");
$config->{'enable_mikrotik'} = $val;
}
} else {
$server->log(LOG_NOTICE,"[MOD_FEATURE_FUP] Value for 'enable_mikrotik' is invalid");
}
}
}
return;
}
## @post_auth_hook($server,$user,$packet)
# Post authentication hook
#
# @param server Server object
# @param user User data
# @param packet Radius packet
#
# @return Result
sub post_auth_hook
{
my ($server,$user,$packet) = @_;
# Skip MAC authentication
return MOD_RES_SKIP if ($user->{'_UserDB'}->{'Name'} eq "SQL User Database (MAC authentication)");
$server->log(LOG_DEBUG,"[MOD_FEATURE_FUP] POST AUTH HOOK");
#
# Get threshold from attributes
#
my $fupPeriod = _getAttributeKeyNumeric($server,$user,$FUP_PERIOD_ATTRIBUTE);
my $trafficThreshold = _getAttributeKeyNumeric($server,$user,$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE);
# If we have no FUP period, skip
if (!defined($fupPeriod)) {
return MOD_RES_SKIP;
};
# If we have no traffic threshold, display an info message and skip
if (!defined($trafficThreshold)) {
$server->log(LOG_INFO,"[MOD_FEATURE_FUP] User has a '$FUP_PERIOD_ATTRIBUTE' defined, but NOT a ".
"'$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE' attribute, aborting FUP checks.");
return MOD_RES_SKIP;
};
#
# Get current traffic and uptime usage
#
my $accountingUsage = _getAccountingUsage($server,$user,$packet,$fupPeriod);
if (!defined($accountingUsage)) {
return MOD_RES_SKIP;
}
#
# Display our FUP info
#
_logUsage($server,$fupPeriod,$accountingUsage->{'TotalDataUsage'},$trafficThreshold);
#
# Check if the user has exceeded the FUP
#
my $fupExceeded = ($accountingUsage->{'TotalDataUsage'} > $trafficThreshold) ? 1 : 0;
#
# Add conditional variables
#
addAttributeConditionalVariable($user,"SMRadius_FUP",$fupExceeded);
return MOD_RES_ACK;
}
## @post_acct_hook($server,$user,$packet)
# Post authentication hook
#
# @param server Server object
# @param user User data
# @param packet Radius packet
#
# @return Result
sub post_acct_hook
{
my ($server,$user,$packet) = @_;
# We cannot cap a user if we don't have a UserDB module can we? no userdb, no cap?
return MOD_RES_SKIP if (!defined($user->{'_UserDB'}->{'Name'}));
# Skip MAC authentication
return MOD_RES_SKIP if ($user->{'_UserDB'}->{'Name'} eq "SQL User Database (MAC authentication)");
# User is either connecting 'START' or disconnecting 'STOP'
return MOD_RES_SKIP if ($packet->rawattr('Acct-Status-Type') ne "1" && $packet->rawattr('Acct-Status-Type') ne "3");
$server->log(LOG_DEBUG,"[MOD_FEATURE_FUP] POST ACCT HOOK");
#
# Get threshold from attributes
#
my $fupPeriod = _getAttributeKeyNumeric($server,$user,$FUP_PERIOD_ATTRIBUTE);
my $trafficThreshold = _getAttributeKeyNumeric($server,$user,$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE);
# If we have no FUP period, skip
if (!defined($fupPeriod)) {
return MOD_RES_SKIP;
};
# If we have no traffic threshold, display an info message and skip
if (!defined($trafficThreshold)) {
$server->log(LOG_INFO,"[MOD_FEATURE_FUP] User has a '$FUP_PERIOD_ATTRIBUTE' defined, but NOT a ".
"'$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE' attribute, aborting FUP checks.");
return MOD_RES_SKIP;
};
#
# Get current traffic and uptime usage
#
my $accountingUsage = _getAccountingUsage($server,$user,$packet,$fupPeriod);
if (!defined($accountingUsage)) {
return MOD_RES_SKIP;
}
#
# Display our FUP info
#
_logUsage($server,$fupPeriod,$accountingUsage->{'TotalDataUsage'},$trafficThreshold);
#
# Check if the user has exceeded the FUP
#
my $fupExceeded = ($accountingUsage->{'TotalDataUsage'} > $trafficThreshold) ? 1 : 0;
#
# Add conditional variables
#
addAttributeConditionalVariable($user,"SMRadius_FUP",$fupExceeded);
return MOD_RES_ACK;
}
## @internal
# Code snippet to grab the current uptime limit by processing the user attributes
sub _getAttributeKeyNumeric
{
my ($server,$user,$attributeKey) = @_;
# Short circuit return if we don't have the uptime key set
return if (!defined($user->{'Attributes'}->{$attributeKey}));
# Short circuit if we do not have a valid attribute operator: ':='
if (!defined($user->{'Attributes'}->{$attributeKey}->{':='})) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_FUP] No valid operators for attribute '".
$user->{'Attributes'}->{$attributeKey}."'");
return;
}
$server->log(LOG_DEBUG,"[MOD_FEATURE_FUP] Attribute '".$attributeKey."' is defined");
# Check for valid attribute value
if (!defined($user->{'Attributes'}->{$attributeKey}->{':='}->{'Value'}) ||
$user->{'Attributes'}->{$attributeKey}->{':='}->{'Value'} !~ /^\d+$/) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_FUP] Attribute '".$user->{'Attributes'}->{$attributeKey}->{':='}->{'Value'}.
"' is NOT a numeric value");
return;
}
return $user->{'Attributes'}->{$attributeKey}->{':='}->{'Value'};
}
## @internal
# Code snippet to grab the accounting usage of a user for a specific period
sub _getAccountingUsage
{
my ($server,$user,$packet,$period) = @_;
foreach my $module (@{$server->{'module_list'}}) {
# Do we have the correct plugin?
if (defined($module->{'Accounting_getUsage'})) {
$server->log(LOG_INFO,"[MOD_FEATURE_FUP] Found plugin: '".$module->{'Name'}."'");
# Fetch users session uptime & bandwidth used for a specific period
if (my $res = $module->{'Accounting_getUsage'}($server,$user,$packet,$period)) {
return $res;
}
$server->log(LOG_ERR,"[MOD_FEATURE_FUP] No usage data found for user '".$user->{'Username'}."'");
}
}
return;
}
## @internal
# Code snippet to log our FUP information
sub _logUsage
{
my ($server,$period,$total,$threshold) = @_;
$server->log(LOG_INFO,"[MOD_FEATURE_FUP] FUP information [period: %s days, total: %s, threshold: %s]",
$period,$total,$threshold);
return;
}
## @internal
# Function snippet to return a attribute
sub _getAttribute
{
my ($server,$user,$attributeName) = @_;
# Check the attribute exists
return if (!defined($user->{'Attributes'}->{$attributeName}));
$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] User attribute '".$attributeName."' is defined");
# Check the required operator is present in this case :=
if (!defined($user->{'Attributes'}->{$attributeName}->{':='})) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] User attribute '".$attributeName."' has no ':=' operator");
return;
}
# Check the operator value is defined...
if (!defined($user->{'Attributes'}->{$attributeName}->{':='}->{'Value'})) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] User attribute '".$attributeName."' has no value");
return;
}
return $user->{'Attributes'}->{$attributeName}->{':='}->{'Value'};
}
1;
# vim: ts=4
# Support for updating of user stats # Support for updating of user stats
# Copyright (C) 2007-2011, AllWorldIT # Copyright (C) 2007-2016, AllWorldIT
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
...@@ -74,7 +74,8 @@ sub init ...@@ -74,7 +74,8 @@ sub init
PeriodKey = %{query.PeriodKey}, PeriodKey = %{query.PeriodKey},
TotalTraffic = %{query.TotalTraffic}, TotalTraffic = %{query.TotalTraffic},
TotalUptime = %{query.TotalUptime}, TotalUptime = %{query.TotalUptime},
NASIdentifier = %{request.NAS-Identifier} NASIdentifier = %{request.NAS-Identifier},
LastAcctUpdate = now()
WHERE WHERE
Username = %{user.Username} Username = %{user.Username}
'; ';
...@@ -109,9 +110,9 @@ sub updateUserStats ...@@ -109,9 +110,9 @@ sub updateUserStats
# Skip MAC authentication # Skip MAC authentication
return MOD_RES_SKIP if (defined($user->{'_UserDB'}->{'Name'}) && return MOD_RES_SKIP if (defined($user->{'_UserDB'}->{'Name'}) &&
$user->{'_UserDB'}->{'Name'} eq "SQL User Database (MAC authentication)"); $user->{'_UserDB'}->{'Name'} eq "SQL User Database (MAC authentication)");
$server->log(LOG_DEBUG,"[MOD_FEATURE_UPDATE_USER_STATS_SQL] UPDATE USER STATS HOOK"); $server->log(LOG_DEBUG,"[MOD_FEATURE_UPDATE_USER_STATS_SQL] UPDATE USER STATS HOOK");
# Build template # Build template
...@@ -119,7 +120,10 @@ sub updateUserStats ...@@ -119,7 +120,10 @@ sub updateUserStats
foreach my $attr ($packet->attributes) { foreach my $attr ($packet->attributes) {
$template->{'request'}->{$attr} = $packet->rawattr($attr) $template->{'request'}->{$attr} = $packet->rawattr($attr)
} }
$template->{'user'} = $user;
# Add user details
$template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'user'}->{'Username'} = $user->{'Username'};
# Current PeriodKey # Current PeriodKey
my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'}); my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'});
...@@ -152,7 +156,7 @@ sub updateUserStats ...@@ -152,7 +156,7 @@ sub updateUserStats
# Perform query # Perform query
my $sth = DBDo(@dbDoParams); my $sth = DBDo(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_FEATURE_UPDATE_USER_STATS_SQL] Database query failed: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"[MOD_FEATURE_UPDATE_USER_STATS_SQL] Database query failed: ".AWITPT::DB::DBLayer::error());
return; return;
} }
......
...@@ -147,6 +147,23 @@ sub updateUserStats ...@@ -147,6 +147,23 @@ sub updateUserStats
return MOD_RES_SKIP; return MOD_RES_SKIP;
} }
} }
# Set user FUP state
# NK: Perhaps this should be moved to the mod_feature_fup module?
my $fupState = $user->{'AttributeConditionalVariables'}->{"SMRadius_FUP"};
if (defined($fupState)) {
$fupState = $fupState->[0];
} else {
$fupState = "-1";
}
$res = $user->{'_UserDB'}->{'Users_data_set'}($server,$user,
'mod_feature_fup','State',
$fupState
);
if (!defined($res)) {
$server->log(LOG_ERR,"[MOD_USERS_DATA] Failed to store FUP state for user '".$user->{'Username'}."'");
return MOD_RES_SKIP;
}
} }
return MOD_RES_ACK; return MOD_RES_ACK;
......
# Validity support # Validity support
# Copyright (C) 2007-2011, AllWorldIT # Copyright (C) 2007-2016, AllWorldIT
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
...@@ -28,12 +28,10 @@ use DateTime; ...@@ -28,12 +28,10 @@ use DateTime;
use Date::Parse; use Date::Parse;
# Exporter stuff # Exporter stuff
require Exporter; use base qw(Exporter);
our (@ISA,@EXPORT,@EXPORT_OK); our @EXPORT = qw(
@ISA = qw(Exporter);
@EXPORT = qw(
); );
@EXPORT_OK = qw( our @EXPORT_OK = qw(
); );
...@@ -41,7 +39,7 @@ our (@ISA,@EXPORT,@EXPORT_OK); ...@@ -41,7 +39,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
our $pluginInfo = { our $pluginInfo = {
Name => "User Validity Feature", Name => "User Validity Feature",
Init => \&init, Init => \&init,
# Authentication hook # Authentication hook
'Feature_Post-Authentication_hook' => \&checkValidity, 'Feature_Post-Authentication_hook' => \&checkValidity,
'Feature_Post-Accounting_hook' => \&checkValidity 'Feature_Post-Accounting_hook' => \&checkValidity
...@@ -148,15 +146,15 @@ sub checkValidity ...@@ -148,15 +146,15 @@ sub checkValidity
if (defined($validFrom)) { if (defined($validFrom)) {
# Convert string to datetime # Convert string to datetime
my $validFrom_unixtime = str2time($validFrom); my $validFrom_unixtime = str2time($validFrom,$server->{'smradius'}->{'event_timezone'});
if (!defined($validFrom_unixtime)) { if (!defined($validFrom_unixtime)) {
$server->log(LOG_NOTICE,"[MOD_FEATURE_VALIDITY] Date conversion failed on '".$validFrom."'"); $server->log(LOG_NOTICE,"[MOD_FEATURE_VALIDITY] Date conversion failed on '%s'",$validFrom);
# If current time before start of valid pariod # If current time before start of valid pariod
} elsif ($now < $validFrom_unixtime) { } elsif ($now < $validFrom_unixtime) {
my $pretty_dt = DateTime->from_epoch( epoch => $validFrom_unixtime )->strftime('%Y-%m-%d %H:%M:%S'); my $pretty_dt = DateTime->from_epoch( epoch => $validFrom_unixtime )->strftime('%Y-%m-%d %H:%M:%S');
$server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] Current date outside valid start date: '".$pretty_dt."', rejecting"); $server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] Current date outside valid start date: '%s', rejecting",$pretty_dt);
# Date not within valid period, must be disconnected # Date not within valid period, must be disconnected
return MOD_RES_NACK; return MOD_RES_NACK;
...@@ -167,14 +165,14 @@ sub checkValidity ...@@ -167,14 +165,14 @@ sub checkValidity
if (defined($validTo)) { if (defined($validTo)) {
# Convert string to datetime # Convert string to datetime
my $validTo_unixtime = str2time($validTo); my $validTo_unixtime = str2time($validTo,$server->{'smradius'}->{'event_timezone'});
if (!defined($validTo_unixtime)) { if (!defined($validTo_unixtime)) {
$server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] Date conversion failed on '".$validTo."'"); $server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] Date conversion failed on '%s'",$validTo);
# If current time after start of valid pariod # If current time after start of valid pariod
} elsif ($now > $validTo_unixtime) { } elsif ($now > $validTo_unixtime) {
my $pretty_dt = DateTime->from_epoch( epoch => $validTo_unixtime )->strftime('%Y-%m-%d %H:%M:%S'); my $pretty_dt = DateTime->from_epoch( epoch => $validTo_unixtime )->strftime('%Y-%m-%d %H:%M:%S');
$server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] Current date outside valid end date: '".$pretty_dt."', rejecting"); $server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] Current date outside valid end date: '%s', rejecting",$pretty_dt);
# Date not within valid period, must be disconnected # Date not within valid period, must be disconnected
return MOD_RES_NACK; return MOD_RES_NACK;
...@@ -194,18 +192,20 @@ sub checkValidity ...@@ -194,18 +192,20 @@ sub checkValidity
# If current time after start of valid pariod # If current time after start of valid pariod
if ($now > $validUntil) { if ($now > $validUntil) {
my $pretty_dt = DateTime->from_epoch( epoch => $validUntil )->strftime('%Y-%m-%d %H:%M:%S'); my $pretty_dt = DateTime->from_epoch( epoch => $validUntil )->strftime('%Y-%m-%d %H:%M:%S');
$server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] Current date outside valid window end date: '".$pretty_dt."', rejecting"); $server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] Current date outside valid window end date: '%s', ".
"rejecting",$pretty_dt);
# Date not within valid window, must be disconnected # Date not within valid window, must be disconnected
return MOD_RES_NACK; return MOD_RES_NACK;
} }
} }
} else { } else {
$server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] No users_data 'global/FirstLogin' found for user '".$user->{'Username'}."'"); $server->log(LOG_DEBUG,"[MOD_FEATURE_VALIDITY] No users_data 'global/FirstLogin' found for user '%s'",
$user->{'Username'});
} # if (defined(my $res = $module->{'Users_data_get'}($server,$user,'global','FirstLogin'))) { } # if (defined(my $res = $module->{'Users_data_get'}($server,$user,'global','FirstLogin'))) {
} else { } else {
$server->log(LOG_WARN,"[MOD_FEATURE_VALIDITY] UserDB module '".$user->{'_UserDB'}->{'Name'}. $server->log(LOG_WARN,"[MOD_FEATURE_VALIDITY] UserDB module '%s' does not support 'users_data'. Therefore no ".
"' does not support 'users_data'. Therefore no support for Validity Window feature"); "support for Validity Window feature",$user->{'_UserDB'}->{'Name'});
} # if (defined($user->{'_UserDB'}->{'Users_data_get'})) { } # if (defined($user->{'_UserDB'}->{'Users_data_get'})) {
} }
......
...@@ -171,7 +171,7 @@ sub getConfig ...@@ -171,7 +171,7 @@ sub getConfig
$server->log(LOG_DEBUG,"Processing DEFAULT realm attributes"); $server->log(LOG_DEBUG,"Processing DEFAULT realm attributes");
my $sth = DBSelect($config->{'get_config_realm_id_query'},$realmName); my $sth = DBSelect($config->{'get_config_realm_id_query'},$realmName);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get default realm ID: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get default realm ID: ".AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
# Set realm ID # Set realm ID
...@@ -186,7 +186,7 @@ sub getConfig ...@@ -186,7 +186,7 @@ sub getConfig
if (defined($realmID)) { if (defined($realmID)) {
$sth = DBSelect($config->{'get_config_realm_attributes_query'},$realmID); $sth = DBSelect($config->{'get_config_realm_attributes_query'},$realmID);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get default realm config attributes: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get default realm config attributes: ".AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
# Add any default realm attributes to config attributes # Add any default realm attributes to config attributes
...@@ -197,14 +197,14 @@ sub getConfig ...@@ -197,14 +197,14 @@ sub getConfig
} }
# Extract realm from username # Extract realm from username
if (defined($user->{'Username'}) && $user->{'Username'} =~ /^\S+@(\S+)$/) { if (defined($user->{'Username'}) && $user->{'Username'} =~ /^\S+(?:@(\S+))?$/) {
$realmName = $1; my $userRealm = $1 // "";
$server->log(LOG_DEBUG,"Processing realm attributes for '$realmName'"); $server->log(LOG_DEBUG,"Processing attributes for realm '$userRealm'");
$sth = DBSelect($config->{'get_config_realm_id_query'},$realmName); $sth = DBSelect($config->{'get_config_realm_id_query'},$userRealm);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get user realm config attributes: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get realm config attributes: ".AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
# Fetch realm ID # Fetch realm ID
...@@ -216,7 +216,7 @@ sub getConfig ...@@ -216,7 +216,7 @@ sub getConfig
# User realm attributes # User realm attributes
$sth = DBSelect($config->{'get_config_realm_attributes_query'},$realmID); $sth = DBSelect($config->{'get_config_realm_attributes_query'},$realmID);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get user realm config attributes: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get realm config attributes: ".AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
# Add any realm attributes to config attributes # Add any realm attributes to config attributes
...@@ -224,6 +224,8 @@ sub getConfig ...@@ -224,6 +224,8 @@ sub getConfig
processConfigAttribute($server,$user,hashifyLCtoMC($row, qw(Name Operator Value))); processConfigAttribute($server,$user,hashifyLCtoMC($row, qw(Name Operator Value)));
} }
DBFreeRes($sth); DBFreeRes($sth);
$realmName = $userRealm;
} }
} }
...@@ -233,6 +235,8 @@ sub getConfig ...@@ -233,6 +235,8 @@ sub getConfig
return MOD_RES_NACK; return MOD_RES_NACK;
} }
$server->log(LOG_DEBUG,"Realm '$realmName' has ID '$realmID'");
# Get client name # Get client name
my $clientID; my $clientID;
...@@ -258,7 +262,7 @@ sub getConfig ...@@ -258,7 +262,7 @@ sub getConfig
$sth = DBSelect($config->{'get_config_accesslist_query'},$realmID); $sth = DBSelect($config->{'get_config_accesslist_query'},$realmID);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get config attributes: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get config attributes: ".AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
...@@ -299,7 +303,7 @@ sub getConfig ...@@ -299,7 +303,7 @@ sub getConfig
if (defined($clientID)) { if (defined($clientID)) {
my $sth = DBSelect($config->{'get_config_client_attributes_query'},$clientID); my $sth = DBSelect($config->{'get_config_client_attributes_query'},$clientID);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get default config attributes: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get default config attributes: ".AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
# Add to config attributes # Add to config attributes
......
...@@ -28,21 +28,18 @@ use AWITPT::Util; ...@@ -28,21 +28,18 @@ use AWITPT::Util;
use smradius::util; use smradius::util;
use smradius::attributes; use smradius::attributes;
use POSIX qw(ceil strftime); use POSIX qw(ceil);
use DateTime; use DateTime;
use Date::Parse; use Date::Parse;
use Math::BigInt; use Math::BigInt;
use Math::BigFloat; use Math::BigFloat;
# Exporter stuff # Exporter stuff
require Exporter; use base qw(Exporter);
our (@ISA,@EXPORT,@EXPORT_OK); our @EXPORT = qw(
@ISA = qw(Exporter);
@EXPORT = qw(
); );
@EXPORT_OK = qw( our @EXPORT_OK = qw(
); );
...@@ -57,7 +54,10 @@ our $pluginInfo = { ...@@ -57,7 +54,10 @@ our $pluginInfo = {
Cleanup => \&cleanup, Cleanup => \&cleanup,
# User database # User database
Config_get => \&getTopups Config_get => \&getTopups,
# Topups
Feature_Config_Topop_add => \&addTopup,
}; };
# Module config # Module config
...@@ -118,6 +118,29 @@ sub init ...@@ -118,6 +118,29 @@ sub init
AND @TP@users.Username = ? AND @TP@users.Username = ?
'; ';
$config->{'topups_add_query'} = '
INSERT INTO
@TP@topups
(
UserID,
Timestamp,
ValidFrom,
ValidTo,
Type,
Value,
Depleted
)
VALUES
(
%{user.ID},
%{query.Timestamp},
%{query.ValidFrom},
%{query.ValidTo},
%{query.Type},
%{query.Value},
%{query.Depleted}
)
';
# Setup SQL queries # Setup SQL queries
if (defined($scfg->{'mod_config_sql_topups'})) { if (defined($scfg->{'mod_config_sql_topups'})) {
...@@ -140,6 +163,15 @@ sub init ...@@ -140,6 +163,15 @@ sub init
} }
} }
if (defined($scfg->{'mod_config_sql_topups'}->{'topups_add_query'}) &&
$scfg->{'mod_config_sql_topups'}->{'topups_add_query'} ne "") {
if (ref($scfg->{'mod_config_sql_topups'}->{'topups_add_query'}) eq "ARRAY") {
$config->{'topups_add_query'} = join(' ',@{$scfg->{'mod_config_sql_topups'}->{'topups_add_query'}});
} else {
$config->{'topups_add_query'} = $scfg->{'mod_config_sql_topups'}->{'topups_add_query'};
}
}
} }
} }
...@@ -185,7 +217,7 @@ sub getTopups ...@@ -185,7 +217,7 @@ sub getTopups
# Query database # Query database
my $sth = DBSelect($config->{'get_topups_summary_query'},$periodKey,$username); my $sth = DBSelect($config->{'get_topups_summary_query'},$periodKey,$username);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get topup information: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get topup information: %s",AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
while (my $row = hashifyLCtoMC($sth->fetchrow_hashref(), qw(Balance Type ID))) { while (my $row = hashifyLCtoMC($sth->fetchrow_hashref(), qw(Balance Type ID))) {
...@@ -196,7 +228,7 @@ sub getTopups ...@@ -196,7 +228,7 @@ sub getTopups
# Query database # Query database
$sth = DBSelect($config->{'get_topups_query'},$thisMonth->ymd,$now->ymd,$username); $sth = DBSelect($config->{'get_topups_query'},$thisMonth->ymd,$now->ymd,$username);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get topup information: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get topup information: %s",AWITPT::DB::DBLayer::error());
return MOD_RES_NACK; return MOD_RES_NACK;
} }
# Fetch all new topups # Fetch all new topups
...@@ -254,7 +286,7 @@ sub cleanup ...@@ -254,7 +286,7 @@ sub cleanup
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select users: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select users: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
return; return;
} }
...@@ -283,7 +315,7 @@ sub cleanup ...@@ -283,7 +315,7 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to delete topup summaries: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to delete topup summaries: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
DBRollback(); DBRollback();
return; return;
} }
...@@ -299,8 +331,7 @@ sub cleanup ...@@ -299,8 +331,7 @@ sub cleanup
SMAdminDepletedOn >= ?', $thisMonth->ymd() SMAdminDepletedOn >= ?', $thisMonth->ymd()
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to undeplete topups: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to undeplete topups: ".AWITPT::DB::DBLayer::error());
AWITPT::DB::DBLayer::Error());
DBRollback(); DBRollback();
return; return;
} }
...@@ -319,7 +350,7 @@ sub cleanup ...@@ -319,7 +350,7 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to retrieve accounting summaries: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to retrieve accounting summaries: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
DBRollback(); DBRollback();
return; return;
} }
...@@ -346,14 +377,14 @@ sub cleanup ...@@ -346,14 +377,14 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select accounting summary record: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select accounting summary record: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
goto FAIL_ROLLBACK; goto FAIL_ROLLBACK;
} }
# Our usage hash # Our usage hash
my %usageTotals; my %usageTotals;
$usageTotals{'TotalSessionTime'} = Math::BigInt->new(); $usageTotals{'TotalSessionTime'} = Math::BigInt->new(0);
$usageTotals{'TotalDataUsage'} = Math::BigInt->new(); $usageTotals{'TotalDataUsage'} = Math::BigInt->new(0);
# Pull in usage and add up # Pull in usage and add up
if (my $row = hashifyLCtoMC($sth->fetchrow_hashref(), if (my $row = hashifyLCtoMC($sth->fetchrow_hashref(),
...@@ -396,7 +427,7 @@ sub cleanup ...@@ -396,7 +427,7 @@ sub cleanup
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select group usage caps: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select group usage caps: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
goto FAIL_ROLLBACK; goto FAIL_ROLLBACK;
} }
...@@ -451,7 +482,7 @@ sub cleanup ...@@ -451,7 +482,7 @@ sub cleanup
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select user usage caps: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select user usage caps: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
goto FAIL_ROLLBACK; goto FAIL_ROLLBACK;
} }
...@@ -520,7 +551,7 @@ sub cleanup ...@@ -520,7 +551,7 @@ sub cleanup
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select topup summaries: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select topup summaries: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
goto FAIL_ROLLBACK; goto FAIL_ROLLBACK;
} }
...@@ -532,7 +563,7 @@ sub cleanup ...@@ -532,7 +563,7 @@ sub cleanup
if (defined($row->{'ValidTo'})) { if (defined($row->{'ValidTo'})) {
# Convert string to unix time # Convert string to unix time
my $unix_validTo = str2time($row->{'ValidTo'}); my $unix_validTo = str2time($row->{'ValidTo'},$server->{'smradius'}->{'event_timezone'});
# Process traffic topup # Process traffic topup
if (_isTrafficTopup($row->{'Type'})) { if (_isTrafficTopup($row->{'Type'})) {
push(@trafficSummary, { push(@trafficSummary, {
...@@ -590,8 +621,7 @@ sub cleanup ...@@ -590,8 +621,7 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select topups: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to select topups: ".AWITPT::DB::DBLayer::error());
AWITPT::DB::DBLayer::Error());
goto FAIL_ROLLBACK; goto FAIL_ROLLBACK;
} }
...@@ -600,7 +630,7 @@ sub cleanup ...@@ -600,7 +630,7 @@ sub cleanup
while (my $row = hashifyLCtoMC($sth->fetchrow_hashref(), qw(ID Value Type ValidTo))) { while (my $row = hashifyLCtoMC($sth->fetchrow_hashref(), qw(ID Value Type ValidTo))) {
# Convert string to unix time # Convert string to unix time
my $unix_validTo = str2time($row->{'ValidTo'}); my $unix_validTo = str2time($row->{'ValidTo'},$server->{'smradius'}->{'event_timezone'});
# If this is a traffic topup ... # If this is a traffic topup ...
if (_isTrafficTopup($row->{'Type'})) { if (_isTrafficTopup($row->{'Type'})) {
push(@trafficTopups, { push(@trafficTopups, {
...@@ -983,7 +1013,7 @@ sub cleanup ...@@ -983,7 +1013,7 @@ sub cleanup
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to create topup summary: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to create topup summary: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
goto FAIL_ROLLBACK; goto FAIL_ROLLBACK;
} }
...@@ -1010,7 +1040,7 @@ sub cleanup ...@@ -1010,7 +1040,7 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to deplete topup: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to deplete topup: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
goto FAIL_ROLLBACK; goto FAIL_ROLLBACK;
} }
...@@ -1036,7 +1066,7 @@ sub cleanup ...@@ -1036,7 +1066,7 @@ sub cleanup
); );
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to update topups_summary: ". $server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Cleanup => Failed to update topups_summary: ".
AWITPT::DB::DBLayer::Error()); AWITPT::DB::DBLayer::error());
goto FAIL_ROLLBACK; goto FAIL_ROLLBACK;
} }
...@@ -1060,6 +1090,47 @@ FAIL_ROLLBACK: ...@@ -1060,6 +1090,47 @@ FAIL_ROLLBACK:
## @addTopup
# Create a topup
#
# @param server Server object
# @param user User
# @param packet Radius packet
#
# @return Result
sub addTopup
{
my ($server,$user,$validFrom,$validTo,$type,$value) = @_;
# Build template
my $template;
$template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'user'}->{'Username'} = $user->{'Username'};
my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'});
$template->{'query'}->{'Timestamp'} = $now->strftime('%F %T');
$template->{'query'}->{'ValidFrom'} = $validFrom;
$template->{'query'}->{'ValidTo'} = $validTo;
$template->{'query'}->{'Type'} = $type;
$template->{'query'}->{'Value'} = $value;
$template->{'query'}->{'Depleted'} = 0;
# Replace template entries
my @dbDoParams = templateReplace($config->{'topups_add_query'},$template);
# Insert into database
my $sth = DBDo(@dbDoParams);
if (!$sth) {
$server->log(LOG_ERR,"[MOD_CONFIG_SQL_TOPUPS] Failed to insert topup record: %s",AWITPT::DB::DBLayer::error());
return MOD_RES_NACK;
}
return MOD_RES_ACK;
}
## @internal ## @internal
# Function snippet to add up traffic summaries based on topup types # Function snippet to add up traffic summaries based on topup types
sub _trafficSummaryAdd sub _trafficSummaryAdd
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
package smradius::smradius::modules::system::mod_config_test; package smradius::modules::system::mod_config_test;
use strict; use strict;
use warnings; use warnings;
......
# SQL user database support for mac authentication # SQL user database support for mac authentication
# Copyright (C) 2007-2011, AllWorldIT # Copyright (C) 2007-2016, AllWorldIT
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
...@@ -21,13 +21,13 @@ use strict; ...@@ -21,13 +21,13 @@ use strict;
use warnings; use warnings;
# Modules we need # Modules we need
use smradius::constants;
use AWITPT::Cache; use AWITPT::Cache;
use smradius::logging;
use AWITPT::DB::DBLayer; use AWITPT::DB::DBLayer;
use AWITPT::Util; use AWITPT::Util;
use smradius::util;
use smradius::attributes; use smradius::attributes;
use smradius::constants;
use smradius::logging;
use smradius::util;
# Exporter stuff # Exporter stuff
require Exporter; require Exporter;
...@@ -70,7 +70,7 @@ sub init ...@@ -70,7 +70,7 @@ sub init
# Default configs... # Default configs...
$config->{'userdb_macauth_find_query'} = ' $config->{'userdb_macauth_find_query'} = '
SELECT SELECT
user_attributes.ID, user_attributes.ID,
user_attributes.Operator, user_attributes.Disabled, user_attributes.Operator, user_attributes.Disabled,
users.Username, users.Disabled AS UserDisabled users.Username, users.Disabled AS UserDisabled
FROM FROM
...@@ -130,7 +130,8 @@ sub find ...@@ -130,7 +130,8 @@ sub find
foreach my $attr ($packet->attributes) { foreach my $attr ($packet->attributes) {
$template->{'request'}->{$attr} = $packet->rawattr($attr) $template->{'request'}->{$attr} = $packet->rawattr($attr)
} }
$template->{'user'} = $user;
# Add MAC address details
$template->{'user'}->{'MACAddress'} = $macAddress; $template->{'user'}->{'MACAddress'} = $macAddress;
# Replace template entries # Replace template entries
...@@ -138,7 +139,7 @@ sub find ...@@ -138,7 +139,7 @@ sub find
my $sth = DBSelect(@dbDoParams); my $sth = DBSelect(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_USERDB_MACAUTH_SQL] Failed to find data for MAC address: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"[MOD_USERDB_MACAUTH_SQL] Failed to find data for MAC address: ".AWITPT::DB::DBLayer::error());
return MOD_RES_SKIP; return MOD_RES_SKIP;
} }
......
# SQL user database support # SQL user database support
# Copyright (C) 2007-2011, AllWorldIT # Copyright (C) 2007-2016, AllWorldIT
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
...@@ -21,13 +21,13 @@ use strict; ...@@ -21,13 +21,13 @@ use strict;
use warnings; use warnings;
# Modules we need # Modules we need
use smradius::constants;
use AWITPT::Cache; use AWITPT::Cache;
use smradius::logging;
use AWITPT::DB::DBLayer; use AWITPT::DB::DBLayer;
use AWITPT::Util; use AWITPT::Util;
use smradius::util;
use smradius::attributes; use smradius::attributes;
use smradius::constants;
use smradius::logging;
use smradius::util;
# Exporter stuff # Exporter stuff
require Exporter; require Exporter;
...@@ -91,7 +91,7 @@ sub init ...@@ -91,7 +91,7 @@ sub init
FROM FROM
@TP@group_attributes, @TP@users_to_groups @TP@group_attributes, @TP@users_to_groups
WHERE WHERE
@TP@users_to_groups.UserID = %{userdb.ID} @TP@users_to_groups.UserID = %{user.ID}
AND @TP@group_attributes.GroupID = @TP@users_to_groups.GroupID AND @TP@group_attributes.GroupID = @TP@users_to_groups.GroupID
AND @TP@group_attributes.Disabled = 0 AND @TP@group_attributes.Disabled = 0
'; ';
...@@ -102,22 +102,22 @@ sub init ...@@ -102,22 +102,22 @@ sub init
FROM FROM
@TP@user_attributes @TP@user_attributes
WHERE WHERE
UserID = %{userdb.ID} UserID = %{user.ID}
AND Disabled = 0 AND Disabled = 0
'; ';
$config->{'users_data_set_query'} = ' $config->{'users_data_set_query'} = '
INSERT INTO INSERT INTO
@TP@users_data (UserID, LastUpdated, Name, Value) @TP@users_data (UserID, LastUpdated, Name, Value)
VALUES VALUES
( (
%{userdb.ID}, %{user.ID},
%{query.LastUpdated}, %{query.LastUpdated},
%{query.Name}, %{query.Name},
%{query.Value} %{query.Value}
) )
'; ';
$config->{'users_data_update_query'} = ' $config->{'users_data_update_query'} = '
UPDATE UPDATE
@TP@users_data @TP@users_data
...@@ -125,25 +125,25 @@ sub init ...@@ -125,25 +125,25 @@ sub init
LastUpdated = %{query.LastUpdated}, LastUpdated = %{query.LastUpdated},
Value = %{query.Value} Value = %{query.Value}
WHERE WHERE
UserID = %{userdb.ID} UserID = %{user.ID}
AND Name = %{query.Name} AND Name = %{query.Name}
'; ';
$config->{'users_data_get_query'} = ' $config->{'users_data_get_query'} = '
SELECT SELECT
LastUpdated, Name, Value LastUpdated, Name, Value
FROM FROM
@TP@users_data @TP@users_data
WHERE WHERE
UserID = %{userdb.ID} UserID = %{user.ID}
AND Name = %{query.Name} AND Name = %{query.Name}
'; ';
$config->{'users_data_delete_query'} = ' $config->{'users_data_delete_query'} = '
DELETE FROM DELETE FROM
@TP@users_data @TP@users_data
WHERE WHERE
UserID = %{userdb.ID} UserID = %{user.ID}
AND Name = %{query.Name} AND Name = %{query.Name}
'; ';
...@@ -165,10 +165,10 @@ sub init ...@@ -165,10 +165,10 @@ sub init
if (defined($scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'}) && if (defined($scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'}) &&
$scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'} ne "") { $scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'} ne "") {
if (ref($scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'}) eq "ARRAY") { if (ref($scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'}) eq "ARRAY") {
$config->{'userdb_get_group_attributes_query'} = join(' ', $config->{'userdb_get_group_attributes_query'} = join(' ',
@{$scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'}}); @{$scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'}});
} else { } else {
$config->{'userdb_get_group_attributes_query'} = $config->{'userdb_get_group_attributes_query'} =
$scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'}; $scfg->{'mod_userdb_sql'}->{'userdb_get_group_attributes_query'};
} }
} }
...@@ -179,7 +179,7 @@ sub init ...@@ -179,7 +179,7 @@ sub init
$config->{'userdb_get_user_attributes_query'} = join(' ', $config->{'userdb_get_user_attributes_query'} = join(' ',
@{$scfg->{'mod_userdb_sql'}->{'userdb_get_user_attributes_query'}}); @{$scfg->{'mod_userdb_sql'}->{'userdb_get_user_attributes_query'}});
} else { } else {
$config->{'userdb_get_user_attributes_query'} = $config->{'userdb_get_user_attributes_query'} =
$scfg->{'mod_userdb_sql'}->{'userdb_get_user_attributes_query'}; $scfg->{'mod_userdb_sql'}->{'userdb_get_user_attributes_query'};
} }
} }
...@@ -225,10 +225,14 @@ sub init ...@@ -225,10 +225,14 @@ sub init
} }
if (defined($scfg->{'mod_userdb_sql'}->{'userdb_data_cache_time'})) { if (defined($scfg->{'mod_userdb_sql'}->{'userdb_data_cache_time'})) {
if ($scfg->{'mod_userdb_sql'}{'userdb_data_cache_time'} =~ /^\s*(yes|true|1)\s*$/i) { if (defined(my $val = isBoolean($scfg->{'mod_userdb_sql'}{'userdb_data_cache_time'}))) {
# Default? # If val is true, we default to the default anyway
} elsif ($scfg->{'mod_userdb_sql'}{'userdb_data_cache_time'} =~ /^\s*(no|false|0)\s*$/i) {
$config->{'userdb_data_cache_time'} = undef; # We're disabled
if (!$val) {
$config->{'userdb_data_cache_time'} = undef;
}
# We *could* have a value...
} elsif ($scfg->{'mod_userdb_sql'}{'userdb_data_cache_time'} =~ /^[0-9]+$/) { } elsif ($scfg->{'mod_userdb_sql'}{'userdb_data_cache_time'} =~ /^[0-9]+$/) {
$config->{'userdb_data_cache_time'} = $scfg->{'mod_userdb_sql'}{'userdb_data_cache_time'}; $config->{'userdb_data_cache_time'} = $scfg->{'mod_userdb_sql'}{'userdb_data_cache_time'};
} else { } else {
...@@ -264,14 +268,16 @@ sub find ...@@ -264,14 +268,16 @@ sub find
foreach my $attr ($packet->attributes) { foreach my $attr ($packet->attributes) {
$template->{'request'}->{$attr} = $packet->rawattr($attr) $template->{'request'}->{$attr} = $packet->rawattr($attr)
} }
$template->{'user'} = $user;
# Add user details, not user ID is available here as thats what we are retrieving
$template->{'user'}->{'Username'} = $user->{'Username'};
# Replace template entries # Replace template entries
my @dbDoParams = templateReplace($config->{'userdb_find_query'},$template); my @dbDoParams = templateReplace($config->{'userdb_find_query'},$template);
my $sth = DBSelect(@dbDoParams); my $sth = DBSelect(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"[MOD_USERDB_SQL] Failed to find user data: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"[MOD_USERDB_SQL] Failed to find user data: ".AWITPT::DB::DBLayer::error());
return MOD_RES_SKIP; return MOD_RES_SKIP;
} }
...@@ -320,6 +326,11 @@ sub get ...@@ -320,6 +326,11 @@ sub get
foreach my $attr ($packet->attributes) { foreach my $attr ($packet->attributes) {
$template->{'request'}->{$attr} = $packet->rawattr($attr) $template->{'request'}->{$attr} = $packet->rawattr($attr)
} }
# Add user details
$template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'user'}->{'Username'} = $user->{'Username'};
# Add in userdb data # Add in userdb data
foreach my $item (keys %{$user->{'_UserDB_Data'}}) { foreach my $item (keys %{$user->{'_UserDB_Data'}}) {
$template->{'userdb'}->{$item} = $user->{'_UserDB_Data'}->{$item}; $template->{'userdb'}->{$item} = $user->{'_UserDB_Data'}->{$item};
...@@ -331,7 +342,7 @@ sub get ...@@ -331,7 +342,7 @@ sub get
# Query database # Query database
my $sth = DBSelect(@dbDoParams); my $sth = DBSelect(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get group attributes: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get group attributes: ".AWITPT::DB::DBLayer::error());
return RES_ERROR; return RES_ERROR;
} }
...@@ -349,7 +360,7 @@ sub get ...@@ -349,7 +360,7 @@ sub get
# Query database # Query database
$sth = DBSelect(@dbDoParams); $sth = DBSelect(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get user attributes: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get user attributes: ".AWITPT::DB::DBLayer::error());
return RES_ERROR; return RES_ERROR;
} }
...@@ -381,23 +392,28 @@ sub data_set ...@@ -381,23 +392,28 @@ sub data_set
# Build template # Build template
my $template; my $template;
# Last updated time would be now
$template->{'query'}->{'LastUpdated'} = $user->{'_Internal'}->{'Timestamp'}; # Add user details
$template->{'query'}->{'Name'} = sprintf('%s/%s',$module,$name); $template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'query'}->{'Value'} = $value; $template->{'user'}->{'Username'} = $user->{'Username'};
# Add in userdb data # Add in userdb data
foreach my $item (keys %{$user->{'_UserDB_Data'}}) { foreach my $item (keys %{$user->{'_UserDB_Data'}}) {
$template->{'userdb'}->{$item} = $user->{'_UserDB_Data'}->{$item}; $template->{'userdb'}->{$item} = $user->{'_UserDB_Data'}->{$item};
} }
# Last updated time would be now
$template->{'query'}->{'LastUpdated'} = $user->{'_Internal'}->{'Timestamp'};
$template->{'query'}->{'Name'} = sprintf('%s/%s',$module,$name);
$template->{'query'}->{'Value'} = $value;
# Replace template entries # Replace template entries
my @dbDoParams = templateReplace($config->{'users_data_update_query'},$template); my @dbDoParams = templateReplace($config->{'users_data_update_query'},$template);
# Query database # Query database
my $sth = DBDo(@dbDoParams); my $sth = DBDo(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to update users data: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to update users data: ".AWITPT::DB::DBLayer::error());
return RES_ERROR; return RES_ERROR;
} }
...@@ -408,7 +424,7 @@ sub data_set ...@@ -408,7 +424,7 @@ sub data_set
# Insert # Insert
$sth = DBDo(@dbDoParams); $sth = DBDo(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to set users data: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to set users data: ".AWITPT::DB::DBLayer::error());
return RES_ERROR; return RES_ERROR;
} }
} }
...@@ -422,7 +438,7 @@ sub data_set ...@@ -422,7 +438,7 @@ sub data_set
$data{'Module'} = $module; $data{'Module'} = $module;
$data{'Name'} = $name; $data{'Name'} = $name;
$data{'Value'} = $value; $data{'Value'} = $value;
# Cache the result # Cache the result
cacheStoreComplexKeyPair('mod_userdb_sql(users_data)', cacheStoreComplexKeyPair('mod_userdb_sql(users_data)',
sprintf('%s/%s/%s',$module,$user->{'_UserDB_Data'}->{'ID'},$name), sprintf('%s/%s/%s',$module,$user->{'_UserDB_Data'}->{'ID'},$name),
...@@ -453,13 +469,18 @@ sub data_get ...@@ -453,13 +469,18 @@ sub data_get
# Build template # Build template
my $template; my $template;
$template->{'query'}->{'Name'} = sprintf('%s/%s',$module,$name);
# Add user details
$template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'user'}->{'Username'} = $user->{'Username'};
# Add in userdb data # Add in userdb data
foreach my $item (keys %{$user->{'_UserDB_Data'}}) { foreach my $item (keys %{$user->{'_UserDB_Data'}}) {
$template->{'userdb'}->{$item} = $user->{'_UserDB_Data'}->{$item}; $template->{'userdb'}->{$item} = $user->{'_UserDB_Data'}->{$item};
} }
$template->{'query'}->{'Name'} = sprintf('%s/%s',$module,$name);
# If we using caching, check how old the result is # If we using caching, check how old the result is
if (defined($config->{'userdb_data_cache_time'})) { if (defined($config->{'userdb_data_cache_time'})) {
my ($res,$val) = cacheGetComplexKeyPair('mod_userdb_sql(data_get)', my ($res,$val) = cacheGetComplexKeyPair('mod_userdb_sql(data_get)',
...@@ -477,7 +498,7 @@ sub data_get ...@@ -477,7 +498,7 @@ sub data_get
# Query database # Query database
my $sth = DBSelect(@dbDoParams); my $sth = DBSelect(@dbDoParams);
if (!$sth) { if (!$sth) {
$server->log(LOG_ERR,"Failed to get users data: ".AWITPT::DB::DBLayer::Error()); $server->log(LOG_ERR,"Failed to get users data: ".AWITPT::DB::DBLayer::error());
return RES_ERROR; return RES_ERROR;
} }
...@@ -497,7 +518,7 @@ sub data_get ...@@ -497,7 +518,7 @@ sub data_get
# If we using caching and got here, it means that we must cache the result # If we using caching and got here, it means that we must cache the result
if (defined($config->{'userdb_data_cache_time'})) { if (defined($config->{'userdb_data_cache_time'})) {
$data{'CachedUntil'} = $user->{'_Internal'}->{'Timestamp-Unix'} + $config->{'userdb_data_cache_time'}; $data{'CachedUntil'} = $user->{'_Internal'}->{'Timestamp-Unix'} + $config->{'userdb_data_cache_time'};
# Cache the result # Cache the result
cacheStoreComplexKeyPair('mod_userdb_sql(users_data)', cacheStoreComplexKeyPair('mod_userdb_sql(users_data)',
sprintf('%s/%s/%s',$module,$user->{'_UserDB_Data'}->{'ID'},$name), sprintf('%s/%s/%s',$module,$user->{'_UserDB_Data'}->{'ID'},$name),
......
# SMRadius Utility Functions # SMRadius Utility Functions
# Copyright (C) 2007-2015, AllWorldIT # Copyright (C) 2007-2016, AllWorldIT
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -16,103 +16,154 @@ ...@@ -16,103 +16,154 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
## @class smradius::util =encoding utf8
# Utility functions
=head1 NAME
smradius::util - SMRadius utils
=head1 SYNOPSIS
my ($str,@vals) = templateReplace("SELECT * FROM abc WHERE %{abc} = ?",{ 'abc' => "some value" });
my $str = quickTemplateToolkit('someval is "[% someval %]"',{ 'someval' = "hello world" });
=head1 DESCRIPTION
The smradius::util class provides utility classes for SMRadius.
=cut
package smradius::util; package smradius::util;
use parent qw(Exporter);
use strict; use strict;
use warnings; use warnings;
# Exporter stuff our (@EXPORT_OK,@EXPORT);
require Exporter; @EXPORT_OK = qw(
our (@ISA,@EXPORT); );
@ISA = qw(Exporter);
@EXPORT = qw( @EXPORT = qw(
niceUndef
templateReplace templateReplace
isBoolean quickTemplateToolkit
); );
use Template;
## @fn niceUndef($string)
# If string defined return 'string', or if undefined return -undef-
#
# @param string String to check
#
# @return Return 'string' if defined, or -undef- otherwise
sub niceUndef
{
my $string = shift;
return defined($string) ? "'$string'" : '-undef-'; =head1 METHODS
}
The following utility methods are available.
## @fn templateReplace($string,$hashref) =cut
# Template string replacer function
#
# @param string String to replace template items in
# @param hashref Hashref containing the hash of tempalte items & values =head2 templateReplace
#
# @return String with replaced items my ($str,@vals) = templateReplace("SELECT * FROM abc WHERE %{abc} = ?",{ 'abc' => "some value" });
The C<templatereplace> method is used to replace variables with a placeholder. This is very useful for SQL templates. The values
are returned in the second and subsequent array items.
=over
=back
=cut
# Replace hashed variables with placeholders and return an array with the values.
sub templateReplace sub templateReplace
{ {
my ($string,$hashref) = @_; my ($string,$hashref,$placeholder) = @_;
my @valueArray = (); my @valueArray = ();
$placeholder //= '?';
# Replace blanks # Replace blanks
while (my ($entireMacro,$section,$item,$default) = ($string =~ /(\%{([a-z]+)\.([a-z0-9\-]+)(?:=([^}]*))?})/i )) { while (my ($entireMacro,$section,$item,$default) = ($string =~ /(\%\{([a-z]+)\.([a-z0-9\-]+)(?:=([^\}]*))?\})/i )) {
# Replace macro with ? # Replace macro with ? or the placeholder if specified
$string =~ s/$entireMacro/\?/; # We also quote the entireMacro
$string =~ s/\Q$entireMacro\E/$placeholder/;
# Get value to substitute # Get value to substitute
my $value = defined($hashref->{$section}->{$item}) ? $hashref->{$section}->{$item} : $default; my $value = (defined($hashref->{$section}) && defined($hashref->{$section}->{$item})) ?
$hashref->{$section}->{$item} : $default;
# Add value onto our array # Add value onto our array
push(@valueArray,$value); push(@valueArray,$value);
} }
return ($string, @valueArray); return ($string, @valueArray);
} }
## @fn isBoolean($var)
# Check if a variable is boolean =head2 quickTemplateToolkit
#
# @param var Variable to check my $str = quickTemplateToolkit('someval is "[% someval %]"',{ 'someval' = "hello world" });
#
# @return 1, 0 or undef The C<quickTemplateToolkit> is a quick and easy template toolkit function.
sub isBoolean
=over
=back
=cut
# Replace hashed variables with placeholders and return an array with the values.
sub quickTemplateToolkit
{ {
my $var = shift; my ($string,$variables) = @_;
# Check if we're defined # This is the config we're going to pass to Template
if (!defined($var)) { my $config = {
return; # Our include path built below
} INCLUDE_PATH => [ ],
};
# Nuke whitespaces # Create template engine
$var =~ s/\s//g; my $tt = Template->new($config);
# Allow true, on, set, enabled, 1, false, off, unset, disabled, 0 # Process the template and output to our OUTPUT_PATH
if ($var =~ /^(?:true|on|set|enabled|1)$/i) { my $output = "";
return 1; if (!(my $res = $tt->process(\$string, $variables, \$output))) {
} return (undef,$tt->error());
if ($var =~ /^(?:false|off|unset|disabled|0)$/i) {
return 0;
} }
# Invalid or unknown return $output;
return;
} }
1; 1;
# vim: ts=4 __END__
=head1 AUTHORS
Nigel Kukard E<lt>nkukard@lbsd.netE<gt>
=head1 BUGS
All bugs should be reported via the project issue tracker
L<http://gitlab.devlabs.linuxassist.net/awit-frameworks/awit-perl-toolkit/issues/>.
=head1 LICENSE AND COPYRIGHT
Copyright (C) 2007-2016, AllWorldIT
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
=head1 SEE ALSO
L<Template>.
=cut
...@@ -35,7 +35,7 @@ our (@ISA,@EXPORT,@EXPORT_OK); ...@@ -35,7 +35,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
our $VERSION = "1.0.x"; our $VERSION = "1.0.1";
......
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
# Preforking configuration # Preforking configuration
# #
# min_server - Minimum servers to keep around # min_server - Minimum servers to keep around
# min_spare_servers - Minimum spare servers to keep around ready to # min_spare_servers - Minimum spare servers to keep around ready to
# handle requests # handle requests
# max_spare_servers - Maximum spare servers to have around doing nothing # max_spare_servers - Maximum spare servers to have around doing nothing
# max_servers - Maximum servers alltogether # max_servers - Maximum servers alltogether
# max_requests - Maximum number of requests each child will serve # max_requests - Maximum number of requests each child will serve
...@@ -44,14 +44,14 @@ ...@@ -44,14 +44,14 @@
# 1 - Warnings and errors # 1 - Warnings and errors
# 2 - Notices, warnings, errors # 2 - Notices, warnings, errors
# 3 - Info, notices, warnings, errors # 3 - Info, notices, warnings, errors
# 4 - Debugging # 4 - Debugging
#log_level=2 #log_level=2
# File to log to instead of stdout # File to log to instead of stdout
#log_file=/var/log/smradiusd.log #log_file=/var/log/smradiusd.log
# Things to log in extreme detail # Things to log in extreme detail
# modules - Log detailed module running information # modules - Log detailed module running information
# #
# There is no default for this configuration option. Options can be # There is no default for this configuration option. Options can be
# separated by commas. ie. modules # separated by commas. ie. modules
...@@ -65,9 +65,9 @@ ...@@ -65,9 +65,9 @@
#timeout=120 #timeout=120
# cidr_allow/cidr_deny # cidr_allow/cidr_deny
# Comma, whitespace or semi-colon separated. Contains a CIDR block to # Comma, whitespace or semi-colon separated. Contains a CIDR block to
# compare the clients IP to. If cidr_allow or cidr_deny options are # compare the clients IP to. If cidr_allow or cidr_deny options are
# given, the incoming client must match a cidr_allow and not match a # given, the incoming client must match a cidr_allow and not match a
# cidr_deny or the client connection will be closed. # cidr_deny or the client connection will be closed.
#cidr_allow=0.0.0.0/0 #cidr_allow=0.0.0.0/0
#cidr_deny= #cidr_deny=
...@@ -76,16 +76,19 @@ ...@@ -76,16 +76,19 @@
# Defaults to "GMT" # Defaults to "GMT"
event_timezone=GMT event_timezone=GMT
# SMTP server to use when sending email
#smtp_server=127.0.0.1
[radius] [radius]
# Use packet timestamp, if unset, the default is to use the server # Use packet timestamp, if unset, the default is to use the server
# timestamp at the moment the packet is received. # timestamp at the moment the packet is received.
# #
# WARNING!!!! # WARNING!!!!
# Not all routers keep time, it may occur that some routers depend on # Not all routers keep time, it may occur that some routers depend on
# getting date & time apon reboot from an ntp server. The problem # getting date & time apon reboot from an ntp server. The problem
# will arise when the router cannot get the date and time before the # will arise when the router cannot get the date and time before the
# first user logs in .. BAM, you'll have sessions with a period key # first user logs in .. BAM, you'll have sessions with a period key
# in current month but an event timestamp in 1970. # in current month but an event timestamp in 1970.
# #
# Defaults to "no" # Defaults to "no"
...@@ -157,9 +160,10 @@ EOT ...@@ -157,9 +160,10 @@ EOT
[features] [features]
modules=<<EOT modules=<<EOT
mod_feature_capping mod_feature_capping
mod_feature_validity
mod_feature_fup
mod_feature_user_stats mod_feature_user_stats
mod_feature_update_user_stats_sql mod_feature_update_user_stats_sql
mod_feature_validity
EOT EOT
...@@ -255,6 +259,28 @@ get_topups_query=<<EOT ...@@ -255,6 +259,28 @@ get_topups_query=<<EOT
AND @TP@users.Username = ? AND @TP@users.Username = ?
EOT EOT
topups_add_query=<<EOT
INSERT INTO @TP@topups (
UserID,
Timestamp,
ValidFrom,
ValidTo,
Type,
Value,
Depleted
) VALUES (
%{user.ID},
%{query.Timestamp},
%{query.ValidFrom},
%{query.ValidTo},
%{query.Type},
%{query.Value},
%{query.Depleted}
)
EOT
# MOD_ACCOUNTING_SQL # MOD_ACCOUNTING_SQL
[mod_accounting_sql] [mod_accounting_sql]
...@@ -306,26 +332,26 @@ accounting_start_query=<<EOT ...@@ -306,26 +332,26 @@ accounting_start_query=<<EOT
%{request.NAS-Identifier}, %{request.NAS-Identifier},
%{request.NAS-IP-Address}, %{request.NAS-IP-Address},
%{request.Acct-Delay-Time}, %{request.Acct-Delay-Time},
%{request.SessionTime}, %{request.Acct-Session-Time},
%{request.InputOctets}, %{request.Acct-Input-Octets},
%{request.InputGigawords}, %{request.Acct-Input-Gigawords},
%{request.InputPackets}, %{request.Acct-Input-Packets},
%{request.OutputOctets}, %{request.Acct-Output-Octets},
%{request.OutputGigawords}, %{request.Acct-Output-Gigawords},
%{request.OutputPackets}, %{request.Acct-Output-Packets},
%{query.PeriodKey} %{query.PeriodKey}
) )
EOT EOT
accounting_update_get_records_query=<<EOT accounting_update_get_records_query=<<EOT
SELECT SELECT
SUM(AcctInputOctets) AS InputOctets, SUM(AcctInputOctets) AS AcctInputOctets,
SUM(AcctInputPackets) AS InputPackets, SUM(AcctInputPackets) AS AcctInputPackets,
SUM(AcctOutputOctets) AS OutputOctets, SUM(AcctOutputOctets) AS AcctOutputOctets,
SUM(AcctOutputPackets) AS OutputPackets, SUM(AcctOutputPackets) AS AcctOutputPackets,
SUM(AcctInputGigawords) AS InputGigawords, SUM(AcctInputGigawords) AS AcctInputGigawords,
SUM(AcctOutputGigawords) AS OutputGigawords, SUM(AcctOutputGigawords) AS AcctOutputGigawords,
SUM(AcctSessionTime) AS SessionTime, SUM(AcctSessionTime) AS AcctSessionTime,
PeriodKey PeriodKey
FROM FROM
@TP@accounting @TP@accounting
...@@ -344,13 +370,13 @@ accounting_update_query=<<EOT ...@@ -344,13 +370,13 @@ accounting_update_query=<<EOT
UPDATE UPDATE
@TP@accounting @TP@accounting
SET SET
AcctSessionTime = %{query.SessionTime}, AcctSessionTime = %{query.Acct-Session-Time},
AcctInputOctets = %{query.InputOctets}, AcctInputOctets = %{query.Acct-Input-Octets},
AcctInputGigawords = %{query.InputGigawords}, AcctInputGigawords = %{query.Acct-Input-Gigawords},
AcctInputPackets = %{query.InputPackets}, AcctInputPackets = %{query.Acct-Input-Packets},
AcctOutputOctets = %{query.OutputOctets}, AcctOutputOctets = %{query.Acct-Output-Octets},
AcctOutputGigawords = %{query.OutputGigawords}, AcctOutputGigawords = %{query.Acct-Output-Gigawords},
AcctOutputPackets = %{query.OutputPackets}, AcctOutputPackets = %{query.Acct-Output-Packets},
AcctStatusType = %{request.Acct-Status-Type} AcctStatusType = %{request.Acct-Status-Type}
WHERE WHERE
Username = %{user.Username} Username = %{user.Username}
...@@ -413,7 +439,7 @@ EOT ...@@ -413,7 +439,7 @@ EOT
# This is how long we going to cache the usage query for # This is how long we going to cache the usage query for
# Default: 300 (seconds) # Default: 300 (seconds)
# #
# You can use "no", "0", "false" to disable, specify a number > 1, or use # You can use "no", "0", "false" to disable, specify a number > 1, or use
# "yes", "1", "true" to enable with the default value # "yes", "1", "true" to enable with the default value
accounting_usage_cache_time=300 accounting_usage_cache_time=300
...@@ -436,7 +462,7 @@ userdb_get_group_attributes_query=<<EOT ...@@ -436,7 +462,7 @@ userdb_get_group_attributes_query=<<EOT
FROM FROM
@TP@group_attributes, @TP@users_to_groups @TP@group_attributes, @TP@users_to_groups
WHERE WHERE
users_to_groups.UserID = %{userdb.ID} users_to_groups.UserID = %{user.ID}
AND group_attributes.GroupID = users_to_groups.GroupID AND group_attributes.GroupID = users_to_groups.GroupID
AND group_attributes.Disabled = 0 AND group_attributes.Disabled = 0
EOT EOT
...@@ -447,7 +473,7 @@ userdb_get_user_attributes_query=<<EOT ...@@ -447,7 +473,7 @@ userdb_get_user_attributes_query=<<EOT
FROM FROM
@TP@user_attributes @TP@user_attributes
WHERE WHERE
UserID = %{userdb.ID} UserID = %{user.ID}
AND Disabled = 0 AND Disabled = 0
EOT EOT
...@@ -456,7 +482,7 @@ users_data_set_query=<<EOT ...@@ -456,7 +482,7 @@ users_data_set_query=<<EOT
@TP@users_data (UserID, LastUpdated, Name, Value) @TP@users_data (UserID, LastUpdated, Name, Value)
VALUES VALUES
( (
%{userdb.ID}, %{user.ID},
%{query.LastUpdated}, %{query.LastUpdated},
%{query.Name}, %{query.Name},
%{query.Value} %{query.Value}
...@@ -470,7 +496,7 @@ users_data_update_query=<<EOT ...@@ -470,7 +496,7 @@ users_data_update_query=<<EOT
LastUpdated = %{query.LastUpdated}, LastUpdated = %{query.LastUpdated},
Value = %{query.Value} Value = %{query.Value}
WHERE WHERE
UserID = %{userdb.ID} UserID = %{user.ID}
AND Name = %{query.Name} AND Name = %{query.Name}
EOT EOT
...@@ -480,7 +506,7 @@ users_data_get_query=<<EOT ...@@ -480,7 +506,7 @@ users_data_get_query=<<EOT
FROM FROM
@TP@users_data @TP@users_data
WHERE WHERE
UserID = %{userdb.ID} UserID = %{user.ID}
AND Name = %{query.Name} AND Name = %{query.Name}
EOT EOT
...@@ -488,7 +514,7 @@ users_data_delete_query=<<EOT ...@@ -488,7 +514,7 @@ users_data_delete_query=<<EOT
DELETE FROM DELETE FROM
@TP@users_data @TP@users_data
WHERE WHERE
UserID = %{userdb.ID} UserID = %{user.ID}
AND Name = %{query.Name} AND Name = %{query.Name}
EOT EOT
...@@ -510,7 +536,8 @@ update_user_stats_query=<<EOT ...@@ -510,7 +536,8 @@ update_user_stats_query=<<EOT
PeriodKey = %{query.PeriodKey}, PeriodKey = %{query.PeriodKey},
TotalTraffic = %{query.TotalTraffic}, TotalTraffic = %{query.TotalTraffic},
TotalUptime = %{query.TotalUptime}, TotalUptime = %{query.TotalUptime},
NASIdentifier = %{request.NAS-Identifier} NASIdentifier = %{request.NAS-Identifier},
LastAcctUpdate = now()
WHERE WHERE
Username = %{user.Username} Username = %{user.Username}
EOT EOT
...@@ -518,6 +545,10 @@ EOT ...@@ -518,6 +545,10 @@ EOT
# MOD_FEATURE_CAPPING # MOD_FEATURE_CAPPING
[mod_feature_capping] [mod_feature_capping]
# Enable Mikrotik-specific return vattributes # Enable Mikrotik-specific return vattributes
#enable_mikrotik=1 #enable_mikrotik=1
# Enable caveat for SMRadius-Capping-Traffic-Limit having the meaning of 0 and -undef- swapped up to v1.0.x
#caveat_captrafzero=1
# Test harness for module loading
# Copyright (C) 2014-2016, AllWorldIT
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use Test::More;
use strict;
use warnings;
require_ok("smradius::daemon");
require_ok("smradius::client");
done_testing();
use strict;
use warnings;
use AWITPT::Util;
use Data::Dumper;
use Date::Parse;
use POSIX qw(:sys_wait_h);
use Test::Most;
use Test::Most::Exception 'throw_failure';
#
# Check that database tests are enabled
#
# We need DBTESTS enabled to run this
if (!$ENV{'DBTESTS'}) {
plan skip_all => 'DBTESTS not set in ENV';
done_testing();
exit 0;
}
#
# Load database handling libraries
#
require_ok('AWITPT::DB::DBILayer');
require_ok('AWITPT::DB::DBLayer');
use AWITPT::DB::DBLayer;
#
# Load our server and client
#
require_ok("smradius::daemon");
require_ok("smradius::client");
#
# Daemon help
#
can_ok("smradius::daemon","displayHelp");
#
# Try connect to database
#
my $dbh = AWITPT::DB::DBILayer->new({
'Username' => 'root',
'DSN' => 'DBI:mysql:database=smradiustest;host=localhost',
});
# If we cannot connect, just bail out
if ($dbh->connect()) {
BAIL_OUT("ERROR: Failed to connect to database for testing purposes: ".$dbh->error());
}
AWITPT::DB::DBLayer::setHandle($dbh);
#
# Make sure DB is clean
#
my $sth;
$sth = DBDo("DELETE FROM topups");
is(AWITPT::DB::DBLayer::error(),"","Clean table 'topups");
$sth = DBDo("DELETE FROM accounting");
is(AWITPT::DB::DBLayer::error(),"","Clean table 'accounting");
$sth = DBDo("DELETE FROM user_attributes");
is(AWITPT::DB::DBLayer::error(),"","Clean table 'user_attributes");
$sth = DBDo("DELETE FROM client_attributes");
is(AWITPT::DB::DBLayer::error(),"","Clean table 'client_attributes");
$sth = DBDo("DELETE FROM users");
is(AWITPT::DB::DBLayer::error(),"","Clean table 'users'");
$sth = DBDo("DELETE FROM clients_to_realms");
is(AWITPT::DB::DBLayer::error(),"","Clean table 'clients_to_realms'");
$sth = DBDo("DELETE FROM clients");
is(AWITPT::DB::DBLayer::error(),"","Clean table 'clients'");
$sth = DBDo("DELETE FROM realms");
is(AWITPT::DB::DBLayer::error(),"","Clean table 'realms'");
#
# Run server and client
#
our $child;
if ($child = fork()) {
# CHLD handler
local $SIG{CHLD} = sub {
warn "SIGCHLD TRIGGER";
waitpid($child,-1);
};
# Install signal handlers to cleanup if we get a TERM or INT
local $SIG{TERM} = local $SIG{INT} = \&cleanup;
# Wait before starting
sleep(2);
# Setup failure handler
set_failure_handler( sub { my @params = @_; cleanup(); throw_failure } );
my $res;
#
# Make sure basic test without any config does not authenticate users
#
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"auth",
"secret123",
'User-Name=testuser1',
'User-Password=test123',
);
is(ref($res),"","smradclient ref should return ''");
is($res,1,"smradclient result should be 1");
#
# Create test case data
#
my $client1_ID = testDBInsert("Create client 'localhost'",
"INSERT INTO clients (Name,AccessList,Disabled) VALUES ('localhost','127.0.0.0/8',0)"
);
my $client1attr1_ID = testDBInsert("Create client 'localhost' secret",
"INSERT INTO client_attributes (ClientID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$client1_ID,'SMRadius-Config-Secret',':=','secret123'
);
# Blank realm
my $realm1_ID = testDBInsert("Create realm ''",
"INSERT INTO realms (Name,Disabled) VALUES ('',0)"
);
my $clientTOrealm1_ID = testDBInsert("Link client 'localhost' to realm ''",
"INSERT INTO clients_to_realms (ClientID,RealmID,Disabled) VALUES (?,?,0)",$client1_ID,$realm1_ID
);
#
# Check we get an Access-Accept for a bare user using a blank realm
#
my $user1_ID = testDBInsert("Create user 'testuser1'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser1',0)"
);
my $user1attr1_ID = testDBInsert("Create user 'testuser1' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user1_ID,'User-Password','==','test123'
);
my $user1attr2_ID = testDBInsert("Create user 'testuser1' attribute 'Framed-IP-Address'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user1_ID,'Framed-IP-Address',':=','10.0.0.1'
);
my $user1attr3_ID = testDBInsert("Create user 'testuser1' vendor attribute '[14988:Mikrotik-Rate-Limit]'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user1_ID,'[14988:Mikrotik-Rate-Limit]',':=','1024k/512k'
);
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"auth",
"secret123",
'User-Name=testuser1',
'User-Password=test123',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'response'}->{'code'},"Access-Accept","Check our return is 'Access-Accept' for bare user blank '' realm");
# Test the normal attribute and vendor attribute
is($res->{'response'}->{'attributes'}->{'Framed-IP-Address'},"10.0.0.1","Check that attribute 'Framed-IP-Address' is".
" returned");
is($res->{'response'}->{'vattributes'}->{'14988'}->{'Mikrotik-Rate-Limit'}->[0],"1024k/512k","Check that the vendor attribute".
"'14988:Mikrotik-Rate-Limit' is returned");
# Add filter attributes
my $user1attr4_ID = testDBInsert("Create user 'testuser1' filter attribute for 'Framed-IP-Address'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user1_ID,'SMRadius-Config-Filter-Reply-Attribute',':=','Framed-IP-Address'
);
my $user1attr5_ID = testDBInsert("Create user 'testuser1' filter vattribute for 'Mikrotik-Rate-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user1_ID,'SMRadius-Config-Filter-Reply-VAttribute',':=','Mikrotik-Rate-Limit'
);
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"auth",
"secret123",
'User-Name=testuser1',
'User-Password=test123',
);
is(ref($res),"HASH","smradclient should return a HASH");
# We shouldn't....
isnt($res->{'response'}->{'attributes'}->{'Framed-IP-Address'},"10.0.0.1","Check that attribute 'Framed-IP-Address' is".
" returned");
#
# Modify data for the default realm
#
testDBDelete("Delete blank realm '' link to client",
"DELETE FROM clients_to_realms WHERE ID = ?",$clientTOrealm1_ID
);
testDBDelete("Delete blank realm '' for realm '<DEFAULT>' test",
"DELETE FROM realms WHERE ID = ?",$realm1_ID
);
my $realm1b_ID = testDBInsert("Create default realm '<DEFAULT>'",
"INSERT INTO realms (Name,Disabled) VALUES ('<DEFAULT>',0)"
);
my $clientTOrealm1b_ID = testDBInsert("Link client 'localhost' to realm '<DEFAULT>'",
"INSERT INTO clients_to_realms (ClientID,RealmID,Disabled) VALUES (?,?,0)",$client1_ID,$realm1b_ID
);
#
# Check we get an Access-Accept for a bare user using the default realm
#
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"auth",
"secret123",
'User-Name=testuser1',
'User-Password=test123',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'response'}->{'code'},"Access-Accept","Check our return is 'Access-Accept' for bare user on <DEFAULT> realm");
#
# Check we get a Access-Reject for a traffic topup user
#
my $user2_ID = testDBInsert("Create user 'testuser2'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser2',0)"
);
my $user2attr1_ID = testDBInsert("Create user 'testuser2' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user2_ID,'User-Password','==','test123'
);
my $user2attr2_ID = testDBInsert("Create user 'testuser2' attribute 'SMRadius-Capping-Traffic-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user2_ID,'SMRadius-Capping-Traffic-Limit',':=','0'
);
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"auth",
"secret123",
'User-Name=testuser2',
'User-Password=test123',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'response'}->{'code'},"Access-Reject","Check our return is 'Access-Reject' for a traffic topup user");
#
# Check we get a Access-Reject for a uptime topup user
#
my $user2b_ID = testDBInsert("Create user 'testuser2b'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser2b',0)"
);
my $user2battr1_ID = testDBInsert("Create user 'testuser2b' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user2b_ID,'User-Password','==','test123'
);
my $user2battr3_ID = testDBInsert("Create user 'testuser2b' attribute 'SMRadius-Capping-Uptime-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user2b_ID,'SMRadius-Capping-Uptime-Limit',':=','0'
);
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"auth",
"secret123",
'User-Name=testuser2b',
'User-Password=test123',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'response'}->{'code'},"Access-Reject","Check our return is 'Access-Reject' for a uptime topup user");
#
# Test accounting START packet
#
my $session1_ID = "09d15244";
my $session1_Timestamp = time();
my $session1_Timestamp_str = DateTime->from_epoch(epoch => $session1_Timestamp,time_zone => 'UTC')
->strftime('%Y-%m-%d %H:%M:%S');
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"acct",
"secret123",
'NAS-IP-Address=10.0.0.3',
'Acct-Delay-Time=11',
'NAS-Identifier=Test-NAS1',
'Acct-Status-Type=Start',
'Event-Timestamp='.$session1_Timestamp,
'Framed-IP-Address=10.0.1.3',
'Acct-Session-Id='.$session1_ID,
'NAS-Port-Id=test iface name1',
'Called-Station-Id=testservice1',
'Calling-Station-Id=00:00:0C:EE:47:AA',
'User-Name=testuser1',
'NAS-Port-Type=Ethernet',
'NAS-Port=45355555',
'Framed-Protocol=PPP',
'Service-Type=Framed-User',
);
is(ref($res),"HASH","smradclient should return a HASH");
testDBResults("Check accounting record is created correctly",'accounting',{'AcctSessionID' => $session1_ID},
{
'NASIPAddress' => '10.0.0.3',
'AcctDelayTime' => '11',
'NASIdentifier' => 'Test-NAS1',
'AcctStatusType' => 1,
'EventTimestamp' => sub{ _timestampCheck(shift,$session1_Timestamp_str) },
'FramedIPAddress' => '10.0.1.3',
'AcctSessionId' => $session1_ID,
'NASPortId' => 'test iface name1',
'CalledStationId' => 'testservice1',
'CallingStationId' => '00:00:0C:EE:47:AA',
'Username' => 'testuser1',
'NASPortType' => 15,
'NASPort' => '45355555',
'FramedProtocol' => 1,
'ServiceType' => 2,
'AcctOutputPackets' => undef,
'AcctOutputGigawords' => undef,
'AcctOutputOctets' => undef,
'AcctInputPackets' => undef,
'AcctInputGigawords' => undef,
'AcctInputOctets' => undef,
'AcctSessionTime' => undef,
}
);
#
# Test accounting ALIVE packet
#
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"acct",
"secret123",
'Acct-Status-Type=Interim-Update',
'Acct-Output-Packets=800000',
'Acct-Output-Gigawords=0',
'Acct-Output-Octets=810000000',
'Acct-Input-Packets=777777',
'Acct-Input-Gigawords=0',
'Acct-Input-Octets=123456789',
'Acct-Session-Time=999',
'User-Name=testuser1',
'Acct-Session-Id='.$session1_ID,
'NAS-IP-Address=10.0.0.3',
'NAS-Port=45355555',
);
is(ref($res),"HASH","smradclient should return a HASH");
testDBResults("Check accounting record is updated correctly",'accounting',{'AcctSessionID' => $session1_ID},
{
'NASIPAddress' => '10.0.0.3',
'AcctDelayTime' => '11',
'NASIdentifier' => 'Test-NAS1',
'AcctStatusType' => 3,
'EventTimestamp' => sub{ _timestampCheck(shift,$session1_Timestamp_str) },
'FramedIPAddress' => '10.0.1.3',
'AcctSessionId' => $session1_ID,
'NASPortId' => 'test iface name1',
'CalledStationId' => 'testservice1',
'CallingStationId' => '00:00:0C:EE:47:AA',
'Username' => 'testuser1',
'NASPortType' => 15,
'NASPort' => '45355555',
'FramedProtocol' => 1,
'ServiceType' => 2,
'AcctOutputPackets' => '800000',
'AcctOutputGigawords' => '0',
'AcctOutputOctets' => '810000000',
'AcctInputPackets' => '777777',
'AcctInputGigawords' => '0',
'AcctInputOctets' => '123456789',
'AcctSessionTime' => '999',
}
);
#
# Test accounting STOP packet
#
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"acct",
"secret123",
'Acct-Status-Type=Stop',
'Acct-Output-Packets=999999',
'Acct-Output-Gigawords=0',
'Acct-Output-Octets=888888888',
'Acct-Input-Packets=1111111',
'Acct-Input-Gigawords=0',
'Acct-Input-Octets=222222222',
'Acct-Session-Time=3998',
'Acct-Terminate-Cause=Session-Timeout',
'User-Name=testuser1',
'Acct-Session-Id='.$session1_ID,
'NAS-IP-Address=10.0.0.3',
'NAS-Port=45355555',
);
is(ref($res),"HASH","smradclient should return a HASH");
testDBResults("Check accounting record is stopped correctly",'accounting',{'AcctSessionID' => $session1_ID},
{
'NASIPAddress' => '10.0.0.3',
'AcctDelayTime' => '11',
'NASIdentifier' => 'Test-NAS1',
'AcctStatusType' => 2,
'EventTimestamp' => sub{ _timestampCheck(shift,$session1_Timestamp_str) },
'FramedIPAddress' => '10.0.1.3',
'AcctSessionId' => $session1_ID,
'NASPortId' => 'test iface name1',
'CalledStationId' => 'testservice1',
'CallingStationId' => '00:00:0C:EE:47:AA',
'Username' => 'testuser1',
'NASPortType' => 15,
'NASPort' => '45355555',
'FramedProtocol' => 1,
'ServiceType' => 2,
'AcctOutputPackets' => '999999',
'AcctOutputGigawords' => '0',
'AcctOutputOctets' => '888888888',
'AcctInputPackets' => '1111111',
'AcctInputGigawords' => '0',
'AcctInputOctets' => '222222222',
'AcctSessionTime' => '3998',
'AcctTerminateCause' => '5',
}
);
#
# Test missing accounting START packet
#
my $session2_ID = "817a0f1b";
my $session2_Timestamp = time();
my $session2_Timestamp_str = DateTime->from_epoch(epoch => $session2_Timestamp,time_zone => 'UTC')
->strftime('%Y-%m-%d %H:%M:%S');
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"acct",
"secret123",
'User-Name=testuser2',
'NAS-IP-Address=10.0.0.1',
'Acct-Delay-Time=12',
'NAS-Identifier=Test-NAS2',
'Acct-Status-Type=Interim-Update',
'Acct-Output-Packets=786933',
'Acct-Output-Gigawords=0',
'Acct-Output-Octets=708163705',
'Acct-Input-Packets=670235',
'Acct-Input-Gigawords=0',
'Acct-Input-Octets=102600046',
'Acct-Session-Time=800',
'Event-Timestamp='.$session2_Timestamp,
'Framed-IP-Address=10.0.1.1',
'Acct-Session-Id='.$session2_ID,
'NAS-Port-Id=wlan1',
'Called-Station-Id=testservice2',
'Calling-Station-Id=00:00:0C:EE:47:BF',
'NAS-Port-Type=Ethernet',
'NAS-Port=15729175',
'Framed-Protocol=PPP',
'Service-Type=Framed-User',
);
is(ref($res),"HASH","smradclient should return a HASH");
testDBResults("Check missing accounting record is created correctly",'accounting',{'AcctSessionID' => $session2_ID},
{
'Username' => 'testuser2',
'NASIPAddress' => '10.0.0.1',
'AcctDelayTime' => '12',
'NASIdentifier' => 'Test-NAS2',
'AcctStatusType' => 3,
'AcctOutputPackets' => '786933',
'AcctOutputGigawords' => '0',
'AcctOutputOctets' => '708163705',
'AcctInputPackets' => '670235',
'AcctInputGigawords' => '0',
'AcctInputOctets' => '102600046',
'AcctSessionTime' => '800',
'EventTimestamp' => sub{ _timestampCheck(shift,$session2_Timestamp_str) },
'FramedIPAddress' => '10.0.1.1',
'AcctSessionId' => $session2_ID,
'NASPortId' => 'wlan1',
'CalledStationId' => 'testservice2',
'CallingStationId' => '00:00:0C:EE:47:BF',
'NASPortType' => 15,
'NASPort' => '15729175',
'FramedProtocol' => 1,
'ServiceType' => 2,
}
);
#
# Check we get a Access-Accept for a traffic autotopup user
#
my $topuptest1_amount = 100;
my $user3_ID = testDBInsert("Create user 'testuser3'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser3',0)"
);
my $user3attr1_ID = testDBInsert("Create user 'testuser3' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3_ID,'User-Password','==','test456'
);
my $user3attr2_ID = testDBInsert("Create user 'testuser3' attribute 'SMRadius-AutoTopup-Traffic-Enabled'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3_ID,'SMRadius-AutoTopup-Traffic-Enabled',':=','yes'
);
my $user3attr3_ID = testDBInsert("Create user 'testuser3' attribute 'SMRadius-AutoTopup-Traffic-Amount'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3_ID,'SMRadius-AutoTopup-Traffic-Amount',':=',$topuptest1_amount
);
my $user3attr4_ID = testDBInsert("Create user 'testuser3' attribute 'SMRadius-AutoTopup-Traffic-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3_ID,'SMRadius-AutoTopup-Traffic-Limit',':=','500'
);
my $user3attr5_ID = testDBInsert("Create user 'testuser3' attribute 'SMRadius-Capping-Traffic-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3_ID,'SMRadius-Capping-Traffic-Limit',':=','0'
);
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"auth",
"secret123",
'User-Name=testuser3',
'User-Password=test456',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'response'}->{'code'},"Access-Accept","Check our return is 'Access-Accept' for a traffic autotopup user");
# Get the time now
my $topuptest1_now = time();
my $topuptest1 = DateTime->from_epoch( 'epoch' => $topuptest1_now, 'time_zone' => 'UTC');
# Use truncate to set all values after 'month' to their default values
my $topuptest1_thisMonth = $topuptest1->clone()->truncate( to => "month" );
# This month, in string form
my $topuptest1_thisMonth_str = $topuptest1_thisMonth->strftime("%Y-%m-%d %H:%M:%S");
# Next month..
my $topuptest1_nextMonth = $topuptest1_thisMonth->clone()->add( months => 1 );
my $topuptest1_nextMonth_str = $topuptest1_nextMonth->strftime("%Y-%m-%d %H:%M:%S");
testDBResults("Check autotopup is added correctly",'topups',{'UserID' => $user3_ID},
{
'UserID' => $user3_ID,
'Timestamp' => sub { return _timestampCheck(shift,$topuptest1_now) },
'Type' => 5,
'ValidFrom' => $topuptest1_thisMonth_str,
'ValidTo' => $topuptest1_nextMonth_str,
'Value' => $topuptest1_amount,
'Depleted' => 0,
'SMAdminDepletedOn' => undef,
}
);
#
# Check we get a Access-Accept for a uptime autotopup user
#
my $topuptest1b_amount = 100;
my $user3b_ID = testDBInsert("Create user 'testuser3b'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser3b',0)"
);
my $user3battr1_ID = testDBInsert("Create user 'testuser3b' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3b_ID,'User-Password','==','test456'
);
my $user3battr2_ID = testDBInsert("Create user 'testuser3b' attribute 'SMRadius-AutoTopup-Uptime-Enabled'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3b_ID,'SMRadius-AutoTopup-Uptime-Enabled',':=','yes'
);
my $user3battr3_ID = testDBInsert("Create user 'testuser3b' attribute 'SMRadius-AutoTopup-Uptime-Amount'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3b_ID,'SMRadius-AutoTopup-Uptime-Amount',':=',$topuptest1b_amount
);
my $user3battr4_ID = testDBInsert("Create user 'testuser3b' attribute 'SMRadius-AutoTopup-Uptime-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3b_ID,'SMRadius-AutoTopup-Uptime-Limit',':=','500'
);
my $user3battr5_ID = testDBInsert("Create user 'testuser3b' attribute 'SMRadius-Capping-Uptime-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user3b_ID,'SMRadius-Capping-Uptime-Limit',':=','0'
);
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"auth",
"secret123",
'User-Name=testuser3b',
'User-Password=test456',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'response'}->{'code'},"Access-Accept","Check our return is 'Access-Accept' for a uptime autotopup user");
# Get the time now
my $topuptest1b_now = time();
my $topuptest1b = DateTime->from_epoch( 'epoch' => $topuptest1b_now, 'time_zone' => 'UTC');
# Use truncate to set all values after 'month' to their default values
my $topuptest1b_thisMonth = $topuptest1b->clone()->truncate( to => "month" );
# This month, in string form
my $topuptest1b_thisMonth_str = $topuptest1b_thisMonth->strftime("%Y-%m-%d %H:%M:%S");
# Next month..
my $topuptest1b_nextMonth = $topuptest1b_thisMonth->clone()->add( months => 1 );
my $topuptest1b_nextMonth_str = $topuptest1b_nextMonth->strftime("%Y-%m-%d %H:%M:%S");
testDBResults("Check autotopup is added correctly",'topups',{'UserID' => $user3b_ID},
{
'UserID' => $user3b_ID,
'Timestamp' => sub { return _timestampCheck(shift,$topuptest1b_now) },
'Type' => 6,
'ValidFrom' => $topuptest1b_thisMonth_str,
'ValidTo' => $topuptest1b_nextMonth_str,
'Value' => $topuptest1b_amount,
'Depleted' => 0,
'SMAdminDepletedOn' => undef,
}
);
#
# Check that if we send an accounting ALIVE we update the auto-topups
#
my $topuptest2_amount = 100;
my $user4_ID = testDBInsert("Create user 'testuser4'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser4',0)"
);
my $user4attr1_ID = testDBInsert("Create user 'testuser4' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user4_ID,'User-Password','==','test456'
);
my $user4attr2_ID = testDBInsert("Create user 'testuser4' attribute 'SMRadius-AutoTopup-Traffic-Enabled'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user4_ID,'SMRadius-AutoTopup-Traffic-Enabled',':=','yes'
);
my $user4attr3_ID = testDBInsert("Create user 'testuser4' attribute 'SMRadius-AutoTopup-Traffic-Amount'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user4_ID,'SMRadius-AutoTopup-Traffic-Amount',':=',$topuptest2_amount
);
my $user4attr4_ID = testDBInsert("Create user 'testuser4' attribute 'SMRadius-AutoTopup-Traffic-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user4_ID,'SMRadius-AutoTopup-Traffic-Limit',':=','500'
);
my $user4attr5_ID = testDBInsert("Create user 'testuser4' attribute 'SMRadius-Capping-Traffic-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user4_ID,'SMRadius-Capping-Traffic-Limit',':=','0'
);
my $user4attr6_ID = testDBInsert("Create user 'testuser4' attribute 'SMRadius-AutoTopup-Traffic-Notify'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user4_ID,'SMRadius-AutoTopup-Traffic-Notify',':=','root@localhost'
);
my $session3_ID = "9c5f24a";
my $session3_Timestamp = time();
$res = smradius::client->run(
"--raddb","dicts",
"127.0.0.1",
"acct",
"secret123",
'User-Name=testuser4',
'NAS-IP-Address=10.0.0.1',
'Acct-Delay-Time=12',
'NAS-Identifier=Test-NAS2',
'Acct-Status-Type=Interim-Update',
'Acct-Output-Packets=786933',
'Acct-Output-Gigawords=0',
'Acct-Output-Octets=708163705',
'Acct-Input-Packets=670235',
'Acct-Input-Gigawords=0',
'Acct-Input-Octets=102600046',
'Acct-Session-Time=800',
'Event-Timestamp='.$session3_Timestamp,
'Framed-IP-Address=10.0.1.1',
'Acct-Session-Id='.$session3_ID,
'NAS-Port-Id=wlan1',
'Called-Station-Id=testservice2',
'Calling-Station-Id=00:00:0C:EE:47:BF',
'NAS-Port-Type=Ethernet',
'NAS-Port=15729175',
'Framed-Protocol=PPP',
'Service-Type=Framed-User',
);
is(ref($res),"HASH","smradclient should return a HASH");
# Get the time now
my $topuptest2_now = time();
my $topuptest2 = DateTime->from_epoch( 'epoch' => $topuptest2_now, 'time_zone' => 'UTC');
# Use truncate to set all values after 'month' to their default values
my $topuptest2_thisMonth = $topuptest2->clone()->truncate( to => "month" );
# This month, in string form
my $topuptest2_thisMonth_str = $topuptest2_thisMonth->strftime("%Y-%m-%d %H:%M:%S");
# Next month..
my $topuptest2_nextMonth = $topuptest2_thisMonth->clone()->add( months => 1 );
my $topuptest2_nextMonth_str = $topuptest2_nextMonth->strftime("%Y-%m-%d %H:%M:%S");
testDBResults("Check autotopup is added correctly after acct_log",'topups',{'UserID' => $user4_ID},
{
'UserID' => $user4_ID,
'Timestamp' => sub { return _timestampCheck(shift,$topuptest2_now) },
'Type' => 5,
'ValidFrom' => $topuptest2_thisMonth_str,
'ValidTo' => $topuptest2_nextMonth_str,
'Value' => $topuptest2_amount,
'Depleted' => 0,
'SMAdminDepletedOn' => undef,
}
);
#
# Check that if we send an accounting ALIVE we do not trigger FUP
#
my $user5_ID = testDBInsert("Create user 'testuser5'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser5',0)"
);
my $user5attr1_ID = testDBInsert("Create user 'testuser5' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user5_ID,'User-Password','==','test456'
);
my $user5attr2_ID = testDBInsert("Create user 'testuser5' attribute 'SMRadius-FUP-Period'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user5_ID,'SMRadius-FUP-Period',':=','1'
);
my $user5attr3_ID = testDBInsert("Create user 'testuser5' attribute 'SMRadius-FUP-Traffic-Threshold'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user5_ID,'SMRadius-FUP-Traffic-Threshold',':=',800
);
# Add an attribute so we can check the FUP match results
my $user5attr4_ID = testDBInsert("Create user 'testuser5' attribute 'SMRadius-Evaluate'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user5_ID,'SMRadius-Evaluate','||+=',"SMRadius_FUP > 0 ? [14988:Mikrotik-Rate-Limit] = 1638k/8m : [14988:Mikrotik-Rate-Limit] = 1k/1m"
);
my $session4_ID = "a8abc40";
my $session4_Timestamp = time();
$res = smradius::client->run(
"--raddb","dicts",
"--listen","127.0.0.1:1700",
"127.0.0.1",
"acct",
"secret123",
'User-Name=testuser5',
'NAS-IP-Address=10.0.0.1',
'Acct-Delay-Time=12',
'NAS-Identifier=Test-NAS2',
'Acct-Status-Type=Interim-Update',
'Acct-Output-Packets=786933',
'Acct-Output-Gigawords=0',
'Acct-Output-Octets=708163705',
'Acct-Input-Packets=670235',
'Acct-Input-Gigawords=0',
'Acct-Input-Octets=102600046',
'Acct-Session-Time=800',
'Event-Timestamp='.$session4_Timestamp,
'Framed-IP-Address=10.0.1.1',
'Acct-Session-Id='.$session4_ID,
'NAS-Port-Id=wlan1',
'Called-Station-Id=testservice2',
'Calling-Station-Id=00:00:0C:EE:47:BF',
'NAS-Port-Type=Ethernet',
'NAS-Port=15729175',
'Framed-Protocol=PPP',
'Service-Type=Framed-User',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'listen'}->{'response'}->{'code'},"CoA-Request","Check that the packet we got back is infact a ".
"CoA-Request");
is($res->{'listen'}->{'response'}->{'vattributes'}->{'14988'}->{'Mikrotik-Rate-Limit'}->[0],"1k/1m","Check that the vendor attribute".
"'14988:Mikrotik-Rate-Limit' is returned on the negative side of the IF");
testDBResults("Check FUP state was added to the user stats table as 0",'users_data',
{'UserID' => $user5_ID, 'Name' => "mod_feature_fup/State"},
{'Value' => "0"},
1, # Disable order
);
#
# Check that if we send an accounting ALIVE with a usage amount that exceeds FUP, that we trigger it
#
my $user6_ID = testDBInsert("Create user 'testuser6'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser6',0)"
);
my $user6attr1_ID = testDBInsert("Create user 'testuser6' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user6_ID,'User-Password','==','test456'
);
my $user6attr2_ID = testDBInsert("Create user 'testuser6' attribute 'SMRadius-FUP-Period'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user6_ID,'SMRadius-FUP-Period',':=','1'
);
my $user6attr3_ID = testDBInsert("Create user 'testuser6' attribute 'SMRadius-FUP-Traffic-Threshold'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user6_ID,'SMRadius-FUP-Traffic-Threshold',':=',800
);
# Add an attribute so we can check the FUP match results
my $user6attr4_ID = testDBInsert("Create user 'testuser6' attribute 'SMRadius-Evaluate'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user6_ID,'SMRadius-Evaluate','||+=',"SMRadius_FUP > 0 ? [14988:Mikrotik-Rate-Limit] = 1638k/8m : [14988:Mikrotik-Rate-Limit] = 1k/1m"
);
my $session5_ID = "582dc00";
my $session5_Timestamp = time();
$res = smradius::client->run(
"--raddb","dicts",
"--listen","127.0.0.1:1700",
"127.0.0.1",
"acct",
"secret123",
'User-Name=testuser6',
'NAS-IP-Address=10.0.0.1',
'Acct-Delay-Time=12',
'NAS-Identifier=Test-NAS2',
'Acct-Status-Type=Interim-Update',
'Acct-Output-Packets=786933',
'Acct-Output-Gigawords=0',
'Acct-Output-Octets=808163705',
'Acct-Input-Packets=670235',
'Acct-Input-Gigawords=0',
'Acct-Input-Octets=202600046',
'Acct-Session-Time=800',
'Event-Timestamp='.$session5_Timestamp,
'Framed-IP-Address=10.0.1.1',
'Acct-Session-Id='.$session5_ID,
'NAS-Port-Id=wlan1',
'Called-Station-Id=testservice2',
'Calling-Station-Id=00:00:0C:EE:47:BF',
'NAS-Port-Type=Ethernet',
'NAS-Port=15729175',
'Framed-Protocol=PPP',
'Service-Type=Framed-User',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'listen'}->{'response'}->{'code'},"CoA-Request","Check that the packet we got back is infact a ".
"CoA-Request");
is($res->{'listen'}->{'response'}->{'vattributes'}->{'14988'}->{'Mikrotik-Rate-Limit'}->[0],"1638k/8m","Check that the ".
"vendor attribute '14988:Mikrotik-Rate-Limit' is returned on the success side of the FUP check");
testDBResults("Check FUP state was added to the user stats table as 1",'users_data',
{'UserID' => $user6_ID, 'Name' => "mod_feature_fup/State"},
{'Value' => "1"},
1, # Disable order
);
#
# Check that if we send an accounting ALIVE with a usage amount that exceeds capping, that we trigger a POD
#
my $user7_ID = testDBInsert("Create user 'testuser7'",
"INSERT INTO users (UserName,Disabled) VALUES ('testuser7',0)"
);
my $user7attr1_ID = testDBInsert("Create user 'testuser7' attribute 'User-Password'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user7_ID,'User-Password','==','test456'
);
# Add an attribute so we can check the capping does a POD
my $user7attr4_ID = testDBInsert("Create user 'testuser7' attribute 'SMRadius-Capping-Traffic-Limit'",
"INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)",
$user7_ID,'SMRadius-Capping-Traffic-Limit',':=','1'
);
my $session6_ID = "5209dac0";
my $session6_Timestamp = time();
$res = smradius::client->run(
"--raddb","dicts",
"--listen","127.0.0.1:1700",
"127.0.0.1",
"acct",
"secret123",
'User-Name=testuser7',
'NAS-IP-Address=10.0.0.1',
'Acct-Delay-Time=12',
'NAS-Identifier=Test-NAS2',
'Acct-Status-Type=Interim-Update',
'Acct-Output-Packets=786933',
'Acct-Output-Gigawords=0',
'Acct-Output-Octets=808163705',
'Acct-Input-Packets=670235',
'Acct-Input-Gigawords=0',
'Acct-Input-Octets=202600046',
'Acct-Session-Time=800',
'Event-Timestamp='.$session6_Timestamp,
'Framed-IP-Address=10.0.1.1',
'Acct-Session-Id='.$session6_ID,
'NAS-Port-Id=wlan1',
'Called-Station-Id=testservice2',
'Calling-Station-Id=00:00:0C:EE:47:BF',
'NAS-Port-Type=Ethernet',
'NAS-Port=15729175',
'Framed-Protocol=PPP',
'Service-Type=Framed-User',
);
is(ref($res),"HASH","smradclient should return a HASH");
is($res->{'listen'}->{'response'}->{'code'},"Disconnect-Request","Check that the packet we got back is infact a ".
"Disconnect-Request");
sleep(5);
} else {
smradius::daemon->run(
"--fg",
"--debug",
"--config", "smradiusd.conf.test",
);
sleep 4;
exit 0;
}
cleanup();
done_testing();
# Cleanup function
sub cleanup
{
if ($child) {
# Kill the child if it exists
if (kill(0,$child)) {
kill('TERM',$child);
}
# Wait for it to be reaped
waitpid($child,-1);
}
}
# Function to quickly and easily insert data into the DB and generate 2 tests out of it
sub testDBInsert
{
my ($name,@params) = @_;
# Do the work...
DBDo(@params);
# Make sure we got no error
is(AWITPT::DB::DBLayer::error(),"",$name);
# Grab the last insert ID
my $id = DBLastInsertID();
# Make sure its > 0
is($id > 0,1,"$name, insert ID > 0");
return $id;
}
# Function to quickly and easily delete data from the DB
sub testDBDelete
{
my ($name,@params) = @_;
# Do the work...
DBDo(@params);
# Make sure we got no error
is(AWITPT::DB::DBLayer::error(),"",$name);
return 1;
}
# Test DB select results
sub testDBResults
{
my ($name,$table,$where,$resultCheck,$disableOrder) = @_;
# Build column list
my $columnList_str = join(',',keys %{$resultCheck});
# Create where criteria
my @whereLines = ();
my @whereData = ();
foreach my $columnName (keys %{$where}) {
# Add template placeholders
push(@whereLines,"$columnName = ?");
# Add data for template placeholders
push(@whereData,$where->{$columnName});
}
my $whereLines_str = join(' AND ',@whereLines);
# Check if we're not disabling ordering
my $extraSQL = "";
if (!defined($disableOrder)) {
$extraSQL = "ORDER BY ID DESC";
}
my $sqlQuery = "
SELECT
$columnList_str
FROM
$table
WHERE
$whereLines_str
$extraSQL
";
# Do select
my $sth = DBSelect($sqlQuery,@whereData);
# Make sure we got no error
is(AWITPT::DB::DBLayer::error(),"","Errors on DBSelect ($sqlQuery interpolated with ".join(', ',$extraSQL).": $name");
# We should get one result...
my $row = hashifyLCtoMC($sth->fetchrow_hashref(),keys %{$resultCheck});
is(defined($row),1,"DBSelect row defined: $name");
# Loop through results and check if they match
foreach my $resultName (keys %{$resultCheck}) {
# Check if the result is a code-based subroutine
if (ref(my $result = $resultCheck->{$resultName}) eq "CODE") {
is($resultCheck->{$resultName}($row->{$resultName}),1,"$name: $resultName sub{} check");
} else {
is($row->{$resultName},$resultCheck->{$resultName},"$name: $resultName check");
}
}
}
sub _timestampCheck
{
my ($testVal,$rightVal) = @_;
# Make sure testVal is defined
return "_timestampCheck: NO \$testVal" if (!defined($testVal));
# Make sure $testVal_time is returned form str2time
my $testVal_time = str2time($testVal,'UTC');
# Check if $rightVal is defined
my $rightVal_time;
if (!defined($rightVal)) {
$rightVal_time = time();
} elsif ($rightVal =~ /^\d+$/) {
$rightVal_time = $rightVal;
} else {
$rightVal_time = str2time($rightVal,'UTC');
}
# Make sure rightVal_time is defined
return "_timestampCheck: NO \$rightVal_time" if (!defined($rightVal_time));
# Grab the absolute difference
my $diff = abs($testVal_time - $rightVal_time);
return ($diff < 10) // "TIME DEVIATION: $diff";
}