allow WLAN-access in certain offices only

radius.pkoch at radius.pkoch at
Thu Apr 22 20:11:41 CEST 2021

Dear Alan, dear Matthew,

first let me thank you for your replies. All your hints and informations 
are very valuable for me and far away from being frustrating.

I'm trying not to waste your time. I would not dare to present a problem 
on this mailinglist without at least trying to figure out what a 
solution might be and give you a description of that "solution". That 
might have given you the impression that I'm already stuck with certain 
solutions (ie. rlm_perl vs. rlm_exec).

I understand that you need precise information about what my problem is 
in order to point me into the 100% correct direction. But I don't know 
exactly what my problem is. Describing the problem seems to be part of 
the solution.

So I will try to explain what I have understood so far. And I will try 
to reformulate my problem.

What I have learned so far is:

The rlm_sql module does not only read user-information from certain 
SQL-tables and store account information into other SQL-tables, but 
loading this model enables %{sql:select ....} variable expansions in the 
rlm_expr module. I wasn't aware of this functionality of rlm_sql and 
since the select-statement in %{sql:....} can fetch data from any table 
or view I can easily select both the room number of User-Name and the 
room number of %{Packet-SRC-IP-Address}.

I tried that today and created file policy.d/check_rooms with content

check_rooms {
         update request {
                 &Tmp-Integer-0 = "%{sql:select nmi_nra_id from 
nav_mitarbeiter where nmi_kuerzel='%{User-Name}'}"
                 &Tmp-String-0 = "%{sql:select nra_name from nav_raeume 
where nra_id=%{Tmp-Integer-0}}"
                 &Tmp-Integer-1 = "%{sql:select egr.nra_id(egr_id) from 
edv_geraete where egr.ip_adresse(egr_id,7)='%{Packet-SRC-IP-Address}'}"
                 &Tmp-String-1 = "%{sql:select nra_name from nav_raeume 
where nra_id=%{Tmp-Integer-1}}"

Since the decision wether an accesspoint with a certain IP-address is 
located in the office of a user or not is completly based on information 
within our oracle database, I felt that instead of fetching multiple 
pieces of data from the database and then compare that with Unlang in 
FreeRadius, this logic should be better placed within the database. By 
the way - this is what I ment when I mentioned that it's "our business 
how this decision has to be taken". Sorry, if that sounded unfriendly.

Hence I created an oracle database function check_rooms(user, ip) that 
does everything and my check_rooms policy now is just a one-liner. 
Tmp-String-0 will have either value "OK" or an error-message or will be 
empty if something went wrong within the database. And I extended this 
function so it takes care of our conference-rooms as well.

check_rooms {
         update request {
                 &Tmp-String-0 = "%{sql:select 
egr.check_rooms('%{User-Name}','%{Packet-SRC-IP-Address}') from dual}"

I still have to figure out how to reject the request if Tmp-String-0 is 
not "OK". There are enough examples so I can figure that out on my own.

I was unsure about where to place the check_rooms statement. My first 
try was at the beginning of the authorize section of default. But then 
freeradius would talk to the database on every packet during 
eap-tunnel-setup. I therefore moved check_rooms to inner-tunnel and 
placed it just before "files".

So my problem "How do I restrict WiFi access to certain offices" seems 
to be solved.

But you pointed me at a different problem, namely "How do I force our 
employees to use their OATH TOTP-token as a second factor when they 
authenticate against a WiFi accesspoint"

I would appreciate your help with this problem very much.

Here are some additional informations about the problem:

- we are running an authentication server, that may verify a token values
- the authentication server has - among others - a REST interface
- the authentication server caches successful results for a certain 
period of time, so valid token-values can be reused
- doing two-factor authentication is part of our security policy, using 
certificates will fullfill this requirement only if the private key of 
the certificate was created within a smart card

It won't surprise you that I have already tried to figure out, what a 
solution might be. Since my original idea (i.e. delegating the 
validation of the password to a script via either rlm_exec or rlm_perl) 
was absolutely unrealistic, I spare you what I have in mind, now that I 
have realized that passwords are contained in radius packets in clear 
only when sent with radtest, but are missing, when eap is used.

Let me know I you need more information (or have better things to do 
than free FreeRadius consulting for dummies :-) )

Kind regards


Am 20.04.2021 um 21:46 schrieb Alan DeKok - aland at
> On Apr 20, 2021, at 1:04 PM, radius.pkoch at wrote:
>> I have just compiled Freeradius from source and red some of the 
>> documentation.
>> WPA2-EAP works with username bob and password hello.
>> radiusd -X shows no errors.
>    That's good.
>> Now here's what I would like to achive and maybe some of you can point me
>> into the right direction:
>> We have equipped all of our offices (approx 100) with seperate WLAN 
>> access points.
>> Every employee should be able to access the access point in its own 
>> office and
>> in some of our conference rooms. Every employee owns an OAuth token that
>> generates a 6digit one time password.
>    That's nice, but you really don't want to use OAuth with Wifi. I 
> don't even know how that would work.
>    i.e. WiFi is bad enough that devices end up re-authenticating 
> multiple times a day.  And you definitely don't want users to be asked 
> 5-10 times a day for a new one-time password.
>    Just use a password.  Or, use EAP-TLS and client certificates.

We are planning to use our time-based one time password OATH tokens just 
in the same way we are already doing this for IMAP authentication. When 
a user authenticates for the first time, he must generate a 6digit value 
with his token, append his own password to that value and use that 
combination. The IMAP server (and I was hoping the radius server could 
do as well) will send the password to our authentication server for 

We do not use OAuth.

