From f5ca42bf2f983ba36753d32d662f1bd17bfbe88b Mon Sep 17 00:00:00 2001
From: Robert Anderson <randerson@lbsd.net>
Date: Wed, 27 May 2009 11:58:54 +0000
Subject: [PATCH] Added topup support

---
 .../modules/config/mod_config_sql_topups.pm   | 138 ++++++++++++++++++
 .../modules/features/mod_feature_capping.pm   | 126 ++++++++++++----
 smradiusd.conf.testing                        |   1 +
 3 files changed, 240 insertions(+), 25 deletions(-)
 create mode 100644 smradius/modules/config/mod_config_sql_topups.pm

diff --git a/smradius/modules/config/mod_config_sql_topups.pm b/smradius/modules/config/mod_config_sql_topups.pm
new file mode 100644
index 00000000..6494da0b
--- /dev/null
+++ b/smradius/modules/config/mod_config_sql_topups.pm
@@ -0,0 +1,138 @@
+# SQL config database support
+# Copyright (C) 2007-2009, 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 mod_config_sql_topups;
+
+use strict;
+use warnings;
+
+# Modules we need
+use smradius::constants;
+use smradius::logging;
+use smradius::dblayer;
+use smradius::util;
+use smradius::attributes;
+use Data::Dumper;
+
+# Exporter stuff
+require Exporter;
+our (@ISA,@EXPORT,@EXPORT_OK);
+@ISA = qw(Exporter);
+@EXPORT = qw(
+);
+@EXPORT_OK = qw(
+);
+
+
+
+# Plugin info
+our $pluginInfo = {
+	Name => "SQL Topups Database",
+	Init => \&init,
+	
+	# User database
+	Config_get => \&getTopups
+};
+
+# Module config
+my $config;
+
+## @internal
+# Initialize module
+sub init
+{
+	my $server = shift;
+	my $scfg = $server->{'inifile'};
+
+
+	# Enable support for database
+	if (!$server->{'smradius'}->{'database'}->{'enabled'}) {
+		$server->log(LOG_NOTICE,"[MOD_USERDB_SQL] Enabling database support.");
+		$server->{'smradius'}->{'database'}->{'enabled'} = 1;
+	}
+
+	# Default configs...
+	$config->{'get_topups_query'} = '
+		SELECT 
+				@TP@topups.ValidFrom,
+				@TP@topups.ValidTo,
+				@TP@topups.Value
+		FROM 
+				@TP@topups,
+				@TP@users
+		WHERE
+				@TP@topups.UserID = @TP@users.ID
+		AND
+				@TP@users.Username = ?
+	';
+	
+
+	# Setup SQL queries
+	if (defined($scfg->{'mod_topups_sql'})) {
+		# Pull in queries
+		if (defined($scfg->{'mod_topups_sql'}->{'get_config_query'}) &&
+				$scfg->{'mod_topups_sql'}->{'get_config_query'} ne "") {
+			if (ref($scfg->{'mod_topups_sql'}->{'get_config_query'}) eq "ARRAY") {
+				$config->{'get_config_query'} = join(' ',@{$scfg->{'mod_config_sql'}->{'get_config_query'}});
+			} else {
+				$config->{'get_config_query'} = $scfg->{'mod_config_sql'}->{'get_config_query'};
+			}
+			
+		}
+	}
+}
+
+
+## @getTopups
+# Try to get topup information
+#
+# @param server Server object
+# @param user User
+# @param packet Radius packet
+#
+# @return Result
+sub getTopups
+{
+	my ($server,$user,$packet) = @_;
+
+	# Set up dbDoParams
+	my @dbDoParams = ($config->{'get_topups_query'},$packet->attr('User-Name'));
+
+	# Query database
+	my $sth = DBSelect(@dbDoParams);
+	if (!$sth) {
+		$server->log(LOG_ERR,"Failed to get topup information: ".smradius::dblayer::Error());
+		return MOD_RES_NACK;
+	}
+
+	# Fetch items 
+	my $topupTotal = 0;
+	while (my $row = $sth->fetchrow_hashref()) {
+		$topupTotal += $row->{'value'};
+	}
+
+	# Add to ConfigAttributes
+	processConfigAttribute($server,$user->{'ConfigAttributes'},{ 'Name' => 'SMRadius-Capping-Traffic-Topup', 'Operator' => ':=', 'Value' => $topupTotal });
+
+	DBFreeRes($sth);
+
+	return MOD_RES_ACK;
+}
+
+
+1;
+# vim: ts=4
diff --git a/smradius/modules/features/mod_feature_capping.pm b/smradius/modules/features/mod_feature_capping.pm
index 84ef4447..4557d9ec 100644
--- a/smradius/modules/features/mod_feature_capping.pm
+++ b/smradius/modules/features/mod_feature_capping.pm
@@ -24,6 +24,7 @@ use warnings;
 use smradius::constants;
 use smradius::logging;
 use smradius::util;
