source: genesis/tools/gformat.py@ 13218

Last change on this file since 13218 was 13218, checked in by huub, 8 years ago

met dank aan Niels: gformat lijstje proxies in rc.conf.local alleen voor status=up van gateways

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 68.6 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# MUCH FASTER WILL IT BE with mod_wsgi, due to caches and avoiding loading all
16# the heavy template lifting all the time.
17#
18# WSGIDaemonProcess gformat threads=25
19# WSGISocketPrefix run/wsgi
20#
21# <Directory /var/www/cgi-bin>
22# WSGIProcessGroup gformat
23# </Directory>
24# WSGIScriptAlias /hello /var/www/cgi-bin/genesis/tools/gformat.py
25#
26# Package dependencies list:
27# yum install python-yaml pyproj proj-epsg python-jinja2
28#
29# Rick van der Zwet <info@rickvanderzwet.nl>
30#
31
32# Hack to make the script directory is also threated as a module search path.
33import sys
34import os
35sys.path.append(os.path.dirname(__file__))
36
37SVN = filter(os.path.isfile, ('/usr/local/bin/svn', '/usr/bin/svn'))[0]
38
39import argparse
40import cgi
41import cgitb
42import copy
43import glob
44import make_network_kml
45import math
46import pyproj
47import random
48import re
49import socket
50import string
51import subprocess
52import textwrap
53import time
54import urlparse
55
56from pprint import pprint
57from collections import defaultdict
58try:
59 import yaml
60except ImportError, e:
61 print e
62 print "[ERROR] Please install the python-yaml or devel/py-yaml package"
63 exit(1)
64
65try:
66 from yaml import CLoader as Loader
67 from yaml import CDumper as Dumper
68except ImportError:
69 from yaml import Loader, Dumper
70
71from jinja2 import Environment, Template
72def yesorno(value):
73 return "YES" if bool(value) else "NO"
74env = Environment()
75env.filters['yesorno'] = yesorno
76def render_template(datadump, template):
77 result = env.from_string(template).render(datadump)
78 # Make it look pretty to the naked eye, as jinja templates are not so
79 # friendly when it comes to whitespace formatting
80 ## Remove extra whitespace at end of line lstrip() style.
81 result = re.sub(r'\n[\ ]+','\n', result)
82 ## Include only a single newline between an definition and a comment
83 result = re.sub(r'(["\'])\n+([a-z]|\n#\n)',r'\1\n\2', result)
84 ## Remove extra newlines after single comment
85 result = re.sub(r'(#\n)\n+([a-z])',r'\1\2', result)
86 return result
87
88import logging
89logging.basicConfig(format='# %(levelname)s: %(message)s' )
90logger = logging.getLogger()
91logger.setLevel(logging.DEBUG)
92
93
94if os.environ.has_key('CONFIGROOT'):
95 NODE_DIR = os.environ['CONFIGROOT']
96else:
97 NODE_DIR = os.path.abspath(os.path.dirname(__file__)) + '/../nodes'
98__version__ = '$Id: gformat.py 13218 2015-03-25 19:45:22Z huub $'
99
100files = [
101 'authorized_keys',
102 'dnsmasq.conf',
103 'dhcpd.conf',
104 'rc.conf.local',
105 'resolv.conf',
106 'motd',
107 'ntp.conf',
108 'pf.hybrid.conf.local',
109 'wleiden.yaml',
110 ]
111
112# Global variables uses
113OK = 10
114DOWN = 20
115UNKNOWN = 90
116
117
118ileiden_proxies = []
119normal_proxies = []
120datadump_cache = {}
121interface_list_cache = {}
122rc_conf_local_cache = {}
123nameservers_cache = []
124def clear_cache():
125 ''' Poor mans cache implementation '''
126 global datadump_cache, interface_list_cache, rc_conf_local_cache, ileiden_proxies, normal_proxies, nameservers_cache
127 datadump_cache = {}
128 interface_list_cache = {}
129 rc_conf_local_cache = {}
130 ileiden_proxies = []
131 normal_proxies = []
132 nameservers_cache = []
133
134NO_DHCP = 0
135DHCP_CLIENT = 10
136DHCP_SERVER = 20
137def dhcp_type(item):
138 if not item.has_key('dhcp'):
139 return NO_DHCP
140 elif not item['dhcp']:
141 return NO_DHCP
142 elif item['dhcp'].lower() == 'client':
143 return DHCP_CLIENT
144 else:
145 # Validation Checks
146 begin,end = map(int,item['dhcp'].split('-'))
147 if begin >= end:
148 raise ValueError("DHCP Start >= DHCP End")
149 return DHCP_SERVER
150
151def etrs2rd(lat, lon):
152 p1 = pyproj.Proj(proj='latlon',datum='WGS84')
153 p2 = pyproj.Proj(init='EPSG:28992')
154 RDx, RDy = pyproj.transform(p1,p2,lon, lat)
155 return (RDx, RDy)
156
157def rd2etrs(RDx, RDy):
158 p1 = pyproj.Proj(init='EPSG:28992')
159 p2 = pyproj.Proj(proj='latlon',datum='WGS84')
160 lon, lat = pyproj.transform(p1,p2, RDx, RDy)
161 return (lat, lon)
162
163def get_yaml(item,add_version_info=True):
164 try:
165 """ Get configuration yaml for 'item'"""
166 if datadump_cache.has_key(item):
167 return datadump_cache[item].copy()
168
169 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
170
171 # Default values
172 datadump = {
173 'autogen_revision' : 'NOTFOUND',
174 'autogen_gfile' : gfile,
175 }
176 f = open(gfile, 'r')
177 datadump.update(yaml.load(f,Loader=Loader))
178 if datadump['nodetype'] == 'Hybrid':
179 # Some values are defined implicitly
180 if datadump.has_key('rdr_rules') and datadump['rdr_rules'] and not datadump.has_key('service_incoming_rdr'):
181 datadump['service_incoming_rdr'] = True
182 # Use some boring defaults
183 defaults = {
184 'service_proxy_normal' : False,
185 'service_proxy_ileiden' : False,
186 'service_accesspoint' : True,
187 'service_incoming_rdr' : False,
188 'service_concentrator' : False,
189 'monitoring_group' : 'wleiden',
190 }
191 for (key,value) in defaults.iteritems():
192 if not datadump.has_key(key):
193 datadump[key] = value
194 f.close()
195
196 # Sometimes getting version information is useless or harmfull, like in the pre-commit hooks
197 if add_version_info:
198 p = subprocess.Popen([SVN, 'info', datadump['autogen_gfile']], stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
199 lines = p.communicate()[0].split('\n')
200 if p.returncode == 0:
201 for line in lines:
202 if line:
203 (key, value) = line.split(': ')
204 datadump["autogen_" + key.lower().replace(' ','_')] = value
205
206 # Preformat certain needed variables for formatting and push those into special object
207 datadump['autogen_iface_keys'] = get_interface_keys(datadump)
208
209 wlan_count=0
210 try:
211 for key in datadump['autogen_iface_keys']:
212 datadump[key]['autogen_ifbase'] = key.split('_')[1]
213 datadump[key]['autogen_gateway'] = datadump[key]['ip'].split('/')[0]
214 if datadump[key]['type'] in ['11a', '11b', '11g', 'wireless']:
215 datadump[key]['autogen_ifname'] = 'wlan%i' % wlan_count
216 wlan_count += 1
217 else:
218 datadump[key]['autogen_ifname'] = datadump[key]['autogen_ifbase']
219 datadump[key]['autogen_bridge'] = datadump[key]['autogen_ifbase'].startswith('bridge')
220 if datadump[key]['autogen_bridge'] and not 'alias' in key:
221 datadump[key]['autogen_bridge_interfaces'] = datadump[key]['members'].split()
222 except Exception as e:
223 print "# Error while processing interface %s" % key
224 raise
225
226 dhcp_interfaces = [datadump[key]['autogen_ifname'] for key in datadump['autogen_iface_keys'] \
227 if dhcp_type(datadump[key]) == DHCP_SERVER]
228
229 datadump['autogen_dhcp_interfaces'] = dhcp_interfaces
230 datadump['autogen_item'] = item
231
232 datadump['autogen_realname'] = get_realname(datadump)
233 datadump['autogen_domain'] = datadump['domain'] if datadump.has_key('domain') else 'wleiden.net.'
234 datadump['autogen_fqdn'] = datadump['autogen_realname'] + '.' + datadump['autogen_domain']
235 datadump_cache[item] = datadump.copy()
236 except Exception as e:
237 print "# Error while processing %s" % item
238 raise
239 return datadump
240
241
242def store_yaml(datadump, header=False):
243 """ Store configuration yaml for 'item'"""
244 item = datadump['autogen_item']
245 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
246
247 output = generate_wleiden_yaml(datadump, header)
248
249 f = open(gfile, 'w')
250 f.write(output)
251 f.close()
252
253
254def network(ip):
255 addr, mask = ip.split('/')
256 # Not parsing of these folks please
257 addr = parseaddr(addr)
258 mask = int(mask)
259 network = addr & ~((1 << (32 - mask)) - 1)
260 return network
261
262
263
264def make_relations(datadumps=dict()):
265 """ Process _ALL_ yaml files to get connection relations """
266 errors = []
267 poel = defaultdict(list)
268
269 if not datadumps:
270 for host in get_hostlist():
271 datadumps[host] = get_yaml(host)
272
273 for host, datadump in datadumps.iteritems():
274 try:
275 for iface_key in datadump['autogen_iface_keys']:
276 net_addr = network(datadump[iface_key]['ip'])
277 poel[net_addr] += [(host,datadump[iface_key])]
278 except (KeyError, ValueError), e:
279 errors.append("[FOUT] in '%s' interface '%s' (%s)" % (host,iface_key, e))
280 continue
281 return (poel, errors)
282
283
284
285def valid_addr(addr):
286 """ Show which address is valid in which are not """
287 return str(addr).startswith('172.')
288
289def get_system_list(prefix):
290 return sorted([os.path.basename(os.path.dirname(x)) for x in glob.glob("%s/%s*/wleiden.yaml" % (NODE_DIR, prefix))])
291
292get_hybridlist = lambda: get_system_list("Hybrid")
293get_nodelist = lambda: get_system_list("CNode")
294get_proxylist = lambda: get_system_list("Proxy")
295
296def get_hostlist():
297 """ Combined hosts and proxy list"""
298 return get_nodelist() + get_proxylist() + get_hybridlist()
299
300def angle_between_points(lat1,lat2,long1,long2):
301 """
302 Return Angle in radians between two GPS coordinates
303 See: http://stackoverflow.com/questions/3809179/angle-between-2-gps-coordinates
304 """
305 dy = lat2 - lat1
306 dx = math.cos(lat1)*(long2 - long1)
307 angle = math.atan2(dy,dx)
308 return angle
309
310
311
312def angle_to_cd(angle):
313 """ Return Dutch Cardinal Direction estimation in 'one digit' of radian angle """
314
315 # For easy conversion get positive degree
316 degrees = math.degrees(angle)
317 abs_degrees = 360 + degrees if degrees < 0 else degrees
318
319 # Numbers can be confusing calculate from the 4 main directions
320 p = 22.5
321 if abs_degrees < p:
322 cd = "n"
323 elif abs_degrees < (90 - p):
324 cd = "no"
325 elif abs_degrees < (90 + p):
326 cd = "o"
327 elif abs_degrees < (180 - p):
328 cd = "zo"
329 elif abs_degrees < (180 + p):
330 cd = "z"
331 elif abs_degrees < (270 - p):
332 cd = "zw"
333 elif abs_degrees < (270 + p):
334 cd = "w"
335 elif abs_degrees < (360 - p):
336 cd = "nw"
337 else:
338 cd = "n"
339 return cd
340
341
342
343def cd_between_hosts(hostA, hostB, datadumps):
344 # Using RDNAP coordinates
345 dx = float(int(datadumps[hostA]['rdnap_x']) - int(datadumps[hostB]['rdnap_x'])) * -1
346 dy = float(int(datadumps[hostA]['rdnap_y']) - int(datadumps[hostB]['rdnap_y'])) * -1
347 return angle_to_cd(math.atan2(dx,dy))
348
349 # GPS coordinates seems to fail somehow
350 #latA = float(datadumps[hostA]['latitude'])
351 #latB = float(datadumps[hostB]['latitude'])
352 #lonA = float(datadumps[hostA]['longitude'])
353 #lonB = float(datadumps[hostB]['longitude'])
354 #return angle_to_cd(angle_between_points(latA, latB, lonA, lonB))
355
356
357def generate_title(nodelist):
358 """ Main overview page """
359 items = {'root' : "." }
360 def fl(spaces, line):
361 return (' ' * spaces) + line + '\n'
362
363 output = """
364<html>
365 <head>
366 <title>Wireless leiden Configurator - GFormat</title>
367 <style type="text/css">
368 th {background-color: #999999}
369 tr:nth-child(odd) {background-color: #cccccc}
370 tr:nth-child(even) {background-color: #ffffff}
371 th, td {padding: 0.1em 1em}
372 </style>
373 </head>
374 <body>
375 <center>
376 <form type="GET" action="%(root)s">
377 <input type="hidden" name="action" value="update">
378 <input type="submit" value="Update Configuration Database (SVN)">
379 </form>
380 <table>
381 <caption><h3>Wireless Leiden Configurator</h3></caption>
382 """ % items
383
384 for node in nodelist:
385 items['node'] = node
386 output += fl(5, '<tr>') + fl(7,'<td><a href="%(root)s/%(node)s">%(node)s</a></td>' % items)
387 for config in files:
388 items['config'] = config
389 output += fl(7,'<td><a href="%(root)s/%(node)s/%(config)s">%(config)s</a></td>' % items)
390 output += fl(5, "</tr>")
391 output += """
392 </table>
393 <hr />
394 <em>%s</em>
395 </center>
396 </body>
397</html>
398 """ % __version__
399
400 return output
401
402
403
404def generate_node(node):
405 """ Print overview of all files available for node """
406 return "\n".join(files)
407
408def generate_node_overview(host):
409 """ Print overview of all files available for node """
410 datadump = get_yaml(host)
411 params = { 'host' : host }
412 output = "<em><a href='..'>Back to overview</a></em><hr />"
413 output += "<h2>Available files:</h2><ul>"
414 for cf in files:
415 params['cf'] = cf
416 output += '<li><a href="%(host)s/%(cf)s">%(cf)s</a></li>\n' % params
417 output += "</ul>"
418
419 # Generate and connection listing
420 output += "<h2>Connected To:</h2><ul>"
421 (poel, errors) = make_relations()
422 for network, hosts in poel.iteritems():
423 if host in [x[0] for x in hosts]:
424 if len(hosts) == 1:
425 # Single not connected interface
426 continue
427 for remote,ifacedump in hosts:
428 if remote == host:
429 # This side of the interface
430 continue
431 params = { 'remote': remote, 'remote_ip' : ifacedump['ip'] }
432 output += '<li><a href="%(remote)s">%(remote)s</a> -- %(remote_ip)s</li>\n' % params
433 output += "</ul>"
434 output += "<h2>MOTD details:</h2><pre>" + generate_motd(datadump) + "</pre>"
435
436 output += "<hr /><em><a href='..'>Back to overview</a></em>"
437 return output
438
439
440def generate_header(datadump, ctag="#"):
441 return """\
442%(ctag)s
443%(ctag)s DO NOT EDIT - Automatically generated by 'gformat'
444%(ctag)s Generated at %(date)s by %(host)s from r%(revision)s
445%(ctag)s
446""" % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname(), 'revision' : datadump['autogen_revision'] }
447
448
449
450def parseaddr(s):
451 """ Process IPv4 CIDR notation addr to a (binary) number """
452 f = s.split('.')
453 return (long(f[0]) << 24L) + \
454 (long(f[1]) << 16L) + \
455 (long(f[2]) << 8L) + \
456 long(f[3])
457
458
459
460def showaddr(a):
461 """ Display IPv4 addr in (dotted) CIDR notation """
462 return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff)
463
464
465def is_member(ip, mask, canidate):
466 """ Return True if canidate is part of ip/mask block"""
467 ip_addr = parseaddr(ip)
468 ip_canidate = parseaddr(canidate)
469 mask = int(mask)
470 ip_addr = ip_addr & ~((1 << (32 - mask)) - 1)
471 ip_canidate = ip_canidate & ~((1 << (32 - mask)) - 1)
472 return ip_addr == ip_canidate
473
474
475
476def cidr2netmask(netmask):
477 """ Given a 'netmask' return corresponding CIDR """
478 return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask))))
479
480def get_network(addr, mask):
481 return showaddr(parseaddr(addr) & ~((1 << (32 - int(mask))) - 1))
482
483
484def generate_dhcpd_conf(datadump):
485 """ Generate config file '/usr/local/etc/dhcpd.conf """
486 output = generate_header(datadump)
487 output += Template("""\
488# option definitions common to all supported networks...
489option domain-name "dhcp.{{ autogen_fqdn }}";
490
491default-lease-time 600;
492max-lease-time 7200;
493
494# Use this to enble / disable dynamic dns updates globally.
495#ddns-update-style none;
496
497# If this DHCP server is the official DHCP server for the local
498# network, the authoritative directive should be uncommented.
499authoritative;
500
501# Use this to send dhcp log messages to a different log file (you also
502# have to hack syslog.conf to complete the redirection).
503log-facility local7;
504
505#
506# Interface definitions
507#
508\n""").render(datadump)
509
510 dhcp_out = defaultdict(list)
511 for iface_key in datadump['autogen_iface_keys']:
512 ifname = datadump[iface_key]['autogen_ifname']
513 if not datadump[iface_key].has_key('comment'):
514 datadump[iface_key]['comment'] = None
515 dhcp_out[ifname].append(" ## %(autogen_ifname)s - %(comment)s\n" % datadump[iface_key])
516
517 (addr, mask) = datadump[iface_key]['ip'].split('/')
518 datadump[iface_key]['autogen_addr'] = addr
519 datadump[iface_key]['autogen_netmask'] = cidr2netmask(mask)
520 datadump[iface_key]['autogen_subnet'] = get_network(addr, mask)
521 if dhcp_type(datadump[iface_key]) != DHCP_SERVER:
522 dhcp_out[ifname].append(" subnet %(autogen_subnet)s netmask %(autogen_netmask)s {\n ### not autoritive\n }\n" % \
523 datadump[iface_key])
524 continue
525
526 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
527 dhcp_part = ".".join(addr.split('.')[0:3])
528 datadump[iface_key]['autogen_dhcp_start'] = dhcp_part + "." + dhcp_start
529 datadump[iface_key]['autogen_dhcp_stop'] = dhcp_part + "." + dhcp_stop
530
531 # Assume the first 10 IPs could be used for static entries
532 if 'no_portal' in datadump:
533 fixed = 5
534 for mac in datadump['no_portal']:
535 dhcp_out[ifname].append("""\
536 host fixed-%(ifname)s-%(fixed)s {
537 hardware ethernet %(mac)s;
538 fixed-address %(prefix)s.%(fixed)s;
539 }
540""" % { 'ifname' : ifname, 'mac' : mac, 'prefix': dhcp_part, 'fixed' : fixed })
541 fixed += 1
542
543 dhcp_out[ifname].append("""\
544 subnet %(autogen_subnet)s netmask %(autogen_netmask)s {
545 range %(autogen_dhcp_start)s %(autogen_dhcp_stop)s;
546 option routers %(autogen_addr)s;
547 option domain-name-servers %(autogen_addr)s;
548 }
549""" % datadump[iface_key])
550
551 for ifname,value in dhcp_out.iteritems():
552 output += ("shared-network %s {\n" % ifname) + ''.join(value) + '}\n\n'
553 return output
554
555
556
557def generate_dnsmasq_conf(datadump):
558 """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """
559 output = generate_header(datadump)
560 output += Template("""\
561# DHCP server options
562dhcp-authoritative
563dhcp-fqdn
564domain=dhcp.{{ autogen_fqdn }}
565domain-needed
566expand-hosts
567log-async=100
568
569# Low memory footprint
570cache-size=10000
571
572\n""").render(datadump)
573
574 for iface_key in datadump['autogen_iface_keys']:
575 if not datadump[iface_key].has_key('comment'):
576 datadump[iface_key]['comment'] = None
577 output += "## %(autogen_ifname)s - %(comment)s\n" % datadump[iface_key]
578
579 if dhcp_type(datadump[iface_key]) != DHCP_SERVER:
580 output += "# not autoritive\n\n"
581 continue
582
583 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
584 (ip, cidr) = datadump[iface_key]['ip'].split('/')
585 datadump[iface_key]['autogen_netmask'] = cidr2netmask(cidr)
586
587 dhcp_part = ".".join(ip.split('.')[0:3])
588 datadump[iface_key]['autogen_dhcp_start'] = dhcp_part + "." + dhcp_start
589 datadump[iface_key]['autogen_dhcp_stop'] = dhcp_part + "." + dhcp_stop
590 output += "dhcp-range=%(autogen_ifname)s,%(autogen_dhcp_start)s,%(autogen_dhcp_stop)s,%(autogen_netmask)s,24h\n\n" % datadump[iface_key]
591
592 return output
593
594
595def make_interface_list(datadump):
596 if interface_list_cache.has_key(datadump['autogen_item']):
597 return (interface_list_cache[datadump['autogen_item']])
598 # lo0 configuration:
599 # - 172.32.255.1/32 is the proxy.wleiden.net deflector
600 # - masterip is special as it needs to be assigned to at
601 # least one interface, so if not used assign to lo0
602 addrs_list = { 'lo0' : [("127.0.0.1/8", "LocalHost"), ("172.31.255.1/32","Proxy IP")] }
603 dhclient_if = {'lo0' : False}
604
605 # XXX: Find some way of send this output nicely
606 output = ''
607
608 masterip_used = False
609 for iface_key in datadump['autogen_iface_keys']:
610 if datadump[iface_key]['ip'].startswith(datadump['masterip']):
611 masterip_used = True
612 break
613 if not masterip_used:
614 addrs_list['lo0'].append((datadump['masterip'] + "/32", 'Master IP Not used in interface'))
615
616 for iface_key in datadump['autogen_iface_keys']:
617 ifacedump = datadump[iface_key]
618 ifname = ifacedump['autogen_ifname']
619
620 # Flag dhclient is possible
621 if not dhclient_if.has_key(ifname) or dhclient_if[ifname] == False:
622 dhclient_if[ifname] = dhcp_type(ifacedump) == DHCP_CLIENT
623
624 # Add interface IP to list
625 item = (ifacedump['ip'], ifacedump['comment'])
626 if addrs_list.has_key(ifname):
627 addrs_list[ifname].append(item)
628 else:
629 addrs_list[ifname] = [item]
630
631 # Alias only needs IP assignment for now, this might change if we
632 # are going to use virtual accesspoints
633 if "alias" in iface_key:
634 continue
635
636 # XXX: Might want to deduct type directly from interface name
637 if ifacedump['type'] in ['11a', '11b', '11g', 'wireless']:
638 # Default to station (client) mode
639 ifacedump['autogen_wlanmode'] = "sta"
640 if ifacedump['mode'] in ['master', 'master-wds', 'ap', 'ap-wds']:
641 ifacedump['autogen_wlanmode'] = "ap"
642
643 if not ifacedump.has_key('channel'):
644 if ifacedump['type'] == '11a':
645 ifacedump['channel'] = 36
646 else:
647 ifacedump['channel'] = 1
648
649 # Allow special hacks at the back like wds and stuff
650 if not ifacedump.has_key('extra'):
651 ifacedump['autogen_extra'] = 'regdomain ETSI country NL'
652 else:
653 ifacedump['autogen_extra'] = ifacedump['extra']
654
655 output += "wlans_%(autogen_ifbase)s='%(autogen_ifname)s'\n" % ifacedump
656 output += ("create_args_%(autogen_ifname)s='wlanmode %(autogen_wlanmode)s mode " +\
657 "%(type)s ssid %(ssid)s %(autogen_extra)s channel %(channel)s'\n") % ifacedump
658 output += "\n"
659
660 elif ifacedump['type'] in ['ethernet', 'eth']:
661 # No special config needed besides IP
662 if ifacedump['autogen_bridge']:
663 output += "cloned_interfaces='%(autogen_ifname)s'\n" % ifacedump
664 output += "ifconfig_%s='addm %s up'\n" % (ifacedump['autogen_ifname'], ' addm '.join(ifacedump['autogen_bridge_interfaces']))
665 for member in ifacedump['autogen_bridge_interfaces']:
666 output += "ifconfig_%s='up'\n" % member
667 output += "\n"
668 else:
669 assert False, "Unknown type " + ifacedump['type']
670
671 store = (addrs_list, dhclient_if, output)
672 interface_list_cache[datadump['autogen_item']] = store
673 return(store)
674
675
676
677def generate_rc_conf_local(datadump):
678 """ Generate configuration file '/etc/rc.conf.local' """
679 item = datadump['autogen_item']
680 if rc_conf_local_cache.has_key(item):
681 return rc_conf_local_cache[item]
682
683 if not datadump.has_key('ileiden'):
684 datadump['autogen_ileiden_enable'] = False
685 else:
686 datadump['autogen_ileiden_enable'] = datadump['ileiden']
687
688 datadump['autogen_ileiden_enable'] = switchFormat(datadump['autogen_ileiden_enable'])
689
690 if not ileiden_proxies or not normal_proxies:
691 for proxy in get_proxylist():
692 proxydump = get_yaml(proxy)
693 if proxydump['ileiden']:
694 ileiden_proxies.append(proxydump)
695 else:
696 normal_proxies.append(proxydump)
697 for host in get_hybridlist():
698 hostdump = get_yaml(host)
699 if hostdump['status'] == 'up':
700 if hostdump['service_proxy_ileiden']:
701 ileiden_proxies.append(hostdump)
702 if hostdump['service_proxy_normal']:
703 normal_proxies.append(hostdump)
704
705 datadump['autogen_ileiden_proxies'] = ileiden_proxies
706 datadump['autogen_normal_proxies'] = normal_proxies
707 datadump['autogen_ileiden_proxies_ips'] = ','.join([x['masterip'] for x in ileiden_proxies])
708 datadump['autogen_ileiden_proxies_names'] = ','.join([x['autogen_item'] for x in ileiden_proxies])
709 datadump['autogen_normal_proxies_ips'] = ','.join([x['masterip'] for x in normal_proxies])
710 datadump['autogen_normal_proxies_names'] = ','.join([x['autogen_item'] for x in normal_proxies])
711
712 output = generate_header(datadump, "#");
713 output += render_template(datadump, """\
714hostname='{{ autogen_fqdn }}'
715location='{{ location }}'
716nodetype="{{ nodetype }}"
717
718#
719# Configured listings
720#
721captive_portal_whitelist=""
722{% if nodetype == "Proxy" %}
723#
724# Proxy Configuration
725#
726{% if gateway -%}
727defaultrouter="{{ gateway }}"
728{% else -%}
729#defaultrouter="NOTSET"
730{% endif -%}
731internalif="{{ internalif }}"
732ileiden_enable="{{ autogen_ileiden_enable }}"
733gateway_enable="{{ autogen_ileiden_enable }}"
734pf_enable="yes"
735pf_rules="/etc/pf.conf"
736{% if autogen_ileiden_enable -%}
737pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={80,443}"
738lvrouted_enable="{{ autogen_ileiden_enable }}"
739lvrouted_flags="-u -s s00p3rs3kr3t -m 28"
740{% else -%}
741pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={0}"
742{% endif -%}
743{% if internalroute -%}
744static_routes="wleiden"
745route_wleiden="-net 172.16.0.0/12 {{ internalroute }}"
746{% endif -%}
747
748{% elif nodetype == "Hybrid" %}
749 #
750 # Hybrid Configuration
751 #
752 list_ileiden_proxies="
753 {% for item in autogen_ileiden_proxies -%}
754 {{ "%-16s"|format(item.masterip) }} # {{ item.autogen_realname }}
755 {% endfor -%}
756 "
757 list_normal_proxies="
758 {% for item in autogen_normal_proxies -%}
759 {{ "%-16s"|format(item.masterip) }} # {{ item.autogen_realname }}
760 {% endfor -%}
761 "
762
763 captive_portal_interfaces="{{ autogen_dhcp_interfaces|join(',')|default('none', true) }}"
764 externalif="{{ externalif|default('vr0', true) }}"
765 masterip="{{ masterip }}"
766
767 # Defined services
768 service_proxy_ileiden="{{ service_proxy_ileiden|yesorno }}"
769 service_proxy_normal="{{ service_proxy_normal|yesorno }}"
770 service_accesspoint="{{ service_accesspoint|yesorno }}"
771 service_incoming_rdr="{{ service_incoming_rdr|yesorno }}"
772 service_concentrator="{{ service_concentrator|yesorno }}"
773 #
774
775 {% if service_proxy_ileiden %}
776 pf_rules="/etc/pf.hybrid.conf"
777 {% if service_concentrator %}
778 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D inet_if=tun0 -D inet_ip='(tun0)' -D masterip=$masterip"
779 {% else %}
780 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D inet_if=$externalif -D inet_ip='($externalif:0)' -D masterip=$masterip"
781 {% endif %}
782 pf_flags="$pf_flags -D publicnat=80,443"
783 lvrouted_flags="$lvrouted_flags -g"
784 {% elif service_proxy_normal or service_incoming_rdr %}
785 pf_rules="/etc/pf.hybrid.conf"
786 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D masterip=$masterip"
787 pf_flags="$pf_flags -D publicnat=0"
788 lvrouted_flags="$lvrouted_flags -z `make_list "$list_ileiden_proxies" ","`"
789 named_setfib="1"
790 tinyproxy_setfib="1"
791 dnsmasq_setfib="1"
792 sshd_setfib="1"
793 {% else %}
794 named_auto_forward_only="YES"
795 pf_rules="/etc/pf.node.conf"
796 pf_flags=""
797 lvrouted_flags="$lvrouted_flags -z `make_list "$list_ileiden_proxies" ","`"
798 {% endif %}
799 {% if service_concentrator %}
800 # Do mind installing certificates is NOT done automatically for security reasons
801 openvpn_enable="YES"
802 openvpn_configfile="/usr/local/etc/openvpn/client.conf"
803 {% endif %}
804
805 {% if service_proxy_normal %}
806 tinyproxy_enable="yes"
807 {% else %}
808 pen_wrapper_enable="yes"
809 {% endif %}
810
811 {% if service_accesspoint %}
812 pf_flags="$pf_flags -D captive_portal_interfaces=$captive_portal_interfaces"
813 {% endif %}
814
815 {% if board == "ALIX2" %}
816 #
817 # ''Fat'' configuration, board has 256MB RAM
818 #
819 dnsmasq_enable="NO"
820 named_enable="YES"
821 {% if autogen_dhcp_interfaces -%}
822 dhcpd_enable="YES"
823 dhcpd_flags="$dhcpd_flags {{ autogen_dhcp_interfaces|join(' ') }}"
824 {% endif -%}
825 {% endif -%}
826
827 {% if gateway %}
828 defaultrouter="{{ gateway }}"
829 {% endif %}
830{% elif nodetype == "CNode" %}
831#
832# NODE iLeiden Configuration
833#
834
835# iLeiden Proxies {{ autogen_ileiden_proxies_names }}
836list_ileiden_proxies="{{ autogen_ileiden_proxies_ips }}"
837# normal Proxies {{ autogen_normal_proxies_names }}
838list_normal_proxies="{{ autogen_normal_proxies_ips }}"
839
840captive_portal_interfaces="{{ autogen_dhcp_interfaces|join(',') }}"
841
842lvrouted_flags="-u -s s00p3rs3kr3t -m 28 -z $list_ileiden_proxies"
843{% endif %}
844
845#
846# Interface definitions
847#\n
848""")
849
850 (addrs_list, dhclient_if, extra_ouput) = make_interface_list(datadump)
851 output += extra_ouput.strip() + "\n"
852
853 # Print IP address which needs to be assigned over here
854 output += "\n"
855 for iface,addrs in sorted(addrs_list.iteritems()):
856 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
857 output += "# %s || %s || %s\n" % (iface, addr, comment)
858
859 # Write DHCLIENT entry
860 if dhclient_if[iface]:
861 output += "ifconfig_%s='SYNCDHCP'\n\n" % (iface)
862
863 # Make sure the external address is always first as this is needed in the
864 # firewall setup
865 addrs = sorted(
866 [x for x in addrs if not '0.0.0.0' in x[0]],
867 key=lambda x: x[0].split('.')[0],
868 cmp=lambda x,y: cmp(1 if x == '172' else 0, 1 if y == '172' else 0)
869 )
870 addr_str = " ".join([x[0] for x in addrs])
871 output += "ipv4_addrs_%s='%s'\n\n" % (iface, addr_str)
872
873 rc_conf_local_cache[datadump['autogen_item']] = output
874 return output
875
876
877
878
879def get_all_configs():
880 """ Get dict with key 'host' with all configs present """
881 configs = dict()
882 for host in get_hostlist():
883 datadump = get_yaml(host)
884 configs[host] = datadump
885 return configs
886
887
888def get_interface_keys(config):
889 """ Quick hack to get all interface keys, later stage convert this to a iterator """
890 return sorted([elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)])
891
892
893def get_used_ips(configs):
894 """ Return array of all IPs used in config files"""
895 ip_list = []
896 for config in configs:
897 ip_list.append(config['masterip'])
898 for iface_key in get_interface_keys(config):
899 l = config[iface_key]['ip']
900 addr, mask = l.split('/')
901 # Special case do not process
902 if valid_addr(addr):
903 ip_list.append(addr)
904 else:
905 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
906 return sorted(ip_list)
907
908
909
910def get_nameservers(max_servers=None):
911 if nameservers_cache:
912 return nameservers_cache[0:max_servers]
913
914 for host in get_hybridlist():
915 hostdump = get_yaml(host)
916 if hostdump['status'] == 'up' and (hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']):
917 nameservers_cache.append((hostdump['masterip'], hostdump['autogen_realname']))
918 for host in get_proxylist():
919 hostdump = get_yaml(host)
920 if hostdump['status'] == 'up':
921 nameservers_cache.append((hostdump['masterip'], hostdump['autogen_realname']))
922
923 return nameservers_cache[0:max_servers]
924
925
926def generate_resolv_conf(datadump):
927 """ Generate configuration file '/etc/resolv.conf' """
928 # XXX: This should properly going to be an datastructure soon
929 datadump['autogen_header'] = generate_header(datadump, "#")
930 datadump['autogen_edge_nameservers'] = ''
931
932
933 for masterip,realname in get_nameservers():
934 datadump['autogen_edge_nameservers'] += "nameserver %-15s # %s\n" % (masterip, realname)
935
936 return Template("""\
937{{ autogen_header }}
938search wleiden.net
939
940# Try local (cache) first
941nameserver 127.0.0.1
942
943{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
944nameserver 8.8.8.8 # Google Public NameServer
945nameserver 8.8.4.4 # Google Public NameServer
946{% else -%}
947# START DYNAMIC LIST - updated by /tools/nameserver-shuffle
948{{ autogen_edge_nameservers }}
949{% endif -%}
950""").render(datadump)
951
952
953
954def generate_ntp_conf(datadump):
955 """ Generate configuration file '/etc/ntp.conf' """
956 # XXX: This should properly going to be an datastructure soon
957
958 datadump['autogen_header'] = generate_header(datadump, "#")
959 datadump['autogen_ntp_servers'] = ''
960 for host in get_proxylist():
961 hostdump = get_yaml(host)
962 datadump['autogen_ntp_servers'] += "server %(masterip)-15s iburst maxpoll 9 # %(autogen_realname)s\n" % hostdump
963 for host in get_hybridlist():
964 hostdump = get_yaml(host)
965 if hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']:
966 datadump['autogen_ntp_servers'] += "server %(masterip)-15s iburst maxpoll 9 # %(autogen_realname)s\n" % hostdump
967
968 return Template("""\
969{{ autogen_header }}
970
971{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
972# Machine hooked to internet.
973server 0.nl.pool.ntp.org iburst maxpoll 9
974server 1.nl.pool.ntp.org iburst maxpoll 9
975server 2.nl.pool.ntp.org iburst maxpoll 9
976server 3.nl.pool.ntp.org iburst maxpoll 9
977{% else -%}
978# Local Wireless Leiden NTP Servers.
979server 0.pool.ntp.wleiden.net iburst maxpoll 9
980server 1.pool.ntp.wleiden.net iburst maxpoll 9
981server 2.pool.ntp.wleiden.net iburst maxpoll 9
982server 3.pool.ntp.wleiden.net iburst maxpoll 9
983
984# All the configured NTP servers
985{{ autogen_ntp_servers }}
986{% endif %}
987
988# If a server loses sync with all upstream servers, NTP clients
989# no longer follow that server. The local clock can be configured
990# to provide a time source when this happens, but it should usually
991# be configured on just one server on a network. For more details see
992# http://support.ntp.org/bin/view/Support/UndisciplinedLocalClock
993# The use of Orphan Mode may be preferable.
994#
995server 127.127.1.0
996fudge 127.127.1.0 stratum 10
997""").render(datadump)
998
999
1000def generate_pf_hybrid_conf_local(datadump):
1001 """ Generate configuration file '/etc/pf.hybrid.conf.local' """
1002 datadump['autogen_header'] = generate_header(datadump, "#")
1003 return Template("""\
1004{{ autogen_header }}
1005
1006# Redirect some internal facing services outside (7)
1007# INFO: {{ rdr_rules|count }} rdr_rules (outside to internal redirect rules) defined.
1008{% for protocol, src_port,dest_ip,dest_port in rdr_rules -%}
1009rdr on $ext_if inet proto {{ protocol }} from any to $ext_if port {{ src_port }} tag SRV -> {{ dest_ip }} port {{ dest_port }}
1010{% endfor -%}
1011""").render(datadump)
1012
1013def generate_motd(datadump):
1014 """ Generate configuration file '/etc/motd' """
1015 output = Template("""\
1016FreeBSD run ``service motd onestart'' to make me look normal
1017
1018 WWW: {{ autogen_fqdn }} - http://www.wirelessleiden.nl
1019 Loc: {{ location }}
1020
1021Services:
1022{% if board == "ALIX2" -%}
1023{{" -"}} Core Node ({{ board }})
1024{% else -%}
1025{{" -"}} Hulp Node ({{ board }})
1026{% endif -%}
1027{% if service_proxy_normal -%}
1028{{" -"}} Normal Proxy
1029{% endif -%}
1030{% if service_proxy_ileiden -%}
1031{{" -"}} iLeiden Proxy
1032{% endif -%}
1033{% if service_incoming_rdr -%}
1034{{" -"}} Incoming port redirects
1035{% endif %}
1036Interlinks:\n
1037""").render(datadump)
1038
1039 (addrs_list, dhclient_if, extra_ouput) = make_interface_list(datadump)
1040 # Just nasty hack to make the formatting looks nice
1041 iface_len = max(map(len,addrs_list.keys()))
1042 addr_len = max(map(len,[x[0] for x in [x[0] for x in addrs_list.values()]]))
1043 for iface,addrs in sorted(addrs_list.iteritems()):
1044 if iface in ['lo0']:
1045 continue
1046 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
1047 output += " - %s || %s || %s\n" % (iface.ljust(iface_len), addr.ljust(addr_len), comment)
1048
1049 output += '\n'
1050 output += """\
1051Attached bridges:
1052"""
1053 has_item = False
1054 for iface_key in datadump['autogen_iface_keys']:
1055 ifacedump = datadump[iface_key]
1056 if ifacedump.has_key('ns_ip'):
1057 ifacedump['autogen_ns_ip'] = ifacedump['ns_ip'].split('/')[0]
1058 has_item = True
1059 output += " - %(autogen_ifname)s || %(mode)s || https://%(autogen_ns_ip)s\n" % ifacedump
1060 if not has_item:
1061 output += " - none\n"
1062
1063 return output
1064
1065
1066def format_yaml_value(value):
1067 """ Get yaml value in right syntax for outputting """
1068 if isinstance(value,str):
1069 output = '"%s"' % value
1070 else:
1071 output = value
1072 return output
1073
1074
1075
1076def format_wleiden_yaml(datadump):
1077 """ Special formatting to ensure it is editable"""
1078 output = "# Genesis config yaml style\n"
1079 output += "# vim:ts=2:et:sw=2:ai\n"
1080 output += "#\n"
1081 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
1082 for key in sorted(set(datadump.keys()) - set(iface_keys)):
1083 if key == 'rdr_rules':
1084 output += '%-10s:\n' % 'rdr_rules'
1085 for rdr_rule in datadump[key]:
1086 output += '- %s\n' % rdr_rule
1087 else:
1088 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
1089
1090 output += "\n\n"
1091
1092 # Format (key, required)
1093 key_order = (
1094 ('comment', True),
1095 ('ip', True),
1096 ('desc', True),
1097 ('sdesc', True),
1098 ('mode', True),
1099 ('type', True),
1100 ('extra_type', False),
1101 ('channel', False),
1102 ('ssid', False),
1103 ('wlan_mac', False),
1104 ('dhcp', True),
1105 ('compass', False),
1106 ('distance', False),
1107 ('ns_ip', False),
1108 ('bullet2_ip', False),
1109 ('ns_mac', False),
1110 ('bullet2_mac', False),
1111 ('ns_type', False),
1112 ('bridge_type', False),
1113 ('members', True),
1114 ('status', True),
1115 )
1116
1117 for iface_key in sorted(iface_keys):
1118 try:
1119 remainder = set(datadump[iface_key].keys()) - set([x[0] for x in key_order])
1120 if remainder:
1121 raise KeyError("invalid keys: %s" % remainder)
1122
1123 output += "%s:\n" % iface_key
1124 for key,required in key_order:
1125 if datadump[iface_key].has_key(key):
1126 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
1127 output += "\n\n"
1128 except Exception as e:
1129 print "# Error while processing interface %s" % iface_key
1130 raise
1131
1132 return output
1133
1134
1135
1136def generate_wleiden_yaml(datadump, header=True):
1137 """ Generate (petty) version of wleiden.yaml"""
1138 output = generate_header(datadump, "#") if header else ''
1139
1140 for key in datadump.keys():
1141 if key.startswith('autogen_'):
1142 del datadump[key]
1143 # Interface autogen cleanups
1144 elif type(datadump[key]) == dict:
1145 for key2 in datadump[key].keys():
1146 if key2.startswith('autogen_'):
1147 del datadump[key][key2]
1148
1149 output += format_wleiden_yaml(datadump)
1150 return output
1151
1152def generate_nanostation_config(datadump, iface, ns_type):
1153 #TODO(rvdz): Make sure the proper nanostation IP and subnet is set
1154 datadump['iface_%s' % iface]['ns_ip'] = datadump['iface_%s' % iface]['ns_ip'].split('/')[0]
1155
1156 datadump.update(datadump['iface_%s' % iface])
1157
1158 return open(os.path.join(os.path.dirname(__file__), 'ns5m.cfg.tmpl'),'r').read() % datadump
1159
1160def generate_yaml(datadump):
1161 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
1162
1163
1164
1165def generate_config(node, config, datadump=None):
1166 """ Print configuration file 'config' of 'node' """
1167 output = ""
1168 try:
1169 # Load config file
1170 if datadump == None:
1171 datadump = get_yaml(node)
1172
1173 if config == 'wleiden.yaml':
1174 output += generate_wleiden_yaml(datadump)
1175 elif config == 'authorized_keys':
1176 f = open(os.path.join(NODE_DIR,"global_keys"), 'r')
1177 output += f.read()
1178 node_keys = os.path.join(NODE_DIR,node,'authorized_keys')
1179 # Fetch local keys if existing
1180 if os.path.exists(node_keys):
1181 output += open(node_keys, 'r').read()
1182 f.close()
1183 elif config == 'dnsmasq.conf':
1184 output += generate_dnsmasq_conf(datadump)
1185 elif config == 'dhcpd.conf':
1186 output += generate_dhcpd_conf(datadump)
1187 elif config == 'rc.conf.local':
1188 output += generate_rc_conf_local(datadump)
1189 elif config == 'resolv.conf':
1190 output += generate_resolv_conf(datadump)
1191 elif config == 'ntp.conf':
1192 output += generate_ntp_conf(datadump)
1193 elif config == 'motd':
1194 output += generate_motd(datadump)
1195 elif config == 'pf.hybrid.conf.local':
1196 output += generate_pf_hybrid_conf_local(datadump)
1197 elif config.startswith('vr'):
1198 interface, ns_type = config.strip('.yaml').split('-')
1199 output += generate_nanostation_config(datadump, interface, ns_type)
1200 else:
1201 assert False, "Config not found!"
1202 except IOError, e:
1203 output += "[ERROR] Config file not found"
1204 return output
1205
1206
1207
1208def process_cgi_request(environ=os.environ):
1209 """ When calling from CGI """
1210 response_headers = []
1211 content_type = 'text/plain'
1212
1213 # Update repository if requested
1214 form = urlparse.parse_qs(environ['QUERY_STRING']) if environ.has_key('QUERY_STRING') else None
1215 if form and form.has_key("action") and "update" in form["action"]:
1216 output = "[INFO] Updating subverion, please wait...\n"
1217 output += subprocess.Popen([SVN, 'cleanup', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
1218 output += subprocess.Popen([SVN, 'up', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
1219 output += "[INFO] All done, redirecting in 5 seconds"
1220 response_headers += [
1221 ('Refresh', '5; url=.'),
1222 ]
1223 reload_cache()
1224 else:
1225 base_uri = environ['PATH_INFO']
1226 uri = base_uri.strip('/').split('/')
1227
1228 output = "Template Holder"
1229 if base_uri.endswith('/create/network.kml'):
1230 content_type='application/vnd.google-earth.kml+xml'
1231 output = make_network_kml.make_graph()
1232 elif base_uri.endswith('/api/get/nodeplanner.json'):
1233 content_type='application/json'
1234 output = make_network_kml.make_nodeplanner_json()
1235 elif not uri[0]:
1236 if is_text_request(environ):
1237 output = '\n'.join(get_hostlist())
1238 else:
1239 content_type = 'text/html'
1240 output = generate_title(get_hostlist())
1241 elif len(uri) == 1:
1242 if is_text_request(environ):
1243 output = generate_node(uri[0])
1244 else:
1245 content_type = 'text/html'
1246 output = generate_node_overview(uri[0])
1247 elif len(uri) == 2:
1248 output = generate_config(uri[0], uri[1])
1249 else:
1250 assert False, "Invalid option"
1251
1252 # Return response
1253 response_headers += [
1254 ('Content-type', content_type),
1255 ('Content-Length', str(len(output))),
1256 ]
1257 return(response_headers, str(output))
1258
1259
1260def get_realname(datadump):
1261 # Proxy naming convention is special, as the proxy name is also included in
1262 # the nodename, when it comes to the numbered proxies.
1263 if datadump['nodetype'] == 'Proxy':
1264 realname = datadump['nodetype'] + datadump['nodename'].replace('proxy','')
1265 else:
1266 # By default the full name is listed and also a shortname CNAME for easy use.
1267 realname = datadump['nodetype'] + datadump['nodename']
1268 return(realname)
1269
1270
1271
1272def make_dns(output_dir = 'dns', external = False):
1273 items = dict()
1274
1275 # hostname is key, IP is value
1276 wleiden_zone = defaultdict(list)
1277 wleiden_cname = dict()
1278
1279 pool = dict()
1280 for node in get_hostlist():
1281 datadump = get_yaml(node)
1282
1283 # Proxy naming convention is special
1284 fqdn = datadump['autogen_realname']
1285 if datadump['nodetype'] in ['CNode', 'Hybrid']:
1286 wleiden_cname[datadump['nodename']] = fqdn
1287
1288 if datadump.has_key('rdr_host'):
1289 remote_target = datadump['rdr_host']
1290 elif datadump.has_key('remote_access') and datadump['remote_access']:
1291 remote_target = datadump['remote_access'].split(':')[0]
1292 else:
1293 remote_target = None
1294
1295 if remote_target:
1296 try:
1297 parseaddr(remote_target)
1298 wleiden_zone[datadump['nodename'] + '.gw'].append((remote_target, False))
1299 except (IndexError, ValueError):
1300 wleiden_cname[datadump['nodename'] + '.gw'] = remote_target + '.'
1301
1302
1303 wleiden_zone[fqdn].append((datadump['masterip'], True))
1304
1305 # Hacking to get proper DHCP IPs and hostnames
1306 for iface_key in get_interface_keys(datadump):
1307 iface_name = iface_key.replace('_','-')
1308 (ip, cidr) = datadump[iface_key]['ip'].split('/')
1309 try:
1310 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
1311 datadump[iface_key]['autogen_netmask'] = cidr2netmask(cidr)
1312 dhcp_part = ".".join(ip.split('.')[0:3])
1313 if ip != datadump['masterip']:
1314 wleiden_zone["dhcp-gateway-%s.%s" % (iface_name, fqdn)].append((ip, False))
1315 for i in range(int(dhcp_start), int(dhcp_stop) + 1):
1316 wleiden_zone["dhcp-%s-%s.%s" % (i, iface_name, fqdn)].append(("%s.%s" % (dhcp_part, i), True))
1317 except (AttributeError, ValueError, KeyError):
1318 # First push it into a pool, to indentify the counter-part later on
1319 addr = parseaddr(ip)
1320 cidr = int(cidr)
1321 addr = addr & ~((1 << (32 - cidr)) - 1)
1322 if pool.has_key(addr):
1323 pool[addr] += [(iface_name, fqdn, ip)]
1324 else:
1325 pool[addr] = [(iface_name, fqdn, ip)]
1326 continue
1327
1328
1329
1330 # WL uses an /29 to configure an interface. IP's are ordered like this:
1331 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
1332
1333 sn = lambda x: re.sub(r'(?i)^cnode','',x)
1334
1335 # Automatic naming convention of interlinks namely 2 + remote.lower()
1336 for (key,value) in pool.iteritems():
1337 # Make sure they are sorted from low-ip to high-ip
1338 value = sorted(value, key=lambda x: parseaddr(x[2]))
1339
1340 if len(value) == 1:
1341 (iface_name, fqdn, ip) = value[0]
1342 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)].append((ip, True))
1343
1344 # Device DNS names
1345 if 'cnode' in fqdn.lower():
1346 wleiden_zone["d-at-%s.%s" % (iface_name, fqdn)].append((showaddr(parseaddr(ip) + 1), False))
1347 wleiden_cname["d-at-%s.%s" % (iface_name,sn(fqdn))] = "d-at-%s.%s" % ((iface_name, fqdn))
1348
1349 elif len(value) == 2:
1350 (a_iface_name, a_fqdn, a_ip) = value[0]
1351 (b_iface_name, b_fqdn, b_ip) = value[1]
1352 wleiden_zone["2%s.%s" % (b_fqdn,a_fqdn)].append((a_ip, True))
1353 wleiden_zone["2%s.%s" % (a_fqdn,b_fqdn)].append((b_ip, True))
1354
1355 # Device DNS names
1356 if 'cnode' in a_fqdn.lower() and 'cnode' in b_fqdn.lower():
1357 wleiden_zone["d-at-%s.%s" % (a_iface_name, a_fqdn)].append((showaddr(parseaddr(a_ip) + 1), False))
1358 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)].append((showaddr(parseaddr(b_ip) - 1), False))
1359 wleiden_cname["d-at-%s.%s" % (a_iface_name,sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1360 wleiden_cname["d-at-%s.%s" % (b_iface_name,sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1361 wleiden_cname["d2%s.%s" % (sn(b_fqdn),sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1362 wleiden_cname["d2%s.%s" % (sn(a_fqdn),sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1363
1364 else:
1365 pool_members = [k[1] for k in value]
1366 for item in value:
1367 (iface_name, fqdn, ip) = item
1368 wleiden_zone["2ring.%s" % (fqdn)].append((ip, True))
1369
1370 # Include static DNS entries
1371 # XXX: Should they override the autogenerated results?
1372 # XXX: Convert input to yaml more useable.
1373 # Format:
1374 ##; this is a comment
1375 ## roomburgh=CNodeRoomburgh1
1376 ## apkerk1.CNodeVosko=172.17.176.8 ;this as well
1377 dns_list = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
1378
1379 # Hack to allow special entries, for development
1380 wleiden_raw = {}
1381
1382 for line in dns_list:
1383 reverse = False
1384 k, items = line.items()[0]
1385 if type(items) == dict:
1386 if items.has_key('reverse'):
1387 reverse = items['reverse']
1388 items = items['a']
1389 else:
1390 items = items['cname']
1391 items = [items] if type(items) != list else items
1392 for item in items:
1393 if item.startswith('IN '):
1394 wleiden_raw[k] = item
1395 elif valid_addr(item):
1396 wleiden_zone[k].append((item, reverse))
1397 else:
1398 wleiden_cname[k] = item
1399
1400 # Hack to get dynamic pool listing
1401 def chunks(l, n):
1402 return [l[i:i+n] for i in range(0, len(l), n)]
1403
1404 ntp_servers = [x[0] for x in get_nameservers()]
1405 for id, chunk in enumerate(chunks(ntp_servers,(len(ntp_servers)/4))):
1406 for ntp_server in chunk:
1407 wleiden_zone['%i.pool.ntp' % id].append((ntp_server, False))
1408
1409 details = dict()
1410 # 24 updates a day allowed
1411 details['serial'] = time.strftime('%Y%m%d%H')
1412
1413 if external:
1414 dns_masters = ['siteview.wirelessleiden.nl', 'ns1.vanderzwet.net']
1415 else:
1416 dns_masters = ['sunny.wleiden.net'] + ["%s.wleiden.net" % x[1] for x in get_nameservers(max_servers=3)]
1417
1418 details['master'] = dns_masters[0]
1419 details['ns_servers'] = '\n'.join(['\tNS\t%s.' % x for x in dns_masters])
1420
1421 dns_header = '''
1422$TTL 3h
1423%(zone)s. SOA %(master)s. beheer.lijst.wirelessleiden.nl. ( %(serial)s 15m 15m 1w 60s )
1424 ; Serial, Refresh, Retry, Expire, Neg. cache TTL
1425
1426%(ns_servers)s
1427 \n'''
1428
1429
1430 if not os.path.isdir(output_dir):
1431 os.makedirs(output_dir)
1432 details['zone'] = 'wleiden.net'
1433 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
1434 f.write(dns_header % details)
1435
1436 for host,items in wleiden_zone.iteritems():
1437 for ip,reverse in items:
1438 if ip not in ['0.0.0.0']:
1439 f.write("%s.wleiden.net. IN A %s \n" % (host.lower(), ip))
1440 for source,dest in wleiden_cname.iteritems():
1441 dest = dest if dest.endswith('.') else dest + ".wleiden.net."
1442 f.write("%s.wleiden.net. IN CNAME %s\n" % (source.lower(), dest.lower()))
1443 for source, dest in wleiden_raw.iteritems():
1444 f.write("%s.wleiden.net. %s\n" % (source, dest))
1445 f.close()
1446
1447 # Create whole bunch of specific sub arpa zones. To keep it compliant
1448 for s in range(16,32):
1449 details['zone'] = '%i.172.in-addr.arpa' % s
1450 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
1451 f.write(dns_header % details)
1452
1453 #XXX: Not effient, fix to proper data structure and do checks at other
1454 # stages
1455 for host,items in wleiden_zone.iteritems():
1456 for ip,reverse in items:
1457 if not reverse:
1458 continue
1459 if valid_addr(ip):
1460 if valid_addr(ip):
1461 if int(ip.split('.')[1]) == s:
1462 rev_ip = '.'.join(reversed(ip.split('.')))
1463 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
1464 f.close()
1465
1466
1467def usage():
1468 print """Usage: %(prog)s <argument>
1469Argument:
1470\tstandalone [port] = Run configurator webserver [8000]
1471\tdns [outputdir] = Generate BIND compliant zone files in dns [./dns]
1472\tnagios-export [--heavy-load] = Generate basic nagios configuration file.
1473\tfull-export = Generate yaml export script for heatmap.
1474\tstatic [outputdir] = Generate all config files and store on disk
1475\t with format ./<outputdir>/%%NODE%%/%%FILE%% [./static]
1476\ttest <node> [<file>] = Receive output for certain node [all files].
1477\ttest-cgi <node> <file> = Receive output of CGI script [all files].
1478\tlist <status> <items> = List systems which have certain status
1479
1480Arguments:
1481\t<node> = NodeName (example: HybridRick)
1482\t<file> = %(files)s
1483\t<status> = all|up|down|planned
1484\t<items> = systems|nodes|proxies
1485
1486NOTE FOR DEVELOPERS; you can test your changes like this:
1487 BEFORE any changes in this code:
1488 $ ./gformat.py static /tmp/pre
1489 AFTER the changes:
1490 $ ./gformat.py static /tmp/post
1491 VIEW differences and VERIFY all are OK:
1492 $ diff -urI 'Generated' -r /tmp/pre /tmp/post
1493""" % { 'prog' : sys.argv[0], 'files' : '|'.join(files) }
1494 exit(0)
1495
1496
1497def is_text_request(environ=os.environ):
1498 """ Find out whether we are calling from the CLI or any text based CLI utility """
1499 try:
1500 return environ['HTTP_USER_AGENT'].split()[0] in ['curl', 'fetch', 'wget']
1501 except KeyError:
1502 return True
1503
1504def switchFormat(setting):
1505 if setting:
1506 return "YES"
1507 else:
1508 return "NO"
1509
1510def rlinput(prompt, prefill=''):
1511 import readline
1512 readline.set_startup_hook(lambda: readline.insert_text(prefill))
1513 try:
1514 return raw_input(prompt)
1515 finally:
1516 readline.set_startup_hook()
1517
1518def fix_conflict(left, right, default='i'):
1519 while True:
1520 print "## %-30s | %-30s" % (left, right)
1521 c = raw_input("## Solve Conflict (h for help) <l|r|e|i|> [%s]: " % default)
1522 if not c:
1523 c = default
1524
1525 if c in ['l','1']:
1526 return left
1527 elif c in ['r','2']:
1528 return right
1529 elif c in ['e', '3']:
1530 return rlinput("Edit: ", "%30s | %30s" % (left, right))
1531 elif c in ['i', '4']:
1532 return None
1533 else:
1534 print "#ERROR: '%s' is invalid input (left, right, edit or ignore)!" % c
1535
1536
1537
1538def print_cgi_response(response_headers, output):
1539 """Could we not use some kind of wsgi wrapper to make this output?"""
1540 for header in response_headers:
1541 print "%s: %s" % header
1542 print
1543 print output
1544
1545
1546def fill_cache():
1547 ''' Poor man re-loading of few cache items (the slow ones) '''
1548 for host in get_hostlist():
1549 get_yaml(host)
1550
1551
1552def reload_cache():
1553 clear_cache()
1554 fill_cache()
1555
1556
1557def main():
1558 """Hard working sub"""
1559 # Allow easy hacking using the CLI
1560 if not os.environ.has_key('PATH_INFO'):
1561 if len(sys.argv) < 2:
1562 usage()
1563
1564 if sys.argv[1] == "standalone":
1565 import SocketServer
1566 import CGIHTTPServer
1567 # Hop to the right working directory.
1568 os.chdir(os.path.dirname(__file__))
1569 try:
1570 PORT = int(sys.argv[2])
1571 except (IndexError,ValueError):
1572 PORT = 8000
1573
1574 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
1575 """ Serve this CGI from the root of the webserver """
1576 def is_cgi(self):
1577 if "favicon" in self.path:
1578 return False
1579
1580 self.cgi_info = (os.path.basename(__file__), self.path)
1581 self.path = ''
1582 return True
1583 handler = MyCGIHTTPRequestHandler
1584 SocketServer.TCPServer.allow_reuse_address = True
1585 httpd = SocketServer.TCPServer(("", PORT), handler)
1586 httpd.server_name = 'localhost'
1587 httpd.server_port = PORT
1588
1589 logger.info("serving at port %s", PORT)
1590 try:
1591 httpd.serve_forever()
1592 except KeyboardInterrupt:
1593 httpd.shutdown()
1594 logger.info("All done goodbye")
1595 elif sys.argv[1] == "test":
1596 # Basic argument validation
1597 try:
1598 node = sys.argv[2]
1599 datadump = get_yaml(node)
1600 except IndexError:
1601 print "Invalid argument"
1602 exit(1)
1603 except IOError as e:
1604 print e
1605 exit(1)
1606
1607
1608 # Get files to generate
1609 gen_files = sys.argv[3:] if len(sys.argv) > 3 else files
1610
1611 # Actual config generation
1612 for config in gen_files:
1613 logger.info("## Generating %s %s", node, config)
1614 print generate_config(node, config, datadump)
1615 elif sys.argv[1] == "test-cgi":
1616 os.environ['PATH_INFO'] = "/".join(sys.argv[2:])
1617 os.environ['SCRIPT_NAME'] = __file__
1618 response_headers, output = process_cgi_request()
1619 print_cgi_response(response_headers, output)
1620 elif sys.argv[1] == "static":
1621 items = dict()
1622 items['output_dir'] = sys.argv[2] if len(sys.argv) > 2 else "./static"
1623 for node in get_hostlist():
1624 items['node'] = node
1625 items['wdir'] = "%(output_dir)s/%(node)s" % items
1626 if not os.path.isdir(items['wdir']):
1627 os.makedirs(items['wdir'])
1628 datadump = get_yaml(node)
1629 for config in files:
1630 items['config'] = config
1631 logger.info("## Generating %(node)s %(config)s" % items)
1632 f = open("%(wdir)s/%(config)s" % items, "w")
1633 f.write(generate_config(node, config, datadump))
1634 f.close()
1635 elif sys.argv[1] == "wind-export":
1636 items = dict()
1637 for node in get_hostlist():
1638 datadump = get_yaml(node)
1639 sql = """INSERT IGNORE INTO nodes (name, name_ns, longitude, latitude)
1640 VALUES ('%(nodename)s', '%(nodename)s', %(latitude)s, %(longitude)s);""" % datadump;
1641 sql = """INSERT IGNORE INTO users_nodes (user_id, node_id, owner)
1642 VALUES (
1643 (SELECT id FROM users WHERE username = 'rvdzwet'),
1644 (SELECT id FROM nodes WHERE name = '%(nodename)s'),
1645 'Y');""" % datadump
1646 #for config in files:
1647 # items['config'] = config
1648 # print "## Generating %(node)s %(config)s" % items
1649 # f = open("%(wdir)s/%(config)s" % items, "w")
1650 # f.write(generate_config(node, config, datadump))
1651 # f.close()
1652 for node in get_hostlist():
1653 datadump = get_yaml(node)
1654 for iface_key in sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]):
1655 ifacedump = datadump[iface_key]
1656 if ifacedump.has_key('mode') and ifacedump['mode'] == 'ap-wds':
1657 ifacedump['nodename'] = datadump['nodename']
1658 if not ifacedump.has_key('channel') or not ifacedump['channel']:
1659 ifacedump['channel'] = 0
1660 sql = """INSERT INTO links (node_id, type, ssid, protocol, channel, status)
1661 VALUES ((SELECT id FROM nodes WHERE name = '%(nodename)s'), 'ap',
1662 '%(ssid)s', 'IEEE 802.11b', %(channel)s, 'active');""" % ifacedump
1663 elif sys.argv[1] == "nagios-export":
1664 try:
1665 heavy_load = (sys.argv[2] == "--heavy-load")
1666 except IndexError:
1667 heavy_load = False
1668
1669 hostgroup_details = {
1670 'wleiden' : 'Stichting Wireless Leiden - FreeBSD Nodes',
1671 'wzoeterwoude' : 'Stichting Wireless Leiden - Afdeling Zoeterwoude - Free-WiFi Project',
1672 'walphen' : 'Stichting Wireless Alphen',
1673 'westeinder' : 'WestEinder Plassen',
1674 }
1675
1676 params = {
1677 'check_interval' : 5 if heavy_load else 120,
1678 'retry_interval' : 1 if heavy_load else 10,
1679 'max_check_attempts' : 10 if heavy_load else 6,
1680 }
1681
1682 print '''\
1683define host {
1684 name wleiden-node ; Default Node Template
1685 use generic-host ; Use the standard template as initial starting point
1686 check_period 24x7 ; By default, FreeBSD hosts are checked round the clock
1687 check_interval %(check_interval)s ; Actively check the host every 5 minutes
1688 retry_interval %(retry_interval)s ; Schedule host check retries at 1 minute intervals
1689 max_check_attempts %(max_check_attempts)s ; Check each FreeBSD host 10 times (max)
1690 check_command check-host-alive ; Default command to check FreeBSD hosts
1691 register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
1692}
1693
1694define service {
1695 name wleiden-service ; Default Service Template
1696 use generic-service ; Use the standard template as initial starting point
1697 check_period 24x7 ; By default, FreeBSD hosts are checked round the clock
1698 check_interval %(check_interval)s ; Actively check the host every 5 minutes
1699 retry_interval %(retry_interval)s ; Schedule host check retries at 1 minute intervals
1700 max_check_attempts %(max_check_attempts)s ; Check each FreeBSD host 10 times (max)
1701 register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
1702}
1703
1704# Please make sure to install:
1705# make -C /usr/ports/net-mgmt/nagios-check_netsnmp install clean
1706#
1707define command{
1708 command_name check_netsnmp_disk
1709 command_line $USER1$/check_netsnmp -H $HOSTADDRESS$ -o disk
1710}
1711
1712define command{
1713 command_name check_netsnmp_load
1714 command_line $USER1$/check_netsnmp -H $HOSTADDRESS$ -o load
1715}
1716
1717define command{
1718 command_name check_netsnmp_proc
1719 command_line $USER1$/check_netsnmp -H $HOSTADDRESS$ -o proc
1720}
1721
1722define command{
1723 command_name check_disk_snmp
1724 command_line /usr/local/libexec/nagios/check_disk_snmp.pl -H $HOSTADDRESS$ -d $ARG1$
1725}
1726
1727define command{
1728 command_name check_by_ssh
1729 command_line $USER1$/check_by_ssh -H $HOSTADDRESS$ -p $ARG1$ -C "$ARG2$ $ARG3$ $ARG4$ $ARG5$ $ARG6$"
1730}
1731
1732define command{
1733 command_name check_dns_wl
1734 command_line $USER1$/v2/check_dns_wl $HOSTADDRESS$ $ARG1$
1735}
1736
1737
1738# TDB: dhcp leases
1739# /usr/local/libexec/nagios/check_netsnmp -H 192.168.178.47 --oid 1 exec
1740
1741# TDB: internet status
1742# /usr/local/libexec/nagios/check_netsnmp -H 192.168.178.47 --oid 1 file
1743
1744# TDB: Advanced local passive checks
1745# /usr/local/libexec/nagios/check_by_ssh
1746''' % params
1747
1748 print '''\
1749# Service Group, not displayed by default
1750define hostgroup {
1751 hostgroup_name srv_hybrid
1752 alias All Hybrid Nodes
1753 register 0
1754}
1755
1756define service {
1757 use wleiden-service
1758 hostgroup_name srv_hybrid
1759 service_description SSH
1760 check_command check_ssh
1761}
1762
1763define service {
1764 use wleiden-service
1765 hostgroup_name srv_hybrid
1766 service_description HTTP
1767 check_command check_http
1768}
1769
1770define service {
1771 use wleiden-service
1772 hostgroup_name srv_hybrid
1773 service_description DNS
1774 check_command check_dns_wl!"www.wirelessleiden.nl"
1775}
1776
1777
1778define service {
1779 use wleiden-service
1780 hostgroup_name srv_hybrid
1781 service_description VAR space
1782 check_command check_disk_snmp!/var
1783}
1784'''
1785
1786 if heavy_load:
1787 print '''\
1788define service {
1789 use wleiden-service
1790 hostgroup_name srv_hybrid
1791 service_description SNMP
1792 check_command check_snmp
1793}
1794
1795define service {
1796 use wleiden-service
1797 hostgroup_name srv_hybrid
1798 service_description NTP
1799 check_command check_ntp_peer
1800}
1801
1802define service {
1803 use wleiden-service
1804 hostgroup_name srv_hybrid
1805 service_description LOAD
1806 check_command check_netsnmp_load
1807}
1808
1809define service {
1810 use wleiden-service
1811 hostgroup_name srv_hybrid
1812 service_description PROC
1813 check_command check_netsnmp_proc
1814}
1815
1816define service {
1817 use wleiden-service
1818 hostgroup_name srv_hybrid
1819 service_description DISK
1820 check_command check_netsnmp_disk
1821}
1822'''
1823 for node in get_hostlist():
1824 datadump = get_yaml(node)
1825 if not datadump['status'] == 'up':
1826 continue
1827 if not hostgroup_details.has_key(datadump['monitoring_group']):
1828 hostgroup_details[datadump['monitoring_group']] = datadump['monitoring_group']
1829 print '''\
1830define host {
1831 use wleiden-node
1832 host_name %(autogen_fqdn)s
1833 address %(masterip)s
1834 hostgroups srv_hybrid,%(monitoring_group)s
1835}
1836''' % datadump
1837
1838 for name,alias in hostgroup_details.iteritems():
1839 print '''\
1840define hostgroup {
1841 hostgroup_name %s
1842 alias %s
1843} ''' % (name, alias)
1844
1845
1846 elif sys.argv[1] == "full-export":
1847 hosts = {}
1848 for node in get_hostlist():
1849 datadump = get_yaml(node)
1850 hosts[datadump['nodename']] = datadump
1851 print yaml.dump(hosts)
1852
1853 elif sys.argv[1] == "dns":
1854 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns', 'external' in sys.argv)
1855 elif sys.argv[1] == "cleanup":
1856 # First generate all datadumps
1857 datadumps = dict()
1858 ssid_to_node = dict()
1859 for host in get_hostlist():
1860 logger.info("# Processing: %s", host)
1861 # Set some boring default values
1862 datadump = { 'board' : 'UNKNOWN' }
1863 datadump.update(get_yaml(host))
1864 datadumps[datadump['autogen_realname']] = datadump
1865
1866 (poel, errors) = make_relations(datadumps)
1867 print "\n".join(["# WARNING: %s" % x for x in errors])
1868
1869 for host,datadump in datadumps.iteritems():
1870 try:
1871 # Convert all yes and no to boolean values
1872 def fix_boolean(dump):
1873 for key in dump.keys():
1874 if type(dump[key]) == dict:
1875 dump[key] = fix_boolean(dump[key])
1876 elif str(dump[key]).lower() in ["yes", "true"]:
1877 dump[key] = True
1878 elif str(dump[key]).lower() in ["no", "false"]:
1879 # Compass richting no (Noord Oost) is valid input
1880 if key != "compass": dump[key] = False
1881 return dump
1882 datadump = fix_boolean(datadump)
1883
1884 if datadump['rdnap_x'] and datadump['rdnap_y']:
1885 datadump['latitude'], datadump['longitude'] = map(lambda x: "%.5f" % x, rd2etrs(datadump['rdnap_x'], datadump['rdnap_y']))
1886 elif datadump['latitude'] and datadump['longitude']:
1887 datadump['rdnap_x'], datadump['rdnap_y'] = etrs2rd(datadump['latitude'], datadump['longitude'])
1888
1889 if datadump['nodename'].startswith('Proxy'):
1890 datadump['nodename'] = datadump['nodename'].lower()
1891
1892 for iface_key in datadump['autogen_iface_keys']:
1893 try:
1894 # All our normal wireless cards are normal APs now
1895 if datadump[iface_key]['type'] in ['11a', '11b', '11g', 'wireless']:
1896 datadump[iface_key]['mode'] = 'ap'
1897 # Wireless Leiden SSID have an consistent lowercase/uppercase
1898 if datadump[iface_key].has_key('ssid'):
1899 ssid = datadump[iface_key]['ssid']
1900 prefix = 'ap-WirelessLeiden-'
1901 if ssid.lower().startswith(prefix.lower()):
1902 datadump[iface_key]['ssid'] = prefix + ssid[len(prefix)].upper() + ssid[len(prefix) + 1:]
1903 if datadump[iface_key].has_key('ns_ip') and not datadump[iface_key].has_key('mode'):
1904 datadump[iface_key]['mode'] = 'autogen-FIXME'
1905 if not datadump[iface_key].has_key('comment'):
1906 datadump[iface_key]['comment'] = 'autogen-FIXME'
1907
1908 if datadump[iface_key].has_key('ns_mac'):
1909 datadump[iface_key]['ns_mac'] = datadump[iface_key]['ns_mac'].lower()
1910
1911 if datadump[iface_key]['comment'].startswith('autogen-') and datadump[iface_key].has_key('comment'):
1912 datadump[iface_key] = datadump[iface_key]['desc']
1913
1914 # We are not using 802.11b anymore. OFDM is preferred over DSSS
1915 # due to better collision avoidance.
1916 if datadump[iface_key]['type'] == '11b':
1917 datadump[iface_key]['type'] = '11g'
1918
1919 # Setting 802.11g channels to de-facto standards, to avoid
1920 # un-detected sharing with other overlapping channels
1921 #
1922 # Technically we could also use channel 13 in NL, but this is not
1923 # recommended as foreign devices might not be able to select this
1924 # channel. Secondly using 1,5,9,13 instead is going to clash with
1925 # the de-facto usage of 1,6,11.
1926 #
1927 # See: https://en.wikipedia.org/wiki/List_of_WLAN_channels
1928 channels_at_2400Mhz = (1,6,11)
1929 if datadump[iface_key]['type'] == '11g' and datadump[iface_key].has_key('channel'):
1930 datadump[iface_key]['channel'] = int(datadump[iface_key]['channel'])
1931 if datadump[iface_key]['channel'] not in channels_at_2400Mhz:
1932 datadump[iface_key]['channel'] = random.choice(channels_at_2400Mhz)
1933
1934 # Mandatory interface keys
1935 if not datadump[iface_key].has_key('status'):
1936 datadump[iface_key]['status'] = 'planned'
1937
1938 x = datadump[iface_key]['comment']
1939 datadump[iface_key]['comment'] = x[0].upper() + x[1:]
1940
1941 # Fixing bridge_type if none is found
1942 if datadump[iface_key].get('extra_type', '') == 'eth2wifibridge':
1943 if not 'bridge_type' in datadump[iface_key]:
1944 datadump[iface_key]['bridge_type'] = 'NanoStation M5'
1945
1946 # Making sure description works
1947 if datadump[iface_key].has_key('desc'):
1948 if datadump[iface_key]['comment'].lower() == datadump[iface_key]['desc'].lower():
1949 del datadump[iface_key]['desc']
1950 else:
1951 print "# ERROR: At %s - %s" % (datadump['nodename'], iface_key)
1952 response = fix_conflict(datadump[iface_key]['comment'], datadump[iface_key]['desc'])
1953 if response:
1954 datadump[iface_key]['comment'] = response
1955 del datadump[iface_key]['desc']
1956
1957 # Check DHCP configuration
1958 dhcp_type(datadump[iface_key])
1959
1960 # Set the compass value based on the angle between the poels
1961 if datadump[iface_key].has_key('ns_ip'):
1962 my_pool = poel[network(datadump[iface_key]['ip'])]
1963 remote_hosts = list(set([x[0] for x in my_pool]) - set([host]))
1964 if remote_hosts:
1965 compass_target = remote_hosts[0]
1966 datadump[iface_key]['compass'] = cd_between_hosts(host, compass_target, datadumps)
1967
1968 # Monitoring Group default
1969 if not 'monitoring_group' in datadump:
1970 datadump['monitoring_group'] = 'wleiden'
1971
1972 except Exception as e:
1973 print "# Error while processing interface %s" % iface_key
1974 raise
1975 store_yaml(datadump)
1976 except Exception as e:
1977 print "# Error while processing %s" % host
1978 raise
1979 elif sys.argv[1] == "list":
1980 use_fqdn = False
1981 if len(sys.argv) < 4 or not sys.argv[2] in ["up", "down", "planned", "all"]:
1982 usage()
1983 if sys.argv[3] == "nodes":
1984 systems = get_nodelist()
1985 elif sys.argv[3] == "proxies":
1986 systems = get_proxylist()
1987 elif sys.argv[3] == "systems":
1988 systems = get_hostlist()
1989 else:
1990 usage()
1991 if len(sys.argv) > 4:
1992 if sys.argv[4] == "fqdn":
1993 use_fqdn = True
1994 else:
1995 usage()
1996
1997 for system in systems:
1998 datadump = get_yaml(system)
1999
2000 output = datadump['autogen_fqdn'] if use_fqdn else system
2001 if sys.argv[2] == "all":
2002 print output
2003 elif datadump['status'] == sys.argv[2]:
2004 print output
2005 elif sys.argv[1] == "create":
2006 if sys.argv[2] == "network.kml":
2007 print make_network_kml.make_graph()
2008 elif sys.argv[2] == "host-ips.txt":
2009 for system in get_hostlist():
2010 datadump = get_yaml(system)
2011 ips = [datadump['masterip']]
2012 for ifkey in datadump['autogen_iface_keys']:
2013 ips.append(datadump[ifkey]['ip'].split('/')[0])
2014 print system, ' '.join(ips)
2015 elif sys.argv[2] == "host-pos.txt":
2016 for system in get_hostlist():
2017 datadump = get_yaml(system)
2018 print system, datadump['rdnap_x'], datadump['rdnap_y']
2019 elif sys.argv[2] == 'ssh_config':
2020 print '''
2021Host *.wleiden.net
2022 User root
2023
2024Host 172.16.*.*
2025 User root
2026'''
2027 for system in get_hostlist():
2028 datadump = get_yaml(system)
2029 print '''\
2030Host %s
2031 User root
2032
2033Host %s
2034 User root
2035
2036Host %s
2037 User root
2038
2039Host %s
2040 User root
2041''' % (system, system.lower(), datadump['nodename'], datadump['nodename'].lower())
2042 else:
2043 usage()
2044 else:
2045 usage()
2046 else:
2047 # Do not enable debugging for config requests as it highly clutters the output
2048 if not is_text_request():
2049 cgitb.enable()
2050 response_headers, output = process_cgi_request()
2051 print_cgi_response(response_headers, output)
2052
2053def application(environ, start_response):
2054 status = '200 OK'
2055 response_headers, output = process_cgi_request(environ)
2056 start_response(status, response_headers)
2057
2058 # Debugging only
2059 # output = 'wsgi.multithread = %s' % repr(environ['wsgi.multithread'])
2060 # soutput += '\nwsgi.multiprocess = %s' % repr(environ['wsgi.multiprocess'])
2061 return [output]
2062
2063if __name__ == "__main__":
2064 main()
Note: See TracBrowser for help on using the repository browser.