12. Adding custom AuthBy modules

Radiator provides an easy way of plugging in and integrating additional custom AuthBy modules. This allows you to use Radiator to authenticate from and store accounting information to other databases and storage types not currently supported by Radiator. You will need to be a competent Perl programmer in order to implement custom AuthBy modules.
Custom modules are usually written in Perl. They are automatically loaded when a matching <AuthBy ...> clause is read in the configuration file. Details on how to load, configure and implement a custom AuthBy module are described in this section.
There is a simple AuthBy module called AuthTEST.pm included in the RADIUS directory. It implements the <AuthBy TEST> clause, and would be a good starting point if you want to write your own custom AuthBy module. You should copy it to a new name and modify the copy to suit your needs.

12.1. Loading and configuring

When radiusd sees an <AuthBy XYZ> clause while parsing a Realm or Handler, it will load the file Radius/AuthXYZ.pm with a Perl require. AuthXYZ.pm is expected to be a Perl package that implements the class Radius::AuthXYZ. Realm.pm will then instantiate a new Radius::AuthXYZ by calling the constructor with Radius::AuthXYZ->new($file). The constructor is expected to create an instance of Radius::AuthXYZ that can handle all the requests for that type of authentication.
The constructor is passed a filehandle $file, which is the configuration file being currently read. The constructor is expected to configure its instance from lines read from the configuration file up until it sees a </AuthBy> line. This is made very easy by the Radius::Configurable class, which your AuthBy module should inherit from.
If the Radius::AuthXYZ constructor fails, it is expected to return undef.

12.2. Handling Requests

After construction and initialisation, your instance will be called upon to handle requests that are sent to it. For each request received, the Handler.pm module will call ($result, $reason) = Radius::AuthXYZ->handle_request($p, $rp), where $p is a reference to the Radius::Radius packet received from the client, and $rp is an empty reply packet, ready for you to fill. Client.pm and Handler.pm will filter out duplicate requests, requests from unknown clients and requests with bad authenticators, so your handle_request will only be called for new, good requests from known clients. The contents of the request will have been unpacked into the Radius::Radius instance passed in as $p, so you can immediately start examining attributes and doing things.
handle_request returns an array. The first element is a result code, and the second is an optional reason message.
The result code from handle_request will indicate whether Handler.pm should automatically send the reply to the original requester:
  • If handle_request returns $main::ACCEPT, Handler.pm will send back $rp as an accept message appropriate to the type of request received (i.e. it will turn $rp into an Access-Accept if the original request was an Access-Request). In the case of Accounting-Request, this is the only result code that will cause a reply to be sent back.
  • If handle_request returns $main::REJECT, Handler.pm will send back $rp as a reject message appropriate to the type of request received (i.e. it will turn $rp into an Access-Reject if the original request was an Access-Request). In this case the reason message should be supplied.
  • If handle_request returns $main::CHALLENGE, Handler.pm will send back $rp as a Access-Challenge message.
  • If handle_request returns $main::IGNORE, Handler.pm will not send any reply back to the originating client. You should only use this if the request is to be completely ignored, or if your module undertakes to send its own reply some time in the future. If the Handler or Realm has more than one AuthBy handler module specified, it will continue calling handlers in the order in which they were specified until one returns something other than $main::IGNORE. You can change this behaviour with AuthByPolicy, for more information, see Section 3.31.12. AuthByPolicy.
Your handle_request function may want to use utility functions in Radius::Radius (see Radius.pm) to examine attributes in the incoming request, and to construct replies. There are some convenience routines in Client.pm for packing and sending replies to the original requester, such as
$p->{Client}->replyTo($rp, $p);
If your handler cannot successfully handle the request, perhaps due to some unforeseen event, software failure, system unavailability etc., it is common to not reply at all to the original request. This will usually force the original NAS to retransmit to another server as a fallback. You can do this by returning $main::IGNORE from handle_request.

12.3. AuthGeneric

This is a generic authentication module superclass. It implements behaviour that will be required in most authentication modules, and therefore most modules should inherit from it. Most simple authentication can be handled merely by overriding the findUser() method, which is expected to find and return a User object given a user name. The AuthGeneric::handle_request handles all the cascading of DEFAULT users, checking of check items, assembling replies etc. All you have to do is find the user in your database and return it in findUser().
AuthGeneric only responds to Access-Request messages. Accounting-Requests are accepted but ignored (i.e. it does nothing with them). If you want to do something with accounting messages (other than what Realm.pm does, such as logging to the accounting log file or wtmp file), you will probably want to override handle_request, pass Access-Request messages to $self->SUPER::handle_request(), and process Accounting- Request messages yourself.
If your handler needs to fork so it can do a “slow” authentication or accounting task, you can call AuthGeneric::handlerFork, which will arrange for the handler to fork(2), and also arrange for the child to exit after handling is complete.
AuthGeneric has a number of other methods that you can override for specific functions like:
  • log($p, $s) Logs a message that originated in that class. $p is a message priority (see Log.pm). $s is a message. The base class behaviour is to log to the all the logging systems configured into Radiator by calling main::log($p, $s).

12.4. Step-by-step

Assuming you want to create a custom AuthBy module called XYZ, these are the basic steps:
  • Figure out what parameters your new module needs from the Radiator configuration file to configure its behaviour.
  • Copy AuthTEST.pm to AuthXYZ.pm.
  • Replace all instances of TEST in AuthXYZ.pm with XYZ.
  • Implement the keyword function so it parses the parameters that your new module needs.
  • If your module needs any sub-objects, implement the object function to parse out any sub-objects (see example code in Radius::Realm->object).
  • Implement the handle_request function. You may wish to handle Access-Requests and Accounting-Requests differently.
  • Add a test realm to the configuration file with something like:
    <Realm xxxxxx>
          RealmParameter1 ....
          <AuthBy XYZ>
                AuthByParameter1 ....
                .....
          </AuthBy>
    </Realm>
  • Restart or SIGHUP Radiator.
  • Test your new module by sending requests to the server with the radpwtst utility.
  • Install your new module with make install.
If your authentication method is simple and synchronous, your class only has to override the following methods in AuthGeneric:
  • new Create a new instance by calling $class->SUPER::new($file);
  • keyword Recognise any class specific parameter keywords from the configuration file and remember them.
  • findUser Construct and return a User object if the named user can be found in your database.

12.5. Class Hierarchy

This section is only of interest to developers who plan to build new Radiator classes. It shows the class inheritance hierarchy of all the classes provided with Radiator.

Figure 21. Radiator Class inheritance hierarchy

inheritance.png