+use Date::Parse;
 
 # Exporter stuff
 require Exporter;
@@ -40,7 +41,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
 our $pluginInfo = {
 	Name => "User Capping Feature",
 	Init => \&init,
-	
+
 	# Authentication hook
 	'Feature_Post-Authentication_hook' => \&post_auth_hook,
 
@@ -52,7 +53,7 @@ our $pluginInfo = {
 # Some constants
 my $TRAFFIC_LIMIT_KEY = 'SMRadius-Capping-Traffic-Limit';
 my $UPTIME_LIMIT_KEY = 'SMRadius-Capping-UpTime-Limit';
-
+my $TRAFFIC_TOPUPS_KEY = 'SMRadius-Capping-Traffic-Topup';
 
 ## @internal
 # Initialize module
@@ -75,9 +76,9 @@ sub post_auth_hook
 	my ($server,$user,$packet) = @_;
 
 	$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] POST AUTH HOOK");
-	
+
 	my ($trafficLimit,$timeLimit);
-	
+
 	# Compare uptime limit
 	if (defined($user->{'Attributes'}->{$UPTIME_LIMIT_KEY})) {
 		$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] '".$UPTIME_LIMIT_KEY."' is defined");
@@ -87,15 +88,17 @@ sub post_auth_hook
 			if ($user->{'Attributes'}->{$UPTIME_LIMIT_KEY}->{':='}->{'Value'} =~ /^[0-9]+$/) {
 				$timeLimit = $user->{'Attributes'}->{$UPTIME_LIMIT_KEY};
 			} else {
-				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'Attributes'}->{$UPTIME_LIMIT_KEY}->{':='}->{'Value'}."' is NOT a numeric value");
+				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'Attributes'}->{$UPTIME_LIMIT_KEY}->{':='}->{'Value'}.
+						"' is NOT a numeric value");
 			}
 		} else {
-			$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] No valid operators for attribute '".$user->{'Attributes'}->{$UPTIME_LIMIT_KEY}."'");
+			$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] No valid operators for attribute '".
+					$user->{'Attributes'}->{$UPTIME_LIMIT_KEY}."'");
 		}
 	}
 
 
-	# Compare SMRadius-Capping-Traffic-Limit
+	# Compare traffic limit
 	if (defined($user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY})) {
 		$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] '".$TRAFFIC_LIMIT_KEY."' is defined");
 		# Operator: +=
@@ -104,14 +107,16 @@ sub post_auth_hook
 			if ($user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}->{':='}->{'Value'} =~ /^[0-9]+$/) {
 				$trafficLimit = $user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY};
 			} else {
-				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}->{':='}->{'Value'}."' is NOT a numeric value");
+				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}->{':='}->{'Value'}.
+						"' is NOT a numeric value");
 			}
 		} else {
-			$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] No valid operators for attribute '".$user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}."'");
+			$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] No valid operators for attribute '".
+					$user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}."'");
 		}
 	}
 
