PostAuthHook nightmares.

Steve Phillips steve at iconz.net
Mon Nov 25 19:20:13 CST 2002


Hey all, I have the following script (included below) that I use to assign
an IP address based upon a user "Class"

NAS --- Radius Proxy --- Radius Auth

                --- Auth File

Say a user logs in with "user at dingbat.com" they will get authed out of a
file, the PostAuthHook looks at the "Class" attribute that is set in the
Auth File and preforms a database lookup against that IP class pool,
allocates the user an IP, then updates the database tagging that IP against
this user.

This all seemed to work quite happily until I tried to do the same thing by
proxying across to a remote radius server (customer run so no control over
the attributes that are returned)

I added the ' AddToReplyIfNotExist Class = "fred.com" ' directive in the
AuthBy RADIUS clause, which appears to work quite happily, however, the
hook documentation seems to be rather lacking when it comes to discussing
radius proxy requests

Under the AuthBy FILE directive, the hook variables as set as such (for an
Access-Accept)

${$_[0]} is the current request
${$_[1]} is the reply packet -> nas
${$_[2]} is the auth result

The issue appears to be that the Auth Result for a file auth, and the Auth
Result for a radius proxy auth are not the same, in the file auth, we get a
$main::ACCEPT (${$_[2]} == 0) when the auth succeeds, with the radius
proxy, wether it is an Accept or a Reject we end up with a $main::IGNORE.

This obviously is an issue because it becomes difficult to allocate IP's
based upon an access accept, and not allocate when getting an access-reject.

Question is - what should I be testing against if it is not supposed to be
the Auth Result ? I need this to be able to work against both radius
authing and file authing, and what other gotcha's am I going to see later
on ? is there any fuller documentation than the goodies/hooks.txt list ?

Hope this makes some sense :-)

-- Script follows, I've since gone through and added much logging for debug
purposes --
-- The script is still in development obviously so please ignore any
discrepancies --

