Module interface
   Packet handling


   Using VSAs
   Installing on OSX

Mailing list...


Other software...

OpenRADIUS behaviour language

0. Contents

1. General
2. Terms
    2.1. Immediate values
    2.2. Attribute references
3. Types and contexts
4. Operators
    4.1. Miscellaneous (unary prefix)
    4.2. Conversion (unary postfix)
    4.3. Arithmetic, boolean, string transformations (right-associative unary prefix)
    4.4. Arithmetic (left-associative binary)
    4.5. String operations (left- and right-associative binary)
    4.6. Comparing (left-associative binary)
    4.7. Assignment (right-associative binary)
    4.8. Boolean / flow control (short-circuit binary, unary postfix)
5. Automatic type conversion
6. Interface calls

Appendix A. Operator usage reference
Appendix B. Example behaviour file from OpenRADIUS release 0.9.10.

1. General

The language used in the behaviour file is quite simple: it has no concept of statements, blocks, or function calls. The whole behaviour file consists of one single expression, and the only things the compiler knows about are contexts, terms and operators.

As far as overall syntax goes, comments are started by '#' and ended by a linefeed; whitespace is not important other than to make a difference between cases such as 'Framed-Protocol' which refers to the attribute, and 'Framed -Protocol', which refers to an attribute called 'Framed', the subtraction operator, and an attribute called 'Protocol'; and a term can not be followed directly by another term.

The expression can have side effects on two lists of attribute/value pairs: the REQUEST list, which contains every piece of information that is known about a request, including RADIUS attributes, protocol fields, timestamp and source and destination addresses and ports; and the REPLY list, which is used to build the server's response when the expression is completed.

The expression may freely read and write, add, replace and delete pairs on any list.

It may also make calls to external modules; this causes all pairs that are on the REQUEST list at that point in time and that are allowed by the interface's send ACL, to be transmitted an idle subprogram that's started for this interface.

While transmitting and waiting for the answer to have fully arrived, the current job is suspended. However, the server can still handle new requests that come in and sending and receiving on other interfaces. See the documentation about the module interface for more details.

When the answer has arrived, all received pairs that are allowed by the interface's receive ACL are added to the REPLY list, and execution of the expression is resumed until it completes or makes another interface call.

2. Terms

The language's terms come in two categories: immediate values and attribute references.

The value of an immediate term is already known at compile time. When a term looks like 123, 0x55aa, SLIP, PPP,, or "Hello!\n", the value it has is known immediately after parsing the term (and possibly looking up a constant value in the dictionary based on the 'attribute context' - see below).

