Subject: | Add this Net::Server/OATH example |
I have attached a little example on how to use Net::Server and and OATH-
calculations (Time based one time keys) to implement a RADIUS server.
Works fine with OpenSSH.
Tested with "OTP c200" one time keys.
Using this means to have to change %seeds to reflect your own keys (i
replaced the valies with random numbers before uploading).
Subject: | netserverexample.pl |
#!/usr/bin/perl -w
#---AUTOPRAGMASTART---
use 5.012;
use strict;
use warnings;
use diagnostics;
use mro 'c3';
use English qw( -no_match_vars );
use Carp;
our $VERSION = 0.996;
#---AUTOPRAGMAEND---
BEGIN {
unshift @INC, ".";
};
my $server = Rader->new(
port => 1812,
proto => "udp",
);
print "Server ready to run()...\n";
$server->run();
package Rader;
#---AUTOPRAGMASTART---
use 5.012;
use strict;
use warnings;
use diagnostics;
use mro 'c3';
use English qw( -no_match_vars );
use Carp;
our $VERSION = 0.996;
#---AUTOPRAGMAEND---
use base qw(Net::Server::Single);
use RADIUS::Dictionary;
use RADIUS::Packet;
sub process_request {
my $self = shift;
my $prop = $self->{'server'};
# This is a VERY simple RADIUS authentication server which responds
# to Access-Request packets with Access-Accept. This allows anyone
# to log in.
my $secret = "mysecret"; # Shared secret on the term server
# Parse the RADIUS dictionary file (must have dictionary in current dir)
my $dict = new RADIUS::Dictionary "dictionary"
or die "Couldn't read dictionary: $!";
my $um = OATHusers->new();
# Get the data
my $rec = $prop->{udp_data};
# Unpack it
my $p = RADIUS::Packet->new($dict, $rec);
if ($p->code eq 'Access-Request') {
# Print some details about the incoming request (try ->dump here)
#print $p->attr('User-Name'), " logging in with password ",
# $p->password($secret), "\n";
#$p->dump;
# Create a response packet
my $rp = new RADIUS::Packet $dict;
if($um->validate($p->attr('User-Name'), $p->password($secret))) {
$rp->set_code('Access-Accept');
print "Password OK\n";
} else {
$rp->set_code('Access-Reject');
print "Password FAIL\n";
}
$rp->set_identifier($p->identifier);
$rp->set_authenticator($p->authenticator);
# (No attributes are needed.. but you could set IP addr, etc. here)
# Authenticate with the secret and send to the server.
my $outpacket = auth_resp($rp->pack, $secret);
$prop->{'client'}->send($outpacket, 0);
#$s->sendto(auth_resp($rp->pack, $secret), $whence);
}
else {
# It's not an Access-Request
print "***** Unexpected packet type recieved. ******";
$p->dump;
}
}
package OATHusers;
use strict;
use warnings;
use Authen::OATH;
sub new {
my $class = shift;
my $self = bless {}, $class;
my %seeds = (
'user1' => {
key_id => '23874987234871234',
pin => '1234',
seed => uc('23d4cd23c4dc23d4cd23c4d2d34cd23c4dcd234cd'),
},
'user2' => {
key_id => '23452345234523455',
pin => '74747474',
seed => uc('eeeeeeeeeeeeeeeeeeeeeeeeeeeeee5555555555'),
},
);
$self->{seeds} = \%seeds;
return $self;
}
sub validate {
my ($self, $username, $password) = @_;
# Missing fields
if(!defined($username) || !defined($password) ||
$username eq '' || $password eq '') {
return 0;
}
# Unknown username
if(!defined($self->{seeds}->{$username})) {
return 0;
}
# For the easy part: Check length of password
if(length($password) != (length($self->{seeds}->{$username}->{pin}) + 6)) {
return 0;
}
my $oath = Authen::OATH->new(timestep => 60);
my $valid = 0;
my $now = time;
my $userseed = $self->{seeds}->{$username}->{seed};
for(my $i = -300; $i <= 300; $i += 60) {
my $totp = $oath->totp($userseed, $now + $i);
my $fullpass = $self->{seeds}->{$username}->{pin} . $totp;
if($fullpass eq $password) {
print "$username key drift $i\n";
$valid = 1;
last;
}
}
return $valid;
}