#!/usr/bin/env python # # vim:ts=2:et:sw=2:ai # Wireless Leiden configuration generator, based on yaml files' # Rick van der Zwet import cgi import cgitb import copy import glob import os import socket import string import subprocess import sys import time import yaml NODE_DIR = os.path.dirname(os.path.realpath(__file__)) __version__ = '$Id: gformat.py 8298 2010-08-10 13:58:59Z rick $' files = [ 'authorized_keys', 'dnsmasq.conf', 'rc.conf.local', 'resolv.conf', 'wleiden.yaml' ] def get_proxylist(): """Get all available proxies proxyX sorting based on X number""" os.chdir(NODE_DIR) proxylist = sorted(glob.glob("proxy*"), key=lambda name: int(''.join([c for c in name if c in string.digits])), cmp=lambda x,y: x - y) return proxylist def get_nodelist(): """ Get all available nodes - sorted """ os.chdir(NODE_DIR) nodelist = sorted(glob.glob("CNode*")) return nodelist def get_hostlist(): """ Combined hosts and proxy list""" return get_nodelist() + get_proxylist() def generate_title(nodelist): """ Main overview page """ items = {'root' : "." } output = """ Wireless leiden Configurator - GFormat
""" % items for node in nodelist: items['node'] = node output += '' % items for config in files: items['config'] = config output += '' % items output += "" output += """

Wireless Leiden Configurator

%(node)s%(config)s