Once a password was used successfully, the same value can be used again 
for a certain period of time that depends on the username. With our 
IMAP-server this period is 30 days, for a WiFi-guest account this would 
be 12 hours.

The passwords we generate with our OATH-token are not used as ONE time 
passwords. Hence they must be kept secret and must not be sent over the 
network in clear.

>> Whenever a user tries to access a WLAN access point with his username
>> and his one time password the following should happen:
>    Scratch all that.
>    First, you should figure out how WiFi works.  Then, figure out if 
> your suggested process fits into that.
>    If it doesn't, throw away your requirements about what "should 
> happen", and go with something which is realistic.

So here's what I figured out so far about how WiFi works:

- accesspoint is configured for WPA-Enterprise
- hence the supplicant is asked for a user / password combination or has 
to provide a client certificate
- the accesspoint receives additional informations from the supplicant 
(i.e. its mac-address)
- the accesspoint sends all the information it has received from the 
supplicant together with informations about itself to the radius server
- the radius server decides wether to allow or deny access and answers 
the request

In our case the radius server will get - among other informations - the 
username and password of the supplicant plus the IP-address of the 
accesspoint. This is all that is needed to decide wether the password is 
correct and wether the accesspoint is located within the office of the 
person with the given username.

But the radius server cannot do this decision on its own. It cannot 
verify the password but has to delegate that decision. And it cannot 
query our central oracle database on its own about what accesspoint has 
the given IP-address and wether this accesspoint is physically located 
in the office of the person with the given username.

I was hoping that the radius server could delegate this decisions to a 
script. And it seems to me, that there are (at least) the following two 

- use a php-script via rlm_exec

- use a perl-script via rlm_perl

I don't like the rlm_exec option since it seems to have security issues 
(password is visible within the environment of the process) and might be 
slow since for every authentication the php-interpreter has to be 
started and a new oracle-database connection has to be created. But this 
is the easiest way to go.

I don't like the rlm_perl option as well. I have never written a perl 
script, nor have I connected an oracle database from within a perl 
script. Performace would be a lot better since the perl-interpreter must 
not be started on every authentication. But it seems to me that maybe a 
database connection must be created on every authentication request. 
With oracle databases that is a lengthy process and takes more time than 
starting an interpreter.

>> 1) if the password is wrong access should be denied
>    So... password checking like normal.  But if this is for passwords 
> which change multiple times a day, then it just won't work.

see above - password is token generated but will not change until it expires

>> 2) if the access point is not located in the office of the employee 
>> or in one
>> of the conference rooms of the employees department access should be 
>> denied
>>    So... check the source AP to see if it's allowed.  How do you 
>> check that?  Read the debug output to see what each AP sends, and 
>> then write rules to match those.
wether an accesspoint is allowed or not depends on the data within our 
central oracle database. If an employee moves to a new office (which 
happens a lot lately due to corona) our hotline changes the information 
within the oracle database. This should give the employee immediate 
access to the accesspoint in his new office without changing 
configuration data at additional places (neither manually nor 
>> Our central oracle database has information about the ip-address and 
>> location
>> of every access point and the office rooms of every employee.
>    Location is irrelevant.  The only thing that matters is what's in 
> the RADIUS packets, and what's shown in the debug logs.
>    Does the term "Conference room 5" appear in the RADIUS packet? 
> No?   Then you'll have to figure out some other way which access point 
> is which.  Maybe by looking at host names (if they show up in the 
> RADIUS packet), or IP addresses (if they should up in the RADIUS 
> packet), or by MAC (well, you get the idea by now).

"Conference room 5" does not appear in the RADIUS packet but so does the 
IP-Adress of the accesspoint. And our central database has all the 
information that is needed to deduce the room number from that 
ip-address and wether the conference room belongs to the same department 
as the user.

>> My first idea was to write a php-script (because that's the scripting 
>> language
>> I'm familiar with) and use that via rlm_exec. I will do this as a 
>> proof of concept.
>    To do... what?  You haven't said.

see above. The idea was to let the script verify the password and 
compare the physical location of the accesspoint with the office of the 
user, based on the information that the script got from the radius 
packet (username, password, ip-address).

>> Since neither I nor any of my colleagues have perl-experience I'd 
>> rather write
>> a new module in C than use perl.
>> Is there a module that will send all parameters to a unix or inet 
>> socket and
>> receives the results from that socket? How abount rlm_socket?
>    To do... what?
>    How is any token going to be checked?  REST API?  What?
>    Your last comments here are really "how do I write a script to do 
> stuff".  The only answer is "I dunno, it depends on what stuff you 
> want to do".

I'm not asking how a script should be written. This is our business and 
from Freeradiuses point of view it should make no difference wether the 
script uses a REST API to talk with our authentication server or connect 
an oracle database or do whatever is needed to allow or reject a request.

My current understanding is that freeradius will feed a list of 
key-value pairs into such a script and will receive at least a status 
and maybe additional key-value pairs.

Right now my plans are:

1: Create a php-script that will receive key-value data from Freeradius 
via evironment-variables and will return data via exit-code. This script 
has to create a database connection for every authentication request. 
This will be slow (approx 2 seconds) but my hope is, that Freeradius 
will start processes in parallel.

2: Create a perl-script with identical functionality. Maybe the first 
invocation of such a perl script can create a database connection that 
can be reused by future requests. That would improve performance. I have 
no idea wether Freeradiuses builtin perl-interpreter allows to store a 
database connection in a global variable and what happens if multiple 
invocations of such a perl sript are running in parallel.

Any feedback on these ideas is welcome.

Kind regard


More information about the Freeradius-Users mailing list