I was previously running straight 2.15 (and perl 5.6.0, incidentally), except for a single patch to Radius.pm that allows it to send responses to NASes correctly even when ipaddresses and netmasks were retrieved from Auth modules in BINARY format (i.e. 4-ascii-character).  (BTW, thanks for sticking that request into 2.16, Mike - though I'm happy to say that I've learned a lot of perl, and I could now parse it in PostAuthHook, if I had to :D ).
 
My config is below. I had been letting Radiator use its internal session database, though I don't really need the session limits at this time (I'll do it with SQL when I overcome a problem unrelated to this message). Everything was working great. I installed 2.16, and restarted radiusd, and immediately, nobody was able to log in. Enabling Trace 4 and a glance at the debug log showed that everyone was being denied because "session limit of 1 exceeded" (I may have the exact phrasing wrong).
 
Well, I wasn't sure what might have changed between 2.15 and 2.16 that would break my config, but having read the release notes, I was sure I knew how to work around it quickly and give myself time to figure it out. I quickly added the <SessionDatabase NULL> lines you see at the bottom of the config (no, my comments there aren't actually in the config, I only put those in this message to help illustrate) and restarted radiusd.
 
Much to my chagrin, the problem didn't go away. I then added the MaxSessions line and restarted again. Still no improvement. Well, I had been down for about three minutes at this point, which isn't good, so I backed out and reinstalled 2.15 (putting Mike's patched Radius.pm back in place) and restarted again (I didn't even take the MaxSessions or the <SessionDatabase NULL> out of the config). Ahhh...all better. I let everything hum happily for 15 or 20 minutes before testing a bit more.
 
I reinstalled 2.16, restarted, and everything broke again exactly as it had before. I reinstalled 2.15, restarted, and everything was happy again. That last cycle was just to be sure I hadn't inadvertently broken something in the config myself, so I did these steps without even touching it.
 
So, Mike, Hugh, or anyone: any ideas what I may be doing that's incompatible with 2.16? It must be something unusual, because I haven't heard anyone else screaming about this problem.
AuthPort                1812
AcctPort                1813
BindAddress             209.xxx.xxx.xxx
LogDir                  /var/log/radius
DbDir                   /usr/local/etc/radius.cfg.d
PidFile                 /var/run/radiusd.pid
LivingstonOffs          22
LivingstonHole          1
Trace                  4



# Username rewriting rules
# First, downcase the whole thing
RewriteUsername         tr/A-Z/a-z/
# Remove all white space
RewriteUsername         s/\s+//g
# For Mailsite interoperability, change %'s to @'s
RewriteUsername         tr/%/@/
# Convert Microsoft domain syntax to Internet
RewriteUsername         s/(^.*)[\/\\]+(.*)/$2\@$1/
# Change realm txdirect* or idworld* (with no suffix or any suffix that starts with a .) to realm idworld.net
RewriteUsername         s/[\@\.]+(?:idworld|txdirect)(?:\..*$|$)/\@idworld.net/
# If no realm, make it @idworld.net
RewriteUsername         s/^([^@]*)$/$1\@idworld.net/

include %D/clients.cfg

# For testing from localhost
<Client 209.xxx.xxx.xxx>
  Secret xxxxxx
  NasType NortelCVX1800
</Client>

PreClientHook file:"%D/PreClientHook.pl"

<Realm DEFAULT>
MaxSession 9999999 <---- (2nd thing I did)
  PreAuthHook file:"%D/PreAuthHook.pl"
  PostAuthHook file:"%D/PostAuthHook.pl"
  <AuthBy SQL>
    DBSource dbi:mysql:idaaa
    DBUsername radiusd
    DBAuth xxxxxxxx
    AuthSelect                                          \
      select                                            \
        password,                                       \
        status,                                         \
        find_in_set('dialup',services),                 \
        simultaneoususe,                                \
        ipaddress,                                      \
        netmask,                                        \
        dialuptype,                                     \
        portlimit,                                      \
        checkitems,                                     \
        replyitems                                      \
      from subscribers where                            \
        mailbox                       = '%U'     and    \
        domain                        = '%R'
    AuthColumnDef 0, User-Password, check
    AuthColumnDef 1, IDI-Status, reply
    AuthColumnDef 2, IDI-Dialup-Enabled, reply
    AuthColumnDef 3, Simultaneous-Use, check
    AuthColumnDef 4, Framed-IP-Address, reply
    AuthColumnDef 5, Framed-IP-Netmask, reply
    AuthColumnDef 6, IDI-Dialup-Type, reply
    AuthColumnDef 7, Port-Limit, reply
    AuthColumnDef 8, GENERIC, check
    AuthColumnDef 9, GENERIC, reply
    AcctSQLStatement                                    \
      insert into accounting set                        \
        mailbox        = '%U',                          \
        domain         = '%R',                          \
        timestamp      = from_unixtime(%{Timestamp}),   \
        statustype     = '%{IDI-Acct-Status-Type-Int}', \
        ipaddress      = %{IDI-SQL-Framed-IP-Address},  \
        inputoctets    = '%{Acct-Input-Octets}',        \
        outputoctets   = '%{Acct-Output-Octets}',       \
        sessionid      = '%{Acct-Session-Id}',          \
        sessiontime    = '%{Acct-Session-Time}',        \
        terminatecause = '%{IDI-Terminate-Cause}',      \
        nasipaddress   = %{IDI-SQL-NAS-IP-Address},     \
        nasport        = '%{NAS-Port}',                 \
        nasporttype    = '%{IDI-NAS-Port-Type-Int}',    \
        servicetype    = '%{IDI-Service-Type-Int}',     \
        callednumber   = '%{Called-Station-Id}',        \
        callingnumber  = '%{Calling-Station-Id}'
  </AuthBy>
</Realm>


<SessionDatabase NULL> <---- (1st thing I did)
</SessionDatabase> <---- (1st thing I did)
And here are hooks:
PreClientHook:
# Our PreClientHook's job is to extrapolate a few more bits of info to
#   facilitate logging, storing them in some pseudo-attributes. Note that
#   Acct-Terminate-Cause and Ascend-Disconnect-Cause are combined into one
#   and that we shift A-D-C up 8 bits so we can tell them apart when
#   examining our logs.
sub {
  &main::log($main::LOG_DEBUG, "Entering PreClientHook");
  my $request = ${$_[0]};
  if ($request->code eq 'Accounting-Request') {
    &main::log($main::LOG_DEBUG, "Adding IDI pseudo-attributes to accounting request");
    $request->add_attr('IDI-Terminate-Cause',
        ($request->{Dict}->valNameToNum('Acct-Terminate-Cause', $request->get_attr('Acct-Terminate-Cause'))) |
        ($request->{Dict}->valNameToNum('Ascend-Disconnect-Cause', $request->get_attr('Ascend-Disconnect-Cause')) << 8));
    $request->add_attr('IDI-Acct-Status-Type-Int',
        $request->{Dict}->valNameToNum('Acct-Status-Type', $request->get_attr('Acct-Status-Type')));
    $request->add_attr('IDI-NAS-Port-Type-Int',
        $request->{Dict}->valNameToNum('NAS-Port-Type', $request->get_attr('NAS-Port-Type')));
    $request->add_attr('IDI-Service-Type-Int',
        $request->{Dict}->valNameToNum('Service-Type', $request->get_attr('Service-Type')));
    my $octets = $request->getNasId();
    $octets =~ tr/\./,/;
    $request->add_attr('IDI-SQL-NAS-IP-Address', "char($octets)");
    $octets = $request->get_attr('Framed-IP-Address');
    $octets =~ tr/\./,/;
    $octets or $octets = '0,0,0,0';
    $request->add_attr('IDI-SQL-Framed-IP-Address', "char($octets)");
  }
  &main::log($main::LOG_DEBUG, "Exiting PreClientHook");
}
PreAuthHook:
# This hook is a kludge to make things compatible with MailSite's authentication
#   from the same database. Since we want everyone to be able to use their email
#   address as their login, and MailSite understands that some of our SPECIFIC
#   domains are considered synonyms of our main domain, idworld.net, we have to
#   make Radiator understand that, too.
sub {
  &main::log($main::LOG_DEBUG, "Entering PreAuthHook");
  my $request = ${$_[0]};
  my $reply = ${$_[1]};
  if ($request->code eq 'Access-Request') {
    my ($mailbox, $domain) = split(/@/, $request->getAttrByNum($Radius::Radius::USER_NAME),2);
    if (-e "/usr/local/etc/idworld-synonyms/$domain") {
      $domain = 'idworld.net';
      $request->changeAttrByNum($Radius::Radius::USER_NAME, "$mailbox\@$domain");
      &main::log($main::LOG_DEBUG, "Rewrote user name to $mailbox\@$domain (synonym)");
    }
  }
  &main::log($main::LOG_DEBUG, "Exiting PreAuthHook");
}
PostAuthHook:
# This PostAuthHook accomplishes three things: 1. It extrapolates some
#   implicit timeouts based on the subscriber's dialup 'type' (unlimited,
#   metered, or dedicated) and sticks those in the reply packet; 2. It
#   notices if the NAS is an Ascend NAS and converts several attributes
#   to Ascend's syntax (including the aforementioned timeouts) and adds
#   a couple more that Ascends seem to need. We've been doing this for
#   years, and perhaps Ascend has corrected this incompatibility, but I
#   don't feel like finding out, and it WORKS this way; 3. It checks
#   a couple of other requirements before accepting an Access-Request:
#   The customer must have a status of 'active' (as opposed to 'locked'
#   or 'cancelled'), and must be enabled for 'dialup' service (remember,
#   we use this same database for authentication of other services, such
#   as email-only accounts). Otherwise, change the reply to a deny and
#   strip the unneeded attributes.
sub {
  &main::log($main::LOG_DEBUG, "Entering PostAuthHook");
  my $request = ${$_[0]};
  my $reply = ${$_[1]};
  my $denied = ${$_[2]};
  if (not $denied and $request->code eq 'Access-Request') {
    my $status = $reply->get_attr('IDI-Status');
    my $dialup = $reply->get_attr('IDI-Dialup-Enabled');
    $reply->delete_attr('IDI-Status');
    $reply->delete_attr('IDI-Dialup-Enabled');
    if ($status eq 'active' and $dialup) {
      &main::log($main::LOG_DEBUG,
          "Extrapolating timeouts for Access-Accept");
      $reply->change_attr('Idle-Timeout', 900);
      $reply->change_attr('Session-Timeout', 36000);
      if ($reply->get_attr('IDI-Dialup-Type') eq 'metered') {
        $reply->change_attr('Session-Timeout', 0);
      } elsif ($reply->get_attr('IDI-Dialup-Type') eq 'dedicated') {
        $reply->change_attr('Idle-Timeout', 0);
        $reply->change_attr('Session-Timeout', 0);
      }
      if ($request->{Client}->{NasType} =~ /^Ascend/) {
        &main::log($main::LOG_DEBUG,
            "Converting Access-Accept for NasType Ascend");
        $reply->change_attr('Ascend-Idle-Limit',
            $reply->get_attr('Idle-Timeout'));
        $reply->delete_attr('Idle-Timeout');
        $reply->change_attr('Ascend-Maximum-Time',
            $reply->get_attr('Session-Timeout'));
        $reply->delete_attr('Session-Timeout');
        $reply->change_attr('Ascend-Maximum-Channels',
            $reply->get_attr('Port-Limit'));
        $reply->delete_attr('Port-Limit');
        $reply->change_attr('Ascend-PPP-Address',
            $reply->get_attr('Ascend-PPP-Address') || '209.142.75.128');
        $reply->change_attr('Ascend-Route-IP',
            $reply->get_attr('Ascend-Route-IP') || 'Route-IP-Yes');
      }
    } else {
      ${$_[2]} = $denied = 1;
      $reply->delete_attr('Framed-IP-Address');
      $reply->delete_attr('Framed-IP-Netmask');
      $reply->delete_attr('Port-Limit');
      my $message = 'Access rejected for ' .
          $request->get_attr('User-Name') . ': ';
      if ($dialup) {
        $message .= "\u$status";
      } else {
        $message .= 'Not enabled for dialup service';
      }
      &main::log($main::LOG_INFO, $message);
    }
  }
  $reply->delete_attr('IDI-Dialup-Type');
  &main::log($main::LOG_DEBUG, "Exiting PostAuthHook");
}
Thanks in advance for any help any of you might be able to provide.
 
Mike Nerone

Reply via email to