INTRODUCTION
------------
When I set up an open wireless access point at my house I was a little
worried.  Not because I thought I'd get hacked -- the wireless subnet
is totally separated from my internal network by a freebsd firewall.
And I wasn't worried about my ISP getting pissed -- they are actually
okay with it (check out Speakeasy if you're looking for a clueful ISP
who caters to geeks).  I was worried about two things:

    1. People sucking up all my bandwidth.
    2. People sending spam, raising hell, etc from my IP.

This document outlines my approach to handling both problems.  It is
also a technical guide to setting up a "captive portal" on
FreeBSD-4.x.


CAPTIVE PORTAL:
---------------
If you've ever been at a hotel where they charge for Internet access
you know what a captive portal is.  You plug your laptop into the
network but you can't do anything until you open your browser and type
in your credit card number.  I wanted the same thing (minus the credit
card number, plus an acceptable use notice) on my open access point.
People can use if for free but they have to click "I ACCEPT" on a
thing that says they won't spam, won't trade warez, etc.

To accomplish this I used FreeBSD's IPFIREWALL.  I already had a
firewall on the machine that protected my inside network and shared
the Internet connection via NAT.  I'll describe how I added a captive
portal to this setup but if you need a primer on how to get your basic
FreeBSD firewall setup, check out http://www.freebsddiary.org/ipfw.php.

The executive summary of how to do this is:

    1. install a rule that diverts all wireless http traffic to your
       own webserver.
    2. install a rule that blocks all other wireless traffic.
    3. install a CGI script on your webserver that shows your
       acceptable use policy.
    4. When someone SUBMITs on the form served by this CGI script,
       add a rule to the firewall for their IP address that bypasses
       the rules in steps 1. and 2. 

THE WEBSERVER
-------------
You divert http traffic from (unregistered) wireless clients to your
own webserver by means of a firewall rule.  You can find a working
example rc.firewall in this tarball but here's a sample forwarding
rule:

add 10000 fwd ${wip},8080 tcp from $wireless to any 80 in via ${wif}

That means when a packet comes in from the wireless subnet via the
wireless interface and is destined for whatever.com port 80 it will be
forwarded to my wireless IP address port 8080.  This section of the
ipfw manpage about forwarding is vital:

     "The fwd action does not change the contents of the packet at
     all.  In particular, the destination address remains unmodified,
     so packets forwarded to another system will usually be rejected
     by that system unless there is a matching rule on that system to
     capture them.  For packets forwarded locally, the local address
     of the socket will be set to the original destination address of
     the packet.  This makes the netstat(1) entry look rather weird
     but is intended for use with transparent proxy servers.

In other words, the webserver at port 8080 on my system is gunna be
getting packets that look like they are to whatever.com.  Moreover
they are going to get the original path of the request.  Clearly this
webserver has to be able to handle crazy URLs.

I'm not an apache guru so maybe what I'm about to tell you is wrong.
That said, I could not get my stock http server to do this.  Instead I
made a new webserver just to handle the crazy wireless URLs and serve
the CGI script that authenticates wireless clients.  You can find the
full httpd.conf file included in this tarball but here's the important
part that allows it to do this:

AliasMatch .* /usr/local/www/data/wireless/notice.cgi

The apache manual on mod_alias describes AliasMatch as follows:

    "This directive is equivalent to Alias, but makes use of standard
    regular expressions, instead of simple prefix matching. The
    supplied regular expression is matched against the URL-path, and
    if it matches, the server will substitute any parenthesized
    matches into the given string and use it as a filename. For
    example, to activate the /icons directory, one might use:

    AliasMatch ^/icons(.*) /usr/local/apache/icons$1"

My regular expression, .*, matches anything and directs it to
notice.cgi, a little perl script.  Since scripts are enabled by
extension on this server the script runs and instead of whatever.com
the wireless user gets my portal page.

THE CGI SCRIPT
--------------
The CGI script has two jobs: to display a page full of acceptible use
and to handle when people accept it.

The first job is simple, it just throws some HTML out.  People
probably were surfing to whatever.com so they don't expect this page.
It's friendly, tells them what's up, and gives them a chance to
accept.  The same CGI script has to handle the acceptance because
their IP is not unblocked yet -- whatever URL they try to get to they
will come right back to this script.  That includes other URLs that
the script tries to give them.

When they accept the script says thanks and adds their IP address to
the list that are to be unblocked.  A seperate process handles
unblocking the IP address by modifying the firewall because we want to
be able to re-block the IP address after some amount of time has
passed.  Since the CGI script is only executed as a response to to a
web hit it is not able to re-block an IP after some time has expired.

The last thing the CGI script does is rediect them to the original
website that they surfed to. 

THE DAEMON
----------
Once the CGI script has decided to unblock an IP address, that IP is
placed in a DBM file that is tied to a hash array in a perl script.
This perl script is a daemon process in that it is always running in
the background.  Every few seconds it wakes up and...:

    1. Sees if there are any new IP addresses to unblock
    2. Sees if any of the unblocked IP addresses have expired and must
       be reblocked.

The script in question is included in this tarball for your use.  The
rules it adds to unblock a new wireless client look like this:

    add $start_range set 1 skipto 20000 ip from $ip to any in via ${wif}

These rules are all part of a set (set 1) so that they can be deleted
easily in one ipfw command when the firewall is being rebuilt.  All
they do is allow one particular wireless IP address to skip around
this part of the firewall rules:

    # ---> THE RULES SKIP FROM HERE ...

    #    I. Forward all packets from the wireless network to our 
    #       wireless authorization page

    ${fwcmd} add 10000 fwd ${wip},8080 tcp from $wireless to any 80,8080,443 in via ${wif}

    #    J. Deny anything else from wireless.

    ${fwcmd} add deny all from ${wireless} to any in via ${wif}

    # ---> TO HERE

    ${fwcmd} add 20000 allow all from ${wireless} to any in via ${wif}


BANDWIDTH SUCKING:
------------------
You can use the ipfw to limit the amount of bandwidth that your
wireless clients can use by setting up some pipes.  Here's an example:

    pipe 1 config bw 5kbytes/s
    add pipe 1 ip from ${wireless} to not ${wireless}

    pipe 2 config bw 44kbytes/s
    add pipe 2 ip from not ${wireless} to ${wireless}

ACCEPTABLE USE:
---------------
My ISP is actually cool with people sharing my bandwidth but they have
to abide by the same usage policy that I do.  So the acceptable use
policy I used on my site is a mixture of my ISP's restrictions, some
things I thought about, and (largely) a "sample" policy I found
online.

OTHER SOURCES
-------------
Some other sources for the same type of thing are Wicap and NoCatAuth.
There is also something called OpenSplash that I used as a basis for
my stuff.

If you have any questions, send some email: scott (at) wannabe.guru.org.
