Case insensitive username
Hello, we use FreeRadius at a school with about 3000 users and divide them into different groups. Since support requests often come from teachers who cannot enter their login data with correct capitalization, we want to use FreeRadius "case insensitive". At school we basically have 3 different groups. username student : 1234567 (a student's unique ID) username teacher: ABCD username employee: t.surname Some of our teachers try to login with "Abcd" or "abcd". That does not work, because FreeRadius is case sensitive. So far I've been browsing the forums and trying a few possible solutions. This includes the solutions from the following thread: https://lists.freeradius.org/pipermail/freeradius-users/2020-February/097517... 1. Changes in the server configuration authorize { update request { Stripped-User-Name := "%{tolower:%{User-Name}}" } ... everything else ... } 2. Changes in the "files" module key = "%{%{Stripped-User-Name}:-%{tolower:%{User-Name}}}" I can only test the login over the command line on our testserver, because i do not want to disrupt our currently working FreeRadius server including the wifi. All tests performed follow the scheme below. root@bkr-app:~# radtest TEST password 10.11.100.31:1812 10 test123 Sent Access-Request Id 245 from 0.0.0.0:33130 to 10.11.100.31:1812 length 74 User-Name = "TEST" User-Password = "start12K" NAS-IP-Address = 10.11.100.31 NAS-Port = 10 Message-Authenticator = 0x00 Cleartext-Password = "start12K" Received Access-Accept Id 245 from 10.11.100.31:1812 to 0.0.0.0:0 length 55 Reply-Message = "DEBUG: Lehrer VPN" Tunnel-Type:0 = VLAN Tunnel-Medium-Type:0 = IEEE-802 Tunnel-Private-Group-Id:0 = "37" root@bkr-app:~# radtest test start12K 10.11.100.31:1812 10 test123 Sent Access-Request Id 73 from 0.0.0.0:45170 to 10.11.100.31:1812 length 74 User-Name = "test" User-Password = "start12K" NAS-IP-Address = 10.11.100.31 NAS-Port = 10 Message-Authenticator = 0x00 Cleartext-Password = "start12K" Received Access-Reject Id 73 from 10.11.100.31:1812 to 0.0.0.0:0 length 46 Reply-Message = "DEBUG: Not found, reject" (0) -: Expected Access-Accept got Access-Reject root@bkr-app:~# Is there a way to use FreeRadius case insensitive so that we can reduce the support requests? Greetings from Germany, Simon
On May 12, 2022, at 10:03 AM, Simon Dankau <SDankau@rf-computer.de> wrote:
we use FreeRadius at a school with about 3000 users and divide them into different groups. Since support requests often come from teachers who cannot enter their login data with correct capitalization, we want to use FreeRadius "case insensitive".
At school we basically have 3 different groups.
username student : 1234567 (a student's unique ID) username teacher: ABCD username employee: t.surname
Some of our teachers try to login with "Abcd" or "abcd". That does not work, because FreeRadius is case sensitive.
Most databases are case-sensitive, too.
So far I've been browsing the forums and trying a few possible solutions.
This includes the solutions from the following thread: https://lists.freeradius.org/pipermail/freeradius-users/2020-February/097517...
1. Changes in the server configuration authorize { update request { Stripped-User-Name := "%{tolower:%{User-Name}}" } ... everything else ...
}
2. Changes in the "files" module key = "%{%{Stripped-User-Name}:-%{tolower:%{User-Name}}}"
That should work. IF all of the users and passwords are in the "users" file. So... where are the users? LDAP? SQL? Active Directory? You haven't said.
I can only test the login over the command line on our testserver, because i do not want to disrupt our currently working FreeRadius server including the wifi.
All tests performed follow the scheme below.
root@bkr-app:~# radtest TEST password 10.11.100.31:1812 10 test123
Why are you wasting your time looking at the output of radtest? ALL of the documentation says to run the server in debug mode. Read the documentation and follow it. You get a link to the documentation when you join the list: http://wiki.freeradius.org/list-help
Is there a way to use FreeRadius case insensitive so that we can reduce the support requests?
Yes. But it involves explaining what you're doing, and following the documentation. Alan DeKok.
Hello,
1. Changes in the server configuration authorize { >> update request { Stripped-User-Name := "%{tolower:%{User-Name}}" } ... everything else ...
}
2. Changes in the "files" module >> key = "%{%{Stripped-User-Name}:-%{tolower:%{User-Name}}}"
That should work. IF all of the users and passwords are in the "users" file. So... where are the users? LDAP? SQL? Active Directory? You haven't said.
Our users are logging in via LDAP. I´ve seen that the file "ldap" has some filter rules. # filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})" filter = "(uid=%{mschap:User-Name:-%%{User-Name}})" I´ve activated the first one without mschap and the variations with tolower -> filter = "(uid=%{mschap:User-Name:-%{tolower:%{User-Name}}})"
Why are you wasting your time looking at the output of radtest? ALL of the documentation says to run the server in debug mode.
Read the documentation and follow it. You get a link to the documentation when you join the list:
After starting freeradius in the debug mode i saw a configuration file that contains the attribute case_sensitive. That file "is called radutmp". But from my understanding of the file description that file is only for some sort of logging. To see who is logged in and some other stuff. After testing the the login while in debug mode, the error message stays the same. I´ve added the debug messages. You can see that the username is affected from the tolower in the server configuration in the authorize section. The problem lies with the ldap. The uid of the user stays as the normal input and seems not to be affected by the tolower string. LDAP somehow has no problem with it, because an user object is found with the right name. But it somehow can´t change the username in the request while searching the groups. (1) ldap: EXPAND (uid=%{mschap:User-Name:-%{tolower:%{User-Name}}}) (1) ldap: --> (uid=test) I hope my bad english is understandable. Received Access-Request Id 51 from 10.11.100.31:45609 to 10.11.100.31:1812 length 74 (1) User-Name = "Test" (1) User-Password = "start12K" (1) NAS-IP-Address = 10.11.100.31 (1) NAS-Port = 10 (1) Message-Authenticator = 0x4098a526c0098b192ce7ac66a620125f (1) # Executing section authorize from file /etc/freeradius/3.0/sites-enabled/VPN (1) authorize { (1) policy filter_username { (1) if (&User-Name) { (1) if (&User-Name) -> TRUE (1) if (&User-Name) { (1) if (&User-Name =~ / /) { (1) if (&User-Name =~ / /) -> FALSE (1) if (&User-Name =~ /@[^@]*@/ ) { (1) if (&User-Name =~ /@[^@]*@/ ) -> FALSE (1) if (&User-Name =~ /\.\./ ) { (1) if (&User-Name =~ /\.\./ ) -> FALSE (1) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) { (1) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) -> FALSE (1) if (&User-Name =~ /\.$/) { (1) if (&User-Name =~ /\.$/) -> FALSE (1) if (&User-Name =~ /@\./) { (1) if (&User-Name =~ /@\./) -> FALSE (1) } # if (&User-Name) = notfound (1) } # policy filter_username = notfound (1) update request { (1) EXPAND %{tolower:%{User-Name}} (1) --> test (1) Stripped-User-Name := test (1) } # update request = noop (1) policy filter_password { (1) if (&User-Password && (&User-Password != "%{string:User-Password}")) { (1) EXPAND %{string:User-Password} (1) --> start12K (1) if (&User-Password && (&User-Password != "%{string:User-Password}")) -> FALSE (1) } # policy filter_password = noop (1) [preprocess] = ok (1) [chap] = noop (1) [mschap] = noop (1) ntdomain: Checking for prefix before "\" (1) ntdomain: No '\' in User-Name = "test", looking up realm NULL (1) ntdomain: No such realm "NULL" (1) [ntdomain] = noop (1) eap: No EAP-Message, not doing EAP (1) [eap] = noop (1) files: EXPAND %{%{Stripped-User-Name}:-%{User-Name}} (1) files: --> test (1) [files] = noop rlm_ldap (ldap): Reserved connection (3) (1) ldap: EXPAND (uid=%{mschap:User-Name:-%{tolower:%{User-Name}}}) (1) ldap: --> (uid=Test) (1) ldap: Performing search in "dc=my,dc=bkrnet,dc=de" with filter "(uid=Test)", scope "sub" (1) ldap: Waiting for search result... (1) ldap: User object found at DN "uid=TEST,cn=lehrer,cn=users,ou=bkrheine,dc=my,dc=bkrnet,dc=de" (1) ldap: Processing user attributes (1) ldap: control:Password-With-Header += '{crypt}$1$Opdss7kS$eZKivFVADLBPjgEEbmynZ.' (1) ldap: control:NT-Password := 0x3242343330343534394332353342443445353237413139353243433434424432 rlm_ldap (ldap): Released connection (3) rlm_ldap (ldap): Need 4 more connections to reach 10 spares rlm_ldap (ldap): Opening additional connection (6), 1 of 26 pending slots used rlm_ldap (ldap): Connecting to ldap://bkr-app.my.bkrnet.de:7389 rlm_ldap (ldap): Waiting for bind result... rlm_ldap (ldap): Bind successful (1) [ldap] = updated (1) [expiration] = noop (1) [logintime] = noop (1) pap: Converted: &control:Password-With-Header -> &control:Crypt-Password (1) pap: Removing &control:Password-With-Header (1) pap: Normalizing NT-Password from hex encoding, 32 bytes -> 16 bytes (1) [pap] = updated (1) } # authorize = updated (1) Found Auth-Type = PAP (1) # Executing group from file /etc/freeradius/3.0/sites-enabled/VPN (1) Auth-Type PAP { (1) pap: Login attempt with password (1) pap: Comparing with "known-good" NT-Password (1) pap: User authenticated successfully (1) [pap] = ok (1) if (noop && User-Password) { (1) if (noop && User-Password) -> FALSE (1) } # Auth-Type PAP = ok (1) # Executing section post-auth from file /etc/freeradius/3.0/sites-enabled/VPN (1) post-auth { (1) update { (1) No attributes updated (1) } # update = noop (1) policy remove_reply_message_if_eap { (1) if (&reply:EAP-Message && &reply:Reply-Message) { (1) if (&reply:EAP-Message && &reply:Reply-Message) -> FALSE (1) else { (1) [noop] = noop (1) } # else = noop (1) } # policy remove_reply_message_if_eap = noop (1) ldap: EXPAND . (1) ldap: --> . (1) [ldap] = noop (1) if ("%{request:User-Name}" =~ /^host\/(.*).domain$/) { (1) EXPAND %{request:User-Name} (1) --> test (1) if ("%{request:User-Name}" =~ /^host\/(.*).domain$/) -> FALSE (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=schueler-bkrheine))}") { rlm_ldap (ldap): Reserved connection (4) (1) Performing search in "dc=my,dc=bkrnet,dc=de" with filter "(&(memberUid=test)(cn=schueler-bkrheine))", scope "sub" (1) Waiting for search result... (1) Search returned no results rlm_ldap (ldap): Released connection (4) (1) EXPAND %{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=schueler-bkrheine))} (1) --> (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=schueler-bkrheine))}") -> FALSE (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=lehrer-bkrheine))}") { rlm_ldap (ldap): Reserved connection (0) (1) Performing search in "dc=my,dc=bkrnet,dc=de" with filter "(&(memberUid=test)(cn=lehrer-bkrheine))", scope "sub" (1) Waiting for search result... (1) Search returned no results rlm_ldap (ldap): Released connection (0) (1) EXPAND %{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=lehrer-bkrheine))} (1) --> (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=lehrer-bkrheine))}") -> FALSE (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=admins-bkrheine))}") { rlm_ldap (ldap): Reserved connection (5) (1) Performing search in "dc=my,dc=bkrnet,dc=de" with filter "(&(memberUid=test)(cn=admins-bkrheine))", scope "sub" (1) Waiting for search result... (1) Search returned no results rlm_ldap (ldap): Released connection (5) (1) EXPAND %{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=admins-bkrheine))} (1) --> (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=admins-bkrheine))}") -> FALSE (1) else { (1) update reply { (1) Reply-Message := "DEBUG: Not found, reject" (1) } # update reply = noop (1) [reject] = reject (1) } # else = reject (1) } # post-auth = reject (1) Using Post-Auth-Type Reject (1) # Executing group from file /etc/freeradius/3.0/sites-enabled/VPN (1) REJECT { ... } # empty sub-section is ignored (1) Rejected in post-auth: [test/start12K] (from client bkr-app port 10) (1) Delaying response for 1.000000 seconds Waking up in 0.3 seconds. Waking up in 0.6 seconds. (1) Sending delayed response (1) Sent Access-Reject Id 51 from 10.11.100.31:1812 to 10.11.100.31:45609 length 46 (1) Reply-Message := "DEBUG: Not found, reject" Greetings, Simon
On May 13, 2022, at 8:44 AM, Simon Dankau <SDankau@rf-computer.de> wrote:
Our users are logging in via LDAP. I´ve seen that the file "ldap" has some filter rules.
# filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})" filter = "(uid=%{mschap:User-Name:-%%{User-Name}})"
The default filter is the first one. Changing it to mschap likely isn't correct.
I´ve activated the first one without mschap and the variations with tolower -> filter = "(uid=%{mschap:User-Name:-%{tolower:%{User-Name}}})"
That should work. Or just lowercase the whole thing: filter = "(uid=%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}})"
After starting freeradius in the debug mode i saw a configuration file that contains the attribute case_sensitive. That file "is called radutmp". But from my understanding of the file description that file is only for some sort of logging. To see who is logged in and some other stuff.
Yes. Configuration for one module doesn't affect another module.
After testing the the login while in debug mode, the error message stays the same. I´ve added the debug messages.
That's good.
You can see that the username is affected from the tolower in the server configuration in the authorize section. The problem lies with the ldap. The uid of the user stays as the normal input and seems not to be affected by the tolower string.
The server is printing out what it got from LDAP.
LDAP somehow has no problem with it, because an user object is found with the right name. But it somehow can´t change the username in the request while searching the groups.
Because it gets an LDAP user DN from the first LDAP search, which it then uses for the group lookups. That LDAP user DN comes from LDAP. So... how are the user names stored in LDAP? Lowercase?
I hope my bad english is understandable.
Your English is fine.
Received Access-Request Id 51 from 10.11.100.31:45609 to 10.11.100.31:1812 length 74 (1) User-Name = "Test" (1) User-Password = "start12K" (1) NAS-IP-Address = 10.11.100.31 (1) NAS-Port = 10 (1) Message-Authenticator = 0x4098a526c0098b192ce7ac66a620125f
That's good...
(1) update request { (1) EXPAND %{tolower:%{User-Name}} (1) --> test (1) Stripped-User-Name := test
You're setting that, but not using it anywhere. This doesn't seem right. Just delete this.
(1) ldap: EXPAND (uid=%{mschap:User-Name:-%{tolower:%{User-Name}}}) (1) ldap: --> (uid=Test)
Because of the %{mschap:User-Name} expansion. Don't do that. Just do: filter = "(uid=%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}})"
(1) ldap: Performing search in "dc=my,dc=bkrnet,dc=de" with filter "(uid=Test)", scope "sub" (1) ldap: Waiting for search result... (1) ldap: User object found at DN "uid=TEST,cn=lehrer,cn=users,ou=bkrheine,dc=my,dc=bkrnet,dc=de"
That is information which is coming from LDAP. Go check LDAP. The user name in LDAP is "TEST". Not "test". And also the LDAP database is doing case insensitive name comparisons. You ask it for "Test", and it returns user "TEST". So at least part of the problem here is that you're not really clear on what the problem is. You think it's that the name checks are case sensitive, but they're not. And then in an attempt to fix this, you're trying a bunch of random things, without being clear on what you're changing or why.
(1) pap: Converted: &control:Password-With-Header -> &control:Crypt-Password (1) pap: Removing &control:Password-With-Header (1) pap: Normalizing NT-Password from hex encoding, 32 bytes -> 16 bytes (1) [pap] = updated (1) } # authorize = updated (1) Found Auth-Type = PAP (1) # Executing group from file /etc/freeradius/3.0/sites-enabled/VPN (1) Auth-Type PAP { (1) pap: Login attempt with password (1) pap: Comparing with "known-good" NT-Password (1) pap: User authenticated successfully
That's good.
(1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=schueler-bkrheine))}") { rlm_ldap (ldap): Reserved connection (4) (1) Performing search in "dc=my,dc=bkrnet,dc=de" with filter "(&(memberUid=test)(cn=schueler-bkrheine))", scope "sub" (1) Waiting for search result... (1) Search returned no results rlm_ldap (ldap): Released connection (4) (1) EXPAND %{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=schueler-bkrheine))} (1) --> (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=schueler-bkrheine))}") -> FALSE
You can run those queries manually via "ldapsearch" to verify what they do. See the documentation in mods-available/ldap.
(1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=lehrer-bkrheine))}") { rlm_ldap (ldap): Reserved connection (0) (1) Performing search in "dc=my,dc=bkrnet,dc=de" with filter "(&(memberUid=test)(cn=lehrer-bkrheine))", scope "sub" (1) Waiting for search result... (1) Search returned no results rlm_ldap (ldap): Released connection (0) (1) EXPAND %{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=lehrer-bkrheine))} (1) --> (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=lehrer-bkrheine))}") -> FALSE (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=admins-bkrheine))}") { rlm_ldap (ldap): Reserved connection (5) (1) Performing search in "dc=my,dc=bkrnet,dc=de" with filter "(&(memberUid=test)(cn=admins-bkrheine))", scope "sub" (1) Waiting for search result... (1) Search returned no results rlm_ldap (ldap): Released connection (5) (1) EXPAND %{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=admins-bkrheine))} (1) --> (1) elsif ("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{User-Name})(cn=admins-bkrheine))}") -> FALSE (1) else { (1) update reply { (1) Reply-Message := "DEBUG: Not found, reject"
i.e. the "test' user isn't in any of the groups. And you configured the server to reject users who aren't in any of those groups. So FreeRADIUS is doing exactly what you told it to do. If you want an Access-Accept, put the "test" user into one of the groups. Once that works, you can go check real user accounts. Tests are nice, but they're not always the same was what is coming in for real users. Alan DeKok.
Hello Alan,
So... how are the user names stored in LDAP? Lowercase?
Our users are categorized in 3 different groups. The usernames of those groups looks like this: username student : 1234567 (a student's unique ID) username teacher: ABCD username employee: t.surname I found the right solution to my problem. My thoughts on this was that it would be hard to geht right because we differentiate between 3 to 4 groups with different upper and lower case. After understanding more and more with the authentication and authorization and when and where username changes affect freeradius I found the right spot to use toloer and toupper. We just had to implement one single change in our configuration..... The change that was needed is in our server configuration. I just had to add tolower to our teachers group in the ldap search and toupper for our employees. So the LDAP search looks like this now: elsif("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{toupper:%{User-Name}})(cn=lehrer-bkrheine))}") { update reply { .... } After testing this configuration our user TEST can be written test, Test and so on. It always comes out as TEST and gets access to our radius wifi. I'll have to thank you for brining me on the right track and for your time and suggestions. Thank you and best regards, Simon
On May 13, 2022, at 9:44 AM, Simon Dankau <SDankau@rf-computer.de> wrote:
So... how are the user names stored in LDAP? Lowercase?
Our users are categorized in 3 different groups. The usernames of those groups looks like this:
username student : 1234567 (a student's unique ID) username teacher: ABCD username employee: t.surname
I'm presuming that they're in different DNs? Or they're in different groups? You need a way to differentiate them.
I found the right solution to my problem.
My thoughts on this was that it would be hard to geht right because we differentiate between 3 to 4 groups with different upper and lower case. After understanding more and more with the authentication and authorization and when and where username changes affect freeradius I found the right spot to use toloer and toupper. We just had to implement one single change in our configuration.....
The change that was needed is in our server configuration. I just had to add tolower to our teachers group in the ldap search and toupper for our employees.
So the LDAP search looks like this now: elsif("%{ldap:ldap:///dc=my,dc=bkrnet,dc=de?cn?sub?(&(memberUid=%{toupper:%{User-Name}})(cn=lehrer-bkrheine))}") { update reply { .... }
That's reasonable. It also shows that even though FreeRADIUS can be difficult at times, it lets you do pretty much whatever you want.
After testing this configuration our user TEST can be written test, Test and so on. It always comes out as TEST and gets access to our radius wifi.
That's good to hear.
I'll have to thank you for brining me on the right track and for your time and suggestions.
You're welcome. Alan DeKok.
participants (2)
-
Alan DeKok -
Simon Dankau