Extensions
**********

The use of extension language allows extending the functionality of GNU Radius without having to modify its source code. The two extension languages supported are Rewrite and Scheme. Use of Rewrite is always enabled. Use of Scheme requires Guile version 1.4 or higher.

Filters
=======

The simplest way to extend the functionality of Radius is to use filters. A filter is an external program that communicates with Radius via its standard input and output channels.

Getting Acquainted with Filters
-------------------------------

Suppose we wish to implement an authentication method based on the user name and the user's calling station ID. We have a database of user names with valid IDs, and the new method should authenticate a user only if the combination USER_NAME,ID is found in this database.

We write a filter program that reads its standard input line by line. Each input line must consist of exactly two words: the user name and the calling station ID. For each input line, the program prints 0 if the USER_NAME,ID is found in the database and 1 otherwise. Let's suppose for the sake of example that the database is a plaintext file and the filter is written in a shell programming language. Then it will look like

     #! /bin/sh

     DB=/var/db/userlist

     while read NAME CLID
     do
         if grep "$1:$2" $DB; then
             echo "0"
         else
             echo "1"
         fi
     done

Declaring the Filter
--------------------

Here is how this filter is declared in the raddb/config file:

     filters {
         filter check_clid {
             exec-path "/usr/libexec/myfilter";
             error-log "myfilter.log";
             auth {
                 input-format "%C{User-Name}
                 %C{Calling-Station-Id}";
                 wait-reply yes;
             };
         };
     };

