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 2842 additions and 360 deletions
#
# Valemount Networks Corporation specific radius attributes
# networks@valemount.com
#
# $Id: dictionary.valemount,v 1.1 2006/11/14 17:44:59 lem Exp $
#
VENDOR ValemountNetworks 16313
# Rates to give PPPoE customers, can be used in Authentication replies,
# in bits/s
ATTRIBUTE VNC-PPPoE-CBQ-RX 1 integer ValemountNetworks
ATTRIBUTE VNC-PPPoE-CBQ-TX 2 integer ValemountNetworks
# Fallback support for each direction. (1 / 0)
ATTRIBUTE VNC-PPPoE-CBQ-RX-Fallback 3 integer ValemountNetworks
ATTRIBUTE VNC-PPPoE-CBQ-TX-Fallback 4 integer ValemountNetworks
ATTRIBUTE VNC-Splash 10 integer ValemountNetworks
VALUE VNC-Splash Show 1
VALUE VNC-Splash No-Show 0
#
# dictionary.versanet Vendor specfic attributes for versanet
#
#
# VersaNet Communications, Inc.
# Http://www.versa-net.com
#
#
#Versanet add Vendor specific terminal cause in our radius group.
#You can follow this to set it in NAS box.
#
# >> gr radius
# >> sh
# >> set 34 23
# >> co
#
#This will let our unit transfer every detail terminal cause
#information to Redius server's accounting log file and
#save as "Vendor Specific=Terminate Cause".
#
# Version: @(#)dictionary.versanet 1.00 22-Jul-1999 support@versanetcomm.com
#
VENDOR Versanet 2180
ATTRIBUTE Versanet-Termination-Cause 1 integer Versanet
VALUE Versanet-Termination-Cause Normal-Hangup-No-Error-Occurred 0
VALUE Versanet-Termination-Cause Call-Waiting-Caused-Disconnect 3
VALUE Versanet-Termination-Cause Physical-Carrier-Loss 4
VALUE Versanet-Termination-Cause No-err-correction-at-other-end 5
VALUE Versanet-Termination-Cause No-resp-to-feature-negotiation 6
VALUE Versanet-Termination-Cause 1st-modem-async-only-2nd-sync 7
VALUE Versanet-Termination-Cause No-framing-technique-in-common 8
VALUE Versanet-Termination-Cause No-protocol-in-common 9
VALUE Versanet-Termination-Cause Bad-resp-to-feature-negotiation 10
VALUE Versanet-Termination-Cause No-sync-info-from-remote-modem 11
VALUE Versanet-Termination-Cause Normal-Hangup-by-Remote-modem 12
VALUE Versanet-Termination-Cause Retransmission-limit-reached 13
VALUE Versanet-Termination-Cause Protocol-violation-occurred 14
VALUE Versanet-Termination-Cause Lost-DTR 15
VALUE Versanet-Termination-Cause Received-GSTN-cleardown 16
VALUE Versanet-Termination-Cause Inactivity-timeout 17
VALUE Versanet-Termination-Cause Speed-not-supported 18
VALUE Versanet-Termination-Cause Long-space-disconnect 19
VALUE Versanet-Termination-Cause Key-abort-disconnect 20
VALUE Versanet-Termination-Cause Clears-previous-disc-reason 21
VALUE Versanet-Termination-Cause No-connection-established 22
VALUE Versanet-Termination-Cause Disconnect-after-three-retrains 23
# Copyright (C) 2007-2016, AllWorldIT
# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The FreeRADIUS Server Project
#
# The following dictionary file is a derivative work of the dictionary file
# from the FreeRADIUS Server Project, which is licensed GPLv2. This file
# therefore is also licensed under the terms of the GNU Public License,
# verison 2
#
# 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.
# #
# dictionary.wispr # dictionary.wispr
# #
......
##############################################################################
#
# XEDIA, AP series routers
# From Yard RADIUS, and Piotr Orlewicz, porlewicz@teleton.pl www.real-data.pl
#
# $Id: dictionary.xedia,v 1.1 2006/11/14 17:44:59 lem Exp $
#
#############################################################################
VENDOR Xedia 838
ATTRIBUTE Xedia-DNS-Server 1 ipaddr Xedia
ATTRIBUTE Xedia-NetBios-Server 2 ipaddr Xedia
ATTRIBUTE Xedia-Address-Pool 3 string Xedia
ATTRIBUTE Xedia-PPP-Echo-Interval 4 integer Xedia
ATTRIBUTE Xedia-SSH-Privileges 5 integer Xedia
ATTRIBUTE Xedia-Client-Access-Network 6 string Xedia
package Radius::Dictionary; # COPYRIGHT AND LICENSE
#
# Copyright 2007-2016, AllWorldIT
#
# Original work (c) Christopher Masto. Changes (c) 2002,2003 Luis
# E. Muñoz <luismunoz@cpan.org>.
#
# This software can be used under the same terms as perl itself. It also
# carries the same warranties.
#
# Please send bug reports (or patches) as well as feedback and
# suggestions to
#
# luismunoz@cpan.org
#
# When submitting bugs, it is very important that you include the
# relevant information for reproducing the bug. Packet dumps are most
# useful.
package smradius::Radius::Dictionary;
use strict; use strict;
use warnings; use warnings;
...@@ -31,9 +50,9 @@ sub new { ...@@ -31,9 +50,9 @@ sub new {
sub readfile { sub readfile {
my ($self, $filename) = @_; my ($self, $filename) = @_;
open(DICT, "< $filename") or return undef; open(my $DICT, "<", $filename) or return;
while (defined(my $l = <DICT>)) { while (defined(my $l = <$DICT>)) {
next if $l =~ /^\#/; next if $l =~ /^\#/;
next unless my @l = split /\s+/, $l; next unless my @l = split /\s+/, $l;
...@@ -166,7 +185,8 @@ sub readfile { ...@@ -166,7 +185,8 @@ sub readfile {
warn "Warning: Weird dictionary line: $l\n"; warn "Warning: Weird dictionary line: $l\n";
} }
} }
close DICT; close $DICT;
return 1;
} }
# Accessors for standard attributes # Accessors for standard attributes
......
package Radius::Packet; # COPYRIGHT AND LICENSE
#
# Copyright 2007-2016, AllWorldIT
#
# Original work (c) Christopher Masto. Changes (c) 2002,2003 Luis
# E. Muñoz <luismunoz@cpan.org>.
#
# This software can be used under the same terms as perl itself. It also
# carries the same warranties.
#
# Please send bug reports (or patches) as well as feedback and
# suggestions to
#
# luismunoz@cpan.org
#
# When submitting bugs, it is very important that you include the
# relevant information for reproducing the bug. Packet dumps are most
# useful.
package smradius::Radius::Packet;
use strict; use strict;
require Exporter; require Exporter;
...@@ -13,7 +32,7 @@ $VSA = 26; # Type assigned in RFC2138 to the ...@@ -13,7 +32,7 @@ $VSA = 26; # Type assigned in RFC2138 to the
# Vendor-Specific Attributes # Vendor-Specific Attributes
# Be sure our dictionaries are current # Be sure our dictionaries are current
use Radius::Dictionary 1.50; use smradius::Radius::Dictionary 1.50;
use Carp; use Carp;
use Socket; use Socket;
use Digest::MD5; use Digest::MD5;
......
There are notable differences between Packet.pm and the Net::Radius Packet.pm, most notable raw value support.
# Attribute handling functions # Attribute handling functions
# Copyright (C) 2007-2010, 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
...@@ -24,24 +24,36 @@ use strict; ...@@ -24,24 +24,36 @@ use strict;
use warnings; use warnings;
# Exporter stuff # Exporter stuff
require Exporter; use base qw(Exporter);
our (@ISA,@EXPORT); our (@EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw( @EXPORT = qw(
addAttribute addAttribute
checkAuthAttribute checkAuthAttribute
checkAcctAttribute
setReplyAttribute setReplyAttribute
setReplyVAttribute setReplyVAttribute
processConfigAttribute processConfigAttribute
getAttributeValue getAttributeValue
addAttributeConditionalVariable
processAttributeConditionals
); );
use AWITPT::Util;
# Check Math::Expression is installed
if (!eval {require Math::Expression; 1;}) {
print STDERR "You're missing Math::Expression, try 'apt-get install libmath-expression-perl'\n";
exit 1;
}
use smradius::logging; use smradius::logging;
use smradius::util; use smradius::util;
# Attributes we do not handle # Attributes we do not handle
my @attributeCheckIgnoreList = ( my @attributeCheckIgnoreList = (
'User-Password' 'User-Password'
...@@ -51,13 +63,34 @@ my @attributeReplyIgnoreList = ( ...@@ -51,13 +63,34 @@ my @attributeReplyIgnoreList = (
'SMRadius-Capping-Traffic-Limit', 'SMRadius-Capping-Traffic-Limit',
'SMRadius-Capping-Uptime-Limit', 'SMRadius-Capping-Uptime-Limit',
'SMRadius-Validity-ValidFrom', 'SMRadius-Validity-ValidFrom',
'SMRadius-Validity-ValidTo' 'SMRadius-Validity-ValidTo',
'SMRadius-Validity-ValidWindow',
'SMRadius-Username-Transform',
'SMRadius-Evaluate',
'SMRadius-Peer-Address',
'SMRadius-Disable-WebUITopup',
'SMRadius-AutoTopup-Traffic-Enabled',
'SMRadius-AutoTopup-Traffic-Amount',
'SMRadius-AutoTopup-Traffic-Limit',
'SMRadius-AutoTopup-Traffic-Notify',
'SMRadius-AutoTopup-Traffic-NotifyTemplate',
'SMRadius-AutoTopup-Traffic-Threshold',
'SMRadius-AutoTopup-Uptime-Enabled',
'SMRadius-AutoTopup-Uptime-Amount',
'SMRadius-AutoTopup-Uptime-Limit',
'SMRadius-AutoTopup-Uptime-Notify',
'SMRadius-AutoTopup-Uptime-NotifyTemplate',
'SMRadius-AutoTopup-Uptime-Threshold',
'SMRadius-Config-Filter-Reply-Attribute',
'SMRadius-Config-Filter-Reply-VAttribute',
'SMRadius-FUP-Period',
'SMRadius-FUP-Traffic-Threshold',
); );
my @attributeVReplyIgnoreList = ( my @attributeVReplyIgnoreList = (
); );
## @fn addAttribute($server,$nattributes,$vattributes,$attribute) ## @fn addAttribute($server,$user,$attribute)
# Function to add an attribute to $attributes # Function to add an attribute to $attributes
# #
# @param server Server instance # @param server Server instance
...@@ -66,13 +99,13 @@ my @attributeVReplyIgnoreList = ( ...@@ -66,13 +99,13 @@ my @attributeVReplyIgnoreList = (
# @param attribute Attribute to add, eg. Those from a database # @param attribute Attribute to add, eg. Those from a database
sub addAttribute sub addAttribute
{ {
my ($server,$nattributes,$vattributes,$attribute) = @_; my ($server,$user,$attribute) = @_;
# Check we have the name, operator AND value # Check we have the name, operator AND value
if (!defined($attribute->{'Name'}) || !defined($attribute->{'Operator'}) || !defined($attribute->{'Value'})) { if (!defined($attribute->{'Name'}) || !defined($attribute->{'Operator'}) || !defined($attribute->{'Value'})) {
$server->log(LOG_DEBUG,"[ATTRIBUTES] Problem adding attribute with name = ".niceUndef($attribute->{'Name'}). $server->log(LOG_DEBUG,"[ATTRIBUTES] Problem adding attribute with name = ".prettyUndef($attribute->{'Name'}).
", operator = ".niceUndef($attribute->{'Operator'}).", value = ".niceUndef($attribute->{'Value'})); ", operator = ".prettyUndef($attribute->{'Operator'}).", value = ".prettyUndef($attribute->{'Value'}));
return; return;
} }
...@@ -80,12 +113,12 @@ sub addAttribute ...@@ -80,12 +113,12 @@ sub addAttribute
$attribute->{'Name'} =~ s/\s*(\S+)\s*/$1/; $attribute->{'Name'} =~ s/\s*(\S+)\s*/$1/;
$attribute->{'Operator'} =~ s/\s*(\S+)\s*/$1/; $attribute->{'Operator'} =~ s/\s*(\S+)\s*/$1/;
# Grab attribue name, operator and value # Grab attribute name, operator and value
my $name = $attribute->{'Name'}; my $name = $attribute->{'Name'};
my $operator = $attribute->{'Operator'}; my $operator = $attribute->{'Operator'};
my $value = $attribute->{'Value'}; my $value = $attribute->{'Value'};
# Default attribute to add is normal # Default attribute to add is normal
my $attributes = $nattributes; my $attributes = $user->{'Attributes'};
# Check where we must add this attribute, maybe to the vendor attributes? # Check where we must add this attribute, maybe to the vendor attributes?
if ($name =~ /^\[(\d+):(\S+)\]$/) { if ($name =~ /^\[(\d+):(\S+)\]$/) {
...@@ -95,7 +128,7 @@ sub addAttribute ...@@ -95,7 +128,7 @@ sub addAttribute
# Reset attribute name # Reset attribute name
$attribute->{'Name'} = $name; $attribute->{'Name'} = $name;
# Set the attributes to use to the vendor # Set the attributes to use to the vendor
$attributes = $vattributes; $attributes = $user->{'VAttributes'};
} }
# Check if this is an array # Check if this is an array
...@@ -118,6 +151,9 @@ sub addAttribute ...@@ -118,6 +151,9 @@ sub addAttribute
} else { } else {
$attributes->{$name}->{$operator} = $attribute; $attributes->{$name}->{$operator} = $attribute;
} }
# Process the item incase its a config attribute
return processConfigAttribute($server,$user,$attribute);
} }
...@@ -130,7 +166,7 @@ sub addAttribute ...@@ -130,7 +166,7 @@ sub addAttribute
# @param attribute Attribute to check, eg. One of the ones from the database # @param attribute Attribute to check, eg. One of the ones from the database
sub checkAuthAttribute sub checkAuthAttribute
{ {
my ($server,$packetAttributes,$attribute) = @_; my ($server,$user,$packetAttributes,$attribute) = @_;
# Check ignore list # Check ignore list
...@@ -153,18 +189,21 @@ sub checkAuthAttribute ...@@ -153,18 +189,21 @@ sub checkAuthAttribute
# Get packet attribute value # Get packet attribute value
my $attrVal = $packetAttributes->{$attribute->{'Name'}}; my $attrVal = $packetAttributes->{$attribute->{'Name'}};
$server->log(LOG_DEBUG,"[ATTRIBUTES] Processing CHECK attribute value ".niceUndef($attrVal)." against: '". $server->log(LOG_DEBUG,"[ATTRIBUTES] Processing CHECK attribute value ".prettyUndef($attrVal)." against: '".
$attribute->{'Name'}."' ".$attribute->{'Operator'}." '".join("','",@attrValues)."'"); $attribute->{'Name'}."' ".$attribute->{'Operator'}." '".join("','",@attrValues)."'");
# Loop with all the test attribute values # Loop with all the test attribute values
foreach my $tattrVal (@attrValues) { foreach my $tattrVal (@attrValues) {
# Sanitize the operator
my ($operator) = ($attribute->{'Operator'} =~ /^(?:\|\|)?(.*)$/);
# Operator: == # Operator: ==
# #
# Use: Attribute == Value # Use: Attribute == Value
# As a check item, it matches if the named attribute is present in the request, # As a check item, it matches if the named attribute is present in the request,
# AND has the given value. # AND has the given value.
# #
if ($attribute->{'Operator'} eq '==' ) { if ($operator eq '==' ) {
# Check for correct value # Check for correct value
if (defined($attrVal) && $attrVal eq $tattrVal) { if (defined($attrVal) && $attrVal eq $tattrVal) {
$matched = 1; $matched = 1;
...@@ -178,7 +217,7 @@ sub checkAuthAttribute ...@@ -178,7 +217,7 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '>') { } elsif ($operator eq '>') {
if (defined($attrVal) && $attrVal =~ /^[0-9]+$/) { if (defined($attrVal) && $attrVal =~ /^[0-9]+$/) {
# Check for correct value # Check for correct value
if ($attrVal > $tattrVal) { if ($attrVal > $tattrVal) {
...@@ -196,7 +235,7 @@ sub checkAuthAttribute ...@@ -196,7 +235,7 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '<') { } elsif ($operator eq '<') {
# Check for correct value # Check for correct value
if (defined($attrVal) && $attrVal < $tattrVal) { if (defined($attrVal) && $attrVal < $tattrVal) {
$matched = 1; $matched = 1;
...@@ -210,7 +249,7 @@ sub checkAuthAttribute ...@@ -210,7 +249,7 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '<=') { } elsif ($operator eq '<=') {
# Check for correct value # Check for correct value
if (defined($attrVal) && $attrVal <= $tattrVal) { if (defined($attrVal) && $attrVal <= $tattrVal) {
$matched = 1; $matched = 1;
...@@ -224,7 +263,7 @@ sub checkAuthAttribute ...@@ -224,7 +263,7 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '>=') { } elsif ($operator eq '>=') {
# Check for correct value # Check for correct value
if (defined($attrVal) && $attrVal >= $tattrVal) { if (defined($attrVal) && $attrVal >= $tattrVal) {
$matched = 1; $matched = 1;
...@@ -238,7 +277,7 @@ sub checkAuthAttribute ...@@ -238,7 +277,7 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '=*') { } elsif ($operator eq '=*') {
# Check for matching value # Check for matching value
if (defined($attrVal)) { if (defined($attrVal)) {
$matched = 1; $matched = 1;
...@@ -252,9 +291,9 @@ sub checkAuthAttribute ...@@ -252,9 +291,9 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '!=') { } elsif ($operator eq '!=') {
# Check for correct value # Check for correct value
if (defined($attrVal) && $attrVal ne $tattrVal) { if (!defined($attrVal) || $attrVal ne $tattrVal) {
$matched = 1; $matched = 1;
} }
...@@ -266,7 +305,7 @@ sub checkAuthAttribute ...@@ -266,7 +305,7 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '!*') { } elsif ($operator eq '!*') {
# Skip if value not defined # Skip if value not defined
if (!defined($attrVal)) { if (!defined($attrVal)) {
$matched = 1; $matched = 1;
...@@ -280,7 +319,7 @@ sub checkAuthAttribute ...@@ -280,7 +319,7 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '=~') { } elsif ($operator eq '=~') {
# Check for correct value # Check for correct value
if (defined($attrVal) && $attrVal =~ /$tattrVal/) { if (defined($attrVal) && $attrVal =~ /$tattrVal/) {
$matched = 1; $matched = 1;
...@@ -295,7 +334,7 @@ sub checkAuthAttribute ...@@ -295,7 +334,7 @@ sub checkAuthAttribute
# #
# Not allowed as a reply item. # Not allowed as a reply item.
} elsif ($attribute->{'Operator'} eq '!~') { } elsif ($operator eq '!~') {
# Check for correct value # Check for correct value
if (defined($attrVal) && !($attrVal =~ /$tattrVal/)) { if (defined($attrVal) && !($attrVal =~ /$tattrVal/)) {
$matched = 1; $matched = 1;
...@@ -307,12 +346,114 @@ sub checkAuthAttribute ...@@ -307,12 +346,114 @@ sub checkAuthAttribute
# Always matches as a check item, and adds the current # Always matches as a check item, and adds the current
# attribute with value to the list of configuration items. # attribute with value to the list of configuration items.
# #
# As a reply item, it has an itendtical meaning, but the # As a reply item, it has an idendtical meaning, but the
# attribute is added to the reply items.
} elsif ($operator eq '+=') {
# Check if we're a conditional and process
if ($attribute->{'Name'} eq "SMRadius-Evaluate") {
$matched = processConditional($server,$user,$attribute,$tattrVal);
} else {
$matched = 1;
}
# FIXME
# Operator: :=
#
# Use: Attribute := Value
# Always matches as a check item, and replaces in the configuration items any attribute of the same name.
} elsif ($operator eq ':=') {
# FIXME - Add or replace config items
# FIXME - Add attribute to request
# Check if we're a conditional and process
if ($attribute->{'Name'} eq "SMRadius-Evaluate") {
$matched = processConditional($server,$user,$attribute,$tattrVal);
} else {
$matched = 1;
}
# Attributes that are not defined
} else {
# Ignore
$matched = 2;
last;
}
}
# Some debugging info
if ($matched == 1) {
$server->log(LOG_DEBUG,"[ATTRIBUTES] - Attribute '".$attribute->{'Name'}."' matched");
} elsif ($matched == 2) {
$server->log(LOG_DEBUG,"[ATTRIBUTES] - Attribute '".$attribute->{'Name'}."' ignored");
} else {
$server->log(LOG_DEBUG,"[ATTRIBUTES] - Attribute '".$attribute->{'Name'}."' not matched");
}
return $matched;
}
## @fn checkAcctAttribute($server,$packetAttributes,$attribute)
# Function to check an attribute in the accounting stage
#
# @param server Server instance
# @param packetAttributes Hashref of attributes provided, eg. Those from the packet
# @param attribute Attribute to check, eg. One of the ones from the database
sub checkAcctAttribute
{
my ($server,$user,$packetAttributes,$attribute) = @_;
# Check ignore list
foreach my $ignoredAttr (@attributeCheckIgnoreList) {
# 2 = IGNORE, so return IGNORE for all ignored items
return 2 if ($attribute->{'Name'} eq $ignoredAttr);
}
# Matched & ok?
my $matched = 0;
# Figure out our attr values
my @attrValues;
if (ref($attribute->{'Value'}) eq "ARRAY") {
@attrValues = @{$attribute->{'Value'}};
} else {
@attrValues = ( $attribute->{'Value'} );
}
# Get packet attribute value
my $attrVal = $packetAttributes->{$attribute->{'Name'}};
$server->log(LOG_DEBUG,"[ATTRIBUTES] Processing CHECK attribute value ".prettyUndef($attrVal)." against: '".
$attribute->{'Name'}."' ".$attribute->{'Operator'}." '".join("','",@attrValues)."'");
# Loop with all the test attribute values
foreach my $tattrVal (@attrValues) {
# Sanitize the operator
my ($operator) = ($attribute->{'Operator'} =~ /^(?:\|\|)?(.*)$/);
# Operator: +=
#
# Use: Attribute += Value
# Always matches as a check item, and adds the current
# attribute with value to the list of configuration items.
#
# As a reply item, it has an idendtical meaning, but the
# attribute is added to the reply items. # attribute is added to the reply items.
} elsif ($attribute->{'Operator'} eq '+=') { if ($operator eq '+=') {
# FIXME - Add to config items
$matched = 1; # Check if we're a conditional and process
if ($attribute->{'Name'} eq "SMRadius-Evaluate") {
$matched = processConditional($server,$user,$attribute,$tattrVal);
} else {
$matched = 1;
}
# FIXME # FIXME
# Operator: := # Operator: :=
...@@ -320,10 +461,16 @@ sub checkAuthAttribute ...@@ -320,10 +461,16 @@ sub checkAuthAttribute
# Use: Attribute := Value # Use: Attribute := Value
# Always matches as a check item, and replaces in the configuration items any attribute of the same name. # Always matches as a check item, and replaces in the configuration items any attribute of the same name.
} elsif ($attribute->{'Operator'} eq ':=') { } elsif ($operator eq ':=') {
# FIXME - Add or replace config items # FIXME - Add or replace config items
# FIXME - Add attribute to request # FIXME - Add attribute to request
$matched = 1;
# Check if we're a conditional and process
if ($attribute->{'Name'} eq "SMRadius-Evaluate") {
$matched = processConditional($server,$user,$attribute,$tattrVal);
} else {
$matched = 1;
}
# Attributes that are not defined # Attributes that are not defined
} else { } else {
...@@ -403,7 +550,7 @@ sub setReplyAttribute ...@@ -403,7 +550,7 @@ sub setReplyAttribute
# Always matches as a check item, and replaces in the configuration items any attribute of the same name. # Always matches as a check item, and replaces in the configuration items any attribute of the same name.
# If no attribute of that name appears in the request, then this attribute is added. # If no attribute of that name appears in the request, then this attribute is added.
# #
# As a reply item, it has an itendtical meaning, but for the reply items, instead of the request items. # As a reply item, it has an idendtical meaning, but for the reply items, instead of the request items.
} elsif ($attribute->{'Operator'} eq ':=') { } elsif ($attribute->{'Operator'} eq ':=') {
# Overwrite # Overwrite
...@@ -418,7 +565,7 @@ sub setReplyAttribute ...@@ -418,7 +565,7 @@ sub setReplyAttribute
# Always matches as a check item, and adds the current # Always matches as a check item, and adds the current
# attribute with value to the list of configuration items. # attribute with value to the list of configuration items.
# #
# As a reply item, it has an itendtical meaning, but the # As a reply item, it has an idendtical meaning, but the
# attribute is added to the reply items. # attribute is added to the reply items.
} elsif ($attribute->{'Operator'} eq '+=') { } elsif ($attribute->{'Operator'} eq '+=') {
...@@ -429,9 +576,8 @@ sub setReplyAttribute ...@@ -429,9 +576,8 @@ sub setReplyAttribute
# Attributes that are not defined # Attributes that are not defined
} else { } else {
# Ignore and b0rk out # Ignore invalid operator
$server->log(LOG_NOTICE,"[ATTRIBUTES] - Attribute '".$attribute->{'Name'}."' ignored, invalid operator?"); $server->log(LOG_NOTICE,"[ATTRIBUTES] - Attribute '".$attribute->{'Name'}."' ignored, invalid operator?");
last;
} }
return; return;
...@@ -468,7 +614,7 @@ sub setReplyVAttribute ...@@ -468,7 +614,7 @@ sub setReplyVAttribute
@attrValues = ( $attribute->{'Value'} ); @attrValues = ( $attribute->{'Value'} );
} }
$server->log(LOG_DEBUG,"[VATTRIBUTES] Processing REPLY attribute: '". $server->log(LOG_DEBUG,"[VATTRIBUTES] Processing REPLY vattribute: '".
$attribute->{'Name'}."' ".$attribute->{'Operator'}." '".join("','",@attrValues)."'"); $attribute->{'Name'}."' ".$attribute->{'Operator'}." '".join("','",@attrValues)."'");
...@@ -498,7 +644,7 @@ sub setReplyVAttribute ...@@ -498,7 +644,7 @@ sub setReplyVAttribute
# Always matches as a check item, and replaces in the configuration items any attribute of the same name. # Always matches as a check item, and replaces in the configuration items any attribute of the same name.
# If no attribute of that name appears in the request, then this attribute is added. # If no attribute of that name appears in the request, then this attribute is added.
# #
# As a reply item, it has an itendtical meaning, but for the reply items, instead of the request items. # As a reply item, it has an idendtical meaning, but for the reply items, instead of the request items.
} elsif ($attribute->{'Operator'} eq ':=') { } elsif ($attribute->{'Operator'} eq ':=') {
# Overwrite # Overwrite
...@@ -513,7 +659,7 @@ sub setReplyVAttribute ...@@ -513,7 +659,7 @@ sub setReplyVAttribute
# Always matches as a check item, and adds the current # Always matches as a check item, and adds the current
# attribute with value to the list of configuration items. # attribute with value to the list of configuration items.
# #
# As a reply item, it has an itendtical meaning, but the # As a reply item, it has an idendtical meaning, but the
# attribute is added to the reply items. # attribute is added to the reply items.
} elsif ($attribute->{'Operator'} eq '+=') { } elsif ($attribute->{'Operator'} eq '+=') {
...@@ -535,7 +681,7 @@ sub setReplyVAttribute ...@@ -535,7 +681,7 @@ sub setReplyVAttribute
## @fn processConfigAttribute($server,$packetAttributes,$attribute) ## @fn processConfigAttribute($server,$user,$attribute)
# Function to process a configuration attribute # Function to process a configuration attribute
# #
# @param server Server instance # @param server Server instance
...@@ -543,11 +689,13 @@ sub setReplyVAttribute ...@@ -543,11 +689,13 @@ sub setReplyVAttribute
# @param attribute Attribute to process, eg. One of the ones from the database # @param attribute Attribute to process, eg. One of the ones from the database
sub processConfigAttribute sub processConfigAttribute
{ {
my ($server,$configAttributes,$attribute) = @_; my ($server,$user,$attribute) = @_;
# Make things easier?
my $configAttributes = $user->{'ConfigAttributes'};
# Matched & ok? # Did we get processed?
my $matched = 0; my $processed = 0;
# Figure out our attr values # Figure out our attr values
my @attrValues; my @attrValues;
...@@ -557,21 +705,18 @@ sub processConfigAttribute ...@@ -557,21 +705,18 @@ sub processConfigAttribute
@attrValues = ( $attribute->{'Value'} ); @attrValues = ( $attribute->{'Value'} );
} }
$server->log(LOG_DEBUG,"[ATTRIBUTES] Processing CONFIG attribute: '".$attribute->{'Name'}."' ".
$attribute->{'Operator'}." '".join("','",@attrValues)."'");
# Operator: += # Operator: +=
# #
# Use: Attribute += Value # Use: Attribute += Value
# Always matches as a check item, and adds the current # Always matches as a check item, and adds the current
# attribute with value to the list of configuration items. # attribute with value to the list of configuration items.
# #
# As a reply item, it has an itendtical meaning, but the # As a reply item, it has an idendtical meaning, but the
# attribute is added to the reply items. # attribute is added to the reply items.
if ($attribute->{'Operator'} eq '+=') { if ($attribute->{'Operator'} eq '+=') {
$server->log(LOG_DEBUG,"[ATTRIBUTES] Operator '+=' triggered: Adding item to configuration items.");
push(@{$configAttributes->{$attribute->{'Name'}}},@attrValues); push(@{$configAttributes->{$attribute->{'Name'}}},@attrValues);
$processed = 1;
# Operator: := # Operator: :=
# #
...@@ -579,17 +724,21 @@ sub processConfigAttribute ...@@ -579,17 +724,21 @@ sub processConfigAttribute
# Always matches as a check item, and replaces in the configuration items any attribute of the same name. # Always matches as a check item, and replaces in the configuration items any attribute of the same name.
# If no attribute of that name appears in the request, then this attribute is added. # If no attribute of that name appears in the request, then this attribute is added.
# #
# As a reply item, it has an itendtical meaning, but for the reply items, instead of the request items. # As a reply item, it has an idendtical meaning, but for the reply items, instead of the request items.
} elsif ($attribute->{'Operator'} eq ':=') { } elsif ($attribute->{'Operator'} eq ':=') {
$server->log(LOG_DEBUG,"[ATTRIBUTES] Operator ':=' triggered: Adding or replacing item in configuration items.");
@{$configAttributes->{$attribute->{'Name'}}} = @attrValues; @{$configAttributes->{$attribute->{'Name'}}} = @attrValues;
$processed = 1;
# Operators that are not defined
} else {
# Ignore
$server->log(LOG_DEBUG,"[ATTRIBUTES] - Attribute '".$attribute->{'Name'}."' ignored");
} }
# If we got procsessed output some debug
if ($processed) {
$server->log(LOG_DEBUG,"[ATTRIBUTES] Processed CONFIG attribute: '".$attribute->{'Name'}."' ".
$attribute->{'Operator'}." '".join("','",@attrValues)."'");
}
return $processed;
} }
...@@ -615,6 +764,113 @@ sub getAttributeValue ...@@ -615,6 +764,113 @@ sub getAttributeValue
} }
## @fn addAttributeConditionalVariable($user,$name,$value)
# Function that adds a conditional variable
#
# @param user User hash
# @param name Variable name
# @param value Variable value
sub addAttributeConditionalVariable
{
my ($user,$name,$value) = @_;
$user->{'AttributeConditionalVariables'}->{$name} = [ $value ];
return;
}
## @fn processConditional($server,$user,$attribute,$attrVal)
# This function processes a attribute conditional
#
# @param server Server hash
# @param user User hash
# @param attribute Attribute hash to process
# @param attrVal Current value we need to process
sub processConditional
{
my ($server,$user,$attribute,$attrVal) = @_;
# Split off expression
# NK: This probably needs a bit of work
my ($condition,$onTrue,$onFalse) = ($attrVal =~ /^([^\?]*)(?:\?\s*((?:\S+)?[^:]*)(?:\:\s*(.*))?)?$/);
# If there is no condition we cannot really continue?
if (!defined($condition)) {
$server->log(LOG_WARN,"[ATTRIBUTES] Conditional '$attrVal' cannot be parsed");
return 1;
}
$server->log(LOG_DEBUG,"[ATTRIBUTES] Conditional parsed ".$attribute->{'Name'}." => if ($condition) then {".
( $onTrue ? $onTrue : "-undef-")."} else {".( $onFalse ? $onFalse : "-undef-")."}");
# Create the environment
my @error;
my $mathEnv = Math::Expression->new(
'PrintErrFunc' => sub { @error = @_ },
'VarHash' => $user->{'AttributeConditionalVariables'}
);
# Parse and create math tree
my $mathTree = $mathEnv->Parse($condition);
# Check for error
if (@error) {
my $errorStr = sprintf($error[0],$error[1]);
$server->log(LOG_WARN,"[ATTRIBUTES] Conditional '$condition' in '$attrVal' does not parse: $errorStr");
return 1;
}
# Evaluate tree
my $res = $mathEnv->Eval($mathTree);
if (!defined($res)) {
$server->log(LOG_WARN,"[ATTRIBUTES] Conditional '$condition' in '$attrVal' does not evaluate");
return 1;
}
# Check result
# If we have a onTrue or onFalse we will return "Matched = True"
# If we don't have an onTrue or onFalse we will return the result of the $condition
my $attribStr;
if ($res && defined($onTrue)) {
$attribStr = $onTrue;
$res = 1;
} elsif (!$res && defined($onFalse)) {
$attribStr = $onFalse;
$res = 1;
} elsif (defined($onTrue) || defined($onFalse)) {
$res = 1;
}
$server->log(LOG_DEBUG,"[ATTRIBUTES] - Evaluated to '$res' returning '".(defined($attribStr) ? $attribStr : "-undef-")."'");
# Loop with attributes:
# We only get here if $res is set to 1 above, if its only a conditional with no onTrue & onFalse
# Then attribStr will be unef
if ($res && defined($attribStr)) {
# Sanitize the output
$attribStr =~ s/^\s*//;
$attribStr =~ s/\s*$//;
foreach my $rawAttr (split(/;/,$attribStr)) {
# Split off attribute string: name = value
my ($attrName,$attrVal) = ($rawAttr =~ /^\s*([^=]+)=\s*(.*)/);
# Build attribute
my $attribute = {
'Name' => $attrName,
'Operator' => ':=',
'Value' => $attrVal
};
# Add attribute
addAttribute($server,$user,$attribute);
}
}
return $res;
}
1; 1;
# vim: ts=4 # vim: ts=4
# Radius client
# 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::client;
use strict;
use warnings;
use base qw(AWITPT::Object);
use Getopt::Long qw( GetOptionsFromArray );
use IO::Select;
use IO::Socket;
use smradius::version;
use smradius::Radius::Packet;
# Check Config::IniFiles is instaslled
if (!eval {require Config::IniFiles; 1;}) {
print STDERR "You're missing Config::IniFiles, try 'apt-get install libconfig-inifiles-perl'\n";
exit 1;
}
# Run the client
sub run
{
my ($self,@methodArgs) = @_;
# Instantiate if we're not already instantiated
$self = $self->new() if (!ref($self));
# The hash we're going to return
my $ret = { };
print(STDERR "SMRadClient v".VERSION." - Copyright (c) 2007-2019, AllWorldIT\n");
print(STDERR "\n");
# Set defaults
my $cfg;
$cfg->{'config_file'} = "/etc/smradiusd.conf";
# Grab runtime arguments
my @runArgs = @methodArgs ? @methodArgs : @ARGV;
# Parse command line params
my $cmdline;
%{$cmdline} = ();
if (!GetOptionsFromArray(
\@runArgs,
\%{$cmdline},
"config:s",
"raddb:s",
"listen:s",
"help",
)) {
print(STDERR "ERROR: Error parsing commandline arguments");
return 1;
}
# Check for some args
if ($cmdline->{'help'}) {
displayHelp();
return 0;
}
# Make sure we only have 2 additional args
if (@runArgs < 3) {
print(STDERR "ERROR: Invalid number of arguments\n");
displayHelp();
return 1;
}
if (!defined($cmdline->{'raddb'}) || $cmdline->{'raddb'} eq "") {
print(STDERR "ERROR: No raddb directory specified!\n");
displayHelp();
return 1;
}
# Get variables we need
my $server = shift(@runArgs);
my $type = shift(@runArgs);
$self->{'secret'} = shift(@runArgs);
# Validate type
if (!defined($type) || ( $type ne "acct" && $type ne "auth" && $type ne "disconnect")) {
print(STDERR "ERROR: Invalid packet type specified!\n");
displayHelp();
return 1;
}
print(STDERR "\n");
# Time to start loading the dictionary
print(STDERR "Loading dictionaries...");
my $raddb = smradius::Radius::Dictionary->new();
# Look for files in the dir
my $DIR;
if (!opendir($DIR, $cmdline->{'raddb'})) {
print(STDERR "ERROR: Cannot open '".$cmdline->{'raddb'}."': $!");
return 1;
}
my @raddb_files = readdir($DIR);
# And load the dictionary
foreach my $df (@raddb_files) {
my $df_fn = $cmdline->{'raddb'}."/$df";
# Load dictionary
if (!$raddb->readfile($df_fn)) {
print(STDERR "Failed to load dictionary '$df_fn': $!");
}
print(STDERR ".");
}
print(STDERR "\n");
# Decide what type of packet this is
my $port;
my $pkt_code;
if ($type eq "acct") {
$port = 1813;
$pkt_code = "Accounting-Request";
} elsif ($type eq "auth") {
$port = 1812;
$pkt_code = "Access-Request";
} elsif ($type eq "disconnect") {
$port = 1813;
$pkt_code = "Disconnect-Request";
}
print(STDERR "\nRequest:\n");
printf(STDERR " > Secret => '%s'\n",$self->{'secret'});
# Build packet
$self->{'packet'} = smradius::Radius::Packet->new($raddb);
$self->{'packet'}->set_code($pkt_code);
# Generate identifier
my $ident = int(rand(32768));
$self->{'packet'}->set_identifier($ident);
print(STDERR " > Identifier: $ident\n");
# Generate authenticator number
my $authen = int(rand(32768));
$self->{'packet'}->set_authenticator($authen);
print(STDERR " > Authenticator: $ident\n");
# Pull in attributes from STDIN if we're not being called as a function
if (!@runArgs) {
while (my $line = <STDIN>) {
$self->addAttributesFromString($line);
}
}
# Pull in attributes from commandline
while (my $line = shift(@runArgs)) {
$self->addAttributesFromString($line);
}
# Create UDP packet
my $udp_packet = $self->{'packet'}->pack();
# Create socket to send packet out on
my $sockTimeout = "10"; # 10 second timeout
my $sock = IO::Socket::INET->new(
PeerAddr => $server,
PeerPort => $port,
Type => SOCK_DGRAM,
Proto => 'udp',
Timeout => $sockTimeout,
);
if (!$sock) {
print(STDERR "ERROR: Failed to create socket\n");
return 1;
}
my $sock2;
# Check if we must listen on another IP/port
if (defined($cmdline->{'listen'}) && $cmdline->{'listen'} ne "") {
print(STDERR "Creating second socket\n");
# Check the details we were provided
my ($localAddr,$localPort) = split(/:/,$cmdline->{'listen'});
if (!defined($localPort)) {
print(STDERR "ERROR: The format for --listen is IP:Port\n");
return 1;
}
$sock2 = IO::Socket::INET->new(
LocalAddr => $localAddr,
LocalPort => $localPort,
Type => SOCK_DGRAM,
Proto => 'udp',
Timeout => $sockTimeout,
);
if (!$sock2) {
print(STDERR "ERROR: Failed to create second socket\n");
return 1;
}
}
# Check if we sent the packet...
if (!$sock->send($udp_packet)) {
print(STDERR "ERROR: Failed to send data on socket\n");
return 1;
}
# And time for the response
print(STDERR "\nResponse:\n");
# Once sent, we need to get a response back
my $rsock = IO::Select->new($sock);
if (!$rsock) {
print(STDERR "ERROR: Failed to select response data on socket\n");
return 1;
}
# Check if we can read a response after the select()
if (!$rsock->can_read($sockTimeout)) {
print(STDERR "ERROR: Failed to receive response data on socket\n");
return 1;
}
# Read packet
$sock->recv($udp_packet, 65536);
if (!$udp_packet) {
print(STDERR "ERROR: Receive response data failed on socket: $!\n");
return 1;
}
# Parse packet
my $pkt = smradius::Radius::Packet->new($raddb,$udp_packet);
print(STDERR " > Authenticated: ". (defined(auth_req_verify($udp_packet,$self->{'secret'},$authen)) ? "yes" : "no") ."\n");
print(STDERR $pkt->str_dump());
# Setup response
$ret->{'request'} = $self->hashedPacket($self->{'packet'});
$ret->{'response'} = $self->hashedPacket($pkt);
my $udp_packet2;
if (defined($sock2)) {
my $rsock2 = IO::Select->new($sock2);
if (!$rsock2) {
print(STDERR "ERROR: Failed to select response data on socket2\n");
return 1;
}
# Check if we can read a response after the select()
if (!$rsock2->can_read($sockTimeout)) {
print(STDERR "ERROR: Failed to receive response data on socket2\n");
return 1;
}
# Read packet
my $udp_packet2;
$sock2->recv($udp_packet2, 65536);
if (!$udp_packet2) {
print(STDERR "ERROR: Receive response data failed on socket2: $!\n");
return 1;
}
my $pkt2 = smradius::Radius::Packet->new($raddb,$udp_packet2);
print(STDERR $pkt2->str_dump());
# Save the packet we got
$ret->{'listen'}->{'response'} = $self->hashedPacket($pkt2);
}
# If we were called as a function, return hashed version of the response packet
if (@methodArgs) {
return $ret;
}
return 0;
}
# Return a hashed version of the packet
sub hashedPacket
{
my ($self,$pkt) = @_;
my $res = {};
$res->{'code'} = $pkt->code();
$res->{'identifier'} = $pkt->identifier();
foreach my $attrName (sort $pkt->attributes()) {
my $attrVal = $pkt->rawattr($attrName);
$res->{'attributes'}->{$attrName} = $attrVal;
}
foreach my $attrVendor ($pkt->vendors()) {
foreach my $attrName ($pkt->vsattributes($attrVendor)) {
$res->{'vattributes'}->{$attrVendor}->{$attrName} = $pkt->vsattr($attrVendor,$attrName);
}
}
return $res;
}
# Allow adding attribute from a string
sub addAttributesFromString
{
my ($self,$line) = @_;
# Remove EOL
chomp($line);
# Split on , and newline
my @rawAttributes = split(/[,\n]+/,$line);
foreach my $attr (@rawAttributes) {
# Pull off attribute name & value
my ($name,$value) = ($attr =~ /\s*(\S+)\s*=\s?(.+)/);
$self->addAttribute($name,$value);
}
return;
}
# Add attribute to packet
sub addAttribute
{
my ($self,$name,$value) = @_;
# Add to packet
print(STDERR " > Adding '$name' => '$value'\n");
if ($name eq "User-Password") {
$self->{'packet'}->set_password($value,$self->{'secret'});
} else {
$self->{'packet'}->set_attr($name,$value);
}
return;
}
# Display help
sub displayHelp {
print(STDERR<<EOF);
Usage: $0 [args] <server> <acct|auth|disconnect> <secret> [ATTR=VALUE,...]
--raddb=<DIR> Directory where the radius dictionary files are
EOF
return;
}
1;
# vim: ts=4
# SMRadius config information # SMRadius config information
# Copyright (C) 2007-2010, AllWorldIT # Copyright (C) 2007-2015, 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
...@@ -24,13 +24,14 @@ use strict; ...@@ -24,13 +24,14 @@ use strict;
use warnings; use warnings;
# Exporter stuff # Exporter stuff
require Exporter; use base qw(Exporter);
our (@ISA,@EXPORT); our @EXPORT = qw(
@ISA = qw(Exporter); );
@EXPORT = qw( our @EXPORT_OK = qw(
); );
use AWITPT::Util;
use smradius::logging; use smradius::logging;
...@@ -68,22 +69,55 @@ sub Init ...@@ -68,22 +69,55 @@ sub Init
} else { } else {
$server->{'smradius'}{'event_timezone'} = "GMT"; $server->{'smradius'}{'event_timezone'} = "GMT";
} }
# Should we use the packet timestamp? # Should we use the packet timestamp?
if (defined($config->{'server'}{'use_packet_timestamp'})) { if (defined($config->{'radius'}{'use_packet_timestamp'})) {
if ($config->{'server'}{'use_packet_timestamp'} =~ /^\s*(yes|true|1)\s*$/i) { if (defined(my $val = isBoolean($config->{'radius'}{'use_packet_timestamp'}))) {
$server->{'smradius'}{'use_packet_timestamp'} = 1; $server->{'smradius'}{'use_packet_timestamp'} = $val;
} elsif ($config->{'server'}{'use_packet_timestamp'} =~ /^\s*(no|false|0)\s*$/i) {
$server->{'smradius'}{'use_packet_timestamp'} = 0;
} else { } else {
$server->log(LOG_NOTICE,"smradius/config.pm: Value for 'use_packet_timestamp' is invalid"); $server->log(LOG_NOTICE,"smradius/config.pm: Value for 'use_packet_timestamp' is invalid");
} }
} else { } else {
$server->{'smradius'}{'use_packet_timestamp'} = 0; $server->{'smradius'}{'use_packet_timestamp'} = 0;
} }
# Should we use abuse prevention?
if (defined($config->{'radius'}{'use_abuse_prevention'})) {
if (defined(my $val = isBoolean($config->{'radius'}{'use_abuse_prevention'}))) {
$server->{'smradius'}{'use_abuse_prevention'} = $val;
} else {
$server->log(LOG_NOTICE,"smradius/config.pm: Value for 'use_abuse_prevention' is invalid");
}
} else {
$server->{'smradius'}{'use_abuse_prevention'} = 0;
}
if (defined($config->{'radius'}{'access_request_abuse_threshold'})) {
if ($config->{'radius'}{'access_request_abuse_threshold'} =~ /^[1-9][0-9]*$/i) {
$server->{'smradius'}{'access_request_abuse_threshold'} = $config->{'radius'}{'access_request_abuse_threshold'};
} else {
$server->log(LOG_NOTICE,"smradius/config.pm: Value for 'access_request_abuse_threshold' is invalid");
}
} else {
$server->{'smradius'}{'access_request_abuse_threshold'} = 10;
}
if (defined($config->{'radius'}{'accounting_request_abuse_threshold'})) {
if ($config->{'radius'}{'accounting_request_abuse_threshold'} =~ /^[1-9][0-9]*$/i) {
$server->{'smradius'}{'accounting_request_abuse_threshold'} = $config->{'radius'}{'accounting_request_abuse_threshold'};
} else {
$server->log(LOG_NOTICE,"smradius/config.pm: Value for 'accounting_request_abuse_threshold' is invalid");
}
} else {
$server->{'smradius'}{'accounting_request_abuse_threshold'} = 5;
}
$server->log(LOG_NOTICE,"smradius/config.pm: Using ". ( $server->{'smradius'}{'use_packet_timestamp'} ? 'packet' : 'server' ) ." timestamp"); $server->log(LOG_NOTICE,"smradius/config.pm: Using ". ( $server->{'smradius'}{'use_packet_timestamp'} ? 'packet' : 'server' ) ." timestamp");
$server->log(LOG_NOTICE,"smradius/config.pm: Using timezone '".$server->{'smradius'}{'event_timezone'}."'"); $server->log(LOG_NOTICE,"smradius/config.pm: Using timezone '".$server->{'smradius'}{'event_timezone'}."'");
$server->log(LOG_NOTICE,"smradius/config.pm: Abuse prevention ".( $server->{'smradius'}{'use_abuse_prevention'} ?
'active (access-threshold = '.$server->{'smradius'}{'access_request_abuse_threshold'}.
', accounting-threshold = '.$server->{'smradius'}{'accounting_request_abuse_threshold'}.')'
: 'inactive'));
return;
} }
......
# SMRadius Constants # SMRadius Constants
# Copyright (C) 2007-2010, AllWorldIT # Copyright (C) 2007-2015, 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
...@@ -20,22 +20,23 @@ ...@@ -20,22 +20,23 @@
## @class smradius::constants ## @class smradius::constants
# SMRadius constants package # SMRadius constants package
package smradius::constants; package smradius::constants;
use base qw(Exporter);
use strict; use strict;
use warnings;
# Exporter stuff
require Exporter;
our (@ISA,@EXPORT,@EXPORT_OK); our (@EXPORT,@EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT = qw( @EXPORT = qw(
RES_OK RES_OK
RES_ERROR RES_ERROR
MOD_RES_ACK MOD_RES_ACK
MOD_RES_NACK MOD_RES_NACK
MOD_RES_SKIP MOD_RES_SKIP
UINT_MAX GIGAWORD_VALUE
); );
@EXPORT_OK = (); @EXPORT_OK = ();
...@@ -47,8 +48,8 @@ use constant { ...@@ -47,8 +48,8 @@ use constant {
MOD_RES_SKIP => 0, MOD_RES_SKIP => 0,
MOD_RES_ACK => 1, MOD_RES_ACK => 1,
MOD_RES_NACK => 2, MOD_RES_NACK => 2,
UINT_MAX => 2**32 GIGAWORD_VALUE => 2**32,
}; };
......
#!/usr/bin/perl
# Radius daemon # Radius daemon
# Copyright (C) 2007-2010, 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
...@@ -17,38 +16,101 @@ ...@@ -17,38 +16,101 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
package smradius::daemon;
use strict; use strict;
use warnings; use warnings;
# Set library directory
use lib qw(
../ ./
smradius
awitpt/db
);
package radiusd; # Check if we have Net::Server::PreFork installed
if (!eval {require Net::Server::PreFork; 1;}) {
print STDERR "You're missing Net::Server::PreFork, try 'apt-get install libnet-server-perl'\n";
exit 1;
}
# Check Config::IniFiles is instaslled
if (!eval {require Config::IniFiles; 1;}) {
print STDERR "You're missing Config::IniFiles, try 'apt-get install libconfig-inifiles-perl'\n";
exit 1;
}
# Check DateTime is installed
if (!eval {require DateTime; 1;}) {
print STDERR "You're missing DateTime, try 'apt-get install libdatetime-perl'\n";
exit 1;
}
# Check Crypt::DES is installed
if (!eval {require Crypt::DES; 1;}) {
print STDERR "You're missing DateTime, try 'apt-get install libcrypt-des-perl'\n";
exit 1;
}
# Check Crypt::RC4 is installed
if (!eval {require Crypt::RC4; 1;}) {
print STDERR "You're missing Crypt::RC4, try 'apt-get install libcrypt-rc4-perl'\n";
exit 1;
}
# Check Digest::MD4 is installed
if (!eval {require Digest::MD4; 1;}) {
print STDERR "You're missing Digest::MD4, try 'apt-get install libdigest-md4-perl'\n";
exit 1;
}
# Check Digest::SHA is installed
if (!eval {require Digest::SHA; 1;}) {
print STDERR "You're missing Digest::SHA, try 'apt-get install libdigest-sha-perl'\n";
exit 1;
}
# Check Date::Parse is installed
if (!eval {require Date::Parse; 1;}) {
print STDERR "You're missing TimeDate, try 'apt-get install libtimedate-perl'\n";
exit 1;
}
# Check Cache::FastMmap is installed
if (!eval {require Cache::FastMmap; 1;}) {
print STDERR "You're missing DateTime, try 'apt-get install libcache-fastmmap-perl'\n";
exit 1;
} else {
eval {use AWITPT::Cache;};
}
# Check MIME::Lite is installed
if (!eval {require MIME::Lite; 1;}) {
print STDERR "You're missing MIME::Lite, try 'apt-get install libmime-lite-perl'\n";
exit 1;
}
## no critic (BuiltinFunctions::ProhibitStringyEval)
eval qq{
use base qw(Net::Server::PreFork);
};
## use critic
use base qw(Net::Server::PreFork); use Getopt::Long qw( GetOptionsFromArray );
use Config::IniFiles; use Socket;
use DateTime;
use Getopt::Long;
use Sys::Syslog; use Sys::Syslog;
use Time::HiRes qw( gettimeofday tv_interval ); use Time::HiRes qw( gettimeofday tv_interval );
use AWITPT::DB::DBLayer;
use AWITPT::Util qw( booleanize );
use smradius::Radius::Packet;
use smradius::version; use smradius::version;
use smradius::constants; use smradius::constants;
use smradius::daemon::request;
use smradius::logging; use smradius::logging;
use smradius::config; use smradius::config;
use awitpt::db::dbilayer;
use awitpt::cache;
use smradius::util; use smradius::util;
use smradius::attributes; use smradius::attributes;
use Radius::Packet;
use Socket;
...@@ -69,12 +131,13 @@ sub configure { ...@@ -69,12 +131,13 @@ sub configure {
# Set defaults # Set defaults
my $cfg; my $cfg;
$cfg->{'config_file'} = "/etc/smradiusd.conf"; $cfg->{'config_file'} = "/etc/smradiusd.conf";
$cfg->{'cache_file'} = '/var/run/smradius/cache';
$server->{'timeout'} = 120; $server->{'timeout'} = 120;
$server->{'background'} = "yes"; $server->{'background'} = "yes";
$server->{'pid_file'} = "/var/run/smradiusd.pid"; $server->{'pid_file'} = "/var/run/smradius/smradiusd.pid";
$server->{'log_level'} = 2; $server->{'log_level'} = 2;
$server->{'log_file'} = "/var/log/smradiusd.log"; $server->{'log_file'} = "/var/log/smradius/smradiusd.log";
$server->{'host'} = "*"; $server->{'host'} = "*";
$server->{'port'} = [ 1812, 1813 ]; $server->{'port'} = [ 1812, 1813 ];
...@@ -86,16 +149,23 @@ sub configure { ...@@ -86,16 +149,23 @@ sub configure {
$server->{'max_servers'} = 25; $server->{'max_servers'} = 25;
$server->{'max_requests'} = 1000; $server->{'max_requests'} = 1000;
# Work out runtime arguments
my @runArgs = @{$server->{'_run_args'}} ? @{$server->{'_run_args'}} : @ARGV;
# Parse command line params # Parse command line params
my $cmdline; my $cmdline;
%{$cmdline} = (); %{$cmdline} = ();
GetOptions( if (!GetOptionsFromArray(
\@runArgs,
\%{$cmdline}, \%{$cmdline},
"help", "help",
"config:s", "config:s",
"debug", "debug",
"fg", "fg",
) or die "Error parsing commandline arguments"; )) {
print(STDERR "ERROR: Error parsing commandline arguments");
return 1;
}
# Check for some args # Check for some args
if ($cmdline->{'help'}) { if ($cmdline->{'help'}) {
...@@ -178,9 +248,9 @@ sub configure { ...@@ -178,9 +248,9 @@ sub configure {
if (ref($config{'system'}{'modules'}) eq "ARRAY") { if (ref($config{'system'}{'modules'}) eq "ARRAY") {
foreach my $module (@{$config{'system'}{'modules'}}) { foreach my $module (@{$config{'system'}{'modules'}}) {
$module =~ s/\s+//g; $module =~ s/\s+//g;
# Skip comments # Skip comments
next if ($module =~ /^#/); next if ($module =~ /^#/);
$module = "system/$module"; $module = "system/$module";
push(@{$cfg->{'module_list'}},$module); push(@{$cfg->{'module_list'}},$module);
} }
} else { } else {
...@@ -199,9 +269,9 @@ sub configure { ...@@ -199,9 +269,9 @@ sub configure {
if (ref($config{'features'}{'modules'}) eq "ARRAY") { if (ref($config{'features'}{'modules'}) eq "ARRAY") {
foreach my $module (@{$config{'features'}{'modules'}}) { foreach my $module (@{$config{'features'}{'modules'}}) {
$module =~ s/\s+//g; $module =~ s/\s+//g;
# Skip comments # Skip comments
next if ($module =~ /^#/); next if ($module =~ /^#/);
$module = "features/$module"; $module = "features/$module";
push(@{$cfg->{'module_list'}},$module); push(@{$cfg->{'module_list'}},$module);
} }
} else { } else {
...@@ -220,9 +290,9 @@ sub configure { ...@@ -220,9 +290,9 @@ sub configure {
if (ref($config{'authentication'}{'mechanisms'}) eq "ARRAY") { if (ref($config{'authentication'}{'mechanisms'}) eq "ARRAY") {
foreach my $module (@{$config{'authentication'}{'mechanisms'}}) { foreach my $module (@{$config{'authentication'}{'mechanisms'}}) {
$module =~ s/\s+//g; $module =~ s/\s+//g;
# Skip comments # Skip comments
next if ($module =~ /^#/); next if ($module =~ /^#/);
$module = "authentication/$module"; $module = "authentication/$module";
push(@{$cfg->{'module_list'}},$module); push(@{$cfg->{'module_list'}},$module);
} }
} else { } else {
...@@ -238,9 +308,9 @@ sub configure { ...@@ -238,9 +308,9 @@ sub configure {
if (ref($config{'authentication'}{'users'}) eq "ARRAY") { if (ref($config{'authentication'}{'users'}) eq "ARRAY") {
foreach my $module (@{$config{'authentication'}{'users'}}) { foreach my $module (@{$config{'authentication'}{'users'}}) {
$module =~ s/\s+//g; $module =~ s/\s+//g;
# Skip comments # Skip comments
next if ($module =~ /^#/); next if ($module =~ /^#/);
$module = "userdb/$module"; $module = "userdb/$module";
push(@{$cfg->{'module_list'}},$module); push(@{$cfg->{'module_list'}},$module);
} }
} else { } else {
...@@ -259,9 +329,9 @@ sub configure { ...@@ -259,9 +329,9 @@ sub configure {
if (ref($config{'accounting'}{'modules'}) eq "ARRAY") { if (ref($config{'accounting'}{'modules'}) eq "ARRAY") {
foreach my $module (@{$config{'accounting'}{'modules'}}) { foreach my $module (@{$config{'accounting'}{'modules'}}) {
$module =~ s/\s+//g; $module =~ s/\s+//g;
# Skip comments # Skip comments
next if ($module =~ /^#/); next if ($module =~ /^#/);
$module = "accounting/$module"; $module = "accounting/$module";
push(@{$cfg->{'module_list'}},$module); push(@{$cfg->{'module_list'}},$module);
} }
} else { } else {
...@@ -281,8 +351,8 @@ sub configure { ...@@ -281,8 +351,8 @@ sub configure {
if (ref($config{'dictionary'}->{'load'}) eq "ARRAY") { if (ref($config{'dictionary'}->{'load'}) eq "ARRAY") {
foreach my $dict (@{$config{'dictionary'}->{'load'}}) { foreach my $dict (@{$config{'dictionary'}->{'load'}}) {
$dict =~ s/\s+//g; $dict =~ s/\s+//g;
# Skip comments # Skip comments
next if ($dict =~ /^#/); next if ($dict =~ /^#/);
push(@{$cfg->{'dictionary_list'}},$dict); push(@{$cfg->{'dictionary_list'}},$dict);
} }
} else { } else {
...@@ -294,10 +364,18 @@ sub configure { ...@@ -294,10 +364,18 @@ sub configure {
} }
} }
# Check if the user specified a cache_file in the config
if (defined($config{'server'}{'cache_file'})) {
$cfg->{'cache_file'} = $config{'server'}{'cache_file'};
}
# Save our config and stuff # Save our config and stuff
$self->{'config'} = $cfg; $self->{'config'} = $cfg;
$self->{'cmdline'} = $cmdline; $self->{'cmdline'} = $cmdline;
$self->{'inifile'} = \%config; $self->{'inifile'} = \%config;
return;
} }
...@@ -308,6 +386,8 @@ sub post_configure_hook { ...@@ -308,6 +386,8 @@ sub post_configure_hook {
my $config = $self->{'config'}; my $config = $self->{'config'};
$self->log(LOG_NOTICE,"[SMRADIUS] SMRadius - v$VERSION");
# Init config # Init config
$self->log(LOG_INFO,"[SMRADIUS] Initializing configuration..."); $self->log(LOG_INFO,"[SMRADIUS] Initializing configuration...");
smradius::config::Init($self); smradius::config::Init($self);
...@@ -315,13 +395,13 @@ sub post_configure_hook { ...@@ -315,13 +395,13 @@ sub post_configure_hook {
# Load dictionaries # Load dictionaries
$self->log(LOG_INFO,"[SMRADIUS] Initializing dictionaries..."); $self->log(LOG_INFO,"[SMRADIUS] Initializing dictionaries...");
my $dict = new Radius::Dictionary; my $dict = smradius::Radius::Dictionary->new();
foreach my $df (@{$config->{'dictionary_list'}}) { foreach my $df (@{$config->{'dictionary_list'}}) {
# Load dictionary # Load dictionary
if (!$dict->readfile($df)) { if (!$dict->readfile($df)) {
$self->log(LOG_WARN,"[SMRADIUS] Failed to load dictionary '$df': $!"); $self->log(LOG_WARN,"[SMRADIUS] Failed to load dictionary '$df': $!");
} }
$self->log(LOG_DEBUG,"[SMRADIUS] Loaded module '$df'."); $self->log(LOG_DEBUG,"[SMRADIUS] Loaded dictionary '$df'.");
} }
$self->log(LOG_INFO,"[SMRADIUS] Dictionaries initialized."); $self->log(LOG_INFO,"[SMRADIUS] Dictionaries initialized.");
# Store the dictionary # Store the dictionary
...@@ -335,10 +415,12 @@ sub post_configure_hook { ...@@ -335,10 +415,12 @@ sub post_configure_hook {
my ($mod_dir,$mod_name) = ($1,$2); my ($mod_dir,$mod_name) = ($1,$2);
# Load module # Load module
my $res = eval(" ## no critic (BuiltinFunctions::ProhibitStringyEval)
my $res = eval qq{
use smradius::modules::${mod_dir}::${mod_name}; use smradius::modules::${mod_dir}::${mod_name};
plugin_register(\$self,\"${mod_name}\",\$smradius::modules::${mod_dir}::${mod_name}::pluginInfo); plugin_register(\$self,\"${mod_name}\",\$smradius::modules::${mod_dir}::${mod_name}::pluginInfo);
"); };
## use critic
if ($@ || (defined($res) && $res != 0)) { if ($@ || (defined($res) && $res != 0)) {
$self->log(LOG_WARN,"[SMRADIUS] Error loading module $module ($@)"); $self->log(LOG_WARN,"[SMRADIUS] Error loading module $module ($@)");
} else { } else {
...@@ -349,9 +431,15 @@ sub post_configure_hook { ...@@ -349,9 +431,15 @@ sub post_configure_hook {
$self->log(LOG_INFO,"[SMRADIUS] Initializing system modules."); $self->log(LOG_INFO,"[SMRADIUS] Initializing system modules.");
# Init caching engine # Init caching engine
# awitpt::cache::Init($self); AWITPT::Cache::Init($self,{
'cache_file' => $self->{'config'}{'cache_file'},
'cache_file_user' => $self->{'server'}->{'user'},
'cache_file_group' => $self->{'server'}->{'group'}
});
$self->log(LOG_INFO,"[SMRADIUS] System modules initialized."); $self->log(LOG_INFO,"[SMRADIUS] System modules initialized.");
return;
} }
...@@ -380,6 +468,7 @@ sub plugin_register { ...@@ -380,6 +468,7 @@ sub plugin_register {
} }
# Initialize child # Initialize child
sub child_init_hook sub child_init_hook
{ {
...@@ -389,8 +478,8 @@ sub child_init_hook ...@@ -389,8 +478,8 @@ sub child_init_hook
$self->SUPER::child_init_hook(); $self->SUPER::child_init_hook();
$self->log(LOG_DEBUG,"[SMRADIUS] Starting up caching engine"); $self->log(LOG_INFO,"[SMRADIUS] Starting up caching engine");
awitpt::cache::connect($self); AWITPT::Cache::connect($self);
# Do we need database support? # Do we need database support?
if ($self->{'smradius'}->{'database'}->{'enabled'}) { if ($self->{'smradius'}->{'database'}->{'enabled'}) {
...@@ -399,21 +488,22 @@ sub child_init_hook ...@@ -399,21 +488,22 @@ sub child_init_hook
$self->{'client'}->{'dbh_status'} = time(); $self->{'client'}->{'dbh_status'} = time();
# Init core database support # Init core database support
$self->{'client'}->{'dbh'} = awitpt::db::dbilayer::Init($self,'smradius'); $self->{'client'}->{'dbh'} = AWITPT::DB::DBILayer::Init($self,'smradius');
if (defined($self->{'client'}->{'dbh'})) { if (defined($self->{'client'}->{'dbh'})) {
# Check if we succeeded # Check if we succeeded
if (!($self->{'client'}->{'dbh'}->connect())) { if (!($self->{'client'}->{'dbh'}->connect())) {
# If we succeeded, record OK # If we succeeded, record OK
$self->{'client'}->{'dbh_status'} = 0; $self->{'client'}->{'dbh_status'} = 0;
} else { } else {
$self->log(LOG_WARN,"[SMRADIUS] Failed to connect to database: ".$self->{'client'}->{'dbh'}->Error(). $self->log(LOG_WARN,"[SMRADIUS] Failed to connect to database: ".$self->{'client'}->{'dbh'}->error().
" ($$)"); " ($$)");
} }
} else { } else {
$self->log(LOG_WARN,"[SMRADIUS] Failed to Initialize: ".awitpt::db::dbilayer::internalError()." ($$)"); $self->log(LOG_WARN,"[SMRADIUS] Failed to Initialize: ".AWITPT::DB::DBILayer::internalError()." ($$)");
} }
} }
return;
} }
...@@ -425,11 +515,14 @@ sub child_finish_hook { ...@@ -425,11 +515,14 @@ sub child_finish_hook {
$self->SUPER::child_finish_hook(); $self->SUPER::child_finish_hook();
$self->log(LOG_DEBUG,"[SMRADIUS] Shutting down caching engine ($$)"); $self->log(LOG_INFO,"[SMRADIUS] Shutting down caching engine ($$)");
awitpt::cache::disconnect($self); AWITPT::Cache::disconnect($self);
return;
} }
# Process requests we get # Process requests we get
sub process_request { sub process_request {
my $self = shift; my $self = shift;
...@@ -439,20 +532,20 @@ sub process_request { ...@@ -439,20 +532,20 @@ sub process_request {
# Grab packet # Grab packet
my $udp_packet = $server->{'udp_data'}; my $rawPacket = $server->{'udp_data'};
# Check min size # Check min size
if (length($udp_packet) < 18) if (length($rawPacket) < 18)
{ {
$self->log(LOG_WARN, "[SMRADIUS] Packet too short - Ignoring"); $self->log(LOG_WARN, "[SMRADIUS] Packet too short - Ignoring");
return; return;
} }
# Profiling... # Very first timer ...
my $timer0 = [gettimeofday]; my $timer0 = [gettimeofday];
# Parse packet # Grab NOW()
my $pkt = new Radius::Packet($self->{'radius'}->{'dictionary'},$udp_packet); my $now = time();
# VERIFY SOURCE SERVER # VERIFY SOURCE SERVER
$self->log(LOG_DEBUG,"[SMRADIUS] Packet From = > ".$server->{'peeraddr'}); $self->log(LOG_DEBUG,"[SMRADIUS] Packet From = > ".$server->{'peeraddr'});
...@@ -480,7 +573,7 @@ sub process_request { ...@@ -480,7 +573,7 @@ sub process_request {
$timeout = 120; $timeout = 120;
} }
# Get time left # Get time left
my $timepassed = time() - $self->{'client'}->{'dbh_status'}; my $timepassed = $now - $self->{'client'}->{'dbh_status'};
# Then check... # Then check...
if ($timepassed >= $timeout) { if ($timepassed >= $timeout) {
$self->log(LOG_WARN,"[SMRADIUS] Client BYPASS timeout exceeded, reconnecting..."); $self->log(LOG_WARN,"[SMRADIUS] Client BYPASS timeout exceeded, reconnecting...");
...@@ -493,50 +586,85 @@ sub process_request { ...@@ -493,50 +586,85 @@ sub process_request {
} }
# Setup database handle # Setup database handle
awitpt::db::dblayer::setHandle($self->{'client'}->{'dbh'}); AWITPT::DB::DBLayer::setHandle($self->{'client'}->{'dbh'});
# Log line to use with logging my $request = smradius::daemon::request->new($self);
my $logLine = ""; my $logReason = "UNKNOWN"; if (!$request->setTimezone($self->{'smradius'}->{'event_timezone'})) {
foreach my $attr ($pkt->attributes) { $self->log(LOG_ERR,"[SMRADIUS] Setting event_timezone to '%s' failed",$self->{'smradius'}->{'event_timezone'});
$logLine .= " $attr: '".$pkt->rawattr($attr)."',"; return;
} }
chop($logLine);
$request->parsePacket($self->{'radius'}->{'dictionary'},$rawPacket);
# Main user hash with everything in
my $user; # Check if we need to override the packet timestamp, if we are not using the packet timestamp, set it to when we go the packet
$user->{'ConfigAttributes'} = {}; if (!booleanize($self->{'smradius'}->{'use_packet_timestamp'})) {
$user->{'ReplyAttributes'} = {}; $request->setTimestamp($now);
$user->{'ReplyVAttributes'} = {};
# Private data
# Where we going to get the timestamp to use from?, from the packet, if its there, or from ourselves
if (defined($pkt->rawattr('Event-Timestamp')) && $self->{'smradius'}->{'use_packet_timestamp'}) {
$user->{'_Internal'}->{'Timestamp-Unix'} = $pkt->rawattr('Event-Timestamp');
} else {
$user->{'_Internal'}->{'Timestamp-Unix'} = time();
} }
# VERY IMPORTANT!!!!!! # Username should always be defined?
# Timestamp AND Timestamp-Unix are in the CONVERTED timezone (event_timezone) if (!$request->hasUsername()) {
my $eventTimestamp = DateTime->from_epoch( $self->log(LOG_NOTICE,"[SMRADIUS] Packet with no username from ".$server->{'peeraddr'});
epoch => $user->{'_Internal'}->{'Timestamp-Unix'}, return;
time_zone => $self->{'smradius'}->{'event_timezone'} }
);
$user->{'_Internal'}->{'Timestamp'} = $eventTimestamp->strftime('%Y-%m-%d %H:%M:%S');
# Set username
$user->{'Username'} = $pkt->attr('User-Name'); # TODO/FIXME: WIP
my $pkt = $request->{'packet'};
my $user = $request->{'user'};
my $logReason = "UNKNOWN";
# First thing we do is to make sure the NAS behaves if we using abuse prevention
if ($self->{'smradius'}->{'use_abuse_prevention'} && defined($user->{'Username'})) {
my ($res,$val) = cacheGetKeyPair('FloodCheck',$server->{'peeraddr'}."/".$user->{'Username'}."/".$pkt->code);
if (defined($val)) {
my $timePeriod = $now - $val;
# Check if we're still within the abuse threshold
if ($pkt->code eq "Access-Request" && $timePeriod < $self->{'smradius'}->{'access_request_abuse_threshold'}) {
$self->log(LOG_NOTICE,"[SMRADIUS] ABUSE: NAS trying too fast. NAS = ".$server->{'peeraddr'}.", user = ".$user->{'Username'}.
", code = ".$pkt->code.", timeout = ".($now - $val));
# Tell the NAS we got its packet
my $resp = smradius::Radius::Packet->new($self->{'radius'}->{'dictionary'});
$resp->set_code('Access-Reject');
$resp->set_identifier($pkt->identifier);
$resp->set_authenticator($pkt->authenticator);
$server->{'client'}->send(
auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"))
);
return;
} elsif ($pkt->code eq "Accounting-Request" && $timePeriod < $self->{'smradius'}->{'accounting_request_abuse_threshold'}) {
$self->log(LOG_NOTICE,"[SMRADIUS] ABUSE: NAS trying too fast. NAS = ".$server->{'peeraddr'}.", user = ".$user->{'Username'}.
", code = ".$pkt->code.", timeout = ".($now - $val));
# Tell the NAS we got its packet
my $resp = smradius::Radius::Packet->new($self->{'radius'}->{'dictionary'});
$resp->set_code('Accounting-Response');
$resp->set_identifier($pkt->identifier);
$resp->set_authenticator($pkt->authenticator);
$server->{'client'}->send(
auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"))
);
return;
}
}
# We give the benefit of the doubt and let a query take 60s. We update to right stamp at end of this function
cacheStoreKeyPair('FloodCheck',$server->{'peeraddr'}."/".$user->{'Username'}."/".$pkt->code,$now + 60);
}
# #
# GRAB & PROCESS CONFIG # GRAB & PROCESS CONFIG
# #
my $configured = 1;
foreach my $module (@{$self->{'module_list'}}) { foreach my $module (@{$self->{'module_list'}}) {
# Try find config attribute # Try find config attribute
if ($module->{'Config_get'}) { if ($module->{'Config_get'}) {
# Get result from config module # Get result from config module
$self->log(LOG_INFO,"[SMRADIUS] CONFIG: Trying plugin '".$module->{'Name'}."' for incoming connection"); $self->log(LOG_DEBUG,"[SMRADIUS] CONFIG: Trying plugin '".$module->{'Name'}."' for incoming connection");
my $res = $module->{'Config_get'}($self,$user,$pkt); my $res = $module->{'Config_get'}($self,$user,$pkt);
# Check result # Check result
...@@ -550,42 +678,74 @@ sub process_request { ...@@ -550,42 +678,74 @@ sub process_request {
# Check if we got a positive result back # Check if we got a positive result back
} elsif ($res == MOD_RES_ACK) { } elsif ($res == MOD_RES_ACK) {
$self->log(LOG_INFO,"[SMRADIUS] CONFIG: Configuration retrieved from '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] CONFIG: Configuration retrieved from '".$module->{'Name'}."'");
$logReason = "Config Retrieved"; $logReason = "Config Retrieved";
# Check if we got a negative result back # Check if we got a negative result back
} elsif ($res == MOD_RES_NACK) { } elsif ($res == MOD_RES_NACK) {
$self->log(LOG_INFO,"[SMRADIUS] CONFIG: Configuration rejection when using '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] CONFIG: Configuration rejection when using '".$module->{'Name'}."'");
$logReason = "Config Rejected"; $logReason = "Config Rejected";
goto CHECK_RESULT;
# FIXME/TODO NK WIP
return;
# $configured = 0;
# last;
} }
} }
} }
# FIXME - need secret
# FIXME - need acl list
# #
# START PROCESSING # USERNAME TRANSFORM
#
# If we have a config attribute to transform username, use it
if (defined($user->{'ConfigAttributes'}->{'SMRadius-Username-Transform'})) {
$self->log(LOG_DEBUG,"[SMRADIUS] Attribute 'SMRadius-Username-Transform' exists, transforming username.");
# NK: Not ready for prime time yet
# # Get clients(NAS) username transform pattern
# my $transform = shift(@{$user->{'ConfigAttributes'}->{'SMRadius-Username-Transform'}});
# if ($transform =~ /^(@\S+)=(@\S+)$/i) {
#
# # Set old and new, prevents warnings
# my ($old,$new) = ($1,$2);
#
# # Use client username transform on temp username
# my $tempUsername = $user->{'Username'};
# $tempUsername =~ s/$old/$new/;
#
# # Override username
# $user->{'Username'} = $tempUsername;
# } else {
# $self->log(LOG_DEBUG,"[SMRADIUS] No string replacement possible on pattern '".
# $transform."', using username '".$user->{'Username'}."'");
# }
}
#
# FIND USER
# #
# UserDB module if we using/need it # Get the user timer
my $userdb; my $timer1 = [gettimeofday];
# FIXME - need secret
# FIXME - need acl list
# Common stuff for multiple codes.... # Common stuff for multiple codes....
if ($pkt->code eq "Accounting-Request" || $pkt->code eq "Access-Request") { if ($pkt->code eq "Accounting-Request" || $pkt->code eq "Access-Request") {
#
# FIND USER
#
# Loop with modules to try find user # Loop with modules to try find user
foreach my $module (@{$self->{'module_list'}}) { foreach my $module (@{$self->{'module_list'}}) {
# Try find user # Try find user
if ($module->{'User_find'}) { if ($module->{'User_find'}) {
$self->log(LOG_INFO,"[SMRADIUS] FIND: Trying plugin '".$module->{'Name'}."' for username '". $self->log(LOG_DEBUG,"[SMRADIUS] FIND: Trying plugin '".$module->{'Name'}."' for username '".
$user->{'Username'}."'"); $user->{'Username'}."'");
my ($res,$userdb_data) = $module->{'User_find'}($self,$user,$pkt); my ($res,$userDB_Data) = $module->{'User_find'}($self,$user,$pkt);
# Check result # Check result
if (!defined($res)) { if (!defined($res)) {
...@@ -598,14 +758,16 @@ sub process_request { ...@@ -598,14 +758,16 @@ sub process_request {
# Check if we got a positive result back # Check if we got a positive result back
} elsif ($res == MOD_RES_ACK) { } elsif ($res == MOD_RES_ACK) {
$self->log(LOG_INFO,"[SMRADIUS] FIND: Username found with '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] FIND: Username found with '".$module->{'Name'}."'");
$userdb = $module; $user->{'_UserDB'} = $module;
$user->{'_UserDB_Data'} = $userdb_data; $user->{'_UserDB_Data'} = $userDB_Data;
# The user ID is supposed to be global unique, on the same level as the username
$user->{'ID'} = $user->{'_UserDB_Data'}->{'ID'};
last; last;
# Or a negative result # Or a negative result
} elsif ($res == MOD_RES_NACK) { } elsif ($res == MOD_RES_NACK) {
$self->log(LOG_INFO,"[SMRADIUS] FIND: Username not found with '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] FIND: Username not found with '".$module->{'Name'}."'");
$logReason = "User Not Found"; $logReason = "User Not Found";
last; last;
...@@ -615,24 +777,41 @@ sub process_request { ...@@ -615,24 +777,41 @@ sub process_request {
} }
#
# PROCESS PACKET
#
# Process the packet timer
my $timer2 = [gettimeofday];
# Is this an accounting request # Is this an accounting request
if ($pkt->code eq "Accounting-Request") { if ($pkt->code eq "Accounting-Request") {
$self->log(LOG_DEBUG,"[SMRADIUS] Accounting Request Packet"); $self->log(LOG_DEBUG,"[SMRADIUS] Accounting Request Packet");
# Add onto logline
$request->addLogLine(". REQUEST => ");
foreach my $attrName ($pkt->attributes) {
$request->addLogLine(
"%s: '%s'",
$attrName,
$pkt->rawattr($attrName)
);
}
# #
# GET USER # GET USER
# #
# Get user data # Get user data
if (defined($userdb) && defined($userdb->{'User_get'})) { if (defined($user->{'_UserDB'}) && defined($user->{'_UserDB'}->{'User_get'})) {
my $res = $userdb->{'User_get'}($self,$user,$pkt); my $res = $user->{'_UserDB'}->{'User_get'}($self,$user,$pkt);
# Check result # Check result
if (defined($res) && ref($res) eq "HASH") { if ($res) {
# We're only after the attributes here $self->log(LOG_WARN,"[SMRADIUS] GET: Error returned from '".$user->{'_UserDB'}->{'Name'}.
$user->{'Attributes'} = $res->{'Attributes'}; "' for username '".$user->{'Username'}."'");
$user->{'VAttributes'} = $res->{'VAttributes'};
} }
} }
...@@ -640,7 +819,7 @@ sub process_request { ...@@ -640,7 +819,7 @@ sub process_request {
foreach my $module (@{$self->{'module_list'}}) { foreach my $module (@{$self->{'module_list'}}) {
# Try find user # Try find user
if ($module->{'Accounting_log'}) { if ($module->{'Accounting_log'}) {
$self->log(LOG_INFO,"[SMRADIUS] ACCT: Trying plugin '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] ACCT: Trying plugin '".$module->{'Name'}."'");
my $res = $module->{'Accounting_log'}($self,$user,$pkt); my $res = $module->{'Accounting_log'}($self,$user,$pkt);
# Check result # Check result
...@@ -654,32 +833,25 @@ sub process_request { ...@@ -654,32 +833,25 @@ sub process_request {
# Check if we got a positive result back # Check if we got a positive result back
} elsif ($res == MOD_RES_ACK) { } elsif ($res == MOD_RES_ACK) {
$self->log(LOG_INFO,"[SMRADIUS] ACCT: Accounting logged using '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] ACCT: Accounting logged using '".$module->{'Name'}."'");
$logReason = "Accounting Logged"; $logReason = "Accounting Logged";
# Check if we got a negative result back # Check if we got a negative result back
} elsif ($res == MOD_RES_NACK) { } elsif ($res == MOD_RES_NACK) {
$self->log(LOG_INFO,"[SMRADIUS] ACCT: Accounting NOT LOGGED using '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] ACCT: Accounting NOT LOGGED using '".$module->{'Name'}."'");
$logReason = "Accounting NOT Logged"; $logReason = "Accounting NOT Logged";
} }
} }
} }
# Tell the NAS we got its packet
my $resp = Radius::Packet->new($self->{'radius'}->{'dictionary'});
$resp->set_code('Accounting-Response');
$resp->set_identifier($pkt->identifier);
$resp->set_authenticator($pkt->authenticator);
$udp_packet = auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"));
$server->{'client'}->send($udp_packet);
# Are we going to POD the user? # Are we going to POD the user?
my $PODUser = 0; my $PODUser = 0;
# Loop with modules that have post-authentication hooks # Loop with modules that have post-accounting hooks
foreach my $module (@{$self->{'module_list'}}) { foreach my $module (@{$self->{'module_list'}}) {
# Try authenticate # Try authenticate
if ($module->{'Feature_Post-Accounting_hook'}) { if ($module->{'Feature_Post-Accounting_hook'}) {
$self->log(LOG_INFO,"[SMRADIUS] POST-ACCT: Trying plugin '".$module->{'Name'}."' for '". $self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Trying plugin '".$module->{'Name'}."' for '".
$user->{'Username'}."'"); $user->{'Username'}."'");
my $res = $module->{'Feature_Post-Accounting_hook'}($self,$user,$pkt); my $res = $module->{'Feature_Post-Accounting_hook'}($self,$user,$pkt);
...@@ -694,112 +866,181 @@ sub process_request { ...@@ -694,112 +866,181 @@ sub process_request {
# Check if we got a positive result back # Check if we got a positive result back
} elsif ($res == MOD_RES_ACK) { } elsif ($res == MOD_RES_ACK) {
$self->log(LOG_INFO,"[SMRADIUS] POST-ACCT: Passed post accounting hook by '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Passed post accounting hook by '".$module->{'Name'}."'");
$logReason = "Post Accounting Success"; $logReason = "Post Accounting Success";
# Or a negative result # Or a negative result
} elsif ($res == MOD_RES_NACK) { } elsif ($res == MOD_RES_NACK) {
$self->log(LOG_INFO,"[SMRADIUS] POST-ACCT: Failed post accounting hook by '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Failed post accounting hook by '".$module->{'Name'}."'");
$logReason = "Failed Post Accounting"; $logReason = "Failed Post Accounting";
$PODUser = 1; $PODUser = 1;
} }
} }
} }
# Check if we must POD the user # Tell the NAS we got its packet
if ($PODUser) { my $resp = smradius::Radius::Packet->new($self->{'radius'}->{'dictionary'});
$self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Trying to disconnect user..."); $resp->set_code('Accounting-Response');
$resp->set_identifier($pkt->identifier);
$resp->set_authenticator($pkt->authenticator);
$server->{'client'}->send(
auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"))
);
# CoA and POD only apply to accounting updates...
if ($pkt->rawattr('Acct-Status-Type') eq "3") {
# Build a list of our attributes in the packet
my $acctAttributes;
foreach my $attr ($pkt->attributes) {
$acctAttributes->{$attr} = $pkt->rawattr($attr);
}
# Loop with attributes we got from the user
foreach my $attrName (keys %{$user->{'Attributes'}}) {
# Loop with operators
foreach my $attrOp (keys %{$user->{'Attributes'}->{$attrName}}) {
# Grab attribute
my $attr = $user->{'Attributes'}->{$attrName}->{$attrOp};
# Check attribute against accounting attributes
my $res = checkAcctAttribute($self,$user,$acctAttributes,$attr);
# We don't care if it fails
}
}
# The coaReq may be either POD or CoA
my $coaReq = smradius::Radius::Packet->new($self->{'radius'}->{'dictionary'});
my $resp = Radius::Packet->new($self->{'radius'}->{'dictionary'}); # Set packet identifier
$coaReq->set_identifier( $$ & 0xff );
$resp->set_code('Disconnect-Request'); # Check if we must POD the user, if so we set the code to disconnect
my $id = $$ & 0xff; if ($PODUser) {
$resp->set_identifier( $id ); $self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Trying to disconnect user...");
$coaReq->set_code('Disconnect-Request');
} else {
# If this is *not* a POD, we need to process reply attributes
$self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Sending CoA...");
$coaReq->set_code('CoA-Request');
# Process the reply attributes
$self->_processReplyAttributes($request,$user,$coaReq);
}
$resp->set_attr('User-Name',$pkt->attr('User-Name')); # NAS identification
$resp->set_attr('Framed-IP-Address',$pkt->attr('Framed-IP-Address')); $coaReq->set_attr('NAS-IP-Address',$pkt->attr('NAS-IP-Address'));
$resp->set_attr('NAS-IP-Address',$pkt->attr('NAS-IP-Address')); # Session identification
$coaReq->set_attr('User-Name',$pkt->attr('User-Name'));
$coaReq->set_attr('NAS-Port',$pkt->attr('NAS-Port'));
$coaReq->set_attr('Acct-Session-Id',$pkt->attr('Acct-Session-Id'));
# Add onto logline # Add onto logline
$logLine .= ". REPLY => "; $request->addLogLine(". REPLY => ");
foreach my $attr ($resp->attributes) { foreach my $attrName ($coaReq->attributes) {
$logLine .= " $attr: '".$resp->rawattr($attr)."',"; $request->addLogLine(
"%s: '%s'",
$attrName,
$coaReq->rawattr($attrName)
);
} }
chop($logLine);
# Grab packet # Generate coaReq packet
$udp_packet = auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret")); my $coaReq_packet = auth_resp($coaReq->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"));
# Check for POD Servers and send disconnect # Array CoA servers to contact
my @coaServers;
# Check for old POD server attribute
if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-PODServer'})) { if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-PODServer'})) {
$self->log(LOG_DEBUG,"[SMRADIUS] SMRadius-Config-PODServer is defined"); $self->log(LOG_DEBUG,"[SMRADIUS] SMRadius-Config-PODServer is defined");
@coaServers = @{$user->{'ConfigAttributes'}->{'SMRadius-Config-PODServer'}};
}
# Check address format # Check for new CoA server attribute
foreach my $podServerAttribute (@{$user->{'ConfigAttributes'}->{'SMRadius-Config-PODServer'}}) { if (defined($user->{'ConfigAttributes'}->{'SMRadius-Config-CoAServer'})) {
# Check for valid IP $self->log(LOG_DEBUG,"[SMRADIUS] SMRadius-Config-CoAServer is defined");
if ($podServerAttribute =~ /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/) { @coaServers = @{$user->{'ConfigAttributes'}->{'SMRadius-Config-CoAServer'}};
my $podServer = $1; }
# If we have a port, use it, otherwise use default 1700 # If we didn't get provided a CoA server, use the peer address
my $podServerPort; if (!@coaServers) {
if ($podServerAttribute =~ /:([0-9]+)$/) { push(@coaServers,$server->{'peeraddr'});
$podServerPort = $1; }
} else {
$podServerPort = 1700; # Check address format
} foreach my $coaServer (@coaServers) {
# Remove IPv6 portion for now...
$self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Trying PODServer => IP: '".$podServer."' Port: '".$podServerPort."'"); $coaServer =~ s/^::ffff://;
# Check for valid IP
# Create socket to send packet out on my ($coaServerIP,$coaServerPort) = ($coaServer =~ /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})(?::([0-9]+))?/);
my $podServerTimeout = "10"; # 10 second timeout
my $podSock = new IO::Socket::INET( if (!defined($coaServerIP)) {
PeerAddr => $podServer, $self->log(LOG_NOTICE,"[SMRADIUS] POST-ACCT: CoAServer '$coaServer' looks incorrect");
PeerPort => $podServerPort, next;
Type => SOCK_DGRAM, }
Proto => 'udp',
TimeOut => $podServerTimeout, # Set default CoA server port
); $coaServerPort //= 1700;
if (!$podSock) { $self->log(LOG_DEBUG,"[SMRADIUS] POST-ACCT: Trying CoAServer => IP: '".$coaServer."' Port: '".$coaServerPort."'");
$self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to create socket to send POD on");
next; # Create socket to send packet out on
} my $coaServerTimeout = "2"; # 2 second timeout
my $coaSock = IO::Socket::INET->new(
# Check if we sent the packet... PeerAddr => $coaServerIP,
if (!$podSock->send($udp_packet)) { PeerPort => $coaServerPort,
$self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to send data on socket"); Type => SOCK_DGRAM,
next; Proto => 'udp',
} TimeOut => $coaServerTimeout,
);
# Once sent, we need to get a response back
my $sh = new IO::Select($podSock); if (!$coaSock) {
if (!$sh) { $self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to create socket to send CoA on: $!");
$self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to select data on socket"); next;
next; }
}
# Check if we sent the packet...
if (!$sh->can_read($podServerTimeout)) { if (!$coaSock->send($coaReq_packet)) {
$self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to receive data on socket"); $self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to send data on CoA socket: $!");
next; next;
} }
my $data; # Once sent, we need to get a response back
$podSock->recv($data, 65536); my $select = IO::Select->new($coaSock);
if (!$data) { if (!$select) {
$self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Receive data failed"); $self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to select data on socket: $!");
$logReason = "POD Failure"; next;
} else { }
$logReason = "User POD";
} if (!$select->can_read($coaServerTimeout)) {
$self->log(LOG_ERR,"[SMRADIUS] POST-ACCT: Failed to receive data on socket: $!");
#my @stuff = unpack('C C n a16 a*', $data); next;
#$self->log(LOG_DEBUG,"STUFF: ".Dumper(\@stuff)); }
} else {
$self->log(LOG_DEBUG,"[SMRADIUS] Invalid POD Server value: '".$podServerAttribute."'"); # Grab CoA response
} my $coaRes_packet;
$coaSock->recv($coaRes_packet, 65536);
if (!$coaRes_packet) {
$self->log(LOG_INFO,"[SMRADIUS] POST-ACCT: No data received in response to our request to '$coaServerIP:$coaServerPort': $!");
$request->addLogLine(". No response to CoA/POD");
next;
}
# Parse the radius packet
my $coaRes = smradius::Radius::Packet->new($self->{'radius'}->{'dictionary'},$coaRes_packet);
# Check status
if ($coaRes->code eq "CoA-ACK") {
$request->addLogLine(". CoA Success");
last;
} elsif ($coaRes->code eq "CoA-NACK") {
$request->addLogLine(". CoA Fail");
} elsif ($coaRes->code eq "Disconnect-ACK") {
$request->addLogLine(". POD Success");
last;
} elsif ($coaRes->code eq "Disconnect-NACK") {
$request->addLogLine(". POD Fail");
} else {
$request->addLogLine(". Invalid CoA/POD response");
} }
} else {
$self->log(LOG_DEBUG,"[SMRADIUS] SMRadius-Config-PODServer is not defined");
} }
} }
...@@ -817,8 +1058,8 @@ sub process_request { ...@@ -817,8 +1058,8 @@ sub process_request {
# If no user is found, bork out ... # If no user is found, bork out ...
if (!defined($userdb)) { if (!defined($user->{'_UserDB'})) {
$self->log(LOG_INFO,"[SMRADIUS] FIND: No plugin found for username '".$user->{'Username'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] FIND: No plugin found for username '".$user->{'Username'}."'");
goto CHECK_RESULT; goto CHECK_RESULT;
} }
...@@ -827,20 +1068,17 @@ sub process_request { ...@@ -827,20 +1068,17 @@ sub process_request {
# #
# Get user data # Get user data
if ($userdb->{'User_get'}) { if ($user->{'_UserDB'}->{'User_get'}) {
my $res = $userdb->{'User_get'}($self,$user,$pkt); my $res = $user->{'_UserDB'}->{'User_get'}($self,$user,$pkt);
# Check result # Check result
if (!defined($res) || ref($res) ne "HASH") { if ($res) {
$self->log(LOG_WARN,"[SMRADIUS] GET: No data returned from '".$userdb->{'Name'}. $self->log(LOG_WARN,"[SMRADIUS] GET: Error returned from '".$user->{'_UserDB'}->{'Name'}.
"' for username '".$user->{'Username'}."'"); "' for username '".$user->{'Username'}."'");
goto CHECK_RESULT; goto CHECK_RESULT;
} }
# Setup user dataw
$user->{'Attributes'} = $res->{'Attributes'};
$user->{'VAttributes'} = $res->{'VAttributes'};
} else { } else {
$self->log(LOG_INFO,"[SMRADIUS] GET: No 'User_get' function available for module '".$userdb->{'Name'}."'"); $self->log(LOG_ERR,"[SMRADIUS] GET: No 'User_get' function available for module '".$user->{'_UserDB'}->{'Name'}."'");
goto CHECK_RESULT; goto CHECK_RESULT;
} }
...@@ -853,7 +1091,7 @@ sub process_request { ...@@ -853,7 +1091,7 @@ sub process_request {
foreach my $module (@{$self->{'module_list'}}) { foreach my $module (@{$self->{'module_list'}}) {
# Try authenticate # Try authenticate
if ($module->{'Authentication_try'}) { if ($module->{'Authentication_try'}) {
$self->log(LOG_INFO,"[SMRADIUS] AUTH: Trying plugin '".$module->{'Name'}."' for '".$user->{'Username'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] AUTH: Trying plugin '".$module->{'Name'}."' for '".$user->{'Username'}."'");
my $res = $module->{'Authentication_try'}($self,$user,$pkt); my $res = $module->{'Authentication_try'}($self,$user,$pkt);
# Check result # Check result
...@@ -866,7 +1104,7 @@ sub process_request { ...@@ -866,7 +1104,7 @@ sub process_request {
# Check if we got a positive result back # Check if we got a positive result back
} elsif ($res == MOD_RES_ACK) { } elsif ($res == MOD_RES_ACK) {
$self->log(LOG_INFO,"[SMRADIUS] AUTH: Authenticated by '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] AUTH: Authenticated by '".$module->{'Name'}."'");
$logReason = "User Authenticated"; $logReason = "User Authenticated";
$mechanism = $module; $mechanism = $module;
$authenticated = 1; $authenticated = 1;
...@@ -874,7 +1112,7 @@ sub process_request { ...@@ -874,7 +1112,7 @@ sub process_request {
# Or a negative result # Or a negative result
} elsif ($res == MOD_RES_NACK) { } elsif ($res == MOD_RES_NACK) {
$self->log(LOG_INFO,"[SMRADIUS] AUTH: Failed authentication by '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] AUTH: Failed authentication by '".$module->{'Name'}."'");
$logReason = "User NOT Authenticated"; $logReason = "User NOT Authenticated";
$mechanism = $module; $mechanism = $module;
last; last;
...@@ -888,7 +1126,7 @@ sub process_request { ...@@ -888,7 +1126,7 @@ sub process_request {
foreach my $module (@{$self->{'module_list'}}) { foreach my $module (@{$self->{'module_list'}}) {
# Try authenticate # Try authenticate
if ($module->{'Feature_Post-Authentication_hook'}) { if ($module->{'Feature_Post-Authentication_hook'}) {
$self->log(LOG_INFO,"[SMRADIUS] POST-AUTH: Trying plugin '".$module->{'Name'}. $self->log(LOG_DEBUG,"[SMRADIUS] POST-AUTH: Trying plugin '".$module->{'Name'}.
"' for '".$user->{'Username'}."'"); "' for '".$user->{'Username'}."'");
my $res = $module->{'Feature_Post-Authentication_hook'}($self,$user,$pkt); my $res = $module->{'Feature_Post-Authentication_hook'}($self,$user,$pkt);
...@@ -903,13 +1141,13 @@ sub process_request { ...@@ -903,13 +1141,13 @@ sub process_request {
# Check if we got a positive result back # Check if we got a positive result back
} elsif ($res == MOD_RES_ACK) { } elsif ($res == MOD_RES_ACK) {
$self->log(LOG_INFO,"[SMRADIUS] POST-AUTH: Passed authenticated by '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] POST-AUTH: Passed authenticated by '".$module->{'Name'}."'");
$logReason = "Post Authentication Success"; $logReason = "Post Authentication Success";
# Or a negative result # Or a negative result
} elsif ($res == MOD_RES_NACK) { } elsif ($res == MOD_RES_NACK) {
$self->log(LOG_INFO,"[SMRADIUS] POST-AUTH: Failed authentication by '".$module->{'Name'}."'"); $self->log(LOG_DEBUG,"[SMRADIUS] POST-AUTH: Failed authentication by '".$module->{'Name'}."'");
$logReason = "Post Authentication Failure"; $logReason = "Post Authentication Failure";
$authenticated = 0; $authenticated = 0;
# Do we want to run the other modules ?? # Do we want to run the other modules ??
last; last;
...@@ -927,6 +1165,8 @@ sub process_request { ...@@ -927,6 +1165,8 @@ sub process_request {
foreach my $attr ($pkt->attributes) { foreach my $attr ($pkt->attributes) {
$authAttributes->{$attr} = $pkt->rawattr($attr); $authAttributes->{$attr} = $pkt->rawattr($attr);
} }
# Peer address
$authAttributes->{'SMRadius-Peer-Address'} = $server->{'peeraddr'};
# Loop with attributes we got from the user # Loop with attributes we got from the user
foreach my $attrName (keys %{$user->{'Attributes'}}) { foreach my $attrName (keys %{$user->{'Attributes'}}) {
# Loop with operators # Loop with operators
...@@ -934,7 +1174,7 @@ sub process_request { ...@@ -934,7 +1174,7 @@ sub process_request {
# Grab attribute # Grab attribute
my $attr = $user->{'Attributes'}->{$attrName}->{$attrOp}; my $attr = $user->{'Attributes'}->{$attrName}->{$attrOp};
# Check attribute against authorization attributes # Check attribute against authorization attributes
my $res = checkAuthAttribute($self,$authAttributes,$attr); my $res = checkAuthAttribute($self,$user,$authAttributes,$attr);
if ($res == 0) { if ($res == 0) {
$authorized = 0; $authorized = 0;
last; last;
...@@ -947,62 +1187,19 @@ sub process_request { ...@@ -947,62 +1187,19 @@ sub process_request {
# Check if we authenticated or not # Check if we authenticated or not
if ($authenticated && $authorized) { if ($authenticated && $authorized) {
$self->log(LOG_DEBUG,"[SMRADIUS] Authenticated and authorized"); $self->log(LOG_DEBUG,"[SMRADIUS] Authenticated and authorized");
$logReason = "User Authorized"; $logReason = "User Authorized";
my $resp = Radius::Packet->new($self->{'radius'}->{'dictionary'}); my $resp = smradius::Radius::Packet->new($self->{'radius'}->{'dictionary'});
$resp->set_code('Access-Accept'); $resp->set_code('Access-Accept');
$resp->set_identifier($pkt->identifier); $resp->set_identifier($pkt->identifier);
$resp->set_authenticator($pkt->authenticator); $resp->set_authenticator($pkt->authenticator);
# Loop with attributes we got from the getReplyAttributes function, its a hash of arrays which are the values # Process the reply attributes
my %replyAttributes = %{ $user->{'ReplyAttributes'} }; $self->_processReplyAttributes($request,$user,$resp);
foreach my $attrName (keys %{$user->{'Attributes'}}) {
# Loop with operators
foreach my $attrOp (keys %{$user->{'Attributes'}->{$attrName}}) {
# Grab attribute
my $attr = $user->{'Attributes'}->{$attrName}->{$attrOp};
# Add this to the reply attribute?
setReplyAttribute($self,\%replyAttributes,$attr);
}
}
# Loop with reply attributes
foreach my $attrName (keys %replyAttributes) {
# Loop with values
foreach my $value (@{$replyAttributes{$attrName}}) {
# Add each value
$resp->set_attr($attrName,$value);
}
}
# Loop with vendor reply attributes
my %replyVAttributes = %{ $user->{'ReplyVAttributes'} };
foreach my $attrName (keys %{$user->{'VAttributes'}}) {
# Loop with operators
foreach my $attrOp (keys %{$user->{'VAttributes'}->{$attrName}}) {
# Grab attribute
my $attr = $user->{'VAttributes'}->{$attrName}->{$attrOp};
# Add this to the reply attribute?
setReplyVAttribute($self,\%replyVAttributes,$attr);
}
}
foreach my $vendor (keys %replyVAttributes) {
# Loop with operators
foreach my $attrName (keys %{$replyVAttributes{$vendor}}) {
# Add each value
foreach my $value (@{$replyVAttributes{$vendor}->{$attrName}}) {
$resp->set_vsattr($vendor,$attrName,$value);
}
}
}
# Add onto logline
$logLine .= ". REPLY => ";
foreach my $attr ($resp->attributes) {
$logLine .= " $attr: '".$resp->rawattr($attr)."',";
}
chop($logLine);
$udp_packet = auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret")); $server->{'client'}->send(
$server->{'client'}->send($udp_packet); auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"))
);
} }
...@@ -1010,14 +1207,15 @@ CHECK_RESULT: ...@@ -1010,14 +1207,15 @@ CHECK_RESULT:
# Check if found and authenticated # Check if found and authenticated
if (!$authenticated || !$authorized) { if (!$authenticated || !$authorized) {
$self->log(LOG_DEBUG,"[SMRADIUS] Authentication or authorization failure"); $self->log(LOG_DEBUG,"[SMRADIUS] Authentication or authorization failure");
$logReason = "User NOT Authenticated or Authorized"; $logReason = "User NOT Authenticated or Authorized";
my $resp = Radius::Packet->new($self->{'radius'}->{'dictionary'}); my $resp = smradius::Radius::Packet->new($self->{'radius'}->{'dictionary'});
$resp->set_code('Access-Reject'); $resp->set_code('Access-Reject');
$resp->set_identifier($pkt->identifier); $resp->set_identifier($pkt->identifier);
$resp->set_authenticator($pkt->authenticator); $resp->set_authenticator($pkt->authenticator);
$udp_packet = auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret")); $server->{'client'}->send(
$server->{'client'}->send($udp_packet); auth_resp($resp->pack, getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"))
);
} }
# We don't know how to handle this # We don't know how to handle this
...@@ -1026,14 +1224,35 @@ CHECK_RESULT: ...@@ -1026,14 +1224,35 @@ CHECK_RESULT:
} }
# END # END
my $timer1 = [gettimeofday]; my $timer9 = [gettimeofday];
my $timediff = tv_interval($timer0,$timer1); my $timediff1 = tv_interval($timer0,$timer1);
$self->log(LOG_NOTICE,"[SMRADIUS] Result: $logReason (${timediff}s) => $logLine"); my $timediff2 = tv_interval($timer1,$timer2);
my $timediff3 = tv_interval($timer2,$timer9);
my $timediff = tv_interval($timer0,$timer9);
# FIXME/TODO NK WIP
my $logLine = join(' ',@{$request->{'logLine'}});
my @logLineArgs = @{$request->{'logLineParams'}};
# How should we output this ...
if ($server->{'log_level'} > LOG_NOTICE) {
$self->log(LOG_NOTICE,"[SMRADIUS] Result: $logReason (%.3fs + %.3fs + %.3fs = %.3fs) => $logLine",
$timediff1,$timediff2,$timediff3,$timediff,@logLineArgs);
} else {
$self->log(LOG_NOTICE,"[SMRADIUS] Result: $logReason => $logLine",@logLineArgs);
}
# If we using abuse prevention record the time we ending off
if ($self->{'smradius'}->{'use_abuse_prevention'} && defined($user->{'Username'})) {
cacheStoreKeyPair('FloodCheck',$server->{'peeraddr'}."/".$user->{'Username'}."/".$pkt->code,time());
}
return; return;
} }
# Initialize child # Initialize child
sub server_exit sub server_exit
{ {
...@@ -1042,20 +1261,23 @@ sub server_exit ...@@ -1042,20 +1261,23 @@ sub server_exit
$self->log(LOG_DEBUG,"Destroying system modules."); $self->log(LOG_DEBUG,"Destroying system modules.");
# Destroy cache # Destroy cache
# cbp::cache::Destroy($self); AWITPT::Cache::Destroy($self);
$self->log(LOG_DEBUG,"System modules destroyed."); $self->log(LOG_DEBUG,"System modules destroyed.");
# Parent exit # Parent exit
$self->SUPER::server_exit(); $self->SUPER::server_exit();
return;
} }
# Slightly better logging # Slightly better logging
sub log sub log ## no critic (Subroutines::ProhibitBuiltinHomonyms)
{ {
my ($self,$level,$msg,@args) = @_; my ($self,$level,$msg,@args) = @_;
# Check log level and set text # Check log level and set text
my $logtxt = "UNKNOWN"; my $logtxt = "UNKNOWN";
if ($level == LOG_DEBUG) { if ($level == LOG_DEBUG) {
...@@ -1077,14 +1299,19 @@ sub log ...@@ -1077,14 +1299,19 @@ sub log
$msg = "[CORE] $logtxt: $msg"; $msg = "[CORE] $logtxt: $msg";
} }
$self->SUPER::log($level,"[".$self->log_time." - $$] $msg",@args); # If we have args, this is more than likely a format string & args
if (@args > 0) {
$msg = sprintf($msg,@args);
}
return $self->SUPER::log($level,"[".$self->log_time." - $$] $msg");
} }
# Display help # Display help
sub displayHelp { sub displayHelp {
print(STDERR "SMRadius v".VERSION." - Copyright (c) 2007-2009, AllWorldIT\n"); print(STDERR "SMRadius v$VERSION - Copyright (c) 2007-2016, AllWorldIT\n");
print(STDERR<<EOF); print(STDERR<<EOF);
...@@ -1094,13 +1321,133 @@ Usage: $0 [args] ...@@ -1094,13 +1321,133 @@ Usage: $0 [args]
--fg Don't go into background --fg Don't go into background
EOF EOF
return;
} }
#
# Internal functions
#
# Process reply attributes
sub _processReplyAttributes
{
my ($self,$request,$user,$pkt) = @_;
# Add attributes we got from plugins and process attributes attached to the user
my %replyAttributes = %{ $user->{'ReplyAttributes'} };
foreach my $attrName (keys %{$user->{'Attributes'}}) {
# Loop with operators
foreach my $attrOp (keys %{$user->{'Attributes'}->{$attrName}}) {
# Grab attribute
my $attr = $user->{'Attributes'}->{$attrName}->{$attrOp};
# Add this to the reply attribute?
setReplyAttribute($self,\%replyAttributes,$attr);
}
}
# Add vendor attributes we got from plugins and process attributes attached to the user
my %replyVAttributes = %{ $user->{'ReplyVAttributes'} };
foreach my $attrName (keys %{$user->{'VAttributes'}}) {
# Loop with operators
foreach my $attrOp (keys %{$user->{'VAttributes'}->{$attrName}}) {
# Grab attribute
my $attr = $user->{'VAttributes'}->{$attrName}->{$attrOp};
# Add this to the reply attribute?
setReplyVAttribute($self,\%replyVAttributes,$attr);
}
}
# Loop with reply attributes add them to our response, or output them to log if they were excluded
$request->addLogLine("RFILTER => ");
foreach my $attrName (keys %replyAttributes) {
# Loop with values
foreach my $value (@{$replyAttributes{$attrName}}) {
# Check for filter matches
my $excluded = 0;
foreach my $item (@{$user->{'ConfigAttributes'}->{'SMRadius-Config-Filter-Reply-Attribute'}}) {
my @attrList = split(/[;,]/,$item);
foreach my $aItem (@attrList) {
$excluded = 1 if (lc($attrName) eq lc($aItem));
}
}
# If we must be filtered, just exclude it then
if (!$excluded) {
# Add each value
$pkt->set_attr($attrName,$value);
} else {
$request->addLogLine("%s ",$attrName);
}
}
}
# Loop with reply vendor attributes add them to our response, or output them to log if they were excluded
$request->addLogLine(". RVFILTER => ");
# Process reply vattributes already added
foreach my $vendor (keys %replyVAttributes) {
# Loop with operators
foreach my $attrName (keys %{$replyVAttributes{$vendor}}) {
# Add each value
foreach my $value (@{$replyVAttributes{$vendor}->{$attrName}}) {
# Check for filter matches
my $excluded = 0;
foreach my $item (@{$user->{'ConfigAttributes'}->{'SMRadius-Config-Filter-Reply-VAttribute'}}) {
my @attrList = split(/[;,]/,$item);
foreach my $aItem (@attrList) {
$excluded = 1 if (lc($attrName) eq lc($aItem));
}
}
# If we must be filtered, just exclude it then
if (!$excluded) {
# This attribute is not excluded, so its ok
$pkt->set_vsattr($vendor,$attrName,$value);
} else {
$request->addLogLine("%s ",$attrName);
}
}
}
}
# Add attributes onto logline
$request->addLogLine(". REPLY => ");
foreach my $attrName ($pkt->attributes) {
$request->addLogLine(
"%s: '%s",
$attrName,
$pkt->rawattr($attrName)
);
}
# Add vattributes onto logline
$request->addLogLine(". VREPLY => ");
# Loop with vendors
foreach my $vendor ($pkt->vendors()) {
# Loop with attributes
foreach my $attrName ($pkt->vsattributes($vendor)) {
# Grab the value
my @attrRawVal = ( $pkt->vsattr($vendor,$attrName) );
my $attrVal = $attrRawVal[0][0];
# Sanatize it a bit
if ($attrVal =~ /[[:cntrl:]]/) {
$attrVal = "-nonprint-";
} else {
$attrVal = "'$attrVal'";
}
$request->addLogLine(
"%s/%s: %s",
$vendor,
$attrName,
$attrVal
);
}
}
return $self;
};
__PACKAGE__->run;
1; 1;
# vim: ts=4 # vim: ts=4
# Radius daemon request processing
# 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 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::daemon::request;
use strict;
use warnings;
use base qw{AWITPT::Object};
use DateTime;
use DateTime::TimeZone;
use Try::Tiny;
use smradius::Radius::Packet;
# Parse radius packet
sub parsePacket
{
my ($self,$dictionary,$rawPacket) = @_;
# Parse the radius packet
$self->{'packet'} = smradius::Radius::Packet->new($dictionary,$rawPacket);
# Loop with packet attribute names and add to our log line
$self->addLogLine("PACKET => ");
foreach my $attrName (sort $self->{'packet'}->attributes()) {
# Make the value a bit more pretty to print
my $attrVal;
if ($attrName eq "User-Password") {
$attrVal = "-encrypted-";
} else {
$attrVal = $self->{'packet'}->rawattr($attrName);
}
# Add it onto the log line...
$self->addLogLine(
"%s: '%s'",
$attrName,
$attrVal,
);
}
# Set the username
$self->{'user'}->{'Username'} = $self->{'packet'}->attr('User-Name');
# Set the packet timestamp in unix time
if (my $timestamp = $self->{'packet'}->rawattr('Event-Timestamp')) {
$self->setTimestamp($timestamp);
} else {
$self->setTimestamp(time());
}
return $self;
}
# Set internal timestamp
sub setTimestamp
{
my ($self,$timestamp) = @_;
# Set timestamp
$self->{'user'}->{'_Internal'}->{'Timestamp-Unix'} = $timestamp;
# Grab real event timestamp in local time uzing the time zone
my $eventTimestamp = DateTime->from_epoch(
epoch => $self->{'user'}->{'_Internal'}->{'Timestamp-Unix'},
time_zone => $self->{'timezone'},
);
# Set the timestamp (not in unix)
$self->{'user'}->{'_Internal'}->{'Timestamp'} = $eventTimestamp->strftime('%Y-%m-%d %H:%M:%S');
return $self;
}
# Set internal time zone
sub setTimezone
{
my ($self,$timezone) = @_;
my $timezone_obj;
try {
$timezone_obj = DateTime::TimeZone->new('name' => $timezone);
};
# Retrun if we don't have a value, this means we failed
return if (!defined($timezone_obj));
$self->{'timezone'} = $timezone_obj;
return $self;
}
# Add something onto the log line in printf() style...
sub addLogLine
{
my ($self,$template,@params) = @_;
# Add on template and params
push(@{$self->{'logLine'}},$template);
push(@{$self->{'logLineParams'}},@params);
return $self;
}
# Return if the Username attribute of the user is defined
sub hasUsername
{
my $self = shift;
return defined($self->{'user'}->{'Username'});
}
#
# INTERNAL METHODS
#
# This method is called from the new-> method during instantiation
sub _init
{
my ($self) = @_;
# Initialize log line
$self->{'logLine'} = [ ];
$self->{'logLineParams'} = [ ];
$self->{'timezone'} = "UTC";
# Initialize user
$self->{'user'} = {
'Username' => undef,
'ConfigAttributes' => { },
'Attributes' => { },
'VAttributes' => { },
'ReplyAttributes' => { },
'ReplyVAttributes' => { },
'AttributeConditionalVariables' => { },
};
return $self;
}
1;
# vim: ts=4
# Logging constants # Logging constants
# Copyright (C) 2007-2010, AllWorldIT # Copyright (C) 2007-2015, 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
......
# SQL accounting database # SQL accounting database
# Copyright (C) 2007-2010, 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.
...@@ -22,7 +22,9 @@ use warnings; ...@@ -22,7 +22,9 @@ use warnings;
# Modules we need # Modules we need
use smradius::constants; use smradius::constants;
use awitpt::db::dblayer; use AWITPT::Cache;
use AWITPT::DB::DBLayer;
use AWITPT::Util;
use smradius::logging; use smradius::logging;
use smradius::util; use smradius::util;
...@@ -33,12 +35,10 @@ use Math::BigFloat; ...@@ -33,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(
); );
...@@ -49,6 +49,7 @@ our $pluginInfo = { ...@@ -49,6 +49,7 @@ our $pluginInfo = {
Init => \&init, Init => \&init,
# Cleanup run by smadmin # Cleanup run by smadmin
CleanupOrder => 30,
Cleanup => \&cleanup, Cleanup => \&cleanup,
# Accounting database # Accounting database
...@@ -108,14 +109,14 @@ sub init ...@@ -108,14 +109,14 @@ sub init
) )
VALUES VALUES
( (
%{request.User-Name}, %{user.Username},
%{request.Service-Type}, %{request.Service-Type},
%{request.Framed-Protocol}, %{request.Framed-Protocol},
%{request.NAS-Port}, %{request.NAS-Port},
%{request.NAS-Port-Type}, %{request.NAS-Port-Type},
%{request.Calling-Station-Id}, %{request.Calling-Station-Id},
%{request.Called-Station-Id}, %{request.Called-Station-Id},
%{request.NAS-Port-Id=}, %{request.NAS-Port-Id},
%{request.Acct-Session-Id}, %{request.Acct-Session-Id},
%{request.Framed-IP-Address}, %{request.Framed-IP-Address},
%{request.Acct-Authentic}, %{request.Acct-Authentic},
...@@ -124,34 +125,34 @@ sub init ...@@ -124,34 +125,34 @@ 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
WHERE WHERE
Username = %{request.User-Name} Username = %{user.Username}
AND AcctSessionID = %{request.Acct-Session-Id} AND AcctSessionID = %{request.Acct-Session-Id}
AND NASIPAddress = %{request.NAS-IP-Address} AND NASIPAddress = %{request.NAS-IP-Address}
AND NASPortID = %{request.NAS-Port-Id=} AND NASPort = %{request.NAS-Port}
GROUP BY GROUP BY
PeriodKey PeriodKey
ORDER BY ORDER BY
...@@ -162,19 +163,19 @@ sub init ...@@ -162,19 +163,19 @@ 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 = %{request.User-Name} Username = %{user.Username}
AND AcctSessionID = %{request.Acct-Session-Id} AND AcctSessionID = %{request.Acct-Session-Id}
AND NASIPAddress = %{request.NAS-IP-Address} AND NASIPAddress = %{request.NAS-IP-Address}
AND NASPortID = %{request.NAS-Port-Id=} AND NASPort = %{request.NAS-Port}
AND PeriodKey = %{query.PeriodKey} AND PeriodKey = %{query.PeriodKey}
'; ';
...@@ -185,10 +186,10 @@ sub init ...@@ -185,10 +186,10 @@ sub init
AcctStatusType = %{request.Acct-Status-Type}, AcctStatusType = %{request.Acct-Status-Type},
AcctTerminateCause = %{request.Acct-Terminate-Cause} AcctTerminateCause = %{request.Acct-Terminate-Cause}
WHERE WHERE
Username = %{request.User-Name} Username = %{user.Username}
AND AcctSessionID = %{request.Acct-Session-Id} AND AcctSessionID = %{request.Acct-Session-Id}
AND NASIPAddress = %{request.NAS-IP-Address} AND NASIPAddress = %{request.NAS-IP-Address}
AND NASPortID = %{request.NAS-Port-Id=} AND NASPort = %{request.NAS-Port}
'; ';
$config->{'accounting_usage_query'} = ' $config->{'accounting_usage_query'} = '
...@@ -201,20 +202,34 @@ sub init ...@@ -201,20 +202,34 @@ sub init
FROM FROM
@TP@accounting @TP@accounting
WHERE WHERE
Username = %{request.User-Name} Username = %{user.Username}
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
FROM FROM
@TP@accounting @TP@accounting
WHERE WHERE
Username = %{request.User-Name} Username = %{user.Username}
AND AcctSessionID = %{request.Acct-Session-Id} AND AcctSessionID = %{request.Acct-Session-Id}
AND NASIPAddress = %{request.NAS-IP-Address} AND NASIPAddress = %{request.NAS-IP-Address}
AND NASPortID = %{request.NAS-Port-Id=} AND NASPort = %{request.NAS-Port}
AND PeriodKey = %{query.PeriodKey} AND PeriodKey = %{query.PeriodKey}
ORDER BY ORDER BY
ID ID
...@@ -228,6 +243,9 @@ sub init ...@@ -228,6 +243,9 @@ sub init
ID = %{query.DuplicateID} ID = %{query.DuplicateID}
'; ';
$config->{'accounting_usage_cache_time'} = 300;
# Setup SQL queries # Setup SQL queries
if (defined($scfg->{'mod_accounting_sql'})) { if (defined($scfg->{'mod_accounting_sql'})) {
# Pull in queries # Pull in queries
...@@ -276,6 +294,15 @@ sub init ...@@ -276,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") {
...@@ -294,41 +321,96 @@ sub init ...@@ -294,41 +321,96 @@ sub init
$config->{'accounting_delete_duplicates_query'} = $scfg->{'mod_accounting_sql'}->{'accounting_delete_duplicates_query'}; $config->{'accounting_delete_duplicates_query'} = $scfg->{'mod_accounting_sql'}->{'accounting_delete_duplicates_query'};
} }
} }
if (defined($scfg->{'mod_accounting_sql'}->{'accounting_usage_cache_time'})) {
# Check if we're a boolean
if (defined(my $val = isBoolean($scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'}))) {
# If val is true, we default to the default anyway
# 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]+$/) {
$config->{'accounting_usage_cache_time'} = $scfg->{'mod_accounting_sql'}{'accounting_usage_cache_time'};
} else {
$server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] Value for 'accounting_usage_cache_time' is invalid");
}
}
}
# Log this for info sake
if (defined($config->{'accounting_usage_cache_time'})) {
$server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] getUsage caching ENABLED, cache time is %ds.",
$config->{'accounting_usage_cache_time'});
} else {
$server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] getUsage caching DISABLED");
} }
} }
# 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 (defined($config->{'accounting_usage_cache_time'})) {
my ($res,$val) = cacheGetComplexKeyPair('mod_accounting_sql(getUsage)',$user->{'Username'}."/".
$template->{'query'}->{'PeriodKey'});
if (defined($val) && $val->{'CachedUntil'} > $user->{'_Internal'}->{'Timestamp-Unix'}) {
return $val;
}
}
# 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(),
...@@ -345,7 +427,7 @@ sub getUsage ...@@ -345,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
...@@ -354,22 +436,30 @@ sub getUsage ...@@ -354,22 +436,30 @@ 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
my %res; my %res;
$res{'TotalDataUsage'} = $totalData->bdiv('1024')->bdiv('1024')->bceil()->bstr(); $res{'TotalDataUsage'} = $totalData->bdiv(1024)->bdiv(1024)->bceil()->bstr();
$res{'TotalSessionTime'} = $totalTime->bdiv('60')->bceil()->bstr(); $res{'TotalSessionTime'} = $totalTime->bdiv(60)->bceil()->bstr();
# If we using caching and got here, it means that we must cache the result
if (defined($config->{'accounting_usage_cache_time'})) {
$res{'CachedUntil'} = $user->{'_Internal'}->{'Timestamp-Unix'} + $config->{'accounting_usage_cache_time'};
# Cache the result
cacheStoreComplexKeyPair('mod_accounting_sql(getUsage)',$user->{'Username'}."/".$template->{'query'}->{'PeriodKey'},\%res);
}
return \%res; return \%res;
} }
...@@ -387,6 +477,7 @@ sub acct_log ...@@ -387,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) {
...@@ -395,8 +486,9 @@ sub acct_log ...@@ -395,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'});
...@@ -405,28 +497,30 @@ sub acct_log ...@@ -405,28 +497,30 @@ 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 if ($packet->rawattr('Acct-Status-Type') eq "2" || $packet->rawattr('Acct-Status-Type') eq "3") {
my $newPeriod;
if ($packet->attr('Acct-Status-Type') eq "Stop" || $packet->attr('Acct-Status-Type') eq "Alive") {
# 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);
# 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'});
...@@ -435,24 +529,32 @@ sub acct_log ...@@ -435,24 +529,32 @@ sub acct_log
my $totalSessionTime = Math::BigInt->new($template->{'request'}->{'Acct-Session-Time'}); my $totalSessionTime = Math::BigInt->new($template->{'request'}->{'Acct-Session-Time'});
# 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 = $sth->fetchrow_hashref()) { while (my $sessionPart = hashifyLCtoMC($sth->fetchrow_hashref(),
$sessionPart = hashifyLCtoMC( qw(AcctInputOctets AcctInputPackets AcctOutputOctets AcctOutputPackets AcctInputGigawords AcctOutputGigawords
$sessionPart, SessionTime PeriodKey)
qw(InputOctets InputPackets OutputOctets OutputPackets InputGigawords OutputGigawords SessionTime PeriodKey) )) {
); # Make sure we treat undef values sort of sanely
$sessionPart->{'AcctInputGigawords'} //= 0;
# Convert this session usage to bytes $sessionPart->{'AcctInputOctets'} //= 0;
my $sessionInputBytes = Math::BigInt->new(); $sessionPart->{'AcctOutputGigawords'} //= 0;
$sessionInputBytes->badd($sessionPart->{'InputGigawods'})->bmul(UINT_MAX); $sessionPart->{'AcctOutputOctets'} //= 0;
$sessionInputBytes->badd($sessionPart->{'InputOctets'}); $sessionPart->{'AcctInputPackets'} //= 0;
my $sessionOutputBytes = Math::BigInt->new(); $sessionPart->{'AcctOutputPackets'} //= 0;
$sessionOutputBytes->badd($sessionPart->{'OutputGigawods'})->bmul(UINT_MAX); $sessionPart->{'AcctSessionTime'} //= 0;
$sessionOutputBytes->badd($sessionPart->{'OutputOctets'});
# 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) {
...@@ -471,36 +573,36 @@ sub acct_log ...@@ -471,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
...@@ -510,7 +612,7 @@ sub acct_log ...@@ -510,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;
} }
...@@ -519,7 +621,7 @@ sub acct_log ...@@ -519,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);
} }
} }
...@@ -532,17 +634,25 @@ sub acct_log ...@@ -532,17 +634,25 @@ sub acct_log
# Possible aswell if we are missing a start packet for this session or for the period # Possible aswell if we are missing a start packet for this session or for the period
# #
if ($packet->attr('Acct-Status-Type') eq "Start" || $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?
if (defined($user->{'_UserDB'}->{'Users_data_get'}) && defined($user->{'_UserDB'}->{'Users_data_set'})) {
# Try get his first login
my $firstLogin = $user->{'_UserDB'}->{'Users_data_get'}($server,$user,'global','FirstLogin');
# If we don't get it, set it
if (!defined($firstLogin)) {
$user->{'_UserDB'}->{'Users_data_set'}($server,$user,'global','FirstLogin',$user->{'_Internal'}->{'Timestamp-Unix'});
}
}
} }
...@@ -550,7 +660,7 @@ sub acct_log ...@@ -550,7 +660,7 @@ sub acct_log
# S T O P P A C K E T specifics # S T O P P A C K E T specifics
# #
if ($packet->attr('Acct-Status-Type') eq "Stop") { if ($packet->rawattr('Acct-Status-Type') eq "2") {
# Replace template entries # Replace template entries
my @dbDoParams = templateReplace($config->{'accounting_stop_status_query'},$template); my @dbDoParams = templateReplace($config->{'accounting_stop_status_query'},$template);
...@@ -558,11 +668,12 @@ sub acct_log ...@@ -558,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;
} }
...@@ -579,17 +690,13 @@ sub fixDuplicates ...@@ -579,17 +690,13 @@ 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;
} }
# Pull in duplicates # Pull in duplicates
my @IDList; my @IDList;
while (my $duplicates = $sth->fetchrow_hashref()) { while (my $duplicates = hashifyLCtoMC($sth->fetchrow_hashref(), qw(ID))) {
$duplicates = hashifyLCtoMC(
$duplicates,
qw(ID)
);
push(@IDList,$duplicates->{'ID'}); push(@IDList,$duplicates->{'ID'});
} }
DBFreeRes($sth); DBFreeRes($sth);
...@@ -606,7 +713,7 @@ sub fixDuplicates ...@@ -606,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;
} }
...@@ -624,29 +731,45 @@ sub fixDuplicates ...@@ -624,29 +731,45 @@ sub fixDuplicates
# Add up totals function # Add up totals function
sub cleanup sub cleanup
{ {
my ($server) = @_; my ($server,$runForDate) = @_;
# The datetime now..
my $now = DateTime->now->set_time_zone($server->{'smradius'}->{'event_timezone'});
# If this is a new year # The datetime now
my ($prevYear,$prevMonth); my $now = DateTime->from_epoch(epoch => $runForDate)->set_time_zone($server->{'smradius'}->{'event_timezone'});
if ($now->month == 1) {
$prevYear = $now->year - 1; # Use truncate to set all values after 'month' to their default values
$prevMonth = 12; my $thisMonth = $now->clone()->truncate( to => "month" );
} else {
$prevYear = $now->year; # Last month..
$prevMonth = $now->month - 1; my $lastMonth = $thisMonth->clone()->subtract( months => 1 );
my $prevPeriodKey = $lastMonth->strftime("%Y-%m");
# Begin transaction
DBBegin();
$server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] Cleanup => Removing previous accounting summaries (if any)");
# Delete duplicate records
# NK: MYSQL SPECIFIC
my $sth = DBDo('
DELETE FROM
@TP@accounting_summary
WHERE
STR_TO_DATE(PeriodKey,"%Y-%m") >= ?',
$prevPeriodKey
);
if (!$sth) {
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to delete accounting summary record: ".
AWITPT::DB::DBLayer::error());
DBRollback();
return;
} }
# New datetime $server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] Cleanup => Generating accounting summaries");
my $lastMonth = DateTime->new( year => $prevYear, month => $prevMonth, day => 1 );
my $periodKey = $lastMonth->strftime("%Y-%m");
# Sanitize
$lastMonth = $lastMonth->ymd();
# Select totals for last month # Select totals for last month
my $sth = DBSelect(' $sth = DBSelect('
SELECT SELECT
Username, Username,
AcctSessionTime, AcctSessionTime,
...@@ -659,12 +782,11 @@ sub cleanup ...@@ -659,12 +782,11 @@ sub cleanup
WHERE WHERE
PeriodKey = ? PeriodKey = ?
', ',
$periodKey $prevPeriodKey
); );
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;
} }
...@@ -686,7 +808,7 @@ sub cleanup ...@@ -686,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
...@@ -695,7 +817,7 @@ sub cleanup ...@@ -695,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);
} }
...@@ -703,9 +825,9 @@ sub cleanup ...@@ -703,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) {
...@@ -717,7 +839,7 @@ sub cleanup ...@@ -717,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
...@@ -726,30 +848,14 @@ sub cleanup ...@@ -726,30 +848,14 @@ 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);
} }
} }
} }
# Begin transaction $server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] Cleanup => Creating new accounting summaries");
DBBegin();
# Delete duplicate records
my @dbDoParams;
@dbDoParams = ('
DELETE FROM
@TP@accounting_summary
WHERE
PeriodKey = ?',
$lastMonth
);
if ($sth) {
# Do query
$sth = DBDo(@dbDoParams);
}
# Loop through users and insert totals # Loop through users and insert totals
foreach my $username (keys %usageTotals) { foreach my $username (keys %usageTotals) {
...@@ -761,11 +867,12 @@ sub cleanup ...@@ -761,11 +867,12 @@ sub cleanup
# Rounding up # Rounding up
my $res; my $res;
$res->{'TotalDataInput'} = $totalDataInput->bdiv('1024')->bdiv('1024')->bceil()->bstr(); $res->{'TotalDataInput'} = $totalDataInput->bdiv(1024)->bdiv(1024)->bceil()->bstr();
$res->{'TotalDataOutput'} = $totalDataOutput->bdiv('1024')->bdiv('1024')->bceil()->bstr(); $res->{'TotalDataOutput'} = $totalDataOutput->bdiv(1024)->bdiv(1024)->bceil()->bstr();
$res->{'TotalSessionTime'} = $totalTime->bdiv('60')->bceil()->bstr(); $res->{'TotalSessionTime'} = $totalTime->bdiv(60)->bceil()->bstr();
@dbDoParams = (' # Do query
$sth = DBDo('
INSERT INTO INSERT INTO
@TP@accounting_summary @TP@accounting_summary
( (
...@@ -779,31 +886,30 @@ sub cleanup ...@@ -779,31 +886,30 @@ sub cleanup
(?,?,?,?,?) (?,?,?,?,?)
', ',
$username, $username,
$lastMonth, $prevPeriodKey,
$res->{'TotalSessionTime'}, $res->{'TotalSessionTime'},
$res->{'TotalDataInput'}, $res->{'TotalDataInput'},
$res->{'TotalDataOutput'} $res->{'TotalDataOutput'}
); );
if (!$sth) {
if ($sth) { $server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to create accounting summary record: ".
# Do query AWITPT::DB::DBLayer::error());
$sth = DBDo(@dbDoParams); DBRollback();
return;
} }
}
# Rollback with error if failed # Lets log
if (!$sth) { $server->log(LOG_DEBUG,"[MOD_ACCOUNTING_SQL] Cleanup => INSERT: Username = '%s', PeriodKey = '%s', ".
DBRollback(); "TotalSessionTime = '%s', TotalInput = '%s', TotalOutput = '%s'", $username, $prevPeriodKey,
$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to insert accounting summary record: ". $res->{'TotalSessionTime'}, $res->{'TotalDataInput'}, $res->{'TotalDataOutput'});
awitpt::db::dblayer::Error());
return;
} }
# Commit if succeeded # Commit if succeeded
DBCommit(); DBCommit();
$server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] Cleanup => Accounting summary updated"); $server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] Cleanup => Accounting summaries created");
} }
1; 1;
# vim: ts=4 # vim: ts=4
# Test accounting database # Test accounting database
# Copyright (C) 2007-2010, 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,15 +99,18 @@ Acct-Delay-Time: %{accounting.Acct-Delay-Time} ...@@ -99,15 +99,18 @@ 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;
if ($packet->attr('Acct-Status-Type') eq "Start") { # Add user details
$template->{'user'}->{'ID'} = $user->{'ID'};
$template->{'user'}->{'Username'} = $user->{'Username'};
if ($packet->rawattr('Acct-Status-Type') eq "1") {
$server->log(LOG_DEBUG,"Start Packet: ".$packet->dump()); $server->log(LOG_DEBUG,"Start Packet: ".$packet->dump());
} elsif ($packet->attr('Acct-Status-Type') eq "Alive") { } elsif ($packet->rawattr('Acct-Status-Type') eq "3") {
$server->log(LOG_DEBUG,"Alive Packet: ".$packet->dump()); $server->log(LOG_DEBUG,"Alive Packet: ".$packet->dump());
} elsif ($packet->attr('Acct-Status-Type') eq "Stop") { } elsif ($packet->rawattr('Acct-Status-Type') eq "2") {
$server->log(LOG_DEBUG,"Stop Packet: ".$packet->dump()); $server->log(LOG_DEBUG,"Stop Packet: ".$packet->dump());
} }
......
# CHAP authentication # CHAP authentication
# Copyright (C) 2007-2010, AllWorldIT # Copyright (C) 2007-2015, AllWorldIT
# #
# References: # References:
# RFC1944 - PPP Challenge Handshake Authentication Protocol (CHAP) # RFC1944 - PPP Challenge Handshake Authentication Protocol (CHAP)
......
# SMRadius Utility Functions # MAC Authentication
# Copyright (C) 2007-2010, AllWorldIT # Copyright (C) 2007-2015, 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.
package smradius::modules::authentication::mod_auth_macauth;
## @class smradius::util
# Utility functions
package smradius::util;
use strict; use strict;
use warnings; use warnings;
# Modules we need
use smradius::attributes;
use smradius::constants;
use smradius::logging;
# Exporter stuff # Exporter stuff
require Exporter; require Exporter;
our (@ISA,@EXPORT); our (@ISA,@EXPORT,@EXPORT_OK);
@ISA = qw(Exporter); @ISA = qw(Exporter);
@EXPORT = qw( @EXPORT = qw(
niceUndef );
templateReplace @EXPORT_OK = qw(
isBoolean
); );
## @fn niceUndef($string) # Plugin info
# If string defined return 'string', or if undefined return -undef- our $pluginInfo = {
# Name => "MAC Authentication",
# @param string String to check Init => \&init,
#
# @return Return 'string' if defined, or -undef- otherwise # Authentication
sub niceUndef Authentication_try => \&authenticate,
{ };
my $string = shift;
return defined($string) ? "'$string'" : '-undef-';
}
## @fn templateReplace($string,$hashref) ## @internal
# Template string replacer function # Initialize module
# sub init
# @param string String to replace template items in
# @param hashref Hashref containing the hash of tempalte items & values
#
# @return String with replaced items
sub templateReplace
{ {
my ($string,$hashref) = @_; my $server = shift;
my @valueArray = ();
# Replace blanks
while (my ($entireMacro,$section,$item,$default) = ($string =~ /(\%{([a-z]+)\.([a-z0-9\-]+)(?:=([^}]*))?})/i )) {
# Replace macro with ?
$string =~ s/$entireMacro/\?/;
# Get value to substitute
my $value = defined($hashref->{$section}->{$item}) ? $hashref->{$section}->{$item} : $default;
# Add value onto our array
push(@valueArray,$value);
}
return ($string, @valueArray);
} }
## @fn isBoolean($var)
# Check if a variable is boolean ## @authenticate
# Try authenticate user
# #
# @param var Variable to check # @param server Server object
# @param user User hash
# @param packet Radius packet
# #
# @return 1, 0 or undef # @return Result
sub isBoolean sub authenticate
{ {
my $var = shift; my ($server,$user,$packet) = @_;
# Check if we're defined # This is not a MAC authentication request
if (!defined($var)) { if ($user->{'_UserDB'}->{'Name'} ne "SQL User Database (MAC authentication)") {
return undef; return MOD_RES_SKIP;
} }
# Nuke whitespaces $server->log(LOG_DEBUG,"[MOD_AUTH_MACAUTH] This is a MAC authentication request");
$var =~ s/\s//g;
# Allow true, on, set, enabled, 1, false, off, unset, disabled, 0
if ($var =~ /^(?:true|on|set|enabled|1)$/i) {
return 1;
}
if ($var =~ /^(?:false|off|unset|disabled|0)$/i) {
return 0;
}
# Invalid or unknown return MOD_RES_ACK;
return undef;
} }
1; 1;
# vim: ts=4 # vim: ts=4
# Microsoft CHAP version 1 and 2 support # Microsoft CHAP version 1 and 2 support
# Copyright (C) 2007-2010, AllWorldIT # Copyright (C) 2007-2015, AllWorldIT
# #
# References: # References:
# RFC1994 - PPP Challenge Handshake Authentication Protocol (CHAP) # RFC1994 - PPP Challenge Handshake Authentication Protocol (CHAP)
...@@ -38,7 +38,7 @@ use smradius::constants; ...@@ -38,7 +38,7 @@ use smradius::constants;
use smradius::logging; use smradius::logging;
use Crypt::DES; use Crypt::DES;
use Crypt::RC4; use Crypt::RC4;
use Digest::SHA1; use Digest::SHA;
use Digest::MD4 qw( md4 ); use Digest::MD4 qw( md4 );
use Digest::MD5 qw( ); use Digest::MD5 qw( );
...@@ -328,7 +328,7 @@ sub ChallengeHash ...@@ -328,7 +328,7 @@ sub ChallengeHash
# SHA encryption # SHA encryption
my $sha = Digest::SHA1->new(); my $sha = Digest::SHA->new();
$sha->add($PeerChallenge); $sha->add($PeerChallenge);
$sha->add($AuthenticatorChallenge); $sha->add($AuthenticatorChallenge);
$sha->add($UserName); $sha->add($UserName);
...@@ -565,7 +565,7 @@ sub GenerateAuthenticatorResponse ...@@ -565,7 +565,7 @@ sub GenerateAuthenticatorResponse
my $PasswordHashHash = HashNtPasswordHash($PasswordHash); my $PasswordHashHash = HashNtPasswordHash($PasswordHash);
# SHA encryption # SHA encryption
my $sha = Digest::SHA1->new(); my $sha = Digest::SHA->new();
$sha->add($PasswordHashHash); $sha->add($PasswordHashHash);
$sha->add($NTResponse); $sha->add($NTResponse);
foreach my $item (@Magic1) { foreach my $item (@Magic1) {
...@@ -575,7 +575,7 @@ sub GenerateAuthenticatorResponse ...@@ -575,7 +575,7 @@ sub GenerateAuthenticatorResponse
my $Challenge = ChallengeHash($PeerChallenge, $AuthenticatorChallenge, $UserName); my $Challenge = ChallengeHash($PeerChallenge, $AuthenticatorChallenge, $UserName);
$sha = Digest::SHA1->new(); $sha = Digest::SHA->new();
$sha->add($Digest); $sha->add($Digest);
$sha->add($Challenge); $sha->add($Challenge);
foreach my $item (@Magic2) { foreach my $item (@Magic2) {
...@@ -849,7 +849,7 @@ sub GetMasterKey ...@@ -849,7 +849,7 @@ sub GetMasterKey
"68", "65", "20", "4d", "50", "50", "45", "20", "4d", "68", "65", "20", "4d", "50", "50", "45", "20", "4d",
"61", "73", "74", "65", "72", "20", "4b", "65", "79"); "61", "73", "74", "65", "72", "20", "4b", "65", "79");
my $sha = Digest::SHA1->new(); my $sha = Digest::SHA->new();
$sha->add($PasswordHashHash); $sha->add($PasswordHashHash);
$sha->add($NTResponse); $sha->add($NTResponse);
foreach my $item (@Magic1) { foreach my $item (@Magic1) {
...@@ -962,7 +962,7 @@ sub GetAsymmetricStartKey ...@@ -962,7 +962,7 @@ sub GetAsymmetricStartKey
} }
} }
my $sha = Digest::SHA1->new(); my $sha = Digest::SHA->new();
$sha->add($MasterKey); $sha->add($MasterKey);
foreach my $item (@SHSpad1) { foreach my $item (@SHSpad1) {
$sha->add(pack("H*",$item)); $sha->add(pack("H*",$item));
......
# PAP # PAP
# Copyright (C) 2007-2010, AllWorldIT # Copyright (C) 2007-2015, AllWorldIT
# #
# References: # References:
# RFC1334 - PPP Authentication Protocols # RFC1334 - PPP Authentication Protocols
...@@ -80,6 +80,8 @@ sub authenticate ...@@ -80,6 +80,8 @@ sub authenticate
# Check if this is PAP authentication # Check if this is PAP authentication
return MOD_RES_SKIP if (!defined($encPassword)); return MOD_RES_SKIP if (!defined($encPassword));
# Skip MAC authentication
return MOD_RES_SKIP if ($user->{'_UserDB'}->{'Name'} eq "SQL User Database (MAC authentication)");
$server->log(LOG_DEBUG,"[MOD_AUTH_PAP] This is a PAP authentication request"); $server->log(LOG_DEBUG,"[MOD_AUTH_PAP] This is a PAP authentication request");
......