June 26, 2013

Pi Job – Part 3

  

And we’re down to the last part of setting up your Raspberry Pi as a radius authentication server and together with a compatible wireless router, building your own private hotspot.

Hotspot login script

What is the hotspot login script?

hotspotlogin

Well what it is, is a script (in shell or perl or php or any other language that suits you) which handles the entire login and authentication process to your radius server and it gives you the login page similar to whats shown above. There are lots of guides to write one of these, but I prefer not to reinvent the wheel, so I just lifted the default hotspotlogin.cgi from Chillispot. Chillispot was the foremost hotspot solution available but its all but defunct now, with the author having completely vanished and development taken over by CoovaChilli. You can get the hotspotlogin.cgi by downloading the source code from the CoovaChilli website and after uncompressing the tar.gz file, the hotspotlogin.cgi is in coova-chilli-x.x.x/doc/ where x.x.x (mind out of the gutter!) is the version.

Place the hotspotlogin.cgi into /var/www/cgi-bin and make sure its executable. You only need to edit two things in the file:

$uamsecret = "theuamsecret";
$userpassword=1;

The uamsecret is yet another phrase to link your wireless router to your hotspot, so remember it as you’ll need it later when you configure your router.

If everything is setup correctly, and you try to use your browser to get to the hotspotlogin.cgi page (eg: http://192.168.1.20/cgi-bin/hotspotlogin.cgi) you should be greeted by a failure message:

loginthruhotspot

It essentially means you can’t just browse directly to the login page, you need to associate through your wireless router to get to it. Why? Well remember, this isn’t a page that should be accessible from anywhere by anyone on any machine unless its for the specific purpose of validating their credentials so that they can connec to the internet from your network.

Configuring the wireless router

Hotspot configuration

Not all wireless routers come with a hotspot function and infact you’re going to be hard pressed to find a SOHO wireless router or AP  that actually does. The last wireless APs that came with a hotspot, as far as I tinkered with, was the 3 Com Office connect series, which, if memory serves, had its own built in hotspot login  (so there was no need for one to configure their own webserver) . These APs however, cost a bomb which I don’t think you want to spend on a simple SOHO setup. The cheap alternative is to get a simple home router, like the Linksys WRT54G series (I use the WRT54GL) and overwrite the stock Linksys firmware with Tomato, DD-WRT or OpenWRT. I’m not going to go into the installation of DD-WRT , etc because there are so many tutorials on how to do it already, especially on the individual sites themselves. If you’ve managed to get this far with my posts, then installing the firmware into the router would be a breeze. What I will cover is how to fill up the essential parts of the hotspot config.

The configuration for the hotspot can be seperated into core, networking and captive portal sections. In a lot of hotspot implementations these days, the core and networking sections will be merged amd some (like DD-WRT) have only a single section encompassing all three.

Core

The core portion takes care of the client machines that connect to your hotspot and basically takes information on the following:

  • DHCP
  • DNS

For the DHCP portion, the system needs to know what network you want your clients to be on. Your home network might be 192.168.1.0/24, so to ensure complete separation from your clients and home network, you can have them be on 192.168.120.0/24 . The DHCP portion may also you how long you want the IP assigned to a client to last for (if not renewed) before it allows some other client to grab it – this is called the DHCP lease. For a hotspot, 600 seconds is more than enough.

On the DNS portion, you’ll need to decide which DNS servers you want your clients to be referred to for resolving addresses (URLs for example). This all depends on what your purpose for the hotspot is. If you want to restrict all your client to only say Google – you can setup your own DNS server (using DNSMasq for example) and configure every resolution to point to Google’s IP. Of course, the clients could very easily set their machines to point to very specific DNS servers, so I find that specifying public DNS servers (like Google’s DNS) is a much better option.

Networking

You might be wondering why the core section’s DHCP and DNS settings are not in the networking portion. Well thats because the networking here is referring to the radius server’s networking options. Key parts here would be:

  • Radius server (IP)
  • Radius secret

The radius server is the IP address of your radius server – in this case, its the IP address of your Raspberry Pi box. Some configurations will ask for a second radius server, but since you have only one, just leave it blank.

The radius secret is nothing more than the secret pass phrase you set up in your Free Radius clients.conf

root@rpi:/etc/freeradius/# cat clients.conf
client 192.168.1.0/24 {
        secret          = thisismysecretphrase
}

In this case, the radius secret is “thisismysecretphrase”. There might also be some port settings, but unless you changed the ports in the radius server setup, you should be able to leave these as the default values.

Captive Portal

The last section relates to the webserver where your hotspotlogin.cgi is and tells the hotspot that for all unauthenticated network requests, send the client to this webpage. Now this only redirects web traffic, so if the client is trying to do SSH, FTP, etc they will just time out cos the packets won’t be allowed out. They have to open a browser and try to go to some URL so they will get redirected to the login page. What you have to fill in here is:

  • UAM server (URL)
  • UAM secret

Before you ask, UAM stands for Universal Access Method and its a common wifi term used to depict the process of using a web based login to gain access to a network.

The UAM server is the full URL to your hotspotlogin.cgi. From the previous posts, we know that in this write up it would be https://192.168.1.20/cgi-bin/hotspotlogin.cgi. Fill in your own IP and location of the hotspotlogin script as appropriate and don’t forget its https and not http.

The UAM secret is what you defined in your hotspotlogin.cgi. In this write up, all the way at the beginning of this post, you can see the UAM secret was set (boringly) to “theuamsecret”.

Now some captive portal settings may also have options for:

  • UAM allowed
  • UAM any DNS

The UAM allowed lets you set specific IPs or addresses that can be fully accessed without a client needing to be authenticated. Like how some hotspots irritatingly let you do a Google search but won’t let you click on any of the found links without authenticating.

The UAM any DNS is usually a checkbox and when checked allows users to specify their own DNS servers. Remember under the DNS portion of the core section I mentioned that clients may specify their own DNS servers to bypass using your defined DNS servers? Well this basically allows or disallows them to do that. Uncheck this, and all clients will need to use your defined DNS or nothing gets resolved. This is actually a dangerous part of hotspots – a hotspot owner can very easily setup a rogue DNS that redirects bank or mail server URLs over to his replica sites and steal information. This is one reason you never do online banking over hotspot wireless. Since we don’t want to do anything nefarious (I hope you don’t), you should always allow clients to use their own DNS servers and always set your DNS servers to trusted  public DNS servers.

Last but not least, make sure you run Chillispot or CoovaChilli with “–coaport <port number> –coanoipcheck” arguments. Some web management portals (like DD-WRT) have these options there for you to edit, others don’t, and if they don’t, then you’ll have to ssh into the router and put it directly into the startup file in the /etc/init.d directory. Not advisable to put it into the config file, since you don’t know exactly how the file gets re-written everytime you make changes from the web management portal. If it just replaces sections of the file that have been changed, thats fine, but if it rewrites everything (which is most likely the case), then you’ll lose those options everytime you make changes from the portal.

So what is this CoA thing? It stands for Change of Authorization and enables RFC 3567 which allows radius clients (like Chillispot or CoovaChilli) to accept disconnect packets through a predefined UDP port and only from the radius server it is configured with, unless its specifically told (via the –coanoipcheck option) to accept the packets from any server, to knock users off the internet. Handy when the expiration time has run out. The disconnect packed does a clean up of the accounting information for the particular disconnected user too. If this not done, essentially even after you’ve deleted the user from the database, they will still be able to connect to your system. Its all in the way radius sort of caches what it does for previous logins – long winded and really boring. If you’re interested, go Google it. There is no real default port for the CoA, but typically UDP port 3799 is used, so make sure you open that port on the radius client. For a typical OpenWRT router, find the /etc/firewall.users or rules file and add:

iptables -t nat -A prerouting_wan -p udp --dport 3799 -j ACCEPT
iptables -A input_wan -p udp --dport 3799 -j ACCEPT

You can add the above either through direct edtiting (OpenWRT/Tomato) or via the web interface (DD-WRT). Might be important to note that the latest versions of OpenWRT don’t have a web interface module to Chillispot or CoovaChilli. That means you either go with the old version (Kamikaze 7.09 – the last version with the web interface) or you manually edit config files to setup all the above information. A good guide (using CoovaChilli) can be found here

At this point, you’ve got everything set up for your hotspot to work, and you can manually create users into the radcheck table of the radius database in your MySQL server to get your authentication going.  You could write a simple script to do this, for example in perl:

#!/usr/bin/perl

# First argument  is the username, second argument is the password

use DBI;
use Crypt::Passwd;

my @chars = ("A".."Z", "a".."z", "0..9", "-", "_","\!","\@");
my $pwsalt;
$pwsalt .= $chars[rand @chars] for 1..24;

# Generate encrypted password with the username as the salt
$pwd = unix_std_crypt($ARGV[1], $pwsalt);

# Connect to MySQL database and add the user
$dbh = DBI->connect('dbi:mysql:radius','radius','myr4d1u5p455');
$sql = "insert into radcheck VALUES (NULL , '$ARGV[0]', 'Crypt-Password', ':=', '$pwd')";
$sth = $dbh->prepare($sql);
$sth->execute || die "Could not execute SQL statement on Password... maybe invalid?";

$dbh->disconnect;

Save that as some filename, say hsadduser, make it executable and run it giving it a username and password:

root@rpi:~# chmod +x hsadduser
root@rpi:~# ./hsadduser johndoe johndoepw

Log into MySQL and check the table:

mysql> select * from radcheck;
+----+----------+---------------------+----+---------------+
| id | username | attribute           | op | value         |
+----+----------+---------------------+----+---------------+
|  1 | johndoe  | Crypt-Password      | := | tepjTqyjw.F6. |
+----+----------+---------------------+----+---------------+
1 rows in set (0.00 sec)

Similarly for deleting entries:

#!/usr/bin/perl

# First argument  is the username

use DBI;
use Crypt::Passwd;

# Connect to MySQL database and delete the user
$dbh = DBI->connect('dbi:mysql:radius','radius','myr4d1u5p455');
$sql = "delete from radcheck where username='$ARGV[0]'";
$sth = $dbh->prepare($sql);
$sth->execute || die "Could not execute SQL statement on Password... maybe invalid?";

$dbh->disconnect;

Of course I have my users created dynamically from a sign up web page, but the scripts above are a basis from which dynamic user creation (and removal) can be done.

You could easily merge the above 2 scripts into a single script using functions and an argument call to determine if its a add user or delete user request like so:

#!/usr/bin/perl

use DBI;
use Crypt::Passwd;

$rdb = "radius";
$rrt = "radius";
$rpw = "myr4d1u5p455";

sub rad_adduser {
my @chars = ("A".."Z", "a".."z", "0..9", "-", "_","\!","\@");
my $string;
$mysalt .= $chars[rand @chars] for 1..24;

# Generate encrypted password with the username as the salt
$pwd = unix_std_crypt($ARGV[1], $mysalt);

# Connect to MySQL database and add the user
$dbh = DBI->connect("dbi:mysql:$rdb", $rrt, $rpw);
$sql = "insert into radcheck VALUES (NULL , '$ARGV[0]', 'Crypt-Password', ':=', '$pwd')";
$sth = $dbh->prepare($sql);
$sth->execute || die "Could not execute SQL statement on Password... maybe invalid?";

$dbh->disconnect;
}

sub rad_deluser {
# Connect to MySQL database and delete the user
$dbh = DBI->connect("dbi:mysql:$rdb", $rrt, $rpw);
$sql = "delete from radcheck where username='$ARGV[0]'";
$sth = $dbh->prepare($sql);
$sth->execute || die "Could not execute SQL statement on Password... maybe invalid?";

$dbh->disconnect;
}

if (($#ARGV == 2) && ("$ARGV[2]" eq "add")) {
        &rad_adduser;
        exit;
} elsif (($#ARGV == 1) && ("$ARGV[1]" eq "del")) {
        &rad_deluser;
        exit;
} else {
        print "Usage: <progname> <username> <password> [add|del]\n";
        exit;
}

If we save the combined script in /usr/local/bin as radtla (which stands for “radius time limited account”), this is used for adding a new radius user by doing:

root@rpi:~# /usr/local/bin/radtla raduser1 raduser12345 add

And to remove the radius user:

root@rpi:~# /usr/local/bin/radtla raduser1 del

Where raduser1 is the radius username you are trying to add/delete and raduser12345 is the password you are allocating to raduser1.

From here its child’s play to write a simple shell script that on authorization (from clicking a link from an email for example), will run the above combined script to add the user with the supplied credentials to the radius database, sleep for whatever time interval you want and then send the disconnect packet and run the above combined script again but this time to delete the user.

#!/bin/sh

# First argument is the userid and second argument is the password

TIMEINT=3600 # One hour time limit to expire the account
RADTLA="/usr/local/bin/radtla"

# Call radtla to add the account
$RADTLA "$1" "$2" "add"

# Sleep for the time limit
sleep $TIMEINT

# Send disconnect packet to knock user off the internet
echo "User-name=$1" | /usr/bin/radclient -x 192.168.1.20 disconnect thisismysecretphrase

# Call radtla to delete the account
$RADTLA "$1" "del"

If the shell script is called dotla.sh and also located in /usr/local/bin and you want to create a radius user johndoe@jmail.com and password jd12345, then call it with a nohup and push it to the background:

/usr/local/bin/dotla.sh 
root@rpi:~# nohup /usr/local/bin/dotla.sh "johndoe@jmail.com" "jd12345" &

The nohup keeps the process going even if you log out and the & pushes the process to the background so the system can still be used while its waiting for the process to complete.

Let me emphasize that there are a whole load of different ways to implement the addition of user accounts and account expiry. You could write the scripts in php, you could use cron (instead of sleep) to run your expiry script against a log that notes down when the account was created, etc – bottom line is, the scripts and method above are just one way of doing things. It may not be the most efficient or elegant way, but the purpose in this post is to detail, in as much scripting as possible, how it can be done and give the reader a the concept of how to write the functions. From this detailed mess (its a mess of code, I’ll concede that), one can clean things up and find a better way. If you want to be a penny pincher about it, you can even link this to PayPal and make folks pay for using your bandwidth – don’t know how legal that is though.

Also I’m not going to go through how to write a portal page to accept the username and password and integrate the different parts of the script into the portal because if you can’t do that yourself, you shouldn’t be even trying any of this.

So there you have it – your own hotspot with an old router and a Raspberry Pi. Hopefully I’ve not completely confused you and driven you insane, but in the off chance that I have, drop me a line and I’ll be more than happy to help clarify your queries and try to cure your insanity.

Or make it worse.

UPDATE:

Folks have been emailing (why can’t they just leave comments) asking if I have a php solution to all this without as many scripts – yes I do. Its a neat php solution for the portals and the expiry employing cron. Drop me a line if you are really too lazy or inept to write your own solution. Actually with all the spoon feeding above, I’d say just plain lazy.

UPDATE 2:

I forgot to mention, that if you’re going to create a hotspot service, you better make sure you have a set of terms and conditions that users agree to by using your service. You don’t want the authorities knocking on your door claiming you did all sorts of nefarious deeds from your internet connection and dragging you through all kinds of legal formalities when the real culprit was actually someone using your hotspot. Sure, the service is for people you know, but how well do you really know them? Lots of sample T&Cs out there for you to modify for your needs – remember, Google is your friend!

Comments (0)

Comments are closed.