source: genesis/tools/gformat.py@ 13419

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

APU board requires 'fat' daemons to run.

Note: Since this are 10.2-RELEASE systems local unbound will be used instead of named.

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