Attribute references have no value at compile time, but are substituted each time they are used by an operator, with the value that is held by a particular instance (first or last) of a particular attribute (as defined in the dictionary) on a particular list (REQUEST, REPLY, the operator's default or its opposite).

2.1. Immediate values

If a term is allowed in the current context, the first thing the compiler does is to check if the next character is one that introduces a term of a particular type, like this:

  • If a term starts with a decimal digit (or minus sign), the compiler tries to parse it as a dotted-decimal IP address first (n.n.n.n; 0 <= n <= 255; see meta_atoip() in metatype.c).

    If that doesn't succeed, it tries to parse it as a normal numeric value. The following rules apply for that (see meta_atoord() in metatype.c):

    • if the number starts with a minus (-), the resulting value is negated;
    • if the value starts with 0x, the rest is interpreted as a hexadecimal value; the number stops at the first character that's not a hexadecimal digit (between 0..9 or A..F / a..f, inclusive);
    • if the value starts with a zero without a following 'x', the rest is interpreted as an octal value; the number stops at the first character that's not between 0 and 7, inclusive.

    Parsing this term as a numeric value will always succeed, because the compiler only tries to do so after it has seen a digit in the first place.

  • If a term starts with a single (') or double (") quote, the compiler parses it as a string, up to the first non-escaped quote of the same type. All other characters are taken as-is, with the exception of the backslash (\), which starts an escape sequence (see meta_prttoa() in metatype.c; the same function is also used when receiving non-hex strings from external modules that use the ASCII interface).

    Escape sequences are similar to those used in C. The following rules apply:

    • \n is interpreted as a linefeed;
    • \r is interpreted as a carriage-return;
    • \xNN is interpreted as a character with value NN in hexadecimal. NN is either one or two digits, so "\xyz" is an error and ends the string, "\x9z" is an ASCII TAB followed by the letter 'z' and "\x41123" is the same as "A123".
    • \NNN is interpreted as a character with value NNN in octal. NNN is either one, two or three octal digits (each between 0 and 7, inclusive).
    • If any other character than n, r, x or an octal digit follows the backslash, it's copied as-is. Eg. "C:\\COMMAND.COM" will return a valid DOS path.

  • Before checking for named constants as immediate values, the compiler first scans the operator table to see if it can match an operator to the text ahead, but if no operator is found that is valid in this context, it takes as many characters as possible, as long as they are from the set A..Z, a..z, 0..9, semicolon (;) and minus (-).

    Then, it first tries to find an attribute using that string (see Attribute reference syntax under 2.2 below for more details).

    Only if no attribute is found either, it tries to find a named constant that is listed in the dictionary for the attribute that was last referenced in the same or a higher-level subexpression.

    For example, this means that if you'd write:

    Service-Type = ((Framed-Protocol = PPP), Login),

    the name 'PPP' is searched for as a constant value for the Framed-Protcol attribute, but 'Login' is searched for as a constant value for the Service-Type attribute, not for Framed-Protcol. This is because the subexpression 'Framed-Protocol = PPP' is at a lower level, and is closed before the constant 'Login' is used. To quote a Perl manpage: "This may seem a little weird, but that's ok, because it is weird."

2.2. Attribute references

You can think of an attribute reference as kind of a variable, although it's not the sort of variable that always keeps its value until you change it explicitly; it actually refers to a location where a particular variable can be instead of an actual one.

The difference can be seen here: suppose you have have three instances of an attribute called 'int' on the request list, having values 22, 33 and 44, respectively. At this point, 'int' refers to the instance that has the value 44.

But after the subexpression 'del int' (which deletes the last instance of the 'int' attribute on the request list), 'int' will refer to the instance that is now the last one, which has the value 33.

Attribute reference syntax

Attribute references are written like this (square brackets denote optional components):

[REQ: or REP:][F:]attribute specification
       |       |	     |
list override  |   [space:][vendor:]name
      'first instance' flag

A list override, if present, specifies that the referenced attribute must be searched for on the specified list, regardless of the default for the operator that uses it as an operand. Most operators use the REQuest list by default; generally only the ones that 'write' values have the REPly list by default, such as '=' (add pair), ':=' (replace/add pair) and 'del' (delete pair).

If the 'first instance' flag is present, it specifies that the first matching attribute on the referenced list is to be used. If not present, the last instance of the attribute is used.

Within the attribute specification, the space and vendor names are optional - most attribute names are not ambiguous anyway. See the dictionary for more details about spaces and vendors.

One important note about the attribute name: if no list override is present, i.e. when the list that is used depends on the operator, the following rule applies: if the name starts with a lowercase letter, the list that's not the operator's default is used.

Some examples:

  • Framed-IP-Address / 28 == masks the last 4 bits of the last instance of the Framed-IP-Address attribute on the REQUEST list and compares the result to the IP address shown.

  • Service-Type = Login refers to the last instance of the Service-Type attribute on the REPLY list, causing the '=' operator to add an instance with constant value 'Login' that's defined for this attribute, at the bottom of that list.

  • int = 123 adds an 'int' attribute with value 123 at the bottom of the REQUEST list (inversed default for = operator because lowercase attribute).

  • REQ:User-Name := User-Name afterfirst "@" . "-" . User-Name beforefirst "@" replaces the contents of the last instance of the User-Name attribute on the REQUEST list with the contents of the same instance of the same attribute after the occurance of the first '@', followed by a '-' and by the contents of the same instance before the first '@'.

  • Unixpasswd(str:=User-Name), int && Reply-Message="Ok" will replace the value of the last instance of the 'str' attribute on the REQUEST list (or add the instance at the bottom if no 'str' was present yet) with the contents of the last instance of the 'User-Name' attribute on the REQUEST list, calls the interface Unixpasswd, tests the last instance of the 'int' attribute on the REPLY list for boolean true and only adds an instance of Reply-Message with the value 'Yes' at the bottom of the REPLY list if it is. Heh. Try to read that in one breath.

3. Types and contexts

A term is always of one of the four data integer, IP address, date or string. For immediate value terms, the type is apparent immediately, just as the value. For attribute reference terms, the type depends on how the attribute is defined in the dictionary.

After the compiler has seen a term of a particular type, it sets the current context to that type. The difference with contexts in Perl is that there, operators set a context which can make terms behave differently; here, terms set a context, which can make operators behave differently (or better yet, which can select among differently behaving operators). Eg. 170 ^ 255 (numeric bitwise xor) does something different than "AaCgE" ^ " \x03" (stringwise xor).

Even though the language is indeed strongly-typed, the operators' auto-conversion properties that are described below keep you from worrying too much about that. Eg. although the '+' operator is allowed in any context, it causes its surrounding terms to be converted to integers first; "123" + "1" will return 124 (numeric, because that's the type that '+' returns, but of course if this term is again used by an operator that requires a string, auto conversion will happen again, giving "124").

You'll see this type of operator behaviour more often than 'overloaded' behaviour, because I generally think overloading should be used only moderately. With auto-conversion, it's generally more obvious what the operator in question actually does and returns.

Note that the context for an operator is only defined by the type of the subexpression on its left: at the time the operator is searched for, the right term's type is not known yet.

If no term was encountered yet in the current subexpression, the context is 'none'. (This is also the only context where terms are allowed - that's why no term can directly follow another. Basically only the opening paren '(' and the comma operator reset the context to 'none'. The comma does this after the subexpression is closed, which happens immediately because it's an unary postfix operator).

The operators valid in this context are the unary prefix operators, as in -3, or hex "hello", etc. This also allows the minus sign to be used for both the negation and substraction operators.

4. Operators and precedence

As said earlier, if the compiler doesn't see a term, it searches its operator table for an operator that matches the current context.

After an operator is found, the compiler looks at its precedence to see if the current subexpression needs to be closed or not. Eg. take the expression 1 + 3 * 4: when it has already done the 1 + 3, and it sees the '*', it knows it should not close the 1 + 3, but apply the '* 4' first, because that operator has a higher precedence than '+'.

And while compiling 2 * 3 + 4 at the point of the '+', it knows it has to close the 2 * 3 subexpression first, because '+' has a lower precedence than the current subexpression.

If the operator is of equal precedence, what happens depends on the type of operator, which can be left-associative (close the current subexpression first), or right-associative (don't close; apply operator first).

E.g. 3 firstof 5 lastof "This is an example", will cause the 'lastof' to be applied before the 'firstof', even though they have equal precedence; it is a right-associating operator. No subexpression is closed here until the compiler sees the comma (which has a very low precedence).

If you want to play around with the behaviour language, go to the language subdirectory after building the server and type 'make testprogs'. This will produce an executable called 'langtest', which prompts you for an expression, shows the compiler output and the result of executing it in the VM.

As a reference, here is the full table of operators in order of precedence, taken almost literally from language/langcompile.c, roughly divided in categories: WARNING: This is outdated. Please refer to language/langcompile.c instead

NameContextPrecedenceType (LHS)Type (RHS)Ret.context

4.1. Miscellaneous (unary prefix)
haltnone32 (L)N.A.anyinteger
abortnone32 (L)N.A.anyinteger
delallnone32 (L)N.A.anyinteger
delnone32 (L)N.A.anyinteger
moveallnone32 (L)N.A.anyinteger

4.2. Conversion (unary postfix)

Note: these operators will probably be renamed or otherwise restructured soon. To make some sense out of it all, read "X asY" like "X viewed as Y". That's why the same operator name is sometimes associated with both the conversion from and to strings. I should probably make them NOPs and have them take advantage of the standard operand auto-conversion feature instead. Or something.

asrawinteger30 (L)N.A.N.A.string
asrawIP addr.30 (L)N.A.N.A.string
asrawdate30 (L)N.A.N.A.string
asstrinteger30 (L)N.A.N.A.string
asstrIP addr.30 (L)N.A.N.A.string
asstrdate30 (L)N.A.N.A.string
asintIP addr.30 (L)N.A.N.A.integer
asintstring30 (L)N.A.N.A.integer
asintstring30 (L)N.A.N.A.integer
asipinteger30 (L)N.A.N.A.IP addr.
asipstring30 (L)N.A.N.A.IP addr.
ashexintstring30 (L)N.A.N.A.integer
ashexipstring30 (L)N.A.N.A.IP addr.
ashexstring30 (L)N.A.N.A.string
ashexany (other)30 (L)N.A.N.A.string
asymdhms *1string30 (L)
asymdhmsdate / integer30 (L)N.A.N.A.string
asymd *1string30 (L)
asymddate / integer30 (L)N.A.N.A.string
ashms *1string30 (L)
ashmsdate / integer30 (L)N.A.N.A.string

4.3. Arithmetic, boolean, string transformations (right-associative unary prefix)
~none28 (R)N.A.integerinteger
-none28 (R)N.A.integerinteger
!none28 (R)N.A.anyinteger
md5none28 (R)N.A.stringstring
hexnone28 (R)N.A.stringstring

4.4. Arithmetic (left-associative binary)
*any25 (L)integerintegerinteger
/IP addr.25 (L)N.A.integerIP addr.
/any25 (L)integerintegerinteger
%any25 (L)integerintegerinteger
+any24 (L)integerintegerinteger
-any24 (L)integerintegerinteger
>>any23 (L)integerintegerinteger
<<any23 (L)integerintegerinteger
^integer22 (L)N.A.integerinteger
^IP addr.22 (L)N.A.IP addr.IP addr.
^string22 (L)N.A.stringstring
&any21 (L)integerintegerinteger
|any20 (L)integerintegerinteger

4.5. String operations (left- and right-associative binary)
asdate / integer18 (L)N.A.stringstring
beforefirstany16 (L)stringstringstring
afterfirstany16 (L)stringstringstring
beforelastany16 (L)stringstringstring
afterlastany16 (L)stringstringstring
firstofany16 (R)integerstringstring
lastofany16 (R)integerstringstring
.any14 (L)stringstringstring

4.6. Comparing (left-associative binary)
> *1string12 (L)N.A.stringinteger
>any (other)12 (L)integerintegerinteger
< *1string12 (L)N.A.stringinteger
<any (other)12 (L)integerintegerinteger
>= *1string12 (L)N.A.stringinteger
>=any (other)12 (L)integerintegerinteger
<= *1string12 (L)N.A.stringinteger
<=any (other)12 (L)integerintegerinteger
==string11 (L)N.A.stringinteger
==any (other)11 (L)integerintegerinteger
!=string11 (L)N.A.stringinteger
!=any (other)11 (L)integerintegerinteger

4.7. Assignment (right-associative binary)
=integer7 (R)N.A.integerinteger
=IP addr.7 (R)N.A.IP addr.IP addr.
=date7 (R)N.A.datedate
=string7 (R)N.A.stringstring
:=integer7 (R)N.A.integerinteger
:=IP addr.7 (R)N.A.IP addr.IP addr.
:=date7 (R)N.A.datedate
:=string7 (R)N.A.stringstring

4.8. Boolean / flow control (short-circuit binary, unary postfix)
&&any5 (R)anyanyrhs' type
||any4 (R)anylhs' typerhs' type
,any1 (L)anyN.A.none

Notes: *1 - Not implemented yet.

5. Automatic type conversion

As can be seen above, there are quite a number of operators that do nothing but convert between the various data types. However, in most cases you won't neeed to worry about them, as most type conversion happens automatically.

This works because operators expect certain types of terms around them, as shown above in the Type (LHS) and Type (RHS) columns. Even if the operator can be applied in any context, it can still specify that the term on its left must be converted to a particular type first. Eg. the string concatenation operator '.' is allowed in any context, but indeed requires a string on both sides.

Thus, 0xaa . "-abc", causes the numeric value 0xaa (or 170) to be converted to a (decimal) string first, giving the result "170-abc".

The same goes for the right side: when a new subexpression is compiled on the right side of an operator, the compiler is told that whatever the result is, it must be converted to a particular type after it ends, because that's what the operator expects.

So when the compiler has gotten to the point of the left paren in "abc-" . (12 + 3), it knows that just after the coming subexpression is ended, the 'convert to string' operator must be applied before the concatenation operator, giving the result "abc-15" here.

6. Interface calls

Interface calls are just operators. This operator is allowed only in context 'none', i.e. as a unary prefix operator, has a very high precedence, doesn't require any type for the subexpression on its right side (even ignores its result), and returns nothing meaningful.

So, Interface(int=3, str="abc"), is equivalent to int=3, Interface(str="abc"), and also to int=3, str="abc", Interface 0, - in each case, an 'int' and a 'str' instance are first added to the request list and then the interface 'Interface' is called.

It just *looks* kind of fancy - like some function call with named parameters, it also behaves a little like that - but in reality, it isn't fancy at all.

Often, you'll see something like Gofind(str:=User-Name), int && ( ... This calls the interface, and then applies the && operator to the last instance of the 'int' attribute on the reply list. It looks a bit like testing a return value, but in reality interface calls have no return value themselves.

Note also that the parens don't limit the scope of the assignments in any way; after the call, you still have the pseudo-parameters in your request list, and you still have to delete them explicitly if you want to get rid of them.

Appendix A. Operator usage reference


Appendix B. Example behaviour file

This is the working example file 'behaviour.sample-usersfile' that is included in OpenRADIUS v0.9.10 It uses a flat ASCII table for shared secrets and a Livingston-style users file for accounts and profiles.

# BEHAVIOUR - Expression that defines the server's operating rules
# This is compiled at startup and ran for every request that comes in.
# Upon entry, the REQUEST list of A/V pairs is already populated with
# information from and about the request. Upon exit, the REPLY list is
# used to build a response to send to the client.
# Other than the attributes / fixed fields you want to send, you need
# to set the first instance of the attribute 'Secret' (see subdicts/dict.
# internal) to the shared secret to be used for signing the response.
# You also need to set the first instance of the RAD-Authenticator
# attribute to the value that this attribute had in the original request; 
# i.e. copying the attribute from the REQUEST list to the REPLY list.
# The same goes for the RAD-Identifier attribute and all instances of
# the Proxy-State attribute. See RFC 2865.
# Then, when the expression completes without being aborted,
# the server will build the packet based on the attributes
# on the REPLY list, so at first also putting in the original request
# authenticator as the response authenticator. It then signs the
# packet using the shared secret provided on the REPLY list, putting the
# signature over the original response authenticator, to create a valid
# RADIUS response.
# See openradius-language.html for a list showing all operators, 
# with contexts, precedence, association and auto-conversion properties.  
# Also read this to understand that Attribute = Attribute references two
# different attributes, and how REQ: and REP: influence that.
# The && and || operators do short-circuit boolean evaluation as they do
# in C, Perl and shell scripts - that's how conditional subexpressions
# are implemented. 'and' and 'or' are synonyms.

# First, look up the client's secret by packet's source IP address; 
# log an error and drop the request if not found
# Set the first 'str' instance to IP-Source (type conversion is automatic), 
# call Clients and evaluate the returned 'str' instance as a boolean 
# (false if nonexistant or empty).

Clientsfile(str = IP-Source), 
str or (
  Log-Line = "Request received on " . IP-Dest . ":" . UDP-Dest . 
  	     " from unknown client " . IP-Source . " identified as NAS " . 
	     (NAS-Identifier or NAS-IP-Address) . " for user " . User-Name,

# Save returned string attribute in REP:Secret and delete the 'str' 
# attribute used as parameter for the Clientsfile call and the attributes 
# that were returned. Note that the 'del' operator has the REQUEST list 
# as default for lowercase attributes; it's a 'write' operator after all.

Secret = str, del str, del REP:int, del REP:str,

# Now we can create legitimate responses, initialise the reply list

RAD-Code = Access-Reject, 
RAD-Identifier = RAD-Identifier, 
RAD-Authenticator = "" . RAD-Authenticator,	# we need a copy
moveall Proxy-State,

# Create the start of a log line. We add to it as we proceed below. 
# The response type and RADIUS code are added by the server.

Log-Line = "from " . IP-Dest . ":" . UDP-Dest . " for request from NAS " . 
	   (NAS-Identifier or NAS-IP-Address) .
	   (NAS-Port exists and (" port " . NAS-Port)) .
	   " via " . IP-Source . " for " . User-Name .
	   (Calling-Station-Id and (" CLI " . Calling-Station-Id)),

# Add NAS-dependent data

Nasesfile(str = (NAS-Identifier or NAS-IP-Address)), 
del str, del REP:int,

# Add realm-dependent data. Also used to find local realms, i.e. realms
# that should be stripped before we process things further. I strongly
# believe in the home server stripping, not the upstream proxy, so that
# the home server can distinguish among multiple services.

str = (User-Name beforefirst "/" or User-Name afterlast "@"),
REQ:str and (
  Realmsfile 0, del REP:int,
  local-realm and REQ:User-Name := (User-Name afterfirst "/" or
				    User-Name beforelast "@" or
del str,

# We now take one of two separate paths that later join again, 
# depending on whether we received an accounting request or not.

RAD-Code != Accounting-Request and (

  # This is for authentication. Decrypt PAP password, if any; 
  # set CHAP-Challenge by copying it from the request authenticator 
  # if we're doing CHAP and it wasn't already there

  User-Password and (
    REQ:User-Password := (md5 (REP:Secret . RAD-Authenticator) ^ User-Password
    			  . "\x00") beforefirst "\x00",
    Log-Line := REP:Log-Line . " [" . User-Password . "]" 
  CHAP-Password and (
    CHAP-Challenge or (REQ:CHAP-Challenge = RAD-Authenticator)

  # Check for hardcoded (backdoor) users. Just a few examples 
  # that must of course be commented out when in production. This may
  # also be useful to allow a telecommuting administrator in when the
  # real backend database is down.
  #(User-Name == "evb" and User-Password == "pingping" or 
  # User-Name == "emile" and User-Password == "pingping" or 
  # User-Name == "evbergen" and User-Password == "pingping") and (
  #  Reply-Message := "You rang, milord?\n",
  #  Service-Type = Administrative,
  #  accept
  #User-Name == "backdoor" and User-Password == "user" and (
  #  Reply-Message := "Welcome, backdoor user.\n",
  #  Service-Type = Framed,
  #  Framed-Protocol = PPP,
  #  accept
  #User-Name == "staff" and User-Password == "member" and (
  #  Reply-Message := "Hi, staff member. You're coming in on" . 
  #		      " NAS " . (NAS-Identifier or NAS-IP-Address) . 
  #		      " port " . NAS-Port . ".\nEnjoy.\n",
  #  Service-Type = Administrative,
  #  accept

  # Find user in users file and get attributes from it. Reject right 
  # here if REP:auth-type is Reject, and accept without checking 
  # passwords if REP:auth-type is Accept.

  Usersfile(str = User-Name), 
  del str, del REP:int,

  auth-type == Reject and reject,
  auth-type == Accept and accept,

  # Handle both supported authentication types (either PAP or CHAP) 
  # if the users file gave us a cleartext password

  clear-password and (

    # See if we're doing PAP

    User-Password exists and (

      # PAP: check and done

      User-Password == clear-password and accept,
      Reply-Message = "PAP authentication failed. Access denied.",

    # See if we're doing CHAP

    CHAP-Password exists and (

      # CHAP: check and done

      16 lastof CHAP-Password == md5 (1 firstof CHAP-Password .
				      clear-password . CHAP-Challenge) 
      and accept,
      Reply-Message = "CHAP authentication failed. Access denied.",

    # Apparently neither, but the users file _did_ contain 
    # clear-password: reject user


  # Handle Md5-Hex style hashed password (PAP only) if the users
  # file returned a md5-hex-password attribute. 
  # A cleartext PAP password is checked against a stored md5-hex-password by 
  # adding the first 4 octets of the md5-hex-password (the salt) to the PAP 
  # password, calculating md5 over the whole, converting the resulting 16 
  # octets to 32 hexadecimal digits and comparing those to the last 32 octets
  # of the md5-hex-password.
  # This is similar to the crypt(3) algorithm, but uses MD5 instead of DES and
  # a salt up to 32 bits (24 when using the same charset, 16 when using only
  # hexadecimal digits in the salt) instead of 12 bits.

  REP:md5-hex-password exists and User-Password exists and (
    32 lastof REP:md5-hex-password == 
        hex md5 (User-Password . 4 firstof REP:md5-hex-password) 
    and accept,
    Reply-Message = "MD5-Hex authentication failed. Access denied.",

  # Add other authentication schemes here.

1) or (

  # Handle accounting. First verify request authenticator. Note that 
  # REP:RAD-Authenticator contains a saved copy of REQ:RAD-Authenticator

  REQ:Acct-Authenticator = Mismatch,
  REQ:RAD-Authenticator pokedwith "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
  REP:RAD-Authenticator == md5 (RAD-Packet . REP:Secret) 
  and REQ:Acct-Authenticator := Verified,

  # Next, create unique database key based on NAS, timestamp and session ID.
  # No single NAS will reuse the same session ID in the same second.

  REQ:Record-Unique-Key = hex (NAS-IP-Address toraw . Timestamp toraw) .

  # Extend summary log line
  Log-Line := REP:Log-Line . " signature " . 
	      (Acct-Authenticator and "Verified" or "MISMATCH" ) . " key " . 

  # Always log it; drop request if that fails
  Acctlogger(str = Timestamp as "%c"),
  int or abort,
  del REP:int

# If we weren't able yet to answer an authentication request, and for
# all accounting requests, we continue here.
# If somehow we obtained one or more Target-Server attributes, proxy.

REP:Target-Server exists and (

  # for such things you'd want negative ACLs, but we don't have them yet.
  delall REQ:RAD-Identifier,
  delall REQ:RAD-Length,
  delall REQ:RAD-Authenticator,
  delall REQ:RAD-Attributes,

  # strip realm before proxying if asked to
  strip-realm and REQ:User-Name := (User-Name afterfirst "/" or
				    User-Name beforelast "@" or

  # proxy; drop request if radclient gave us a real error
  Log-Line := REP:Log-Line . " proxied as " . User-Name . " to " . 
  Radiusclient(moveall REP:Target-Server),
  Log-Line := REP:Log-Line . " resulting in " . REP:RAD-Code . " (" . int . ")",
  int < 64 or abort,
  del REP:int,

  # for such things you'd want negative ACLs, but we don't have them yet.
  del F:RAD-Code,
  del RAD-Identifier,
  del RAD-Length,
  del RAD-Authenticator,
  del RAD-Attributes,

  # We rely on the home server and our receive ACL to keep inappropriate 
  # attributes from rejects and accounting responses. If you want to be 
  # really sure that we follow our own dictionary in this respect, uncomment 
  # the following two lines.
  #REP:RAD-Code == Accounting-Response and acctresp,
  #REP:RAD-Code == Access-Reject and reject,


# Are you still here? We don't really know what to do,
# but these should be some sensible default actions for 
# access requests and accounting requests.

RAD-Code == Accounting-Request and acctresp,
RAD-Code == Access-Request and reject,

Generated on Sat Jul 2 01:18:04 2011 by /