FreeRadius and MySQL

Introduction

In September 2001 I started playing around with FreeRadius (then at version 0.2!) and storing user authorisation details in a MySQL database. I had previously been using a proprietary RADIUS solution and wanted rid of it. Lots of people seemed to be posting to the freeradius-users list that they were trying to do the same and found it tricky due to the lack of documentation. Thus, to help anyone out there who needed it, I wrote down all the snippets of info, tips I'd received, and steps I'd used to make it work. This is the result.

This document assumes that you are familiar with:

  • *nix system admin and networking
  • What RADIUS is and should do
  • MySQL administration
  • The basics of how to compile and install open source software.

I'm not going to describe any of the above stuff, especially the latter as I'm far from an expert on it. This document focuses on getting FreeRadius running with MySQL. It does NOT describe a basic FreeRadius installation in detail (e.g. getting it up and running with a 'users' text file or other FreeRadius configurations), nor does it cover using multiple authentication methods, fall-through's or any of that stuff. Just plain-old-MySQL-only. If you don't know about RADIUS itself, go do some background reading... the O'Reilly book ('RADIUS') is pretty good and covers FreeRadius too.

Please note: This isn't official documentation. It's not even UNofficial documentation. It's not documentation of any type by any stretch of the imagination. So far, it's just my own personal notes, written on the fly. Little editing, little detail. You takes your chances. I will try to improve when I can, or have additional information - don't hold your breath though, as life can get busy around here. The notes focus on the SQL element, NOT generally on getting FreeRadius installed and configured and operational with text files (maybe later!) although there is a little bit on that.

Also note: I'm not a programmer - editing low-level code and compiling stuff is not something I'm particularly familiar with. Ask me to read C code and I'll probably panic. My background and experience on Linux (and other stuff) puts me in the system admin/networking bracket (I'm a network builder and web app developer by day), so please bear that in mind here. Feel free to mail me, especially with suggestions and any info useful to add here, but please don't ask me 'how to I compile' stuff. Thanks.

Lastly for this bit : a big thank you to all those that helped, emailed and generally contributed to me getting this up and going, and thus to the creation of these notes.

System

I did my original testing on SuSe Linux 7.0 on Intel with FreeRadius 0.2 and MySQL 3.23.42 using a Cisco 3640 acting as a test NAS unit. The final deployment was to RedHat 7.1. Today I'm running FreeRadius 0.8.1. If you're running an older version you are strongly recommended to upgrade.

Before You Start

Before starting with FreeRadius, make sure your box is up and configured on your network, that you have MySQL installed and running, and that your NAS is configured to point to your server.

If you're using Cisco kit as your NAS, here's a quick example snippet of how to configure IOS to authenticate PPP (e.g. dial, DSL etc) users to a RADIUS server:

 aaa new-model
aaa authentication ppp default if-needed group radius local
aaa authorization network default group radius
aaa accounting update newinfo
aaa accounting exec default start-stop group radius
aaa accounting network default wait-start group radius
aaa accounting connection default start-stop group radius

radius-server host a.b.c.d auth-port 1645 acct-port 1646
radius-server host e.f.g.h auth-port 1645 acct-port 1646
radius-server key YOUR-RADIUS-KEY

[a.b.c.d and e.f.g.h are the IP's of your primary and secondary RADIUS servers. YOUR-RADIUS-KEY is your RADIUS secret key as defined in clients.conf (see below). ]

Make SURE you have included the development headers in your MySQL installation otherwise the FreeRadius installation/compilation will barf. To make my own life easy, I just installed MySQL to the default location.

Just to clarify: ABSOLUTELY MAKE SURE you have the mysql-devel (headers and libraries) package installed with your MySQL, otherwise freeradius won't compile with MySQL support properly. Many people seem to miss having this.

Oh yep, did I mention about having the MySQL development headers installed? No? Make sure you do... ;-)

Getting Started

First off, you should get FreeRadius compiled, installed and running in a basic text file configuration (e.g. using the 'users' file) on your box. This I'm not going to describe in details (read the stuff in /docs, etc), but it should basically be the following:

1 - Get the latest FreeRadius source code tarball from ftp://ftp.freeradius.org/pub/radius/freeradius.tar.gz. If you're so minded, get the latest CVS instead.

2 - Unpack the tarball and install it. On my own system the basic steps were all that was needed, and everything got dumped in the standard places:


tar xvf freeradius.tar.gz
cd freeradius
./configure
make
make install

Note that you might need to add options to ./configure if you installed MySQL to a non-standard place, or want FreeRadius to a non-standard place, or want or need any other odd bits and pieces. I was keeping it simple and didn't need to.

