Modules2
From FreeRADIUS Wiki
Creating a Module (Version 2)
FreeRADIUS provides several different mechanisms to add site specific authentication and accounting procedures. The traditional approach was to create a Exec-Program-Wait script and include it in the users file. This approach is still available in FreeRADIUS but has been determined to be obsolete and may be removed in some future version. In addition there is the rlm_exec module provided by FreeRADIUS that permits a script or program to be included in some of the FreeRADIUS.conf sections. There have been a number of problems discussed with this approach as it does not always provide all the flexibility desired.
In any case, both of those approaches require that FreeRADIUS fork another process and incur all associated process startup overhead each time an authentication or accounting request is received. On a high volume server this overhead can become significant and impact response times. The approach that eliminates the overhead is the rlm approach where you create your own module and compile it directly into FreeRADIUS. While this sounds like a lot of work, its actually very straight forward. However, the rlm does have to be written in C. Converting shell scripts to C is not that big a deal. Modules in Version 2 are a bit different from Version 1. This page addresses adding modules to FreeRADIUS Version 2. For Version 1 see Modules.
Establishing the Environment
There are two ways to compile your module: inside FreeRADIUS or separately. I have not tried the inside approach with Version 2. However, it should be quite similar to that used with Version 1. This page realy addresses compiling the modules searately from FreeRADIUS.
The simple approach is to build your module outside of the FreeRADIUS environment and then move the resulting .so file into the library. To do that you need a Makefile. Here is one that works for a module named rlm_xxx:
VERS = 2.0.5
CFLAGS = -DNDEBUG -Wall -I/usr/include -I/usr/local/msql3/include \
-I/usr/ports/net/freeradius2/work/freeradius-server-$(VERS)/src
LIBS = -lc -L/usr/local/msql3/lib -lmsql
ALL: rlm_xxx.o rlm_xxx-$(VERS).so
rlm_xxx.o: rlm_xxx.c
cc -g -fPIC -DPIC -c $(CFLAGS) rlm_xxx.c
rlm_xxx-$(VERS).so: rlm_xxx.o
cc -g -shared -Wl,-soname,rlm_xxx-$(VERS).so \
-o rlm_xxx-$(VERS).so rlm_xxx.o $(LIBS)
install: ALL
install rlm_xxx-$(VERS).so /usr/local/lib/freeradius-$(VERS)
ln -fs rlm_xxx-$(VERS).so /usr/local/lib/freeradius-$(VERS)/rlm_xxx.so
clean:
rm rlm_xxx*.o rlm_xxx*.so
The FreeRADIUS package is located at: /usr/local/ports/freeradius/2/work/freeradius-server-2.0.5. You will need to change that to your location.
Note, you need access to the various include files used by FreeRADIUS. Change the location above for the include directory to match where they are on your system. Note, in Version 2 the include structure is a bit unusual. You need to reference the src directory and not the more obvious include directory. The reason is that all the FreeRADIUS source modules use includes of the form "freeradius-dev/module" which is linked to the proper place. You need to follow that convention for things to work properly. The version numbering I am using for the module is that of FreeRADIUS but you can use any you want. This example shows how to link to a static library from the module. The second cc command is quite touchy about the ordering of items. There may be issues with libraries if they are not after the module source name.
Notice the -DNDEBUG in the CFLAGS definition above. This is a compile option in the base system. You need to be sure that this option is included if it was included when the base system was compiled. Likewise it should not be included if it was not included in the base system. If this option does not match the base system, radiusd will get a segment violation almost immediately. If you can not figure out how the base system was built, try it both ways. One of them should work.
Establishing the Module
Now start building rlm_xxx.c. Start by copying rlm_example.c and editing it to create your module. Note, there is an obscure note near the end of rlm_example.c that talks about global variables. What that really is telling you is that your rlm_xxx.c module must include all storage definitions within one of the modules defined in the module_t rlm_xxx definition. Data storage outside those modules will not work properly. Your rlm may compile and seem to run, but it will not do what you want.
Also, keep in mind that in general, rlm’s must be thread safe. There can be multiple threads using your module at any time. The RLM_TYPE_THREAD_SAFE definition tells FreeRADIUS that this is a thread-safe rlm. If you need to use one that cannot be made thread safe, then change RLM_TYPE_THREAD_SAFE to RLM_TYPE_THREAD_UNSAFE. FreeRADIUS will then only permit one instance of that rlm. Be advised that this will adversely affect performance and response times.
Setup struct rlm_xxx_t to hold data that needs to be accessed by all instances of the rlm. This data is not necessarily the same for each instance. There is a separate copy for each instance. For example, this is the place to store configuration variables that will be provided in FreeRADIUS.conf.
module_config is where the configuration variables from FreeRADIUS.conf are defined. There needs to be an entry here for each configuration variable. Do note there is a special entry for ending the list. That must be the last entry in this list. Each other entry has first a name for the configuration variable. The following is a sample entry:
{ "host", PW_TYPE_STRING_PTR, offsetof(rlm_xxx_t,host), NULL, "0"},
The first field is the string that will be used in the FreeRADIUS.conf entry for the variable. In this case the variable name is “host”. In the FreeRADIUS.conf file in the modules section for xxx you might find:
host = “my_host”
The second entry is the type of data the variable holds.
| Entry | Data Type |
|---|---|
| PW_TYPE_STRING_PTR | Character string |
| PW_TYPE_INTEGER | Integer |
| PW_TYPE_BOOLEAN | Boolean |
| PW_TYPE_IPADDR | IP Address |
The third entry tells FreeRADIUS where to save the configuration variable. the host entry is the name of the variable in rlm_xxx_t. The fourth entry is unknown. I could only find NULL used here.
The last entry is an initial value for the configuration variable if nothing is specified in FreeRADIUS.conf. This value must have the proper type as established in the send entry and must be enclosed in double-quotes.
The xxx_instantiate module is called each time a new instance is started during the initial configuration process. Generally this module is used to establish the data for the instance that needs to be retained during the life of the instance. For example, reading the configuration variables. cf_section_parse(conf, data, module_config) is used to do this function. Note that the instantiate module is not called each time a new instantiation of the module is started during run time. The data established during the instantiate module is available to all instantiations during run time. If you need to store data that is associated with a particulare *request*, and is valid only for the lifetime of a request, see request_data_add(), and request_data_get().
The module_t rlm_xxx definition establishes the connection between FreeRADIUS and the available services your rlm can provide. Here is a sample definition which only includes instantiate, detach and authorize:
module_t rlm_xxx = {
RLM_MODULE_INIT,
"xxx",
RLM_TYPE_THREAD_SAFE, /* type */
xxx_instantiate, /* instantiation */
xxx_detach, /* detach */
{
NULL, /* authentication */
xxx_authorize, /* authorization */
NULL, /* preaccounting */
NULL, /* accounting */
NULL, /* checksimul */
NULL, /* pre-proxy */
NULL, /* post-proxy */
NULL /* post-auth */
},
};
This example only has one authorization module. You can have multiple modules for use in different parts of the FreeRADIUS processing. You can also make separate rlm’s for each module. There does not appear to be any significant difference in performance in using one or multiple rlm’s. Your specific needs will probably make one approach easier than the other.
The example above indicates one of the least understood aspects of FreeRADIUS’s architecture. Both the authentication and authorization modules are part of the radius authentication request processing. However, those two sections are handled quite differently. Authentication is the checking of the password (or other authentication token) provided by the user to insure the user is who they are claiming to be. Generally only one authentication module is used. Authorization is a policy decision - should this request be permitted. This is generally where local policy issues are addressed.
Module Return Codes
There are a number of return codes possible from each of the rlm modules:
| Return Code | Meaning |
|---|---|
| RLM_MODULE_REJECT | This request is not permitted under local policy. FreeRADIUS responds immediately with a reject response. |
| RLM_MODULE_FAIL | Processing of this request could not be completed. Something is not working properly. FreeRADIUS responds with a reject response. |
| RLM_MODULE_OK | This request is permitted or this module has processed the request successfully. The FreeRADIUS response will be determined by processing of later modules. If this is the last module then the response will be an OK |
| RLM_MODULE_HANDLED | The module handled the request, so stop |
| RLM_MODULE_INVALID | The module considers the request invalid |
| RLM_MODULE_USERLOCK | Reject the request (user is locked out) |
| RLM_MODULE_NOTFOUND | User not found (Probably shouldn’t be used in a RLM) |
| RLM_MODULE_NOOP | Module succeeded without doing anything |
| RLM_MODULE_UPDATED | OK (pairs modified) |
| RLM_MODULE_NUMCODES | How many return codes there are |
Accessing Radius Request Attributes
Version 2 uses a very generalized data format. The details are provided in libradius.h. You will probably want to review that. The basics are provided here. Do note, that in Version 1 an IPv4 attribute value was returned as a character string (e.g., "xxx.xxx.xxx.xxx"). In Version 2 it is returned as the binary 4 byte value. You have to do the conversion if needed.
The following example shows how to access the Framed-IP-Address attribute:
int *ip;
pair = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS);
if (pair) {
ip = pair->vp_octet;
DEBUG("rlm_xxx: Found IP Address");
}
The standard radius dictionary items are prefixed by PW_. Vendor specific dictionary entries can usually be accessed by prefixing the vendor name to the item. For example, ASCEND_DATA_RATE can be used to obtain the Ascend specific Data-Rate attribute.
The data types are used in the vp_type form. This is strongly recommended because the actual storage location for the value is in transition. Some of them have been moved into the value_pair_data structure and some have not yet been converted. The vp_type form points to the proper place so you do not have to be concerned if the specific field has been converted yet. See libradius.h for all the details. Here is a table of the available data types.
| Data Type | Content |
|---|---|
| vp_strvalue | A character string with max size of MAX_STRING_LEN. |
| vp_octets | An octet string with max size of MAX_STRING_LEN |
| vp_ip6addr | An IPv6 address in struct in6_addr format |
| vp_ifld | Some sort of structure - Check the source code for this one |
| vp_ipv6prefix | Some sort of structure - Check the source code for this one |
| vp_filter | A data type of filter[32] |
| vp_ether | A 6 byte ethernet address in octet format (not printable) |
| vp_ipaddr | An IPv4 address in struct in_addr format |
| vp_date | A date value |
| vp_integer | An integer value in octet format (not printable) |