In order to successfully put OpenRADIUS to use, the best thing is to first install it with the example configuration, to test it, and then to adapt it to suit your needs.
Here is a short summary of the steps you'll need to take.
If you need to change the location of the 'raddb' directory to something other than /usr/local/etc/raddb, you also have to configure 'raddb/configuration' to reflect this on command lines of some of the external modules specified in that file. But you can do this later as well.
Not counting the dictionary, OpenRADIUS itself uses only two configuration files:
The configuration file actually uses the same language as the behaviour file, which is described here.
Both are compiled once, when the server is started; the difference is that the configuration file is also actually executed immediately after compiling, causing the network sockets to be created and the interface modules to be started. (The behaviour file, on the other hand, is executed for each incoming request.)
Defining sources to listen on and callable interfaces for use in the behaviour file, is done using two magic callable 'interfaces' that are defined internally when the configuration file is compiled and executed: 'source', and 'interface'.
Other than their special purpose (to add sources and interfaces), they only differ from the normal callable interfaces that you create here in that they completely empty the request- and reply lists after each call. (This is done purely for convenience; otherwise you'd have to remember to do at least a 'delall sendattr, delall recvattr' between each interface that you create - see below why).
Each call to the pseudo-interface 'source' defines one or more sockets to listen on, based on the instances of the 'addr' and 'port' attributes that you have put on the request list before calling it. In fact, each instance of 'port' adds a new socket definition; the 'addr' specifications are optional and are put in the socket definitions only after they have been created for this source.
Some examples of using 'source' that illustrates this behaviour:
# The following few examples all define two # sockets, one listening on 172.16.1.1:1645 # and the other on port 1812 for all addresses: source(addr=172.16.1.1, port=1645, addr=0.0.0.0, port=1812), # which is the same as: source(port=1645, addr=172.16.1.1, port=1812, addr=0.0.0.0), # which is the same as: source(port=1645, addr=172.16.1.1, port=1812), # which is the same as: source(port=1645, port=1812, addr=172.16.1.1, addr=0.0.0.0), # which is of course the same as: source(port=1645, port=1812, addr=172.16.1.1), # but not the same as: source(port=1812, port=1645, addr=172.16.1.1), # although it *is* the same as: source(port=1812, port=1645, addr=0.0.0.0, addr=172.16.1.1), # which is also the same as source(addr=172.16.1.1, port=1645), source(addr=0.0.0.0, port=1812), # which is the same as source(port=1645, addr=172.16.1.1), source(port=1812, addr=0.0.0.0), # which is the same as source(port=1645, addr=172.16.1.1), source(port=1812), # which is the same as source(port=1812), source(port=1645, addr=172.16.1.1), # Enfin, you get the idea ;)
Each call to the pseudo-interface 'interface' defines a new external interface that can be used in the behaviour file. The following attributes are meaningful to 'interface':
At least one instance is required; if you specify multiple subprocesses, then each time the interface is called, the server looks in round-robin fashion for one that is idle. If all are busy, the job put in a queue for this interface; the first subprocess that becomes idle after that will immediately take the call from the queue.
This provides simple load sharing, database connection pooling, takes
advantage of SMP and allows the modules themselves to be extremely
simple as they only have to worry about one request at a time. See the
module interface documentation
for more details.
The first three define logging interfaces that all use the radlogger module, but each logs a different set of attributes (the 'sendattr'-lists) to a different output file, as specified on the module's command line ('prog').
The fourth interface is a dummy one for testing the module support (uses the 'cat' command as a module that echoes all requests as responses). This works both for ASCII and binary interface types.
The fifth defines an interface that uses the 'radldap' module to connect to a replicated LDAP tree that is available on two different machines that are to be queried in round-robin mode.
interface (name="Stdlogger", sendattr="RAD-Code", sendattr="User-Name", sendattr="User-Password", sendattr="Calling-Station-Id", sendattr="Called-Station-Id", sendattr="NAS-Port", sendattr="NAS-Port-Type", sendattr="NAS-IP-Address", sendattr="NAS-Identifier", sendattr="str", prog="radlogger /tmp/radaccess.log", flags=Ascii + Short-Attr + Named-Const + Double-Backslash), interface (name="Errorlogger", sendattr="str", prog="radlogger /tmp/raderrs.log", flags=Ascii + Short-Attr + Double-Backslash), interface (name="Acctlogger", sendattr="str", sendattr="User-Name", sendattr="Calling-Station-Id", sendattr="Called-Station-Id", sendattr="NAS-Port", sendattr="NAS-Port-Type", sendattr="NAS-IP-Address", sendattr="NAS-Identifier", sendattr="Acct-Status-Type", sendattr="Acct-Session-Id", sendattr="Record-Unique-Key", sendattr="Session-Key", sendattr="Acct-Input-Octets", sendattr="Acct-Output-Octets", sendattr="Acct-Input-Packets", sendattr="Acct-Output-Packets", sendattr="Acct-Terminate-Cause", sendattr="Timestamp", sendattr="Acct-Delay-Time", sendattr="Acct-Session-Time", sendattr="Acct-Authentic", prog="radlogger /tmp/radacct.log", recvattr="int", flags=Ascii + Short-Attr + Named-Const + Double-Backslash, timeout=5), interface (name="Loopback", prog="/bin/cat", prog="/bin/cat", prog="/bin/cat", timeout=1), interface (name="Ldapdb", sendattr="User-Name", sendattr="User-Password", sendattr="str", # Lines wrapped and concatenated with '.' only for # brevity here. Not really needed. prog="radldap -b cn=radius,ou=People," . "dc=my,dc=dom,dc=tld " . " -s ldappassword 192.168.5.26", prog="radldap -b cn=radius,ou=People," . "dc=my,dc=dom,dc=tld " . " -s ldappassword 192.168.6.33", timeout=25),
Tutorial not yet written. See the language reference for now.
Some hints: the server works by putting everything that it knows about an incoming request on a so-called REQUEST list of attribute/value pairs, and prepares an empty list, the REPLY list. Together with an expression execution context, these three things are called a 'job'.
After a new job is created (because a new request came in), the server will execute the compiled behaviour expression. You can use it to perform any operation on any instance of any pair on either list.
The expression is ran until it finishes (because of the 'abort' operator which causes the request to be dropped, the end of the expression or the 'halt' operator, which causes a response to be encoded and sent based on the contents of the REPLY list), or until it makes a call to an interface that's defined in the configuration file.
When that happens, the server builds a request message for the module using the current REQUEST list and sends it to the module. When the answer comes in, all attributes allowed in according to the recvattr ACL are added to the REPLY list, and the job again continues running the expression, again until it finishes or makes another interface call.
In the mean time, the server still tends to new requests, module communications, possibly crashed childs, and so on.
One important aspect of the expression language is the short-circuit boolean evaluation. This allows you to do conditional subexpressions, like int && str="abc", which only adds a 'str' attribute with value 'abc' to the bottom of the request list if the last instance of the int attribute on the reply list has a value that can be interpreted as 'true'.
The || operator only executes the subexpression on its right if the one on its left is 'false'. The operator returns the last evaluated subexpression, so you can write things like str = (str || "hello"), to supply a default value for an attribute, or more powerful things that employ auto-conversion, like str = (NAS-Identifier || NAS-IP-Address).
These two operators together also provide if-then-else constructs, like this:
Reply-Message = "The last 'int' on the reply list was ", int == 3 && ( Reply-Message := REP:Reply-Message . "indeed 3! Yes sir.", 1) || ( Reply-Message := REP:Reply-Message . "not 3, but " . int . "!" ),
For the rest, and why the '=' operator added a pair to the REQUEST instead of the REPLY list in the 'str="abc"'-example, and why the '==' operator tested the
'int' attribute on the REPLY list instead of the REQUEST list, which is what
you'd expect and what the language normally does, and why I used a REP: prefix
on the right hand side of the ':=' operator and not on the left even though
both sides refer to the same A/V pair, see the
real documentation.
Not yet written - do 'radiusd -h' and see main.c for now.