Then you should configure FreeRadius appropriately. It's best to start with a simple config using the standard text files, if at least only to test that FreeRadius installed OK and will work. To very briefly summarise getting the text files configured :

1 - Edit /usr/local/etc/raddb/clients.conf and enter the details of your NAS unit(s). There are examples here, so it should be easy. Tip: You'll also want to enter 'localhost' here for testing purposes (i.e. so you can use radtest).

2 - Edit /usr/local/etc/raddb/users and create an example user account. The file is commented on how to do this. I'm not going to repeat that here. If you've previously used another RADIUS server with text-file configuration (e.g. Livingston, Cistron) you'll know what goes here...

3 - Edit /usr/local/etc/raddb/realms. I just put a single line 'DEFAULT LOCAL' and that was sufficient to strip any suffix domain names in given user names - if you're using realms or proxing you'll doubtless need to do something else here, but I recommend you start with this then come back to setting up realms/ proxying when you know MySQL is working. If you're not using realms, just ignore this.

4 - Edit /usr/local/etc/raddb/radiusd.conf and change as needed. For my own installation I changed the default port to run on 1645 (old port) to match what our existing boxes use (but otherwise make sure your NAS and FreeRadius are using the same) and said 'yes' to all the logging options (I'd strongly recommend you do switch on all the logging to start with). At this point I also said 'no' to using proxy to keep stuff simple. I then told it to run under the 'radius' user and group (I'd initially installed FreeRadius as root and didn't want to run it as such, so I created a user account called 'radius' in a group called 'radius' and then just blanket chown'd and chgrp'd the various radius directories to that user just to be sure the account can access all the right stuff. A bit of a sledgehammer there, but it was quick! I'm sure there's a better and/or more elegant way of doing this!). The rest of the radiusd.conf file was left alone.

At this point you should be able to manually fired up /usr/local/sbin/radiusd. You should do this with the debug turned on so you can see what happens:

      /usr/local/sbin/radiusd -X

Lots of stuff will scroll to the screen, and it should tell you it's ready to accept requests. If you get an error, READ THE DEBUG, then check the docs, check the above and try again.

You should now be able to use FreeRadius. You can use radtest to test an account from the command line:

      radtest username password servername port secret

So, if your example user is 'fred' with password 'wilma', your server is called 'radius.domain.com', is using port 1645, and you put localhost (or your localhost's IP) in clients.conf with a secret of 'mysecret', you should use:


radtest fred wilma radius.domain.com 1645 mysecret

And you should get back something like:

      Sending Access-Request of id 226 to 127.0.0.1:1645
User-Name = 'fred'
User-Password = '\304\2323\326B\017\376\322?K\332\350Z;}'
NAS-IP-Address = radius.domain.com
NAS-Port = 1645

rad_recv : Access-Accept packet from host 127.0.0.1:1645,id=226, length=56
Framed-IP-Address = 80.84.161.1
Framed-Protocol = PPP
Service-Type = Framed-User
Framed-Compression = Van-Jacobson-TCP-IP
Framed-IP- Netmask = 255.255.255.255

You should get an 'Access Accept' response. If you don't, do not pass Go, do not collect £200. Go back and check everything. Read the docs, READ THE DEBUG!!

Personally, I used NTradPing (downloadable from MasterSoft) on a desktop Windows PC to send test packets towards the radius server - very handy tool. If you do this, or test from any other machine, remember your PC (or other machine) needs to be in your NAS list in clients.conf too!

OK, so at this point you should have text-file authentication working in FreeRadius...

Setting up the RADIUS database in MySQL

First, you should a new empty 'radius' database in MySQL and login user with permissions to that database. You could of course call the database and the user anything you like but we'll stick to 'radius' for both for the purposes of this discussion

Next up, you need to create the schema for the database. There is a file which describes this and is actually a SQL script file. It can be found at /src/modules/rlm_sql/drivers/rlm_sql_mysql/db_mysql.sql where you untar'd FreeRadius. This is the bit that, at least at the time I originally wrote these notes, wasn't really documented anywhere and was the thing most people seemed to be asking.

How you run that script is up to you and how you like to admin MySQL. The easiest way is to:
       mysql -uroot -prootpass radius < db_mysql.sql 

...where 'root' and 'rootpass' are your mysql root name and password respectively.

I happened to run it using MacSQL 2.0 on my Powerbook G4/OS X machine (Cool...). You could do it on the server, or use a MySQL admin tool from a Windows PC (e.g. MySQL CC, SQLion, dbtools etc) or whatever.

Now you have the database running, albeit empty.

Configuring FreeRadius to use MySQL

Edit /usr/local/etc/raddb/sql.conf and enter the server, name and password details to connect to your MySQL server and the RADIUS database. The database and table names should be left at the defaults if you used the default schema. For testing/debug purposes, switch on sqltrace if you wish - FreeRadius will dump all SQL commands to the debug output with this on.

If you're stripping all realm names (i.e. you want user joe@domain.com to authenticate as just 'joe'), then in sql.conf, under the 'query config: username' section, you MAY need to adjust the line(s) referring to sql_user_name. I needed to do this originally because we want to dump all realms, but you probably won't need to do this with the latest FreeRadius. For example, in our case I needed to uncomment the line:

             sql_user_name = '%{Stripped-User-Name}'

...and comment out the following line referring to just User-Name. If you want to see what's happening here, switch on all the logging options in radiusd.conf and run radiusd in debug mode (-X) to see what's happening : you'll see " user@domain" being passed to MySQL when using User-Name, but just "user" when using Stripped-User-Name. Using the latter, realms worked for me (basically, I strip everything, as all user names are unique on the server anyway). Of course, set all your other SQL options as needed (database login details, etc)

Edit /usr/local/etc/raddb/radiusd.conf and add a line saying 'sql' to the authorize{} section (which is towards the end of the file). The best place to put it is just before the 'files' entry. Indeed, if you'll just be using MySQL, and not falling back to text files, you could comment out or lose the 'files' entry altogether.

Also add a line saying 'sql' to the accounting{} section too between 'unix' and 'radutmp'. FreeRadius will now do accounting to MySQL as well.

The end of your radiusd.conf should then look something like this:

authorise {
preprocess
chap
mschap
#counter
#attr_filter
#eap
suffix
sql
#files
#etc_smbpasswd
}

authenticate {
authtype PAP {
pap
}
authtype CHAP {
chap
}
authtype MS-CHAP{
mschap
}
#pam
#unix
#authtype LDAP {
# ldap
#}
}

preacct {
preprocess
suffix
#files
}

accounting {
acct_unique
detail
#counter
unix
sql
radutmp
#sradutmp
}

session {
radutmp
}

Populating MySQL

You should now created some dummy data in the database to test against. It goes something like this:

  • In usergroup, put entries matching a user account name to a group name.
  • In radcheck, put an entry for each user account name with a 'Password' attribute with a value of their password.
  • In radreply, create entries for each user-specific radius reply attribute against their username
  • In radgroupreply, create attributes to be returned to all group members

Here's a dump of tables from the 'radius' database from mysql on my test box (edited slightly for clarity). This example includes three users, one with a dynamically assigned IP by the NAS (fredf), one assigned a static IP (barney), and one representing a dial-up routed connection (dialrouter):

      mysql> select * from usergroup;
+----+---------------+-----------+
| id | UserName | GroupName |
+----+---------------+-----------+
| 1 | fredf | dynamic |
| 2 | barney | static |
| 2 | dialrouter | netdial |
+----+---------------+-----------+
3 rows in set (0.00 sec)

mysql> select * from radcheck;
+----+----------------+----------------+------------------+------+
| id | UserName | Attribute | Value | Op |
+----+----------------+----------------+------------------+------+
| 1 | fredf | Password | wilma | == |
| 2 | barney | Password | betty | == |
| 2 | dialrouter | Password | dialup | == |
+----+----------------+----------------+------------------+------+
3 rows in set (0.02 sec)

mysql> select * from radgroupcheck;

+----+------------+-------------------+---------------------+------+
| id | GroupName | Attribute | Value | Op |
+----+------------+-------------------+---------------------+------+
| 1 | dynamic | Auth-Type | Local | := |
| 2 | static | Auth-Type | Local | := |
| 3 | netdial | Auth-Type | Local | := |
+----+------------+-------------------+---------------------+------+
3 rows in set (0.01 sec)

mysql> select * from radreply;

+----+------------+-------------------+---------------------------------+------+
| id | UserName | Attribute | Value | Op |
+----+------------+-------------------+---------------------------------+------+
| 1 | barney | Framed-IP-Address | 1.2.3.4 | := |
| 2 | dialrouter | Framed-IP-Address | 2.3.4.1 | := |
| 3 | dialrouter | Framed-IP-Netmask | 255.255.255.255 | := |
| 4 | dialrouter | Framed-Routing | Broadcast-Listen | := |
| 5 | dialrouter | Framed-Route | 2.3.4.0 255.255.255.248 | := |
| 6 | dialrouter | Idle-Timeout | 900 | := |
+----+------------+-------------------+---------------------------------+------+
6 rows in set (0.01 sec)

mysql> select * from radgroupreply;
+----+-----------+--------------------+---------------------+------+
| id | GroupName | Attribute | Value | Op |
+----+-----------+--------------------+---------------------+------+
| 34 | dynamic | Framed-Compression | Van-Jacobsen-TCP-IP | := |
| 33 | dynamic | Framed-Protocol | PPP | := |
| 32 | dynamic | Service-Type | Framed-User | := |
| 35 | dynamic | Framed-MTU | 1500 | := |
| 37 | static | Framed-Protocol | PPP | := |
| 38 | static | Service-Type | Framed-User | := |
| 39 | static | Framed-Compression | Van-Jacobsen-TCP-IP | := |
| 41 | netdial | Service-Type | Framed-User | := |
| 42 | netdial | Framed-Protocol | PPP | := |
+----+-----------+--------------------+---------------------+------+
12 rows in set (0.01 sec)

mysql>

In this example, 'barney' (who is a single user dialup) only needs an attribute for IP address in radreply so he gets his static IP - he does not need any other attributes here as all the others get picked up from the 'static' group entries in radgroupreply.

'fred' needs no entries in radreply as he is dynamically assigned an IP via the NAS - so he'll just get the 'dynamic' group entries from radgroupreply ONLY.

'dialrouter' is a dial-up router, so as well as needing a static IP it needs route and mask attributes (etc) to be returned. Hence the additional entries.

'dialrouter' also has an idle-timeout attribute so the router gets kicked if it's not doing anything - you could add this for other users too if you wanted to. Of course, if you feel like or need to add any other attributes, that's kind of up to you!

Note the operator ('op') values used in the various tables. The password check attribute should use ==. Most return attributes should have a := operator, although if you're returning multiple attributes of the same type (e.g. multiple Cisco- AVpair's) you should use the += operator instead otherwise only the first one will be returned. Read the docs for more details on operators.

If you're stripping all domain name elements from usernames via realms, remember NOT to include the domain name elements in the usernames you put in the MySQL tables - they should get stripped BEFORE the database is checked, so name@domain will NEVER match if you're realm stripping (assuming you follow point 2 above) – you should just have 'name' as a user in the database. Once it's working without, and if you want more complex realm handling, go back to work out not stripping (and keeping name@domain in the db) if you really want to.

Auth-Type Note, Feb 2003: At the time of writing (i.e. up to and including FreeRadius 0.8.1), FreeRadius will default to an Auth-Type of 'local' if one is not found. This means that you do not need to include this (i.e. the radgroupcheck table above could actually be empty, and indeed is on my own box), but you probably should include it for clarity and for future-proofing in case FreeRadius changes. Please note that a previous version of this page indicated that Auth-Type should be included in the rad(group)reply tables. It appears that this is incorrect and that Auth-Type should be in the rad(group)check tables. Other than Auth-Type, for simple setups, you probably need nothing in radgroupcheck - unless you want users dialing certain nas'es, etc etc.

Using FreeRadius and MySQL

Fire up radiusd again in debug mode. The debug output should show it connecting to the MySQL database. Use radtest (or NTradPing) to test again - the user should authenticate and the debug output should show FreeRadius talking to MySQL.

You're done!

Additional Snippets:

To use encrypted passwords in radcheck use the attribute 'Crypt-Password', instead of 'Password', and just put the encrypted password in the value field. ( i.e. UNIX crypt'd password).

To get NTradPing to send test accounting (e.g. stop) packets it needs arguments, namely acct-session-time. Put something like 'Acct-Session-Time=99999' into the 'Additional RADIUS Attributes' box when sending stops. Thanks to JL for the tip.

If you have a Cisco nas, set the cisco-vsa-hack

Running a backup FreeRadius server and need to replicate the RADIUS database to it? I followed Colin Bloch's basic instructions at http://www.ls-l.net/mysql/ and got replication setup between two MySQL servers. Real easy. Read the MySQL docs on replication for more details. Note that MySQL replication is one-way-only.

On the subject of backup servers. If you want to run TWO MySQL servers and have FreeRadius fall over between them, you'll need to do something like this: duplicate your sql.conf and edit the second copy to reflect connecting to your backup server ; then name the files something like sql1.conf and sql2.conf ; in radiusd.conf change and duplicate the include line for sql.conf to include sql1.conf and sql2.conf instead ; in the 'authorize' section of radiusd.conf change the 'sql' entry to a 'group' one, like this:


group {
sql1 {
fail = 1
notfound = return
noop = 2
ok = return
updated = 3
reject = return
userlock = 4
invalid = 5
handled = 6
}
sql2 {
fail = 1
notfound = return
noop = 2
ok = return
updated = 3
reject = return
userlock = 4
invalid = 5
handled = 6
}
}

Note that if FreeRadius fails over to the second MySQL server and tries to update the accounting table (radacct), nasty things might possibly happen to your replication setup and database integrity as the first MySQL server won't have got the updates...

0 $type={blogger}: