#!/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(); } }