source: genesis/tools/gformat.py@ 13384

Last change on this file since 13384 was 13359, checked in by rick, 9 years ago

Remove duplicated code block

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