-	# Check if we need to get the users' usage	
+	# Check if we need to get the users' usage
 	my $accountingUsage;
 	if (defined($timeLimit) || defined($trafficLimit)) {
 		# Loop with plugins to find anyting supporting getting of usage
@@ -134,15 +139,48 @@ sub post_auth_hook
 	# Check values against limits
 	if (defined($timeLimit)) {
 		if ($accountingUsage->{'TotalTimeUsage'} >= $timeLimit->{':='}->{'Value'}) {
-			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Usage exceeds ".$timeLimit->{':='}->{'Value'}.", returning [NACK]");
+			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Usage exceeds ".$timeLimit->{':='}->{'Value'}.", rejecting");
 			# Exceeding maximum, must be disconnected
 			return MOD_RES_NACK;
 		}
 	}
+
+	my $topupAmount = 0;
 	if (defined($trafficLimit)) {
-		if ($accountingUsage->{'TotalDataUsage'} >= $trafficLimit->{':='}->{'Value'}) {
-			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Usage exceeds ".$trafficLimit->{':='}->{'Value'}.", returning [NACK]");
-			# Exceeding maximum, must be disconnected
+
+		# Get topups
+		if (defined($user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY})) {
+			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] '".$TRAFFIC_TOPUPS_KEY."' is defined");
+			# Operator: +=
+			if (defined($user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY}->[0])) {
+				# Is it a number?
+				if ($user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY}->[0] =~ /^[0-9]+$/) {
+					$topupAmount = $user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY}->[0];
+				} else {
+					$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY}->[0].
+							"' is NOT a numeric value");
+				}
+			} else {
+				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$TRAFFIC_TOPUPS_KEY."' has no value");
+			}
+		}
+
+		# Set allowed traffic usage
+		my $alteredTrafficLimit;
+		if ($topupAmount > 0) {
+			$alteredTrafficLimit = $trafficLimit->{':='}->{'Value'} + $topupAmount;
+		} else {
+			$alteredTrafficLimit = $trafficLimit->{':='}->{'Value'};
+		}
+
+		# Bandwidth usage
+		$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Bandwidth => Usage total: ".$accountingUsage->{'TotalDataUsage'}.
+				"Mb (Cap: ".$trafficLimit->{':='}->{'Value'}."Mb, Topups: ".$topupAmount."Mb)");
+
+		# If bandwidth limit exceeded, cap user
+		if ($accountingUsage->{'TotalDataUsage'} >= $alteredTrafficLimit) {
+			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Usage of ".$accountingUsage->{'TotalDataUsage'}.
+					"Mb exceeds allowed limit of ".$alteredTrafficLimit."Mb. Capped.");
 			return MOD_RES_NACK;
 		}
 	}
@@ -163,6 +201,10 @@ sub post_acct_hook
 {
 	my ($server,$user,$packet) = @_;
 
+
+	# Exceeding maximum, must be disconnected
+	return MOD_RES_SKIP if ($packet->attr('Acct-Status-Type') ne "Alive");
+
 	$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] POST ACCT HOOK");
 
 	my ($trafficLimit,$timeLimit);
@@ -176,10 +218,12 @@ sub post_acct_hook
 			if ($user->{'Attributes'}->{$UPTIME_LIMIT_KEY}->{':='}->{'Value'} =~ /^[0-9]+$/) {
 				$timeLimit = $user->{'Attributes'}->{$UPTIME_LIMIT_KEY};
 			} else {
-				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'Attributes'}->{$UPTIME_LIMIT_KEY}->{':='}->{'Value'}."' is NOT a numeric value");
+				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'Attributes'}->{$UPTIME_LIMIT_KEY}->{':='}->{'Value'}.
+						"' is NOT a numeric value");
 			}
 		} else {
-			$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] No valid operators for attribute '".$user->{'Attributes'}->{$UPTIME_LIMIT_KEY}."'");
+			$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] No valid operators for attribute '".
+					$user->{'Attributes'}->{$UPTIME_LIMIT_KEY}."'");
 		}
 	}
 
