Coova Chilli Hotspot/Installation
Inhaltsverzeichnis
Hauptseite
Zurück zur Einführung
Debian Lenny Installation
Zwar verwendet der Autor hier Debian Lenny, aber diese Anleitung sollte auch weitestgehend auf alle anderen Debian- basierten Distributionen übertragbar sein. Mit ein paar Modifikationen kann man sie sicher auch auf RedHat oder SuSE übertragen.
Basis Installation
Für die Installation reicht in der Regel die NetInst Installations CD Um flexibel zu bleiben, entscheide ich mich für eine LVM Konfiguration für die Partitionierung:
Quelle | MountPoint | Größe | Dateisystem |
---|---|---|---|
/dev/sda1 | /boot | 150MB | ext3 |
/dev/sda2 | SWAP | 512MB - 1GB | SwapFS |
/dev/system/root_fs | / | 3-5GB | ext3 |
/dev/system/tmp_fs | /tmp | 512MB | ext3 |
/dev/system/var_log_fs | 2GB | /var/log | ext3 |
Weitere Pakete
Die Installation sollte selbst nur die absolute Basis sein. Es reicht daher in der Softwareauswahl entweder alles zu deaktivieren, oder einzig "Standard" aktiviert zu lassen. Ist die Installation soweit abgeschlossen und der Rootprompt bereit, werden nun einige Pakete nachinstalliert:
- Pakete nachinstallieren
spot01 ~ # apt-get install vim ssh nullmailer mysql-server |
Vim dient hier lediglich dazu, den Komfort zu erhöhen, beim editieren von Textdateien. Es kann stattdessen natürlich jeder andere Editor verwendet werden, wie z.B joe od. mc. Als absolutes Pflichtpaket gehört ein SSH Server auf den Rechner. Für den Fall der Fälle, dass E-Mail den Rechner verlassen sollen, wird der SMTP Server nullmailer verwendet. Er dient lediglich dazu, E-Mails an einen richtigen E-Mail Server weiterzuleiten. Der MySQL Server wird für die Authentifizierung und Session Verwaltung benötigt. Während der Installation wird nach einem Root Kennwort gefragt, welches entsprechend ausgefüllt werden sollte.
Netzwerk Konfiguration
Wir gehen hier davon aus, dass wir über zwei Netzwerkkarten verfügen:
- eth0 - über sie erreichen wir das Internet und wird per DHCP konfiguriert
- eth1 - an dieser Netzwerkkarte hängt der AccessPoint und/oder ein Switch für die W-Lan od. Kabel Clients
Während die eth0 ihre IP Adresse und Default Route über einen DHCP Server bezieht, wird die eth1 dagegen unkonfiguriert belassen. Sie wird später automatisch von Coova Chilli verwendet.
- /etc/network/interfaces
auto lo iface lo inet loopback # Externe NIC zum Router/Internet auto eth0 iface eth0 inet dhcp # NIC mit den W-LAN Clients auto eth1 |
FreeRadius Server
Wie bereits erwähnt, benötigen wir einen Authentifizierungsserver. Diese Funktion stellt uns der Service FreeRadius zur Verfügung. Als Datenquelle kann der Radius Server unterschiedliche Dienste abfragen. Darunter sind z.B. LDAP, SQL, oder die lokale /etc/passwd Datei. In diesem Fall verwenden wir den bereits installierten MySQL Server. Daher kommt noch das Modul für diesen dazu.
- FreeRadius installieren
spot01 ~ # apt-get install freeradius freeradius-mysql |
MySQL Datenbank
Datenbank anlegen
Als nächstes wird die Datenbank erstellt und mit Tabellen gefüllt:
- Datenbank erstellen
spot01 ~ # mysqladmin -psecret create radius spot01 ~ # mysql radius < /etc/freeradius/sql/mysql/schema.sql -psecret spot01 ~ # mysql radius < /etc/freeradius/sql/mysql/nas.sql -psecret |
Mit dem ersten Kommando erzeugen erzeugen wir die Datenbank. Das MySQL Root Kennwort (hier secret) wird der Bequemlichkeit- halber gleich mit gegeben. Das zweite und dritte Kommando füllt die Datenbank und legt die nötigen Tabellen an. Auch hier geben wir das Root Kennwort gleich mit.
Datenbank Benutzer
Da wir nicht den Root Account verwenden wollen, für den Zugriff auf die Datenbank, erzeugen wir einen Benutzer, der die dazu nötigen Rechte erhält. Am einfachsten ist es, eine Datei mit den entsprechenden Kommandos anzulegen und sie dann dem MySQL Server zuzuführen:
- radius-user.sql
CREATE USER 'radius'@'localhost'; SET PASSWORD FOR 'radius'@'localhost' = PASSWORD('radiuspass'); # The server can read any table in SQL GRANT SELECT ON radius.* TO 'radius'@'localhost'; # The server can write to the accounting and post-auth logging table. # # i.e. GRANT ALL on radius.radacct TO 'radius'@'localhost'; GRANT ALL on radius.radpostauth TO 'radius'@'localhost'; GRANT ALL on radius.radcheck TO 'radius'@'localhost'; GRANT ALL on radius.nas TO 'radius'@'localhost'; |
Es wird also ein Benutzer radius angelegt, mit dem Kennwort radiuspass. Er darf auf alle Tabellen lesend zugreifen und zusätzlich schreibend auf die Tabellen radacct, radpostauth, radcheck, nas. Dies jedoch nur vom eigenen Rechner. Nun fügen wir den Benutzer mit folgendem Befehl hinzu:
- Benutzer hinzufügen
spot01 ~ # mysql < radius-user.sql -psecret |
Ob dies geklappt hat, kann man überprüfen mit:
spot1:~# mysql -uradius -pradiuspass radius -e 'show tables;' +------------------+ | Tables_in_radius | +------------------+ | nas | | radacct | | radcheck | | radgroupcheck | | radgroupreply | | radpostauth | | radreply | | radusergroup | +------------------+ |
FreeRadius anpassen
Nun muss der FreeRadius Server darauf vorbereitet werden. Dazu bearbeiten wir zuerst die für MySQL zuständige Datei:
- /etc/freeradius/sql.conf
sql { database = "mysql" driver = "rlm_sql_${database}" server = "localhost" # DB Server login = "radius" # DB Benutzernamen password = "radiuspass" # DB Passwort radius_db = "radius" acct_table1 = "radacct" acct_table2 = "radacct" postauth_table = "radpostauth" authcheck_table = "radcheck" authreply_table = "radreply" groupcheck_table = "radgroupcheck" groupreply_table = "radgroupreply" usergroup_table = "radusergroup" read_groups = no deletestalesessions = yes sqltrace = no sqltracefile = ${logdir}/sqltrace.sql num_sql_socks = 5 connect_failure_retry_delay = 60 readclients = yes # von No auf Yes nas_table = "nas" # $INCLUDE sql/${database}/dialup.conf # Einbinden } |
Da die Datei eine Menge Kommentare beinhaltet, wird sie hier Vollständigkeit abgebildet. Wirklich interessant sind allerdings nur die Zeilen für den MySQL Server, sowie die letzten drei Zeilen.
Nun muss diese Datei noch vom Radius Server eingebunden werden, welches wieder über ein Include geschieht. Dazu müssen wir nun die Hauptkonfiguration- Datei öffnen:
- /etc/freeradius/radiusd.conf
prefix = /usr exec_prefix = /usr sysconfdir = /etc localstatedir = /var sbindir = ${exec_prefix}/sbin logdir = /var/log/freeradius raddbdir = /etc/freeradius radacctdir = ${logdir}/radacct confdir = ${raddbdir} run_dir = ${localstatedir}/run/freeradius db_dir = $(raddbdir) libdir = /usr/lib/freeradius pidfile = ${run_dir}/freeradius.pid user = freerad group = freerad max_request_time = 30 cleanup_delay = 5 max_requests = 1024 listen { type = auth ipaddr = * port = 0 } listen { ipaddr = * port = 0 type = acct } hostname_lookups = no allow_core_dumps = no regular_expressions = yes extended_expressions = yes log { destination = files file = ${logdir}/radius.log syslog_facility = daemon stripped_names = no auth = no auth_badpass = no auth_goodpass = no } checkrad = ${sbindir}/checkrad security { max_attributes = 200 reject_delay = 1 status_server = yes } ### kann abgeschaltet werden #### proxy_requests = no ##### ##### $INCLUDE proxy.conf $INCLUDE clients.conf snmp = no $INCLUDE snmp.conf thread pool { start_servers = 5 max_servers = 32 min_spare_servers = 3 max_spare_servers = 10 max_requests_per_server = 0 } modules { pap { auto_header = no } chap { authtype = CHAP } pam { pam_auth = radiusd } unix { radwtmp = ${logdir}/radwtmp } $INCLUDE eap.conf mschap { } ldap { server = "ldap.your.domain" basedn = "o=My Org,c=UA" filter = "(uid=%{Stripped-User-Name:-%{User-Name}})" ldap_connections_number = 5 timeout = 4 timelimit = 3 net_timeout = 1 tls { start_tls = no } dictionary_mapping = ${confdir}/ldap.attrmap edir_account_policy_check = no } realm IPASS { format = prefix delimiter = "/" } realm suffix { format = suffix delimiter = "@" } realm realmpercent { format = suffix delimiter = "%" } realm ntdomain { format = prefix delimiter = "\\" } checkval { item-name = Calling-Station-Id check-name = Calling-Station-Id data-type = string } preprocess { huntgroups = ${confdir}/huntgroups hints = ${confdir}/hints with_ascend_hack = no ascend_channels_per_line = 23 with_ntdomain_hack = no with_specialix_jetstream_hack = no with_cisco_vsa_hack = no } files { usersfile = ${confdir}/users acctusersfile = ${confdir}/acct_users preproxy_usersfile = ${confdir}/preproxy_users compat = no } detail { detailfile = ${radacctdir}/%{Client-IP-Address}/detail-%Y%m%d detailperm = 0600 header = "%t" } acct_unique { key = "User-Name, Acct-Session-Id, NAS-IP-Address, Client-IP-Address, NAS-Port" } $INCLUDE sql.conf ###### Wichtiger Teil ####### radutmp { filename = ${logdir}/radutmp username = %{User-Name} case_sensitive = yes check_with_nas = yes perm = 0600 callerid = "yes" } radutmp sradutmp { filename = ${logdir}/sradutmp perm = 0644 callerid = "no" } attr_filter attr_filter.post-proxy { attrsfile = ${confdir}/attrs } attr_filter attr_filter.pre-proxy { attrsfile = ${confdir}/attrs.pre-proxy } attr_filter attr_filter.access_reject { key = %{User-Name} attrsfile = ${confdir}/attrs.access_reject } attr_filter attr_filter.accounting_response { key = %{User-Name} attrsfile = ${confdir}/attrs.accounting_response } counter daily { filename = ${db_dir}/db.daily key = User-Name count-attribute = Acct-Session-Time reset = daily counter-name = Daily-Session-Time check-name = Max-Daily-Session reply-name = Session-Timeout allowed-servicetype = Framed-User cache-size = 5000 } always fail { rcode = fail } always reject { rcode = reject } always noop { rcode = noop } always handled { rcode = handled } always updated { rcode = updated } always notfound { rcode = notfound } always ok { rcode = ok simulcount = 0 mpp = no } expr { } digest { } expiration { reply-message = "Password Has Expired\r\n" } logintime { reply-message = "You are calling outside your allowed timespan\r\n" minimum-timeout = 60 } exec { wait = yes input_pairs = request shell_escape = yes output = none } exec echo { wait = yes program = "/bin/echo %{User-Name}" input_pairs = request output_pairs = reply shell_escape = yes } # Kann unbeachtet bleiben, nur fuer Dial IN !!! ippool main_pool { range-start = 192.168.1.1 range-stop = 192.168.3.254 netmask = 255.255.255.0 cache-size = 800 session-db = ${db_dir}/db.ippool ip-index = ${db_dir}/db.ipindex override = no maximum-timeout = 0 } policy { filename = ${confdir}/policy.txt } } instantiate { exec expr expiration logintime } $INCLUDE policy.conf #### Nebenconfig #### $INCLUDE sites-enabled/ |
Auch hier die vollständige Datei. Zu beachten ist lediglich $INCLUDE sql.conf Zeile.
Da auch hier wiederum Teile ausgegliedert wurde, muss eine weitere Datei editiert werden. Ähnlich wie bei einem Apachen, können unterschiedliche Konfigurationen erzeugt werden, für unterschiedliche Zwecke. Diese liegen im /etc/freeradius/sites-available Verzeichnis.
- /etc/freeradius/sites-available/default
authorize { preprocess chap mschap suffix eap { ok = return } unix sql # aktivieren expiration logintime pap } authenticate { Auth-Type PAP { pap } Auth-Type CHAP { chap } Auth-Type MS-CHAP { mschap } unix eap } preacct { preprocess acct_unique suffix files } accounting { detail unix radutmp sql # aktivieren attr_filter.accounting_response } session { radutmp sql # aktivieren } post-auth { exec Post-Auth-Type REJECT { attr_filter.access_reject } } pre-proxy { } post-proxy { eap } |
Hier müssen die entsprechend auskommentierten SQL Zeilen von ihrem Kommentarzeichen befreit werden. Vor allem die Zeilen session müssen aktiviert werden.
Zu guter letzt wird die Client Konfiguration vom Radius Server angepasst:
- /etc/freeradius/clients.conf
client localhost { ipaddr = 127.0.0.1 secret = radiussecret require_message_authenticator = no } |
Radius Benutzer hinzufügen
Nun können wir zwei Benutzer für Radius hinzufügen, welche in die Datenbank eingepflegt werden:
spot01 ~ # echo "INSERT INTO radcheck (UserName, Attribute, Value) VALUES ('guest', 'Password','guest');" | mysql -u radius -pradspot01 radius spot01 ~ # echo "INSERT INTO radcheck (UserName, Attribute, Value) VALUES ('chillispot', 'Password','chillipass');" | mysql -u radius -pradiuspass radius |
Es werden also zwei Benutzer hinzugefügt:
- Benutzer: guest - Passwort: guest
- Benutzer: chillispot - Passwort: chillipass
Der erste Benutzer dient später dazu, um unseren W-Lan Clients einen freien Internet Zugang zu gewähren, wenn sie einem Disclaimer zugestimmt haben. Der zweite Benutzer wird von Coova Chilli verwendet. Um die Funktionsfähigkeit zu überprüfen, kann dies mit Hilfe von radtest geschehen.
spot01 ~ # /etc/init.d/freeradius restart spot01 ~ # radtest guest guest 127.0.0.1 0 radiussecret Sending Access-Request of id 203 to 127.0.0.1 port 1812 User-Name = "guest" User-Password = "guest" NAS-IP-Address = 127.0.1.1 NAS-Port = 0 rad_recv: Access-Accept packet from host 127.0.0.1 port 1812, id=203, length=2 |
Hier kann man nun erkennen, dass die Verbindung erfolgreich war. Sollte es nicht klappen, so lohnt sich der Debugaufruf mittels freeradius -XX. Dazu muss jedoch der Radius Server vorher gestoppt werden, wenn er denn laufen sollte!
Coova CHilli
Nach den Vorarbeiten, kann nun die Installation und Konfiguration von Chilli erfolgen.
Installation Chilli
Es gibt ein fertiges Paket für Debian Distributionen, welches installiert werden kann. Dazu laden wir es herunter und installieren es per dpkg:
- Installieren
spot01 ~ # wget http://ap.coova.org/chilli/coova-chilli_1.0.12-1_i386.deb spot01 ~ # dpkg -i coova-chilli_1.0.12-1_i386.deb |
Webseite Vorarbeiten
Nun müssen ein paar Dateien und Verzeichnisse für die Webseite erstellt und mit Inhalt gefüllt werden.
- Für die Webseite
spot01 ~ # cp /etc/chilli/defaults /etc/chilli/config spot01 ~ # mkdir -p /var/www/hotspot/uam spot01 ~ # cp /etc/chilli/www/* /var/www/hotspot |
Als erstes kopieren wir die Standard Konfigurations- Datei für Chilli um. Dann wird die Struktur für den späteren HotSpotLogin erstellt, den die Benutzer zu Gesicht bekommen werden.
spot01 ~ # cd /var/www/hotspot/uam spot01 ~ # wget http://ap.coova.org/uam/ spot01 ~ # wget http://ap.coova.org/js/chilli.js |
Hier wird eine Standardseite bezogen, welche einige JavaScript Sachen bereithält, die für die spätere Session nötig werden.
Ruft ein Gast eine Webseite auf, so wird er auf die index.html in dem Verzeichnis /var/www/hotspot/uam umgeleitet, welche wiederum selbst eine Umleitung vornimmt. Diese wird ins Leere laufen, solang die dort enthaltene Adresse nicht durch die eigene ausgetauscht wird. Ein kurzer sed Aufruf löst diese Problem:
spot01~ # sed -i 's/coova.org\/js\/chilli.js/10.1.0.1\/uam\/chilli.js/g' /var/www/hotspot/uam/index.html |
Damit ändern wir also den Umleitung von coova.org auf die IP Adresse 10.1.0.1 (unserem Coova Chilli Spot) um. Sofern ein DNS Server bereit steht, kann das natürlich erneut ein Name sein.
Chiili Konfiguration
Damit Coova Chilli automatisch startet, bedarf es einer kleinen Anpassung:
- /etc/default/chilli
START_CHILLI=1 |
Als nächstes wird nun Chilli selbst konfiguriert. Dazu wieder eine von normalen Kommentaren befreite Datei:
- /etc/chilli/config
# Die Externe Netzwerkkarte zum Internet HS_WANIF=eth0 # Die Interne Netzwerkkarte zu den HotSpot Clients HS_LANIF=eth1 # Das gewünsche Netzwerk für die Hotspot Clients HS_NETWORK=10.1.0.0 # Die Netzwerkmaske dazu HS_NETMASK=255.255.255.0 # HotSpot Network Netmask # Die IP Adresse, an der Chilli lauschen soll incl. DHCP Server. Muss zum Netz oben pssen! HS_UAMLISTEN=10.1.0.1 # Der Port, an dem Chilli lauscht. Auf diesen greifen die HotSpot Clients zu HS_UAMPORT=3990 # Der DNS Server, welcher den Clients mitgegeben wird HS_DNS1=10.1.0.1 # Eine ID HS_NASID=nas01 # Das Kennwort auf den später das CGI Login Script "hotspotlogin.cgi" zugreift HS_UAMSECRET=spotsecret # Wo ist der erste Radius Server zu finden HS_RADIUS=localhost # Wo ist der zweite Radius Server zu finden HS_RADIUS2=localhost # Mit welchem Kennwort greifen wir zu, siehe: /etc/freeradius/clients.conf HS_RADSECRET=radiussecret # Auf welche DNS/IP Adressen dürfen die HotSpot Clients zugreifen, ohne Authentifizierung! HS_UAMALLOW=10.1.0.0/24 # Wo befindet sich die Loginserver, auf die die Clients umgeleitet werden HS_UAMSERVER=10.1.0.1/cgi-bin/hotspotlogin.cgi # Welche Seite wird aufgerufen HS_UAMFORMAT=https://\$HS_UAMSERVER/uam/chilli # Was ist die Standardwebseite HS_UAMHOMEPAGE=http://\$HS_UAMLISTEN:\$HS_UAMPORT/www/coova.html # Unbekannt ??? HS_UAMSERVICE=https://10.1.0.1/uam/auth # Soll angebelich für viele Seiten benötigt werden, die google Map eingebettet haben HS_USE_MAP=on # Wenn im Radius kein Session Timeout definiert wurde, wann fliegt der Hotspot User wieder raus HS_DEFSESSIONTIMEOUT=900 # In Sekunden # Wenn nichts passiert, wann fliegt der Hotspot User raus HS_DEFIDLETIMEOUT=900 # In Sekunden # Der Modus HS_MODE=hotspot # Der Typ HS_TYPE=chillispot # Benutzer in der Datenbank HS_ADMUSR=chillispot # Passwort vom User chillispot in der Datenbank HS_ADMPWD=chillipass # Das Stammverzeichnis von der Webseite HS_WWWDIR=/var/www/hotspot # Die Benärdatei wwwsh HS_WWWBIN=/etc/chilli/wwwsh # Freier Name für den Hotspot HS_LOC_NAME="KBB Spot1" # WISPr Location Name and used in portal |
Firewall Script
Bei jedem Start von Chilli Spot und nach der Anmeldung eines HotSpot Clients, werden Aktionen ausgelöst, wie z.B. das setzen von Firewall Regeln. Darin kann bestimmt werden, welche Ports dem Benutzer zur Verfügung stehen sollen. Dieses Script kann unter dem Namen /etc/chilli/ipup.sh abgelegt werden:
- /etc/chilli/ipup.sh
#!/bin/sh # # Firewall script for ChilliSpot # A Wireless LAN Access Point Controller # # Uses $EXTIF (eth0) as the external interface (Internet or intranet) and # $INTIF (eth1) as the internal interface (access points). # # # SUMMARY # * All connections originating from chilli are allowed. # * Only ssh is allowed in on external interface. # * Nothing is allowed in on internal interface. # * Forwarding is allowed to and from the external interface, but disallowed # to and from the internal interface. # * NAT is enabled on the external interface. IPTABLES="/sbin/iptables" EXTIF="eth0" INTIF="eth1" $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 releated, established and ssh on $EXTIF. Reject everything else. $IPTABLES -A INPUT -i $EXTIF -p tcp -m tcp --dport 22 --syn -j ACCEPT $IPTABLES -A INPUT -i $EXTIF -j REJECT #Allow related and established from $INTIF. Drop everything else. $IPTABLES -A INPUT -i $INTIF -j DROP #Allow http and https on other interfaces (input). #This is only needed if authentication server is on same server as chilli $IPTABLES -A INPUT -p tcp -m tcp --dport 80 --syn -j ACCEPT $IPTABLES -A INPUT -p tcp -m tcp --dport 443 --syn -j ACCEPT #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 # Drop everything to and from $INTIF (forward) # This means that access points can only be managed from ChilliSpot $IPTABLES -A FORWARD -i $INTIF -j DROP $IPTABLES -A FORWARD -o $INTIF -j DROP #Enable NAT on output device $IPTABLES -t nat -A POSTROUTING -o $EXTIF -j MASQUERADE |
Das Script muss natürlich ausführbar sein:
spot01 ~ # chmod +x /etc/chilli/ipup.sh |
Apache
Nun folgt die Apache2 Konfiguration. Zum einen benötigen wir einen Virtual Host der CGI Dateien ausführen darf, zum anderen soll er über SSL arbeiten.
Vorarbeit
Wir erzeugen ein CGI Verzeichnis und kopieren eine Beispiel hotspotlogin.cg hinein:
spot01 ~ # mkdir -p /var/www/hotspot/cgi-bin spot01 ~ # zcat /usr/share/doc/coova-chilli/hotspotlogin.cgi.gz > /var/www/hotspot/cgi-bin/hotspotlogin.cgi spot01 ~ # chmod a+x /var/www/hotspot/cgi-bin/hotspotlogin.cgi |
Damit die CGI Datei hotspotlogin.cgi mit dem Chilli Server kommunizieren kann, muss die Authentifizierung aktiviert, sowie das Passwort angegeben werden. Dazu passen wir diese Datei an
- /var/www/hotspot/cgi-bin/hotspotlogin.cgi
[...] # Siehe /etc/chilli/config -> HS_UAMSECRET $uamsecret = "spotsecret"; $userpassword=1; [..] |
Apache SSL
Da die Datei hotspotlogin.cgi erwartet, dass der HotSpot User über eine HTTPS Adresse kommt, benötigen wir daher einen SSL aktivierten Apachen. Um das dafür nötige Zertifikat bequem zu erstellen, eignet sich das Paket ssl-cert. Mit dem richtigen Aufruf erledigt er alles dafür nötige:
spot01 ~ # apt-get install apache2 ssl-cert spot01 ~ # mkdir /etc/apache2/ssl spot01 ~ # hostname -f |
Die letzte Ausgabe merken, da wir sie für das Zertifikat benötigen:
spot01 ~ # make-ssl-cert /usr/share/ssl-cert/ssleay.cnf /etc/apache2/ssl/apache.pem |
Bei der Frage für den CommonName bitte die Ausgabe von hostname -f eintragen.
Nun wird das SSL Modul vom Apachen noch aktiviert:
- SSL Modul aktivieren
spot01 ~ # a2enmod ssl |
Virtual Host
Nun können wir den Virtual Host erstellen und aktivieren:
- /etc/apache2/sites-available/hotspot
NameVirtualHost 10.1.0.1:443 <VirtualHost 10.1.0.1:443> ServerAdmin hotpot@wireless.local DocumentRoot "/var/www/hotspot" ServerName "spot01.wireless.local" <Directory "/var/www/hotspot/"> Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory> Alias "/dialupadmin/" "/usr/share/freeradius-dialupadmin/htdocs/" <Directory "/usr/share/freeradius-dialupadmin/htdocs/"> Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory> ScriptAlias /cgi-bin/ /var/www/hotspot/cgi-bin/ <Directory "/var/www/hotspot/cgi-bin/"> AllowOverride None Options ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory> ErrorLog /var/log/apache2/hotspot-error.log LogLevel warn CustomLog /var/log/apache2/hotspot-access.log combined ServerSignature On SSLEngine on SSLCertificateFile /etc/apache2/ssl/apache.pem </VirtualHost> |
- Virtual Host aktivieren
spot01 ~ # a2ensite hotspot spot01 ~ # apache2ctl -t spot01 ~ # /etc/init.d/apache2 reload |
Wer möchte, kann auch noch die Hautpkonfiguration vom Apache anpasen. Anbieten würde sich z.B. der Servername:
- /etc/apache2/apache2.conf
{{code|
[...] ServerName 10.1.0.1 [...]
Den Syntax check und ein anschließendes reload nicht vergessen.
Testen
Sind all die Arbeiten abgeschlossen worden, kann nun getestet werden. Der Autor hat hierzu alles in Vmware laufen und kann so beliebige Tests ausführen. Wichtig ist hierbei die richtige Reihenfolge der Dienste:
- /etc/init.d/mysql restart
- /etc/init.d/freeradius restart
- /etc/init.d/chilli restart
- /etc/init.d/apache2 restart
Beim start des Hotspot Clients muss dieser eine IP Adresse vom Chilli DHCP Server erhalten haben. Des weiteren wurde auch der DNS Server mitgegeben. Beim öffnen einer beliebigen Webseite vom Hotspot Client, wird die Seite umgeleitet auf die hotspotlogin.cgi Seite. Eventuell gibt es ein Warnhinweis, aufgrund des selbstsignierten SSL Zertifikat. Wurde die Login Seite geöffnet, kann nun der Benutzer "guest" und das Passwort "guest" verwendet werden. Hat alles geklappt, sollte der Zugang zum Internet nun möglich sein. Ein eigenes Fenster klärt über den Status auf.
eth1 wird zu tun0
Wird Chili gestartet, so wird die IP Adresse und der DHCP Server auf das Interface tun0 gelegt, welches in diesem Moment entsteht. Daher ist es wichtig, dass die eth1 nicht konfiguriert wird. Im gleichen Atemzug muss daher das Modul für tun vorhanden sein. Dies kann leicht mit modprobe tun festgestellt werden.
DNS Server
Sollte bisher noch kein DNS Server vorhanden sein, so eignet sich am besten ein Cachin Name Server, wie DNSMasq:
spot01 ~ # apt-get install dnsmasq |
Er bedarf keiner Konfiguration mehr. Einzig in der /etc/resolv.conf sollten mindestens zwei richtige DNS Server vorhanden sein. Nach einem Neustart von dnsmasq steht er den HotSpot Clients zur Verfügung.
HotSpotLogin.cgi anpassen
Sind alle diese Arbeiten abgeschlossen, kann als nächstes die hotspotlogin.cgi Datei dahingehend angepasst werden, dass lediglich ein Disclaimer verwendet wird, dem die HotSpot Clients zustimmen müssen.
Dazu öffnen wir erneut die Datei und springen zur Zeile 387.
Ab dort befinden sich die entsprechenden Zeilen. Der Benutzername und das Kennwort werden hier abgefragt. Nun müssen wir nichts anderes unternehmen, als diese Felder zu befüllen und zu verstecken:
- Aus diesen Zeilen ...
<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> |
- Werden diese Zeilen ...
<center> <table border=\"0\" cellpadding=\"5\" cellspacing=\"0\" style=\"width: 217px;\"> <tbody> <tr> <!-- <td align=\"right\">Benutzername:</td> --> <td><input TYPE=\"hidden\" STYLE=\"font-family: Arial\" type=\"text\" name=\"UserName\" size=\"20\" value=\"guest\" maxlength=\"128\"></td> </tr> <!-- DISCLAIMER einbinden--> <tr> Wenn sie auf \"Login\" klicken. Stimmen sie unserem Disclaimer zu! </tr> <!-- DISCLAIMER Ende --> <tr> <!-- <td align=\"right\">Passwort:</td> --> <td><input TYPE=\"hidden\" STYLE=\"font-family: Arial\" type=\"password\" name=\"Password\" size=\"20\" value=\"guest\" maxlength=\"128\"></td> </tr> <tr> <td align=\"center\" colspan=\"2\" height=\"23\">AGB akzeptieren: <input type=\"submit\" name=\"button\" value=\"Login\" onClick=\"javascript:popUp('$loginpath?res=popup1&uamip=$uamip&uamport=$uamport')\"></td> </tr> </tbody> </table> </center> |
Im Zweiten Teil werden die Felder für Benutzernamen und Passwort versteckt. Natürlich ist dies nicht besonders schön, zumal Anführungszeichen maskiert werden müssen. Daher gibt es noch eine weit elegantere Lösung. wir können das Script so anpassen, dass eine externe HTML Datei eingelesen wird. Der Inhalt davon, wird an der Stelle positioniert, wo wir eine entsprechende Variable unterbringen. Dazu müssen wir erneut ein paar Zeilen hinzufügen. Dies geschieht ab der Zeile 387
[...] if ($result == 5) { print " <h1 style=\"text-align: center;\">Hotspot Login</h1>"; } if ($result == 2 || $result == 5) { [...]
Zwischen der if ($result == 5) {' und der print Zeile, kommen nun folgende Zeilen:
my $inpath="/var/www/hotspot/disclaimer/disclaimer.html"; open IN,"$inpath" || die "cannot open $inpath for read\n\n"; my $disclaimer=""; while (my $l=<IN>) { chomp $l; $disclaimer="$disclaimer" . "$l"; }
Damit wird nun die Datei /var/www/hotspot/disclaimer/disclaimer.htm eingelesen. Damit sie an der passenden Stelle eingepflegt wird, kommt nun die Variable $disclaimer zum zuge.
<tr> <!-- <td align=\"right\">Benutzername:</td> --> <td><input TYPE=\"hidden\" STYLE=\"font-family: Arial\" type=\"text\" name=\"UserName\" size=\"20\" value=\"guest\" maxlength=\"128\"></td> </tr> <!-- DISCLAIMER einbinden--> $disclaimer <!-- DISCLAIMER Ende --> <tr> <!-- <td align=\"right\">Passwort:</td> --> <td><input TYPE=\"hidden\" STYLE=\"font-family: Arial\" type=\"password\" name=\"Password\" size=\"20\" value=\"guest\" maxlength=\"128\"></td> </tr> <tr>
Hat alles geklappt, kann nun sehr leicht der Disclaimer ausgetauscht werden. Dabei können wieder sämtliche HTML Befehle zum Einsatz kommen. Es können nun auch Bilder etc. mit eingebunden werden.
Logging
Wer auf ein Logging angewiesen ist, der kann sehr bequem per SLQ Abfrage die dazu nötigen Tabellen abfragen. In dem nachfolgendem Beispiel werden die Zugriffe vom Benutzer "guest" abgerufen:
spot01 ~ # mysql -uradius -pradiuspass radius -e 'select AcctUniqueId,acctstarttime,acctstoptime,callingstationid,FramedIPAddress from radacct where username="guest"' |
Heraus kommt diese Auflistung:
+------------------+---------------------+---------------------+-------------------+-----------------+ | AcctUniqueId | acctstarttime | acctstoptime | callingstationid | FramedIPAddress | +------------------+---------------------+---------------------+-------------------+-----------------+ | 5193b45afce960fc | 2009-03-05 16:29:59 | 2009-03-05 16:45:02 | 00-0C-29-DD-FA-E7 | 10.1.0.2 | | 090e966bc520bff6 | 2009-03-05 17:01:06 | NULL | 00-0C-29-DD-FA-E7 | 10.1.0.2 | +------------------+---------------------+---------------------+-------------------+-----------------+
- Aufbau
- AcctUniqueId - Eindeutige Session ID
- acctstarttime - Session Startzeit
- acctstoptime - Session Endzeit
- callingstationid - Client MAC Adresse
- FramedIPAddress - Zugewiesene IP Adresse
Natürlich können noch weitere Tabellen abgefragt werden. Einfach die passenden heraussuchen.
Anhang
hotspotlogin.cgi
# Shared secret used to encrypt challenge with. Prevents dictionary attacks. # You should change this to your own shared secret. $uamsecret = "ahhie3Ju"; # Uncomment the following line if you want to use ordinary user-password # for radius authentication. Must be used together with $uamsecret. $userpassword=1; # This code is horrible -- it came that way, and remains that way. A # real open-source captive portal for coova-chilli should be built -- david $loginpath = "/cgi-bin/hotspotlogin.cgi"; use Digest::MD5 qw(md5 md5_hex md5_base64); # 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$/)) { 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;\">HotSpot 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$/i) { $username = $array2[1]; } if ($array2[0] =~ /^password$/i) { $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] =~ /^username$/i) { $username = $array2[1]; } if ($array2[0] =~ /^password$/i) { $password = $array2[1]; } 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\">"; } 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>HostSpot Login fehlgeschlagen</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>HotSpot 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 (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;\">HotSpot Login fehlgeschlagen</h1>"; if ($reply) { print "<center> $reply </BR></BR></center>"; } } if ($result == 5) { print " <h1 style=\"text-align: center;\">Hotspot Login</h1>"; } if ($result == 2 || $result == 5) { my $inpath="/var/www/hotspot/disclaimer/disclaimer.html"; open IN,"$inpath" || die "cannot open $inpath for read\n\n"; my $disclaimer=""; while (my $l=<IN>) { chomp $l; $disclaimer="$disclaimer" . "$l"; } 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=\"$userurl\"> <!--#include virtual=\"index.shtml\"--> <center> <table border=\"0\" cellpadding=\"5\" cellspacing=\"0\" style=\"width: 217px;\"> <tbody> <tr> <!-- <td align=\"right\">Benutzername:</td> --> <td><input TYPE=\"hidden\" STYLE=\"font-family: Arial\" type=\"text\" name=\"UserName\" size=\"20\" value=\"guest\" maxlength=\"128\"></td> </tr> <!-- DISCLAIMER einbinden--> $disclaimer <!-- DISCLAIMER Ende --> <tr> <!-- <td align=\"right\">Passwort:</td> --> <td><input TYPE=\"hidden\" STYLE=\"font-family: Arial\" type=\"password\" name=\"Password\" size=\"20\" value=\"guest\" maxlength=\"128\"></td> </tr> <tr> <td align=\"center\" colspan=\"2\" height=\"23\">AGB akzeptieren: <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);
Quellen
- FreeRadius - http://www.freeradius.org
- CoovaChilli Webseite - http://coova.org/wiki/index.php/CoovaChilli
- Cooca chilli - Ubuntu Installation
--Denny 22:12, 11. Mär. 2009 (UTC)