[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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:
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] | [ ? ] |
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] | [ ? ] |
(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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Request-processing program consists of instructions. There are seven basic instruction types:
grad_instr_conditional_t
grad_instr_call_t
grad_instr_action_t
grad_instr_proxy_t
grad_instr_forward_t
grad_instr_reply_t
Consequently, an instruction is defined as a union of the above node types:
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] | [ ? ] |
(This message will disappear, once this node revised.)
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.
t
.
nil
.
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] | [ ? ] |
(This message will disappear, once this node revised.)
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.
In the second form defun-name is a name of the RPL subprogram
defined by defun
.
First form:
(CALL (ACTION "myfun(%[User-Name])") (REPLY Access-Reject ("Reply-Message" . "Access Denied"))) |
Second form:
(CALL process_users) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(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
.
(RETURN) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(This message will disappear, once this node revised.)
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. </>
(ACTION "%[NAS-IP-Address] = request_source_ip()") |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(This message will disappear, once this node revised.)
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.
Arguments:
cons
: (name-or-number . value)
.
(REPLY Access-Accept ("Service-Type" . "Framed-User") ("Framed-Protocol" . "PPP")) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(This message will disappear, once this node revised.)
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(This message will disappear, once this node revised.)
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] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(This message will disappear, once this node revised.)
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(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] | [ ? ] |
(This message will disappear, once this node revised.)
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |