
    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

    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, 10.0.0.1, 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 == 192.168.5.32 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:

   Name        Context        Precedence Type (LHS) Type (RHS) Ret.context

4.1. Miscellaneous (unary prefix)

   halt        none           32 (L)     N.A.       any        integer
   abort       none           32 (L)     N.A.       any        integer
   delall      none           32 (L)     N.A.       any        integer
   del         none           32 (L)     N.A.       any        integer
   moveall     none           32 (L)     N.A.       any        integer

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.
   asraw       integer        30 (L)     N.A.       N.A.       string
   asraw       IP addr.       30 (L)     N.A.       N.A.       string
   asraw       date           30 (L)     N.A.       N.A.       string
   asstr       integer        30 (L)     N.A.       N.A.       string
   asstr       IP addr.       30 (L)     N.A.       N.A.       string
   asstr       date           30 (L)     N.A.       N.A.       string
   asint       IP addr.       30 (L)     N.A.       N.A.       integer
   asint       string         30 (L)     N.A.       N.A.       integer
   asint       string         30 (L)     N.A.       N.A.       integer
   asip        integer        30 (L)     N.A.       N.A.       IP addr.
   asip        string         30 (L)     N.A.       N.A.       IP addr.
   ashexint    string         30 (L)     N.A.       N.A.       integer
   ashexip     string         30 (L)     N.A.       N.A.       IP addr.
   ashex       string         30 (L)     N.A.       N.A.       string
   ashex       any (other)    30 (L)     N.A.       N.A.       string
   asymdhms *1 string         30 (L)     N.A.       N.A.       date
   asymdhms    date / integer 30 (L)     N.A.       N.A.       string
   asymd *1    string         30 (L)     N.A.       N.A.       date
   asymd       date / integer 30 (L)     N.A.       N.A.       string
   ashms *1    string         30 (L)     N.A.       N.A.       date
   ashms       date / integer 30 (L)     N.A.       N.A.       string

4.3. Arithmetic, boolean, string transformations (right-associative unary
prefix)

   ~           none           28 (R)     N.A.       integer    integer
   -           none           28 (R)     N.A.       integer    integer
   !           none           28 (R)     N.A.       any        integer
   md5         none           28 (R)     N.A.       string     string
   hex         none           28 (R)     N.A.       string     string

4.4. Arithmetic (left-associative binary)

   *           any            25 (L)     integer    integer    integer
   /           IP addr.       25 (L)     N.A.       integer    IP addr.
   /           any            25 (L)     integer    integer    integer
   %           any            25 (L)     integer    integer    integer
   +           any            24 (L)     integer    integer    integer
   -           any            24 (L)     integer    integer    integer
   >>          any            23 (L)     integer    integer    integer
   <<          any            23 (L)     integer    integer    integer
   ^           integer        22 (L)     N.A.       integer    integer
   ^           IP addr.       22 (L)     N.A.       IP addr.   IP addr.
   ^           string         22 (L)     N.A.       string     string
   &           any            21 (L)     integer    integer    integer
   |           any            20 (L)     integer    integer    integer

4.5. String operations (left- and right-associative binary)

   as          date / integer 18 (L)     N.A.       string     string
   beforefirst any            16 (L)     string     string     string
   afterfirst  any            16 (L)     string     string     string
   beforelast  any            16 (L)     string     string     string
   afterlast   any            16 (L)     string     string     string
   firstof     any            16 (R)     integer    string     string
   lastof      any            16 (R)     integer    string     string
   .           any            14 (L)     string     string     string

4.6. Comparing (left-associative binary)

   > *1        string         12 (L)     N.A.       string     integer
   >           any (other)    12 (L)     integer    integer    integer
   < *1        string         12 (L)     N.A.       string     integer
   <           any (other)    12 (L)     integer    integer    integer
   >= *1       string         12 (L)     N.A.       string     integer
   >=          any (other)    12 (L)     integer    integer    integer
   <= *1       string         12 (L)     N.A.       string     integer
   <=          any (other)    12 (L)     integer    integer    integer
   ==          string         11 (L)     N.A.       string     integer
   ==          any (other)    11 (L)     integer    integer    integer
   !=          string         11 (L)     N.A.       string     integer
   !=          any (other)    11 (L)     integer    integer    integer

4.7. Assignment (right-associative binary)

   =           integer        7 (R)      N.A.       integer    integer
   =           IP addr.       7 (R)      N.A.       IP addr.   IP addr.
   =           date           7 (R)      N.A.       date       date
   =           string         7 (R)      N.A.       string     string
   :=          integer        7 (R)      N.A.       integer    integer
   :=          IP addr.       7 (R)      N.A.       IP addr.   IP addr.
   :=          date           7 (R)      N.A.       date       date
   :=          string         7 (R)      N.A.       string     string

4.8. Boolean / flow control (short-circuit binary, unary postfix)

   &&          any            5 (R)      any        any        rhs' type
   ||          any            4 (R)      any        lhs' type  rhs' type
   ,           any            1 (L)      any        N.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. 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 why.
#
# Then, when the expression completes (that is without being aborted),
# the server will first build the packet only based on the attributes
# on the REPLY list, so at first also putting in the original request
# authenticator as the response authenticator. It will then sign 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 neatly showing all operators,
# with contexts, precedence, association and auto-conversion properties.
#
# The && and || operators do short-circuit boolean evaluation as they do
# in C, Perl and shell scripts - that's how conditional subexpressions
# are implemented.
#
# This language has hardly any syntax, other than that a term may be
# not be directly followed by another term.
#
##
#
# This is an expression that goes together with the distributed sample
# 'configuration' file. It allows RADIUS clients to be listed in an
# ASCII file .../raddb/legacy/clients, and users in .../raddb/legacy/users
# (paths are dependent on what's specified in the configuration file).
#
# More specifically, this file provides the following rules:
#
# 1.  look up the secret for the radius client by IP address, using the
#     Oldclients interface that uses the ascfile module to return
#     information from a flat file that lists the IP addresses and their
#     secrets next to each other, normally .../raddb/legacy/clients;
#
# 2.  if not found, log the event using the Errorlogger interface that
#     uses the radlogger shell script module to log the message, normally
#     in /var/log/raderr.log and drops the request. Otherwise continue:
#
# 3.  initialise the reply list by copying the Identifier, Authenticator
#     and all Proxy-State attributes from the request;
#
# 4.  see if we're accounting. If so, verify the request authenticator
#     and put the result in Acct-Authentic, log the information using
#     the Acctlogger interface that uses the radlogger module. If that
#     didn't succeed, drop the request; otherwise, set RAD-Code to
#     Accounting-Response and halt the expression so that the client is
#     answered. But if we're authenticating:
#
# 5.  set RAD-Code to Access-Reject and Reply-Message to something nasty,
#     so we can send a valid response that rejects users by simply
#     halting the expression at any point;
#
# 6.  if a PAP password was supplied in the request, decrypt that
#     using the shared secret that was found for this client.
#     Otherwise, and if also a CHAP password was supplied, add a
#     CHAP-Challenge attribute with the value of the request
#     authenticator if there wasn't already one present in the request;
#
# 7.  log the request using the Stdlogger interface that uses the
#     radlogger shell script to log the username, password, CHAP
#     challenge and password, NAS IP and port, as per the send ACL
#     defined in the configuration file for the Stdlogger interface;
#
# 8.  check if this user is somebody who may always get in (backdoor)
#     when using a particular username and PAP password, if so, change
#     RAD-Code to Access-Accept, change Reply-Message to someting nice
#     and halt. Otherwise go on:
#
# 9.  look up the user using the Oldusers interface that uses the
#     ascfile module to get reply attributes for the user from a
#     Livingston-style users file, normally .../raddb/legacy/users.
#     (Note that ascfile itself doesn't look at check items - it just
#     returns all attributes and the server will add them all to the
#     reply list. So any compares need to be done here);
#
# 10. See if the (internal) auth-type attribute is now present on the reply
#     list, after calling Oldusers. If it is and it has the value
#     Reject, halt as we're done. If it has the value Accept, set
#     RAD-Code to Access-Accept, delete the first (nasty) instance of
#     the Reply-Message attribute and halt, accepting the user with a
#     possible reply message set by the users file. Otherwise, ignore
#     the value and go on:
#
# 11. see if the clear-password attribute was set. If so and we have a
#     PAP password, just compare it (we decrypted it earlier) and if it
#     matches, we set RAD-Code to Access-Accept and delete the first
#     Reply-Message before halting, otherwise:
#
# 12. see if (the clear-password attribute was set and) we have a CHAP
#     password, if so, perform CHAP by comparing it to the
#     clear-password hashed with the CHAP-Challenge. If it matches, set
#     RAD-Code to Access-Accept and delete the first Reply-Message
#     before halting, otherwise:
#
# 13. if indeed the clear-password was set and we neither have a PAP nor
#     a CHAP password, reject the user;
#
# 14. if the md5-hex-password attribute was set, see if we have a PAP
#     password. If not, halt, otherwise, calculate md5 over the PAP
#     password followed by the first 4 characters of the
#     md5-hex-password, write the hash as hex, compare it to the last 32
#     characters of the md5-hex-password and if it matches, set RAD-Code
#     to Access-Accept and delete the first Reply-Message before
#     halting.
#
#     (Note that this looks quite like the crypt(3) algorithm, but using
#     a 16-bit salt, and using md5 instead of some DES hash.  This is
#     also a bit different from the md5 passwords supported in
#     /etc/passwd by some crypt implementations: there, base64 is used
#     instead of hex. But as I haven't implemented a base64 operator
#     yet, I can't support that here for now.


