Topic: [HOWTO] A captive portal using Kamikaze

[HOWTO] A captive portal using Kamikaze

This howto explains using a WRT54GL with OpenWRT as a captive portal for controlling user access to the Internet.

In addition to OpenWRT the Freeradius and Chillispot packages are used.

For a howto covering Whiterussian, please visit:

http://www.chillispot.info/chilliforum/viewtopic.php?id=18



Default configuration

The most important thing when creating a captive portal is to create a separate interface where users
can authenticate themselves. Luckily this is the default configuration in Kamikaze,
so no change is needed besides enabling the wireless interface.

(192.168.1.0/24)  (192.168.182.0/24) 
         |  |  |   \|/
         +-lan-+   wl0
             |      |
           <userspace>

- lan is used for bridging all the LAN ports
- wl0 is used for the WIFI interface


* Change the following line in /etc/config/wireless to enable the wireless interface:

        # REMOVE THIS LINE TO ENABLE WIFI:
#       option disabled 1



Installing the necessary packages

* First we need to update the opkg directory to get a list of all our packages:

opkg update


* Chillispot is used as our captive portal, so install it:

opkg install chillispot


* Freeradius receives RADIUS authentication requests from Chillispot, and we use plain password authentication:

opkg install freeradius-mod-pap


* Freeradius stores all users and attributes in a MySQL database:

opkg install freeradius-mod-sql-mysql


* To be able to keep track of the correct time we use NTP:

opkg install ntpclient


* Monit keeps track of process status, and can restart the processes if they stop

opkg install monit


Note that all packages have dependencies, and dependant packages will be installed as well



Chilli 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 wl0

uamserver https://www.mydomain.com/hotspotlogin.cgi
# or uncomment the following line if you will use the PHP script
#uamserver https://www.mydomain.com/hotspotlogin.php
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


* Enable the following lines in the /etc/config/firewall file:

# include a file with users custom iptables rules
config include
       option path /etc/firewall.user


* Create the /etc/firewall.user file with the following contents:

#!/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

WLANIF="wl0"
LANIF="eth0.0"
WANIF="eth0.1"

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 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 = "<replace with the name of your database server>"
    login = "<replace with your database user login>"
    password = "<replace with your database user password>"
    radius_db = "<replace with your database name>"

    # 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-1



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



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!