Let's analyze this declaration line by line:

 1. filters {

     This keyword opens the filters declaration block. The block may contain several declarations.

 2. filter check_clid {

     This line starts the declaration of this particular filter and names it check_clid.

 3. exec-path "/usr/libexec/myfilter";

     This line tells radiusd where to find the executable image of this filter.

 4. error-log "myfilter.log";

     The diagnostic output from this filter must be redirected to the file myfilter.log in the current logging directory

 5. auth {

     This filter will process authentication requests.

 6. input-format "%C{User-Name} %C{Calling-Station-Id}";

     Define the input line format for this filter. The %C{} expressions will be replaced by the values of the corresponding attributes from the incoming request (see Macro Substitution).

 7. wait-reply yes;

     radiusd will wait for the reply from this filter to decide whether to authenticate the user.

Invoking the Filter from a User Profile
---------------------------------------

To invoke this filter from the user profile, specify its name prefixed with | in the value of Exec-Program-Wait attribute, like this:

     DEFAULT Auth-Type = System,
                     Simultaneous-Use = 1
             Exec-Program-Wait = "|check_clid"

Adding Reply Attributes
-----------------------

Apart from simply deciding whether to authenticate a user, the filter can also modify the reply pairs.

     #! /bin/sh

     DB=/var/db/userlist

     while read NAME CLID
     do
         if grep "$1:$2" $DB; then
             echo "0 Service-Type = Login, Session-Timeout = 1200"
         else
             echo "1 Reply-Message =
                   \"You are not authorized to log in\""
         fi
     done

Accounting Filters
------------------

Let's suppose we further modify our filter to also handle accounting requests. To discern between the authentication and accounting requests we'll prefix each authentication request with the word auth and each accounting request with the word acct. Furthermore, the input line for accounting requests will contain a timestamp.

Now, our filter program will look as follows:

     #! /bin/sh

     AUTH_DB=/var/db/userlist
     ACCT_DB=/var/db/acct.db

     while read CODE NAME CLID DATE
     do
         case CODE
         auth)
             if grep "$1:$2" $DB; then
                 echo "0 Service-Type = Login,
                       Session-Timeout = 1200"
             else
                 echo "1 Reply-Message =
                       \"You are not authorized to log in\""
             fi
         acct)
             echo "$CODE $NAME $CLID $DATE" >> $ACCT_DB
     done

Its declaration in the raddb/config will also change:

     filter check_clid {
         exec-path "/usr/libexec/myfilter";
         error-log "myfilter.log";
         auth {
             input-format "auth %C{User-Name}
                           %C{Calling-Station-Id}";
             wait-reply yes;
         };
         acct {
             input-format "acct %C{User-Name}
                           %C{Calling-Station-Id} %D";
             wait-reply no;
         };
     };

Notice wait-reply no in the acct statement. It tells radiusd that it shouldn't wait for the response on accounting requests from the filter.

Invoking the Accounting Filter
------------------------------

To invoke the accounting filter, specify its name prefixed with a vertical bar character as a value of Acct-Ext-Program in our raddb/hints file. For example:

     DEFAULT NULL
             Acct-Ext-Program = "|check_clid:

Common Filters
--------------

The only aspect of the filter functionality we have not covered is so called common filter. By default, each radiusd thread runs its own instance of the filter. This allows all threads to run concurrently. However, in some configurations it may be necessary to share a single instance of the filter among all threads. Such filters are called common filters. Notice that common filters create potential bottlenecks in the data flow of the program: each thread has to wait for the previous request to be finished before passing its data to the filter.

A filter is declared as common by adding the following statement to its declaration:

     common yes;

Rewrite
=======

Rewrite is the GNU Radius extension language. Its name reflects the fact that it was originally designed to rewrite the broken request packets so they could be processed as usual (see Rewriting Incoming Requests). Beside this basic use, however, Rewrite functions are used in verifying the activity of user sessions (see Checking Simultaneous Logins).

Syntax Overview
---------------

The syntax of Rewrite resembles that of C. Rewrite has two basic data types: integer and string. It does not have global variables; all variables are automatic. The only exceptions are the AVP s from the incoming request, which are accessible to Rewrite functions via the special notation %[ATTR].

Quick Start
-----------

As an example, let's consider the following Rewrite function:

     string
     foo(integer i)
     {
         string rc;
         if (i % 2)
             rc = "odd";
         else
             rc = "even";
         return "the number is " + rc;
     }

The function takes an integer argument and returns the string the number is odd or the number is even, depending on the value of i. This illustrates the fact that in Rewrite the addition operator is defined on the string type. The result of such operation is the concatenation of operands.

Another example is a function that adds a prefix to the User-Name attribute:

     integer
     px_add()
     {
             %[User-Name] = "pfx-" + %[User-Name];
             return 0;
     }

This function manipulates the contents of the incoming request; its return value has no special meaning.

Interaction with Radius
-----------------------

A Rewrite function can be invoked in several ways, depending on its purpose. There are three major kinds of Rewrite functions:

  * Functions used to rewrite the incoming requests.

  * Functions designed for simultaneous login verification.

  * Functions used to generate Radius attribute values.

Rewriting Incoming Requests
---------------------------

The need for rewriting the incoming requests arises from the fact that some NAS es are very particular about the information they send with the requests. There are cases when the information they send is hardly usable or even completely unusable. For example, a Cisco AS5300 terminal server used as a voice-over IP router packs a lot of information into its Acct-Session-Id attribute. Though the information stored there is otherwise relevant, it makes proper accounting impossible, since the Acct-Session-Id attributes in the start and stop packets of the same session become different, and thus Radius cannot determine the session start to which the given session stop request corresponds (see Acct-Session-Id).

In order to cope with such NAS es, GNU Radius is able to invoke a Rewrite function upon arrival of the packet and before processing it further. This function can transform the packet so that it obtains the form prescribed by RFCs and its further processing becomes possible.

For example, in the case of the AS5300 router, a corresponding Rewrite function parses the Acct-Session-Id attribute; breaks it down into fields; stores them into proper attributes, creating them if necessary; and finally replaces Acct-Session-Id with its real value, which is the same for the start and stop records corresponding to a single session. Thus all the information that came with the packet is preserved, but the packet itself is made usable for proper accounting.

A special attribute, Rewrite-Function, is used to trigger invocation of a Rewrite function. Its value is a name of the function to be invoked.

When used in a naslist profile, the attribute causes the function to be invoked when the incoming request matches the huntgroup (see Huntgroups). For example, to have a function fixup invoked for each packet from the NAS 10.10.10.11, the following huntgroup rule may be used:

     DEFAULT  NAS-IP-Address = 11.10.10.11
              Rewrite-Function = "fixup"

The Rewrite-Function attribute may also be used in a hints rule. In this case, it will invoke the function if the request matches the rule (see Hints). For example, this hints rule will cause the function to be invoked for each request containing the user name starting with P:

     DEFAULT  Prefix = "P"
              Rewrite-Function = "fixup"

Note that in both cases the attribute can be used either in LHS or in RHS pairs of a rule.

The packet rewrite function must be declared as having no arguments and returning an integer value:

     integer fixup()
     {
     }

The actual return value from such a function is ignored, the integer return type is just a matter of convention.

The following subsection present some examples of packet rewrite functions.

Examples of Various Rewrite Functions
.....................................

The examples found in this chapter are working functions that can be used with various existing NAS types. They are taken from the rewrite file contained in distribution of GNU Radius.

1. Port rewriting for MAX Ascend terminal servers
-------------------------------------------------

Some MAX Ascend terminal servers pack additional information into the NAS-Port-Id attribute. The port number is constructed as XYYZZ, where X = 1 for digital, X = 2 for analog, YY is the line number (1 for first PRI/T1/E1, 2 for second, and so on), and ZZ is the channel number (on the PRI or channelized T1/E1).

The following rewrite functions are intended to compute the integer port number in the range (1 .. PORTCNT), where PORTCNT represents the real number of physical ports available on the NAS. Such a port number can be used, for example, with the Add-Port-To-IP-Address attribute (see Add-Port-To-IP-Address):

     /*
      * decode MAX port number
      * input: P        --  The value of NAS-Port-Id attribute
      *        portcnt  --  number of physical ports on the NAS
      */
     integer
     max_decode_port(integer P, integer portcnt)
     {
         if (P > 9999) {
             integer s, l, c;

             s = P / 10000;
             l = (P - (10000 * s))/100;
             c = P - ((10000 * s) + (100 * l));
             return (c-1) + (l-1) * portcnt;
         }
         return P;
     }

     /*
      * Interface function for MAX terminal server with 23 ports.
      * Note that it saves the received NAS-Port-Id attribute in
      * the Orig-NAS-Port-Id attribute. The latter must be
      * defined somewhere in the dictionary
      */
     integer
     max_fixup()
     {
         %[Orig-NAS-Port-Id] = %[NAS-Port-Id];
                                       # Preserve original data
         %[NAS-Port-Id] = max_decode_port(%[NAS-Port-Id], 23);
         return 0;
     }

2. Session ID parsing for Cisco AS 5300 series
----------------------------------------------

Cisco VOIP IOS encodes a lot of other information into its Acct-Session-Id. The pieces of information are separated by / characters. The part of Acct-Session-Id up to the first / character is the actual session ID.

On the other hand, its accounting packets lack NAS-Port-Id, though they may contain the vendor-specific pair with code 2 (vendor PEC 9), which is a string in the form ISDN 9:D:999 (9 represents any decimal digit). The number after the last : character can be used as a port number.

The following code parses Acct-Session-Id attribute and stores the information it contains in various other attributes, generates a normal Acct-Session-Id, and attempts to generate a NAS-Port-Id attribute.

     /*
      * The port rewriting function for Cisco AS5300 used for
      * VoIP. This function is used to generate NAS-Port-Id pair
      * on the basis of vendor-specific pair 2. If the latter is
      * in the form "ISDN 9:D:999" (where each 9 represents a
      * decimal digit), then the function returns the number
      * after the last colon. This is used as a port number.
      */
     integer
     cisco_pid(string A)
     {
         if (A =~
             ".*\([0-9][0-9]*\):
              [A-Z0-9][A-Z0-9]*:\([0-9][0-9]*\)") {
             return (integer)\2;
         }
         return -1;
     }

     /*
      * This function parses the packed session id.
      * The actual sid is the number before the first slash
      * character.  Other possibly relevant fields are also
      * parsed out and saved in the Voip-* A/V pairs. The latter
      * should be defined somewhere in the dictionary.
      * Note that the regular expression in this example
      * spans several lines for readability. It should be on one
      * line in real file.
      */
     string
     cisco_sid(string S)
     {
        if (S =~ "\(.[^/]*\)/[^/]*/[^/]*/\([^/]*\)/\([^/]*\)/
                  \([^/]*\)/\([^/]*\)/\([^/]*\)/\([^/]*\)
                  /\([^/]*\).*") {
             %[Voip-Connection-ID] = \2;
             %[Voip-Call-Leg-Type] = \3;
             %[Voip-Connection-Type] = \4;
             %[Voip-Connect-Time] = \5;
             %[Voip-Disconnect-Time] = \6;
             %[Voip-Disconnect-Cause] = \7;
             %[Voip-Remote-IP] = \8;
             return \1;
        }
        return S;
     }

     /*
      * Normalize cisco AS5300 packets
      */
     integer
     cisco_fixup()
     {
         integer pid;

         if ((pid = cisco_pid(%[Cisco-PRI-Circuit])) != -1) {
             if (*%[NAS-Port-Id])
                 %[Orig-NAS-Port-Id] = %[NAS-Port-Id];
             %[NAS-Port-Id] = pid;
         }
         if (*%[Acct-Session-Id]) {
             %[Orig-Acct-Session-Id] = %[Acct-Session-Id];
             %[Acct-Session-Id] = cisco_sid(%[Acct-Session-Id]);
         }
         return 0;
     }

3. User-name rewriting for NT machines
--------------------------------------

Users coming from Windows NT machines often authenticate themselves as NT_DOMAIN\USERNAME. The following function selects the user-name part and stores it in the User-Name attribute:

     integer
     login_nt(string uname)
     {
         integer i;

         if ((i = index(uname, '\\')) != -1)
             return substr(uname, i+1, -1);
         return uname;
     }

     integer
     nt_rewrite()
     {
         %[Orig-User-Name] = %[User-Name];
         %[User-Name] = login_nt(%[User-Name]);
         return 0;
     }

Login Verification Functions
----------------------------

A login verification function is invoked to process the output from the NAS. This process is described in Checking Simultaneous Logins. The function to be invoked for given NAS is defined by a function flag in the raddb/nastypes or raddb/naslist file (see nastypes file). It must be defined as follows:

     integer check(string STR, string NAME, integer PID,
                   string SID)
     {
     }

Its arguments are:

str
     Input string. If the query method is finger, this is the string of output received from the NAS with trailing newline stripped off. If the query method is snmp, it is the received variable value converted to its string representation.

name
     User name.

pid
     Port ID of the session.

sid
     Session ID.

The function should return non-0 if its arguments match the user's session, and 0 otherwise.

Examples of login verification functions
........................................

As an example, let's consider the function for analyzing a line of output from a standard UNIX finger service. In each line of finger output the first field contains the user name; the third field, the tty number (port ID); and the seventh field, the session ID. The function must return 1 if the three fields match the input user name and port and session IDs:

     integer
     check_unix(string str, string name, integer pid, string sid)
     {
         return field(str, 1) == name
                && field(str, 3) == pid
                && field(str, 7) == sid;
     }

The next example is a function to analyze a line of output from an SNMP query returning a user name. This function must return 1 if the entire input line matches the user name:

     integer
     check_username(string str, string name, integer pid, string sid)
     {
         return str == name;
     }

Attribute Creation Functions
----------------------------

These are the functions used to create Radius reply attributes. An attribute creation function can take any number of arguments. The type of its return is determined by the type of Radius attribute the value will be assigned to. To invoke the function, write its name in the AVP of the RHS in the raddb/users file, e.g.:

     DEFAULT Auth-Type = SQL
             Service-Type = Framed-User,
                 Framed-IP-Address = "=get_ip_addr(10.10.10.1)"

The function get_ip_addr will be invoked after successful authentication and it will be passed the IP 10.10.10.1 as its argument. An example of a useful function that can be invoked this way is

     integer
     get_ip_address(integer base)
     {
         return base + %[NAS-Port-Id] - %[NAS-Port-Id]/16;
     }

Full Syntax Description
-----------------------

Rewrite Data Types
..................

There are only two data types: integer and string, the two being coercible to each other in the sense that a string can be coerced to an integer if it contains a valid ASCII representation of a decimal, octal, or hex number, and an integer can always be coerced to a string, the result of such coercion being the ASCII string that is the decimal representation of the number.

Rewrite Symbols
...............

A symbol is a lexical token. The following symbols are recognized:

Arithmetical operators
     These are +, -, *, / representing the basic arithmetical operations, and % meaning remainder.

Comparison operators
     These are: ==, !=, <, <=, >, >= with the same meaning they have in C. Special operators are provided for regular-expression matching. The binary operator =~ returns true if its left-hand-side operand matches the regular expression on its right-hand side. !~ returns true if its left-hand-side operand does not match the regexp on its right-hand side. The right-hand-side operand of !~ or =~ must be a literal string, i.e., the regular expression must be known at compile time.

Unary operators
     The unary operators are - and + for unary plus and minus, ! for boolean negation, and * for testing for the existence of an attribute.

Boolean operators
     These are && and ||.

Parentheses ( and )
     These are used to change the precedence of operators, to introduce type casts (type coercions), to declare functions, and to pass actual arguments to functions.

Curly braces ({ and })
     These are used to delimit blocks of code.

Numbers
     Numbers follow the usual C convention for integers. A number consisting of a sequence of digits is taken to be octal if it begins with 0 (digit zero), and decimal otherwise. If the sequence of digits is preceded by 0x or 0X, it is taken to be a hexadecimal integer.

Characters
     These follow the usual C convention for characters, i.e., they consist either of an ASCII character itself or of its value, enclosed in a pair of singlequotes. The character value begins with \ (backslash) and consists either of three octal or of two hexadecimal digits. A character does not form a special data type; it is represented internally by an integer.

Quoted strings
     These follow usual C conventions for strings.

Attribute values
     The incoming request is passed implicitly to functions invoked via the Rewrite-Function attribute. It is kept as an associative array, whose entries can be accessed using the following syntax:

          %[ ATTRIBUTE-NAME ]
          %[ ATTRIBUTE-NAME ] ( N )

     The first form returns the value of the attribute ATTRIBUTE-NAME. Here ATTRIBUTE-NAME should be a valid Radius dictionary name (see dictionary file).

     The second form returns the value of the Nth attribute of type ATTRIBUTE-NAME. The index N is counted from zero, so

                  %[ATTRIBUTE-NAME](0)

     is equivalent to

                  %[ATTRIBUTE-NAME]

Identifiers
     Identifiers represent functions and variables. These are described in the next sub-subsection.

Regexp group references
     A sequence of characters in the form

          \NUMBER

     refers to the contents of parenthesized group number NUMBER obtained as a result of the last executed =~ command. The regexp group reference has always string data type. For example:

          string
          basename(string ARG)
          {
              if (arg =~ ".*/\(.*\)\..*")
                  return \1;
              else
                  return arg;
          }

     This function strips from ARG all leading components up to the last slash character, and all trailing components after the last dot character. It returns ARG unaltered if it does not contain slashes and dots. It is roughly analogous to the system basename utility.

Rewrite Identifiers
...................

A valid identifier is a string of characters meeting the following requirements:

 1. It starts with either a lower- or an uppercase letter of the Latin alphabet or either of the following symbols: _, $.

 2. It consists of alphanumeric characters, underscores(_), and dollar signs ($).

Rewrite Declarations
....................

Function declarations
---------------------

A Rewrite function is declared as follows:

     TYPE FUNCTION-NAME (PARAMETER-LIST)

where TYPE specifies the return type of the function, FUNCTION-NAME declares the symbolic name of the function, and PARAMETER-LIST declares the formal parameters to the function. It is a comma-separated list of declarations in the form

     TYPE PARM-NAME

TYPE being the parameter type, and PARM-NAME being its symbolic name. Both FUNCTION-NAME and PARM-NAME should be valid identifiers.

Variable declarations
---------------------

There are no global variables in Rewrite. All variables are local. The local variables are declared right after the opening curly brace ({) and before any executable statements. The declaration syntax is

     TYPE IDENT_LIST ;

Here IDENT_LIST is either a valid Rewrite identifier or a comma-separated list of such identifiers.

Note that, unlike in C, no assignments are allowed in variable declarations.

Rewrite Statements
..................

The Rewrite statements are: expressions, assignments, conditional statements, and return statements. A statement is terminated by a semicolon.

Expressions
-----------

An expression is one of the following:

  * A variable identifier

  * A type coercion expression

  * An arithmetic expression

  * A boolean expression

  * An assignment

  * A function call

  * A delete statement

Type coercion
-------------

The type coercion is like a type cast in C. Its syntax is

     ( TYPE ) IDENT

The result of type coercion is as follows:

TYPE   Variable type   Resulting conversion

integer
integer
No conversion. This results in the same integer value.

integer
string
If the string value of the variable is a valid ASCII representation
of the integer number (either decimal, octal, or hex), it is converted to
the integer; otherwise the result of the conversion is undefined.

string
integer
The ASCII representation (in decimal) of the integer number.

string
string
No conversion. This results in the same string value.

Assignment
----------

An assignment is

     IDENT = EXPRESSION ;

The variable IDENT is assigned the value of EXPRESSION.

Function calls
--------------

These take the form

     IDENT ( ARG-LIST )

where IDENT is the identifier representing the function, and ARG-LIST is a comma-separated list of expressions supplying actual arguments to the function. The function that IDENT references can be either a compiled function or a built-in function.

Note that, unlike in C, a mismatch between the number of actual arguments and number of formal parameters in the compiled function declaration is not an error but rather a warning.

delete statement
----------------

The delete statement is used to delete an attribute or attributes from the incoming request. Its syntax is:

     delete ATTRIBUTE-NAME;
     delete ATTRIBUTE-NAME(N);

The first variant deletes all the attributes of the given type. The second variant deletes only the Nth occurrence of the matching attribute.

Rewrite Built-in Functions
..........................

The following built-in functions are provided:

 -- Function: integer length (string S)
     Returns the length of the string S.

 -- Function: integer index (string S, integer C)
     Returns the index of the first occurrence of the character C in the string S. Returns -1 if no such occurrence is found.

 -- Function: integer rindex (string S, integer I)
     Returns the index of the last occurrence of the character C in the string S. Returns -1 if no such occurrence is found.

 -- Function: string substr (string S, integer START, integer LENGTH)
     Returns the substring of S of length at most LENGTH starting at position START.

All character positions in strings are counted from 0.

 -- Function: string field (string BUFFER, integer N)
     This function regards the BUFFER argument as consisting of fields separated with any amount of whitespace. It extracts and returns the Nth field. N is counted from 1.

 -- Function: integer logit (string MSG)
     Outputs its argument to the Radius log channel info. Returns 0. For debugging purposes.

 -- Function: integer inet_aton (string STR)
     Converts the Internet host address STR from the standard numbers-and-dots notation into the equivalent integer in host byte order.

 -- Function: string inet_ntoa (integer IP)
     Converts the Internet host address IP given in host byte order to a string in standard numbers-and-dots notation.

 -- Function: integer htonl (integer N)
     Converts the integer N, regarded as long, from host to network byte order.

 -- Function: integer ntohl (integer N)
     Converts the integer N, regarded as long, from network to host byte order.

 -- Function: integer htons (integer N)
     Converts the integer N, regarded as short, from host to network byte order.

 -- Function: integer ntohs (integer N)
     Converts the integer N, regarded as short, from network to host byte order.

Guile
=====

The name Guile stands for GNU's Ubiquitous Intelligent Language for Extensions. It provides a Scheme interpreter conforming to the R4RS language specification. This section describes use of Guile as an extension language for GNU Radius. It assumes that the reader is sufficiently familiar with the Scheme language. For information about the language, refer to Revised(4) Report on the Algorithmic Language Scheme. For more about Guile, see Overview (The Guile Reference Manual).

Scheme procedures can be called for processing both authentication and accounting requests. The invocation of a Scheme procedure for an authentication request is triggered by the Scheme-Procedure attribute; the invocation for an accounting request is triggered by the Scheme-Acct-Procedure attribute. The following sections address these issues in more detail.

Data Representation
-------------------

AVP lists are the main object Scheme functions operate upon. Scheme is extremely convenient for representation of such objects. A Radius AVP is represented by a Scheme pair; e.g.,

             Session-Timeout = 10

is represented in Guile as

             (cons "Session-Timeout" 10)

The car of the pair can contain either the attribute dictionary name or the attribute number. Thus, the above pair may also be written in Scheme as

             (cons 27 10)

(because Session-Timeout corresponds to attribute number 27).

Lists of AVP s are represented by Scheme lists. For example, the Radius pair list

             User-Name = "jsmith",
                     User-Password = "guessme",
                     NAS-IP-Address = 10.10.10.1,
                     NAS-Port-Id = 10

is written in Scheme as

             (list
               (cons "User-Name" "jsmith")
               (cons "User-Password" "guessme")
               (cons "NAS-IP-Address" "10.10.10.1")
               (cons "NAS-Port-Id" 10))

Authentication with Scheme
--------------------------

The Scheme procedure used for authentication must be declared as follows:

 -- Function Template: auth-function request-list check-list reply-list
     Its arguments are:

     request-list
          The list of AVP s from the incoming request

     check-list
          The list of AVP s from the LHS of the profile entry that matched the request

     reply-list
          The list of AVP s from the RHS of the profile entry that matched the request

The function return value determines whether the authentication will succeed. The function must return either a boolean value or a pair. The return of #t causes authentication to succeed. The return of #f causes it to fail.

For a function to add something to the reply AVP s, it should return a pair in the form

         (cons RETURN-CODE LIST)

where RETURN-CODE is a boolean value of the same meaning as described above. LIST is a list of AVP s to be added to the reply list. For example, the following function will always deny the authentication, returning an appropriate message to the user:

     (define (decline-auth request-list check-list reply-list)
       (cons #f
             (list
              (cons "Reply-Message"
                    "\r\nSorry, you are not
                     allowed to log in\r\n"))))

As a more constructive example, let's consider a function that allows the authentication only if a user name is found in its internal database:

     (define staff-data
       (list
        (list "scheme"
              (cons
               (list (cons "NAS-IP-Address" "127.0.0.1"))
               (list (cons "Framed-MTU" "8096")))
              (cons
               '()
               (list (cons "Framed-MTU" "256"))))))

     (define (auth req check reply)
       (let* ((username (assoc "User-Name" req))
              (reqlist (assoc username req))
              (reply-list '()))
         (if username
             (let ((user-data (assoc (cdr username) staff-data)))
               (rad-log L_INFO (format #f "~A" user-data))
               (if user-data
                   (call-with-current-continuation
                    (lambda (xx)
                      (for-each
                       (lambda (pair)
                         (cond
                          ((avl-match? req (car pair))
                           (set! reply-list (avl-merge
                                             reply-list
                                             (cdr pair)))
                           (xx #t))))
                       (cdr user-data))
                      #f)))))
         (cons
          #t
          reply-list)))

To trigger the invocation of the Scheme authentication function, assign its name to the Scheme-Procedure attribute in the RHS of a corresponding raddb/users profile. For example:

     DEFAULT Auth-Type = SQL
             Scheme-Procedure = "auth"

Accounting with Scheme
----------------------

The Scheme accounting procedure must be declared as follows:

 -- Function Template: acct-function-name request-list
     Its argument is:

     request-list
          The list of AVP s from the incoming request

The function must return a boolean value. The accounting succeeds only if it has returned #t.

Here is an example of a Scheme accounting function. The function dumps the contents of the incoming request to a file:

     (define radius-acct-file "/var/log/acct/radius")

     (define (acct req)
       (call-with-output-file radius-acct-file
         (lambda (port)
           (for-each (lambda (pair)
                       (display (car pair) port)
                       (display "=" port)
                       (display (cdr pair) port)
                       (newline port))
                     req)
           (newline port)))
       #t)

Radius-Specific Functions
-------------------------

 -- Scheme Function: avl-delete av-list attr
     Delete from AV-LIST the pairs with attribute ATTR.

 -- Scheme Function: avl-merge dst src
     Merge SRC into DST.

 -- Scheme Function: avl-match? target list
     Return #t if all pairs from LIST are present in TARGET.

 -- Scheme Function: rad-dict-name->attr name
     Return a dictionary entry for the given attribute NAME or #f if no such name was found in the dictionary.

     A dictionary entry is a list in the form

      -- Scheme List: dict-entry name-string attr-number type-number vendor

          where the arguments are as follows:

          name-string
               The attribute name

          value-number
               The attribute number

          type-number
               The attribute type

          vendor
               The vendor PEC, if the attribute is a vendor-specific one, or #f otherwise.

 -- Scheme Function: rad-dict-value->name attr value
     Returns the dictionary name of the given VALUE for an integer-type attribute ATTR, which can be either an attribute number or its dictionary name.

 -- Scheme Function: rad-dict-name->value attr value
     Convert a symbolic attribute value name into its integer representation.

 -- Scheme Function: rad-dict-pec->vendor pec
     Convert a PEC to the vendor name.

 -- Scheme Function: rad-log-open prio
     Open Radius logging to the severity level PRIO.

 -- Scheme Function: rad-log-close
     Close a Radius logging channel opened by a previous call to rad-log-open.

 -- Scheme Function: rad-rewrite-execute-string string
     Interpret STRING as an invocation of a function in Rewrite language and execute it.

     Return value: return of the corresponding Rewrite call, translated to the Scheme data type.

 -- Scheme Function: rad-rewrite-execute arglist
     Execute a Rewrite language function. (car ARGLIST) is interpreted as a name of the Rewrite function to execute, and (cdr ARGLIST) as a list of arguments to be passed to it.

     Return value: return of the corresponding Rewrite call, translated to the Scheme data type.

 -- Scheme Function: rad-openlog ident option facility
     Scheme interface to the system openlog() call.

 -- Scheme Function: rad-syslog prio text
     Scheme interface to the system syslog() call.

 -- Scheme Function: rad-closelog
     Scheme interface to the system closelog() call.

 -- Scheme Function: rad-utmp-putent status delay list radutmp_file radwtmp_file
     Write the supplied data into the radutmp file. If RADWTMP_FILE is not nil, the constructed entry is also appended to WTMP_FILE.

     LIST is:

      -- Scheme List: utmp-entry user-name orig-name port-id port-type session-id caller-id framed-ip nas-ip proto

          user-name
               The user name

          orig-name
               The original user name from the request

          port-id
               The value of the NAS-Port-Id attribute

          port-type
               A number or character indicating the port type

          session-id
               The session ID

          caller-id
               The value of the Calling-Station-Id attribute from the request

          framed-ip
               The framed IP assigned to the user

          nas-ip
               The NAS IP

          proto
               A number or character indicating the type of the connection