sub {

         use Mysql;

         my $dbuser      = 'someuser';
         my $dbpass      =
'thisisatopsecretdatabasepasswordthatwillneverappearonamailinglist';
         my $dbhost      = 'bigarse.database.server';

         my $dbh_ipalloc = undef;

         my $p           = ${$_[0]};     # Current Request
         my $rp          = ${$_[1]};     # reply packet to NAS
         my $ar          = ${$_[2]};     # Result of Auth
         my $rr          = ${$_[3]};     # Reject Reason

         # get the reply code from the proxy radius
         my $code = $p->code;

         # and a few other attributes
         my $class    = $rp->get_attr('Class');
         my $type     = $p->get_attr('Acct-Status-Type');
         my $actclass = $p->get_attr('Class');
         my $username = $p->get_attr('User-Name');

         &main::log($main::DEBUG, "ar        = $ar");
         &main::log($main::DEBUG, "ACCEPT    = $main::ACCEPT");
         &main::log($main::DEBUG, "REJECT    = $main::REJECT");
         &main::log($main::DEBUG, "IGNORE    = $main::IGNORE");
         &main::log($main::DEBUG, "code      = $code");
         &main::log($main::DEBUG, "Username  = $username");
         &main::log($main::DEBUG, "Type      = $type");
         &main::log($main::DEBUG, "Class     = $class");
         &main::log($main::DEBUG, "AcctClass = $actclass");
         if (($ar == $main::ACCEPT) || ($ar == $main::IGNORE))
         {

                 # delete any framed-ip or netmask
                 $rp->delete_attr('Framed-IP-Address');
                 $rp->delete_attr('Framed-IP-Netmask');

                 my ($user, $realm) = split /@/, $username, 2;

                 if (!$realm) { $realm = $class; }
                 if (!$class) { $class = $realm; }

                 $username = $user . '@' . $realm;

                 my $table =  $class;
                    $table =~ s/\./_/g;
                    $table = 'tb_ipAlloc_' . $table;

                 &main::log($main::DEBUG, "Table used : $table");
                 &main::log($main::DEBUG, "UserName   : $username");

                 # open a databse connection
                 if ($dbh_ipalloc = Mysql->connect($dbhost, undef, $dbuser,
$dbpass)) {
                         $dbh_ipalloc->selectdb('data');
                 } else {
                         &HandleError ("connect failed");
                 }

                 # construct the SQL query to get the IP address

                 my $SQL = "SELECT ip FROM $table WHERE name = '$username'";
                 &main::log($main::DEBUG, "SQL        : $SQL");

                 my $sth = $dbh_ipalloc->query($SQL);
                 my $totalRows = $sth->numrows;

                 &main::log($main::DEBUG, "Rows       : $totalRows");

                 if (!$totalRows) {
                         # There were no rows returned, this means that
                         # there were no instances of this user logging in
already
                         # Lets try and get the next available IP address
from the table

                         $SQL = "SELECT ip FROM $table WHERE name IS NULL
LIMIT 1";
                         &main::log($main::DEBUG, "SQL        : $SQL");
                         my $sth = $dbh_ipalloc->query($SQL);

                         my $returnedIP = $sth->numrows;
                         &main::log($main::DEBUG, "Rows       :
 $returnedIP");

                         if (!$returnedIP) {
                                 # there were no IP's returned, we have run
out !
                                 # set the access code to $main::REJECT and
                                 # set the reject reason to return to the NAS
                                 my $rejectReason = 'No more ports left to
allocate';
                                 &main::log($main::DEBUG, "Reject     :
$rejectReason");
                                 # $ar = $main::REJECT;
                                 # $rp->change_attr('Reply-Message' ,
$rejectReason);
                                 ${$_[2]} = $main::REJECT;
                                 ${$_[1]}->change_attr('Reply-Message' ,
$rejectReason);
                         } else {
                                 # user not already in table, ip addresses
free to allocate
                                 # lets allocate one and update the table
to reflect this
                                 my $ip = $sth->fetchrow;
                                 &main::log($main::DEBUG, "Allocated  :
 $ip"); $rp->add_attr('Framed-IP-Address', $ip);

                                 $SQL = "UPDATE $table SET name =
'$username' WHERE ip = '$ip'";
                                 &main::log($main::DEBUG, "SQL        :
 $SQL"); my $sth = $dbh_ipalloc->query($SQL); }
                 } else {
                         # we had a row returned, this means that the user
exists and an
                         # IP has been allocated, so we need to grab this
IP and re-allocate
                         # it to this user (we do this to account for
missed stop records
                         # and IPNet going nuts and using up our entire IP
pool !

                         # careful here, we should only ever get one result
back so it
                         # should be ok.
                         my $ip = $sth->fetchrow;
                         &main::log($main::DEBUG, "Allocated  : $ip");
                         $rp->add_attr('Framed-IP-Address', $ip);


                 }


         }
         if ($type eq 'Stop')
         {
                 # we have a Stop record, so we will need to update the DB
                 # and remove any allocated IP's so the port is freed up
                 #
                 # As the Class variable is set on the original packet, not
                 # the reply packet (accounting request) we have a new
 variable # that stores the "Class" attribute.

                 my $ip     = $p->get_attr('Framed-IP-Address');
                 my ($user, $realm) = split /@/, $username, 2;

                 if (!$realm) { $realm = $actclass; }
                 $username = $user . '@' . $realm;

                 my $table =  $actclass;
                    $table =~ s/\./_/g;
                    $table = 'tb_ipAlloc_' . $table;

                 &main::log($main::DEBUG, "Table used : $table");
                 &main::log($main::DEBUG, "Type       : $type");
                 &main::log($main::DEBUG, "UserName   : $username");
                 &main::log($main::DEBUG, "IP         : $ip");

                 # open a databse connection
                 if ($dbh_ipalloc = Mysql->connect($dbhost, undef, $dbuser,
$dbpass)) {
                         $dbh_ipalloc->selectdb('data');
                 } else {
                         &HandleError ("connect failed");
                 }

                 # construct the SQL query to get the IP address

                 my $SQL = "UPDATE $table SET name = NULL WHERE ip = '$ip'";
                 &main::log($main::DEBUG, "SQL        : $SQL");

                 my $sth = $dbh_ipalloc->query($SQL);

         }
}


--
Steve
Systems Admin, ICONZ

-------------------------------------------------------

-- 
Mike McCauley                               mikem at open.com.au
Open System Consultants Pty. Ltd            Unix, Perl, Motif, C++, WWW
24 Bateman St Hampton, VIC 3188 Australia   http://www.open.com.au
Phone +61 3 9598-0985                       Fax   +61 3 9598-0955

Radiator: the most portable, flexible and configurable RADIUS server 
anywhere. SQL, proxy, DBM, files, LDAP, NIS+, password, NT, Emerald, 
Platypus, Freeside, TACACS+, PAM, external, Active Directory, EAP, TLS, 
TTLS etc on Unix, Windows, MacOS etc.

===
Archive at http://www.open.com.au/archives/radiator/
Announcements on radiator-announce at open.com.au
To unsubscribe, email 'majordomo at open.com.au' with
'unsubscribe radiator' in the body of the message.


More information about the radiator mailing list