###########################################################################
#
# Step 1 & 2: Look up client's secret by packet's source IP address; log
# error and drop request if not found

# Set the first 'str' instance to IP-Source (type conversion is
# automatic), call Oldclients and evaluate the returned 'str' instance
# as a boolean (false if nonexistant or empty).

Oldclients(str=IP-Source), str || (

        Errorlogger(str=Timestamp as "%c" . ": Request from unknown client " .
                        IP-Source . " identified as NAS " . (NAS-Identifier ||
                                                             NAS-IP-Address) .
                        " for user " . User-Name . " - ignored."),
        abort
),

# Save returned string attribute in REP:Secret and delete the parameter
# and the returned attributes. Note that the 'del' operator has the
# REQUEST list as default for lowercase attributes because of rule c.

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


###########################################################################
#
# Step 3: Initialise reply list

RAD-Identifier = RAD-Identifier,
RAD-Authenticator = RAD-Authenticator,
RAD-Length = 20,                        # Dirty hack. Is overwritten by
                                        # encode() if there are any real
                                        # attributes.

RAD-Code = Access-Reject,               # Another dirty hack, but needed
                                        # now to make sure all RAD-PKT
                                        # attributes appear in the reply
                                        # list before the ones from
                                        # RAD-ATR. buildtree() does not
                                        # yet sort on encapsulation
                                        # depth... :(
moveall Proxy-State,


###########################################################################
#
# Step 4: Handle accounting

RAD-Code == Accounting-Request && (

        # Verify request authenticator. Note that the ',1' in the
        # subexpression after the '&&' makes the '||' following it
        # behave like a logical 'else'.

        RAD-Authenticator == md5 (RAD-Code asraw .
                                  RAD-Identifier asraw .
                                  RAD-Length asraw .
                                  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" .
                                  RAD-Attributes .
                                  REP:Secret) && (

                REQ:Acct-Authentic = Verified,
        1) || (
                REQ:Acct-Authentic = Mismatch
        ),

        # Always log it; drop request if that fails

        Acctlogger(str=Timestamp as "%c",
                   REQ:Record-Unique-Key=NAS-IP-Address asraw ashex .
                                         Timestamp asraw ashex .
                                         Acct-Session-Id), int || abort,
        del str, del REP:int,

        # Set response code and halt the expression

        RAD-Code := Accounting-Response,
        halt
),


