source: genesis/tools/gformat.py@ 10160

Last change on this file since 10160 was 10156, checked in by rick, 13 years ago

WL has an SSID naming scheme, so make sure to check and correct on it.

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 31.1 KB
RevLine 
[8242]1#!/usr/bin/env python
2#
3# vim:ts=2:et:sw=2:ai
4# Wireless Leiden configuration generator, based on yaml files'
[9957]5#
6# XXX: This should be rewritten to make use of the ipaddr.py library.
7#
[10058]8# Sample apache configuration (mind the AcceptPathInfo!)
9# ScriptAlias /wleiden/config /usr/local/www/genesis/tools/gformat.py
10# <Directory /usr/local/www/genesis>
11# Allow from all
12# AcceptPathInfo On
13# </Directory>
14#
[8242]15# Rick van der Zwet <info@rickvanderzwet.nl>
[9957]16#
[8622]17
18# Hack to make the script directory is also threated as a module search path.
19import sys
20import os
[9286]21import re
[8622]22sys.path.append(os.path.dirname(__file__))
23
[8242]24import cgi
[8267]25import cgitb
26import copy
[8242]27import glob
28import socket
29import string
30import subprocess
31import time
[8622]32import rdnap
[8584]33from pprint import pprint
[8575]34try:
35 import yaml
36except ImportError, e:
37 print e
38 print "[ERROR] Please install the python-yaml or devel/py-yaml package"
39 exit(1)
[8588]40
41try:
42 from yaml import CLoader as Loader
43 from yaml import CDumper as Dumper
44except ImportError:
45 from yaml import Loader, Dumper
46
[10110]47from jinja2 import Template
48
[9697]49import logging
50logging.basicConfig(format='# %(levelname)s: %(message)s' )
51logger = logging.getLogger()
52logger.setLevel(logging.DEBUG)
[8242]53
[9283]54
[8948]55if os.environ.has_key('CONFIGROOT'):
56 NODE_DIR = os.environ['CONFIGROOT']
57else:
[9283]58 NODE_DIR = os.path.abspath(os.path.dirname(__file__)) + '/../nodes'
[8242]59__version__ = '$Id: gformat.py 10156 2012-03-14 06:46:40Z rick $'
60
[8267]61
[9283]62files = [
[8242]63 'authorized_keys',
64 'dnsmasq.conf',
65 'rc.conf.local',
66 'resolv.conf',
[10069]67 'motd',
[10054]68 'wleiden.yaml',
[8242]69 ]
70
[8319]71# Global variables uses
[8323]72OK = 10
73DOWN = 20
74UNKNOWN = 90
[8257]75
76
[8267]77def get_proxylist():
78 """Get all available proxies proxyX sorting based on X number"""
[10041]79 proxylist = sorted([os.path.basename(x) for x in glob.glob("%s/proxy*" % NODE_DIR)],
[8267]80 key=lambda name: int(''.join([c for c in name if c in string.digits])),
81 cmp=lambda x,y: x - y)
82 return proxylist
83
84
85
[8321]86def valid_addr(addr):
87 """ Show which address is valid in which are not """
88 return str(addr).startswith('172.')
89
90
[8267]91def get_nodelist():
92 """ Get all available nodes - sorted """
[10041]93 nodelist = sorted([os.path.basename(x) for x in glob.glob("%s/CNode*" % NODE_DIR)])
[8267]94 return nodelist
95
[8296]96def get_hostlist():
97 """ Combined hosts and proxy list"""
98 return get_nodelist() + get_proxylist()
[8267]99
[8588]100def angle_between_points(lat1,lat2,long1,long2):
[9283]101 """
[8588]102 Return Angle in radians between two GPS coordinates
103 See: http://stackoverflow.com/questions/3809179/angle-between-2-gps-coordinates
104 """
105 dy = lat2 - lat1
106 dx = math.cos(math.pi/180*lat1)*(long2 - long1)
107 angle = math.atan2(dy,dx)
108 return angle
[8267]109
[8588]110def angle_to_cd(angle):
111 """ Return Dutch Cardinal Direction estimation in 'one digit' of radian angle """
112
113 # For easy conversion get positive degree
114 degrees = math.degrees(angle)
115 if degrees < 0:
116 360 - abs(degrees)
117
118 # Numbers can be confusing calculate from the 4 main directions
119 p = 22.5
120 if degrees < p:
121 return "n"
[9283]122 elif degrees < (90 - p):
[8588]123 return "no"
[9283]124 elif degrees < (90 + p):
[8588]125 return "o"
[9283]126 elif degrees < (180 - p):
[8588]127 return "zo"
[9283]128 elif degrees < (180 + p):
[8588]129 return "z"
[9283]130 elif degrees < (270 - p):
[8588]131 return "zw"
[9283]132 elif degrees < (270 + p):
[8588]133 return "w"
[9283]134 elif degrees < (360 - p):
[8588]135 return "nw"
136 else:
137 return "n"
138
139
[8267]140def generate_title(nodelist):
[8257]141 """ Main overview page """
[9283]142 items = {'root' : "." }
[8267]143 output = """
[8257]144<html>
145 <head>
146 <title>Wireless leiden Configurator - GFormat</title>
147 <style type="text/css">
148 th {background-color: #999999}
149 tr:nth-child(odd) {background-color: #cccccc}
150 tr:nth-child(even) {background-color: #ffffff}
151 th, td {padding: 0.1em 1em}
152 </style>
153 </head>
154 <body>
155 <center>
[8259]156 <form type="GET" action="%(root)s">
[8257]157 <input type="hidden" name="action" value="update">
158 <input type="submit" value="Update Configuration Database (SVN)">
159 </form>
160 <table>
161 <caption><h3>Wireless Leiden Configurator</h3></caption>
162 """ % items
[8242]163
[8296]164 for node in nodelist:
[8257]165 items['node'] = node
[8267]166 output += '<tr><td><a href="%(root)s/%(node)s">%(node)s</a></td>' % items
[8257]167 for config in files:
168 items['config'] = config
[8267]169 output += '<td><a href="%(root)s/%(node)s/%(config)s">%(config)s</a></td>' % items
170 output += "</tr>"
171 output += """
[8257]172 </table>
173 <hr />
174 <em>%s</em>
175 </center>
176 </body>
177</html>
178 """ % __version__
[8242]179
[8267]180 return output
[8257]181
182
[8267]183
184def generate_node(node):
[8257]185 """ Print overview of all files available for node """
[8267]186 return "\n".join(files)
[8242]187
[8257]188
189
[8242]190def generate_header(ctag="#"):
191 return """\
[9283]192%(ctag)s
[8242]193%(ctag)s DO NOT EDIT - Automatically generated by 'gformat'
194%(ctag)s Generated at %(date)s by %(host)s
[9283]195%(ctag)s
[8242]196""" % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname() }
197
[8257]198
199
[8242]200def parseaddr(s):
[8257]201 """ Process IPv4 CIDR notation addr to a (binary) number """
[8242]202 f = s.split('.')
203 return (long(f[0]) << 24L) + \
204 (long(f[1]) << 16L) + \
205 (long(f[2]) << 8L) + \
206 long(f[3])
207
[8257]208
209
[8242]210def showaddr(a):
[8257]211 """ Display IPv4 addr in (dotted) CIDR notation """
[8242]212 return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff)
213
[8257]214
[8584]215def is_member(ip, mask, canidate):
216 """ Return True if canidate is part of ip/mask block"""
217 ip_addr = gformat.parseaddr(ip)
218 ip_canidate = gformat.parseaddr(canidate)
219 mask = int(mask)
220 ip_addr = ip_addr & ~((1 << (32 - mask)) - 1)
221 ip_canidate = ip_canidate & ~((1 << (32 - mask)) - 1)
222 return ip_addr == ip_canidate
[8257]223
[8584]224
225
[9283]226
[8242]227def netmask2subnet(netmask):
[8257]228 """ Given a 'netmask' return corresponding CIDR """
[8242]229 return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask))))
230
[8257]231
232
[8242]233def generate_dnsmasq_conf(datadump):
[8257]234 """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """
[8242]235 output = generate_header()
236 output += """\
[9283]237# DHCP server options
[8242]238dhcp-authoritative
239dhcp-fqdn
240domain=dhcp.%(nodename_lower)s.%(domain)s
241domain-needed
242expand-hosts
[10120]243log-async=100
[8242]244
245# Low memory footprint
246cache-size=10000
247 \n""" % datadump
248
249 for iface_key in datadump['iface_keys']:
[8262]250 if not datadump[iface_key].has_key('comment'):
251 datadump[iface_key]['comment'] = None
252 output += "## %(interface)s - %(desc)s - %(comment)s\n" % datadump[iface_key]
[8242]253
254 try:
[8257]255 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
[8242]256 (ip, netmask) = datadump[iface_key]['ip'].split('/')
257 datadump[iface_key]['subnet'] = netmask2subnet(netmask)
[8262]258 except (AttributeError, ValueError):
[8242]259 output += "# not autoritive\n\n"
260 continue
261
262 dhcp_part = ".".join(ip.split('.')[0:3])
263 datadump[iface_key]['dhcp_start'] = dhcp_part + "." + dhcp_start
264 datadump[iface_key]['dhcp_stop'] = dhcp_part + "." + dhcp_stop
265 output += "dhcp-range=%(interface)s,%(dhcp_start)s,%(dhcp_stop)s,%(subnet)s,24h\n\n" % datadump[iface_key]
[9283]266
[8242]267 return output
268
[8257]269
270
[8242]271def generate_rc_conf_local(datadump):
[8257]272 """ Generate configuration file '/etc/rc.conf.local' """
[10112]273 datadump['autogen_ileiden_enable'] = 'yes' if datadump['ileiden'] else 'no'
[10110]274
[10112]275 ileiden_proxies = []
276 for proxy in get_proxylist():
277 proxydump = get_yaml(proxy)
278 if proxydump['ileiden']:
279 ileiden_proxies.append(proxydump)
280 datadump['autogen_ileiden_proxies'] = ','.join([x['masterip'] for x in ileiden_proxies])
281 datadump['autogen_ileiden_proxies_names'] = ','.join([x['autogen_item'] for x in ileiden_proxies])
282
[8242]283 output = generate_header("#");
[10110]284 output += Template("""\
285hostname='{{ autogen_fqdn }}.{{ domain }}'
286location='{{ location }}'
287nodetype="{{ nodetype }}"
[9283]288
[10110]289{% if tproxy -%}
[8242]290tproxy_enable='YES'
[10110]291tproxy_range='{{ tproxy }}'
292{% else -%}
293tproxy_enable='NO'
294{% endif -%}
[9283]295
[10110]296{% if nodetype == "Proxy" %}
[10054]297#
298# PROXY Configuration
299#
300sshtun_enable="YES"
[10110]301sshtun_flags="-R 22{{ "%02i"|format(proxyid) }}:localhost:22"
[10053]302
[10054]303static_routes="wleiden"
[10110]304route_wleiden="-net 172.16.0.0/12 {{ internalroute }}"
305{% if gateway -%}
306defaultrouter="{{ gateway }}"
307{% else -%}
308#defaultrouter="NOTSET"
309{% endif -%}
[10053]310
[10110]311internalif="{{ internalif }}"
[10054]312
313# PROXY iLeiden Configuration
[10112]314lvrouted_enable="{{ autogen_ileiden_enable }}"
315lvrouted_flags="-u -s s00p3rs3kr3t -m 28"
316ileiden_enable="{{ autogen_ileiden_enable }}"
317gateway_enable="{{ autogen_ileiden_enable }}"
318firewall_enable="{{ autogen_ileiden_enable }}"
[10054]319firewall_script="/etc/ipfw.sh"
[10112]320firewall_nat_enable="{{ autogen_ileiden_enable }}"
[10110]321{% endif -%}
[10054]322
[10110]323{% if nodetype == "CNode" %}
[10112]324#
[10054]325# NODE iLeiden Configuration
[10112]326#
327# iLeiden Proxies {{ autogen_ileiden_proxies_names }}
328lvrouted_flags="-u -s s00p3rs3kr3t -m 28 -z {{ autogen_ileiden_proxies }}"
329
[10054]330captive_portal_whitelist=""
[10110]331captive_portal_interfaces="{{ autogen_dhcp_interfaces }}"
332{% endif %}
[10054]333
[10110]334""").render(datadump)
335
[8242]336 # lo0 configuration:
337 # - 172.32.255.1/32 is the proxy.wleiden.net deflector
[9283]338 # - masterip is special as it needs to be assigned to at
[8242]339 # least one interface, so if not used assign to lo0
[9808]340 addrs_list = { 'lo0' : [("127.0.0.1/8", "LocalHost"), ("172.31.255.1/32","Proxy IP")] }
[9283]341 iface_map = {'lo0' : 'lo0'}
[8242]342
[8297]343 masterip_used = False
344 for iface_key in datadump['iface_keys']:
345 if datadump[iface_key]['ip'].startswith(datadump['masterip']):
346 masterip_used = True
347 break
[9283]348 if not masterip_used:
[10108]349 addrs_list['lo0'].append((datadump['masterip'] + "/32", 'Master IP Not used in interface'))
[8297]350
[8242]351 for iface_key in datadump['iface_keys']:
352 ifacedump = datadump[iface_key]
[10079]353 interface = ifacedump['interface'].split(':')[0]
[8242]354 # By default no special interface mapping
355 iface_map[interface] = interface
356
357 # Add interface IP to list
[9808]358 item = (ifacedump['ip'], ifacedump['desc'])
[8242]359 if addrs_list.has_key(interface):
[9808]360 addrs_list[interface].append(item)
[8242]361 else:
[9808]362 addrs_list[interface] = [item]
[8242]363
364 # Alias only needs IP assignment for now, this might change if we
365 # are going to use virtual accesspoints
366 if "alias" in iface_key:
367 continue
368
369 # XXX: Might want to deduct type directly from interface name
370 if ifacedump['type'] in ['11a', '11b', '11g', 'wireless']:
371 # Default to station (client) mode
372 ifacedump['wlanmode'] = "sta"
[8274]373 if ifacedump['mode'] in ['master', 'master-wds']:
[8242]374 ifacedump['wlanmode'] = "ap"
375 # Default to 802.11b mode
376 ifacedump['mode'] = '11b'
377 if ifacedump['type'] in ['11a', '11b' '11g']:
[9283]378 ifacedump['mode'] = ifacedump['type']
[8242]379
380 if not ifacedump.has_key('channel'):
381 if ifacedump['type'] == '11a':
382 ifacedump['channel'] = 36
383 else:
384 ifacedump['channel'] = 1
385
386 # Allow special hacks at the back like wds and stuff
387 if not ifacedump.has_key('extra'):
388 ifacedump['extra'] = 'regdomain ETSI country NL'
389
[10054]390 output += "wlans_%(interface)s='%(autogen_ifname)s'\n" % ifacedump
391 output += ("create_args_%(autogen_ifname)s='wlanmode %(wlanmode)s mode " +\
[8274]392 "%(mode)s ssid %(ssid)s %(extra)s channel %(channel)s'\n") % ifacedump
[9283]393
[8242]394 elif ifacedump['type'] in ['ethernet', 'eth']:
395 # No special config needed besides IP
396 pass
397 else:
398 assert False, "Unknown type " + ifacedump['type']
399
[9283]400 # Print IP address which needs to be assigned over here
[8242]401 output += "\n"
402 for iface,addrs in sorted(addrs_list.iteritems()):
[10079]403 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
[10069]404 ifacedump['iface'] = iface
[9808]405 output += "# %s || %s || %s\n" % (iface, addr, comment)
406 output += "ipv4_addrs_%s='%s'\n\n" % (iface_map[iface], " ".join([x[0] for x in addrs]))
[8242]407
408 return output
409
[8257]410
411
[8242]412def get_yaml(item):
[8257]413 """ Get configuration yaml for 'item'"""
[9284]414 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
[8242]415
416 f = open(gfile, 'r')
[8588]417 datadump = yaml.load(f,Loader=Loader)
[10052]418 datadump['autogen_iface_keys'] = get_interface_keys(datadump)
[10054]419
420 wlan_count=0
421 for key in datadump['autogen_iface_keys']:
422 if datadump[key]['type'] in ['11a', '11b', '11g', 'wireless']:
423 datadump[key]['autogen_ifname'] = 'wlan%i' % wlan_count
424 wlan_count += 1
425 else:
426 datadump[key]['autogen_ifname'] = datadump[key]['interface']
427
428
429 dhcp_interfaces = [datadump[key]['autogen_ifname'] for key in datadump['autogen_iface_keys'] if datadump[key]['dhcp'] != 'no']
430 datadump['autogen_dhcp_interfaces'] = ' '.join(dhcp_interfaces)
[10053]431 datadump['autogen_item'] = item
[10054]432 datadump['autogen_fqdn'] = get_fqdn(datadump)
[8242]433 f.close()
434
435 return datadump
436
[10074]437def store_yaml(datadump, header=False):
[8588]438 """ Store configuration yaml for 'item'"""
[10053]439 item = datadump['autogen_item']
[9284]440 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
[8257]441
[8588]442 f = open(gfile, 'w')
[10067]443 f.write(generate_wleiden_yaml(datadump, header))
[8588]444 f.close()
[8257]445
[8588]446
447
[8317]448def get_all_configs():
449 """ Get dict with key 'host' with all configs present """
450 configs = dict()
451 for host in get_hostlist():
452 datadump = get_yaml(host)
453 configs[host] = datadump
454 return configs
455
456
[8319]457def get_interface_keys(config):
458 """ Quick hack to get all interface keys, later stage convert this to a iterator """
[10054]459 return sorted([elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)])
[8317]460
[8319]461
[8317]462def get_used_ips(configs):
463 """ Return array of all IPs used in config files"""
464 ip_list = []
[8319]465 for config in configs:
[8317]466 ip_list.append(config['masterip'])
[8319]467 for iface_key in get_interface_keys(config):
[8317]468 l = config[iface_key]['ip']
469 addr, mask = l.split('/')
470 # Special case do not process
[8332]471 if valid_addr(addr):
472 ip_list.append(addr)
473 else:
[9728]474 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
[8317]475 return sorted(ip_list)
476
477
478
[8242]479def generate_resolv_conf(datadump):
[8257]480 """ Generate configuration file '/etc/resolv.conf' """
[8242]481 output = generate_header("#");
482 output += """\
483search wleiden.net
484# Try local (cache) first
485nameserver 127.0.0.1
[10053]486"""
487 if datadump['nodetype'] == 'Proxy':
488 output += """\
489nameserver 8.8.8.8 # Google Public NameServer
490nameserver 8.8.4.4 # Google Public NameServer
491"""
492 else:
493 output += """\
[9283]494# Proxies are recursive nameservers
[8242]495# needs to be in resolv.conf for dnsmasq as well
496""" % datadump
[10053]497 for proxy in get_proxylist():
498 proxy_ip = get_yaml(proxy)['masterip']
499 output += "nameserver %-15s # %s\n" % (proxy_ip, proxy)
[9283]500
[8242]501 return output
502
[10069]503def generate_motd(datadump):
504 """ Generate configuration file '/etc/motd' """
505 output = """\
506FreeBSD 9.0-RELEASE (kernel.wleiden) #0 r230587: Sun Jan 29 17:09:57 CET 2012
[8242]507
[10069]508 WWW: %(autogen_fqdn)s.wleiden.net - http://www.wirelessleiden.nl
[10113]509 Loc: %(location)s
[8257]510
[10069]511Interlinks:
512""" % datadump
513
514 # XXX: This is a hacky way to get the required data
515 for line in generate_rc_conf_local(datadump).split('\n'):
516 if '||' in line and not line[1:].split()[0] in ['lo0', 'ath0'] :
517 output += " - %s \n" % line[1:]
518 output += """\
519Attached bridges:
520"""
521 for iface_key in datadump['autogen_iface_keys']:
522 ifacedump = datadump[iface_key]
523 if ifacedump.has_key('ns_ip'):
524 output += " - %(interface)s || %(mode)s || %(ns_ip)s\n" % ifacedump
525
526 return output
527
528
[8267]529def format_yaml_value(value):
530 """ Get yaml value in right syntax for outputting """
531 if isinstance(value,str):
[10049]532 output = '"%s"' % value
[8267]533 else:
534 output = value
[9283]535 return output
[8267]536
537
538
539def format_wleiden_yaml(datadump):
[8242]540 """ Special formatting to ensure it is editable"""
[9283]541 output = "# Genesis config yaml style\n"
[8262]542 output += "# vim:ts=2:et:sw=2:ai\n"
[8242]543 output += "#\n"
544 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
545 for key in sorted(set(datadump.keys()) - set(iface_keys)):
[8267]546 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
[9283]547
[8242]548 output += "\n\n"
[9283]549
[8272]550 key_order = [ 'comment', 'interface', 'ip', 'desc', 'sdesc', 'mode', 'type',
551 'extra_type', 'channel', 'ssid', 'dhcp' ]
552
[8242]553 for iface_key in sorted(iface_keys):
554 output += "%s:\n" % iface_key
[8272]555 for key in key_order + list(sorted(set(datadump[iface_key].keys()) - set(key_order))):
556 if datadump[iface_key].has_key(key):
[9283]557 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
[8242]558 output += "\n\n"
559
560 return output
561
562
[8257]563
[10067]564def generate_wleiden_yaml(datadump, header=True):
[8267]565 """ Generate (petty) version of wleiden.yaml"""
[10053]566 for key in datadump.keys():
567 if key.startswith('autogen_'):
568 del datadump[key]
[10054]569 # Interface autogen cleanups
570 elif type(datadump[key]) == dict:
571 for key2 in datadump[key].keys():
572 if key2.startswith('autogen_'):
573 del datadump[key][key2]
574
[10067]575 output = generate_header("#") if header else ''
[8267]576 output += format_wleiden_yaml(datadump)
577 return output
578
579
[8588]580def generate_yaml(datadump):
581 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
[8267]582
[8588]583
[9283]584
[8298]585def generate_config(node, config, datadump=None):
[8257]586 """ Print configuration file 'config' of 'node' """
[8267]587 output = ""
[8242]588 try:
589 # Load config file
[8298]590 if datadump == None:
591 datadump = get_yaml(node)
[9283]592
[8267]593 # Preformat certain needed variables for formatting and push those into special object
594 datadump_extra = copy.deepcopy(datadump)
595 if not datadump_extra.has_key('domain'):
596 datadump_extra['domain'] = 'wleiden.net'
597 datadump_extra['nodename_lower'] = datadump_extra['nodename'].lower()
598 datadump_extra['iface_keys'] = sorted([elem for elem in datadump.keys() if elem.startswith('iface_')])
599
[8242]600 if config == 'wleiden.yaml':
[8267]601 output += generate_wleiden_yaml(datadump)
602 elif config == 'authorized_keys':
[10051]603 f = open(os.path.join(NODE_DIR,"global_keys"), 'r')
[8267]604 output += f.read()
[8242]605 f.close()
606 elif config == 'dnsmasq.conf':
[8267]607 output += generate_dnsmasq_conf(datadump_extra)
[8242]608 elif config == 'rc.conf.local':
[8267]609 output += generate_rc_conf_local(datadump_extra)
[8242]610 elif config == 'resolv.conf':
[8267]611 output += generate_resolv_conf(datadump_extra)
[10069]612 elif config == 'motd':
613 output += generate_motd(datadump_extra)
[8242]614 else:
[9283]615 assert False, "Config not found!"
[8242]616 except IOError, e:
[8267]617 output += "[ERROR] Config file not found"
618 return output
[8242]619
620
[8257]621
[8258]622def process_cgi_request():
623 """ When calling from CGI """
624 # Update repository if requested
625 form = cgi.FieldStorage()
626 if form.getvalue("action") == "update":
[8259]627 print "Refresh: 5; url=."
[8258]628 print "Content-type:text/plain\r\n\r\n",
629 print "[INFO] Updating subverion, please wait..."
[10143]630 print subprocess.Popen(['svn', 'cleanup', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
[10071]631 print subprocess.Popen(['svn', 'up', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
[8258]632 print "[INFO] All done, redirecting in 5 seconds"
633 sys.exit(0)
[9283]634
635
[8258]636 uri = os.environ['PATH_INFO'].strip('/').split('/')
[8267]637 output = ""
[8258]638 if not uri[0]:
[10070]639 if is_text_request():
[10060]640 output += "Content-type:text/plain\r\n\r\n"
641 output += '\n'.join(get_hostlist())
642 else:
643 output += "Content-type:text/html\r\n\r\n"
644 output += generate_title(get_hostlist())
[8258]645 elif len(uri) == 1:
[8267]646 output += "Content-type:text/plain\r\n\r\n"
647 output += generate_node(uri[0])
[8258]648 elif len(uri) == 2:
[8267]649 output += "Content-type:text/plain\r\n\r\n"
650 output += generate_config(uri[0], uri[1])
[8258]651 else:
652 assert False, "Invalid option"
[8267]653 print output
[8242]654
[8588]655def get_fqdn(datadump):
656 # Proxy naming convention is special
657 if datadump['nodetype'] == 'Proxy':
658 fqdn = datadump['nodename']
659 else:
660 # By default the full name is listed and also a shortname CNAME for easy use.
661 fqdn = datadump['nodetype'] + datadump['nodename']
662 return(fqdn)
[8259]663
[9283]664
665
[9284]666def make_dns(output_dir = 'dns'):
[8588]667 items = dict()
[8598]668
[8588]669 # hostname is key, IP is value
670 wleiden_zone = dict()
671 wleiden_cname = dict()
[8598]672
[8588]673 pool = dict()
674 for node in get_hostlist():
[9697]675 logger.info("Processing host %s", node)
[8588]676 datadump = get_yaml(node)
[9283]677
[8588]678 # Proxy naming convention is special
679 fqdn = get_fqdn(datadump)
680 if datadump['nodetype'] == 'CNode':
681 wleiden_cname[datadump['nodename']] = fqdn
682
683 wleiden_zone[fqdn] = datadump['masterip']
684
[8598]685 # Hacking to get proper DHCP IPs and hostnames
[8588]686 for iface_key in get_interface_keys(datadump):
[8598]687 iface_name = datadump[iface_key]['interface'].replace(':',"-alias-")
[8588]688 (ip, netmask) = datadump[iface_key]['ip'].split('/')
689 try:
690 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
691 datadump[iface_key]['subnet'] = netmask2subnet(netmask)
692 dhcp_part = ".".join(ip.split('.')[0:3])
693 if ip != datadump['masterip']:
694 wleiden_zone["dhcp-gateway-%s.%s" % (iface_name, fqdn)] = ip
695 for i in range(int(dhcp_start), int(dhcp_stop) + 1):
696 wleiden_zone["dhcp-%s-%s.%s" % (i, iface_name, fqdn)] = "%s.%s" % (dhcp_part, i)
697 except (AttributeError, ValueError):
698 # First push it into a pool, to indentify the counter-part later on
699 addr = parseaddr(ip)
700 netmask = int(netmask)
701 addr = addr & ~((1 << (32 - netmask)) - 1)
[9283]702 if pool.has_key(addr):
[8588]703 pool[addr] += [(iface_name, fqdn, ip)]
[9283]704 else:
[8588]705 pool[addr] = [(iface_name, fqdn, ip)]
706 continue
707
[9286]708
709 def pool_to_name(node, pool_members):
710 """Convert the joined name to a usable pool name"""
711
712 # Get rid of the own entry
713 pool_members = list(set(pool_members) - set([fqdn]))
714
715 target = oldname = ''
716 for node in sorted(pool_members):
717 (name, number) = re.match('^([A-Za-z]+)([0-9]*)$',node).group(1,2)
718 target += "-" + number if name == oldname else "-" + node if target else node
719 oldname = name
720
721 return target
722
723
[9957]724 # WL uses an /29 to configure an interface. IP's are ordered like this:
[9958]725 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
[9957]726
727 sn = lambda x: re.sub(r'(?i)^cnode','',x)
728
[8598]729 # Automatic naming convention of interlinks namely 2 + remote.lower()
[8588]730 for (key,value) in pool.iteritems():
[9958]731 # Make sure they are sorted from low-ip to high-ip
732 value = sorted(value, key=lambda x: parseaddr(x[2]))
733
[8588]734 if len(value) == 1:
735 (iface_name, fqdn, ip) = value[0]
736 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)] = ip
[9957]737
738 # Device DNS names
739 if 'cnode' in fqdn.lower():
740 wleiden_zone["d-at-%s.%s" % (iface_name, fqdn)] = showaddr(parseaddr(ip) + 1)
741 wleiden_cname["d-at-%s.%s" % (iface_name,sn(fqdn))] = "d-at-%s.%s" % (iface_name, fqdn)
742
[8588]743 elif len(value) == 2:
744 (a_iface_name, a_fqdn, a_ip) = value[0]
745 (b_iface_name, b_fqdn, b_ip) = value[1]
746 wleiden_zone["2%s.%s" % (b_fqdn,a_fqdn)] = a_ip
747 wleiden_zone["2%s.%s" % (a_fqdn,b_fqdn)] = b_ip
[9957]748
749 # Device DNS names
750 if 'cnode' in a_fqdn.lower() and 'cnode' in b_fqdn.lower():
751 wleiden_zone["d-at-%s.%s" % (a_iface_name, a_fqdn)] = showaddr(parseaddr(a_ip) + 1)
[9958]752 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)] = showaddr(parseaddr(b_ip) - 1)
[9957]753 wleiden_cname["d-at-%s.%s" % (a_iface_name,sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
754 wleiden_cname["d-at-%s.%s" % (b_iface_name,sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
755 wleiden_cname["d2%s.%s" % (sn(b_fqdn),sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
756 wleiden_cname["d2%s.%s" % (sn(a_fqdn),sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
757
[8588]758 else:
759 pool_members = [k[1] for k in value]
760 for item in value:
[9283]761 (iface_name, fqdn, ip) = item
[9286]762 pool_name = "2pool-" + showaddr(key).replace('.','-') + "-" + pool_to_name(fqdn,pool_members)
[8588]763 wleiden_zone["%s.%s" % (pool_name, fqdn)] = ip
[8598]764
765 # Include static DNS entries
766 # XXX: Should they override the autogenerated results?
767 # XXX: Convert input to yaml more useable.
768 # Format:
769 ##; this is a comment
770 ## roomburgh=CNodeRoomburgh1
771 ## apkerk1.CNodeVosko=172.17.176.8 ;this as well
[9284]772 dns = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
[9938]773
774 # Hack to allow special entries, for development
775 wleiden_raw = dns['raw']
776 del dns['raw']
777
[8622]778 for comment, block in dns.iteritems():
779 for k,v in block.iteritems():
[8598]780 if valid_addr(v):
781 wleiden_zone[k] = v
782 else:
783 wleiden_cname[k] = v
[9283]784
[8598]785 details = dict()
786 # 24 updates a day allowed
787 details['serial'] = time.strftime('%Y%m%d%H')
788
789 dns_header = '''
790$TTL 3h
791%(zone)s. SOA sunny.wleiden.net. beheer.lijst.wirelessleiden.nl. ( %(serial)s 1d 12h 1w 3h )
792 ; Serial, Refresh, Retry, Expire, Neg. cache TTL
793
794 NS sunny.wleiden.net.
795 \n'''
796
[9283]797
[8598]798 if not os.path.isdir('dns'):
799 os.makedirs('dns')
800 details['zone'] = 'wleiden.net'
[9284]801 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
[8598]802 f.write(dns_header % details)
803
[8588]804 for host,ip in wleiden_zone.iteritems():
[8598]805 if valid_addr(ip):
[9283]806 f.write("%s.wleiden.net. IN A %s \n" % (host.lower(), ip))
[8588]807 for source,dest in wleiden_cname.iteritems():
[8636]808 f.write("%s.wleiden.net. IN CNAME %s.wleiden.net.\n" % (source.lower(), dest.lower()))
[9938]809 for source, dest in wleiden_raw.iteritems():
810 f.write("%s.wleiden.net. %s\n" % (source, dest))
[8588]811 f.close()
[9283]812
[8598]813 # Create whole bunch of specific sub arpa zones. To keep it compliant
814 for s in range(16,32):
815 details['zone'] = '%i.172.in-addr.arpa' % s
[9284]816 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
[8598]817 f.write(dns_header % details)
[8588]818
[8598]819 #XXX: Not effient, fix to proper data structure and do checks at other
820 # stages
821 for host,ip in wleiden_zone.iteritems():
822 if valid_addr(ip):
823 if int(ip.split('.')[1]) == s:
824 rev_ip = '.'.join(reversed(ip.split('.')))
[9283]825 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
[8598]826 f.close()
[8588]827
[8598]828
[8259]829def usage():
[8598]830 print """Usage: %s <standalone [port] |test [test arguments]|static|dns>
[8259]831Examples:
[9284]832\tdns [outputdir] = Generate BIND compliant zone files in dns.
[8259]833\tstandalone = Run configurator webserver [default port=8000]
[9589]834\twind-export = Generate SQL import scripts for WIND database
835\tfull-export = Generate yaml export script for heatmap.
[8296]836\tstatic = Generate all config files and store on disk
837\t with format ./static/%%NODE%%/%%FILE%%
[9283]838\ttest CNodeRick dnsmasq.conf = Receive output of CGI script
[8259]839\t for arguments CNodeRick/dnsmasq.conf
[9971]840\tlist <nodes|proxies> = List systems which marked up.
[8259]841"""
842 exit(0)
843
844
[10070]845def is_text_request():
[10107]846 """ Find out whether we are calling from the CLI or any text based CLI utility """
847 try:
848 return os.environ['HTTP_USER_AGENT'].split()[0] in ['curl', 'fetch', 'wget']
849 except KeyError:
850 return True
[8259]851
[8267]852def main():
853 """Hard working sub"""
854 # Allow easy hacking using the CLI
855 if not os.environ.has_key('PATH_INFO'):
856 if len(sys.argv) < 2:
857 usage()
[9283]858
[8267]859 if sys.argv[1] == "standalone":
860 import SocketServer
861 import CGIHTTPServer
[10105]862 # Hop to the right working directory.
863 os.chdir(os.path.dirname(__file__))
[8267]864 try:
865 PORT = int(sys.argv[2])
866 except (IndexError,ValueError):
867 PORT = 8000
[9283]868
[8267]869 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
870 """ Serve this CGI from the root of the webserver """
871 def is_cgi(self):
872 if "favicon" in self.path:
873 return False
[9283]874
[8267]875 self.cgi_info = (__file__, self.path)
876 self.path = ''
877 return True
878 handler = MyCGIHTTPRequestHandler
[9807]879 SocketServer.TCPServer.allow_reuse_address = True
[8267]880 httpd = SocketServer.TCPServer(("", PORT), handler)
881 httpd.server_name = 'localhost'
882 httpd.server_port = PORT
[9283]883
[9728]884 logger.info("serving at port %s", PORT)
[8860]885 try:
886 httpd.serve_forever()
887 except KeyboardInterrupt:
888 httpd.shutdown()
[9728]889 logger.info("All done goodbye")
[8267]890 elif sys.argv[1] == "test":
891 os.environ['PATH_INFO'] = "/".join(sys.argv[2:])
892 os.environ['SCRIPT_NAME'] = __file__
893 process_cgi_request()
[10107]894 elif sys.argv[1] == "unit-test":
895 os.environ['SCRIPT_NAME'] = __file__
896 for host in get_hostlist():
897 for outfile in files:
898 os.environ['PATH_INFO'] = "/".join([host,outfile])
899 try:
900 process_cgi_request()
901 except Exception:
902 print "# ERROR: %s" % os.environ['PATH_INFO']
903 raise
904
905
[8296]906 elif sys.argv[1] == "static":
907 items = dict()
908 for node in get_hostlist():
909 items['node'] = node
910 items['wdir'] = "./static/%(node)s" % items
911 if not os.path.isdir(items['wdir']):
912 os.makedirs(items['wdir'])
[8298]913 datadump = get_yaml(node)
[8296]914 for config in files:
915 items['config'] = config
[9728]916 logger.info("## Generating %(node)s %(config)s" % items)
[8296]917 f = open("%(wdir)s/%(config)s" % items, "w")
[8298]918 f.write(generate_config(node, config, datadump))
[8296]919 f.close()
[9514]920 elif sys.argv[1] == "wind-export":
921 items = dict()
922 for node in get_hostlist():
923 datadump = get_yaml(node)
924 sql = """INSERT IGNORE INTO nodes (name, name_ns, longitude, latitude)
925 VALUES ('%(nodename)s', '%(nodename)s', %(latitude)s, %(longitude)s);""" % datadump;
926 sql = """INSERT IGNORE INTO users_nodes (user_id, node_id, owner)
927 VALUES (
928 (SELECT id FROM users WHERE username = 'rvdzwet'),
929 (SELECT id FROM nodes WHERE name = '%(nodename)s'),
930 'Y');""" % datadump
931 #for config in files:
932 # items['config'] = config
933 # print "## Generating %(node)s %(config)s" % items
934 # f = open("%(wdir)s/%(config)s" % items, "w")
935 # f.write(generate_config(node, config, datadump))
936 # f.close()
937 for node in get_hostlist():
938 datadump = get_yaml(node)
939 for iface_key in sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]):
940 ifacedump = datadump[iface_key]
941 if ifacedump.has_key('mode') and ifacedump['mode'] == 'ap-wds':
942 ifacedump['nodename'] = datadump['nodename']
943 if not ifacedump.has_key('channel') or not ifacedump['channel']:
944 ifacedump['channel'] = 0
945 sql = """INSERT INTO links (node_id, type, ssid, protocol, channel, status)
946 VALUES ((SELECT id FROM nodes WHERE name = '%(nodename)s'), 'ap',
947 '%(ssid)s', 'IEEE 802.11b', %(channel)s, 'active');""" % ifacedump
[9589]948 elif sys.argv[1] == "full-export":
949 hosts = {}
950 for node in get_hostlist():
951 datadump = get_yaml(node)
952 hosts[datadump['nodename']] = datadump
953 print yaml.dump(hosts)
954
[8584]955 elif sys.argv[1] == "dns":
[9285]956 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns')
[9283]957 elif sys.argv[1] == "cleanup":
[8588]958 # First generate all datadumps
959 datadumps = dict()
960 for host in get_hostlist():
[9728]961 logger.info("# Processing: %s", host)
[8588]962 datadump = get_yaml(host)
963 datadumps[get_fqdn(datadump)] = datadump
[9283]964
[10156]965 for host,datadump in datadumps.iteritems():
[8622]966 datadump['latitude'], datadump['longitude'] = rdnap.rd2etrs(datadump['rdnap_x'], datadump['rdnap_y'])
[10156]967 for iface_key in datadump['autogen_iface_keys']:
968 # Wireless Leiden SSID have an consistent lowercase/uppercase
969 if datadump[iface_key].has_key('ssid'):
970 ssid = datadump[iface_key]['ssid']
971 prefix = 'ap-WirelessLeiden-'
972 if ssid.lower().startswith(prefix.lower()):
973 datadump[iface_key]['ssid'] = prefix + ssid[len(prefix)].upper() + ssid[len(prefix) + 1:]
[10074]974 store_yaml(datadump)
[9971]975 elif sys.argv[1] == "list":
976 if sys.argv[2] == "nodes":
977 systems = get_nodelist()
978 elif sys.argv[2] == "proxies":
979 systems = get_proxylist()
980 else:
981 usage()
982 for system in systems:
983 datadump = get_yaml(system)
984 if datadump['status'] == "up":
985 print system
[9283]986 else:
987 usage()
988 else:
[10070]989 # Do not enable debugging for config requests as it highly clutters the output
990 if not is_text_request():
991 cgitb.enable()
[9283]992 process_cgi_request()
993
994
995if __name__ == "__main__":
996 main()
Note: See TracBrowser for help on using the repository browser.