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.
Download the source from
here
and unpack it in your home directory. It will create a directory
called openradius-vX.y (for version X.y).
Go to this directory and edit your platform's Makefile to
reflect the desired installation paths, module selection and compiler
flags. Running 'configure' will list the platforms for which a Makefile
is available.
Note: if you set the location of the configuration directory to something
other than '/usr/local/etc/openradius', you'll have to
edit the configuration file as well, as the path also
appears on some modules' command lines. You can also do that after
installation though.
Type 'make -f Makefile.platform' to compile it. You should not get
any warnings or error messages. If you do, please see the system
requirements in the release notes
here.
If you are using a recent GNU make, a working ANSI C compiler, and any BSD,
GNU or SysV-like Unix variant, it should definitely be possible to build
it.
If everything went allright, type 'make -f Makefile.platform install' to
install it. Again, you should not get any error messages. If you
do, you'll have to install into a different directory or become root before
invoking this command.
After installation comes configuration. Not counting the dictionary, OpenRADIUS
itself uses only two configuration files:
.../etc/openradius/configuration - this file defines the
server's sources and external interfaces. For each source it lists
the addresses and ports to listen on; for each interface it
lists the modules to run, the attributes to be included in
the requests to it and the ones that are allowed to be taken
from its responses, its type (ASCII or binary), and some other
options.
.../etc/openradius/behaviour - this file defines how the
server handles each request: when to do what with which attribute. It
defines the external interfaces to be queried, the authentication
methods to be performed, the response attributes to be included, and
so on.
Both files are read only once, at server startup. No HUP handling is done yet -
I'd like to do implement that by having the signal create a complete new
configuration set while keeping the old one around as well; then jobs could
refer to the configuration under which they were created, and we'd use
job-based reference counting to discard the old configuration if there are no
running jobs anymore that use them, thus guaranteeing consistent behaviour at
all times. But that's for later.
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':
name: defines the name by which the new interface can be
called from the behaviour file. One instance is required.
sendattr: if not specified, then all attributes that are
present on the request list at the time of an interface call are
sent to the module. Otherwise, the instances of 'sendattr' together
define an inclusive list of attributes that are allowed to be sent.
prog: defines one or more subprocesses to be spawned at
startup for this interface and their command line arguments.
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.
recvattr: if not specified, all attributes that are
present in a module's response are added to the reply list.
Otherwise, each instance of 'recvattr' adds an attribute to the
inclusive list of attributes that are allowed to be received from this
interface.
timeout: defines the watchdog timer for this
interface's subprocesses. If no data could be sent to or received
from a subprocess during a period longer than the number of seconds
specified in this attribute, the process is terminated (and
subsequently restarted).
flags: a numeric value that holds a combination of
flags. The following flag constants are defined for this attribute
(see also the specification of the
module interface):
Ascii: Use ASCII messages for interface
Add-Tab: Add tabs before pairs (ASCII)
Add-Spaces: Add spaces in pairs around the equals
sign (ASCII)
Add-Type: Add attribute type and a colon before value
(ASCII)
Hex-Value: Send and receive values in hexadecimal
(using a series of two-digit hexadecimal values for strings)
(ASCII)
Double-Backslash: Send two backslashes
instead of one to introduce escape sequences (ASCII)
Named-Const: Send a numeric value's constant name
instead of its decimal value if the dictionary defines one
(ASCII)
Short-Attr: Omit space- and vendor names (ASCII)
Here are some examples of defining interfaces (based on the contents of
.../etc/openradius/configuration.sample included with OpenRADIUS 0.9.5).
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.
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 - sorry. Please use 'radiusd -h' and see main.c for now. The
most important example to get started is 'radiusd -dall -b', which turns on
moderate debugging on all facilities and prevents the server from going to
background.