%s
""" % __version__ return output def generate_node(node): """ Print overview of all files available for node """ return "\n".join(files) def generate_header(ctag="#"): return """\ %(ctag)s %(ctag)s DO NOT EDIT - Automatically generated by 'gformat' %(ctag)s Generated at %(date)s by %(host)s %(ctag)s """ % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname() } def parseaddr(s): """ Process IPv4 CIDR notation addr to a (binary) number """ f = s.split('.') return (long(f[0]) << 24L) + \ (long(f[1]) << 16L) + \ (long(f[2]) << 8L) + \ long(f[3]) def showaddr(a): """ Display IPv4 addr in (dotted) CIDR notation """ return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff) def netmask2subnet(netmask): """ Given a 'netmask' return corresponding CIDR """ return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask)))) def generate_dnsmasq_conf(datadump): """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """ output = generate_header() output += """\ # DHCP server options dhcp-authoritative dhcp-fqdn domain=dhcp.%(nodename_lower)s.%(domain)s domain-needed expand-hosts # Low memory footprint cache-size=10000 \n""" % datadump for iface_key in datadump['iface_keys']: if not datadump[iface_key].has_key('comment'): datadump[iface_key]['comment'] = None output += "## %(interface)s - %(desc)s - %(comment)s\n" % datadump[iface_key] try: (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-') (ip, netmask) = datadump[iface_key]['ip'].split('/') datadump[iface_key]['subnet'] = netmask2subnet(netmask) except (AttributeError, ValueError): output += "# not autoritive\n\n" continue dhcp_part = ".".join(ip.split('.')[0:3]) datadump[iface_key]['dhcp_start'] = dhcp_part + "." + dhcp_start datadump[iface_key]['dhcp_stop'] = dhcp_part + "." + dhcp_stop output += "dhcp-range=%(interface)s,%(dhcp_start)s,%(dhcp_stop)s,%(subnet)s,24h\n\n" % datadump[iface_key] return output def generate_rc_conf_local(datadump): """ Generate configuration file '/etc/rc.conf.local' """ output = generate_header("#"); output += """\ hostname='%(nodetype)s%(nodename)s.%(domain)s' location='%(location)s' """ % datadump # TProxy configuration output += "\n" try: if datadump['tproxy']: output += """\ tproxy_enable='YES' tproxy_range='%(tproxy)s' """ % datadump except KeyError: output += "tproxy_enable='NO'\n" output += '\n' # lo0 configuration: # - 172.32.255.1/32 is the proxy.wleiden.net deflector # - masterip is special as it needs to be assigned to at # least one interface, so if not used assign to lo0 addrs_list = { 'lo0' : ["127.0.0.1/8", "172.31.255.1/32"] } iface_map = {'lo0' : 'lo0'} masterip_used = False for iface_key in datadump['iface_keys']: if datadump[iface_key]['ip'].startswith(datadump['masterip']): masterip_used = True break if not masterip_used: addrs_list['lo0'].append(datadump['masterip'] + "/32") wlan_count = 0 for iface_key in datadump['iface_keys']: ifacedump = datadump[iface_key] interface = ifacedump['interface'] # By default no special interface mapping iface_map[interface] = interface # Add interface IP to list if addrs_list.has_key(interface): addrs_list[interface].append(ifacedump['ip']) else: addrs_list[interface] = [ifacedump['ip']] # Alias only needs IP assignment for now, this might change if we # are going to use virtual accesspoints if "alias" in iface_key: continue # XXX: Might want to deduct type directly from interface name if ifacedump['type'] in ['11a', '11b', '11g', 'wireless']: # Create wlanX interface ifacedump['wlanif'] ="wlan%i" % wlan_count iface_map[interface] = ifacedump['wlanif'] wlan_count += 1 # Default to station (client) mode ifacedump['wlanmode'] = "sta" if ifacedump['mode'] in ['master', 'master-wds']: ifacedump['wlanmode'] = "ap" # Default to 802.11b mode ifacedump['mode'] = '11b' if ifacedump['type'] in ['11a', '11b' '11g']: ifacedump['mode'] = ifacedump['type'] if not ifacedump.has_key('channel'): if ifacedump['type'] == '11a': ifacedump['channel'] = 36 else: ifacedump['channel'] = 1 # Allow special hacks at the back like wds and stuff if not ifacedump.has_key('extra'): ifacedump['extra'] = 'regdomain ETSI country NL' output += "wlans_%(interface)s='%(wlanif)s'\n" % ifacedump output += ("create_args_%(wlanif)s='wlanmode %(wlanmode)s mode " +\ "%(mode)s ssid %(ssid)s %(extra)s channel %(channel)s'\n") % ifacedump elif ifacedump['type'] in ['ethernet', 'eth']: # No special config needed besides IP pass else: assert False, "Unknown type " + ifacedump['type'] # Print IP address which needs to be assigned over here output += "\n" for iface,addrs in sorted(addrs_list.iteritems()): output += "ipv4_addrs_%s='%s'\n" % (iface_map[iface], " ".join(addrs)) return output def get_yaml(item): """ Get configuration yaml for 'item'""" gfile = NODE_DIR + '/%s/wleiden.yaml' % item f = open(gfile, 'r') datadump = yaml.load(f) f.close() return datadump def write_yaml(item, datadump): """ Write configuration yaml for 'item'""" gfile = NODE_DIR + '/%s/wleiden.yaml' % item f = open(gfile, 'w') f.write(format_wleiden_yaml(datadump)) f.close() def generate_resolv_conf(datadump): """ Generate configuration file '/etc/resolv.conf' """ output = generate_header("#"); output += """\ search wleiden.net # Try local (cache) first nameserver 127.0.0.1 # Proxies are recursive nameservers # needs to be in resolv.conf for dnsmasq as well """ % datadump for proxy in get_proxylist(): proxy_ip = get_yaml(proxy)['masterip'] output += "nameserver %-15s # %s\n" % (proxy_ip, proxy) return output def format_yaml_value(value): """ Get yaml value in right syntax for outputting """ if isinstance(value,str): output = "'%s'" % value else: output = value return output def format_wleiden_yaml(datadump): """ Special formatting to ensure it is editable""" output = "# Genesis config yaml style\n" output += "# vim:ts=2:et:sw=2:ai\n" output += "#\n" iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')] for key in sorted(set(datadump.keys()) - set(iface_keys)): output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key])) output += "\n\n" key_order = [ 'comment', 'interface', 'ip', 'desc', 'sdesc', 'mode', 'type', 'extra_type', 'channel', 'ssid', 'dhcp' ] for iface_key in sorted(iface_keys): output += "%s:\n" % iface_key for key in key_order + list(sorted(set(datadump[iface_key].keys()) - set(key_order))): if datadump[iface_key].has_key(key): output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key])) output += "\n\n" return output def generate_wleiden_yaml(datadump): """ Generate (petty) version of wleiden.yaml""" output = generate_header("#") output += format_wleiden_yaml(datadump) return output def generate_config(node, config, datadump=None): """ Print configuration file 'config' of 'node' """ output = "" try: # Load config file if datadump == None: datadump = get_yaml(node) # Preformat certain needed variables for formatting and push those into special object datadump_extra = copy.deepcopy(datadump) if not datadump_extra.has_key('domain'): datadump_extra['domain'] = 'wleiden.net' datadump_extra['nodename_lower'] = datadump_extra['nodename'].lower() datadump_extra['iface_keys'] = sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]) if config == 'wleiden.yaml': output += generate_wleiden_yaml(datadump) elif config == 'authorized_keys': f = open("global_keys", 'r') output += f.read() f.close() elif config == 'dnsmasq.conf': output += generate_dnsmasq_conf(datadump_extra) elif config == 'rc.conf.local': output += generate_rc_conf_local(datadump_extra) elif config == 'resolv.conf': output += generate_resolv_conf(datadump_extra) else: assert False, "Config not found!" except IOError, e: output += "[ERROR] Config file not found" return output def process_cgi_request(): """ When calling from CGI """ # Update repository if requested form = cgi.FieldStorage() if form.getvalue("action") == "update": print "Refresh: 5; url=." print "Content-type:text/plain\r\n\r\n", print "[INFO] Updating subverion, please wait..." print subprocess.Popen(['svn', 'up', NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0], print "[INFO] All done, redirecting in 5 seconds" sys.exit(0) uri = os.environ['PATH_INFO'].strip('/').split('/') output = "" if not uri[0]: output += "Content-type:text/html\r\n\r\n" output += generate_title(get_hostlist()) elif len(uri) == 1: output += "Content-type:text/plain\r\n\r\n" output += generate_node(uri[0]) elif len(uri) == 2: output += "Content-type:text/plain\r\n\r\n" output += generate_config(uri[0], uri[1]) else: assert False, "Invalid option" print output def usage(): print """Usage: %s Examples: \tstandalone = Run configurator webserver [default port=8000] \tstatic = Generate all config files and store on disk \t with format ./static/%%NODE%%/%%FILE%% \ttest CNodeRick dnsmasq.conf = Receive output of CGI script \t for arguments CNodeRick/dnsmasq.conf """ exit(0) def main(): """Hard working sub""" # Allow easy hacking using the CLI if not os.environ.has_key('PATH_INFO'): if len(sys.argv) < 2: usage() if sys.argv[1] == "standalone": import SocketServer import CGIHTTPServer try: PORT = int(sys.argv[2]) except (IndexError,ValueError): PORT = 8000 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): """ Serve this CGI from the root of the webserver """ def is_cgi(self): if "favicon" in self.path: return False self.cgi_info = (__file__, self.path) self.path = '' return True handler = MyCGIHTTPRequestHandler httpd = SocketServer.TCPServer(("", PORT), handler) httpd.server_name = 'localhost' httpd.server_port = PORT print "serving at port", PORT httpd.serve_forever() elif sys.argv[1] == "test": os.environ['PATH_INFO'] = "/".join(sys.argv[2:]) os.environ['SCRIPT_NAME'] = __file__ process_cgi_request() elif sys.argv[1] == "static": items = dict() for node in get_hostlist(): items['node'] = node items['wdir'] = "./static/%(node)s" % items if not os.path.isdir(items['wdir']): os.makedirs(items['wdir']) datadump = get_yaml(node) for config in files: items['config'] = config print "## Generating %(node)s %(config)s" % items f = open("%(wdir)s/%(config)s" % items, "w") f.write(generate_config(node, config, datadump)) f.close() else: usage() else: cgitb.enable() process_cgi_request() if __name__ == "__main__": main()