@@ -193,14 +237,16 @@ sub post_acct_hook
 			if ($user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}->{':='}->{'Value'} =~ /^[0-9]+$/) {
 				$trafficLimit = $user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY};
 			} else {
-				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}->{':='}->{'Value'}."' is NOT a numeric value");
+				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}->{':='}->{'Value'}.
+						"' is NOT a numeric value");
 			}
 		} else {
-			$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] No valid operators for attribute '".$user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}."'");
+			$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] No valid operators for attribute '".
+					$user->{'Attributes'}->{$TRAFFIC_LIMIT_KEY}."'");
 		}
 	}
 
-	# Check if we need to get the users' usage	
+	# Check if we need to get the users' usage
 	my $accountingUsage;
 	if (defined($timeLimit) || defined($trafficLimit)) {
 		# Loop with plugins to find anyting supporting getting of usage
@@ -223,19 +269,49 @@ sub post_acct_hook
 	# Check values against limits
 	if (defined($timeLimit)) {
 		if ($accountingUsage->{'TotalTimeUsage'} >= $timeLimit->{':='}->{'Value'}) {
-			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Usage exceeds ".$timeLimit->{':='}->{'Value'}.", returning [NACK]");
+			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Usage exceeds ".$timeLimit->{':='}->{'Value'}.", rejecting");
 			# Exceeding maximum, must be disconnected
 			return MOD_RES_NACK;
 		}
 	}
+	my $topupAmount = 0;
 	if (defined($trafficLimit)) {
-		if ($accountingUsage->{'TotalDataUsage'} >= $trafficLimit->{':='}->{'Value'}) {
-			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Usage exceeds ".$trafficLimit->{':='}->{'Value'}.", returning [NACK]");
-			# Exceeding maximum, must be disconnected
-			if ($packet->attr('Acct-Status-Type') eq "Alive") {
-				return MOD_RES_NACK;
+
+		# Get topups
+		if (defined($user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY})) {
+			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] '".$TRAFFIC_TOPUPS_KEY."' is defined");
+			# Operator: +=
+			if (defined($user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY}->[0])) {
+				# Is it a number?
+				if ($user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY}->[0] =~ /^[0-9]+$/) {
+					$topupAmount = $user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY}->[0];
+				} else {
+					$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$user->{'ConfigAttributes'}->{$TRAFFIC_TOPUPS_KEY}->[0].
+							"' is NOT a numeric value");
+				}
+			} else {
+				$server->log(LOG_NOTICE,"[MOD_FEATURE_CAPPING] '".$TRAFFIC_TOPUPS_KEY."' has no value");
 			}
 		}
+
+		# Set allowed traffic usage
+		my $alteredTrafficLimit;
+		if ($topupAmount > 0) {
+			$alteredTrafficLimit = $trafficLimit->{':='}->{'Value'} + $topupAmount;
+		} else {
+			$alteredTrafficLimit = $trafficLimit->{':='}->{'Value'};
+		}
+
+		# Bandwidth usage
+		$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Bandwidth => Usage total: ".$accountingUsage->{'TotalDataUsage'}.
+				"Mb (Cap: ".$trafficLimit->{':='}->{'Value'}."Mb, Topups: ".$topupAmount."Mb)");
+
+		# If bandwidth limit exceeded, cap user
+		if ($accountingUsage->{'TotalDataUsage'} >= $alteredTrafficLimit) {
+			$server->log(LOG_DEBUG,"[MOD_FEATURE_CAPPING] Usage of ".$accountingUsage->{'TotalDataUsage'}.
+					"Mb exceeds allowed limit of ".$alteredTrafficLimit."Mb. Capped.");
+			return MOD_RES_NACK;
+		}
 	}
 
 	return MOD_RES_ACK;
diff --git a/smradiusd.conf.testing b/smradiusd.conf.testing
index 496bbdc9..c534f525 100644
--- a/smradiusd.conf.testing
+++ b/smradiusd.conf.testing
@@ -110,6 +110,7 @@ EOT
 [system]
 plugins=<<EOT
 mod_config_sql
+mod_config_sql_topups
 EOT
 
 
-- 
GitLab