143 lines
4.7 KiB
Perl
143 lines
4.7 KiB
Perl
|
#!/usr/bin/perl
|
||
|
|
||
|
use Net::Radius::Dictionary;
|
||
|
use Net::Radius::Packet;
|
||
|
use Net::LDAP::Util;
|
||
|
use Net::Inet;
|
||
|
use Net::LDAP;
|
||
|
use Net::UDP;
|
||
|
use warnings;
|
||
|
use Socket;
|
||
|
use strict;
|
||
|
use Fcntl;
|
||
|
|
||
|
# This is a simple RADIUS authentication server which accepts
|
||
|
# any user whose User-Name and Password validiate via LDAP
|
||
|
|
||
|
# NOTE - This server must be run as root on systems with shadow passwords.
|
||
|
|
||
|
# $Id: radius2ldap.pl 7 2003-01-08 03:42:41Z lem $
|
||
|
|
||
|
my $testing = 0; # set non-zero if testing
|
||
|
my %hostname_secret = ('rad1' => 'secret1', 'rad2' => '2secret',
|
||
|
'testhost' => 'testsecret');
|
||
|
my %host_secret; #same as above translated to 4 byte address keys
|
||
|
foreach my $host(keys %hostname_secret) {
|
||
|
$host_secret{inet_aton($host)} = $hostname_secret{$host};
|
||
|
}
|
||
|
my $uselogfile = "/var/log/rad2ldaplog";
|
||
|
my $errlogf = "/var/log/rad2ldaperrs";
|
||
|
open ERRLOG, ">>$errlogf";
|
||
|
print ERRLOG "Started ",scalar(localtime()),"\n";
|
||
|
close ERRLOG;
|
||
|
my $ldap;
|
||
|
connect_and_bind();
|
||
|
# Parse the RADIUS dictionary file
|
||
|
my $dict = new Net::Radius::Dictionary "/usr/local/lib/radius.dictionary"
|
||
|
or die "Couldn't read dictionary: $!";
|
||
|
|
||
|
# Set up the network socket (must have radius in /etc/services)
|
||
|
my $s = new Net::UDP { thisservice => "radius" } or die $!;
|
||
|
$s->bind or die "Couldn't bind: $!";
|
||
|
$s->fcntl(F_SETFL, $s->fcntl(F_GETFL,0) | O_NONBLOCK)
|
||
|
or die "Couldn't make socket non-blocking: $!";
|
||
|
|
||
|
# Loop forever, receiving packets and replying to them
|
||
|
while (1) {
|
||
|
my ($rec, $whence);
|
||
|
# Wait for a packet
|
||
|
my $nfound = $s->select(1, 0, 1, undef);
|
||
|
if ($nfound > 0) {
|
||
|
# Get the data
|
||
|
$rec = $s->recv(undef, undef, $whence);
|
||
|
my $fromname = inet_ntoa(substr($whence,4,4));
|
||
|
print "from $fromname " if $testing;
|
||
|
# Unpack it
|
||
|
my $p = new Net::Radius::Packet $dict, $rec;
|
||
|
if ($p->code eq 'Access-Request') {
|
||
|
open LOG, ">>$uselogfile";
|
||
|
# Print some details about the incoming request (try ->dump here)
|
||
|
print $p->attr('User-Name'), " attempting login with password ",
|
||
|
$p->password($host_secret{substr($whence,4,4)}), "\n" if $testing;
|
||
|
print LOG $p->attr('User-Name');
|
||
|
# Initialize the response packet we'll send back
|
||
|
my $rp = new Net::Radius::Packet $dict;
|
||
|
$rp->set_identifier($p->identifier);
|
||
|
$rp->set_authenticator($p->authenticator);
|
||
|
# Check against the authorization source passwd file
|
||
|
if (check_pass($p->attr('User-Name'),
|
||
|
$p->password($host_secret{substr($whence,4,4)}))) {
|
||
|
$rp->set_code('Access-Accept');
|
||
|
print LOG " OK ";
|
||
|
}
|
||
|
else {
|
||
|
print "Invalid login.\n" if $testing;
|
||
|
$rp->set_code('Access-Reject');
|
||
|
$rp->set_attr('Reply-Message', "\r\nInvalid login.\r\n");
|
||
|
print LOG " bad ";
|
||
|
}
|
||
|
# Authenticate with the secret and send to the server.
|
||
|
$s->sendto(auth_resp($rp->pack,
|
||
|
$host_secret{substr($whence,4,4)}), $whence);
|
||
|
print LOG scalar(localtime()),"\n";
|
||
|
close LOG;
|
||
|
}
|
||
|
else {
|
||
|
# It's not an Access-Request
|
||
|
print "Unexpected packet type recieved." if $testing;
|
||
|
$p->dump;
|
||
|
open ERRLOG, ">>$errlogf";
|
||
|
print ERRLOG "Bad packet type received ",localtime(),"\n";
|
||
|
close ERRLOG;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
sub connect_and_bind {
|
||
|
# make a connection to an LDAP server and bind to it.
|
||
|
$ldap->unbind if $ldap;
|
||
|
$ldap = Net::LDAP->new("ldaphost1.dirplace.com");
|
||
|
$ldap = Net::LDAP->new("ldaphost2.dirplace.com") unless $ldap;
|
||
|
$ldap = Net::LDAP->new("ldapbackup.elsewhere.com") unless $ldap;
|
||
|
die "$@" unless $ldap;;
|
||
|
$ldap->bind (version => 3) or die $@;
|
||
|
}
|
||
|
|
||
|
sub check_pass {
|
||
|
# Function to check name and password. Returns undef if no such user.
|
||
|
my ($login, $password) = @_;
|
||
|
return undef unless $password;
|
||
|
my $retries = 0;
|
||
|
while (1) {
|
||
|
return undef if $retries > 2;
|
||
|
my $mesg = $ldap->search(base => "o=myorg",
|
||
|
filter => "(uid=$login)",
|
||
|
attrs => ["sn"]);
|
||
|
# login doesn't exist
|
||
|
return undef
|
||
|
if (Net::LDAP::Util::ldap_error_name($mesg->code)
|
||
|
eq "LDAP_NO_SUCH_OBJECT" ||
|
||
|
(($mesg->code == 0) and ($mesg->count() != 1)));
|
||
|
if ($mesg->code) {
|
||
|
++$retries;
|
||
|
print "retry search due to ", Net::LDAP::Util::ldap_error_name($mesg->code),"\n" if $testing;
|
||
|
open ERRLOG, ">>$errlogf";
|
||
|
print ERRLOG "retry search due to ", Net::LDAP::Util::ldap_error_name($mesg->code),scalar(localtime()),"\n";
|
||
|
close ERRLOG;
|
||
|
connect_and_bind();
|
||
|
next;
|
||
|
}
|
||
|
my $entry = $mesg->entry(0);
|
||
|
my $dn = $entry->dn;
|
||
|
# print "Dn is $dn\n" if $testing;
|
||
|
$mesg = $ldap->bind (dn => $dn, password => $password, version => 3) ;
|
||
|
return 0 if Net::LDAP::Util::ldap_error_name($mesg->code) eq "LDAP_INVALID_CREDENTIALS";
|
||
|
return 1 if $mesg->code == 0;
|
||
|
++$retries;
|
||
|
print "retry auth due to", Net::LDAP::Util::ldap_error_name($mesg->code),"\n" if $testing;
|
||
|
connect_and_bind();
|
||
|
|
||
|
}
|
||
|
}
|