Modules
From FreeRADIUS Wiki
Creating a Module
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.
Establishing the Environment
There are two ways to compile your module: with or without configure. You do not need to use configure, but it may be helpful is establishing some parameters. The only required file is Makefile. If you create it completely yourself you don’t need the configure files. The instructions below show how to update the configure files. If you don’t use them, delete them. Go into the FreeRADIUS distribution in the src/modules directory. Copy rlm_example to rlm_xxx where xxx is the name you select: cp -R rlm_example rlm_xxx Cd into rlm_xxx and delete any .o, .a, .la, .lo files, Makefile, config.h, config.log, config.status, configure, and everything in .libs. Vi all the remaining files and change example to xxx wherever it occurs. Note, it appears in a number of places. In several places it will not say to change this. Change it anyway. It won’t work if you do not.
The simpler 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:
VERS = 1.1.0
CFLAGS = -DNDEBUG -Wall -I/usr/include -I/usr/local/msql3/include \
-I/usr/ports/net/freeradius/work/freeradius-$(VERS)/src/include
LIBS = -lc -L/usr/local/msql3/lib -lmsql
ALL: rlm_lafn.o rlm_lafn-$(VERS).so
rlm_lafn.o: rlm_lafn.c
cc -g -fPIC -DPIC -c $(CFLAGS) rlm_lafn.c
rlm_lafn-$(VERS).so: rlm_lafn.o
cc -g -shared -Wl,-soname,rlm_lafn-$(VERS).so \
-o rlm_lafn-$(VERS).so rlm_lafn.o $(LIBS)
install: ALL
install rlm_lafn-$(VERS).so /usr/local/lib
ln -fs /usr/local/lib/rlm_lafn-$(VERS).so /usr/local/lib/rlm_lafn.so
clean:
rm rlm_lafn*.o rlm_lafn*.so
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. 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. 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_init module is only called when FreeRADIUS is first started. It cannot establish any instance unique data. It can only establish data that is the same for all instances. Returns from this module are 0 for all ok or non-zero for an error that terminates FreeRADIUS. Generally this module is not used.
The xxx_instantiate module is called each time a new instance is started. 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.
The module_t rlm_xxx definition establishes the connection between FreeRADIUS and the available services your rlm can provide. Here is a sample definition:
module_t rlm_xxx = {
"xxx",
RLM_TYPE_THREAD_SAFE, /* type */
xxx_init, /* initialization */
xxx_instantiate, /* instantiation */
{
NULL, /* authentication */
xxx_authorize, /* authorization */
NULL, /* preaccounting */
NULL, /* accounting */
NULL, /* checksimul */
NULL, /* pre-proxy */
NULL, /* post-proxy */
NULL /* post-auth */
},
lafn_detach, /* detach */
NULL, /* destroy */
};
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
The following example shows how to access the Framed-IP-Address attribute:
char *ip;
pair = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS);
if (pair) {
ip = pair->strvalue;
DEBUG("rlm_xxx: Found IP Address");
}
else ip = "n/a";
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.
Accessing Configuration Parameters
Configuration parameters are saved in the rlm_xxx_t data structure. They are accessed through the instance argument. For example the host parameter shown above would be accessed using instance->host
Adding a Radius Response Item
The following shows how to add a response item to the radius response:
sprintf (auth_msg, "%d", sestime*60);
VALUE_PAIR *timeout;
timeout = pairmake("Session-Timeout",
auth_msg, T_OP_SET);
pairadd(&request->reply->vps, timeout);
The first argument to pairmake is the attribute name. The second argument is its value. Note the value must be a string. There is a way to set vendor specific attributes but I have not needed to do that.
Creating Debug Entries
FreeRADIUS has a very helpful debug capability. You can certainly use trace commands like ktrace, strace, or truss. However, those are limited in what the show you about how your module is working. It is much easier to add debug statements into your module. If your FreeRADIUS is compiled with enable-debug then you can start it with:
/.../FreeRADIUS -X
and it will generate lots of debug entries on the console to show you what it is doing. To add debug information to your modules you can use the following command:
DEBUG (“format string”, variables);
The format string and variable list are the same as for a printf statement. Use debug statements at critical places to show how the request is being processed.
Creating Log Entries
Log entries are created using the following: radlog (level, “format string”, variables); The level argument is the syslog level (e.g., L_ERR). These are the same as those in syslog.h except only L is used rather then LOG. The format string and variables are the same as for a printf statement. Log entries should be used for things that need to be retained in the log files.
Compiling Your Module
Cd to src/modules. Edit the stable file and add rlm_xxx to the end. While you could put your entry anywhere in the file, the end is the most helpful. When you are trying to get the configuration/compilation to work, it will be the last module configured/compiled. Any messages will be easier to find. A configuration error does not terminate configuration or compilation. It just places a message that the module is being skipped in the output. To configure FreeRADIUS you must add --enable-rlm_xxx to the configure input. Then you should be able to configure and make. Once you have a successful make you can make install.
Configuring the Module in radiusd.conf
You need to make 2 modifications to the radiusd.conf file to add your rlm. A new section in the modules configuration should look something like:
- # xxx module
- xxx {
- host = “myhost”
- }
This establishes the configuration parameters for the module.
You also need to add an entry in the appropriate instantiation section for xxx so that it will be called at the proper time. For example:
- # Authorization. First preprocess (hints and huntgroups files),
- authorize {
- preprocess
- files
- xxx
- }
Available Modules
- rlm_acctlog
- rlm_acct_unique
- rlm_always
- rlm_attr_filter
- rlm_attr_rewrite
- rlm_caching
- rlm_chap
- rlm_checkval
- rlm_counter
- rlm_cram
- rlm_dbm
- rlm_detail
- rlm_digest
- rlm_eap
- rlm_eap_gtc
- rlm_eap_md5
- rlm_eap_peap
- rlm_eap_sim
- rlm_eap_ttls
- rlm_eap_leap
- rlm_eap_mschapv2
- rlm_eap_psk
- rlm_eap_tls
- rlm_example
- rlm_exec
- rlm_expiration
- rlm_expr
- rlm_fastusers
- rlm_files
- rlm_ippool
- rlm_krb5
- rlm_ldap
- rlm_linelog
- rlm_logintime
- rlm_mschap
- rlm_ns_mta_md5
- rlm_otp
- rlm_pam
- rlm_pap
- rlm_passwd
- rlm_perl
- rlm_policy
- rlm_preprocess
- rlm_protocol_filter
- rlm_python
- rlm_radutmp
- rlm_realm
- rlm_sim_files
- rlm_smb
- rlm_sql
- rlm_sqlcounter
- rlm_sqlippool
- rlm_sql_log
- rlm_unix
- rlm_x99_token
See Also
- Development Roadmap
- Modules
- CVSWeb
- Changelog