source: ccsd/private/modules/ccs_monitor_rurallink.py @ 717

Last change on this file since 717 was 717, checked in by mglb1, 7 years ago
  • Improve robustness of routing setup
    • Centralise route addition/removal
    • Check routes periodically
  • Property svn:keywords set to Id
File size: 140.9 KB
Line 
1# Copyright (C) 2006  The University of Waikato
2#
3# This file is part of crcnetd - CRCnet Configuration System Daemon
4#
5# Rural Link autoconfiguration and management functionality
6#
7# Author:       Matt Brown <matt@crc.net.nz>
8# Version:      $Id$
9#
10# No usage or redistribution rights are granted for this file unless
11# otherwise confirmed in writing by the copyright holder.
12import sys
13import os, os.path
14import crypt
15import re
16import iwtools
17import httplib
18import socket
19import urllib
20from md5 import md5
21from telnetlib import Telnet
22from BaseHTTPServer import BaseHTTPRequestHandler
23
24from crcnetd._utils.ccsd_common import *
25from crcnetd._utils.ccsd_log import *
26from crcnetd._utils.ccsd_clientserver import registerPage, registerRealm, \
27        registerDir, AsyncHTTPServer, CCSDHandler, registerRecurring
28from crcnetd._utils.ccsd_config import config_getboolean, pref_get, pref_set, \
29        pref_getboolean
30from crcnetd._utils.ccsd_events import catchEvent
31from crcnetd._utils.interfaces import debian_interfaces
32from crcnetd._utils.dhcpd import dhcpd_conf
33
34from ccs_monitor_web import registerMenuItem, returnPage, MENU_TOP, \
35        MENU_GROUP_GENERAL, CPE_ADMIN_REALM, monitor_prefs, realm, \
36        ADMIN_USERNAME, generateSelect, HTable, DEFAULT_RESOURCE_DIR, \
37        homepage
38from ccs_monitor_status import getSSMeter, initialiseNetworkMap
39
40class ccs_rurallink_error(ccsd_error):
41    pass
42
43ccs_mod_type = CCSD_CLIENT
44
45SERIALNO_FILE = "/etc/rlserial"
46DEFAULT_PASSWORD = "rurallink"
47DEFAULT_INTERNAL_IFNAME = "int"
48DEFAULT_BACKHAUL_IFNAME = "bhaul"
49DEFAULT_ISP_IFNAME = "ext"
50DEFAULT_AP0_IFNAME = "ap0"
51RL_ESSID_PREFIX = "rl-"
52DHCP_DEFAULTS = "/etc/default/dhcp3-server"
53ASSOC_STORE_DIR = "/var/lib/ccsd/"
54HOSTNAME_CHAR_RE = "[A-za-z0-9\-]"
55MAP_UPDATE_INTERVAL = 60*30
56
57BCL_IFACE="bcl"
58BCL_CONF="/etc/ppp/peers/bcl"
59PAP_SECRETS="/etc/ppp/pap-secrets"
60
61ISP_BCL = "bcl"
62ISP_DSL = "dsl"
63RL_ISPS = { ISP_BCL:"BCL (Xtra Wireless)", ISP_DSL:"DSL Connection" }
64   
65DSL_MODEM_IP = "10.1.1.1"
66DSL_CPE_IP = "10.1.1.254"
67DSL_NETMASK = "8"
68DSL_TELNET_TIMEOUT = 2
69
70# How often (seconds) to check DNS settings
71DNS_UPDATE_INTERVAL = 60
72
73# How often (seconds) to check routing
74ROUTE_CHECK_INTERVAL = 60
75
76ISP_IF_DISCONNECT = """<a href="/rladmin/isp-disconnect">[Disconnect]</a>"""
77ISP_IF_CONNECT = """<a href="/rladmin/isp-connect">[Connect]</a>"""
78ISP_IF_RELOAD = """<script language="javascript" type="text/javascript">
79remaining=30
80function progress() {
81    remaining--;
82    if (remaining == 0) {
83        clearInterval(id);
84        reload();
85        return;
86    }
87    $("rem").innerHTML=remaining;
88}
89function reload() {
90    l = document.location.toString();
91    if (l.indexOf("isp-connect")!=-1) {
92        newL = l.substring(0, l.length-12);
93    } else {
94        newL = l.substring(0, l.length-15);
95    }
96    document.location = newL;
97}
98id = window.setInterval(progress, 1000);
99</script>
100"""
101
102RL_NODE = -1
103RL_INTERNAL = 0
104RL_AP0 = 1
105RL_AP1 = 2
106RL_AP2 = 3
107
108channels = {1:"01 - 2.412Ghz *", 2:"02 - 2.417Ghz", 3:"03 - 2.422Ghz", 
109    4:"04 - 2.427Ghz", 5:"05 - 2.432Ghz", 6:"06 - 2.437Ghz *", 
110    7:"07 - 2.442Ghz", 8:"08 - 2.447Ghz", 9:"09 - 2.452Ghz", 
111    10:"10 - 2.457Ghz", 11:"11 - 2.462Ghz *"}
112
113DEFAULT_TRAFFIC_PASSWD_FILE = "/var/lib/ccsd/passwd"
114USERNAME_CHAR_RE = "[A-za-z0-9]"
115FIREWALL_NET_OPTIONS = { "allow":"Always allow Internet access", \
116        "deny":"Always deny Internet access", \
117        "password":"Require a username and password for Internet access"}
118DEFAULT_INTERNAL_POLICY = "allow"
119DEFAULT_AP0_POLICY = "password"
120
121# How often (seconds) to retrieve traffic stats from iptables
122TRAFFIC_UPDATE_INTERVAL = 60
123
124# These parameters are currently configured to generate a /26 for
125# each node based on the RL_BASE of 12 bits + 14 bits of serial
126# number. Two bits are then allocated for a function on each node
127# giving a total of 4 /28s per node.
128# RL_BASE = hex(172.16.0.0)
129RL_BASE_S = "172.16.0.0"
130RL_MASK_S = "255.240.0.0"
131RL_BASE = 0xAC100000
132BASE_BITS = 12
133SERIAL_BITS = 14
134FUNCTION_BITS = 2
135
136# The following constants are calculated automatically from the values above
137BASE_SHIFT = 32 - BASE_BITS
138SERIAL_SHIFT = BASE_SHIFT - SERIAL_BITS
139FUNCTION_SHIFT = SERIAL_SHIFT - FUNCTION_BITS
140
141BASE_MASK = (((2 ** BASE_BITS) - 1) << BASE_SHIFT)
142SERIAL_MASK =  (((2 ** SERIAL_BITS) - 1) << SERIAL_SHIFT)
143FUNCTION_MASK = (((2 ** FUNCTION_BITS) - 1) << FUNCTION_SHIFT)
144
145rl_serialno = -1
146rl_password = DEFAULT_PASSWORD
147rl_masterip = None
148rl_mastername = ""
149rl_isptype = None
150rl_lastmapupdate = 0
151rl_trafficusers = {}
152unallocated_traffic=0
153
154##############################################################################
155# Utility Functions
156##############################################################################
157def generateRLNetwork(serialno, function):
158    """Generates a network address for the specified device and function
159   
160    Returns a tuple with two items. The first is the network address. The
161    second describes how many bits long the netmask is.
162    """
163
164    if function == RL_NODE:
165        function = 0
166        mask = 32 - SERIAL_SHIFT
167    else:
168        mask = 32 - FUNCTION_SHIFT
169
170    return (RL_BASE | ((serialno << SERIAL_SHIFT) & SERIAL_MASK) | \
171            ((function << FUNCTION_SHIFT) & FUNCTION_MASK), mask)
172
173def isValidRLSerialNo(serialno):
174
175    if int(serialno) <= 0:
176        return False
177    if int(serialno) > (2**SERIAL_BITS):
178        return False
179
180    return True
181
182def setupAssociation(gw, apname):
183    """Send a request to the AP to let us associate"""
184    global rl_password, rl_serialno, monitor_prefs
185   
186    name = socket.gethostname()
187   
188    data = "password=%s&serialno=%s" % (rl_password, rl_serialno)
189    try:
190        conn = httplib.HTTPConnection(gw)
191        conn.putrequest('POST', "/register_client/%s" % name, data)
192        conn.putheader('content-length', str(len(data)))
193        conn.endheaders()
194        conn.send(data)
195        rv = conn.getresponse()
196        conn.close()
197    except:
198        (type, value, tb) = sys.exc_info()
199        log_error("Failed to connect to upstream server - %s" % value)
200        return False
201   
202    if rv.status == 200:
203        log_info("Associated with upstream node %s" % apname)
204        pref_set("rurallink", "backhaul_assoc", apname, monitor_prefs)
205        retrieveMasterIP()
206        registerSubClients()
207        return True
208
209    pref_set("rurallink", "backhaul_assoc", "", monitor_prefs)
210    log_error("Client registration failed: %s" % rv.reason)
211    return False
212
213def configureBhaulInterface(essid):
214    """Configure an interface to talk to an access point"""
215    global backhaul_ifname, backhaul_vbase
216
217    # If there is no backhaul interface it can't be configured
218    if backhaul_ifname is None:
219        return False
220   
221    # Take interface down
222    log_command("/sbin/ifdown %s 2>&1" % backhaul_ifname)
223   
224    # Write /etc/network/interfaces stanza
225    ifaces = debian_interfaces()
226    iface = {"name":backhaul_ifname, "family":"inet", "method":"dhcp"}
227    if backhaul_vbase is not None:
228        iface["madwifi_base"] = backhaul_vbase
229        iface["wireless_standard"] = "g"
230    iface["wireless_mode"] = "Managed"
231    iface["wireless_essid"] = essid
232    remountrw()
233    ifaces[backhaul_ifname] = iface
234    ifaces.write()
235    remountro()
236   
237    # Bring Up, restart services
238    if log_command("/sbin/ifup %s 2>&1" % backhaul_ifname) != None:
239        # Failed
240        return False
241
242    # Get our interface IP address
243    gw = getGatewayIP()
244   
245    # Check connectivity
246    if not isHostUp(gw):
247        # Cannot associate now...
248        return True
249   
250    # Send an association request
251    return setupAssociation(gw, essid.split("-")[1])
252   
253def getBackhaulStatus():
254    """Returns a tuple describing the status of the backhaul link"""
255    global backhaul_ifname
256
257    nopeer = {"snr":0,"signal":0,"noise":0}
258   
259    if backhaul_ifname is None:
260        return None
261   
262    ifaces = getInterfaces(returnOne=backhaul_ifname)
263    if len(ifaces) != 1:
264        return ("critical", "Backhaul interface not found", nopeer)
265    iface = ifaces[0]
266    wiface = iwtools.get_interface(backhaul_ifname)
267   
268    # Check if the interface is configured on a ruralllink network
269    if wiface:
270        essid = wiface.get_essid()
271        if not essid.startswith(RL_ESSID_PREFIX):
272            return ("critical", "Not attached to RuralLink device", nopeer)
273        peer = essid.split("-")[1]
274        stats = wiface.get_stats()
275    else:
276        peer = ""
277        stats = nopeer
278
279    assoc = pref_get("rurallink", "backhaul_assoc", monitor_prefs, "")
280    if assoc=="" or assoc!=peer:
281        # No record of association
282        return ("warning", "%s - Not authenticated" % peer, stats) 
283   
284    return ("ok", "%s" % peer, stats)
285
286def retrieveMasterIP():
287    """Asks the parent node for the IP address of the Master node"""
288    global rl_masterip, monitor_prefs
289   
290    # Get default gateway's IP
291    gw = getGatewayIP()
292   
293    # Check connectivity
294    if not isHostUp(gw):
295        # Cannot associate now...
296        return True
297
298    try:
299        conn = httplib.HTTPConnection(gw)
300        conn.putrequest('GET', "/masterip")
301        conn.endheaders()
302        rv = conn.getresponse()
303        conn.close()
304    except:
305        (type, value, tb) = sys.exc_info()
306        log_error("Failed to connect to upstream server - %s" % value)
307        return None
308   
309    if rv.status == 200:
310        rl_masterip = rv.read()
311        pref_set("rurallink", "masterip", rl_masterip, monitor_prefs)
312        return rl_masterip
313
314    return None
315   
316def isMasterNode():
317    """Returns True if this node is configured to act as a Master
318
319    If there is no configuration information available we use a simple
320    heuristic: If there is only one wireless interface we are a Master.
321    """
322
323    is_master = config_getboolean("rurallink", "is_master", None)
324    if is_master is not None:
325        return is_master
326
327    return False
328
329def allocateChannel(ifname):
330    """Uses a heuristic to find the best channel to use for a new AP
331
332    - Checks for channel use on other interfaces and removes them
333    - Scans for other channels it can hear and removes them
334    -
335    - Uses 1,6,11 if available
336    - Otherwise picks a random channel out of the remaining list
337    """
338    global backhaul_ifname, channels
339   
340    achannels = channels.keys()
341   
342    # Remove channels in use on other wireless interfaces
343    if backhaul_ifname is not None:
344        wiface = iwtools.get_interface(backhaul_ifname)
345        if wiface:
346            channel = wiface.get_channel()
347            log_debug("Removing channel %d from list. In use on %s" % \
348                    (channel, backhaul_ifname))
349            achannels.pop(channel-1)
350
351    # Remove channels that we see in a scan - may not be able to scan if the
352    # interface doesn't yet exist...
353    wiface = iwtools.get_interface(ifname)
354    if wiface:
355        try:
356            essid = wiface.get_essid()
357            scanres = wiface.scanall()
358        except:
359            pass
360        n=0
361        for network in scanres:
362            # Skip the network on the interface we're allocating for
363            if network.essid == essid:
364                continue
365            log_debug("Removing channel %d from list. In use on network %s" % \
366                    (network.channel, network.essid))
367            achannels.pop(network.channel-1)
368   
369    log_debug("Available channels: %s" % achannels)
370    if 1 in achannels:
371        channel = 1
372    elif 6 in achannels:
373        channel = 6
374    elif 11 in achannels:
375        channel = 11
376    else:
377        random.seed()
378        channel = random.choice(achannels)
379    log_info("Allocating channel %s to %s" % (channel, ifname))
380
381    return channel
382
383def addRoute(network, mask, dest, dev):
384    """Adds a route to the route table if it does not already exist"""
385   
386    routes = getRoutes()
387    found = False
388    for route in routes:
389        if network=="default" and route["network"]== "0.0.0.0" and \
390                route["netmask"] == "0.0.0.0":
391            found = True
392            break
393        elif route["network"] == formatIP(network) and \
394                route["netmask"]==formatIP(bitsToNetmask(mask)):
395            found = True
396            break
397    if not found:
398        if network != "default":
399            cidr = "%s/%s" % (formatIP(network), mask)
400        else:
401            cidr = network
402        rv = log_command("/sbin/ip route add %s via %s dev %s 2>&1" % \
403                (cidr, dest, dev))
404        if rv != None:
405            return False
406
407    return True
408
409def delRoute(network, mask, dest, dev):
410    """Removes a route from the route table"""
411    if network != "default":
412        cidr = "%s/%s" % (formatIP(network), mask)
413    else:
414        cidr = network
415    rv = log_command("/sbin/ip route del %s via %s dev %s 2>&1" % \
416            (cidr, dest, dev))
417
418    return True
419
420def changeHostname(hostname):
421    """Changes the device's hostname"""
422    global ap0_ifname
423   
424    updateInterfaces = False
425   
426    # Check the hostname format is correct
427    if re.match(HOSTNAME_CHAR_RE, hostname) is None:
428        raise ccs_rurallink_error("Invalid character in hostname")
429   
430    hostname = hostname.lower()
431
432    # Update Access Point ESSID
433    if ap0_ifname is not None:
434        interfaces = debian_interfaces()
435        iface = interfaces[ap0_ifname]
436        iface["wireless_essid"] = "rl-%s-ap0" % hostname
437        interfaces[ap0_ifname] = iface
438        updateInterfaces = True
439       
440    # Save it
441    try:
442        remountrw("changeHostname")
443        fp = open("/etc/hostname", "w")
444        fp.write("%s\n" % hostname)
445        fp.close()
446        if updateInterfaces:
447            interfaces.write()
448    finally:
449        remountro("changeHostname")
450   
451    # Update running config
452    log_command("/bin/hostname -F /etc/hostname 2>&1")
453    if updateInterfaces:
454        log_command("/sbin/ifdown %s 2>&1; /sbin/ifup %s 2>&1" % \
455                (ap0_ifname, ap0_ifname))
456   
457    return True
458
459def retrieveNodemapFile(path):
460    """Retrieves the network nodemap from the master node"""
461    global rl_masterip
462    conn = None
463
464    try:
465        conn = httplib.HTTPConnection(rl_masterip)
466        conn.putrequest('GET', "/nodemap")
467        conn.endheaders()
468        rv = conn.getresponse()
469    except:
470        (type, value, tb) = sys.exc_info()
471        log_error("retrieveNodemapFile failed: %s" % value)
472        if conn is not None: conn.close()
473        return False
474
475    if rv.status == 200:
476        map = rv.read()
477        try:
478            fp = open(path, "w")
479        except IOError:
480            log_error("Could not open nodemap destination for writing!", \
481                    sys.exc_info())
482            return False
483        fp.write(map)
484        fp.close()
485        if conn is not None: conn.close()
486        return True
487
488    log_error("getNodeName received an invalid response from %s!" % ip)
489    if conn is not None: conn.close()
490    return False
491   
492def buildNodemapFile(path):
493    """Builds a nodemap suitable for use with the status graph"""
494    global rl_isptype, ap0_ifname
495   
496    try:
497        fp = open(path, "w")
498    except IOError:
499        log_error("Could not open nodemap destination for writing!", \
500                sys.exc_info())
501        return False
502
503    # Get ISP adddress - assume this is our default gateway
504    # XXX: Also assume that we're connected to our ISP within a /24. This
505    # assumption seems to hold for BCL...
506    ispaddr = "%s/24" % getGatewayIP()
507   
508    # Write out the "infrastructure bits"
509    fp.write("""# CRCnet Monitor Autogenerated Nodemap
510rurallink "Rural Link Server" 192.107.171.165/28
511%s "%s" %s 192.107.171.174/28
512""" % (rl_isptype, RL_ISPS[rl_isptype], ispaddr))
513
514    # Write out this (master node)
515    ispifname = getISPIfName()
516    hostname = socket.gethostname()
517    addresses = ""
518    for i in getInterfaces():
519        if i["name"] == ispifname:
520            # Hack the netmask to be a /24 rather than /32 so the map is happy
521            parts = i["address"].split("/")
522            addresses += "%s/24 " % (parts[0])
523        else:
524            addresses += "%s " % i["address"]
525    fp.write("""%s "%s (Master Node)" %s\n""" % (hostname, hostname, addresses))
526   
527    # Write out the clients that we have registered
528    if ap0_ifname is not None:
529        for client in getAssociatedClients(ap0_ifname):
530            fp.write(doNodemapClient(client))
531            # Check for subclients
532            for subclient in getAssociatedSubClients(client["ip"]):
533                fp.write(doNodemapClient(subclient))
534
535    # Close the file
536    fp.close()
537   
538    return True
539     
540def doNodemapClient(client):
541    """Returns a line suitable for addition to the nodemap
542
543    We include the client's name, the IP address they have configured for
544    backhaul and the IP address that they would use as a gateway for their
545    internal and AP networks.
546
547    The Internal and AP networks will only show on the status map if there are
548    clients registered on them.
549    """
550   
551    # Backhaul IP
552    addresses = "%s/%s " % (client["ip"], 32 - FUNCTION_SHIFT)
553   
554    # Internal Network
555    (network, bits) = generateRLNetwork(int(client["serialno"]), \
556            RL_INTERNAL)
557    netmask = bitsToNetmask(bits)
558    bcast = ipbroadcast(network, netmask)
559    addresses += "%s/%s " % (formatIP(bcast-1), bits)
560   
561    # AP0 Network
562    (network, bits) = generateRLNetwork(int(client["serialno"]), \
563            RL_AP0)
564    netmask = bitsToNetmask(bits)
565    bcast = ipbroadcast(network, netmask)
566    addresses += "%s/%s " % (formatIP(bcast-1), bits)
567   
568    # Return the line
569    return """%s "%s" %s\n""" % (client["name"], client["name"], addresses)
570
571def getNodeName(ip):
572    """Performs an HTTP request to determine the name of the device"""
573    conn = None
574   
575    try:
576        conn = httplib.HTTPConnection(ip)
577        conn.putrequest('GET', "/node_name")
578        conn.endheaders()
579        rv = conn.getresponse()
580    except:
581        (type, value, tb) = sys.exc_info()
582        log_error("getNodeName failed to connect to %s: %s" % (ip, value))
583        if conn is not None: conn.close()
584        return ""
585
586    if rv.status == 200:
587        name = rv.read()
588        if conn is not None: conn.close()
589        return name.strip()
590
591    log_error("getNodeName received an invalid response from %s!" % ip)
592    if conn is not None: conn.close()
593    return ""
594
595# Client Association Routines
596# - Clients are the devices directly connected to this AP
597###############################################################################
598def getAssociatedClients(ifname):
599    """Returns a list of associated clients"""
600    clients = []
601   
602    try:
603        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, ifname), "r")
604        lines = fp.readlines()
605        fp.close()
606    except IOError:
607        lines = []
608       
609    for line in lines:
610        parts = line.split()
611        clients.append({"serialno":parts[0], "name":parts[1], "ip":parts[2], \
612                "mac":parts[3]})
613
614    return clients
615   
616def isClientAssociated(client_serialno, client_name, client_ip):
617    """Returns true if the client has successfully associate before"""
618    global ap0_ifname
619    in_iface = ap0_ifname
620    if in_iface is None:
621        return False
622    mac = getNeighbourMAC(client_ip)
623    if mac == "":
624        return False
625   
626    try:
627        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, in_iface), "r")
628        lines = fp.readlines()
629        fp.close()
630    except IOError:
631        lines = []
632       
633    for line in lines:
634        parts = line.split()
635        if int(parts[0]) != client_serialno:
636            continue
637        if parts[2] != client_ip or parts[3] != mac:
638            return parts[2]
639        return parts[1]
640
641    return ""
642
643def updateClientAssociation(client_serialno, client_name, client_ip):
644    global ap0_ifname
645    in_iface = ap0_ifname
646    if in_iface is None:
647        return False
648    mac = getNeighbourMAC(client_ip)
649    if mac == "":
650        return False
651   
652    try:
653        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, in_iface), "r")
654        lines = fp.readlines()
655        fp.close()
656    except IOError:
657        lines = []
658    try:
659        fp = open("%s/%s.assoc.tmp" % (ASSOC_STORE_DIR, in_iface), "w")
660    except IOError:
661        log_error("Unable to update client association!", sys.exc_info())
662        return False
663   
664    updated=False
665    for line in lines:
666        parts = line.split()
667        if int(parts[0]) == client_serialno:
668            parts[1] = client_name
669            parts[2] = client_ip
670            parts[3] = mac
671            line = " ".join(parts)
672        fp.write(line)
673    fp.close()
674    os.system("mv %s/%s.assoc.tmp %s/%s.assoc" % \
675            (ASSOC_STORE_DIR, in_iface, ASSOC_STORE_DIR, in_iface))
676
677    return True
678
679def removeClientAssociation(client_serialno):
680    """Removes a particular client association from an interface"""
681    global ap0_ifname
682    in_iface = ap0_ifname
683    if in_iface is None:
684        return False
685
686    # Open association file and retrieve list of clients
687    try:
688        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, in_iface), "r")
689        lines = fp.readlines()
690        fp.close()
691    except IOError:
692        lines = []
693   
694    # Find the client association entry
695    client = None
696    n=0
697    for line in lines:
698        parts = line.split()
699        if int(parts[0]) == int(client_serialno):
700            client = parts
701            del lines[n]
702            break
703        n+=1
704
705    # Already disassociated
706    if client is None:
707        return True
708
709    # Remove static DHCP entry
710    del dhcp[client[3]]
711    # Remove routes
712    (network, mask) = generateRLNetwork(int(client[0]), RL_NODE)
713    delRoute(network, mask, client[2], iface)
714   
715    # Update the associations
716    try:
717        remountrw("removeClientAssociation")
718        fp = open("%s/%s.assoc.tmp" % (ASSOC_STORE_DIR, in_iface), "w")
719        for line in lines:
720            fp.write(line)
721        fp.close()
722        os.system("mv %s/%s.assoc.tmp %s/%s.assoc" % \
723                (ASSOC_STORE_DIR, in_iface, ASSOC_STORE_DIR, in_iface))
724        dhcp.write()
725        remountro("removeClientAssociation")
726    except IOError:
727        remountro("removeClientAssociation")
728        log_error("Unable to update client association!", sys.exc_info())
729        return False
730
731    # Tell our parent node this client is no longer registered with us
732    if not isMasterNode() and gw_ip != "":
733        name = socket.gethostname()
734        body = "password=%s;serialno=%s;client_serialno=%s" % \
735                (rl_password, rl_serialno, client_serialno)
736        conn = httplib.HTTPConnection(gw_ip)
737        conn.request("POST", "/unregister_subclient/%s" % name, body)
738        rv = conn.getresponse()
739        conn.close()       
740        if rv.status != 200:
741            log_error("Could not notify parent host of subclient dereg!")
742
743    # Check for subclients and remove if necessary
744    removeSubclientAssociations(client[2])
745   
746    # Restart DHCP and Reload Firewall
747    log_command("/etc/init.d/dhcp3-server restart 2>&1")
748    log_command("/etc/init.d/firewall start & 2>&1")
749   
750    return True
751
752def removeClientAssociations(iface=None):
753    """Removes all associated clients from an interface in one foul swoop"""
754   
755    global rl_masterip, rl_password, ap0_ifname
756   
757    if iface is None:
758        iface = ap0_ifname
759    if iface is None:
760        return False
761   
762    # Open association file and retrieve list of clients
763    try:
764        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, iface), "r")
765        lines = fp.readlines()
766        fp.close()
767    except IOError:
768        lines = []
769       
770    # Get DHCP and routing configuration details
771    dhcp = dhcpd_conf()
772
773    # Get rid of each clients association data
774    for line in lines:
775        parts = line.split()
776        # Remove static DHCP entry
777        del dhcp[parts[3]]
778        # Remove routes
779        (network, mask) = generateRLNetwork(int(parts[0]), RL_NODE)
780        delRoute(network, mask, parts[2], iface)
781
782    # Update the association and DHCP configuration
783    try:
784        remountrw("removeClientAssociations")
785        dhcp.write()
786        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, iface), "w")
787        fp.close()
788        remountro("removeClientAssociations")
789    except IOError:
790        remountro("removeClientAssociations")
791        log_error("Unable to remove all client associations on %s!" % iface, \
792                sys.exc_info())
793        return False
794
795    # Restart DHCP and Reload Firewall
796    log_command("/etc/init.d/dhcp3-server restart 2>&1")
797    log_command("/etc/init.d/firewall start & 2>&1")
798   
799    return True
800
801def setupClientAssociation(client_serialno, client_name, client_ip):
802    global rl_password, rl_masterip
803   
804    in_iface = getIfaceForIP(client_ip)
805    if in_iface == "":
806        return False
807    mac = getNeighbourMAC(client_ip)
808    if mac == None:
809        log_error("Could not determine client's MAC address!")
810        return False
811    gw_ip = getGatewayIP()
812   
813    try:
814        if not isClientAssociated(client_serialno, client_name, client_ip):
815            fp = open("%s/%s.assoc.tmp" % (ASSOC_STORE_DIR, in_iface), "a")
816            fp.write("%s %s %s %s\n" % \
817                    (client_serialno, client_name, client_ip, mac))
818            fp.close()
819            os.system("mv %s/%s.assoc.tmp %s/%s.assoc" % \
820                    (ASSOC_STORE_DIR, in_iface, ASSOC_STORE_DIR, in_iface))
821        else:
822            if not updateClientAssociation(client_serialno, client_name,\
823                    client_ip):
824                return False
825    except IOError:
826        log_error("Unable to setup client association!", sys.exc_info())
827        return False
828   
829    # Setup static DHCP entry
830    dhcp = dhcpd_conf()
831    dhcp[mac] = {"mac":mac, "fixed-address":client_ip,"name":client_name, \
832            "comment":"Static entry for associated device Serial #: %s" % \
833            client_serialno}
834    remountrw()
835    dhcp.write()
836    remountro()
837   
838    # POST serial/name to parent device
839    if not isMasterNode() and gw_ip != "":
840        name = socket.gethostname()
841        body = "password=%s;serialno=%s;client_name=%s;client_serialno=%s;" \
842                "client_ip=%s" % (rl_password, rl_serialno, client_name, \
843                client_serialno, client_ip)
844        conn = httplib.HTTPConnection(gw_ip)
845        conn.request("POST", "/register_subclient/%s" % name, body)
846        rv = conn.getresponse()
847        conn.close()       
848        if rv.status != 200:
849            log_error("Could not notify parent host of new client!")
850   
851    # add routes
852    (network, mask) = generateRLNetwork(int(client_serialno), RL_NODE)
853    if not addRoute(network, mask, client_ip, in_iface):
854        log_error("Failed to add client networks route for %s (%s)!" % \
855                (client_name, client_serialno))
856
857    # Restart DHCP and Reload Firewall
858    log_command("/etc/init.d/dhcp3-server restart 2>&1")
859    log_command("/etc/init.d/firewall start & 2>&1")
860
861    return True
862
863def registerClients():
864    """Register all our clients with the parent node.
865
866    Called when we've just associated successfully to the parent node
867    """
868    global rl_password, rl_serialno, ap0_ifname
869    iface = ap0_ifname
870    if iface is None:
871        return False
872
873    gw_ip = getGatewayIP()
874   
875    try:
876        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, in_iface), "r")
877        lines = fp.readlines()
878        fp.close()
879    except IOError:
880        lines = []
881       
882    for line in lines:
883        parts = line.split()
884        # Register this client with our parent node
885        if not isMasterNode() and gw_ip != "":
886            name = socket.gethostname()
887            body = "password=%s;serialno=%s;client_name=%s;client_serialno=%s;" \
888                    "client_ip=%s" % (rl_password, rl_serialno, parts[1], \
889                    parts[0], parts[2])
890            conn = httplib.HTTPConnection(gw_ip)
891            conn.request("POST", "/register_subclient/%s" % name, body)
892            rv = conn.getresponse()
893            conn.close()       
894            if rv.status != 200:
895                log_error("Could not notify parent host of new client!")
896        # Check if the client has any subclients and register them
897        registerSubClients(parts[2])
898   
899def initialiseClientRoutes():
900    """Setup routes to ensure that we can talk to all clients"""
901    global ap0_ifname
902    # XXX: There is only one interface that can handle clients atm
903    # XXX: But this will need to be extended in future if we have nodes with
904    # XXX: 2 APs
905    iface = ap0_ifname
906    if iface is None:
907        return False
908
909    try:
910        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, iface), "r")
911        lines = fp.readlines()
912        fp.close()
913    except IOError:
914        lines = []
915       
916    for line in lines:
917        parts = line.split()
918        (network, mask) = generateRLNetwork(int(parts[0]), RL_NODE)
919        if not addRoute(network, mask, parts[2], iface):
920            log_error("Failed to initialise client route for %s (%s)!" % \
921                    (parts[1], parts[0]))
922        initialiseSubClientRoutes(parts[2])
923   
924def resetNodeAssociations():
925    """Called when the node's password changes
926
927    - Tells the parent node that we're disassociating
928    - Removes all client associations
929    """
930    global rl_masterip, monitor_prefs
931   
932    name = socket.gethostname()
933
934    # Tell parent that we're disassociating
935    if not isMasterNode() and rl_masterip is not None:
936        body = "password=%s;serialno=%s" % \
937                (rl_password, client_serialno, client_ip)
938        conn = httplib.HTTPConnection(rl_masterip)
939        conn.request("POST", "/unregister_client/%s" % name, body)
940        rv = conn.getresponse()
941        conn.close()       
942        if rv.status != 200:
943            log_error("Could not notify parent that we were unregistering!")
944    # Mark backhaul as not associated
945    pref_set("rurallink", "backhaul_assoc", "", monitor_prefs)
946
947    # Disassociate all the clients
948    removeClientAssociations()
949
950# Sub client Association Routines
951# - Sub clients are the clients of this APs directly connected clients
952###############################################################################
953def getAssociatedSubClients(ip):
954    """Returns a list of associated sub clients"""
955    subclients = []
956   
957    try:
958        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, ip), "r")
959        lines = fp.readlines()
960        fp.close()
961    except IOError:
962        lines = []
963       
964    for line in lines:
965        parts = line.split()
966        subclients.append({"serialno":parts[0], "name":parts[1], \
967                "ip":parts[2]})
968
969    return subclients
970
971def isSubClientAssociated(client_ip, subclient_serialno, subclient_name, \
972        subclient_ip):
973    """Returns true if the subclient has been registered"""
974   
975    try:
976        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, client_ip), "r")
977        lines = fp.readlines()
978        fp.close()
979    except IOError:
980        lines = []
981       
982    for line in lines:
983        parts = line.split()
984        if int(parts[0]) != subclient_serialno:
985            continue
986        if parts[2] != subclient_ip:
987            return parts[2]
988        return parts[1]
989
990    return ""
991
992def updateSubClientAssociation(client_ip, subclient_serialno, \
993        subclient_name, subclient_ip):
994   
995    try:
996        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, client_ip), "r")
997        lines = fp.readlines()
998        fp.close()
999    except IOError:
1000        lines = []
1001    try:
1002        fp = open("%s/%s.assoc.tmp" % (ASSOC_STORE_DIR, client_ip), "w")
1003    except IOError:
1004        log_error("Unable to update subclient association!", sys.exc_info())
1005        return False
1006   
1007    updated=False
1008    for line in lines:
1009        parts = line.split()
1010        if int(parts[0]) == subclient_serialno:
1011            parts[1] = subclient_name
1012            parts[2] = subclient_ip
1013            line = " ".join(parts)
1014        fp.write(line)
1015    fp.close()
1016    os.system("mv %s/%s.assoc.tmp %s/%s.assoc" % \
1017            (ASSOC_STORE_DIR, client_ip, ASSOC_STORE_DIR, client_ip))
1018
1019    return True
1020
1021def removeSubClientAssociation(client_ip, subclient_serialno):
1022    """Removes a particular subclient association"""
1023    global rl_password, rl_serialno
1024   
1025    gw_ip = getGatewayIP()
1026
1027    # Open association file and retrieve list of subclients
1028    try:
1029        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, client_ip), "r")
1030        lines = fp.readlines()
1031        fp.close()
1032    except IOError:
1033        lines = []
1034   
1035    # Find the subclient association entry
1036    subclient = None
1037    n=0
1038    for line in lines:
1039        parts = line.split()
1040        if int(parts[0]) == int(subclient_serialno):
1041            subclient = parts
1042            del lines[n]
1043            break
1044        n+=1
1045
1046    # Already disassociated
1047    if subclient is None:
1048        return True
1049   
1050    iface = getIfaceForIP(client_ip)
1051    if iface == "":
1052        return False
1053   
1054    # Tell our parent node this client is no longer registered with us
1055    if not isMasterNode() and gw_ip != "":
1056        name = socket.gethostname()
1057        body = "password=%s;serialno=%s;client_serialno=%s" % \
1058            (rl_password, rl_serialno, client[0])
1059        conn = httplib.HTTPConnection(gw_ip)
1060        conn.request("POST", "/unregister_subclient/%s" % name, body)
1061        rv = conn.getresponse()
1062        conn.close()       
1063        if rv.status != 200:
1064            log_error("Parent notification failed: Subclient " \
1065                    "disassociation of %s (@ %s)!" % (client[0], client_ip))
1066
1067    # Remove routes
1068    (network, mask) = generateRLNetwork(int(subclient[0]), RL_NODE)
1069    delRoute(network, mask, client_ip, iface)
1070   
1071    return True
1072
1073def removeSubClientAssociations(client_ip):
1074    """Remove all subclients that were registered against the specified IP
1075
1076    Called when the node at the specified IP has disassociated
1077    """
1078    global rl_password, rl_serialno
1079   
1080    gw_ip = getGatewayIP()
1081   
1082    # Open association file and retrieve list of subclients
1083    try:
1084        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, client_ip), "r")
1085        lines = fp.readlines()
1086        fp.close()
1087    except IOError:
1088        lines = []
1089       
1090    # Remove the association file
1091    try:
1092        remountrw("removeSubClientAssociations")
1093        log_command("/bin/rm -f %s/%s.assoc 2>&1" % \
1094            (ASSOC_STORE_DIR, client_ip))
1095    finally:
1096        remountro("removeSubClientAssociations")
1097   
1098    # Go through each associated subclient and unregister it
1099    for line in lines:
1100        parts = line.split()
1101        (network, mask) = generateRLNetwork(int(parts[0]), RL_NODE)
1102        delRoute(network, mask, client_ip, iface)
1103        # Tell our parent node this client is no longer registered with us
1104        if not isMasterNode() and gw_ip != "":
1105            name = socket.gethostname()
1106            body = "password=%s;serialno=%s;client_serialno=%s" % \
1107                (rl_password, rl_serialno, parts[0])
1108            conn = httplib.HTTPConnection(gw_ip)
1109            conn.request("POST", "/unregister_subclient/%s" % name, body)
1110            rv = conn.getresponse()
1111            conn.close()       
1112            if rv.status != 200:
1113                log_error("Parent notification failed: Subclient " \
1114                        "disassociation of %s (@ %s)!" % (parts[0], client_ip))
1115       
1116   
1117def setupSubClientAssociation(client_ip, subclient_serialno, \
1118        subclient_name, subclient_ip):
1119    global rl_password
1120   
1121    iface = getIfaceForIP(client_ip)
1122    if iface == "":
1123        return False
1124    gw_ip = getGatewayIP()
1125       
1126    try:
1127        if not isSubClientAssociated(client_ip, subclient_serialno, \
1128                subclient_name, subclient_ip):
1129            fp = open("%s/%s.assoc.tmp" % (ASSOC_STORE_DIR, client_ip), "a")
1130            fp.write("%s %s %s\n" % \
1131                    (subclient_serialno, subclient_name, subclient_ip))
1132            fp.close()
1133            os.system("mv %s/%s.assoc.tmp %s/%s.assoc" % \
1134                    (ASSOC_STORE_DIR, client_ip, ASSOC_STORE_DIR, client_ip))
1135        else:
1136            if not updateSubClientAssociation(client_ip, subclient_serialno, \
1137                    subclient_name, subclient_ip):
1138                return False
1139    except IOError:
1140        log_error("Unable to setup subclient association!", sys.exc_info())
1141        return False
1142   
1143    # POST serial/name to parent device
1144    if not isMasterNode() and gw_ip != "":
1145        name = socket.gethostname()
1146        body = "password=%s;serialno=%s;client_name=%s;client_serialno=%s;" \
1147                "client_ip=%s" % (rl_password, rl_serialno, subclient_name, \
1148                subclient_serialno, subclient_ip)
1149        conn = httplib.HTTPConnection(gw_ip)
1150        conn.request("POST", "/register_subclient/%s" % name, body)
1151        rv = conn.getresponse()
1152        conn.close()       
1153        if rv.status != 200:
1154            log_error("Could not notify parent host of new node!")
1155   
1156    # add routes
1157    (network, mask) = generateRLNetwork(int(subclient_serialno), RL_NODE)
1158    if not addRoute(network, mask, client_ip, iface):
1159        log_error("Failed to add subclient networks route for %s (%s)!" % \
1160                (subclient_name, subclient_serialno))
1161
1162    return True
1163
1164def registerSubClients(client_ip):
1165    """Register all the subclients of a client with the parent node.
1166
1167    Called when we've just associated successfully to the parent node
1168    """
1169    global rl_password, rl_serialno, ap0_ifname
1170
1171    gw_ip = getGatewayIP()
1172   
1173    try:
1174        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, client_ip), "r")
1175        lines = fp.readlines()
1176        fp.close()
1177    except IOError:
1178        lines = []
1179       
1180    for line in lines:
1181        parts = line.split()
1182        # Register this subclient with our parent node
1183        if not isMasterNode() and gw_ip != "":
1184            name = socket.gethostname()
1185            body = "password=%s;serialno=%s;client_name=%s;client_serialno=%s;" \
1186                    "client_ip=%s" % (rl_password, rl_serialno, parts[1], \
1187                    parts[0], parts[2])
1188            conn = httplib.HTTPConnection(gw_ip)
1189            conn.request("POST", "/register_subclient/%s" % name, body)
1190            rv = conn.getresponse()
1191            conn.close()       
1192            if rv.status != 200:
1193                log_error("Could not notify parent host of new subclient!")
1194       
1195    return ""
1196
1197def initialiseSubClientRoutes(client_ip):
1198    """Setup routes to ensure that we can talk to all subclients"""
1199
1200    try:
1201        fp = open("%s/%s.assoc" % (ASSOC_STORE_DIR, client_ip), "r")
1202        lines = fp.readlines()
1203        fp.close()
1204    except IOError:
1205        lines = []
1206       
1207    for line in lines:
1208        parts = line.split()
1209        (network, mask) = generateRLNetwork(int(parts[0]), RL_NODE)
1210        if not addRoute(network, mask, parts[2], iface):
1211            log_error("Failed to initialise subclient route for %s (%s)!" % \
1212                    (parts[1], parts[0]))
1213
1214# Traffic Management
1215##############################################################################
1216def readTrafficUsers():
1217    """Loads the list of users from the password file"""
1218
1219    passwdfile =  config_get("traffic", "passwd_file", \
1220            DEFAULT_TRAFFIC_PASSWD_FILE)
1221   
1222    users = {}
1223   
1224    fp = open(passwdfile, "r")
1225    i=0
1226    for line in fp.readlines():
1227        parts = line.strip().split(":")
1228        i+=1
1229        if (len(parts)!=3):
1230            log_warning("Skipping invalid line (%d) in password file!" % i)
1231            continue
1232        if parts[2]=="t":
1233            s = True
1234        else:
1235            s = False
1236        # Store the user
1237        users[parts[0]] = {"username":parts[0],"passwd":parts[1],"enabled":s}
1238    fp.close()
1239
1240    return users
1241       
1242def writeTrafficUsers():
1243    """Writes the list of users from the array to the password file"""
1244    global rl_trafficusers
1245   
1246    passwdfile =  config_get("traffic", "passwd_file", \
1247            DEFAULT_TRAFFIC_PASSWD_FILE)
1248   
1249    try:
1250        remountrw()
1251        fp = open(passwdfile, "w")
1252        for username, user in rl_trafficusers.items():
1253            line = "%s:%s:%s\n" % (username, user["passwd"], \
1254                    user["enabled"] and "t" or "f")
1255            fp.write(line)
1256        fp.close()
1257    finally:
1258        remountro()
1259       
1260def checkFirewall():
1261    """Checks that the firewall is correctly loaded and running"""
1262
1263    fw_start = """<a href="/traffic/firewall/start">[Start Firewall]</a>"""
1264    fw_stop = """<a href="/traffic/firewall/stop">[Stop Firewall]</a>"""
1265   
1266    # Check for the fw-in, fw-out, fw-forward chains in the filter table
1267    # if these are loaded we assume that the firewall script has been started
1268    if getChainRules("fw-in") is None:
1269        return ("critical", "Firewall is not running", fw_start)
1270    if getChainRules("fw-out") is None:
1271        return ("critical", "Firewall is not running", fw_start)
1272    if getChainRules("fw-forward") is None:
1273        return ("critical", "Firewall is not running", fw_start)
1274
1275    # Seems ok
1276    return ("ok", "Firewall running", fw_stop)
1277
1278def doFirewall(mode):
1279    """Sets the firewall to the specified mode"""
1280
1281    if mode not in ["start", "stop", "reload"]:
1282        raise ccs_rurallink_error("Unknown firewall mode!")
1283    if mode == "reload":
1284        mode = "start"
1285   
1286    # Try and do it
1287    if log_command("/etc/init.d/firewall %s 2>&1" % mode) == None:
1288        return True
1289
1290    return False
1291   
1292def isAllowed(ip_address):
1293    """Returns true if the captive portal is currently allowing the IP"""
1294
1295    # Find which interface the user will be coming in from
1296    iface = getIfaceForIP(ip_address)
1297   
1298    # Check the the necessary pre-routing chain exists and is setup for
1299    # captive-portalling
1300    rules = getChainRules("%s-prerouting-in" % iface, "nat") 
1301    if rules is None:
1302        log_warn("Cannot check status of %s (%s). %s-prerouting-in is bad!" % \
1303                (ip_address, username, iface))
1304        return False
1305   
1306    # Look for the IP in the source field
1307    for rule in rules:
1308        if rule["source"] == ip_address and rule["target"]=="ACCEPT":
1309            return True
1310       
1311    # Not found
1312    return False
1313
1314def removeAllowRule(ip_address):
1315    """Removes an IP address from the captive portals allow list"""
1316    global rl_trafficusers
1317   
1318    # Do final traffic accounting
1319    updatetraffic()
1320   
1321    # Find which interface the user will be coming in from
1322    iface = getIfaceForIP(ip_address)
1323
1324    # Remove from pre-routing
1325    log_command("/sbin/iptables -D %s-prerouting-in -t nat -s %s/32 " \
1326                "-j ACCEPT 2>&1" % (iface, ip_address))
1327
1328    # Remove from forward
1329    log_command("/sbin/iptables -D %s-forward-in -s %s/32 " \
1330                "-j fw-forward-out 2>&1" % (iface, ip_address))
1331
1332    # Remove from accounting
1333    log_command("/sbin/iptables -D accounting -s %s/32 -j RETURN 2>&1" % \
1334        ip_address)
1335    log_command("/sbin/iptables -D accounting -d %s/32 -j RETURN 2>&1" % \
1336        ip_address)
1337   
1338    # Remove from user record
1339    for username, user in rl_trafficusers.items():
1340        if user["ip"] == ip_address:
1341            user["ip"] = None
1342   
1343    log_info("Internet Access disabled for %s" % (ip_address))
1344    return True
1345
1346def addAllowRule(ip_address, username):
1347    """Adds the specified IP address to teh captive portals allow list
1348
1349    This is called once the user has authenticated to allow them to access the
1350    Internet.
1351    """
1352    global rl_trafficusers
1353
1354    # Check whether the user is already logged in from another IP and
1355    # log them out if they are
1356    if rl_trafficusers[username]["ip"] is not None:
1357        removeAllowRule(ip_address)
1358       
1359    # Find which interface the user will be coming in from
1360    iface = getIfaceForIP(ip_address)
1361
1362    # Check the the necessary pre-routing chain exists and is setup for
1363    # captive-portalling
1364    rules = getChainRules("%s-prerouting-in" % iface, "nat") 
1365    if rules is None:
1366        log_warn("Cannot allow %s (%s) on %s. No chain!" % \
1367                (ip_address, username, iface))
1368        return False
1369
1370    # Last rule will be a redirect if we are captive portalling
1371    if rules[len(rules)-1]["target"] != "REDIRECT":
1372        log_error("Cannot allow %s (%s) on %s. No redirect rule!" % \
1373                (ip_address, username, iface))
1374        return False
1375   
1376    # The first three rules should be RFC1918 allows
1377    for i in range(0,3):
1378        if rules[i]["target"] != "ACCEPT":
1379            log_error("Cannot allow %s (%s) on %s. Missing ACCEPT on a " \
1380                    "RFC1918 rule!" % (ip_address, username, iface))
1381            return False
1382        if rules[i]["destination"] not in RFC1918:
1383            log_error("Cannot allow %s (%s) on %s. Initial rule (%s)" \
1384                    "is not RFC1918!" % \
1385                    (ip_address, username, iface, rules[i]["destination"]))
1386            return False
1387
1388    # All good, insert into position 4 in the chain, ie. after RFC1918 rules
1389    if log_command ("/sbin/iptables -I %s-prerouting-in 4 -t nat -s %s/32 " \
1390            "-j ACCEPT 2>&1" % (iface, ip_address)) != None:
1391        # Failed to add rule
1392        log_error("Failed to add allow rule for %s (%s) on %s-prerouting!" % \
1393                (ip_address, username, iface))
1394        return False
1395   
1396    # And the same for the forward chain at postion 5 as there is an extra
1397    # macfilter rule at the start of this chain
1398    if log_command("/sbin/iptables -I %s-forward-in 5 -s %s/32 " \
1399            "-j fw-forward-out 2>&1" % (iface, ip_address)) != None:
1400        # Failed to add rule
1401        log_error("Failed to add allow rule for %s (%s) on %s-forward!" % \
1402                (ip_address, username, iface))
1403        # Try and re-enable the captive portal for the user!
1404        log_command("/sbin/iptables -D %s-prerouting-in -t nat -s %s/32 " \
1405                "-j ACCEPT &>/dev/null 2>&1" % (iface, ip_address))
1406        return False
1407
1408    # Setup accounting entries
1409    if log_command("/sbin/iptables -A accounting -s %s/32 -j RETURN && " \
1410            "/sbin/iptables -A accounting -d %s/32 -j RETURN 2>&1" % \
1411            (ip_address, ip_address)) != None:
1412        # Failed to add rule
1413        log_warn("Failed to initialise accounting for %s @ %s!" % \
1414                (username, ip_address))
1415       
1416    # Record IP against this user for accounting
1417    rl_trafficusers[username]["ip"] = ip_address
1418   
1419    log_info("Internet Access enabled for %s @ %s" % (username, ip_address))
1420    return True
1421
1422@registerRecurring(TRAFFIC_UPDATE_INTERVAL)
1423def updatetraffic():
1424    """Called regularly to read the appropriate traffic information"""
1425    global rl_trafficusers
1426   
1427    ifaces = getInterfaceNames()
1428   
1429    # Check for host specific rules in the accounting chain
1430    rules = getChainRules("accounting")
1431    if rules is None:
1432        return
1433    # Collect stats
1434    for rule in rules:
1435        # Can't do any traffic if you were denied!
1436        if rule["target"] != "RETURN":
1437            continue
1438        # Skip non-host rules
1439        source = rule["source"].split("/")[0]
1440        dest = rule["destination"].split("/")[0]
1441        if rule["source"].find("/") != -1 and \
1442                cidrToLength(rule["source"])!=32:
1443            if rule["destination"].find("/") != -1 and \
1444                    cidrToLength(rule["destination"])!=32:
1445                # Neither source or destination are a host
1446                continue
1447            else:
1448                ip_address = dest
1449        else:
1450            ip_address = source
1451        # Try and find the user associated with this host
1452        for username, user in rl_trafficusers.items():
1453            if user["ip"] == ip_address:
1454                user["traffic"] += int(rule["bytes"])
1455                break
1456
1457    # Zero the chain
1458    os.system("/sbin/iptables -Z accounting")
1459       
1460class CaptivePortalHandler(BaseHTTPRequestHandler):
1461
1462    pages = {}
1463    dirs = {}
1464    realms = {}
1465   
1466    def __init__(self, request, client_address, server):
1467        self.cookies = {}
1468        self.cookie_names = []
1469        self.username = ""
1470        # Call base handler
1471        BaseHTTPRequestHandler.__init__(self, request, client_address, server)
1472
1473    def findIfaceIP(self, client_ip):
1474        """Returns the IP address of the interface this client uses
1475
1476        We assume that we don't have assymetric routing and that the iface
1477        that we would talk to the client on is the same as the packet is
1478        received from.
1479        """
1480   
1481        ip = getIfaceIPForIP(client_ip)
1482        if ip == "":
1483            # Uh-oh, just return HTTP Host!
1484            return self.headers.get("Host").strip()
1485   
1486        return ip
1487
1488    def getPostData(self):
1489        """Returns a dictionary of posted variables"""
1490
1491        vars = {}
1492
1493        data = self.rfile.read(int(self.headers["content-length"]))
1494        for pair in data.split("&"):
1495            parts = pair.split("=")
1496            if len(parts)<2:
1497                continue
1498            vars[parts[0]] = parts[1]
1499
1500        return vars
1501               
1502    def authSuccess(self, url):
1503
1504        host = self.headers.get("Host").strip()
1505        output = """<div class="content"><h2>Access Granted</h2>
1506<br />
1507Thank you, your username and password have been accepted and you now have
1508access to the Internet.<br />
1509<br />
1510<b>NOTE:You must return to this website (<a href="http://%s/">http://%s/</a>)
1511when you have finished using the computer to end your Internet session. If you
1512do not end your Internet session you may be liable for all bandwidth charges
1513incurred until your session is ended.</b><br />
1514<br />
1515<br />
1516<a href="%s">Click here to continue on to %s &gt;&gt;</a><br />
1517<br />
1518""" % (host, host, url, url)
1519
1520        returnPage(self, "Access Granted", output)
1521       
1522    def logoutSuccess(self):
1523
1524        host = self.headers.get("Host").strip()
1525        output = """<div class="content"><h2>Session Ended</h2>
1526<br />
1527Thank you, your Internet session has been ended. You may now close the
1528web browser.<br />
1529<br />
1530"""
1531
1532        returnPage(self, "Session Ended", output)
1533
1534    def authRequired(self, message, back):
1535   
1536        if message != "":
1537            message = "<br /><span class=\"error\"><b>ERROR:</b> %s</span>" \
1538                    "<br />" % message
1539
1540        # Determine the captive-portal host the user should submit to
1541        url = "http://%s:%s" % \
1542                (self.findIfaceIP(self.client_address[0]), \
1543                self.server.server_port)
1544       
1545        output = """<div class="content"><h2>Authentication Required</h2>
1546<br />
1547Authentication is required before you can access the Internet. Please enter
1548your username and password below.<br />
1549%s
1550<br />
1551<form method="POST" action="%s/captive-portal">
1552<input type="hidden" name="back" value="%s" />
1553<table>
1554<tr>
1555<th width="20%%">Username</th>
1556<td><input type="input" value="" name="username"></td>
1557</tr>
1558<tr>
1559<th width="20%%">Password</th>
1560<td><input type="password" value="" name="password"></td>
1561</tr>
1562<tr>
1563<td>&nbsp;</td>
1564<td><input type="submit" value="Enable Internet Access &gt;&gt;"></td>
1565</tr>
1566</table>
1567""" % (message, url, back)
1568
1569        returnPage(self, "Authentication Required", output)
1570       
1571    def handleAuthenticated(self):
1572        global rl_trafficusers
1573
1574        # Determine the captive-portal host the user should submit to
1575        url = "http://%s:%s" % \
1576                (self.findIfaceIP(self.client_address[0]), \
1577                self.server.server_port)
1578
1579        # How much traffic has the user done?
1580        datastr = "an unknown amount"
1581        for username, user in rl_trafficusers.items():
1582            if user["ip"] != self.client_address[0]:
1583                continue
1584            # Found user
1585            datastr = formatBytes(user["traffic"])
1586            break
1587       
1588        output = """<div class="content"><h2>Session Active</h2>
1589<br />
1590Your Internet session is currently active and you should have no problems
1591browsing websites.<br />
1592<br />
1593You have transferred <b>%s</b> of data.<br />
1594<br />
1595If you would like to end your Internet session please click the button below.
1596<br /><br />
1597<form method="POST" action="%s/captive-portal">
1598<input type="hidden" name="logout" value="yes" />
1599<table>
1600<tr>
1601<td>&nbsp;</td>
1602<td><input type="submit" value="Logout (End Internet Session) &gt;&gt;"></td>
1603</tr>
1604</table>
1605""" % (datastr, url)
1606
1607        returnPage(self, "Authentication Required", output)
1608       
1609    def handleRequest(self, method):
1610        """Looks to see if we know how to handle the path and passes it off"""
1611        global rl_trafficusers
1612       
1613        # Handle static directories from the real webserver
1614        #########################################################
1615        dirname = os.path.dirname(self.path)
1616        if dirname in CCSDHandler.dirs.keys():
1617            # Try and send the file back
1618            filename = os.path.basename(self.path)
1619            data = ""
1620            try:
1621                fp = open("%s/%s" % (CCSDHandler.dirs[dirname], filename), "r")
1622                data = fp.read()
1623                fp.close()
1624            except:
1625                self.send_error(404, "The specified resource does not exist!")
1626                return
1627            length = len(data)
1628            self.send_response(200)
1629            self.send_header("Length", length)
1630            self.end_headers()
1631            self.wfile.write(data)
1632            return
1633       
1634        # Handled POSTed authentication data
1635        #########################################################
1636        if self.path.startswith("/captive-portal") and method == "POST":
1637            data = self.getPostData()
1638            # Logout
1639            if "logout" in data.keys() and data["logout"]=="yes":
1640                removeAllowRule(self.client_address[0])
1641                self.logoutSuccess()
1642                return
1643            # Verify authentication
1644            back = ""
1645            if "back" in data.keys():
1646                back = urllib.unquote(data["back"])
1647            if "username" not in data.keys() or "password" not in data.keys():
1648                # No auth data present
1649                self.authRequired("Invalid authentication request!", back)
1650                return
1651            if data["username"] not in rl_trafficusers.keys():
1652                # Invalid Username
1653                self.authRequired("Invalid username or password!", back)
1654                return
1655            passwd = rl_trafficusers[data["username"]]["passwd"]
1656            if crypt.crypt(data["password"], passwd) != passwd:
1657                # Invalid password
1658                self.authRequired("Invalid username or password!", back)
1659                return
1660            if not rl_trafficusers[data["username"]]["enabled"]:
1661                # Account disabled
1662                self.authRequired("Your account is disabled!", back)
1663                return
1664           
1665            # Success - Enable Firewall
1666            if not addAllowRule(self.client_address[0], data["username"]):
1667                self.authRequired("An error occured while trying to " \
1668                        "enable your Internet access. Please try again " \
1669                        "shortly.", "")
1670                return
1671           
1672            # Tell the user
1673            if back == "": back = "http://www.google.com/"
1674            self.authSuccess(back)
1675            return
1676
1677        # Check for an already authenticated user
1678        #########################################################
1679        if isAllowed(self.client_address[0]):
1680            self.handleAuthenticated()
1681            return
1682
1683        # Send the Login Required form
1684        #########################################################
1685        back = ""
1686        if not self.path.startswith("/captive-portal"):
1687            back = "http://%s%s" % \
1688                    (self.headers.get("Host").strip(), self.path)
1689        self.authRequired("", back)
1690        return
1691   
1692    def do_GET(self):
1693        return self.handleRequest("GET")
1694    def do_POST(self):
1695        return self.handleRequest("POST")
1696           
1697    # No logging for now
1698    def log_error(self, *args):
1699        pass
1700    def log_request(self, code='-', size='-'):
1701        pass
1702
1703def getChainRules(chain, table="filter"):
1704    """Returns all the rules for the specified chain in the table"""
1705
1706    rules = []
1707   
1708    fh = os.popen("/sbin/iptables --line-numbers -nxvL %s -t %s" % \
1709            (chain, table))
1710    output = fh.readlines()
1711    rv = fh.close()
1712    if rv != None :
1713        output = "".join(output)
1714        if len(output) > 0: log_debug("iptables output:\n%s" % output)
1715        # Chain probably doesn't exit
1716        return None
1717
1718    # Parse each line and store it, first line is chain name
1719    names = output[1].split() # second line is headers
1720    names.append("extra")
1721    for line in output[2:]: # data starts on the third line
1722        parts = line.split()
1723        if len(parts) > len(names):
1724            pmax = len(names)-1
1725            parts[pmax] = " ".join(parts[pmax:])
1726            parts = parts[:len(names)]
1727        obj = dict(zip(names, parts))
1728        rules.append(obj)
1729
1730    return rules
1731
1732# ISP
1733##############################################################################
1734def checkBCLPeerFile():
1735    """Checks whether the PPP peers file for the BCL connection exists.
1736
1737    If it does not it is created from a default.
1738    """
1739
1740    if os.path.exists(BCL_CONF):
1741        return True
1742
1743    # Need to create the default
1744    try:
1745        remountrw("checkBCLPeerFile")
1746
1747        fp = open(BCL_CONF, "w")
1748        fp.write("""# CRCnet-monitor created PPPoE configuration for BCL
1749#       
1750# See the manual page pppd(8) for information on all the options.
1751#
1752
1753# The BCL module of crcnet-monitor will keep this line up to date
1754user default
1755
1756# PPPoE
1757pty "/usr/sbin/pppoe -I int -T 80 -m 1452"
1758
1759# Connection Settings
1760noipdefault
1761usepeerdns
1762defaultroute
1763
1764# Default PPP settings
1765hide-password
1766lcp-echo-interval 20
1767lcp-echo-failure 3
1768connect /bin/true
1769noauth
1770persist
1771maxfail 0
1772holdoff 5
1773mtu 1492
1774noaccomp
1775default-asyncmap
1776""")
1777        fp.close()
1778       
1779    finally:
1780        remountro("checkBCLPeerFile")
1781       
1782    # Check whether we succeeded or not
1783    if os.path.exists(BCL_CONF):
1784        return True
1785    return False
1786
1787def updateBCL(user, passwd):
1788    # Do filesystem manipulation
1789    try:
1790        # Remount RW so we can write to files
1791        remountrw("updateBCL")
1792        # Check whether the files exists
1793        checkBCLPeerFile()
1794        ensureFileExists(PAP_SECRETS)
1795        # Update the username in the PPP options file
1796        if not filterFile("^user.*$", "user %s" % user, BCL_CONF):
1797            raise ccs_rurallink_error("Unable to set username!")
1798        # Update the username / password in pap-secrets
1799        if not filterFile("^\"%s\".*$" % user, "\"%s\"\t*\t\"%s\"" % \
1800                (user, passwd), PAP_SECRETS):
1801            # Couldn't replace username/password, append new
1802            # XXX: This will leave the old username/password in the
1803            # pap-secrets file
1804            if not appendFile("\"%s\"\t*\t\"%s\"\n" % \
1805                    (user, passwd), PAP_SECRETS):
1806                raise ccs_rurallink_error("Unable to set password!")
1807        # Ensure that there is an interfaces file entry for the BCL connection
1808        ifaces = debian_interfaces()
1809        ifaces[ISP_BCL] = {"name":ISP_BCL, "family":"inet", "method":"ppp", \
1810                "auto":True, "provider":ISP_BCL}
1811        ifaces.write()
1812    finally:
1813        # Make sure we don't leave the FS read-write
1814        remountro("updateBCL")
1815
1816    # Success
1817    return True
1818
1819def getBCLUser():
1820    """Returns the username that is currently configured for BCL access"""
1821
1822    # Open file and read all lines
1823    try:
1824        fp = open(BCL_CONF, "r")
1825    except:
1826        fp = False
1827    if not fp:
1828        return "<i>No username configured</i>"
1829    lines = fp.readlines()
1830    fp.close()
1831   
1832    # Find the user line
1833    for line in lines:
1834        if re.match("^user.*$", line) is None:
1835            continue
1836        # User line found
1837        parts = line.split()
1838        if len(parts) != 2:
1839            return "<i>Unable to read username</i>"
1840        return parts[1]
1841       
1842    return "<i>No username configured</i>"
1843
1844def connectBCL():
1845    """Tried to bring up the BCL internet connection"""
1846   
1847    ifname = getISPIfName()
1848    if log_command("/sbin/ifup %s 2>&1" % ifname) is None:
1849        return "warning", \
1850            "Connecting... <span id=\"rem\">30</span>s remaining.", \
1851            ISP_IF_RELOAD
1852    else:
1853        return "critical", "BCL connection attempt failed.", ISP_IF_CONNECT
1854   
1855def disconnectBCL():
1856    """Tries to bring down the BCL internet connection"""
1857   
1858    ifname = getISPIfName()
1859    if log_command("/sbin/ifdown %s 2>&1; poff 2>&1" % ifname) is None:
1860        return "ok", \
1861            "Disconnecting... <span id=\"rem\">30</span>s remaining.", \
1862            ISP_IF_RELOAD
1863    else:
1864        stat = getISPStatus()
1865        return stat[2:]
1866
1867def getBCLStatus():
1868    """Returns the status of the BCL internet connection"""
1869   
1870    gwstatus, gwtext = getISPGatewayStatus()
1871    ifname = getISPIfName()
1872
1873    if gwstatus == "ok":
1874        ifstatus = "ok"
1875        iftext = "Connected"
1876        ifaction = ISP_IF_DISCONNECT
1877    else:
1878        # Default gateway is not ok. Iface probably isn't either
1879        ifaces = getInterfaces(returnOne=ifname)
1880        ifstatus = ""
1881        for iface in ifaces:
1882            if iface["name"] != ifname:
1883                continue
1884            # ISP Interface
1885            if iface["up"]:
1886                ifstatus = "ok"
1887                iftext = "Connected - %s" % iface["address"]
1888                ifaction = ISP_IF_DISCONNECT
1889            else:
1890                ifstatus = "warning"
1891                iftext = "Disconnected"
1892                ifaction = ISP_IF_CONNECT
1893            break
1894        if ifstatus == "":
1895            ifstatus = "critical"
1896            iftext = "Disconnected"
1897            ifaction = ISP_IF_CONNECT
1898
1899    return (gwstatus, gwtext, ifstatus, iftext, ifaction)
1900
1901def getDSLAdminPassword():
1902    """Returns the admin password for a DSL device connected to this CPE
1903       
1904    The admin password is the last 8 characters of an MD5 hash generated from
1905    the mac address of the internal interface of this CPE.
1906    """
1907    global int_ifname
1908   
1909    if int_ifname is None:
1910        raise ccs_rurallink_error("DSL password cannot be generated " \
1911            "without an internal interface!")
1912    ifaces = getInterfaces(returnOne=int_ifname)
1913    if len(ifaces) != 1:
1914        raise ccs_rurallink_error("Could not find internal interface to " \
1915            "generate DSL password from!")
1916    mac = ifaces[0]["mac"].lower()
1917    return md5(mac).hexdigest()[-8:]
1918   
1919def DSLLogin():
1920    """Attempts to login to the DSL modem via telnet
1921
1922    Tries the password that a modem attached to this device should be using
1923    before falling back to the default admin/admin
1924    """
1925   
1926    # Possible return strings from a login attempt
1927    login_regexps = ["login:", "\$"]
1928
1929    if not isHostUp(DSL_MODEM_IP):
1930        raise ccs_rurallink_error("Unable to contact DSL modem!")
1931   
1932    conn = Telnet(DSL_MODEM_IP)
1933   
1934    # Wait for the login prompt
1935    r = conn.read_until("login:", DSL_TELNET_TIMEOUT)
1936    if not r.endswith("login:"):
1937        log_debug("DSL Modem: Unexpected string upon connect\n%s" % r)
1938        conn.close()
1939        raise ccs_rurallink_error("Could not login to DSL modem. " \
1940            "Unexpected string!")
1941   
1942    for password in [getDSLAdminPassword(), "admin"]:
1943        # Send the admin username
1944        conn.write("admin\n")
1945       
1946        # Wait for the password prompt
1947        r = conn.read_until("password:", DSL_TELNET_TIMEOUT)
1948        if not r.endswith("password:"):
1949            log_debug("DSL Modem: Unexpected string after sending " \
1950                "username\n%s" % r)
1951            conn.close()
1952            raise ccs_rurallink_error("Could not login to DSL modem. " \
1953                "Unexpected string!")
1954       
1955        # Send the password
1956        conn.write("%s\n" % password)
1957       
1958        # Wait for the appropriate prompt
1959        index, match, text = conn.expect(login_regexps, DSL_TELNET_TIMEOUT)
1960        if index == -1:
1961            log_debug("DSL Modem: Unexpected error after sending " \
1962                "password\n%s" % text)
1963            conn.close()
1964            raise ccs_rurallink_error("Could not login to DSL modem. " \
1965                "Unexpected string!")
1966        if index == 1:
1967            # Logged in successfully
1968            return conn
1969       
1970    # Could not login
1971    conn.close()
1972    return None
1973   
1974def updateDSL(username, password):
1975    """Connects to the DSL modem and updates the username/password
1976
1977    Currently we only support the D-Link DSL-504T and DSL-302G modems
1978    and we assume they are in their factory default configuration before
1979    we talk to them. E.g. The modem is at 10.1.1.1
1980    """
1981   
1982    # Try and configure the DSL modem if necessary
1983    if not DSLModemConfigured():
1984        configureDSLModem()
1985
1986    conn = DSLLogin()
1987    if conn is None:
1988        raise ccs_rurallink_error("Unable to login to DSL modem!")
1989     
1990    # Update the username and password
1991    conn.write("modify ppp security ifname ppp-0 login %s passwd %s\n" % \
1992        (username, password))
1993    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
1994
1995    if r.find("Set Done") == -1:
1996        conn.close()
1997        raise ccs_rurallink_error("Failed to update modem auth details!")         
1998    # Commit the changes
1999    conn.write("commit\n")
2000    conn.close()
2001   
2002    return True
2003
2004def getDSLUser():
2005    """Retrieves the currently configured user on the DSL modem"""
2006   
2007    conn = None
2008    try:
2009        conn = DSLLogin()
2010    except:
2011        pass
2012    if conn is None:
2013        return "<i>Could not connect to DSL modem</i>"
2014
2015    # Ask for the auth credentials
2016    conn.write("get ppp security ifname ppp-0\n")
2017   
2018    # Wait for the prompt
2019    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2020    conn.close()
2021   
2022    # Try and extract the login name out of the response
2023    lines = r.strip().split("\n")
2024    for line in lines:
2025        if line.strip().startswith("Login"):
2026            try:
2027                return line.split(":")[1].strip()
2028            except:
2029                return "<i>Unable to read username</i>"
2030               
2031    # Not found
2032    return "<i>No username configured</i>"
2033   
2034def getDSLNameservers():
2035    """Retrieves the ISP allocated nameservers"""
2036    dns1 = None
2037    dns2 = None
2038    conn = None
2039    try:
2040        conn = DSLLogin()
2041    except:
2042        pass
2043    if conn is None:
2044        return None, None
2045
2046    # Ask for the DHCP server configuration details
2047    conn.write("get dhcp server cfg\n")
2048   
2049    # Wait for the prompt
2050    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2051    conn.close()
2052               
2053    # Extract DHCP server addresses
2054    lines = r.strip().split("\n")
2055    for line in lines:
2056        if line.strip().startswith("Def Pri"):
2057            try:
2058                parts = line.split(":")
2059                dns1 = parts[1].split()[0].strip()
2060                dns2 = parts[2].strip()
2061            except:
2062                log_error("Could not parse DNS servers from DSL modem!")
2063                log_debug(line.strip())
2064            break
2065    if dns1 == "0.0.0.0": dns1 = None
2066    if dns2 == "0.0.0.0": dns2 = None
2067
2068    # Return them
2069    return dns1, dns2
2070
2071def connectDSL():
2072    """Tells the modem to connect"""
2073
2074    # Try and configure the DSL modem if necessary
2075    if not DSLModemConfigured():
2076        configureDSLModem()
2077       
2078    conn = DSLLogin()
2079    if conn is None:
2080        return "critical", "Unable to login to DSL modem!", ISP_IF_CONNECT
2081
2082    # Send the command
2083    conn.write("modify ppp intf ifname ppp-0 start\n")
2084   
2085    # Wait for the prompt
2086    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2087    conn.close()
2088
2089    if r.find("Set Done") == -1:
2090        return "critical", "DSL modem failed to connect!", ISP_IF_CONNECT
2091
2092    return "warning", "Connecting... <span id=\"rem\">30</span>s remaining.", \
2093        ISP_IF_RELOAD
2094       
2095def disconnectDSL():
2096    """Tells the modem to disconnect"""
2097
2098    conn = DSLLogin()
2099    if conn is None:
2100        return "critical", "Unable to login to DSL modem!", ISP_IF_CONNECT
2101
2102    # Send the command
2103    conn.write("modify ppp intf ifname ppp-0 stop\n")
2104   
2105    # Wait for the prompt
2106    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2107    conn.close()
2108
2109    if r.find("Set Done") == -1:
2110        return "critical", "DSL modem failed to disconnect!", ISP_IF_DISCONNECT
2111
2112    return "warning", \
2113        "Disconnecting... <span id=\"rem\">30</span>s remaining.", \
2114        ISP_IF_RELOAD
2115       
2116def getDSLStatus():
2117    """Returns the status of the DSL internet connection"""
2118   
2119    gwstatus, gwtext = getISPGatewayStatus()
2120
2121    # Check we can talk to the DSL modme
2122    if not isHostUp(DSL_MODEM_IP):
2123        return gwstatus, gwtext, "critical", "DSL Modem disconnected", ""
2124
2125     # Try and configure the DSL modem if necessary
2126    if not DSLModemConfigured():
2127        return gwstatus, gwtext, "critical", "DSL Modem unconfigured", ""
2128   
2129    conn = DSLLogin()
2130    if conn is None:
2131        return gwstatus, gwtext, "critical", \
2132            "Unable to login to DSL modem!", ISP_IF_CONNECT
2133   
2134    # Check if DSL is up... look for the AAL interface first
2135    conn.write("get interface stats ifname aal5-0\n")
2136    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2137    m = re.search("Operational Status.*:(.*)\n", r)
2138    if m is None or len(m.groups())==0:
2139        log_error("Could not retrieve aal5-0 status from DSL modem")
2140    else:
2141        status = m.groups()
2142        if status[0].strip() == "Down":
2143            # No DSL connection
2144            conn.close()
2145            return gwstatus, gwtext, "critical", "No DSL signal", ""
2146        # DSL is up
2147
2148    # Check if PPP is up
2149    conn.write("get interface stats ifname ppp-0\n")
2150    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2151    m = re.search("Operational Status.*:(.*)\n", r)
2152    if m is None or len(m.groups())==0:
2153        log_error("Could not retrieve ppp-0 status from DSL modem")
2154    else:
2155        status = m.groups()
2156        if status[0].strip() == "Down":
2157            # No PPP connection
2158            conn.close()
2159            return gwstatus, gwtext, "warning", "Disconnected", ISP_IF_CONNECT
2160
2161    # PPP is up, should all be OK
2162    return gwstatus, gwtext, "ok", "Connected", ISP_IF_DISCONNECT
2163   
2164def getDSLGatewayIP():
2165    """Returns the default gateway being used by the DSL modem"""
2166   
2167    conn = None
2168    try:
2169        conn = DSLLogin()
2170    except:
2171        pass
2172    if conn is None:
2173        return ""
2174       
2175    # Get the DSL Modems route table
2176    conn.write("get ip route\n")
2177    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2178    conn.close()
2179   
2180    # Parse the table
2181    lines = r.split("\n")
2182    for line in lines[2:]:
2183        parts = line.strip().split()
2184        if parts[0] == "0.0.0.0":
2185            return parts[2].strip()
2186   
2187    return ""
2188
2189def DSLModemConfigured():
2190    """Checks whether the DSL modem is configured for RuralLink use"""
2191   
2192    conn = None
2193    try:
2194        conn = DSLLogin()
2195    except:
2196        pass
2197    if conn is None:
2198        return False
2199   
2200    # Look for "RuralLink" in the vendor string
2201    conn.write("get system\n")
2202    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2203    conn.close()
2204   
2205    if r.find(": RuralLink") != -1:
2206        return True
2207
2208    return False
2209   
2210def configureDSLModem():
2211    """Updates the DSL modems configuration to bend it to our will..."""
2212    global rl_serialno
2213   
2214    conn = DSLLogin()
2215    if conn is None:
2216        raise ccs_rurallink_error("Unable to login to DSL modem!")
2217
2218    # Turn off the DHCP server
2219    conn.write("modify dhcp server cfg disable\n")
2220    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2221    if r.find("Set Done") == -1:
2222        log_error("Failed to disable DHCP server on DSL modem")
2223        log_debug("%s" % r)
2224
2225    # Pass SSH traffic through to our server on port 2001
2226    conn.write("create nat rule entry ruleid 2 rdr destportfrom num 2001 " \
2227        "destportto num 2001 lcladdrfrom %s lcladdrto %s\n" % \
2228        (DSL_CPE_IP, DSL_CPE_IP))
2229    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2230    if r.find("Entry Created") == -1:
2231        log_error("Failed to setup SSH pinhole on DSL modem")
2232        log_debug("%s" % r)
2233       
2234    # Add route to the wireless network
2235    conn.write("create ip route ip %s mask %s gwyip %s\n" % \
2236        (RL_BASE_S, RL_MASK_S, DSL_CPE_IP))
2237    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2238    if r.find("Entry Created") == -1:
2239        log_error("Failed to setup wireless network route on DSL modem")
2240        log_debug("%s" % r)
2241
2242    # Configure the password
2243    password = getDSLAdminPassword()
2244    auth=False
2245    conn.write("passwd\n")
2246    for oldpass in [password, "admin"]:
2247        r = conn.read_until("Old Password:", DSL_TELNET_TIMEOUT)
2248        if not r.endswith("Old Password:"):
2249            log_debug("DSL Modem: Unexpected string after sending " \
2250                "password change request\n%s" % r)
2251            break
2252        # Send the password
2253        conn.write("%s\n" % oldpass)
2254        # Wait for the appropriate prompt
2255        index, match, text = conn.expect(["Error:", "New Password:"], \
2256            DSL_TELNET_TIMEOUT)
2257        if index == -1:
2258            log_debug("DSL Modem: Unexpected error after sending " \
2259                "password\n%s" % text)
2260            break
2261        if index == 1:
2262            # Logged in successfully
2263            auth = True
2264            break
2265        else:
2266            # Gobble
2267            conn.read_until("$")
2268            conn.write("passwd\n")
2269
2270    if auth:
2271        # Change the password
2272        conn.write("%s\n" % password)
2273        r = conn.read_until("New Password:", DSL_TELNET_TIMEOUT)
2274        if not r.endswith("New Password:"):
2275            log_error("Failed to set new DSL modem password")
2276            log_debug("DSL Modem: Unexpected string after sending " \
2277                "new password\n%s" % r)
2278        else:
2279            conn.write("%s\n" % password)
2280            r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2281            if r.find("Set Done") == -1:
2282                log_error("Failed to change DSL modem password")
2283                log_debug("%s" % r)
2284
2285    # Configure the device name
2286    conn.write("modify system name \"rl-dsl-%s\"\n" % rl_serialno)
2287    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2288    if r.find("Set Done") == -1:
2289        log_error("Failed to modify system name on DSL modem")
2290        log_debug("%s" % r)
2291    conn.write("modify system contact \"help@rurallink.co.nz\"\n")
2292    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2293    if r.find("Set Done") == -1:
2294        log_error("Failed to modify system contact on DSL modem")
2295        log_debug("%s" % r)
2296    conn.write("modify system vendor \"RuralLink\"\n")
2297    r = conn.read_until("$", DSL_TELNET_TIMEOUT)
2298    if r.find("Set Done") == -1:
2299        log_error("Failed to modify vendor on DSL modem")
2300        log_debug("%s" % r)
2301   
2302    # Commit the changes
2303    conn.write("commit\n")
2304    conn.close()
2305
2306    return True
2307
2308def changeISP(newisp):
2309    global rl_isptype, int_ifname
2310   
2311    if newisp not in RL_ISPS.keys():
2312        raise ccs_rurallink_error("Invalid ISP specified")
2313    # Disconnect the old ISP
2314    ISPDisconnect()
2315   
2316    # Update the type
2317    rl_isptype = newisp
2318    pref_set("rurallink", "isptype", rl_isptype, monitor_prefs)
2319
2320    # Setup routes
2321    initialiseISPRoutes()
2322
2323def updateISP(username, password):
2324    global rl_isptype
2325
2326    if rl_isptype == ISP_BCL:
2327        updateBCL(username, password)
2328    if rl_isptype == ISP_DSL:
2329        updateDSL(username, password)
2330    else:
2331        raise ccs_rurallink_error("No other ISPs implemented")
2332   
2333def getISPIfName():
2334    global rl_isptype, int_ifname
2335   
2336    # Get the name of the interface
2337    if rl_isptype == ISP_BCL:
2338        return BCL_IFACE
2339    elif rl_isptype == ISP_DSL:
2340        return int_ifname
2341    else:
2342        return DEFAULT_ISP_IFNAME
2343   
2344def getISPUser():
2345    global rl_isptype
2346   
2347    if rl_isptype == ISP_BCL:
2348        return getBCLUser()
2349    elif rl_isptype == ISP_DSL:
2350        return getDSLUser()
2351    else:
2352        raise ccs_rurallink_error("No other ISPs implemented")
2353
2354def getISPGatewayStatus():
2355       
2356    # Interface Status
2357    dg = getISPGatewayIP()
2358    if dg != "":
2359        # Got IP try and ping it
2360        command = "fping -am  %s" % dg
2361        result = os.popen("%s 2>/dev/null" % command).readlines()
2362        if len(result):
2363            if getGatewayIface() == getISPIfName():
2364                gwstatus = "ok"
2365                gwtext = "OK - %s reachable" % dg
2366            else:
2367                gwstatus = "warning"
2368                gwtext = "WARNING - ISP connection is not used!"
2369        else:
2370            gwstatus = "critical"
2371            gwtext = "CRITICAL - %s not reachable" % dg
2372    else:
2373        gwstatus = "critical"
2374        gwtext = "CRITICAL - No default gateway"
2375   
2376    return gwstatus, gwtext
2377
2378def getISPGatewayIP():
2379    global rl_isptype
2380   
2381    if rl_isptype == ISP_BCL:
2382        return getGatewayIP()
2383    elif rl_isptype == ISP_DSL:
2384        return getDSLGatewayIP()
2385    else:
2386        raise ccs_rurallink_error("Unknown ISP passed to getISPGatewayIP!")
2387
2388def ISPConnect():
2389    global rl_isptype
2390   
2391    if rl_isptype == ISP_BCL:
2392        ifstatus, iftext, ifaction = connectBCL()
2393    elif rl_isptype == ISP_DSL:
2394        ifstatus, iftext, ifaction = connectDSL()
2395    else:
2396        raise ccs_rurallink_error("Unknown ISP type passed to ISPConnect!")
2397   
2398    gwstatus, gwtext = getISPGatewayStatus()
2399    return (gwstatus, gwtext, ifstatus, iftext, ifaction)
2400   
2401def ISPDisconnect():
2402    global rl_isptype
2403           
2404    if rl_isptype == ISP_BCL:
2405        ifstatus, iftext, ifaction = disconnectBCL()
2406    elif rl_isptype == ISP_DSL:
2407        ifstatus, iftext, ifaction = disconnectDSL()
2408    else:
2409        raise ccs_rurallink_error("Unknown ISP type passed to ISPDisconnect!")
2410
2411    gwstatus, gwtext = getISPGatewayStatus()
2412    return (gwstatus, gwtext, ifstatus, iftext, ifaction)
2413
2414def getISPStatus():
2415    global rl_isptype
2416   
2417    if rl_isptype == ISP_BCL:
2418        return getBCLStatus()
2419    elif rl_isptype == ISP_DSL:
2420        return getDSLStatus()
2421    else:
2422        raise ccs_rurallink_error("Unknown ISP type passed to ISPDisconnect!")
2423
2424def initialiseISPRoutes():
2425    global rl_isptype, int_ifname
2426   
2427    if rl_isptype == ISP_DSL:
2428        # Add DSL alias IP
2429        log_command("/sbin/ip addr add %s/%s dev %s &>/dev/null" % \
2430                (DSL_CPE_IP, DSL_NETMASK, int_ifname))
2431        addRoute("default", 0, DSL_MODEM_IP, int_ifname)
2432    else:
2433        # Remove DSL alias IP
2434        log_command("/sbin/ip addr del %s/%s dev %s &>/dev/null" % \
2435                (DSL_CPE_IP, DSL_NETMASK, int_ifname))
2436        delRoute("default", 0, DSL_MODEM_IP, int_ifname)
2437
2438@registerRecurring(ROUTE_CHECK_INTERVAL)
2439def checkRoutes():
2440    """Checks that the routing is setup correctly"""
2441
2442    if isMasterNode():
2443        initialiseISPRoutes()
2444    initialiseClientRoutes()
2445
2446@registerRecurring(DNS_UPDATE_INTERVAL)
2447def updateDNS():
2448    """Checks that pdnsd has appropriate name servers configured"""
2449    global rl_isptype, rl_masterip
2450   
2451    if isMasterNode():
2452        if rl_isptype == ISP_DSL:
2453            # Check if pdnsd has any servers
2454            fh = os.popen("/usr/sbin/pdnsd-ctl status | grep ip: | " \
2455                "grep -v ping")
2456            lines = fh.readlines()
2457            rv = fh.close()
2458            if rv is not None or len(lines)==0:
2459                # It doesn't, get some
2460                ns1, ns2 = getDSLNameservers()
2461                if ns1 is not None:
2462                    log_command("/usr/sbin/pdnsd-ctl server pppdns1 up %s " \
2463                        "2>&1" % ns1)
2464                    log_info("Set NS1 to %s" % ns1)
2465                if ns2 is not None:
2466                    log_command("/usr/sbin/pdnsd-ctl server pppdns2 up %s " \
2467                        "2>&1" % ns2)
2468                    log_info("Set NS2 to %s" % ns2)
2469        # Use localhost as our nameserver
2470        ns = "127.0.0.1"
2471    else:
2472        ns = rl_masterip
2473
2474    # Check that resolv.conf is correct
2475    fp = open("/etc/resolv.conf", "r")
2476    lines = fp.readlines()
2477    fp.close()
2478   
2479    update = False
2480    for line in lines:
2481        parts = line.split()
2482        if line.startswith("search"):
2483            if parts[1].strip() != "rurallink.co.nz":
2484                update=True
2485                break
2486        elif line.startswith("nameserver"):
2487            if parts[1].strip() != ns:
2488                update=True
2489                break
2490    if update:
2491        try:
2492            remountrw("updateDNS")
2493            fp = open("/etc/resolv.conf", "w")
2494            fp.write("search rurallink.co.nz\n")
2495            fp.write("nameserver %s\n" % ns)
2496            fp.close()
2497            remountro("updateDNS")
2498        except:
2499            log_error("Could not write resolv.conf!", sys.exc_info())
2500            remountro("updateDNS")
2501 
2502##############################################################################
2503# Initialisation
2504##############################################################################
2505def ccs_init():
2506    global rl_serialno, rl_password, monitor_prefs, rl_trafficusers
2507    global int_ifname, ap0_ifname, backhaul_ifname, ext_ifname
2508    global backhaul_vbase, ap0_vbase, rl_masterip, rl_isptype
2509    global rl_mastername
2510   
2511    # Read the serial number for this device from a file
2512    try:
2513        fp = open(SERIALNO_FILE, "r")
2514        rl_serialno = int(fp.read())
2515        if rl_serialno <= 0 or rl_serialno > (2**16):
2516            raise Exception("Invalid serial number!")
2517        fp.close()
2518        log_info("RuralLink Serial No: %s" % rl_serialno)
2519    except:
2520        (type, value, tb) = sys.exc_info()
2521        log_fatal("Could not initialise RuralLink module! - %s" % value, \
2522                (type, value, tb))
2523
2524    # Retrieve the network password
2525    rl_password = pref_get("rurallink", "password", monitor_prefs, \
2526            DEFAULT_PASSWORD)
2527
2528    # Retrieve interface configuration details from the configuration file
2529    int_ifname = config_get("rurallink", "internal_ifname", \
2530            DEFAULT_INTERNAL_IFNAME)
2531    ap0_ifname = config_get("rurallink", "ap0_ifname", DEFAULT_AP0_IFNAME)
2532    backhaul_ifname = config_get("rurallink", "backhaul_ifname", \
2533            DEFAULT_BACKHAUL_IFNAME)
2534    ext_ifname = "ppp0"
2535    ap0_vbase = config_get("rurallink", "ap0_vbase", None)
2536    backhaul_vbase = config_get("rurallink", "backhaul_vbase", None)
2537   
2538    # Check interface configurations
2539    if isMasterNode():
2540        checkMasterInterfaces()
2541    else:
2542        checkClientInterfaces()
2543
2544    # Install the setup wizard if required
2545    rl_wizardrun = pref_getboolean("rurallink", "wizardrun", monitor_prefs, \
2546            False)
2547    if not rl_wizardrun:
2548        # Replace the CPE homepage with the setup wizard
2549        log_info("Installing Rural Link setup wizard")
2550        @registerPage("/", force=True)
2551        def setupwizard(request, method):
2552            return rlwizard(request, method)
2553       
2554        # Make sure the internal interface is up and DHCP is running
2555        log_command("/sbin/ifup %s 2>&1" % int_ifname)
2556        t = ap0_ifname
2557        ap0_ifname = None
2558        checkDHCPServer()
2559        ap0_ifname = t
2560    else:
2561        # Reload interfaces to pick up any new configuration
2562        log_command("/etc/init.d/networking restart 2>&1")
2563   
2564    # Check if we know the master device to talk to
2565    nm_filename = "%s/nodemap" % config_get(None, "tmpdir", DEFAULT_TMPDIR)
2566    if not isMasterNode():
2567        rl_masterip = pref_get("rurallink", "masterip", monitor_prefs, None)
2568        if rl_masterip is None:
2569            # No master IP recorded, see if we can retrieve on
2570            retrieveMasterIP()
2571        rl_mastername = getNodeName(rl_masterip)
2572        # Initialise the status map
2573        if retrieveNodemapFile(nm_filename):
2574            setupStatusMap(nm_filename, rl_mastername)
2575
2576    else:
2577        # Master node can get its own IP when it needs it...
2578        rl_masterip = None
2579       
2580        # Retrieve the ISP we're connecting to
2581        rl_isptype = pref_get("rurallink", "isptype", monitor_prefs, ISP_BCL)
2582        initialiseISPRoutes()
2583       
2584        # Initialise the status map
2585        if buildNodemapFile(nm_filename):
2586            setupStatusMap(nm_filename, socket.gethostname())
2587           
2588        # Initialise traffic management
2589        try:
2590            passwdfile =  config_get("traffic", "passwd_file", \
2591                    DEFAULT_TRAFFIC_PASSWD_FILE)
2592            ensureFileExists(passwdfile)
2593            rl_trafficusers = readTrafficUsers()
2594        except:
2595            log_error("Could not load users! Traffic management " \
2596                    "functionality disabled.", sys.exc_info())
2597            rl_trafficusers = {}
2598            return
2599       
2600        # Ensure that default traffic management policies are set in the
2601        # preferences file for the firewall to use
2602        ap0_policy = pref_get("traffic", "ap0_policy", monitor_prefs, None)
2603        int_policy = pref_get("traffic", "internal_policy", monitor_prefs, \
2604                None)
2605        if ap0_policy is None:
2606            ap0_policy = DEFAULT_AP0_POLICY
2607            pref_set("traffic", "ap0_policy", ap0_policy, monitor_prefs)
2608        if int_policy is None:
2609            int_policy = DEFAULT_INTERNAL_POLICY
2610            pref_set("traffic", "internal_policy", int_policy, monitor_prefs)
2611
2612        # Initialise the counters
2613        for username,user in rl_trafficusers.items():
2614            rl_trafficusers[username]["traffic"] = 0
2615            rl_trafficusers[username]["ip"] = None
2616           
2617        # Initialise the captive portal server
2618        httpd = AsyncHTTPServer(('', 81), CaptivePortalHandler)
2619        log_info("Captive Portal Initialised. Traffic management in place.")
2620     
2621    # Reload the firewall
2622    log_command("/etc/init.d/firewall start & 2>&1")
2623
2624    # Make sure DNS is correctly setup
2625    updateDNS()
2626   
2627    if rl_wizardrun:
2628        # Check services are running
2629        checkDHCPServer()
2630       
2631        # Initialise routes for the clients
2632        initialiseClientRoutes()
2633
2634@catchEvent("statusMapRequested")
2635def updateStatusMap(*args, **kwargs):
2636    """Called when the status map is requested, so we can update it"""
2637    global rl_lastmapupdate, rl_mastername
2638   
2639    nm_filename = "%s/nodemap" % config_get(None, "tmpdir", DEFAULT_TMPDIR)
2640
2641    # Updated recently, no need to update again
2642    if rl_lastmapupdate!=0 and \
2643            time.time() < rl_lastmapupdate+MAP_UPDATE_INTERVAL:
2644        return None
2645   
2646    if isMasterNode():
2647        if buildNodemapFile(nm_filename):
2648            setupStatusMap(nm_filename, socket.gethostname())
2649    else:
2650        if retrieveNodemapFile(nm_filename):
2651            setupStatusMap(nm_filename, rl_mastername)
2652
2653   
2654def setupStatusMap(nodemapfile, gateway):
2655    global rl_lastmapupdate
2656
2657    try:
2658        initialiseNetworkMap(nodemapfile,socket.gethostname(),gateway,gateway)
2659        rl_lastmapupdate = time.time()
2660    except:
2661        log_error("Could not setup network map using Rural Link data!", \
2662                sys.exc_info())
2663        return
2664
2665def checkDHCPServer():
2666    """Checks that the DCHP server is running on the appropriate interfaces
2667
2668    If not the configuration is fixed so that it is.
2669    """
2670    global int_ifname, ap0_ifname
2671
2672    dhcp = dhcpd_conf()
2673    interfaces = debian_interfaces()
2674    changed = False
2675    iflist = []
2676   
2677    if int_ifname != None:
2678        net = interfaces[int_ifname]["network"]
2679        if net not in dhcp.keys():
2680            netmask = interfaces[int_ifname]["netmask"]
2681            lo = formatIP(ipnum(net)+1)
2682            hi = formatIP(ipnum(interfaces[int_ifname]["broadcast"])-2)
2683            address = interfaces[int_ifname]["address"]
2684            # Create a subnet block for the internal interface
2685            dhcp[net] = {"network":net, "netmask":netmask, \
2686                    "range":"%s %s" % (lo, hi), "options":{"routers":address}}
2687            changed = True
2688        iflist.append(int_ifname)
2689       
2690    if ap0_ifname != None:
2691        net = interfaces[ap0_ifname]["network"]
2692        if net not in dhcp.keys():
2693            netmask = interfaces[ap0_ifname]["netmask"]
2694            lo = formatIP(ipnum(net)+2)
2695            hi = formatIP(ipnum(interfaces[ap0_ifname]["broadcast"])-1)
2696            address = interfaces[ap0_ifname]["address"]
2697            # Create a subnet block for the AP0 interface
2698            dhcp[net] = {"network":net, "netmask":netmask, \
2699                    "range":"%s %s" % (lo, hi), "options":{"routers":address}}
2700            changed = True
2701        iflist.append(ap0_ifname)
2702
2703    # Ensure the appropriate interfaces are enabled
2704    iline = "INTERFACES=\"%s\"" % " ".join(iflist)
2705    lines = open(DHCP_DEFAULTS, "r").readlines()
2706    found = False
2707    for line in lines:
2708        if line.strip() == iline:
2709            found=True
2710            break
2711    if not found:
2712        filterFile("^INTERFACES.*$", iline, DHCP_DEFAULTS)
2713        changed = True
2714       
2715    if changed:
2716        remountrw()
2717        dhcp.write()
2718        remountro()
2719        log_command("/etc/init.d/dhcp3-server restart 2>&1")
2720    else:
2721        # Check that the server is running
2722        if not processExists("dhcpd3"):
2723            log_command("/etc/init.d/dhcp3-server start 2>&1")
2724
2725def createInternalConfig(old):
2726    global rl_serialno, int_ifname
2727    network, bits = generateRLNetwork(rl_serialno, RL_INTERNAL)
2728    netmask = bitsToNetmask(bits)
2729    bcast = ipbroadcast(network, netmask)
2730
2731    # Merge current configuration
2732    iface = old
2733
2734    # Now set the fixed bits
2735    iface["address"] = formatIP(bcast-1)
2736    iface["netmask"] = formatIP(netmask)
2737    iface["network"] = formatIP(network)
2738    iface["broadcast"] = formatIP(bcast)
2739    iface["family"] = "inet"
2740    iface["method"] = "static"
2741    iface["name"] = int_ifname
2742    iface["auto"]=True
2743   
2744    return iface
2745
2746def createBackhaulConfig(old):
2747    global rl_serialno, backhaul_vbase, backhaul_ifname
2748
2749    # Setup bits the current configuration can override
2750    iface = {}
2751    if backhaul_vbase is not None:
2752        iface["madwifi_base"] = backhaul_vbase
2753    iface["wireless_standard"] = "g"
2754    iface["wireless_essid"] = "rl-bhaul-%s" % rl_serialno
2755
2756    # Merge current configuration
2757    iface.update(old)
2758
2759    # Now set the fixed bits
2760    iface["wireless_mode"] = "Managed"
2761    iface["name"]=backhaul_ifname
2762    iface["family"]="inet"
2763    iface["method"]="dhcp"
2764    iface["auto"]=True
2765   
2766    return iface
2767   
2768def createAP0Config(old):
2769    global rl_serialno, ap0_vbase, ap0_ifname
2770    network, bits = generateRLNetwork(rl_serialno, RL_AP0)
2771    netmask = bitsToNetmask(bits)
2772   
2773    # Setup the bits that a current configuration can override
2774    iface = {}
2775    if ap0_vbase is not None:
2776        iface["madwifi_base"] = ap0_vbase
2777    iface["wireless_standard"] = "g"
2778    iface["wireless_essid"] = "rl-%s-ap0" % socket.gethostname()
2779    iface["wireless_channel"] = allocateChannel(ap0_ifname)
2780   
2781    # Merge current configuration
2782    iface.update(old)
2783   
2784    # Now set the fixed bits
2785    iface["wireless_mode"] = "Master"
2786    iface["address"] = formatIP(network+1)
2787    iface["netmask"] = formatIP(netmask)
2788    iface["network"] = formatIP(network)
2789    iface["broadcast"] = formatIP(ipbroadcast(network, netmask))
2790    iface["name"]=ap0_ifname
2791    iface["family"]="inet"
2792    iface["method"]="static"
2793    iface["auto"]=True
2794   
2795    return iface
2796           
2797def checkClientInterfaces():
2798    """Checks that the appropriate interfaces for a client node are present
2799
2800    A client node can be a repeater, a CPE repeater or simply a CPE.
2801
2802    Also checks that the IP addresses and service configuration is correct
2803    given the serial number for this device.
2804    """
2805    global int_ifname, ap0_ifname, backhaul_ifname, ext_ifname
2806    global backhaul_vbase, ap0_vbase
2807   
2808    ifaces = {}
2809    for i in getInterfaces(True): ifaces[i["name"]] = i
2810    interfaces = debian_interfaces()
2811    changed = False
2812           
2813    # No external interface when not acting as a Master
2814    ext_ifname = None
2815   
2816    # Check backhaul devices exist
2817    if backhaul_ifname not in ifaces.keys():
2818        # Doesn't exist, and not configured, check for vbase
2819        if backhaul_vbase is None or backhaul_vbase not in ifaces.keys():
2820            log_fatal("Invalid backhaul interface! = %s" % backhaul_ifname)
2821    # Check backhaul configuration
2822    if backhaul_ifname not in interfaces.keys():
2823        interfaces[backhaul_ifname] = createBackhaulConfig({})
2824        changed = True
2825    else:
2826        iface = interfaces[backhaul_ifname]
2827        if iface["method"]!="dhcp" or not iface["auto"]:
2828            log_warn("backhaul interface reconfigured")
2829            interfaces[backhaul_ifname] = createBackhaulConfig(iface)
2830            changed=True
2831   
2832    # Check that the internal interface is correctly configured if it exists
2833    if int_ifname not in ifaces.keys():
2834        int_ifname = None
2835    else:
2836        network, bits = generateRLNetwork(rl_serialno, RL_INTERNAL)
2837        netmask = bitsToNetmask(bits)
2838        bcast = ipbroadcast(network, netmask)
2839        # Verify internal interface configuration
2840        if int_ifname not in interfaces.keys():
2841            interfaces[int_ifname] = createInternalConfig({})
2842            changed = True
2843        else:
2844            iface = interfaces[int_ifname]
2845            if "address" not in iface.keys() or (iface["address"] != \
2846                    formatIP(bcast-1) or iface["method"]!="static" or \
2847                    not iface["auto"]):
2848                log_warn("Internal interface reconfigured")
2849                interfaces[int_ifname] = createInternalConfig({})
2850                changed = True
2851           
2852    # Check AP0 devices exist
2853    if ap0_ifname not in ifaces.keys():
2854        # Doesn't exist, and not configured, check for vbase
2855        if ap0_vbase is None or ap0_vbase not in ifaces.keys():
2856            ap0_ifname = None
2857            ap0_vbase = None
2858    if ap0_ifname is not None:
2859        # Check AP0 configuration
2860        if ap0_ifname not in interfaces.keys():
2861            interfaces[ap0_ifname] = createAP0Config({})
2862            changed = True
2863        else:
2864            iface = interfaces[ap0_ifname]
2865            network, bits = generateRLNetwork(rl_serialno, RL_AP0)
2866            if "address" not in iface.keys() or (iface["address"] != \
2867                    formatIP(network+1) or iface["method"]!="static" or \
2868                    not iface["auto"]):
2869                log_warn("AP0 interface reconfigured")
2870                interfaces[ap0_ifname] = createAP0Config(iface)
2871                changed=True
2872           
2873    if changed:
2874        remountrw()
2875        interfaces.write()
2876        remountro()
2877   
2878def checkMasterInterfaces():
2879    """Checks that the appropriate interfaces for a master node are present
2880
2881    Also checks that the IP addresses and service configuration is correct
2882    given the serial number for this device.
2883
2884    Corrects and reconfigures the device if not.
2885    """
2886    global int_ifname, ap0_ifname, backhaul_ifname, ext_ifname
2887    global backhaul_vbase, ap0_vbase
2888       
2889    backhaul_ifname = None
2890   
2891    # Get some information about the state of the interfaces on the system
2892    ifaces = {}
2893    for i in getInterfaces(True): ifaces[i["name"]] = i
2894    interfaces = debian_interfaces()
2895    changed = False
2896
2897    # Check that the internal interface is correctly configured if it exists
2898    if int_ifname not in ifaces.keys():
2899        int_ifname = None
2900    else:
2901        network, bits = generateRLNetwork(rl_serialno, RL_INTERNAL)
2902        netmask = bitsToNetmask(bits)
2903        bcast = ipbroadcast(network, netmask)
2904        # Verify internal interface configuration
2905        if int_ifname not in interfaces.keys():
2906            interfaces[int_ifname] = createInternalConfig({})
2907            changed = True
2908        else:
2909            iface = interfaces[int_ifname]
2910            if "address" not in iface.keys() or (iface["address"] != \
2911                    formatIP(bcast-1) or iface["method"]!="static" or \
2912                    not iface["auto"]):
2913                log_warn("Internal interface reconfigured")
2914                interfaces[int_ifname] = createInternalConfig({})
2915                changed = True
2916       
2917    # Check AP0 devices exist
2918    if ap0_ifname not in ifaces.keys():
2919        # Doesn't exist, and not configured, check for vbase
2920        if ap0_vbase is None or ap0_vbase not in ifaces.keys():
2921            log_fatal("Invalid AP0 interface! - %s (%s)" % \
2922                        (ap0_ifname, ap0_vbase))
2923    if ap0_ifname is not None:
2924        # Check AP0 configuration
2925        if ap0_ifname not in interfaces.keys():
2926            interfaces[ap0_ifname] = createAP0Config({})
2927            changed = True
2928        else:
2929            iface = interfaces[ap0_ifname]
2930            network, bits = generateRLNetwork(rl_serialno, RL_AP0)
2931            if "address" not in iface.keys() or (iface["address"] != \
2932                    formatIP(network+1) or iface["method"]!="static" or \
2933                    not iface["auto"]):
2934                log_warn("AP0 interface reconfigured")
2935                interfaces[ap0_ifname] = createAP0Config(iface)
2936                changed=True
2937   
2938    # Write out the interfaces configuration if it changed
2939    if changed:
2940        remountrw()
2941        interfaces.write()
2942        remountro()
2943
2944registerMenuItem(MENU_TOP, MENU_GROUP_GENERAL, \
2945    "/rladmin", "CPE Administration")
2946   
2947##############################################################################
2948# Content Pages
2949##############################################################################
2950
2951@registerPage("/setupwizard", requireAuth=True, realm=CPE_ADMIN_REALM)
2952def rlwizard(request, method):
2953    global rl_password, monitor_prefs
2954
2955    if method == "POST":
2956        data = request.getPostData()
2957        # Incoming data
2958        if "hostname" in data.keys():
2959            try:
2960                # Remove all associations
2961                changeHostname(data["hostname"])
2962            except:
2963                log_error("Failed to change hostname!", sys.exc_info())
2964                request.send_error(500, "Unable to update hostname")
2965                request.end_headers()
2966                return
2967            request.send_response(200, "Hostname updated")
2968            request.end_headers()
2969            return
2970        elif "password" in data.keys():
2971            try:
2972                rl_password = data["password"]
2973                admin_pass = crypt.crypt(rl_password, createSalt())
2974                pref_set("rurallink", "password", rl_password, monitor_prefs)
2975                # Change the CPE administration password as well
2976                pref_set(None, "admin_password", admin_pass, monitor_prefs)
2977                realm["users"][ADMIN_USERNAME] = admin_pass
2978                registerRealm(CPE_ADMIN_REALM, realm, overwrite=True)
2979            except:
2980                log_error("Failed to change password!", sys.exc_info())
2981                request.send_error(500, "Unable to update password")
2982                request.end_headers()
2983                return
2984            request.send_response(200, "Password updated")
2985            request.end_headers()
2986            return
2987        elif "essid" in data.keys():
2988            if not configureBhaulInterface(data["essid"]):
2989                log_error("Failed to associate!", sys.exc_info())
2990                request.send_error(500, "Unable to associate")
2991                request.end_headers()
2992                return
2993            request.send_response(200, "Associated")
2994            request.end_headers()
2995            return
2996        elif "isptype" in data.keys():
2997            try:
2998                changeISP(data["isptype"])
2999            except:
3000                log_error("Exception setting ISP type", sys.exc_info())
3001                isperr = "Failed to set ISP type"
3002                request.send_error(500, "Unable to setup ISP")
3003                request.end_headers()
3004                return
3005            request.send_response(200, "ISP type set")
3006            request.end_headers()
3007            return
3008        elif "username" in data.keys():
3009            try:
3010                if "pass" not in data.keys():
3011                    raise ccs_rurallink_error("Missing post variables!")
3012                username = urllib.unquote(data["username"]).strip()
3013                password = urllib.unquote(data["pass"]).strip()
3014                updateISP(username, password)
3015            except:
3016                log_error("Exception setting ISP password", sys.exc_info())
3017                isperr = "Failed to update username and password"
3018                request.send_error(500, "Unable to setup ISP")
3019                request.end_headers()
3020                return
3021            request.send_response(200, "ISP details configured")
3022            request.end_headers()
3023            return
3024        elif "wizard_complete" in data.keys():
3025            log_info("Rural Link setup wizard completed")
3026            pref_set("rurallink", "wizardrun", True, monitor_prefs)
3027            @registerPage("/", force=True)
3028            def restoreHomepage(request, method):
3029                return homepage(request, method)
3030            # Reload interfaces to pick up any new configuration
3031            log_command("/etc/init.d/networking restart 2>&1")
3032           
3033            # Reload the firewall
3034            log_command("/etc/init.d/firewall start & 2>&1")
3035           
3036            # Check services are running
3037            checkDHCPServer()
3038
3039            # Return Success
3040            request.send_response(200, "OK - Have fun")
3041            request.end_headers()
3042            return
3043       
3044        request.send_error(400, "Invalid method for this page!")
3045        request.end_headers()
3046        return
3047           
3048    # Get the template
3049    resourcedir = config_get("www", "resourcedir", DEFAULT_RESOURCE_DIR)
3050    tfile = "%s/page.html" % resourcedir
3051    try:
3052        fd = open(tfile, "r")
3053        template = fd.read()
3054        fd.close()
3055    except:
3056        log_error("Page template not available!", sys.exc_info())
3057        # Return a very cruddy basic page
3058        template = "<html><head><title>%TITLE%</title></head>" \
3059                "<body><h2>Menu</h2><br />%MENU%<br /><hr />" \
3060                "<h2>%TITLE%</h2><br />%CONTENT%</body></html>"
3061       
3062    hostname = socket.gethostname()
3063
3064    # Setup Uplink configuration
3065    if isMasterNode():
3066        # Get ISP connection details
3067        uplinkdetails = """
3068Please select your ISP from the list below and enter the username and password
3069supplied to you by your ISP.<br /><br />
3070<b>Note:&nbsp;</b>This password is assigned by your ISP and is different to the
3071password your chose in the previous step.<br /><br />
3072<table>        
3073<tr>
3074<th width="20%%">Connection Type</th>
3075<td>%s</td>
3076</tr>
3077<tr>
3078<th width="20%%">Username</th>
3079<td><input type="input" value="" id="wizard_ispusername"></td>
3080</tr>
3081<tr>
3082<th width="20%%">Password</th>
3083<td><input type="text" value="" id="wizard_isppassword"></td>
3084</tr>
3085</table>
3086<br />
3087""" % generateSelect("wizard_isptype", ISP_BCL, RL_ISPS)
3088        passworddetails = """Please enter an administration password for your
3089Rural Link device in the field below. This password will be required by any
3090other wireless device wanting to connect to your network. This password is
3091also used to restrict access to the administration interface of your Rural
3092Link device."""
3093    else:
3094        # Get Backhaul connection details
3095        (html, othernets) = getBackhaulHTML(False, "wizard_")
3096        uplinkdetails = """<div style="width:90%%">%s</div>""" % html
3097        passworddetails = """
3098Please enter your Rural Link device password in the field below. This
3099password is used to authenticate your Rural Link devices to each other (you
3100chose it when you intially configured your master device).
3101This password is also used to restrict access to the administration interface
3102of each Rural Link device on your network.
3103"""
3104
3105    # Generate page content
3106    content = """
3107<div class="wizard" id="step1"> 
3108<h2>Rural Link CPE Setup Wizard</h2><br />
3109<div class="progressbar">
3110    <div>
3111        <span class="stepno">1</span><br />
3112        <span class="stepdesc">Introduction</span>
3113    </div>
3114    <div class="future">
3115        <span class="stepno">2</span><br />
3116        <span class="stepdesc">Set Hostname</span>
3117    </div>
3118    <div class="future">
3119        <span class="stepno">3</span><br />
3120        <span class="stepdesc">Set Password</span>
3121    </div>
3122    <div class="future">
3123        <span class="stepno">4</span><br />
3124        <span class="stepdesc">Configure Uplink</span>
3125    </div>
3126    <div class="future">
3127        <span class="stepno">5</span><br />
3128        <span class="stepdesc">Configuration Complete</span>
3129    </div>
3130</div>
3131<br clear="all" />
3132Welcome to your new Rural Link device. This wizard will help you configure and
3133setup your network in a few simple steps. If you have any questions or require
3134assistance configuring your network please contact the Rural Link helpdesk on
3135<b>0800 RURAL LINK</b><br />
3136<br />
3137<a href="javascript:nextStep()" class="next">Next&nbsp;Step&nbsp;&nbsp;&gt;&gt;</a>
3138<br clear="all" />
3139</div>
3140<div class="wizard" id="step2"> 
3141<h2>Rural Link CPE Setup Wizard - Set Hostname</h2><br />
3142<div class="progressbar">
3143    <div class="past">
3144        <span class="stepno">1</span><br />
3145        <span class="stepdesc">Introduction</span>
3146    </div>
3147    <div>
3148        <span class="stepno">2</span><br />
3149        <span class="stepdesc">Set Hostname</span>
3150    </div>
3151    <div class="future">
3152        <span class="stepno">3</span><br />
3153        <span class="stepdesc">Set Password</span>
3154    </div>
3155    <div class="future">
3156        <span class="stepno">4</span><br />
3157        <span class="stepdesc">Configure Uplink</span>
3158    </div>
3159    <div class="future">
3160        <span class="stepno">5</span><br />
3161        <span class="stepdesc">Configuration Complete</span>
3162    </div>
3163</div>
3164<br clear="all" />
3165Please enter an identifying name for this device in the field below. The name
3166must consist only of the lowercase characters <b>a-z</b> the digits <b>0-9</b>
3167and the hyphen <b>-</b>. It is recommended that you choose a standard prefix
3168to use for all your Rural Link devices.<br /><br />
3169<table>
3170<tr>
3171    <th>Hostname:</th>
3172    <td><input type="text" id="wizard_hostname" value="%s"></td>
3173</tr>
3174</table>
3175<br />
3176<a href="javascript:prevStep()" class="prev">&lt;&lt;&nbsp;&nbsp;Prev&nbsp;Step</a>
3177<a href="javascript:nextStep()" class="next">Next&nbsp;Step&nbsp;&nbsp;&gt;&gt;</a>
3178<br clear="all" />
3179</div>
3180<div class="wizard" id="step3"> 
3181<h2>Rural Link CPE Setup Wizard - Set Password</h2><br />
3182<div class="progressbar">
3183    <div class="past">
3184        <span class="stepno">1</span><br />
3185        <span class="stepdesc">Introduction</span>
3186    </div>
3187    <div class="past">
3188        <span class="stepno">2</span><br />
3189        <span class="stepdesc">Set Hostname</span>
3190    </div>
3191    <div>
3192        <span class="stepno">3</span><br />
3193        <span class="stepdesc">Set Password</span>
3194    </div>
3195    <div class="future">
3196        <span class="stepno">4</span><br />
3197        <span class="stepdesc">Configure Uplink</span>
3198    </div>
3199    <div class="future">
3200        <span class="stepno">5</span><br />
3201        <span class="stepdesc">Configuration Complete</span>
3202    </div>
3203</div>
3204<br clear="all" />
3205%s<br /><br />
3206<b>Note:&nbsp;</b>This password is only used on your wireless network and must
3207be different to the password used to connect to your ISP.<br /><br />
3208<table>
3209<tr>
3210    <th>Device Password:</th>
3211    <td><input type="text" id="wizard_password" value=""></td>
3212</tr>
3213</table>
3214<br />
3215<a href="javascript:prevStep()" class="prev">&lt;&lt;&nbsp;&nbsp;Prev&nbsp;Step</a>
3216<a href="javascript:nextStep()" class="next">Next&nbsp;Step&nbsp;&nbsp;&gt;&gt;</a>
3217<br clear="all" />
3218</div>
3219<div class="wizard" id="step4"> 
3220<h2>Rural Link CPE Setup Wizard - Configure Uplink</h2><br />
3221<div class="progressbar">
3222    <div class="past">
3223        <span class="stepno">1</span><br />
3224        <span class="stepdesc">Introduction</span>
3225    </div>
3226    <div class="past">
3227        <span class="stepno">2</span><br />
3228        <span class="stepdesc">Set Hostname</span>
3229    </div>
3230    <div class="past">
3231        <span class="stepno">3</span><br />
3232        <span class="stepdesc">Set Password</span>
3233    </div>
3234    <div>
3235        <span class="stepno">4</span><br />
3236        <span class="stepdesc">Configure Uplink</span>
3237    </div>
3238    <div class="future">
3239        <span class="stepno">5</span><br />
3240        <span class="stepdesc">Configuration Complete</span>
3241    </div>
3242</div>
3243<br clear="all" />
3244%s
3245<br />
3246<a href="javascript:prevStep()" class="prev">&lt;&lt;&nbsp;&nbsp;Prev&nbsp;Step</a>
3247<a href="javascript:nextStep()" class="next">Next&nbsp;Step&nbsp;&nbsp;&gt;&gt;</a>
3248<br clear="all" />
3249</div>
3250<div class="wizard" id="step5"> 
3251<h2>Rural Link CPE Setup Wizard - Complete</h2><br />
3252<div class="progressbar">
3253    <div class="past">
3254        <span class="stepno">1</span><br />
3255        <span class="stepdesc">Introduction</span>
3256    </div>
3257    <div class="past">
3258        <span class="stepno">2</span><br />
3259        <span class="stepdesc">Set Hostname</span>
3260    </div>
3261    <div class="past">
3262        <span class="stepno">3</span><br />
3263        <span class="stepdesc">Set Password</span>
3264    </div>
3265    <div class="past">
3266        <span class="stepno">4</span><br />
3267        <span class="stepdesc">Configure Uplink</span>
3268    </div>
3269    <div>
3270        <span class="stepno">5</span><br />
3271        <span class="stepdesc">Configuration Complete</span>
3272    </div>
3273</div>
3274<br clear="all" />
3275Thank you. Your Rural Link device is now fully configured and operational.
3276<br /><br />
3277Click <a href="/">Here</a> to continue to your Rural Link device's homepage.
3278</div>
3279<div id="updatewindow">
3280</div>
3281""" % (hostname, passworddetails, uplinkdetails)
3282
3283    # Substitute as necessary
3284    title = "Rural Link CPE Setup Wizard"
3285    menustr = stylestr = ""
3286    scriptstr = """<script type="text/javascript" src="%s"></script>\n""" % \
3287            "/resources/rurallink.js"
3288    footer = "Page generated at: %s" % time.ctime()
3289    output = template.replace("%SCRIPTS%", scriptstr).replace("%STYLES%", \
3290            stylestr).replace("%TITLE%", title).replace("%MENU%", \
3291            menustr).replace("%CONTENT%", content).replace("%FOOTER%", \
3292            footer)
3293           
3294    # Send it away
3295    request.send_response(200)
3296    request.end_headers()
3297    request.wfile.write(output)
3298
3299@registerPage("/rladmin", requireAuth=True, realm=CPE_ADMIN_REALM)
3300def rladmin(request, method, returnDirect=True):
3301    """Returns the HTML for RuralLink administration page"""
3302    global rl_serialno, rl_password, monitor_prefs, realm
3303    global backhaul_ifname, ap0_ifname, channels, rl_isptype
3304   
3305    isperr = ""
3306   
3307    if method == "POST":
3308        data = request.getPostData()
3309        # Incoming data
3310        if "password" in data.keys():
3311            try:
3312                # Remove all associations
3313                resetNodeAssociations()
3314                # Now change the password
3315                rl_password = data["password"]
3316                admin_pass = crypt.crypt(rl_password, createSalt())
3317                pref_set("rurallink", "password", rl_password, monitor_prefs)
3318                # Change the CPE administration password as well
3319                pref_set(None, "admin_password", admin_pass, monitor_prefs)
3320                realm["users"][ADMIN_USERNAME] = admin_pass
3321                registerRealm(CPE_ADMIN_REALM, realm, overwrite=True)
3322            except:
3323                log_error("Failed to change rurallink password!", \
3324                        sys.exc_info())
3325                request.send_error(500, "Unable to update password")
3326                request.end_headers()
3327                return
3328            request.send_response(200, "Password updated")
3329            request.end_headers()
3330            request.wfile.write(rl_password)
3331            return
3332        elif "ap0_channel" in data.keys() and ap0_ifname is not None:
3333            channel = int(data["ap0_channel"])
3334            if channel not in channels.keys():
3335                request.send_error(500, "Invalid channel value!")
3336                request.end_headers()
3337                return
3338            rv = -1
3339            try:
3340                interfaces = debian_interfaces()
3341                t = interfaces[ap0_ifname]
3342                t["wireless_channel"] = channel
3343                interfaces[ap0_ifname] = t
3344                remountrw()
3345                interfaces.write()
3346                remountro()
3347                if log_command("/sbin/iwconfig %s channel %s 2>&1" % \
3348                        (ap0_ifname, channel)) != None:
3349                    raise Exception("Failed to set new interface channel!")
3350            except:
3351                log_error("Failed to change ap channel!", sys.exc_info())
3352                request.send_error(500, "Unable to update channel")
3353                request.end_headers()
3354                return
3355            request.send_response(200, "Channel changed")
3356            request.end_headers()
3357            request.wfile.write(channel)
3358            return
3359        elif "username" in data.keys():
3360            try:
3361                if "pass" not in data.keys():
3362                    raise ccs_rurallink_error("Missing post variables!")
3363                username = urllib.unquote(data["username"]).strip()
3364                password = urllib.unquote(data["pass"]).strip()
3365                updateISP(username, password)
3366            except:
3367                log_error("Exception setting ISP password", sys.exc_info())
3368                isperr = "Failed to update username and password"
3369        elif "isptype" in data.keys():
3370            try:
3371                changeISP(data["isptype"])
3372            except:
3373                log_error("Exception setting ISP type", sys.exc_info())
3374                isperr = "Failed to update isp type"
3375           
3376    # Normal page GET
3377    output = """<div class="content">
3378<h2>RuralLink CPE Administration</h2><br />
3379<style>
3380TH:first-child {
3381    width: 14em;
3382}
3383</style>
3384
3385<table>
3386<tr>
3387    <th>CPE Serial no:</th>
3388    <td>%s</td>
3389</tr>
3390<tr>
3391    <th valign="top">Password:</th>
3392    <td><input id="password" value="%s"><br /><small>This password is used to
3393    control access to your wireless network.<br>It <b>must not</b> be the same
3394    as your Internet password.</small></td>
3395</tr>
3396</table>
3397<br />
3398""" % (rl_serialno, rl_password)
3399
3400    # Configure an ISP interface
3401    #################################
3402    if isMasterNode():
3403        if request.path.endswith("isp-connect"):
3404            gwstatus, gwtext, ispstatus, isptext, ispaction = ISPConnect()
3405        elif request.path.endswith("isp-disconnect"):
3406            gwstatus, gwtext, ispstatus, isptext, ispaction = ISPDisconnect()
3407        else:
3408            gwstatus, gwtext, ispstatus, isptext, ispaction = getISPStatus()
3409        ispuser = getISPUser()
3410        if isperr != "":
3411            isperr = """<br /><span class="error">%s</span><br /><br />""" % \
3412                isperr
3413        fwstatus, fwtext, fwaction = checkFirewall()
3414        output += """
3415<h2>Internet Connection Settings</h2><br />
3416<table>
3417<tr>
3418    <th>Connection Type:</th>
3419    <td><form action="/rladmin" method="post">%s</form></td>
3420</tr>
3421<tr>
3422    <th>Connection Status:</th>
3423    <td><span class="status%s">%s</span>&nbsp;&nbsp;%s</td>
3424</tr>
3425<tr>
3426    <th>Default Gateway</th>
3427    <td class="status%s">%s</td>
3428</tr>
3429<tr>
3430    <th>Firewall Status:</th>
3431    <td><span class="status%s">%s</span>&nbsp;&nbsp;%s</td>
3432</tr>
3433<tr>
3434    <th>Current Username</th>
3435    <td>%s</td>
3436</tr>
3437</table>
3438%s
3439<script language="javascript" type="text/javascript">
3440function displayauth() {
3441        $('ispauthdetails').style.display = 'block';
3442}
3443</script>
3444<a href="javascript:displayauth();">
3445Click here to change your ISP authentication details</a><br />
3446<div id="ispauthdetails" style="display: none;">
3447<form method="POST" action="/rladmin">
3448<table>
3449<tr>
3450<th width="20%%">Username</th>
3451<td><input type="input" value="" name="username"></td>
3452</tr>
3453<tr>
3454<th width="20%%">Password</th>
3455<td><input type="password" value="" name="pass"></td>
3456</tr>
3457<tr>
3458<td>&nbsp;</td>
3459<td><input type="submit" value="Update ISP Details"></td>
3460</tr>
3461</table>
3462</form>
3463</div>
3464<br />
3465<a href="/traffic/admin">Edit Traffic Management Accounts&nbsp;&gt;&gt;</a>
3466<br /><br />
3467""" % (generateSelect("isptype", rl_isptype, RL_ISPS), ispstatus, \
3468        isptext, ispaction, gwstatus, gwtext, fwstatus, fwtext, fwaction, \
3469        ispuser, isperr)
3470
3471
3472    # Configure a backhaul interface
3473    #################################
3474    else:
3475        bhaul_status, bhaul_desc, stats = getBackhaulStatus()
3476        output += """
3477<h2>Backhaul Settings (using %s interface)</h2><br />
3478<table>
3479<tr>
3480    <th>Upstream Node:</th>
3481    <td><span class="status%s">%s</span>&nbsp;&nbsp;
3482    <a href="/rladmin/setup_backhaul">[change]</a>
3483    </td>
3484</tr>
3485<tr>
3486</table>
3487""" % (backhaul_ifname, bhaul_status, bhaul_desc)
3488        output += """<div id="ssmeter-%s" class="ssmeter">""" % backhaul_ifname
3489        output += getSSMeter(stats)
3490        output += "</div><br clear=\"all\" />"
3491        output += "<a href=\"/status/interfaces\">Click here for detailed " \
3492                "signal strength information</a><br /><br />"
3493               
3494    # Configure an AP interface
3495    #################################
3496    if ap0_ifname is not None:
3497        try:
3498            wiface = iwtools.get_interface(ap0_ifname)
3499            ap0_essid = wiface.get_essid()
3500            ap0_channel = wiface.get_channel()
3501        except:
3502            ap0_essid = "<span class=\"warning\">Unknown</span>"
3503            ap0_channel = -1
3504        ap0_channels = generateSelect("ap0_channel", ap0_channel, channels)
3505        ap0_policy = pref_get("traffic", "ap0_policy", monitor_prefs, \
3506            DEFAULT_AP0_POLICY)
3507        ap0_firewall = generateSelect("ap0_policy", ap0_policy, \
3508                FIREWALL_NET_OPTIONS)
3509        output += """
3510<h2>Access Point Settings (using %s interface)</h2><br />
3511<table>
3512<tr>
3513    <th>ESSID:</th>
3514    <td>%s</td>
3515</tr>
3516<tr>
3517    <th>Channel:</th>
3518    <td>%s<br /><small>It is recommended that you select a channel marked
3519    with a <strong>*</strong>.</small>
3520    </td>
3521</tr>
3522<tr>
3523    <th>Access Policy:</th>
3524    <td>%s</td>
3525</tr>
3526<tr>
3527</table>
3528<a href="/status/interfaces">Click here for information on connected clients</a>
3529<br /><br />
3530""" % (ap0_ifname, ap0_essid, ap0_channels, ap0_firewall)
3531
3532    # Configure an Internal interface
3533    #################################
3534    if int_ifname is not None:
3535        int_policy = pref_get("traffic", "internal_policy", monitor_prefs, \
3536            DEFAULT_INTERNAL_POLICY)
3537        int_firewall = generateSelect("internal_policy", int_policy, \
3538                FIREWALL_NET_OPTIONS)
3539        output += """
3540<h2>Internal Network Settings (using %s interface)</h2><br />
3541<table>
3542<tr>
3543    <th>Access Policy:</th>
3544    <td>%s</td>
3545</tr>
3546<tr>
3547</table>
3548""" % (int_ifname, int_firewall)
3549
3550       
3551    # Return the page
3552    if returnDirect:
3553        return returnPage(request, "RuralLink Administration", output, \
3554                scripts=["/resources/rurallink.js"])
3555    else:
3556        return output
3557
3558def getBackhaulHTML(submitButtons=True, inputPrefix=""):
3559    global backhaul_ifname
3560   
3561    othernets = {}
3562    output = ""
3563
3564    try:
3565        wiface = iwtools.get_interface(backhaul_ifname)
3566        scanres = wiface.scanall()
3567    except:
3568        log_error("Could not complete scan on backhaul interface!", \
3569                sys.exc_info())
3570        scanres = []
3571
3572    n=0
3573    for network in scanres:
3574        if not network.essid.startswith(RL_ESSID_PREFIX):
3575            # Skip non rurallink networks
3576            othernets[network.essid] = (network.channel, network.qual)
3577            continue
3578        # Get the node name from the ESSID
3579        parts = network.essid.split("-")
3580        if len(parts) != 3:
3581            othernets[network.essid] = (network.channel, network.qual)
3582            continue
3583       
3584        # Write the record
3585        output += """<h3>%s - (%s Interface)</h3>
3586        <div id="ssmeter-%s" class="ssmeter">%s</div>
3587        <div style="clear: all;">&nbsp;</div><br />
3588        """ % (parts[1], parts[2], parts[1], getSSMeter(network.qual))
3589       
3590        if submitButtons:
3591            output += """<form action="/rladmin/setup_backhaul" method="post">
3592            <input type="hidden" name="essid" value="%s" />
3593            <input type="submit" value="Connect to %s &gt;&gt;" />
3594            </form><br />""" % (network.essid, parts[1])
3595        else:
3596            output += """<input type="radio" id="%sessid" value="%s" />
3597            &nbsp;Connect to <b>'%s'</b><br />""" % \
3598                    (inputPrefix, network.essid, parts[2])
3599        n+=1 
3600
3601    if n==0:
3602        output += "<br /><b>No RuralLink devices found to connect to!</b>" \
3603                "<br /><br />"
3604
3605    return (output, othernets)
3606
3607@registerPage("/rladmin/setup_backhaul", requireAuth=True, \
3608        realm=CPE_ADMIN_REALM)
3609def rladmin_setupbackhaul(request, method):
3610    """Returns the HTML for RuralLink backhaul setup page"""
3611    global backhaul_ifname
3612   
3613    errmsg = ""
3614
3615    if method == "POST":
3616        data = request.getPostData()
3617        # Incoming data
3618        if "essid" in data.keys():
3619            if configureBhaulInterface(data["essid"]):
3620                # Success - back to admin page
3621                return rladmin(request, "GET")
3622            # Failed
3623            peer = data["essid"].split("-")[1]
3624            errmsg = "Failed to associate with %s" % peer
3625       
3626    # Normal page GET
3627    output = """<div class="content">
3628<h2>RuralLink CPE Administration - Configure Backhaul Interface</h2><br />
3629Please select the node to connect to from the following list.
3630<br /><br />
3631"""
3632    if errmsg != "":
3633        output += """<span class="error">%s</span><br /><br />""" % errmsg
3634       
3635    (output, othernets) = getBackhaulHTML()
3636
3637    if len(othernets.keys()) > 0:
3638        output += """<h2>Other networks</h2><br />
3639        The following other networks were detected. The RuralLink CPE is not
3640        able to automatically connect to these networks. You may need to
3641        co-ordinate channel usage with the operators of the listed
3642        networks to minimise the chance of interference.<br /><br />
3643        """
3644        for essid, (channel,qual) in othernets.items():
3645            output += """<h3>%s on Channel %s</h3>
3646            <div id="ssmeter-%s" class="ssmeter">%s</div>
3647            <div style="clear: all;">&nbsp;</div><br /><br />
3648            """ % (essid, channel, essid, getSSMeter(qual))
3649
3650    output += """<a href="/rladmin">&lt;&lt;&nbsp;Return to CPE
3651    Administration page</a><br />"""
3652
3653    return returnPage(request, "RuralLink Administration", output, \
3654                scripts=["/resources/rurallink.js"])
3655
3656@registerPage("/masterip")
3657def masterip(request, method):
3658    """Outputs the IP address of the master node"""
3659    global rl_masterip
3660   
3661    request.send_response(200)
3662    request.end_headers()
3663
3664    if isMasterNode():
3665        # Output the IP that the client is connected via
3666        masterip = getIfaceIPForIP(request.client_address[0])
3667        request.wfile.write(masterip)
3668    else:
3669        if rl_masterip is not None:
3670            request.wfile.write(rl_masterip)
3671        else:
3672            request.wfile.write("")
3673
3674@registerPage("/node_name")
3675def nodename(request, method):
3676    """Outputs the name of the client"""
3677    request.send_response(200)
3678    request.end_headers()
3679    request.wfile.write(socket.gethostname())
3680
3681@registerPage("/nodemap")
3682def serveNodemap(request, method):
3683
3684    if not isMasterNode():
3685        request.send_error(400, "Only the master node can server a nodemap!")
3686        request.end_headers()
3687        return
3688       
3689    nm_filename = "%s/nodemap" % config_get(None, "tmpdir", DEFAULT_TMPDIR)
3690    if not os.path.exists(nm_filename):
3691        buildNodemapFile(nm_filename)
3692    try:
3693        fp = open("%s/%s" % nm_filename, "r")
3694        data = fp.read()
3695        fp.close()
3696    except:
3697        request.send_error(404, "The specified resource does not exist!")
3698        request.end_headers()
3699        return
3700    request.send_response(200)
3701    request.send_header("Length", len(data))
3702    request.end_headers()
3703    request.wfile.write(data)
3704
3705@registerPage("/register_client")
3706def registerClient(request, method):
3707    """Called by a client to authenticate themselves to the access point"""
3708   
3709    parts = request.path.split("/")
3710    client_name = parts[len(parts)-1]
3711   
3712    # Check IP is directly connected
3713    in_iface = getIfaceForIP(request.client_address[0])
3714    ifaces = getInterfaces(returnOne=in_iface)
3715    if len(ifaces) != 1:
3716        # Cannot verify that this is a directly connected client
3717        request.send_error(400, "Could not verify connection status for %s" % \
3718                client_name)
3719        request.end_headers()
3720        return
3721    iface = ifaces[0]
3722    network = cidrToNetwork(iface["address"])
3723    netmask = cidrToNetmask(iface["address"])
3724    if not inNetwork(ipnum(request.client_address[0]), network, netmask):
3725        # Not a directly connected client
3726        request.send_error(401, "%s is not directly connected." % client_name)
3727        request.end_headers()
3728        return
3729   
3730    # Get password / serial no.
3731    data = request.getPostData()
3732    if "password" not in data.keys() or "serialno" not in data.keys():
3733        request.send_error(400, "Request is missing required fields.")
3734        request.end_headers()
3735        return
3736       
3737    # Check password
3738    if data["password"] != rl_password:
3739        request.send_error(400, "Invalid password.")
3740        request.end_headers()
3741        return
3742   
3743    # Check serial
3744    if not isValidRLSerialNo(data["serialno"]) or \
3745            data["serialno"]==rl_serialno:
3746        request.send_error(400, "Invalid serial no.")
3747        request.end_headers()
3748        return
3749       
3750    # Check for current registration status
3751    status = isClientAssociated(data["serialno"], client_name, \
3752            request.client_address[0])
3753    if status == client_name:
3754        request.send_response(200, "Client already associated")
3755        request.end_headers()
3756        return
3757   
3758    if status != "":
3759        # Registration does not match
3760        if status.find(".") == -1:
3761            # The device appears to have changed name
3762            updateClientAssociation(data["serialno"], client_name, \
3763                    request._client_address[0])   
3764        else:
3765            # The device has changed IP or is now registering on a different
3766            # interface... Remove the old registration
3767            removeClientAssociation(data["serialno"])
3768
3769    if not setupClientAssociation(data["serialno"], client_name, \
3770            request.client_address[0]):
3771        request.send_error(500, "Failed to associate client")
3772        request.end_headers()
3773        return
3774       
3775    request.send_response(200, "Client associated successfully")
3776    request.end_headers()
3777    return
3778
3779@registerPage("/unregister_client")
3780def unregisterClient(request, method):
3781    """Called by a client to unauthenticate themselves to the access point"""
3782   
3783    parts = request.path.split("/")
3784    client_name = parts[len(parts)-1]
3785   
3786    # Check IP is directly connected
3787    in_iface = getIfaceForIP(request.client_address[0])
3788    ifaces = getInterfaces(returnOne=in_iface)
3789    if len(ifaces) != 1:
3790        # Cannot verify that this is a directly connected client
3791        request.send_error(400, "Could not verify connection status for %s" % \
3792                client_name)
3793        request.end_headers()
3794        return
3795    iface = ifaces[0]
3796    network = cidrToNetwork(iface["address"])
3797    netmask = cidrToNetmask(iface["address"])
3798    if not inNetwork(ipnum(request.client_address[0]), network, netmask):
3799        # Not a directly connected client
3800        request.send_error(401, "%s is not directly connected." % client_name)
3801        request.end_headers()
3802        return
3803   
3804    # Get password / serial no.
3805    data = request.getPostData()
3806    if "password" not in data.keys() or "serialno" not in data.keys():
3807        request.send_error(400, "Request is missing required fields.")
3808        request.end_headers()
3809        return
3810       
3811    # Check password
3812    if data["password"] != rl_password:
3813        request.send_error(400, "Invalid password.")
3814        request.end_headers()
3815        return
3816   
3817    # Check serial
3818    if not isValidRLSerialNo(data["serialno"]) or \
3819            data["serialno"]==rl_serialno:
3820        request.send_error(400, "Invalid serial no.")
3821        request.end_headers()
3822        return
3823       
3824    removeClientAssociation(data["serialno"])
3825       
3826    request.send_response(200, "Client successfully unassociated")
3827    request.end_headers()
3828    return
3829
3830@registerPage("/register_subclient")
3831def registerSubclient(request, method):
3832    """Called by a client to tell us they have registered a client"""
3833   
3834    parts = request.path.split("/")
3835    client_name = parts[len(parts)-1]
3836   
3837    # Check IP is directly connected
3838    in_iface = getIfaceForIP(request.client_address[0])
3839    ifaces = getInterfaces(returnOne=in_iface)
3840    if len(ifaces) != 1:
3841        # Cannot verify that this is a directly connected client
3842        request.send_error(400, "Could not verify connection status for %s" % \
3843                client_name)
3844        request.end_headers()
3845        return
3846    iface = ifaces[0]
3847    network = cidrToNetwork(iface["address"])
3848    netmask = cidrToNetmask(iface["address"])
3849    if not inNetwork(ipnum(request.client_address[0]), network, netmask):
3850        # Not a directly connected client
3851        request.send_error(401, "Only directly connected clients can " \
3852                "register subclients.")
3853        request.end_headers()
3854        return
3855   
3856    # Get password / serial no.
3857    data = request.getPostData()
3858    if "password" not in data.keys() or "serialno" not in data.keys() or \
3859            "client_name" not in data.keys() or "client_serialno" not in \
3860            data.keys() or "client_ip" not in data.keys():
3861        request.send_error(400, "Request is missing required fields.")
3862        request.end_headers()
3863        return
3864       
3865    # Check password
3866    if data["password"] != rl_password:
3867        request.send_error(400, "Invalid password.")
3868        request.end_headers()
3869        return
3870   
3871    # Check serials
3872    if not isValidRLSerialNo(data["serialno"]) or \
3873            data["serialno"]==rl_serialno:
3874        request.send_error(400, "Invalid serial no.")
3875        request.end_headers()
3876        return
3877    if not isValidRLSerialNo(data["client_serialno"]) or \
3878            data["client_serialno"]==rl_serialno:
3879        request.send_error(400, "Invalid serial no.")
3880        request.end_headers()
3881        return
3882       
3883    # Check that the registering node is associated
3884    status = isClientAssociated(data["serialno"], client_name, \
3885            request.client_address[0])
3886    if status != client_name:
3887        request.send_error(400, "You must associate before registering " \
3888                "sub clients!")
3889        request.end_headers()
3890        return
3891   
3892    # Check if the subclient is registered
3893    status = isSubClientAssociated(request.client_address[0], \
3894            data["client_serialno"], data["client_name"], data["client_ip"])
3895    if status == data["client_name"]:
3896        request.send_response(200, "Subclient already registered")
3897        request.end_headers()
3898        return
3899   
3900    if not setupSubClientAssociation(request.client_address[0], \
3901            data["client_serialno"], data["client_name"], data["client_ip"]):
3902        request.send_error(500, "Failed to associate client")
3903        request.end_headers()
3904        return
3905       
3906    request.send_response(200, "Client associated successfully")
3907    request.end_headers()
3908    return
3909
3910@registerPage("/unregister_subclient")
3911def unregisterSubclient(request, method):
3912    """Called by a client to tell us they nolonger server a subclient"""
3913   
3914    parts = request.path.split("/")
3915    client_name = parts[len(parts)-1]
3916   
3917    # Check IP is directly connected
3918    in_iface = getIfaceForIP(request.client_address[0])
3919    ifaces = getInterfaces(returnOne=in_iface)
3920    if len(ifaces) != 1:
3921        # Cannot verify that this is a directly connected client
3922        request.send_error(400, "Could not verify connection status for %s" % \
3923                client_name)
3924        request.end_headers()
3925        return
3926    iface = ifaces[0]
3927    network = cidrToNetwork(iface["address"])
3928    netmask = cidrToNetmask(iface["address"])
3929    if not inNetwork(ipnum(request.client_address[0]), network, netmask):
3930        # Not a directly connected client
3931        request.send_error(401, "Only directly connected clients can " \
3932                "register subclients.")
3933        request.end_headers()
3934        return
3935   
3936    # Get password / serial no.
3937    data = request.getPostData()
3938    if "password" not in data.keys() or "serialno" not in data.keys() or \
3939            "client_serialno" not in data.keys():
3940        request.send_error(400, "Request is missing required fields.")
3941        request.end_headers()
3942        return
3943       
3944    # Check password
3945    if data["password"] != rl_password:
3946        request.send_error(400, "Invalid password.")
3947        request.end_headers()
3948        return
3949   
3950    # Check serials
3951    if not isValidRLSerialNo(data["serialno"]) or \
3952            data["serialno"]==rl_serialno:
3953        request.send_error(400, "Invalid serial no.")
3954        request.end_headers()
3955        return
3956    if not isValidRLSerialNo(data["client_serialno"]) or \
3957            data["client_serialno"]==rl_serialno:
3958        request.send_error(400, "Invalid serial no.")
3959        request.end_headers()
3960        return
3961
3962    removeSubClientAssociation(data["client_serialno"])
3963       
3964    request.send_response(200, "Subclientsuccessfully unassociated")
3965    request.end_headers()
3966    return
3967
3968@registerPage("/traffic/logout")
3969def toggleaccount(request, method):
3970    """Logs the user out"""
3971   
3972    request.logoutRealm(CPE_ADMIN_REALM)
3973   
3974    output = """<div class="content"><h2>Logged Out</h2>
3975<br />
3976You have been successfully logged out.<br />
3977"""
3978    returnPage(request, "Logged Out", output)
3979
3980@registerPage("/traffic/toggle", requireAuth=True, realm=CPE_ADMIN_REALM)
3981def toggleaccount(request, method):
3982    """Toggles the state of the specified user
3983   
3984    Where the user is specified in the URL e.g:
3985        /traffic/toggle/<username>
3986    """
3987    global rl_trafficusers
3988   
3989    parts = request.path.split("/")
3990    username = parts[len(parts)-1]
3991    errMsg = ""
3992   
3993    if username not in rl_trafficusers.keys():
3994        returnErrorPage(request, "Invalid username!")
3995        return
3996
3997    rl_trafficusers[username]["enabled"] = \
3998            not rl_trafficusers[username]["enabled"]
3999    writeTrafficUsers()
4000    return trafficadmin(request, "GET")
4001
4002@registerPage("/traffic/remove", requireAuth=True, realm=CPE_ADMIN_REALM)
4003def removeaccount(request, method):
4004    """Removes the specified user
4005   
4006    Where the user is specified in the URL e.g:
4007        /traffic/remove/<username>
4008    """
4009    global rl_trafficusers
4010   
4011    parts = request.path.split("/")
4012    username = parts[len(parts)-1]
4013   
4014    if username not in rl_trafficusers.keys():
4015        returnErrorPage(request, "Invalid username!")
4016        return
4017
4018    if method == "POST":
4019        # Assume the form has been filled in, validate and try to change pass
4020        vars = request.getPostData()
4021        if "confirm" in vars.keys() and vars["confirm"]=="yes":
4022            del rl_trafficusers[username]
4023            writeTrafficUsers()
4024        # No confirmation, no action
4025        return trafficadmin(request, "GET")
4026
4027    # Display the form to confirm the remove
4028    output = """<div class="content"><h2>IP Traffic Administration</h2>
4029<br />
4030Are you sure you want to remove the account '%s'?<br />
4031<br />
4032<style>
4033FORM {
4034    display: inline;
4035    margin-right: 10px;
4036}
4037FORM INPUT {
4038    background-color: transparent;
4039    border: 0px;
4040    color: #225599;
4041        text-decoration: none;
4042    cursor: pointer;
4043}
4044FORM INPUT:hover {
4045    color:  #0044FF;
4046}
4047</style>
4048""" % username
4049    if rl_trafficusers[username]["enabled"]:
4050        output += """
4051You may wish to disable this account (which will prevent it from being used
4052to access the Internet until it is re-enabled) instead of completely removing
4053it.<br />
4054<br />
4055<form action="/traffic/toggle/%s" method="POST">
4056<input type="submit" value="&lt;&lt; No - Disable Account" />
4057</form>
4058""" % username
4059    output += """
4060<form action="/traffic/remove/%s" method="POST">
4061<input type="hidden" value="yes" name="confirm" />
4062<input type="submit" value="Yes - Remove Account &gt;&gt;" />
4063</form>
4064
4065""" % username
4066
4067    returnPage(request, "Remove User", output)
4068
4069@registerPage("/traffic/chpass", requireAuth=True, realm=CPE_ADMIN_REALM)
4070def chpass(request, method):
4071    """Changes the password for the specified user
4072   
4073    Where the user is specified in the URL e.g:
4074        /traffic/chpass/<username>
4075    """
4076    global rl_trafficusers
4077   
4078    parts = request.path.split("/")
4079    username = parts[len(parts)-1]
4080    errMsg = ""
4081   
4082    if username not in rl_trafficusers.keys():
4083        returnErrorPage(request, "Invalid username!")
4084        return
4085
4086    if method == "POST":
4087        # Assume the form has been filled in, validate and try to change pass
4088        vars = request.getPostData()
4089        if "pass1" not in vars.keys() or "pass2" not in vars.keys():
4090            returnErrorPage(request, "Missing post variables!")
4091            return
4092        if vars["pass1"] == vars["pass2"]:
4093            rl_trafficusers[username]["passwd"] = crypt.crypt(vars["pass1"], \
4094                    createSalt())
4095            writeTrafficUsers()
4096            return trafficadmin(request, "GET")
4097        # Passwords did not match, display an error
4098        errMsg = """<br /><span class="error">Passwords did not match""" \
4099                """</span><br />"""
4100
4101    # Display the form to get a new password
4102    output = """<div class="content"><h2>IP Traffic Administration</h2>
4103<br />%s
4104Please enter a new password for '%s'<br />
4105<br />
4106<form method="POST" action="/traffic/chpass/%s">
4107<table>
4108<tr>
4109<th width="20%%">Password</th>
4110<td><input type="password" value="" name="pass1"></td>
4111</tr>
4112<tr>
4113<th width="20%%">Password Again</th>
4114<td><input type="password" value="" name="pass2"></td>
4115</tr>
4116<tr>
4117<td>&nbsp;</td>
4118<td><input type="submit" value="Change Password"></td>
4119</tr>
4120</table>
4121""" % (errMsg, username, username)
4122
4123    returnPage(request, "Change Password", output)
4124   
4125@registerPage("/traffic/add", requireAuth=True, realm=CPE_ADMIN_REALM)
4126def create_account(request, method):
4127    """Adds a new user"""
4128    global rl_trafficusers
4129
4130    errMsg = ""
4131    if method == "POST":
4132        # Assume the form has been filled in, validate and try to add
4133        vars = request.getPostData()
4134        if "pass" not in vars.keys() or "username" not in vars.keys():
4135            returnErrorPage(request, "Missing post variables!")
4136            return
4137        username = vars["username"]
4138        if re.match(USERNAME_CHAR_RE, username) is None:
4139            errMsg = "<span class=\"error\">Invalid characters in username!" \
4140                    "</span>"
4141        else:
4142            password = vars["pass"]
4143            rl_trafficusers[username] = \
4144                    {"username":username, "enabled":True, "traffic":0}
4145            rl_trafficusers[username]["passwd"] = \
4146                    crypt.crypt(password, createSalt())
4147            writeTrafficUsers()
4148            return trafficadmin(request, "GET")
4149
4150    # Display the form to get a new password
4151    output = """<div class="content"><h2>IP Traffic Administration</h2>
4152<br />%s
4153Please enter the details for the new user below<br />
4154<br />
4155<form method="POST" action="/traffic/add">
4156<table>
4157<tr>
4158<th width="20%%">Username</th>
4159<td><input type="input" value="" name="username"></td>
4160</tr>
4161<tr>
4162<th width="20%%">Password</th>
4163<td><input type="password" value="" name="pass"></td>
4164</tr>
4165<tr>
4166<td>&nbsp;</td>
4167<td><input type="submit" value="Create User"></td>
4168</tr>
4169</table>
4170""" % (errMsg)
4171
4172    returnPage(request, "Change Password", output)
4173
4174@registerPage("/traffic/policy", requireAuth=True, realm=CPE_ADMIN_REALM)
4175def trafficpolicy(request, method):
4176    """Changes the access policy for the specified network
4177   
4178    The network is specified in the URL e.g:
4179        /traffic/policy/<network>
4180    The policy for the network should be specified in the post vars.
4181    """
4182    global monitor_prefs, int_ifname, ap0_ifname
4183   
4184    if method != "POST":
4185        # Require changes to be POSTed
4186        request.send_header("Allow", "POST")
4187        request.send_error(405, "This URL only handles POST requests")
4188        request.end_headers()
4189        return
4190
4191    parts = request.path.split("/")
4192    network = parts[len(parts)-1]
4193   
4194    if network not in ["internal", "ap0"]:
4195        # Unknown network
4196        request.send_error(404, "Cannot set policy for unknown network!")
4197        request.end_headers()
4198        return
4199   
4200    vars = request.getPostData()
4201    if "policy" not in vars.keys():
4202        # No policy specified
4203        request.send_error(400, "No policy specified!")
4204        request.end_headers()
4205        return
4206       
4207    policy = vars["policy"]
4208    if policy not in FIREWALL_NET_OPTIONS.keys():
4209        # Invalid policy
4210        request.send_error(400, "Invalid policy specified! Options are: %s" \
4211                % ", ".join(FIREWALL_NET_OPTIONS.keys()))
4212        request.end_headers()
4213        return
4214   
4215    # Update the policy
4216    pref_set("traffic", "%s_policy" % network, policy, monitor_prefs)
4217   
4218    # Now update the actual firewall
4219    request.send_response(200, "Policy updated")
4220    request.end_headers()
4221
4222    doFirewall("start")
4223
4224@registerPage("/traffic/firewall", requireAuth=True, realm=CPE_ADMIN_REALM)
4225def trafficfirewall(request, method):
4226    """Manipulates the firewall"""
4227
4228    parts = request.path.split("/")
4229    mode = parts[len(parts)-1]
4230
4231    try:
4232        doFirewall(mode)
4233    except:
4234        (type, value, tb) = sys.exc_info()
4235        log_debug("Firewall manipulation request failed!", (type, value, tb))
4236   
4237    # Redirect the admin page
4238    request.send_response(302, "Firewall updated")
4239    request.send_header("Location", "/rladmin")
4240    request.end_headers()
4241    request.wfile.write(rladmin(request, method, False))
4242   
4243@registerPage("/traffic/admin", requireAuth=True, realm=CPE_ADMIN_REALM)
4244def trafficadmin(request, method):
4245    """Returns the HTML for the network traffic administration page"""
4246    global rl_trafficusers, monitor_prefs
4247   
4248    output = """<div class="content"><h2>IP Traffic Administration</h2>"""
4249    output += "<br />"
4250   
4251    # Setup actions and status fields
4252    tusers = []
4253    for username,user in rl_trafficusers.items():
4254        tuser = {}
4255        if not user["enabled"]:
4256            tuser["status"] = "critical"
4257            tuser["statusdesc"] = "Disabled"
4258            toggle_state = "enable"
4259        else:
4260            tuser["status"] = "ok"
4261            if user["ip"] is None:
4262                tuser["statusdesc"] = "Enabled"
4263            else:
4264                tuser["statusdesc"] = "<span title=\"Logged in from %s\">" \
4265                        "Logged In</span>" % user["ip"]
4266            toggle_state = "disable"
4267        actions = """<a href="/traffic/chpass/%s">[change password]</a>""" \
4268                """<a href="/traffic/toggle/%s">[%s account]</a>""" \
4269                """<a href="/traffic/remove/%s">[remove account]</a>""" % \
4270                (username, username, toggle_state, username)
4271        tuser["actions"] = actions
4272        tuser["traffic"] = formatBytes(user["traffic"])
4273        tuser["username"] = user["username"]
4274        tusers.append(tuser)
4275    tusers.sort(trafficuser_sort)
4276   
4277    output += HTable(tusers, ["username", "status", "traffic", "actions"], \
4278            ["Username", "Status", "Traffic", "Actions"])
4279   
4280    output += "<br />"
4281    output += "<a href=\"/traffic/add\">Add New User &gt;&gt;</a><br />"
4282    output += "</div>"
4283
4284    returnPage(request, "Traffic Administration", output)
4285   
4286def trafficuser_sort(a, b):
4287    return cmp(a["username"], b["username"])
Note: See TracBrowser for help on using the repository browser.