[HOWTO] Setting up OpenWRT as a captive portal on WRT54GL
This howto explains using a WRT54GL with OpenWRT as a captive portal for controlling user access to the Internet.
The configuration of a remote wireless router for repeating the signal is also shown.
In addition to OpenWRT the Freeradius and Chillispot packages are used.
At present this configuration only covers NVRAM settings on Whiterussian.
Do also note that the NVRAM settings can be applied on other routers than the WRT54GL,
but the interfaces for WIFI etc. may be different.
Default settings
The default bridging is set up as follows:
(192.168.1.0/24)
| | | | \|/
+--vlan0-+--+ eth1
| |
+----br0----+
|
<userspace>
- vlan0 is used for bridging all the LAN ports
- eth1 is used for the WIFI interface
- br0 is bridging the WIFI and LAN interface
* On a WRT54GL the NVRAM settings for this can be found as follows:
lan_ifname=br0
lan_ifnames=vlan0 eth1
Creating the captive portal interface
The most important thing when creating a captive portal is to create a separate interface where users
can authenticate themselves. This is done by creating a separate bridge that contains the interfaces that
we need. In the example we also want to include WDS to extend the range of our wireless network.
The 'br0' bridge is used for wireless, and we create a separate bridge 'br1' for the LAN interface:
(192.168.1.0/24) (192.168.182.0/24)
| | | | \|/ \|/
+--vlan0-+--+ eth1 wds
| | |
br1 +--br0--+
| |
| <Chillispot>
| |
| tun0
+-------+ +---------+
| |
<userspace>
- vlan0 is used for bridging all the LAN ports
- eth1 is used for the WIFI interface
- wds is used for extending the WIFI range using WDS
- br0 is the bridge that the captive portal (Chillispot) uses
- br1 is a new bridge for the LAN interface
* The LAN interface is then configured using the new bridge:
lan_ifname=br1
lan_ifnames=vlan0
* The existing bridge is then used for the wireless interface. For every WDS client that we want to
add, we add a new wds0.xxxxx interface, starting at wds0.49153:
wifi_ifname=br0
wifi_ifnames=eth1 wds0.49153 wds0.49154 wds0.49155
* In addition, the Spanning Tree Protocol is set to avoid circular bridges when using WDS:
wifi_stp=1
* We use manual WDS and disables Lazy WDS, and adds the MAC address for all wireless routers
that we want to communicate with:
wl0_lazywds=0
wl0_wds=xx:xx:xx:xx:xx:xx yy:yy:yy:yy:yy:yy zz:zz:zz:zz:zz:zz
Installing the necessary packages
* First we need to update the ipkg directory to get a list of all our packages:
ipkg update
* Chillispot is used as our captive portal, so install it:
ipkg install chillispot
* Freeradius receives RADIUS authentication requests from Chillispot, and we use plain password authentication:
ipkg install freeradius-mod-pap
* Freeradius stores all users and attributes in a MySQL database:
ipkg install freeradius-mod-sql-mysql
* To be able to keep track of the correct time we use NTP:
ipkg install openntpd
ipkg install ntpclient
* Monit keeps track of process status, and can restart the processes if they stop
ipkg install monit
Note that all packages have dependencies, and dependant packages will be installed as well
Chillispot config
* Change the following parameters in /etc/chilli.conf:
interval 3600
pidfile /var/run/chilli.pid
radiuslisten 127.0.0.1
radiusserver1 127.0.0.1
radiusserver2 127.0.0.1
radiussecret testing123
dhcpif br0
uamserver https://www.mydomain.com/hotspotlogin.cgi
uamsecret ht2eb8ej6s4et3rg1ulp
uamallowed www.mydomain.com,www.paypal.com,paypal.com,www.paypalobjects.com,paypalobjects.com,64.4.240.0/20,216.113.160.0/19
uamanydns
* Replace the /etc/init.d/S35firewall file with the following file:
#!/bin/sh
#
# Firewall script for ChilliSpot on OpenWRT
#
# Uses $WANIF (vlan1) as the external interface (Internet or intranet) and
# $WLANIF (eth1) as the internal interface (access point).
# $LANIF is used as a trusted management interface.
#
# SUMMARY
# * All connections originating from ChilliSpot are allowed.
# * Nothing is allowed in on WAN interface.
# * Nothing is allowed in on WLAN interface.
# * Everything is allowed in on LAN interface.
# * Forwarding is allowed to and from WAN interface, but disallowed
# to and from the WLAN interface.
# * NAT is enabled on the WAN interface.
. /etc/functions.sh
WANIF=$(nvram get wan_ifname)
WLANIF=$(nvram get wifi_ifname)
LANIF=$(nvram get lan_ifname)
IPTABLES="/usr/sbin/iptables"
for T in filter nat mangle ; do
$IPTABLES -t $T -F
$IPTABLES -t $T -X
done
$IPTABLES -P INPUT DROP
$IPTABLES -P FORWARD ACCEPT
$IPTABLES -P OUTPUT ACCEPT
#Allow related and established on all interfaces (input)
$IPTABLES -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
#Allow SSH, related and established $WANIF. Reject everything else.
$IPTABLES -A INPUT -i $WANIF -p tcp -m tcp --dport 22 -j ACCEPT
$IPTABLES -A INPUT -i $WANIF -j REJECT
#Allow satellite WDSs, related and established $WLANIF. Drop everything else.
$IPTABLES -A INPUT -i $WLANIF -j DROP
#Allow 3990 on other interfaces (input).
$IPTABLES -A INPUT -p tcp -m tcp --dport 3990 --syn -j ACCEPT
#Allow everything on loopback interface.
$IPTABLES -A INPUT -i lo -j ACCEPT
#Allow everything on $LANIF
$IPTABLES -A INPUT -i $LANIF -j ACCEPT
#Drop everything to and from $WLANIF (forward)
$IPTABLES -A FORWARD -i $WLANIF -j DROP
$IPTABLES -A FORWARD -o $WLANIF -j DROP
#Enable NAT on output device.
$IPTABLES -t nat -A POSTROUTING -o $WANIF -j MASQUERADE
* Place the following login script 'hotspotlogin.cgi' on your SSL and Perl-enabled web server with domain name www.mydomain.com:
#!/usr/bin/perl
# chilli - ChilliSpot.org. A Wireless LAN Access Point Controller
# Copyright (C) 2003, 2004 Mondru AB.
#
# The contents of this file may be used under the terms of the GNU
# General Public License Version 2, provided that the above copyright
# notice and this permission notice is included in all copies or
# substantial portions of the software.
# Redirects from ChilliSpot daemon:
#
# Redirection when not yet or already authenticated
# notyet: ChilliSpot daemon redirects to login page.
# already: ChilliSpot daemon redirects to success status page.
#
# Response to login:
# already: Attempt to login when already logged in.
# failed: Login failed
# success: Login succeded
#
# logoff: Response to a logout
# Shared secret used to encrypt challenge with. Prevents dictionary attacks.
# You should change this to your own shared secret.
#$uamsecret = "ht2eb8ej6s4et3rg1ulp";
# Uncomment the following line if you want to use ordinary user-password
# for radius authentication. Must be used together with $uamsecret.
#$userpassword=1;
# Our own path
$loginpath = $ENV{'SCRIPT_URL'};
use Digest::MD5 qw(md5 md5_hex md5_base64);
# Make sure that the form parameters are clean
$OK_CHARS='-a-zA-Z0-9_.@&=%!';
$| = 1;
if ($ENV{'CONTENT_LENGTH'}) {
read (STDIN, $_, $ENV{'CONTENT_LENGTH'});
}
s/[^$OK_CHARS]/_/go;
$input = $_;
# Make sure that the get query parameters are clean
$OK_CHARS='-a-zA-Z0-9_.@&=%!';
$_ = $query=$ENV{QUERY_STRING};
s/[^$OK_CHARS]/_/go;
$query = $_;
# If she did not use https tell her that it was wrong.
if (!($ENV{HTTPS} =~ /^on$/)) {
print "Content-type: text/html\n\n
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<title>ChilliSpot Login Failed</title>
<meta http-equiv=\"Cache-control\" content=\"no-cache\">
<meta http-equiv=\"Pragma\" content=\"no-cache\">
</head>
<body bgColor = '#c0d8f4'>
<h1 style=\"text-align: center;\">ChilliSpot Login Failed</h1>
<center>
Login must use encrypted connection.
</center>
</body>
<!--
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<WISPAccessGatewayParam
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
xsi:noNamespaceSchemaLocation=\"http://www.acmewisp.com/WISPAccessGatewayParam.xsd\">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>102</ResponseCode>
<ReplyMessage>Login must use encrypted connection</ReplyMessage>
</AuthenticationReply>
</WISPAccessGatewayParam>
-->
</html>
";
exit(0);
}
#Read form parameters which we care about
@array = split('&',$input);
foreach $var ( @array )
{
@array2 = split('=',$var);
if ($array2[0] =~ /^UserName$/) { $username = $array2[1]; }
if ($array2[0] =~ /^Password$/) { $password = $array2[1]; }
if ($array2[0] =~ /^challenge$/) { $challenge = $array2[1]; }
if ($array2[0] =~ /^button$/) { $button = $array2[1]; }
if ($array2[0] =~ /^logout$/) { $logout = $array2[1]; }
if ($array2[0] =~ /^prelogin$/) { $prelogin = $array2[1]; }
if ($array2[0] =~ /^res$/) { $res = $array2[1]; }
if ($array2[0] =~ /^uamip$/) { $uamip = $array2[1]; }
if ($array2[0] =~ /^uamport$/) { $uamport = $array2[1]; }
if ($array2[0] =~ /^userurl$/) { $userurl = $array2[1]; }
if ($array2[0] =~ /^timeleft$/) { $timeleft = $array2[1]; }
if ($array2[0] =~ /^redirurl$/) { $redirurl = $array2[1]; }
}
#Read query parameters which we care about
@array = split('&',$query);
foreach $var ( @array )
{
@array2 = split('=',$var);
if ($array2[0] =~ /^res$/) { $res = $array2[1]; }
if ($array2[0] =~ /^challenge$/) { $challenge = $array2[1]; }
if ($array2[0] =~ /^uamip$/) { $uamip = $array2[1]; }
if ($array2[0] =~ /^uamport$/) { $uamport = $array2[1]; }
if ($array2[0] =~ /^reply$/) { $reply = $array2[1]; }
if ($array2[0] =~ /^userurl$/) { $userurl = $array2[1]; }
if ($array2[0] =~ /^timeleft$/) { $timeleft = $array2[1]; }
if ($array2[0] =~ /^redirurl$/) { $redirurl = $array2[1]; }
}
$reply =~ s/\+/ /g;
$reply =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;
$userurldecode = $userurl;
$userurldecode =~ s/\+/ /g;
$userurldecode =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;
$redirurldecode = $redirurl;
$redirurldecode =~ s/\+/ /g;
$redirurldecode =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;
$password =~ s/\+/ /g;
$password =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;
# If attempt to login
if ($button =~ /^Login$/) {
$hexchal = pack "H32", $challenge;
if (defined $uamsecret) {
$newchal = md5($hexchal, $uamsecret);
}
else {
$newchal = $hexchal;
}
$response = md5_hex("\0", $password, $newchal);
$pappassword = unpack "H32", ($password ^ $newchal);
#sleep 5;
print "Content-type: text/html\n\n";
print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<title>ChilliSpot Login</title>
<meta http-equiv=\"Cache-control\" content=\"no-cache\">
<meta http-equiv=\"Pragma\" content=\"no-cache\">";
if ((defined $uamsecret) && defined($userpassword)) {
print " <meta http-equiv=\"refresh\" content=\"0;url=http://$uamip:$uamport/logon?username=$username&password=$pappassword&userurl=$userurl\">";
} else {
print " <meta http-equiv=\"refresh\" content=\"0;url=http://$uamip:$uamport/logon?username=$username&response=$response&userurl=$userurl\">";
}
print "</head>
<body bgColor = '#c0d8f4'>";
print "<h1 style=\"text-align: center;\">Logging in to ChilliSpot</h1>";
print "
<center>
Please wait......
</center>
</body>
<!--
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<WISPAccessGatewayParam
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
xsi:noNamespaceSchemaLocation=\"http://www.acmewisp.com/WISPAccessGatewayParam.xsd\">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>201</ResponseCode>
";
if ((defined $uamsecret) && defined($userpassword)) {
print "<LoginResultsURL>http://$uamip:$uamport/logon?username=$username&password=$pappassword</LoginResultsURL>";
} else {
print "<LoginResultsURL>http://$uamip:$uamport/logon?username=$username&response=$response&userurl=$userurl</LoginResultsURL>";
}
print "</AuthenticationReply>
</WISPAccessGatewayParam>
-->
</html>
";
exit(0);
}
# Default: It was not a form request
$result = 0;
# If login successful
if ($res =~ /^success$/) {
$result = 1;
}
# If login failed
if ($res =~ /^failed$/) {
$result = 2;
}
# If logout successful
if ($res =~ /^logoff$/) {
$result = 3;
}
# If tried to login while already logged in
if ($res =~ /^already$/) {
$result = 4;
}
# If not logged in yet
if ($res =~ /^notyet$/) {
$result = 5;
}
# If login from smart client
if ($res =~ /^smartclient$/) {
$result = 6;
}
# If requested a logging in pop up window
if ($res =~ /^popup1$/) {
$result = 11;
}
# If requested a success pop up window
if ($res =~ /^popup2$/) {
$result = 12;
}
# If requested a logout pop up window
if ($res =~ /^popup3$/) {
$result = 13;
}
# Otherwise it was not a form request
# Send out an error message
if ($result == 0) {
print "Content-type: text/html\n\n
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<title>ChilliSpot Login Failed</title>
<meta http-equiv=\"Cache-control\" content=\"no-cache\">
<meta http-equiv=\"Pragma\" content=\"no-cache\">
</head>
<body bgColor = '#c0d8f4'>
<h1 style=\"text-align: center;\">ChilliSpot Login Failed</h1>
<center>
Login must be performed through ChilliSpot daemon.
</center>
</body>
</html>
";
exit(0);
}
#Generate the output
print "Content-type: text/html\n\n
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<title>ChilliSpot Login</title>
<meta http-equiv=\"Cache-control\" content=\"no-cache\">
<meta http-equiv=\"Pragma\" content=\"no-cache\">
<SCRIPT LANGUAGE=\"JavaScript\">
var blur = 0;
var starttime = new Date();
var startclock = starttime.getTime();
var mytimeleft = 0;
function doTime() {
window.setTimeout( \"doTime()\", 1000 );
t = new Date();
time = Math.round((t.getTime() - starttime.getTime())/1000);
if (mytimeleft) {
time = mytimeleft - time;
if (time <= 0) {
window.location = \"$loginpath?res=popup3&uamip=$uamip&uamport=$uamport\";
}
}
if (time < 0) time = 0;
hours = (time - (time % 3600)) / 3600;
time = time - (hours * 3600);
mins = (time - (time % 60)) / 60;
secs = time - (mins * 60);
if (hours < 10) hours = \"0\" + hours;
if (mins < 10) mins = \"0\" + mins;
if (secs < 10) secs = \"0\" + secs;
title = \"Online time: \" + hours + \":\" + mins + \":\" + secs;
if (mytimeleft) {
title = \"Remaining time: \" + hours + \":\" + mins + \":\" + secs;
}
if(document.all || document.getElementById){
document.title = title;
}
else {
self.status = title;
}
}
function popUp(URL) {
if (self.name != \"chillispot_popup\") {
chillispot_popup = window.open(URL, 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=375');
}
}
function doOnLoad(result, URL, userurl, redirurl, timeleft) {
if (timeleft) {
mytimeleft = timeleft;
}
if ((result == 1) && (self.name == \"chillispot_popup\")) {
doTime();
}
if ((result == 1) && (self.name != \"chillispot_popup\")) {
chillispot_popup = window.open(URL, 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=375');
}
if ((result == 2) || result == 5) {
document.form1.UserName.focus()
}
if ((result == 2) && (self.name != \"chillispot_popup\")) {
chillispot_popup = window.open('', 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=400,height=200');
chillispot_popup.close();
}
if ((result == 12) && (self.name == \"chillispot_popup\")) {
doTime();
if (redirurl) {
opener.location = redirurl;
}
else if (userurl) {
opener.location = userurl;
}
else if (opener.home) {
opener.home();
}
else {
opener.location = \"about:home\";
}
self.focus();
blur = 0;
}
if ((result == 13) && (self.name == \"chillispot_popup\")) {
self.focus();
blur = 1;
}
}
function doOnBlur(result) {
if ((result == 12) && (self.name == \"chillispot_popup\")) {
if (blur == 0) {
blur = 1;
self.focus();
}
}
}
</script>
</head>
<body onLoad=\"javascript:doOnLoad($result, '$loginpath?res=popup2&uamip=$uamip&uamport=$uamport&userurl=$userurl&redirurl=$redirurl&timeleft=$timeleft','$userurldecode', '$redirurldecode', '$timeleft')\" onBlur = \"javascript:doOnBlur($result)\" bgColor = '#c0d8f4'>";
# if (!window.opener) {
# document.bgColor = '#c0d8f4';
# }
#print "THE INPUT: $input";
#foreach $key (sort (keys %ENV)) {
# print $key, ' = ', $ENV{$key}, "<br>\n";
#}
if ($result == 2) {
print "
<h1 style=\"text-align: center;\">ChilliSpot Login Failed</h1>";
if ($reply) {
print "<center> $reply </BR></BR></center>";
}
}
if ($result == 5) {
print "
<h1 style=\"text-align: center;\">ChilliSpot Login</h1>";
}
if ($result == 2 || $result == 5) {
print "
<form name=\"form1\" method=\"post\" action=\"$loginpath\">
<INPUT TYPE=\"hidden\" NAME=\"challenge\" VALUE=\"$challenge\">
<INPUT TYPE=\"hidden\" NAME=\"uamip\" VALUE=\"$uamip\">
<INPUT TYPE=\"hidden\" NAME=\"uamport\" VALUE=\"$uamport\">
<INPUT TYPE=\"hidden\" NAME=\"userurl\" VALUE=\"$userurldecode\">
<center>
<table border=\"0\" cellpadding=\"5\" cellspacing=\"0\" style=\"width: 217px;\">
<tbody>
<tr>
<td align=\"right\">Username:</td>
<td><input STYLE=\"font-family: Arial\" type=\"text\" name=\"UserName\" size=\"20\" maxlength=\"128\"></td>
</tr>
<tr>
<td align=\"right\">Password:</td>
<td><input STYLE=\"font-family: Arial\" type=\"password\" name=\"Password\" size=\"20\" maxlength=\"128\"></td>
</tr>
<tr>
<td align=\"center\" colspan=\"2\" height=\"23\"><input type=\"submit\" name=\"button\" value=\"Login\" onClick=\"javascript:popUp('$loginpath?res=popup1&uamip=$uamip&uamport=$uamport')\"></td>
</tr>
</tbody>
</table>
</center>
</form>
</body>
</html>";
}
if ($result == 1) {
print "
<h1 style=\"text-align: center;\">Logged in to ChilliSpot</h1>";
if ($reply) {
print "<center> $reply </BR></BR></center>";
}
print "
<center>
<a href=\"http://$uamip:$uamport/logoff\">Logout</a>
</center>
</body>
</html>";
}
if (($result == 4) || ($result == 12)) {
print "
<h1 style=\"text-align: center;\">Logged in to ChilliSpot</h1>
<center>
<a href=\"http://$uamip:$uamport/logoff\">Logout</a>
</center>
</body>
</html>";
}
if ($result == 11) {
print "<h1 style=\"text-align: center;\">Logging in to ChilliSpot</h1>";
print "
<center>
Please wait......
</center>
</body>
</html>";
}
if (($result == 3) || ($result == 13)) {
print "
<h1 style=\"text-align: center;\">Logged out from ChilliSpot</h1>
<center>
<a href=\"http://$uamip:$uamport/prelogin\">Login</a>
</center>
</body>
</html>";
}
exit(0);
* - or place the following login script 'hotspotlogin.php' on your SSL and PHP-enabled web server with domain name www.mydomain.com:
<?php
#
# chilli - ChilliSpot.org. A Wireless LAN Access Point Controller
# Copyright (C) 2003, 2004 Mondru AB.
#
# The contents of this file may be used under the terms of the GNU
# General Public License Version 2, provided that the above copyright
# notice and this permission notice is included in all copies or
# substantial portions of the software.
# Redirects from ChilliSpot daemon:
#
# Redirection when not yet or already authenticated
# notyet: ChilliSpot daemon redirects to login page.
# already: ChilliSpot daemon redirects to success status page.
#
# Response to login:
# already: Attempt to login when already logged in.
# failed: Login failed
# success: Login succeded
#
# logoff: Response to a logout
# Shared secret used to encrypt challenge with. Prevents dictionary attacks.
# You should change this to your own shared secret.
$uamsecret = "ht2eb8ej6s4et3rg1ulp";
# Uncomment the following line if you want to use ordinary user-password
# for radius authentication. Must be used together with $uamsecret.
$userpassword=1;
# Our own path
$loginpath = $_SERVER['PHP_SELF'];
$ChilliSpot="ChilliSpot";
$title="$ChilliSpot Login";
$centerUsername="Username";
$centerPassword="Password";
$centerLogin="Login";
$centerPleasewait="Please wait.......";
$centerLogout="Logout";
$h1Login="$ChilliSpot Login";
$h1Failed="$ChilliSpot Login Failed";
$h1Loggedin="Logged in to $ChilliSpot";
$h1Loggingin="Logging in to $ChilliSpot";
$h1Loggedout="Logged out from $ChilliSpot";
$centerdaemon="Login must be performed through $ChilliSpot daemon";
$centerencrypted="Login must use encrypted connection";
# Make sure that the form parameters are clean
#$OK_CHARS='-a-zA-Z0-9_.@&=%!';
#$_ = $input = <STDIN>;
#s/[^$OK_CHARS]/_/go;
#$input = $_;
# Make sure that the get query parameters are clean
#$OK_CHARS='-a-zA-Z0-9_.@&=%!';
#$_ = $query=$ENV{QUERY_STRING};
#s/[^$OK_CHARS]/_/go;
#$query = $_;
# If she did not use https tell her that it was wrong.
if (!($_ENV['HTTPS'] == 'on')) {
# echo "Content-type: text/html\n\n";
echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<title>$title</title>
<meta http-equiv=\"Cache-control\" content=\"no-cache\">
<meta http-equiv=\"Pragma\" content=\"no-cache\">
</head>
<body bgColor = '#c0d8f4'>
<h1 style=\"text-align: center;\">$h1Failed</h1>
<center>
$centerencrypted
</center>
</body>
<!--
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<WISPAccessGatewayParam
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
xsi:noNamespaceSchemaLocation=\"http://www.acmewisp.com/WISPAccessGatewayParam.xsd\">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>102</ResponseCode>
<ReplyMessage>Login must use encrypted connection</ReplyMessage>
</AuthenticationReply>
</WISPAccessGatewayParam>
-->
</html>
";
exit(0);
}
# Read form parameters which we care about
if (isset($_POST['UserName'])) $username = $_POST['UserName'];
if (isset($_POST['Password'])) $password = $_POST['Password'];
if (isset($_POST['challenge'])) $challenge = $_POST['challenge'];
if (isset($_POST['button'])) $button = $_POST['button'];
if (isset($_POST['logout'])) $logout = $_POST['logout'];
if (isset($_POST['prelogin'])) $prelogin = $_POST['prelogin'];
if (isset($_POST['res'])) $res = $_POST['res'];
if (isset($_POST['uamip'])) $uamip = $_POST['uamip'];
if (isset($_POST['uamport'])) $uamport = $_POST['uamport'];
if (isset($_POST['userurl'])) $userurl = $_POST['userurl'];
if (isset($_POST['timeleft'])) $timeleft = $_POST['timeleft'];
if (isset($_POST['redirurl'])) $redirurl = $_POST['redirurl'];
# Read query parameters which we care about
if (isset($_GET['res'])) $res = $_GET['res'];
if (isset($_GET['challenge'])) $challenge = $_GET['challenge'];
if (isset($_GET['uamip'])) $uamip = $_GET['uamip'];
if (isset($_GET['uamport'])) $uamport = $_GET['uamport'];
if (isset($_GET['reply'])) $reply = $_GET['reply'];
if (isset($_GET['userurl'])) $userurl = $_GET['userurl'];
if (isset($_GET['timeleft'])) $timeleft = $_GET['timeleft'];
if (isset($_GET['redirurl'])) $redirurl = $_GET['redirurl'];
#$reply =~ s/\+/ /g;
#$reply =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;
$userurldecode = $userurl;
#$userurldecode =~ s/\+/ /g;
#$userurldecode =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;
$redirurldecode = $redirurl;
#$redirurldecode =~ s/\+/ /g;
#$redirurldecode =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;
#$password =~ s/\+/ /g;
#$password =~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/seg;
# If attempt to login
if ($button == 'Login') {
$hexchal = pack ("H32", $challenge);
if ($uamsecret) {
$newchal = pack ("H*", md5($hexchal . $uamsecret));
} else {
$newchal = $hexchal;
}
$response = md5("\0" . $password . $newchal);
$newpwd = pack("a32", $password);
$pappassword = implode ("", unpack("H32", ($newpwd ^ $newchal)));
# sleep 5;
# echo 'Content-type: text/html\n\n';
echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<title>$title</title>
<meta http-equiv=\"Cache-control\" content=\"no-cache\">
<meta http-equiv=\"Pragma\" content=\"no-cache\">";
if (isset($uamsecret) && isset($userpassword)) {
echo " <meta http-equiv=\"refresh\" content=\"0;url=http://$uamip:$uamport/logon?username=$username&password=$pappassword\">";
} else {
echo " <meta http-equiv=\"refresh\" content=\"0;url=http://$uamip:$uamport/logon?username=$username&response=$response&userurl=$userurl\">";
}
echo "</head>
<body bgColor = '#c0d8f4'>
<h1 style=\"text-align: center;\">$h1Loggingin</h1>
<center>
$centerPleasewait
</center>
</body>
<!--
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<WISPAccessGatewayParam
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
xsi:noNamespaceSchemaLocation=\"http://www.acmewisp.com/WISPAccessGatewayParam.xsd\">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>201</ResponseCode>
";
if (isset($uamsecret) && isset($userpassword)) {
echo "<LoginResultsURL>http://$uamip:$uamport/logon?username=$username&password=$pappassword</LoginResultsURL>";
} else {
echo "<LoginResultsURL>http://$uamip:$uamport/logon?username=$username&response=$response&userurl=$userurl</LoginResultsURL>";
}
echo "</AuthenticationReply>
</WISPAccessGatewayParam>
-->
</html>
";
exit(0);
}
switch($res) {
case 'success': $result = 1; break; // If login successful
case 'failed': $result = 2; break; // If login failed
case 'logoff': $result = 3; break; // If logout successful
case 'already': $result = 4; break; // If tried to login while already logged in
case 'notyet': $result = 5; break; // If not logged in yet
case 'smartclient': $result = 6; break; // If login from smart client
case 'popup1': $result = 11; break; // If requested a logging in pop up window
case 'popup2': $result = 12; break; // If requested a success pop up window
case 'popup3': $result = 13; break; // If requested a logout pop up window
default: $result = 0; // Default: It was not a form request
}
# Otherwise it was not a form request
# Send out an error message
if ($result == 0) {
# echo "Content-type: text/html\n\n";
echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<title>$title</title>
<meta http-equiv=\"Cache-control\" content=\"no-cache\">
<meta http-equiv=\"Pragma\" content=\"no-cache\">
</head>
<body bgColor = '#c0d8f4'>
<h1 style=\"text-align: center;\">$h1Failed</h1>
<center>
$centerdaemon
</center>
</body>
</html>
";
exit(0);
}
# Generate the output
#echo "Content-type: text/html\n\n";
echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head>
<title>$title</title>
<meta http-equiv=\"Cache-control\" content=\"no-cache\">
<meta http-equiv=\"Pragma\" content=\"no-cache\">
<SCRIPT LANGUAGE=\"JavaScript\">
var blur = 0;
var starttime = new Date();
var startclock = starttime.getTime();
var mytimeleft = 0;
function doTime() {
window.setTimeout( \"doTime()\", 1000 );
t = new Date();
time = Math.round((t.getTime() - starttime.getTime())/1000);
if (mytimeleft) {
time = mytimeleft - time;
if (time <= 0) {
window.location = \"$loginpath?res=popup3&uamip=$uamip&uamport=$uamport\";
}
}
if (time < 0) time = 0;
hours = (time - (time % 3600)) / 3600;
time = time - (hours * 3600);
mins = (time - (time % 60)) / 60;
secs = time - (mins * 60);
if (hours < 10) hours = \"0\" + hours;
if (mins < 10) mins = \"0\" + mins;
if (secs < 10) secs = \"0\" + secs;
title = \"Online time: \" + hours + \":\" + mins + \":\" + secs;
if (mytimeleft) {
title = \"Remaining time: \" + hours + \":\" + mins + \":\" + secs;
}
if(document.all || document.getElementById){
document.title = title;
}
else {
self.status = title;
}
}
function popUp(URL) {
if (self.name != \"chillispot_popup\") {
chillispot_popup = window.open(URL, 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=375');
}
}
function doOnLoad(result, URL, userurl, redirurl, timeleft) {
if (timeleft) {
mytimeleft = timeleft;
}
if ((result == 1) && (self.name == \"chillispot_popup\")) {
doTime();
}
if ((result == 1) && (self.name != \"chillispot_popup\")) {
chillispot_popup = window.open(URL, 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=375');
}
if ((result == 2) || result == 5) {
document.form1.UserName.focus()
}
if ((result == 2) && (self.name != \"chillispot_popup\")) {
chillispot_popup = window.open('', 'chillispot_popup', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=400,height=200');
chillispot_popup.close();
}
if ((result == 12) && (self.name == \"chillispot_popup\")) {
doTime();
if (redirurl) {
opener.location = redirurl;
}
else if (opener.home) {
opener.home();
}
else {
opener.location = \"about:home\";
}
self.focus();
blur = 0;
}
if ((result == 13) && (self.name == \"chillispot_popup\")) {
self.focus();
blur = 1;
}
}
function doOnBlur(result) {
if ((result == 12) && (self.name == \"chillispot_popup\")) {
if (blur == 0) {
blur = 1;
self.focus();
}
}
}
</script>
</head>
<body onLoad=\"javascript:doOnLoad($result, '$loginpath?res=popup2&uamip=$uamip&uamport=$uamport&userurl=$userurl&redirurl=$redirurl&timeleft=$timeleft','$userurldecode', '$redirurldecode', '$timeleft')\" onBlur = 'javascript:doOnBlur($result)' bgColor = '#c0d8f4'>";
/*# begin debugging
print "<center>THE INPUT (for debugging):<br>";
foreach ($_GET as $key => $value) {
print $key . "=" . $value . "<br>";
}
print "<br></center>";
# end debugging
*/
if ($result == 2) {
echo "
<h1 style=\"text-align: center;\">$h1Failed</h1>";
if ($reply) {
echo "<center> $reply </BR></BR></center>";
}
}
if ($result == 5) {
echo "
<h1 style=\"text-align: center;\">$h1Login</h1>";
}
if ($result == 2 || $result == 5) {
echo "
<form name=\"form1\" method=\"post\" action=\"$loginpath\">
<input type=\"hidden\" name=\"challenge\" value=\"$challenge\">
<input type=\"hidden\" name=\"uamip\" value=\"$uamip\">
<input type=\"hidden\" name=\"uamport\" value=\"$uamport\">
<input type=\"hidden\" name=\"userurl\" value=\"$userurl\">
<center>
<table border=\"0\" cellpadding=\"5\" cellspacing=\"0\" style=\"width: 217px;\">
<tbody>
<tr>
<td align=\"right\">$centerUsername:</td>
<td><input style=\"font-family: Arial\" type=\"text\" name=\"UserName\" size=\"20\" maxlength=\"128\"></td>
</tr>
<tr>
<td align=\"right\">$centerPassword:</td>
<td><input style=\"font-family: Arial\" type=\"password\" name=\"Password\" size=\"20\" maxlength=\"128\"></td>
</tr>
<tr>
<td align=\"center\" colspan=\"2\" height=\"23\"><input type=\"submit\" name=\"button\" value=\"Login\" onClick=\"javascript:popUp('$loginpath?res=popup1&uamip=$uamip&uamport=$uamport')\"></td>
</tr>
</tbody>
</table>
</center>
</form>
</body>
</html>";
}
if ($result == 1) {
echo "
<h1 style=\"text-align: center;\">$h1Loggedin</h1>";
if ($reply) {
echo "<center> $reply </br></br></center>";
}
echo "
<center>
<a href=\"http://$uamip:$uamport/logoff\">Logout</a>
</center>
</body>
</html>";
}
if (($result == 4) || ($result == 12)) {
echo "
<h1 style=\"text-align: center;\">$h1Loggedin</h1>
<center>
<a href=\"http://$uamip:$uamport/logoff\">$centerLogout</a>
</center>
</body>
</html>";
}
if ($result == 11) {
echo "
<h1 style=\"text-align: center;\">$h1Loggingin</h1>
<center>
$centerPleasewait
</center>
</body>
</html>";
}
if (($result == 3) || ($result == 13)) {
echo "
<h1 style=\"text-align: center;\">$h1Loggedout</h1>
<center>
<a href=\"http://$uamip:$uamport/prelogin\">$centerLogin</a>
</center>
</body>
</html>";
}
exit(0);
?>
Freeradius config
* Change the following attributes in /etc/freeradius/radiusd.conf to use Freeradius with a MySQL backend:
authorize {
sql
}
authenticate {
Auth-Type PAP {
pap
}
}
accounting {
sql
}
session {
sql
}
* Change the following attributes in /etc/freeradius/sql.conf
sql {
driver = "rlm_sql_mysql"
server = "mysql.mydomain.com"
login = "my_login"
password = "my_password"
radius_db = "my_db"
# Uncomment simul_count_query to enable simultaneous use checking
simul_count_query = "SELECT COUNT(*) FROM ${acct_table1} WHERE UserName='%{SQL-User-Name}' AND AcctStopTime = 0"
readclients = yes
}
* Install the Freeradius tables in the MySQL database on the domain mysql.mydomain.com, and configure a test user:
raduserinfo
UserName = 'testuser'
radcheck for 'testuser'
User-Password := 'password'
Auth-Type := 'PAP'
Simultaneous-Use := 1
radreply for 'testuser'
Idle-Timeout := 1800
NTP config
* Change the attributes in /etc/TZ to match your time zone, for instance:
CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00
Monit config
* Change the following attributes in /etc/monitrc:
set mailserver smtp.mymailserver.com
set mail-format { from: monit@mydomain.com }
set alert monit@mydomain.com
set httpd port 2812 and
allow admin:monit # user 'admin' with password 'monit'
check process chilli with pidfile /var/run/chilli.pid
start program = "/etc/init.d/chilli start"
stop program = "/etc/init.d/chilli stop"
check process radiusd with pidfile /var/run/radiusd.pid
start program = "/etc/init.d/radiusd start"
stop program = "/etc/init.d/radiusd stop"
check host www.mydomain.com with address www.mydomain.com
if failed port 80 protocol http
and request "/hotspotlogin.cgi" then alert
check host mysql.mydomain.com with address mysql.mydomain.com
if failed port 3306 protocol mysql then alert
Configuring the remote routers
Since we only want to use the remote routers to repeat our signal, we bridge the WDS, WIFI and LAN:
(192.168.1.0/24)
| | | | \|/ \|/
+--vlan0-+--+ eth1 wds
| | |
+----------br0-----+-------+
|
<userspace>
- vlan0 is used for bridging all the LAN ports
- eth1 is used for the WIFI interface
- wds is used for extending the WIFI range using WDS
- br0 is the bridge that connects all the interfaces
* The existing bridge is then used for the wireless interface.
For every WDS client that we want to add, we add a new wds0.xxxxx interface, starting at wds0.49153:
lan_ifname=br0
lan_ifnames=vlan0 eth1 wds0.49153 wds0.49154 wds0.49155
* In addition, the Spanning Tree Protocol is set to avoid circular bridges when using WDS:
lan_stp=1
* We use manual WDS and disables Lazy WDS, and adds the MAC address for all wireless routers
that we want to communicate with, including the MAC address of the captive portal:
wl0_lazywds=0
wl0_wds=aa:aa:aa:aa:aa:aa bb:bb:bb:bb:bb:bb cc:cc:cc:cc:cc:cc
* Last, disable all services you do not need, such as DHCP. This is an important step because we do not want the remote routers to hand out IP addresses that confuses Chillispot.
Testing the setup
* Start your WEB browser, and write 'testuser'/'password' in the WEB page that appears. If you have configured everything correctly, you should be authenticated.
* If anything fails, connect your PC to one of the LAN ports on the router and start two SSH sessions, one with Chillispot and the other with Freeradius, both in foreground debug mode:
chilli -fd
radiusd -X
Establish a wireless connection to your WLAN and watch the outputs carefully to debug your setup.
Have fun!