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
Line 
1#!/usr/bin/env python
2#
3# vim:ts=2:et:sw=2:ai
4# Wireless Leiden configuration generator, based on yaml files'
5#
6# XXX: This should be rewritten to make use of the ipaddr.py library.
7#
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#
15# Rick van der Zwet <info@rickvanderzwet.nl>
16#
17
18# Hack to make the script directory is also threated as a module search path.
19import sys
20import os
21import re
22sys.path.append(os.path.dirname(__file__))
23
24import cgi
25import cgitb
26import copy
27import glob
28import socket
29import string
30import subprocess
31import time
32import rdnap
33from pprint import pprint
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)
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
47from jinja2 import Template
48
49import logging
50logging.basicConfig(format='# %(levelname)s: %(message)s' )
51logger = logging.getLogger()
52logger.setLevel(logging.DEBUG)
53
54
55if os.environ.has_key('CONFIGROOT'):
56 NODE_DIR = os.environ['CONFIGROOT']
57else:
58 NODE_DIR = os.path.abspath(os.path.dirname(__file__)) + '/../nodes'
59__version__ = '$Id: gformat.py 10156 2012-03-14 06:46:40Z rick $'
60
61
62files = [
63 'authorized_keys',
64 'dnsmasq.conf',
65 'rc.conf.local',
66 'resolv.conf',
67 'motd',
68 'wleiden.yaml',
69 ]
70
71# Global variables uses
72OK = 10
73DOWN = 20
74UNKNOWN = 90
75
76
77def get_proxylist():
78 """Get all available proxies proxyX sorting based on X number"""
79 proxylist = sorted([os.path.basename(x) for x in glob.glob("%s/proxy*" % NODE_DIR)],
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
86def valid_addr(addr):
87 """ Show which address is valid in which are not """
88 return str(addr).startswith('172.')
89
90
91def get_nodelist():
92 """ Get all available nodes - sorted """
93 nodelist = sorted([os.path.basename(x) for x in glob.glob("%s/CNode*" % NODE_DIR)])
94 return nodelist
95
96def get_hostlist():
97 """ Combined hosts and proxy list"""
98 return get_nodelist() + get_proxylist()
99
100def angle_between_points(lat1,lat2,long1,long2):
101 """
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
109
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"
122 elif degrees < (90 - p):
123 return "no"
124 elif degrees < (90 + p):
125 return "o"
126 elif degrees < (180 - p):
127 return "zo"
128 elif degrees < (180 + p):
129 return "z"
130 elif degrees < (270 - p):
131 return "zw"
132 elif degrees < (270 + p):
133 return "w"
134 elif degrees < (360 - p):
135 return "nw"
136 else:
137 return "n"
138
139
140def generate_title(nodelist):
141 """ Main overview page """
142 items = {'root' : "." }
143 output = """
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>
156 <form type="GET" action="%(root)s">
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
163
164 for node in nodelist:
165 items['node'] = node
166 output += '<tr><td><a href="%(root)s/%(node)s">%(node)s</a></td>' % items
167 for config in files:
168 items['config'] = config
169 output += '<td><a href="%(root)s/%(node)s/%(config)s">%(config)s</a></td>' % items
170 output += "</tr>"
171 output += """
172 </table>
173 <hr />
174 <em>%s</em>
175 </center>
176 </body>
177</html>
178 """ % __version__
179
180 return output
181
182
183
184def generate_node(node):
185 """ Print overview of all files available for node """
186 return "\n".join(files)
187
188
189
190def generate_header(ctag="#"):
191 return """\
192%(ctag)s
193%(ctag)s DO NOT EDIT - Automatically generated by 'gformat'
194%(ctag)s Generated at %(date)s by %(host)s
195%(ctag)s
196""" % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname() }
197
198
199
200def parseaddr(s):
201 """ Process IPv4 CIDR notation addr to a (binary) number """
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
208
209
210def showaddr(a):
211 """ Display IPv4 addr in (dotted) CIDR notation """
212 return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff)
213
214
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
223
224
225
226
227def netmask2subnet(netmask):
228 """ Given a 'netmask' return corresponding CIDR """
229 return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask))))
230
231
232
233def generate_dnsmasq_conf(datadump):
234 """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """
235 output = generate_header()
236 output += """\
237# DHCP server options
238dhcp-authoritative
239dhcp-fqdn
240domain=dhcp.%(nodename_lower)s.%(domain)s
241domain-needed
242expand-hosts
243log-async=100
244
245# Low memory footprint
246cache-size=10000
247 \n""" % datadump
248
249 for iface_key in datadump['iface_keys']:
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]
253
254 try:
255 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
256 (ip, netmask) = datadump[iface_key]['ip'].split('/')
257 datadump[iface_key]['subnet'] = netmask2subnet(netmask)
258 except (AttributeError, ValueError):
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]
266
267 return output
268
269
270
271def generate_rc_conf_local(datadump):
272 """ Generate configuration file '/etc/rc.conf.local' """
273 datadump['autogen_ileiden_enable'] = 'yes' if datadump['ileiden'] else 'no'
274
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
283 output = generate_header("#");
284 output += Template("""\
285hostname='{{ autogen_fqdn }}.{{ domain }}'
286location='{{ location }}'
287nodetype="{{ nodetype }}"
288
289{% if tproxy -%}
290tproxy_enable='YES'
291tproxy_range='{{ tproxy }}'
292{% else -%}
293tproxy_enable='NO'
294{% endif -%}
295
296{% if nodetype == "Proxy" %}
297#
298# PROXY Configuration
299#
300sshtun_enable="YES"
301sshtun_flags="-R 22{{ "%02i"|format(proxyid) }}:localhost:22"
302
303static_routes="wleiden"
304route_wleiden="-net 172.16.0.0/12 {{ internalroute }}"
305{% if gateway -%}
306defaultrouter="{{ gateway }}"
307{% else -%}
308#defaultrouter="NOTSET"
309{% endif -%}
310
311internalif="{{ internalif }}"
312
313# PROXY iLeiden Configuration
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 }}"
319firewall_script="/etc/ipfw.sh"
320firewall_nat_enable="{{ autogen_ileiden_enable }}"
321{% endif -%}
322
323{% if nodetype == "CNode" %}
324#
325# NODE iLeiden Configuration
326#
327# iLeiden Proxies {{ autogen_ileiden_proxies_names }}
328lvrouted_flags="-u -s s00p3rs3kr3t -m 28 -z {{ autogen_ileiden_proxies }}"
329
330captive_portal_whitelist=""
331captive_portal_interfaces="{{ autogen_dhcp_interfaces }}"
332{% endif %}
333
334""").render(datadump)
335
336 # lo0 configuration:
337 # - 172.32.255.1/32 is the proxy.wleiden.net deflector
338 # - masterip is special as it needs to be assigned to at
339 # least one interface, so if not used assign to lo0
340 addrs_list = { 'lo0' : [("127.0.0.1/8", "LocalHost"), ("172.31.255.1/32","Proxy IP")] }
341 iface_map = {'lo0' : 'lo0'}
342
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
348 if not masterip_used:
349 addrs_list['lo0'].append((datadump['masterip'] + "/32", 'Master IP Not used in interface'))
350
351 for iface_key in datadump['iface_keys']:
352 ifacedump = datadump[iface_key]
353 interface = ifacedump['interface'].split(':')[0]
354 # By default no special interface mapping
355 iface_map[interface] = interface
356
357 # Add interface IP to list
358 item = (ifacedump['ip'], ifacedump['desc'])
359 if addrs_list.has_key(interface):
360 addrs_list[interface].append(item)
361 else:
362 addrs_list[interface] = [item]
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"
373 if ifacedump['mode'] in ['master', 'master-wds']:
374 ifacedump['wlanmode'] = "ap"
375 # Default to 802.11b mode
376 ifacedump['mode'] = '11b'
377 if ifacedump['type'] in ['11a', '11b' '11g']:
378 ifacedump['mode'] = ifacedump['type']
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
390 output += "wlans_%(interface)s='%(autogen_ifname)s'\n" % ifacedump
391 output += ("create_args_%(autogen_ifname)s='wlanmode %(wlanmode)s mode " +\
392 "%(mode)s ssid %(ssid)s %(extra)s channel %(channel)s'\n") % ifacedump
393
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
400 # Print IP address which needs to be assigned over here
401 output += "\n"
402 for iface,addrs in sorted(addrs_list.iteritems()):
403 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
404 ifacedump['iface'] = iface
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]))
407
408 return output
409
410
411
412def get_yaml(item):
413 """ Get configuration yaml for 'item'"""
414 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
415
416 f = open(gfile, 'r')
417 datadump = yaml.load(f,Loader=Loader)
418 datadump['autogen_iface_keys'] = get_interface_keys(datadump)
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)
431 datadump['autogen_item'] = item
432 datadump['autogen_fqdn'] = get_fqdn(datadump)
433 f.close()
434
435 return datadump
436
437def store_yaml(datadump, header=False):
438 """ Store configuration yaml for 'item'"""
439 item = datadump['autogen_item']
440 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
441
442 f = open(gfile, 'w')
443 f.write(generate_wleiden_yaml(datadump, header))
444 f.close()
445
446
447
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
457def get_interface_keys(config):
458 """ Quick hack to get all interface keys, later stage convert this to a iterator """
459 return sorted([elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)])
460
461
462def get_used_ips(configs):
463 """ Return array of all IPs used in config files"""
464 ip_list = []
465 for config in configs:
466 ip_list.append(config['masterip'])
467 for iface_key in get_interface_keys(config):
468 l = config[iface_key]['ip']
469 addr, mask = l.split('/')
470 # Special case do not process
471 if valid_addr(addr):
472 ip_list.append(addr)
473 else:
474 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
475 return sorted(ip_list)
476
477
478
479def generate_resolv_conf(datadump):
480 """ Generate configuration file '/etc/resolv.conf' """
481 output = generate_header("#");
482 output += """\
483search wleiden.net
484# Try local (cache) first
485nameserver 127.0.0.1
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 += """\
494# Proxies are recursive nameservers
495# needs to be in resolv.conf for dnsmasq as well
496""" % datadump
497 for proxy in get_proxylist():
498 proxy_ip = get_yaml(proxy)['masterip']
499 output += "nameserver %-15s # %s\n" % (proxy_ip, proxy)
500
501 return output
502
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
507
508 WWW: %(autogen_fqdn)s.wleiden.net - http://www.wirelessleiden.nl
509 Loc: %(location)s
510
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
529def format_yaml_value(value):
530 """ Get yaml value in right syntax for outputting """
531 if isinstance(value,str):
532 output = '"%s"' % value
533 else:
534 output = value
535 return output
536
537
538
539def format_wleiden_yaml(datadump):
540 """ Special formatting to ensure it is editable"""
541 output = "# Genesis config yaml style\n"
542 output += "# vim:ts=2:et:sw=2:ai\n"
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)):
546 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
547
548 output += "\n\n"
549
550 key_order = [ 'comment', 'interface', 'ip', 'desc', 'sdesc', 'mode', 'type',
551 'extra_type', 'channel', 'ssid', 'dhcp' ]
552
553 for iface_key in sorted(iface_keys):
554 output += "%s:\n" % iface_key
555 for key in key_order + list(sorted(set(datadump[iface_key].keys()) - set(key_order))):
556 if datadump[iface_key].has_key(key):
557 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
558 output += "\n\n"
559
560 return output
561
562
563
564def generate_wleiden_yaml(datadump, header=True):
565 """ Generate (petty) version of wleiden.yaml"""
566 for key in datadump.keys():
567 if key.startswith('autogen_'):
568 del datadump[key]
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
575 output = generate_header("#") if header else ''
576 output += format_wleiden_yaml(datadump)
577 return output
578
579
580def generate_yaml(datadump):
581 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
582
583
584
585def generate_config(node, config, datadump=None):
586 """ Print configuration file 'config' of 'node' """
587 output = ""
588 try:
589 # Load config file
590 if datadump == None:
591 datadump = get_yaml(node)
592
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
600 if config == 'wleiden.yaml':
601 output += generate_wleiden_yaml(datadump)
602 elif config == 'authorized_keys':
603 f = open(os.path.join(NODE_DIR,"global_keys"), 'r')
604 output += f.read()
605 f.close()
606 elif config == 'dnsmasq.conf':
607 output += generate_dnsmasq_conf(datadump_extra)
608 elif config == 'rc.conf.local':
609 output += generate_rc_conf_local(datadump_extra)
610 elif config == 'resolv.conf':
611 output += generate_resolv_conf(datadump_extra)
612 elif config == 'motd':
613 output += generate_motd(datadump_extra)
614 else:
615 assert False, "Config not found!"
616 except IOError, e:
617 output += "[ERROR] Config file not found"
618 return output
619
620
621
622def process_cgi_request():
623 """ When calling from CGI """
624 # Update repository if requested
625 form = cgi.FieldStorage()
626 if form.getvalue("action") == "update":
627 print "Refresh: 5; url=."
628 print "Content-type:text/plain\r\n\r\n",
629 print "[INFO] Updating subverion, please wait..."
630 print subprocess.Popen(['svn', 'cleanup', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
631 print subprocess.Popen(['svn', 'up', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
632 print "[INFO] All done, redirecting in 5 seconds"
633 sys.exit(0)
634
635
636 uri = os.environ['PATH_INFO'].strip('/').split('/')
637 output = ""
638 if not uri[0]:
639 if is_text_request():
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())
645 elif len(uri) == 1:
646 output += "Content-type:text/plain\r\n\r\n"
647 output += generate_node(uri[0])
648 elif len(uri) == 2:
649 output += "Content-type:text/plain\r\n\r\n"
650 output += generate_config(uri[0], uri[1])
651 else:
652 assert False, "Invalid option"
653 print output
654
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)
663
664
665
666def make_dns(output_dir = 'dns'):
667 items = dict()
668
669 # hostname is key, IP is value
670 wleiden_zone = dict()
671 wleiden_cname = dict()
672
673 pool = dict()
674 for node in get_hostlist():
675 logger.info("Processing host %s", node)
676 datadump = get_yaml(node)
677
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
685 # Hacking to get proper DHCP IPs and hostnames
686 for iface_key in get_interface_keys(datadump):
687 iface_name = datadump[iface_key]['interface'].replace(':',"-alias-")
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)
702 if pool.has_key(addr):
703 pool[addr] += [(iface_name, fqdn, ip)]
704 else:
705 pool[addr] = [(iface_name, fqdn, ip)]
706 continue
707
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
724 # WL uses an /29 to configure an interface. IP's are ordered like this:
725 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
726
727 sn = lambda x: re.sub(r'(?i)^cnode','',x)
728
729 # Automatic naming convention of interlinks namely 2 + remote.lower()
730 for (key,value) in pool.iteritems():
731 # Make sure they are sorted from low-ip to high-ip
732 value = sorted(value, key=lambda x: parseaddr(x[2]))
733
734 if len(value) == 1:
735 (iface_name, fqdn, ip) = value[0]
736 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)] = ip
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
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
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)
752 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)] = showaddr(parseaddr(b_ip) - 1)
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
758 else:
759 pool_members = [k[1] for k in value]
760 for item in value:
761 (iface_name, fqdn, ip) = item
762 pool_name = "2pool-" + showaddr(key).replace('.','-') + "-" + pool_to_name(fqdn,pool_members)
763 wleiden_zone["%s.%s" % (pool_name, fqdn)] = ip
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
772 dns = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
773
774 # Hack to allow special entries, for development
775 wleiden_raw = dns['raw']
776 del dns['raw']
777
778 for comment, block in dns.iteritems():
779 for k,v in block.iteritems():
780 if valid_addr(v):
781 wleiden_zone[k] = v
782 else:
783 wleiden_cname[k] = v
784
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
797
798 if not os.path.isdir('dns'):
799 os.makedirs('dns')
800 details['zone'] = 'wleiden.net'
801 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
802 f.write(dns_header % details)
803
804 for host,ip in wleiden_zone.iteritems():
805 if valid_addr(ip):
806 f.write("%s.wleiden.net. IN A %s \n" % (host.lower(), ip))
807 for source,dest in wleiden_cname.iteritems():
808 f.write("%s.wleiden.net. IN CNAME %s.wleiden.net.\n" % (source.lower(), dest.lower()))
809 for source, dest in wleiden_raw.iteritems():
810 f.write("%s.wleiden.net. %s\n" % (source, dest))
811 f.close()
812
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
816 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
817 f.write(dns_header % details)
818
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('.')))
825 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
826 f.close()
827
828
829def usage():
830 print """Usage: %s <standalone [port] |test [test arguments]|static|dns>
831Examples:
832\tdns [outputdir] = Generate BIND compliant zone files in dns.
833\tstandalone = Run configurator webserver [default port=8000]
834\twind-export = Generate SQL import scripts for WIND database
835\tfull-export = Generate yaml export script for heatmap.
836\tstatic = Generate all config files and store on disk
837\t with format ./static/%%NODE%%/%%FILE%%
838\ttest CNodeRick dnsmasq.conf = Receive output of CGI script
839\t for arguments CNodeRick/dnsmasq.conf
840\tlist <nodes|proxies> = List systems which marked up.
841"""
842 exit(0)
843
844
845def is_text_request():
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
851
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()
858
859 if sys.argv[1] == "standalone":
860 import SocketServer
861 import CGIHTTPServer
862 # Hop to the right working directory.
863 os.chdir(os.path.dirname(__file__))
864 try:
865 PORT = int(sys.argv[2])
866 except (IndexError,ValueError):
867 PORT = 8000
868
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
874
875 self.cgi_info = (__file__, self.path)
876 self.path = ''
877 return True
878 handler = MyCGIHTTPRequestHandler
879 SocketServer.TCPServer.allow_reuse_address = True
880 httpd = SocketServer.TCPServer(("", PORT), handler)
881 httpd.server_name = 'localhost'
882 httpd.server_port = PORT
883
884 logger.info("serving at port %s", PORT)
885 try:
886 httpd.serve_forever()
887 except KeyboardInterrupt:
888 httpd.shutdown()
889 logger.info("All done goodbye")
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()
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
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'])
913 datadump = get_yaml(node)
914 for config in files:
915 items['config'] = config
916 logger.info("## Generating %(node)s %(config)s" % items)
917 f = open("%(wdir)s/%(config)s" % items, "w")
918 f.write(generate_config(node, config, datadump))
919 f.close()
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
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
955 elif sys.argv[1] == "dns":
956 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns')
957 elif sys.argv[1] == "cleanup":
958 # First generate all datadumps
959 datadumps = dict()
960 for host in get_hostlist():
961 logger.info("# Processing: %s", host)
962 datadump = get_yaml(host)
963 datadumps[get_fqdn(datadump)] = datadump
964
965 for host,datadump in datadumps.iteritems():
966 datadump['latitude'], datadump['longitude'] = rdnap.rd2etrs(datadump['rdnap_x'], datadump['rdnap_y'])
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:]
974 store_yaml(datadump)
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
986 else:
987 usage()
988 else:
989 # Do not enable debugging for config requests as it highly clutters the output
990 if not is_text_request():
991 cgitb.enable()
992 process_cgi_request()
993
994
995if __name__ == "__main__":
996 main()
Note: See TracBrowser for help on using the repository browser.