- FREERADIUS WITH MIKROTIK – Part #1
- FREERADIUS WITH MIKROTIK – Part #2
- FREERADIUS WITH MIKROTIK – Part #3
- FREERADIUS WITH MIKROTIK – Part #4
- FREERADIUS WITH MIKROTIK – Part #5
- FREERADIUS EXTERNAL AUTH SCRIPT & RADPOSTAUTH – Part #6 >>> YOU ARE HERE
Note: This post is for reference purposes only. This post may change rapidly as at the time of writing , frequent changes are being made to adjust many parameters to fulfill local OP requirements. So you may see some display text difference as compared in the code itself … worry not 🙂 Just read and Enjoy, Pick your desired section.
Also you may want to read this reply came from ALAN (freeradius) when I asked on external auth script.
FreeRadius users mailing list (freeradius-users@lists.freeradius.org)
________________________________
From: Freeradius-Users <freeradius-users-bounces+aacable=hotmail.com@lists.freeradius.org> on behalf of Alan DeKok <aland@deployingradius.com>
Sent: Saturday, January 13, 2018 8:30:02 PM
To: FreeRadius users mailing list
Subject: Re: External Auth Script or local AuthOn Jan 13, 2018, at 8:40 AM, JAHANZAIB SYED <aacable@hotmail.com> wrote:
> I am using Freeradius ver 2.1.10You should upgrade to 2.2.10. There’s just no reason to use a version which is almost 10 years old
> I have noticed that some commercial radius servers (with freeradius backend) using External script (php/perl or C code) to authenticate users. I just wanted to know that what are the additional benefits of using external auth script over freeradius own authentication (via rad-groups) ?
So that they can avoid GPL licensing issues.
> I made my own bash script that runs fine for authentication by checking user status in my_users table like expiry, disable/enable, quota, uptime etc , but for heavy load network like 20-30k users, what is recommended?
Use the features in FreeRADIUS. They work. They’re also MUCH faster than forking an external program.
i.e. FreeRADIUS can do 10’s of 1000’s of DB queries per second, and 10’s of 1000’s of authentications per second. When you use shell script, that number can drop by 10x to 100x.
On top of that, why re-implement features which already work? It doesn’t make any sense to write a shell script to do something, when FreeRADIUS can already do it.
Most people who are re-packaging FreeRADIUS are figuring this out. Some have taken the source, and hacked it up… at which point they have their own magic server that no one understands.
And a few years later, FreeRADIUS has more / better features than their hacked-up version, and they can no longer sell decent features to their customers.Alan DeKok.
z@ib
Following is an BASH script which we can use to authenticate users in freeradius. Using external auth script have several benefits over traditional attributes. Example we can provide NAS with radreply based on various attributes in users table like expiration, quota, disabled/expired users & other crazy stuff 🙂
It took many hours for a duffer like me to accomplish this task. Some says BASH is not suitable for servers under heavy load, therefore you may look for C/C++ approach for faster fetching results. I have tested this with 1000 + loop auth requests & it worked fine without putting any load on the server & without any invalid or missing any single request !
Regard’s
Syed Jahanzaib
Scenario:
- OS: Ubuntu 12.4 Server Edition
- FreeRADIUS Version 2.1.10, for host i686-pc-linux-gnu
- Mysql Ver 14.14 Distrib 5.5.54, for debian-linux-gnu (i686) using readline 6.2
>>> OP Requirements :
- If users not found, It should display error message in debug log & REJECT user request
- If user password does not match with the radcheck table entry, REJECT the user request
- If user is Disabled by OP, let him login with disabled-pool (for redirection purposes)
- If user is Expired, let him login with expired-pool (for redirection purposes)
- If user is logged in for the first time, his MAC should update in USERS table for AUTO MAC Binding purposes. So only this device should be able to connect in future
- If user try to login from other device, let him login with invalidmac-pool (for redirection purposes)
- Check for Over Quota users (not required at a moment)
- LOG requests in mysql RADPOSTAUTH table for REJECT actions
So in short we donot want to REJECT user login request if it matches above criteria.
Yes I know its a kind of weird stuff, but still its good for learning !
>>> Mysqle ‘USERS’ Table example
In mysql we have following users table [Just an example, its not fine tuned]
#### USERS TABLE root@testradius:/temp# mysql -uroot -pROOTPASS -e "use radius; describe users;" +------------+---------------+------+-----+-------------------+-----------------------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------------+------+-----+-------------------+-----------------------------+ | id | int(10) | NO | PRI | NULL | auto_increment | | username | varchar(128) | NO | MUL | NULL | | | password | varchar(32) | NO | | NULL | | | firstname | text | NO | | NULL | | | lastname | text | NO | | NULL | | | email | text | NO | | NULL | | | mobile | text | NO | | NULL | | | cnic | text | NO | | NULL | | | srvname | text | NO | | NULL | | | srvid | int(11) | NO | | NULL | | | expiration | datetime | YES | | NULL | | | mac | varchar(30) | NO | | NULL | | | bwpkg | varchar(1000) | NO | | NULL | | | pool | varchar(128) | YES | | other | | | enabled | varchar(1) | NO | | NULL | | | owner | text | NO | | NULL | | | createdon | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | +------------+---------------+------+-----+-------------------+-----------------------------+ 17 rows in set (0.00 sec) root@testradius:/temp# mysql -uroot -pROOTPASS -e "use radius; select * from users;" +----+----------+----------+----------------+---------------+---------------------+-------------+-------------------+---------+-------+---------------------+-------------------+-------------+----------------------+---------+--------+---------------------+ | id | username | password | firstname | lastname | email | mobile | cnic | srvname | srvid | expiration | mac | bwpkg | pool | enabled | owner | createdon | +----+----------+----------+----------------+---------------+---------------------+-------------+-------------------+---------+-------+---------------------+-------------------+-------------+----------------------+---------+--------+---------------------+ | 1 | zaib | | syed | jahanzaib | aacableAThotmail.com | 03333021909 | 1234567890-1-1 | | 0 | 2018-12-23 00:00:00 | 00:0C:29:35:F8:2F | 1024k/1024k | public-pool | 1 | galaxy | 2017-12-23 16:25:09 | | 7 | test | | test firstname | test lastname | test@test.com | 13434234234 | 242342420424-42-2 | | 0 | 2017-12-24 00:00:00 | 00:0C:29:35:F8:2F | 2048k/2048k | public-multinet-pool | 1 | zaib | 2017-12-25 16:27:39 | +----+----------+----------+----------------+---------------+---------------------+-------------+-------------------+---------+-------+---------------------+-------------------+-------------+----------------------+---------+--------+---------------------+
FREERADIUS config to accommodate external authentication script [auth.sh] execution
We need to add following directives in FR config files in order to execute the bash script for auth purpose once the username/password is found ok by FR.
>>> Edit freeradius/USERS file in FR
nano /etc/freeradius/users
& Add following in the end of this file …
DEFAULT Auth-Type := PAP Exec-Program-Wait = "/temp/auth.sh %{User-Name} %{User-Password} %{Calling-Station-Id}"
SAVE & EXIT !
>>> Now edit sites-enabled/default file …
nano /etc/freeradius/sites-enabled/default
& use following … [my working sample file, you must modify it as per your requirements]
authorize { preprocess chap pap mschap digest # suffix # eap { # ok = return # } files ### ZAIB Section-1 Start Here ## sql{ notfound = 1 } if(notfound){ update reply { Reply-Message = 'Username not found' } reject } ### ZAIB Section-1 Ends Here ## # checkval # expiration # logintime # pap } authenticate { Auth-Type PAP { pap } Auth-Type CHAP { chap } Auth-Type MS-CHAP { mschap } digest unix # eap } preacct { preprocess acct_unique suffix files } accounting { detail unix radutmp sql exec attr_filter.accounting_response } session { # radutmp sql } ### ZAIB Section-2 Start Here ## post-auth { exec Post-Auth-Type REJECT { update reply { Reply-Message = 'Wrong Password' } sql attr_filter.access_reject } } ### ZAIB Section-2 ENDS Here ## pre-proxy { } post-proxy { eap }
SAVE & EXIT !
AUTH.SH [bash script as external auth for FR]
Create auth.sh file
- mkdir /temp
- touch /temp/auth.sh
- chmod+x /temp/auth.sh
- nano /temp/auth.sh
& paste following data [after necessary modifications]
#!/bin/bash # Bash script for Freeradius / external auth script # By Syed Jahanzaib / aacable at hotmail dot com # https://aacable.wordpress.com # 22-DEC-2017 # Last modified on 27-DEC-2017 # You may want to disable few sections in it like user validation, password validation # because both are now handled by FR with our custom reply message hurrahhhhh zaib. #set -x USERNAME=$1 PASS=$2 MAC=$3 MYSQL_USR="root" MYSQL_PASS="ROOT-OR-SQL-PASS" DB="radius" TBL="users" MSG="" USRISVALID=`mysql -u$MYSQL_USR -p$MYSQL_PASS --skip-column-names -s -e "use $DB; select id from $TBL where username ='$USERNAME';" | tr "'" " "` if [ -z "$USRISVALID" ];then echo "$USERNAME >>>> User Not Found." exit 1 fi CHKPASS=`mysql -u$MYSQL_USR -p$MYSQL_PASS --skip-column-names -s -e "use $DB; select password from users where username='$USERNAME';"` if [ "$PASS" != "$CHKPASS" ];then echo "$USERNAME / $PASS >>> Incorrect Password." exit 1 fi ENORDIS=`mysql -u$MYSQL_USR -p$MYSQL_PASS -s -e "use $DB; select enabled from $TBL where username ='$USERNAME';" | tail -n 1` if [ "$ENORDIS" == "0" ];then echo 'Mikrotik-Rate-Limit="'1k/1k'",Framed-Pool="'disabled-pool'",Session-Timeout="'0'"' exit 0 fi CHECKMAC=`mysql -u$MYSQL_USR -p$MYSQL_PASS --skip-column-names -s -e "use $DB; select mac from $TBL where username ='$USERNAME';" | tail -n 1` if [ -z "$CHECKMAC" ];then mysql -u$MYSQL_USR -p$MYSQL_PASS -e "use $DB; UPDATE $TBL SET mac ='$MAC' WHERE users.username ='$USERNAME' LIMIT 1;" fi if [ "$MAC" != "$CHECKMAC" ];then echo 'Mikrotik-Rate-Limit="'1k/1k'",Framed-Pool="'invalidmac-pool'",Session-Timeout="'0'"' exit 0 fi SECONDS=`mysql -u$MYSQL_USR -p$MYSQL_PASS -s -e "use $DB; select time_to_sec(TIMEDIFF (expiration, NOW() ) ) as secs from users where username ='$USERNAME';" | tail -n 1` if [ "$SECONDS" -lt 0 ]; then echo 'Mikrotik-Rate-Limit="'1k/1k'",Framed-Pool="'expired-pool'",Session-Timeout="'0'"' exit 0 fi POOL=`mysql -u$MYSQL_USR -p$MYSQL_PASS -s -e "use $DB; select pool as pool from $TBL where username ='$USERNAME';" | tail -n 1` BWPKG=`mysql -u$MYSQL_USR -p$MYSQL_PASS -s -e "use $DB; select bwpkg as bwpkg from $TBL where username ='$USERNAME';" | tail -n 1` echo 'Framed-Pool="'$POOL'",Session-Timeout="'$SECONDS'",Mikrotik-Rate-Limit="'$BWPKG'",' exit 0 fi
EXAMPLE:
After all is configured properly, we can see the results as following.
>>> To test with the script it self
root@testradius:/temp# ./auth.sh MR.PONKA PASS 00:0C:29:35:F8:2F MR.PONKA >>>> User Not Found... ***** root@testradius:/temp# ./auth.sh zaib PASS 00:0C:29:35:F8:2F Framed-Pool="public-pool",Session-Timeout="3020399",Mikrotik-Rate-Limit="1024k/1024k", root@testradius:/temp# ./auth.sh zaib PASS 11:11:11:11:11:11 Mikrotik-Rate-Limit="1k/1k",Framed-Pool="invalidmac-pool",Session-Timeout="0" root@testradius:/temp# ./auth.sh test PASS 00:0C:29:35:F8:2F Mikrotik-Rate-Limit="1k/1k",Framed-Pool="expired-pool",Session-Timeout="0"
>>> To test from RADCLIENT ,
we can use the following radclient syntax to test username/pass/mac.
echo "User-Name = zaib, Password = zaib, Calling-Station-Id =00:0C:29:35:F8:2F" | radclient -s localhost:1812 auth RADIUS_SECRET
If user not found
Sending delayed reject for request 2 Sending Access-Reject of id 32 to 127.0.0.1 port 57901 Reply-Message = "Username not found" Waking up in 4.9 seconds.
If user is connecting from other device [mac binding scenario]
+- entering group post-auth {...} Exec-Program output: Mikrotik-Rate-Limit="1k/1k",Framed-Pool="invalidmac-pool",Session-Timeout="0" Exec-Program-Wait: value-pairs: Mikrotik-Rate-Limit="1k/1k",Framed-Pool="invalidmac-pool",Session-Timeout="0" Exec-Program: returned: 0 ++[exec] returns ok Sending Access-Accept of id 113 to 127.0.0.1 port 50894 Mikrotik-Rate-Limit = "1k/1k" Framed-Pool = "invalidmac-pool" Session-Timeout = 0
If user account is expired…
+- entering group post-auth {...} Exec-Program output: Mikrotik-Rate-Limit="1k/1k",Framed-Pool="expired-pool",Session-Timeout="0" Exec-Program-Wait: value-pairs: Mikrotik-Rate-Limit="1k/1k",Framed-Pool="expired-pool",Session-Timeout="0" Exec-Program: returned: 0 ++[exec] returns ok Sending Access-Accept of id 176 to 127.0.0.1 port 36544 Mikrotik-Rate-Limit = "1k/1k" Framed-Pool = "expired-pool" Session-Timeout = 0
& IF all is OK, & account is valid …. then user will be able to connect with his assigned profile present in the users table …
+- entering group post-auth {...} Exec-Program output: Framed-Pool="public-pool",Session-Timeout="3020399",Mikrotik-Rate-Limit="1024k/1024k", Exec-Program-Wait: value-pairs: Framed-Pool="public-pool",Session-Timeout="3020399",Mikrotik-Rate-Limit="1024k/1024k", Exec-Program: returned: 0 ++[exec] returns ok Sending Access-Accept of id 0 to 127.0.0.1 port 37090 Framed-Pool = "public-pool" Session-Timeout = 3020399 Mikrotik-Rate-Limit = "1024k/1024k"
RADPOSTAUTH mysql query section
Following is RADPOSTAUTH Table to accommodate modified message.
mysql>describe radpostauth; +--------------+--------------+------+-----+-------------------+-----------------------------+ | Field | Type | Null | Key | Default | Extra | +--------------+--------------+------+-----+-------------------+-----------------------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | username | varchar(64) | NO | | | | | pass | varchar(64) | NO | | | | | mac | varchar(18) | NO | | NULL | | | nasipaddress | varchar(16) | NO | | NULL | | | reply | varchar(200) | NO | | | | | authdate | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | | reason | varchar(100) | NO | | NULL | | +--------------+--------------+------+-----+-------------------+-----------------------------+
MYSQL QUERY:
This is mysql query which will be executed on every login attempt. Either success or failed. Replace existing or modify as required.
cat /etc/freeradius/sql/mysql/dialup.conf
postauth_query = "INSERT into ${postauth_table} (username, pass, mac, nasipaddress, reply, authdate, reason) values ('%{User-Name}', '%{User-Password:-Pap-Password}', '%{Calling-Station-Id}', '%{NAS-IP-Address}', '%{reply:Packet-Type}', NOW(), '%{reply:Reply-Message}')"
Now restart your freeradius service in DEBUG mode
freeradius -X
& issue following commands in other terminal window
# echo "User-Name = zaib1, Password = wrongpass, Calling-Station-Id =00:0C:29:35:F8:2F" | radclient -s localhost:1812 auth testing123 Received response ID 30, code 3, length = 40 Reply-Message = "Username not found" Total approved auths: 0 Total denied auths: 1 Total lost auths: 0 # echo "User-Name = zaib, Password = wrongpass, Calling-Station-Id =00:0C:29:35:F8:2F" | radclient -s localhost:1812 auth testing123 Received response ID 77, code 3, length = 47 Reply-Message = "Wrong Password" Total approved auths: 0 Total denied auths: 1 Total lost auths: 0 # echo "User-Name = zaib, Password = zaib, Calling-Station-Id =00:0C:29:35:F8:2F" | radclient -s localhost:1812 auth testing123 Received response ID 121, code 2, length = 58 Framed-Pool = "public-pool" Session-Timeout = 3020399 Mikrotik-Rate-Limit = "1024k/1024k" Total approved auths: 1 Total denied auths: 0 Total lost auths: 0
Now look at the debug window
& you will get
# debug for USERNAME not found request # debug for WRONG Username Sending delayed reject for request 13 Sending Access-Reject of id 30 to 127.0.0.1 port 46089 Reply-Message = "Username not found" # debug for WRONG Password Sending Access-Reject of id 77 to 127.0.0.1 port 41764 Reply-Message = "Wrong Password" Waking up in 4.9 seconds. Cleaning up request 14 ID 77 with timestamp +666 Ready to process requests. # debug for SUCCESSFUL request [pap] login attempt with password "zaib" [pap] Using clear text password "zaib" [pap] User authenticated successfully ++[pap] returns ok # Executing section post-auth from file /etc/freeradius/sites-enabled/default +- entering group post-auth {...} Exec-Program output: Framed-Pool="public-pool",Session-Timeout="3020399",Mikrotik-Rate-Limit="1024k/1024k", Exec-Program-Wait: value-pairs: Framed-Pool="public-pool",Session-Timeout="3020399",Mikrotik-Rate-Limit="1024k/1024k", Exec-Program: returned: 0 ++[exec] returns ok Sending Access-Accept of id 121 to 127.0.0.1 port 46954 Framed-Pool = "public-pool" Session-Timeout = 3020399 Mikrotik-Rate-Limit = "1024k/1024k" Finished request 15. Going to the next request Waking up in 4.9 seconds. Cleaning up request 15 ID 121 with timestamp +675 Ready to process requests.
Now look @ the radpostauth table
Alhamdolillah !
[…] FREERADIUS EXTERNAL AUTH SCRIPT & RADPOSTAUTH – Part #6 […]
LikeLike
Pingback by Mikrotik with Freeradius/mySQL # Part-1 | Syed Jahanzaib Personal Blog to Share Knowledge ! — December 26, 2017 @ 4:23 PM
[…] Freeradius External Auth BASH Script & RADPOSTUATH logging with customized reply message ! […]
LikeLike
Pingback by Playing with the `radpostauth` table in Freeradius | Syed Jahanzaib Personal Blog to Share Knowledge ! — December 26, 2017 @ 4:30 PM
[…] FREERADIUS WITH MIKROTIK – Part # 6 – External Auth Script & RADPOSTAUTH […]
LikeLike
Pingback by Mikrotik with Freeradius/mySQL – Quota Limit # Part-7 | Syed Jahanzaib Personal Blog to Share Knowledge ! — January 8, 2018 @ 11:37 AM
[…] FREERADIUS WITH MIKROTIK – Part # 6 – External Auth Script & RADPOSTAUTH […]
LikeLike
Pingback by Mikrotik with Freeradius/mySQL – Change IP Pool After Expiration # Part-3 | Syed Jahanzaib Personal Blog to Share Knowledge ! — January 10, 2018 @ 12:48 PM
[…] FREERADIUS WITH MIKROTIK – Part # 6 – External Auth Script & RADPOSTAUTH […]
LikeLike
Pingback by Mikrotik with Freeradius/mySQL – Trimming & Archiving RADACCT # Part-8 | Syed Jahanzaib Personal Blog to Share Knowledge ! — January 15, 2018 @ 2:40 PM