###########################################################################
#
# Step 5: Now we're authenticating, set defaults to reject when halted,
# so we can do so at anytime

# RAD-Code = Access-Reject, # Not needed anymore - already done
Reply-Message = "I don't know you. Go away.\n",


###########################################################################
#
# Step 6: Decrypt PAP password or if we're doing CHAP, set
# CHAP-Challenge by copying it from the request authenticator if it
# wasn't already present

User-Password && (

        REQ:User-Password := User-Password ^ md5 (REP:Secret .
                                                  RAD-Authenticator)
                             beforefirst "\x00"
),

CHAP-Password && (

        CHAP-Challenge || (REQ:CHAP-Challenge = RAD-Authenticator)

),


###########################################################################
#
# Step 7: Log the request using the Stdlogger interface

Stdlogger(str=Timestamp as "%c"),
del str, del REP:int,


###########################################################################
#
# Step 8: Check for hardcoded (backdoor) users

(User-Name == "evb" && User-Password == "pingping" ||
 User-Name == "emile" && User-Password == "pingping" ||
 User-Name == "evbergen" && User-Password == "pingping") && (

        RAD-Code := Access-Accept,
        Reply-Message := "You rang, milord?\n",
        Service-Type = Administrative,
        halt
),

User-Name == "backdoor" && User-Password == "user" && (

        RAD-Code := Access-Accept,
        Reply-Message := "Welcome, backdoor user.\n",
        Service-Type = Framed,
        Framed-Protocol = PPP,
        halt
),

User-Name == "staff" && User-Password == "member" && (

        RAD-Code := Access-Accept,
        Reply-Message := "Welcome, staff member. You're coming in on NAS " .
                         (NAS-Identifier || NAS-IP-Address) . " on port " .
                         NAS-Port . ".\nEnjoy.\n",
        Service-Type = Administrative,
        halt
),


###########################################################################
#
# Step 9 & 10: Find user in legacy-style users file and get attributes
# from it. Reject if not found. Also reject right here if REP:auth-type
# is Reject, and accept without checking passwords if REP:auth-type is
# Accept.

Oldusers(str=User-Name), int || halt,
del str, del REP:int,

auth-type == Reject && halt,
auth-type == Accept && (

        RAD-Code := Access-Accept,
        Reply-Message := "Welcome, user with any password.\n",
        halt
),


###########################################################################
#
# Step 11, 12 and 13: Handle supported authentication types if we have a
# cleartext password (that is either PAP or CHAP).

clear-password && (

        # See if we're doing PAP

        User-Password && (

                # PAP: check and done

                User-Password == clear-password && (

                        RAD-Code := Access-Accept,
                        Reply-Message := "Welcome, PAP/Login user.\n"
                ),

                halt
        ),

        # See if we're doing CHAP

        CHAP-Password && (

                # CHAP: check and done

                CHAP-Password == CHAP-Challenge ^
                                 md5 (1 firstof CHAP-Password .
                                      clear-password .
                                      CHAP-Challenge) && (

                        RAD-Code := Access-Accept,
                        Reply-Message := "Welcome, CHAP user.\n"
                ),

                halt
        ),

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

        halt
),


###########################################################################
#
# Step 14: Handle Md5-Hex style hashed password (PAP only) if the users
# file returned a md5-hex-password attribute. See step 14 above for more
# information about this type of password.
#
# Note that the 'REP' override is present to avoid the server searching
# for a password attribute, which it'd want to convert to numeric, negate,
# convert to string, rewrite as hex, convert to numeric, negate, convert
# to string, and do md5 over. Heh.

REP:md5-hex-password && (

        User-Password || halt,

        32 lastof REP:md5-hex-password ==
                hex md5 (User-Password . 4 firstof REP:md5-hex-password) && (

                RAD-Code := Access-Accept,
                Reply-Message := "Welcome, Login user with md5 password.\n"
        ),

        halt
),


###########################################################################
#
# Halt, rejecting the user

halt
