
    0. Contents

   1. Installation
   2. Configuration
      2.1. The file .../raddb/configuration (defines sources and module
   interfaces)
      2.2. The file .../raddb/behaviour
   3. Running the server

    1. Installation

   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 configure the file 'Make.conf' to reflect
       your system type and desired installation paths. The defaults
       should work without change on most GNU/Linux systems.
       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.
     * Type 'make' to compile it. You should not get any warnings or
       error messages. If you do, please check the system requirements
       for this release in the release notes here. If you are using GNU
       make, and a BSD, GNU or SysV-like Unix variant, it should
       definitely be possible to build it.
     * If everything went allright, type 'make install' to install it.
       Again, you should not get any error messages. If you do, you'll
       probably have to change the names of the installation tools for
       your system that are specified in 'Make.conf'. After that you can
       try 'make install' again.

    2. Configuration

   Not counting the dictionary, OpenRADIUS itself uses only two
   configuration files:

     * .../raddb/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.
     * .../raddb/behaviour - this file defines how the server handles
       each request, basically 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.


      2.1. The file .../raddb/configuration

   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).

      2.1.1. Sources

   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 ;)

      2.1.2. Interfaces

   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 ...raddb/configuration.sample included with OpenRADIUS 0.9).

   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),

      2.2. The file .../raddb/behaviour

   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.

    3. Running the server

   Not yet written - do 'radiusd -h' and see main.c for now.
