Forked from
smradius / smradius
712 commits behind the upstream repository.
-
Nigel Kukard authoredNigel Kukard authored
auth-filter 11.15 KiB
#!/usr/bin/perl -w
# Author: Nigel Kukard <nkukard@lbsd.net>
# Date: 12/04/2007
# Desc: Authentication filter for GNU Radius
# License: GPL
use strict;
use Benchmark;
use Getopt::Long;
use DateTime;
use Time::HiRes qw( gettimeofday tv_interval );
# Set library directory
use lib qw(../../);
use sm::config;
use sm::dblayer;
require("common.pm");
my %optctl = ();
GetOptions(\%optctl, "help");
# Check if user wants usage
if (defined($optctl{'help'})) {
displayUsage();
}
# Open up logfile
my $logfile = "/var/log/radius/auth-filter";
open(FH,">> $logfile") or die "Failed to open '$logfile': $!";
# Databases
my $dbh; # Authentication
my $dbh_log; # Logs
# Get db handle
$dbh = sm::dbilayer->new($cfg_db_DSN, $cfg_db_Username, $cfg_db_Password);
if (!$dbh) {
print(STDERR "Error creating database object: ".sm::dbilayer->internalErr());
exit 1;
}
# Connect to database
if ($dbh->connect() != 0) {
print(STDERR "Error connecting to database: ".$dbh->err);
exit 1;
}
# Check if we must use split db's
if (defined($cfg_radiuslog_db_DSN)) {
# Get log db handle
$dbh_log = sm::dbilayer->new($cfg_radiuslog_db_DSN, $cfg_radiuslog_db_Username, $cfg_radiuslog_db_Password);
if (!$dbh_log) {
print(STDERR "Error creating database object: ".sm::dbilayer->internalErr());
exit 1;
}
# Connect to database
if ($dbh_log->connect() != 0) {
print(STDERR "Error connecting to database: ".$dbh_log->err);
exit 1;
}
# If not use the main DB
} else {
$dbh_log = $dbh;
}
# No buffering
select((select(FH), $| = 1)[0]);
select((select(STDOUT), $| = 1)[0]);
# Loop with input
while (my $line = <STDIN>) {
my %request;
my @request;
my %accessAttribs;
my %replyAttribs;
# Munch off \n
chomp($line);
# Check number of results
if ((@request = split /:/, $line) < 6) {
print(FH "ERROR: Number of params from radiusd: ".(@request)."/$line\n");
print(STDOUT "1\n");
next;
}
# Pull in request
($request{'User-Name'},$request{'NAS-IP-Address'},$request{'NAS-Port-Type'},$request{'NAS-Port'},$request{'Connect-Info'},
$request{'Service-Type'}) = @request;
my $dt = DateTime->from_epoch( epoch => time() );
$request{'Timestamp'} = $dt->strftime('%Y-%m-%d %H:%M:%S');
# If this is a auth mechanism that called us, allow
if ($request{'NAS-Port-Type'} eq "0" && $request{'NAS-Port'} eq "0" && $request{'Service-Type'} eq "0") {
print(STDOUT "0\n");
next;
}
my $timer0 = [gettimeofday];
# Grab user details
my $userData = getUser($dbh,$request{'User-Name'});
if (ref $userData ne "HASH") {
print(FH "ERROR: $userData\n");
print(STDOUT "1\n");
next;
}
printf(FH 'INFO: User-Name: %s, Timestamp: %s, NAS-IP-Address: %s, NAS-Port-Type: %s, NAS-Port: %s, Connect-Info: %s, Service-Type: %s, CappingType: %s, UsageCap: %s, AgentDisabled: %s'."\n",
$request{'User-Name'},
$request{'Timestamp'},
$request{'NAS-IP-Address'},
$request{'NAS-Port-Type'},
$request{'NAS-Port'},
$request{'Connect-Info'},
$request{'Service-Type'},
$userData->{'CappingType'},
defined($userData->{'UsageCap'}) ? $userData->{'UsageCap'} : "uncapped",
$userData->{'AgentDisabled'}
);
# Check user active, else insert into auth fail
if ($userData->{'AgentDisabled'} eq "1") {
print(FH " - Agent disabled\n");
print(STDOUT "1 Reply-Message = \"Your account is currently deactivated. Please, contact your ISP.\"\n");
authFail(\%request,1);
next;
}
# Pull in class attribs & check
my $sth = $dbh->select("
SELECT
Attr, OP, Value
FROM
radiusClassAttribs
WHERE
RadiusClassID = ".$dbh->quote($userData->{'RadiusClassID'})."
AND OP IS NOT NULL
");
if (!$sth) {
print(FH "ERROR: Selecting class attributes: ".$dbh->err."\n");
print(STDOUT "1\n");
next;
}
# Loop with class attribs and push
while (my $item = $sth->fetchrow_hashref()) {
push(@{$accessAttribs{$item->{'Attr'}}{$item->{'OP'}}},$item->{'Value'});
}
$sth->finish();
# Pull in user attribs & check
$sth = $dbh->select("
SELECT
Attr, OP, Value
FROM
radiusAttribs
WHERE
RadiusUserID = ".$dbh->quote($userData->{'ID'})."
AND OP IS NOT NULL
");
if (!$sth) {
print(FH "ERROR: Selecting class attributes: ".$dbh->err."\n");
print(STDOUT "1\n");
next;
}
# Loop with class attribs and push
while (my $item = $sth->fetchrow_hashref()) {
push(@{$accessAttribs{$item->{'Attr'}}{$item->{'OP'}}},$item->{'Value'});
}
$sth->finish();
# Loop with access attribs and push
my $rejectAttrs = 0;
foreach my $attr (keys %accessAttribs) {
my $ok = 0;
# Check if we missing something in the request
if (!defined($request{$attr})) {
printf(FH " - WARNING: Attribute '$attr' was in accessAttribs, but not request\n");
next;
}
# Loop with attrib op's and check them out
foreach my $op (keys %{$accessAttribs{$attr}}) {
printf(FH ' - Checking %s, request="%s", op="%s", attr="%s": ',
$attr,
$request{$attr},
$op,
join(',',@{$accessAttribs{$attr}{$op}})
);
# Check value against operator
foreach my $val (@{$accessAttribs{$attr}{$op}}) {
# Equal
if ($op eq "=") {
if ($request{$attr} eq $val) {
print(FH "matched '$val'\n");
$ok = 1;
last;
}
}
}
# Check if we ok, if not continue
if ($ok == 0) {
print(FH "no match\n");
} else {
last
}
}
# Check if we ok, if not we've been violated
if ($ok == 0) {
print(FH " - Class attribute violation: '$attr'\n");
$rejectAttrs = 1;
last;
}
}
# Check if something didn't match up
if ($rejectAttrs == 1) {
print(STDOUT "1 Reply-Message = \"Connection attribute mismatch. Please, contact your ISP.\"\n");
authFail(\%request,5);
next;
}
# Check user type vs. adsl & analogue/isdn
# IF ADSL
if ($request{'NAS-Port-Type'} eq "5") {
# Calculate dates
my $date = DateTime->from_epoch( epoch => time() );
my $today = $date->ymd();
$date->set_day(1);
my $thismonth = $date->ymd();
$date->add( months => 1);
my $nextmonth = $date->ymd();
# Extra query for selects
my $extraQuery = "";
# Check port locking, else insert into auth fail
$sth = $dbh->select("
SELECT
NASPort
FROM
radiusPortLocks
WHERE
RadiusUserID = ".$dbh->quote($userData->{'ID'})."
AND AgentDisabled = 0
");
if (!$sth) {
print(FH "ERROR: Selecting NAS ports: ".$dbh->err."\n");
print(STDOUT "1\n");
next;
}
# Check rows
if ($sth->rows > 0) {
my $found = 0;
# Loop with port locks
while (my $portLock = $sth->fetchrow_hashref()) {
# Check if we found port locking
if ($request{'NAS-Port'} eq $portLock->{'NASPort'}) {
$found = 1;
last;
}
}
# Check if we found it
if ($found == 0) {
print(FH " - Port locked\n");
print(STDOUT "1 Reply-Message = \"Connection from unauthorized port. Please, contact your ISP.\"\n");
authFail(\%request,4);
$sth->finish();
next;
}
}
$sth->finish();
# NULL - uncapped, no limits
if (!defined($userData->{'UsageCap'})) {
$extraQuery = "AND Timestamp > ".$dbh->quote($thismonth)." AND Timestamp < ".$dbh->quote($nextmonth);
# > 0 - normal cap, check usage for this month, check topups for this month
# Calculate cap user has (acctinputoctets + (2^32 * gigawords)) /1024 / 1024
# Calculate usage user has
} elsif ($userData->{'UsageCap'} > 0) {
$extraQuery = "AND Timestamp > ".$dbh->quote($thismonth)." AND Timestamp < ".$dbh->quote($nextmonth);
# 0 - topup account
} elsif ($userData->{'UsageCap'} == 0) {
}
# Grab users usage
my $usageData = getUsage($dbh_log,$request{'User-Name'},$extraQuery);
if (ref $usageData ne "HASH") {
print(FH "ERROR: $usageData\n");
print(STDOUT "1\n");
next;
}
my $totalUsage = $usageData->{'Total'};
# If we a normal or topup, check topups
if (defined($userData->{'UsageCap'}) && ($userData->{'UsageCap'} > 0 || $userData->{'UsageCap'} == 0)) {
# Prepare
$extraQuery = "";
# Only total up this month
if ($userData->{'UsageCap'} > 0) {
$extraQuery = "AND ValidFrom <= ".$dbh->quote($today)." AND ValidTo >= ".$dbh->quote($today);
}
# Get how much we've been topped up
my $topupData = getTopups($dbh,$request{'User-Name'},$extraQuery);
if (ref $topupData ne "HASH") {
print(FH "ERROR: $topupData\n");
print(STDOUT "1\n");
next;
}
my $topupBw = $topupData->{'Total'};
# Check capping, else insert into auth fail
print(FH " - Usage $totalUsage (Cap:".$userData->{'UsageCap'}."+Topup:$topupBw)\n");
# Check capping
if ($userData->{'CappingType'} == 1 && ($userData->{'UsageCap'} + $topupBw) <= $totalUsage) {
print(FH " - User capped\n");
print(STDOUT "1 Reply-Message = \"Your account is has been capped. Please, contact your ISP.\"\n");
authFail(\%request,2);
next;
}
}
# IF ANALOGUE
} elsif ($request{'NAS-Port-Type'} eq "0") {
# IF ISDN
} elsif ($request{'NAS-Port-Type'} eq "2") {
} else {
print(FH "ERROR: Unknown NAS-Port-Type: ".$request{'NAS-Port-Type'}."\n");
}
# Pull in class attribs
$sth = $dbh->select("
SELECT
Attr, Value
FROM
radiusClassAttribs
WHERE
RadiusClassID = ".$dbh->quote($userData->{'RadiusClassID'})."
AND OP IS NULL
");
if (!$sth) {
print(FH "ERROR: Selecting class attributes: ".$dbh->err."\n");
print(STDOUT "1\n");
next;
}
# Loop with class attribs
while (my $item = $sth->fetchrow_hashref()) {
$replyAttribs{$item->{'Attr'}} = $item->{'Value'};
}
$sth->finish();
# Pull in user attribs
$sth = $dbh->select("
SELECT
Attr, Value
FROM
radiusAttribs
WHERE
RadiusUserID = ".$dbh->quote($userData->{'ID'})."
AND OP IS NULL
");
if (!$sth) {
print(FH "ERROR: Selecting user attributes: ".$dbh->err."\n");
print(STDOUT "1\n");
next;
}
# Loop with user attribs
while (my $item = $sth->fetchrow_hashref()) {
$replyAttribs{$item->{'Attr'}} = $item->{'Value'};
}
$sth->finish();
# Build up our attrib pairs
my @replyAttribs;
foreach my $key (keys %replyAttribs) {
push(@replyAttribs,sprintf('%s = %s',$key,$replyAttribs{$key}));
}
my $timer1 = [gettimeofday];
my $timediff = tv_interval($timer0,$timer1);
printf(FH ' - Attributes => %s'."\n",join(", ",@replyAttribs));
print(FH "Code execution took: ${timediff}s\n");
# Reply with positive status plus , separated list of attribs
printf(STDOUT '0 %s'."\n",join(", ",@replyAttribs));
}
close(FH);
# Log failed authentication
sub authFail
{
my ($request,$reason) = @_;
# Insert entry into auth fail table
my $sth = $dbh_log->do("
INSERT INTO radiusAuthFail
(
Username,
Timestamp,
NASIPAddress,
NASPortType,
NASPort,
ConnectInfo,
ServiceType,
Reason
)
VALUES
(
".$dbh->quote($request->{'User-Name'}).",
".$dbh->quote($request->{'Timestamp'}).",
".$dbh->quote($request->{'NAS-IP-Address'}).",
".$dbh->quote($request->{'NAS-Port-Type'}).",
".$dbh->quote($request->{'NAS-Port'}).",
".$dbh->quote($request->{'Connect-Info'}).",
".$dbh->quote($request->{'Service-Type'}).",
".$dbh->quote($request->{'Reason'})."
)
");
if (!$sth) {
print(FH "ERROR: Failed to insert radius auth fail data: ".$dbh_log->err."\n");
}
}
# Display usage
sub displayUsage {
print("Usage: $0 [--quiet]\n");
exit 0;
}
# vim: ts=4