[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17. New Configuration Approach (draft)

(This message will disappear, once this node revised.)

This document presents a draft describing new approach for processing RADIUS requests. It is intended as a request for comments, and, in the long run, as a guide for GNU Radius developers. In its current state it is far from being complete. Please check http://www.gnu.org/software/radius/manual for updated versions. Feel free to send your comments and suggestions to bug-gnu-radius@gnu.org.

17.1 A brief description of Currently Used Approach  
17.2 Deficiencies of Current Operation Model and Configuration Suite  
17.3 Proposed Solution  A Proposed Solution
17.4 Changes to Rewrite Language  
17.5 Support for Traditional Configuration Files.  
17.6 New Configuration Files  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.1 A brief description of Currently Used Approach

When I started to write GNU Radius, back in 1998, I had two major aims. The first and primary aim was to create a flexible and robust system that would follow the principle of Jon Postel:

Be liberal in what you accept and conservative in what you send.

This, I believe, is the main principle of any good software for Internet.

The second aim was to be backward compatible with the implementations that already existed back then. This seemed to be important (and the time has proved it was), because it would allow users to easily switch from older radius daemon to GNU Radius.

An important part of every complex program is its configuration file. Traditional implementations of RADIUS servers (beginning from Livingston Radius) used a configuration suite consisting of several files, usually located in `/etc/raddb' subdirectory. Its main components were:

`dictionary'
A file containing translations of symbolic names of radius attributes and attribute values to their integer numbers as specified by RADIUS protocol.

`hints'
This file was intended to separate incoming requests in groups, based on the form of their login name. Traditionally such separation was performed basing on common prefixes and/or suffixes of login names.

`huntgroups'
The purpose of this file was to separate incoming requests depending on their source, i.e. on the NAS that sent them and the port number on that NAS. It also served as a sort of simplified access control list.

`users'
This file contained a users database. It described criteria for authentication and reply pairs to be sent back to requesting NASes.

Among these files, the first two were used for requests of any kind, whereas `users' was used only for Access-Request packets.

Though this configuration system suffered from many inconsistencies, the second aim required GNU Radius to use this approach.

To compensate for its deficiencies and to fulfill the first aim, this configuration system was extended, while preserving its main functionality. A number of additional internal attributes were added, that control radiusd behavior. A new language was created whose main purpose was to modify incoming requests (see section 11.2 Rewrite). The support for GNU's Ubiquitous Intelligent Language for Extensions (see section 11.3 Guile) was added, that allowed to further extend GNU Radius functionality.

The present operation model(7) of GNU Radius and its configuration file system(8) emerged as a result of the two development aims described above. Since 1998 up to present, GNU Radius users contributed a lot of ideas and code to the further development of the system.

However, it became obvious that this system presents strong obstacles to the further development. The next section addresses its deficiencies.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.2 Deficiencies of Current Operation Model and Configuration Suite

The main deficiencies are inherited with the traditional configuration file suite. The rules for processing each request are split among three files, each of which is processed differently, despite of their external similarity. The administrator has to keep in mind a set of exotic rules when configuring the system(9). When matching incoming requests with configuration file entries (LHS, see section 3.3 Matching Rule), some attributes are taken verbatim, whereas others are used to control radiusd behavior and to pass additional data to other rules (see section 14.3 Radius Internal Attributes). The things become even more complicated when RADIUS realms come into play (see section 3.4.2.1 Proxy Service). Some attributes are meaningful only if used in a certain part of a certain configuration file rule.

So, while being a lot more flexible than the approach used by other RADIUS implementations, the current system is quite difficult to maintain.

Another deficiency is little control over actions executed on different events. For example, it is often asked how can one block a user account after a predefined number of authentication failures? Currently this can only be done by writing an external authentication procedure (either in Scheme, using Guile, or as a standalone executable, using Exec-Program-Wait). The proper solution would be to have a set of user-defined triggers for every RADIUS event (in this case, for authentication failure).

Another commonly asked question is how to make radiusd execute several SQL queries when processing a request. While GNU Radius is not supposed to compensate for deficiencies of some SQL implementations that do not allow for nested queries, such a feature could come quite handy.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3 Proposed Solution

(This message will disappear, once this node revised.)

Processing of incoming requests is controlled by request-processing program. Request-processing program is a list-like structure, consisting of instructions.

17.3.1 Request-processing Instruction  
17.3.2 grad_instr_conditional  
17.3.3 grad_instr_call  
17.3.4 grad_instr_return  
17.3.5 grad_instr_action  
17.3.6 grad_instr_reply  
17.3.7 grad_instr_proxy  
17.3.8 grad_instr_forward  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.1 Request-processing Instruction

Request-processing program consists of instructions. There are seven basic instruction types:

grad_instr_conditional_t
This instruction marks a branch point within the program.

grad_instr_call_t
Represents a call of a subprogram

grad_instr_action_t
Invokes a Rewrite <FIXME> or Scheme? </> function

grad_instr_proxy_t
Proxies a request to the remote server

grad_instr_forward_t
Forwards a request to the remote server

grad_instr_reply_t
Replies back to the requesting NAS.

Consequently, an instruction is defined as a union of the above node types:

Instruction: grad_instr_t
 
enum grad_instr_type
{
  grad_instr_conditional,
  grad_instr_call,
  grad_instr_return,
  grad_instr_action,
  grad_instr_reply,
  grad_instr_proxy,
  grad_instr_forward
};

typedef struct grad_instr grad_instr_t;

struct grad_instr
{
  enum grad_instr_type type;
  grad_instr_t *next;
  union
    {
      grad_instr_conditional_t cond;
      grad_instr_call_t call;
      grad_instr_action_t action;
      grad_instr_reply_t reply;
      grad_instr_proxy_t proxy;
      grad_instr_forward_t forward;
    } v;                                                             
};

Type member contains type of the instruction. The evaluator uses type to determine which part of union v, holds instruction-specific data.

Next points to the next instruction. The evaluator will go to this instruction unless the present one changes the control flow.

Finally, v contains instruction-specific data. These will be discussed in the following subsections.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.2 grad_instr_conditional

(This message will disappear, once this node revised.)

Instruction: grad_instr_conditional_t cond iftrue iffalse
 
struct grad_instr_conditional
{
  grad_entry_point_t cond;  /* Entry point to the compiled
                               Rewrite condition */
  grad_instr_t *iftrue;     /* Points to the ``true'' branch  */
  grad_instr_t *iffalse;    /* Points to the ``false'' branch  */
};
typedef struct grad_instr_conditional grad_instr_conditional_t;

Instructions of type grad_instr_conditional_t indicate branching. Upon encountering an grad_instr_conditional_t, the engine executes a Rewrite expression pointed to by cond. If the expression evaluates to true, execution branches to instruction iftrue. Otherwise, if iffalse is not NULL, execution branches to that instruction. Otherwise, the control flow passes to grad_instr_t.next, as described in the previous section.

RPL representation

RPL defun: COND expr if-true [if-false]

expr
Textual representation of Rewrite conditional expression or its entry point.
if-true
RPL expression executed if expr evaluates to t.
if-true
Optional RPL expression that is executed if expr evaluates to nil.

Example

COND with two arguments:

 
(COND "%[User-Name] ~= \"test-.*\""
      (REPLY Access-Reject ("Reply-Message" . "Test accounts disabled")))

COND with three arguments:

 
(COND "%[Hint] == "PPP" && authorize(PAM)"
      (REPLY Access-Accept
             ("Service-Type" . "Framed-User")
             ("Framed-Protocol" . "PPP"))
      (REPLY Access-Reject
             ("Reply-Message" . "Access Denied")))


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.3 grad_instr_call

(This message will disappear, once this node revised.)

Instruction: grad_instr_call_t entry
 
struct grad_instr_call {
       grad_instr_t *entry;    
};
typedef struct grad_instr_call grad_instr_call_t;

Instructions of type grad_instr_call instruct the engine to call the given subprogram. The engine pushes the current instruction <FIXME> definition of current instruction or pc? </> to the return point stack <FIXME> definition of this? </> and branches to instruction entry. Execution of the subprogram ends when the engine encounters an instruction of one of the following types: grad_instr_return, grad_instr_reply or grad_instr_proxy.

If grad_instr_return is encountered, the engine pops the instruction from the top of the return point stack and makes it current instruction, then it branches to the next node.

If grad_instr_reply or grad_instr_proxy is encountered, the engine, after executing corresponding actions, finishes executing the program.

RPL representation

RPL defun: CALL list
RPL defun: CALL defun-name
In the first form, the argument list is the RPL subprogram to be executed.

In the second form defun-name is a name of the RPL subprogram defined by defun.

Examples

First form:

 
(CALL (ACTION "myfun(%[User-Name])")
      (REPLY Access-Reject
             ("Reply-Message" . "Access Denied")))

Second form:

 
(CALL process_users)


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.4 grad_instr_return

(This message will disappear, once this node revised.)

An instruction of type grad_instr_return indicates a return point from the subprogram. If encountered in a subprogram (i.e. a program entered by grad_instr_call node), it indicates return to the calling subprogram (see the previous subsection). Otherwise, if grad_instr_return is encountered within the main trunk, it ends evaluating of the program.

Instructions of this type have no data associated with them in union v.

RPL representation

RPL defun: RETURN

Examples

 
(RETURN)


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.5 grad_instr_action

(This message will disappear, once this node revised.)

Instruction: grad_instr_reply_t expr
 
struct grad_instr_action {
       grad_entry_point_t expr;    /* Entry point to the compiled
                                      Rewrite expression */
};
typedef struct grad_instr_action grad_instr_reply_t;

The machine executes a Rewrite expression with entry point expr. Any return value from the expression is ignored. <FIXME> Should the expression receive any arguments? If so, what arguments? I'd say it should take at least the request being processed and the reply pairs collected so far. </>

RPL representation

RPL defun: ACTION expr
RPL defun: ACTION entry-point
<FIXME> Description </>

Examples

 
(ACTION "%[NAS-IP-Address] = request_source_ip()")


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.6 grad_instr_reply

(This message will disappear, once this node revised.)

Instruction: grad_instr_reply_t return_code
 
struct grad_instr_reply {
       u_char reply_code;         /* Radius request code */
};
typedef struct grad_instr_reply grad_instr_reply_t;

grad_instr_reply instructs radiusd to send to the requesting NAS a reply with code reply_code. Any reply pairs collected while executing the program are attached to the reply.

After executing grad_instr_reply instruction, the engine stops executing of the program.

Any execution path will usually end with this instruction.

RPL representation

RPL defun: REPLY reply-code [attr-list]

Arguments:

reply-code
Radius reply code.
attr-list
List of A/V pairs to be added to the reply. Each A/V pair is represented as a cons: (name-or-number . value).

Example

 
(REPLY Access-Accept
       ("Service-Type" . "Framed-User")
       ("Framed-Protocol" . "PPP"))


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.7 grad_instr_proxy

(This message will disappear, once this node revised.)

Instruction: grad_instr_proxy_t realm
 
struct grad_instr_proxy
{
  grad_realm_t realm;
};
typedef struct grad_instr_proxy grad_instr_proxy_t;

This instruction tells radius to proxy the request to the server defined in realm. In other words, the engine executes proxy_send. Further processing of the program is stopped.

RPL representation

RPL defun: PROXY realm-name
Realm-name is name of the realm as defined in `raddb/realms'. <FIXME> No, no. That's senseless. I must get rid of `raddb/*'! </>

Examples

<FIXME> Fix the above definition, then provide an example </> .


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.8 grad_instr_forward

(This message will disappear, once this node revised.)

Instruction: grad_instr_forward_t server_list
 
struct grad_instr_forward
{
  grad_list_t server_list; 
};
typedef struct grad_instr_forward grad_instr_forward_t;

This node forwards the request to each servers from server_list. Forwarding differs from proxying in that the requests are sent to the remote servers and processed locally. The remote server is not expected to reply. See section forwarding, for more information on this subject.

In contrast to grad_instr_proxy, this instruction type does not cause the execution to stop.

Elements of server_list are of type grad_server_t.

Currently forwarding is performed by forward_request function (`forward.c'), which could be used with little modifications. Namely, it will be rewritten to get server list as argument, instead of using static variable forward_list. Consequently, the functions responsible for creating and initializing this static variable will disappear along with the variable itself. <FIXME> Ok, but what shall we do with forward statement in `raddb/config'? I should address this issue in the section dedicated to backward compatibility </> .


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.3.9 RPL representation

RPL defun: FORWARD server-list
<FIXME> What's in server-list? </>


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.4 Changes to Rewrite Language

(This message will disappear, once this node revised.)


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.5 Support for Traditional Configuration Files.

(This message will disappear, once this node revised.)

Within the new configuration system, the traditional "trio" `hints-huntgroups-users' will be translated to the following program:

 
(defprog main
 (CALL hints)
 (CALL huntgroups)
 (COND "request_code() == Access-Request"
       (CALL users))
 (REPLY Access-Reject
        (Reply-Message . "\nAccess denied\n")))

For example, consider the following configuration:

 
# raddb/hints:
DEFAULT Prefix = "PPP" Hint = PPP

This will produce the following program:

 
(defprog hints
 (COND "%[Prefix] == \"PPP\"")
       (ACTION "%[Hint] = \"PPP\""))

 
#raddb/huntgroups
DEFAULT NAS-IP-Address = 10.10.4.1      Suffix = "staff"
DEFAULT NAS-IP-Address = 10.10.4.2      Huntgroup-Name = "second"

Will produce

 
(defprog huntgroups
 (COND "%[NAS-IP-Address] == 10.10.4.1 && !(%[Suffix] == \"staff\")"
       (REPLY Access-Reject
              ("Reply-Message" . "Access Denied by Huntgroup")))
 (COND "%[NAS-IP-Address] == 10.10.4.2"
       (ACTION "%[Huntgroup-Name] = \"second\"")))

Finally, `users':

 
#raddb/users
DEFAULT Hint = "PPP",
               Auth-Type = PAM
        Service-Type = Framed-User,
               Framed-Protocol = PPP

DEFAULT Huntgroup-Name = "second",
               Auth-Type = PAM
        Service-Type = "Authenticate-Only",
               Reply-Message = "Authentity Confirmed"

will produce

 
(defprog users
 (COND "%[Hint] == "PPP" && authorize(PAM)"
       (REPLY Access-Accept
             (Service-Type . Framed-User)
             (Framed-Protocol . PPP))
       (REPLY Access-Reject
             (Reply-Message . "Access Denied")))
 (COND "%[Huntgroup-Name] == \"second\" && authorize(PAM)"
       (REPLY Access-Accept
              (Service-Type . "Authenticate-Only")
              (Reply-Message . "Authentity Confirmed"))))


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

17.6 New Configuration Files

(This message will disappear, once this node revised.)


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Sergey Poznyakoff on November, 20 2004 using texi2html