[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
11.1 Filters Using external filter programs. 11.2 Rewrite The built-in extension language. 11.3 Guile Using Scheme.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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:
filters {
This keyword opens the filters declaration block. The block may contain several declarations.
filter check_clid {
This line starts the declaration of this particular filter and names it `check_clid'.
exec-path "/usr/libexec/myfilter";
This line tells radiusd
where to find the executable image of
this filter.
error-log "myfilter.log";
The diagnostic output from this filter must be redirected to the file `myfilter.log' in the current logging directory
auth {
This filter will process authentication requests.
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 section 5.14 Macro Substitution).
wait-reply yes;
radiusd
will wait for the reply from this filter to decide whether to
authenticate the user.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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" |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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; }; }; |
(The input-format
lines are split for readability. Each of them
is actually one line).
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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: |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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 section 11.2.4 Rewriting Incoming Requests). Beside this basic use, however, Rewrite functions are used to control various aspects of GNU Radius functionality, such as verifying the activity of user sessions, controlling the amount of information displayed in log messages, etc (see section 11.2.3 Interaction with Radius).
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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 A/V pairs from the incoming request,
which are accessible to Rewrite functions via the special notation
%[attr]
.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A Rewrite function can be invoked in several ways, depending on its purpose. There are three major kinds of Rewrite functions:
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The need for rewriting the incoming requests arises from the fact that
some NASes 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 section 14.2.7 Acct-Session-Id
).
In order to cope with such NASes, 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 section 3.4.4 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 section 3.4.3 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.
11.2.4.1 Examples of Various Rewrite Functions
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
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, to create a dynamic pool
of IP addresses (see section 14.1.8 Framed-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; } |
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; } |
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; } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A login verification function is invoked to process the output from the
NAS. This process is described in 7.9 Multiple Login Checking.
The function to be invoked for given NAS is defined by
a function
flag in the `raddb/nastypes' or `raddb/naslist'
file (see section 5.5 NAS Types -- `raddb/nastypes'). It must be defined as follows:
Its arguments are:
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.
The function should return non-0 if its arguments match the user's session, and 0 otherwise.
11.2.5.1 Examples of Login Verification Functions
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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
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; } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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 A/V pair 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; } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A logging hook functions should be declared as follows:
request_code_string
function (see section 11.2.8.7 Rewrite Built-in Functions).
Notice that the hook function shall not produce any side effects, in particular it shall not modify the incoming request in any way.
Following is an example prefix hook function that formats the incoming request data:
string compat_log_prefix(integer reqtype, string nas, integer id) { string result; return "(" + request_code_string(reqtype) + " " + nas + " " + (string)id + " " + %[User-Name] + ")"; } |
Here is a sample log produced by radiusd
before and after
enabling this function:
Auth.notice: Login OK [jsmith] ... Auth.notice: (AUTHREQ nas-2 251 jsmith): Login OK [jsmith] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A symbol is a lexical token. The following symbols are recognized:
If the character following the backslash is not one of those specified, the backslash is ignored.
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 section 5.2 Dictionary of Attributes -- `raddb/dictionary').
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] |
`\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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A valid identifier is a string of characters meeting the following requirements:
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Rewrite statements are: expressions, assignments, conditional statements, and return statements. A statement is terminated by a semicolon.
An expression is one of the following:
`(' 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. |
ident = expression ; |
The variable ident is assigned the value of expression.
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 number of the expressions must correspond exactly to the number of formal parameters in the function definition. The function that ident references can be either a compiled function or a built-in function.
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Rewrite uses POSIX regular expressions (See section `Regular Expression Library' in Regular Expression Library, for the detailed description of these).
You control the exact type of regular expressions by the use of the
pragmatic comment regex
. Its syntax is as follows:
#pragma regex option-list |
Option-list is a whitespace-separated list of options. Each option is one of the following words prefixed with `+' or `-':
extended
icase
newline
A non-matching list (`[^...]') not containing a newline does not match a newline.
Match-beginning-of-line operator (`^') matches the empty string immediately after a newline.
Match-end-of-line operator (`$') matches the empty string immediately before a newline.
Prefixing an option with `+' means to enable the corresponding behavior. Prefixing it with `-' means to disable it. Thus, the following statement:
#pragma regex +extended +icase |
will enable extended POSIX regular expressions using case-insensitive comparison.
Using the following comment:
#pragma regex -extended |
will switch to the basic POSIX regular expressions.
The settings of a regex
pragmatic comment remain in force up to
the end of current source file, or to the next regex
comment,
whichever occurs first.
For compatibility with previous versions, GNU Radius uses the following defaults:
#pragma regex -extended -icase -newline |
i.e. all regular expressions are treated as basic POSIX, comparison is case-sensitive.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The following built-in functions are provided:
length("string") => 6 |
index("/raddb/users", 47) => 0 index("/raddb/users", 45) => -1 |
rindex("/raddb/users", 47) => 6 |
substr("foo-bar-baz", 3, 5) => "-bar-" |
All character positions in strings are counted from 0.
field("GNU's not UNIX", 1) => "GNU's" field("GNU's not UNIX", 2) => "not" field("GNU's not UNIX", 3) => "UNIX" field("GNU's not UNIX", 4) => "" |
info
. Returns 0.
For debugging purposes.
inet_aton("127.0.0.1") => 2130706433 |
inet_ntoa(2130706433) => "127.0.0.1" |
gsub("s","S","strings") => "StringS" gsub("[0-9][0-9]*","N","28 or 29 days") => "N or N days" gsub("[()'\"]","/","\"a\" (quoted) 'string'") => "/a/ /quoted/ /string/" |
qprn("a string/value") => "a%20string%2Fvalue" |
quote_string("François contains non-!-printable chars") => "Fran\347ois contains non\tprintable chars" |
unquote_string("Fran\347ois") => "François" |
toupper("a-string") => "A-STRING" |
tolower("A-STRING") => "a-string" |
request_code_string(4) => "Accounting-Request" |
The native language support is provided via the functions described
below. These functions are interfaces to GNU gettext
library.
For the information about general concepts and principles of
Native Language Support, please refer to
section `gettext' in GNU gettext
utilities.
The default current textual domain is `radius'.
gettext
and ngettext
.
Returns the name of the previously used domain.
The second form of this function provides a traditional shortcut notation.
For a detailed description of the GNU gettext
interface,
refer to section `Interface to gettext' in GNU gettext
utilities.
ngettext
function is used to translate the messages that
have singular and plural forms. The msgid_singular parameter
must contain the singular form of the string to be converted. It is
also used as the key for the search in the catalog. The
msgid_plural
parameter is the plural form. The parameter
number is used to determine the plural form. If no message
catalog is found msgid_singular is returned if
number == 1
, otherwise msgid_plural.
For a detailed description of the GNU gettext
interface for the
plural translation,
refer to section `Additional functions for plural forms' in GNU gettext
utilities.
ngettext
, but searches translation in the given domain.
The following functions are used to read some internal fields of a RADIUS request.
NAS-IP-Address
attribute to the requests
lacking one, e.g.:
integer restore_nas_ip() { if (!*%[NAS-IP-Address]) %[NAS-IP-Address] = request_source_ip(); return 0; } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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 section `Top' in Revised(4) Report on the Algorithmic Language Scheme. For more information about Guile, see section `Overview' in 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.
11.3.1 Data Representation 11.3.2 Authentication with Scheme 11.3.3 Accounting with Scheme 11.3.4 Radius-Specific Functions
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A/V pair lists are the main object Scheme functions operate upon. Scheme is extremely convenient for representation of such objects. A Radius A/V pair 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 A/V pairs 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)) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Scheme procedure used for authentication must be declared as follows:
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 A/V pairs, 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 A/V pairs 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" |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Scheme accounting procedure must be declared as follows:
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) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
#t
if all pairs from list are present in target.
#f
if
no such name was found in the dictionary.
A dictionary entry is a list in the form
where the arguments are as follows:
#f
otherwise.
rad-log-open
.
Return value: return of the corresponding Rewrite call, translated to the Scheme data type.
(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.
openlog()
call.
syslog()
call.
closelog()
call.
list is:
NAS-Port-Id
attribute
Calling-Station-Id
attribute from the request
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |