#!/usr/bin/perl -w
#
# This sample Perl script limits the number of concurrent logins a user can
# create from the same location within certain time frame.
#
# Originator page:
# See also:
use strict;
use Time::Local;
use CLI; #get one from www.stalker.com/CGPerl/
###############
my $loginTimeFrame=60*2; #seconds
my $CLIAddress='127.0.0.1';
my $CLILogin='postmaster';
my $CLIPassword='pass';
my $doDebug=1;
###############
$| = 1;
print "* sample RADIUS helper script v1.02 started\n";
my $connTime=0;
my $cli;
while () {
chomp;
my $cmdLine=$_;
my ( $prefix, $command, @eargs ) = split (/ /);
if ( $command eq 'LOGIN' ) {
unless ( $prefix && $command && $cmdLine=~/({.*})\s+({.*})/ ) {
print "$prefix ERROR Expected: nnn LOGIN user\@domain {attributes} {settings}\n";
} else {
LoginCommand($prefix,$eargs[0],$1,$2);
}
}
elsif ( $command eq 'ACCNT' ) {
unless ( $prefix && $command && $cmdLine=~/(started|ended|updated) (\S+) ({.*})/ ) {
print "$prefix ERROR Expected: nnn ACCNT command user\@domain {attributes}\n";
} else {
AccntCommand($prefix,$1,$2,$3);
}
}
elsif ( $command eq 'INTF' ) {
print "$prefix INTF 1\n";
}
elsif ( $command eq 'QUIT' ) {
print "$prefix OK\n";
last;
}
else {
print "$prefix ERROR Only LOGIN,ACCNT,INTF and QUIT commands supported\n";
}
}
print "* sample RADIUS helper script v1.02 ended\n";
exit;
sub ReconnectCLI {
if(time()>$connTime+4*60+30) {
debugMsg("(re)Connecting to CLI");
$cli=new CGP::CLI( { PeerAddr => $CLIAddress,
PeerPort => 106,
login => $CLILogin,
password => $CLIPassword,
Timeout => 2
} );
unless($cli) { #trying again
debugMsg("connection failed, re-connecting to CLI again");
sleep(2); #wait for 2 seconds
$cli=new CGP::CLI( { PeerAddr => $CLIAddress,
PeerPort => 106,
login => $CLILogin,
password => $CLIPassword,
Timeout => 2
} );
}
unless($cli) { #give up trying
die "Can't login to CGPro: ".$CGP::ERR_STRING."\n";
}
}
$connTime=time();
}
sub LoginCommand {
my ($prefix,$account,$attrDict,$settingsDict) = @_;
ReconnectCLI();
my $attr=$cli->parseWords($attrDict);
my $settings=$cli->parseWords($settingsDict);
if(ref($attr) ne 'HASH') {
debugMsg("Error: attributes is not in dictionary format");
print "$prefix REJECT was error\n";
return;
};
if(ref($settings) ne 'HASH') {
debugMsg("Error: settings is not in dictionary format\n");
print "$prefix REJECT was error\n";
return;
};
my $radConcurrent=@$settings{RADIUSConcurrent};
if((!defined($radConcurrent))) {
debugMsg("no RADIUSConcurrent setting");
$radConcurrent=1;
}
my $nRecentLogins=0;
my $sessions=$cli->GetAccountInfo($account,'RADIUSSESSIONS');
unless($cli->isSuccess) {
die "Error: GetAccountInfo failed:".$cli->getErrMessage.", quitting";
}
if(defined($sessions) && ref($sessions) eq 'HASH') {
debugMsg("RADIUSSESSIONS data:");
foreach(keys %$sessions) {
my $key=$_;
my $item=@$sessions{$_};
if(ref($item) eq 'HASH') {
debugMsg(" $key: $_ = @$item{$_}") foreach(keys %$item);
my $when=@$item{'when'};
if(defined($when)) {
if((Date2Secs($when)+$loginTimeFrame) > time()) {
$nRecentLogins++;
}
}
}
}
debugMsg("$nRecentLogins recent logins");
} else {
debugMsg("no RADIUSSESSIONS info");
}
if($nRecentLogins > $radConcurrent) {
print "$prefix REJECT\n";
return;
}
my $framedIP=@$settings{FramedIPAddress};
if((!defined($framedIP))) {
debugMsg("no FramedIPAddress setting");
}
if(defined($framedIP)) {
print "$prefix ACCEPT {8=$framedIP;}\n";
} else {
print "$prefix ACCEPT {}\n";
}
return;
}
sub AccntCommand {
my ($prefix,$command,$account,$attrDict) = @_;
ReconnectCLI();
my $attr=$cli->parseWords($attrDict);
if(ref($attr) ne 'HASH') {
debugMsg("Error: attributes is not in dictionary format");
print "$prefix REJECT was error\n";
return;
};
foreach (keys %$attr) {
debugMsg(" $_ = \"@$attr{$_}\"");
}
#LockAccoount($account);
my $sessions=$cli->GetAccountInfo($account,'RADIUSSESSIONS');
unless($cli->isSuccess) {
die "Error: GetAccountInfo failed:".$cli->getErrMessage.", quitting";
}
if(defined($sessions) && ref($sessions) eq 'HASH') {
debugMsg("RADIUSSESSIONS data:");
foreach(keys %$sessions) {
my $key=$_;
my $item=@$sessions{$_};
if(ref($item) eq 'HASH') {
debugMsg("$key: $_ = @$item{$_}") foreach(keys %$item);
my $when=@$item{'when'};
if(defined($when)) {
if((Date2Secs($when)+$loginTimeFrame) < time()) {
delete @$sessions{$key};
debugMsg("deleting old entry");
}
}
}
}
} else {
debugMsg("no RADIUSSESSIONS info");
$sessions={};
}
my $newKey="";
my $elem;
$elem=@$attr{'4'};
$elem="" unless(defined($elem) && !ref($elem));
$newKey.=$elem . '-';
$elem=@$attr{'5'};
$elem="" unless(defined($elem) && !ref($elem));
$newKey.=$elem . '-';
$elem=@$attr{'61'};
$elem="" unless(defined($elem) && !ref($elem));
$newKey.=$elem . '-';
$elem=@$attr{'32'};
$elem="" unless(defined($elem) && !ref($elem));
$newKey.=$elem;
if($command eq 'ended') {
delete @$sessions{$newKey};
} else {
@$sessions{$newKey}={when => Secs2Date(time()),};
}
#$cli->UpdateAccountInfo($account,{RADIUSSESSIONS=>$sessions});
$cli->SendCommand("UpdateAccountInfo $account ".$cli->printWords({RADIUSSESSIONS=>$sessions}));
#UnlockAccount($account);
print "$prefix OK\n";
return;
}
sub Date2Secs {
my ($year,$month,$day,$hour,$min,$sec);
if($_[0] =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/) {
($year,$month,$day,$hour,$min,$sec) = ($1,$2,$3,$4,$5,$6);
} else {
die "Unknown date format: \"$_[0]\", quitting";
}
return timegm($sec,$min,$hour,$day,$month-1,$year-1900);
}
sub Secs2Date {
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($_[0]);
return sprintf("%04d%02d%02d%02d%02d%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
}
sub debugMsg {
my $msg=$_[0];
print "* $msg\n" if($doDebug);
}
__END__;