source: genesis/tools/gformat.py@ 13406

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

Hybrid* will also be removed from references to in FQDN

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 71.6 KB
Line 
1#!/usr/bin/env python
2#
3# vim:ts=2:et:sw=2:ai
4# Wireless Leiden configuration generator, based on yaml files'
5#
6# XXX: This should be rewritten to make use of the ipaddr.py library.
7#
8# Sample apache configuration (mind the AcceptPathInfo!)
9# ScriptAlias /wleiden/config /usr/local/www/genesis/tools/gformat.py
10# <Directory /usr/local/www/genesis>
11# Allow from all
12# AcceptPathInfo On
13# </Directory>
14#
15# MUCH FASTER WILL IT BE with mod_wsgi, due to caches and avoiding loading all
16# the heavy template lifting all the time.
17#
18# WSGIDaemonProcess gformat threads=25
19# WSGISocketPrefix run/wsgi
20#
21# <Directory /var/www/cgi-bin>
22# WSGIProcessGroup gformat
23# </Directory>
24# WSGIScriptAlias /hello /var/www/cgi-bin/genesis/tools/gformat.py
25#
26# Package dependencies list:
27# yum install python-yaml pyproj proj-epsg python-jinja2
28#
29# Rick van der Zwet <info@rickvanderzwet.nl>
30#
31
32# Hack to make the script directory is also threated as a module search path.
33import sys
34import os
35sys.path.append(os.path.dirname(__file__))
36
37SVN = filter(os.path.isfile, ('/usr/local/bin/svn', '/usr/bin/svn'))[0]
38
39import argparse
40import cgi
41import cgitb
42import copy
43import glob
44import make_network_kml
45import math
46import pyproj
47import random
48import re
49import socket
50import string
51import subprocess
52import textwrap
53import time
54import urlparse
55
56from pprint import pprint
57from collections import defaultdict
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 13405 2015-09-20 19:17:01Z 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 {% endif -%}
837{% elif nodetype == "CNode" %}
838#
839# NODE iLeiden Configuration
840#
841
842# iLeiden Proxies {{ autogen_ileiden_proxies_names }}
843list_ileiden_proxies="{{ autogen_ileiden_proxies_ips }}"
844# normal Proxies {{ autogen_normal_proxies_names }}
845list_normal_proxies="{{ autogen_normal_proxies_ips }}"
846
847captive_portal_interfaces="{{ autogen_dhcp_interfaces|join(',') }}"
848
849lvrouted_flags="-u -s s00p3rs3kr3t -m 28 -z $list_ileiden_proxies"
850{% endif %}
851
852#
853# Script variables
854#
855attached_devices="{{ autogen_attached_devices|join(' ') }}"
856neighbours="{{ autogen_neighbours|join(' ') }}"
857
858
859#
860# Interface definitions
861#\n
862""")
863
864 (addrs_list, vlan_list, dhclient_if, extra_ouput) = make_interface_list(datadump)
865 for iface, vlans in vlan_list.items():
866 output += 'vlans_%s="%s"\n' % (iface, ' '.join(vlans))
867
868 # Details like SSID
869 if extra_ouput:
870 output += extra_ouput.strip() + "\n"
871
872 # Print IP address which needs to be assigned over here
873 output += "\n"
874 for iface,addrs in sorted(addrs_list.iteritems()):
875 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
876 output += "# %s || %s || %s\n" % (iface, addr, comment)
877
878 # Write DHCLIENT entry
879 if dhclient_if[iface]:
880 output += "ifconfig_%s='SYNCDHCP'\n\n" % (iface)
881
882 # Make sure the external address is always first as this is needed in the
883 # firewall setup
884 addrs = sorted(
885 [x for x in addrs if not '0.0.0.0' in x[0]],
886 key=lambda x: x[0].split('.')[0],
887 cmp=lambda x,y: cmp(1 if x == '172' else 0, 1 if y == '172' else 0)
888 )
889 output += "ifconfig_%s='inet %s'\n" % (iface, addrs[0][0])
890 for idx, addr in enumerate(addrs[1:]):
891 output += "ifconfig_%s_alias%s='inet %s'\n" % (iface, idx, addr[0])
892 output += "\n"
893
894 rc_conf_local_cache[datadump['autogen_item']] = output
895 return output
896
897
898
899def get_all_configs():
900 """ Get dict with key 'host' with all configs present """
901 configs = dict()
902 for host in get_hostlist():
903 datadump = get_yaml(host)
904 configs[host] = datadump
905 return configs
906
907
908def get_interface_keys(config, extra=False):
909 """ Quick hack to get all interface keys, later stage convert this to a iterator """
910 elems = sorted([elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)])
911 if extra == False:
912 return filter(lambda x: not "extra" in x, elems)
913 else:
914 return elems
915
916
917def get_used_ips(configs):
918 """ Return array of all IPs used in config files"""
919 ip_list = []
920 for config in configs:
921 ip_list.append(config['masterip'])
922 for iface_key in get_interface_keys(config, True):
923 l = config[iface_key]['ip']
924 addr, mask = l.split('/')
925 # Special case do not process
926 if valid_addr(addr):
927 ip_list.append(addr)
928 else:
929 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
930 return sorted(ip_list)
931
932
933
934def get_nameservers(max_servers=None):
935 if nameservers_cache:
936 return nameservers_cache[0:max_servers]
937
938 for host in get_hostlist():
939 hostdump = get_yaml(host)
940 if hostdump['status'] == 'up' and (hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']):
941 nameservers_cache.append((hostdump['masterip'], hostdump['nodename']))
942
943 return nameservers_cache[0:max_servers]
944
945
946def get_neighbours(datadump):
947 (addrs_list, _, dhclient_if, extra_ouput) = make_interface_list(datadump)
948
949 (poel, errors) = make_relations()
950 table = []
951 for iface,addrs in sorted(addrs_list.iteritems()):
952 if iface in ['lo0']:
953 continue
954
955 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
956 if not addr.startswith('172.'):
957 # Avoid listing internet connections as pool
958 continue
959 for neighbour in poel[network(addr)]:
960 if neighbour[0] != datadump['autogen_item']:
961 table.append((iface, neighbour[1]['ip'].split('/')[0], neighbour[0] + " (" + neighbour[1]['autogen_ifname'] + ")", neighbour[1]['comment']))
962 return table
963
964
965def get_attached_devices(datadump, url=False):
966 table = []
967 for iface_key in get_interface_keys(datadump, True):
968 # Quick to avoid listing ath(4) interface as attached device
969 if 'ath0' in iface_key:
970 continue
971 ifacedump = datadump[iface_key]
972 if ifacedump.has_key('ns_ip'):
973 x_ip = ifacedump['ns_ip'].split('/')[0]
974 else:
975 x_ip = ifacedump['ip'].split('/')[0]
976
977 if 'mode' in ifacedump:
978 x_mode = ifacedump['mode']
979 else:
980 x_mode = 'unknown'
981
982 if 'bridge_type' in ifacedump:
983 device_type = ifacedump['bridge_type']
984 else:
985 device_type = 'Unknown'
986
987 table.append((ifacedump['autogen_ifname'], x_mode, 'http://%s' % x_ip if url else x_ip, device_type))
988 return table
989
990
991def generate_resolv_conf(datadump):
992 """ Generate configuration file '/etc/resolv.conf' """
993 # XXX: This should properly going to be an datastructure soon
994 datadump['autogen_header'] = generate_header(datadump, "#")
995 datadump['autogen_edge_nameservers'] = ''
996
997
998 for masterip,realname in get_nameservers():
999 datadump['autogen_edge_nameservers'] += "nameserver %-15s # %s\n" % (masterip, realname)
1000
1001 return Template("""\
1002{{ autogen_header }}
1003search wleiden.net
1004
1005# Try local (cache) first
1006nameserver 127.0.0.1
1007
1008{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
1009nameserver 8.8.8.8 # Google Public NameServer
1010nameserver 4.2.2.1 # Level3 Public NameServer
1011{% else -%}
1012# START DYNAMIC LIST - updated by /tools/nameserver-shuffle
1013{{ autogen_edge_nameservers }}
1014{% endif -%}
1015""").render(datadump)
1016
1017
1018
1019def generate_ntp_conf(datadump):
1020 """ Generate configuration file '/etc/ntp.conf' """
1021 # XXX: This should properly going to be an datastructure soon
1022
1023 datadump['autogen_header'] = generate_header(datadump, "#")
1024 datadump['autogen_ntp_servers'] = ''
1025 for host in get_hostlist():
1026 hostdump = get_yaml(host)
1027 if hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']:
1028 datadump['autogen_ntp_servers'] += "server %(masterip)-15s iburst maxpoll 9 # %(nodename)s\n" % hostdump
1029
1030 return Template("""\
1031{{ autogen_header }}
1032
1033{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
1034# Machine hooked to internet.
1035server 0.nl.pool.ntp.org iburst maxpoll 9
1036server 1.nl.pool.ntp.org iburst maxpoll 9
1037server 2.nl.pool.ntp.org iburst maxpoll 9
1038server 3.nl.pool.ntp.org iburst maxpoll 9
1039{% else -%}
1040# Local Wireless Leiden NTP Servers.
1041server 0.pool.ntp.wleiden.net iburst maxpoll 9
1042server 1.pool.ntp.wleiden.net iburst maxpoll 9
1043server 2.pool.ntp.wleiden.net iburst maxpoll 9
1044server 3.pool.ntp.wleiden.net iburst maxpoll 9
1045
1046# All the configured NTP servers
1047{{ autogen_ntp_servers }}
1048{% endif %}
1049
1050# If a server loses sync with all upstream servers, NTP clients
1051# no longer follow that server. The local clock can be configured
1052# to provide a time source when this happens, but it should usually
1053# be configured on just one server on a network. For more details see
1054# http://support.ntp.org/bin/view/Support/UndisciplinedLocalClock
1055# The use of Orphan Mode may be preferable.
1056#
1057server 127.127.1.0
1058fudge 127.127.1.0 stratum 10
1059""").render(datadump)
1060
1061
1062def generate_pf_hybrid_conf_local(datadump):
1063 """ Generate configuration file '/etc/pf.hybrid.conf.local' """
1064 datadump['autogen_header'] = generate_header(datadump, "#")
1065 return Template("""\
1066{{ autogen_header }}
1067
1068# Redirect some internal facing services outside (7)
1069# INFO: {{ rdr_rules|count }} rdr_rules (outside to internal redirect rules) defined.
1070{% for protocol, src_port,dest_ip,dest_port in rdr_rules -%}
1071rdr on $ext_if inet proto {{ protocol }} from any to $ext_if port {{ src_port }} tag SRV -> {{ dest_ip }} port {{ dest_port }}
1072{% endfor -%}
1073""").render(datadump)
1074
1075def generate_motd(datadump):
1076 """ Generate configuration file '/etc/motd' """
1077 output = Template("""\
1078FreeBSD run ``service motd onestart'' to make me look normal
1079
1080 WWW: {{ autogen_fqdn }} - http://www.wirelessleiden.nl
1081 Loc: {{ location }}
1082
1083Services:
1084{% if board == "ALIX2" -%}
1085{{" -"}} Core Node ({{ board }})
1086{% else -%}
1087{{" -"}} Hulp Node ({{ board }})
1088{% endif -%}
1089{% if service_proxy_normal -%}
1090{{" -"}} Normal Proxy
1091{% endif -%}
1092{% if service_proxy_ileiden -%}
1093{{" -"}} iLeiden Proxy
1094{% endif -%}
1095{% if service_incoming_rdr -%}
1096{{" -"}} Incoming port redirects
1097{% endif %}
1098Interlinks:\n
1099""").render(datadump)
1100
1101
1102 def make_table(table):
1103 if not table:
1104 return " - none\n"
1105 else:
1106 lines = ""
1107 col_width = [max(len(x) for x in col) for col in zip(*table)]
1108 for row in table:
1109 lines += " - " + " || ".join("{:{}}".format(x, col_width[i]) for i, x in enumerate(row)) + "\n"
1110 return lines
1111
1112 (addrs_list, vlan_list, dhclient_if, extra_ouput) = make_interface_list(datadump)
1113 table = []
1114 for iface,addrs in sorted(addrs_list.iteritems()):
1115 if iface in ['lo0']:
1116 continue
1117 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
1118 table.append((iface, addr, comment))
1119
1120 output += make_table(table)
1121 output += '\n'
1122 output += """\
1123Attached devices:
1124"""
1125 output += make_table(get_attached_devices(datadump, url=True))
1126 output += '\n'
1127 output += """\
1128Available neighbours:
1129"""
1130 output += make_table(get_neighbours(datadump))
1131
1132 return output
1133
1134
1135def format_yaml_value(value):
1136 """ Get yaml value in right syntax for outputting """
1137 if isinstance(value,str):
1138 output = '"%s"' % value
1139 else:
1140 output = value
1141 return output
1142
1143
1144
1145def format_wleiden_yaml(datadump):
1146 """ Special formatting to ensure it is editable"""
1147 output = "# Genesis config yaml style\n"
1148 output += "# vim:ts=2:et:sw=2:ai\n"
1149 output += "#\n"
1150 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
1151 for key in sorted(set(datadump.keys()) - set(iface_keys)):
1152 if key == 'rdr_rules':
1153 output += '%-10s:\n' % 'rdr_rules'
1154 for rdr_rule in datadump[key]:
1155 output += '- %s\n' % rdr_rule
1156 else:
1157 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
1158
1159 output += "\n\n"
1160
1161 # Format (key, required)
1162 key_order = (
1163 ('comment', True),
1164 ('ip', True),
1165 ('desc', True),
1166 ('sdesc', True),
1167 ('mode', True),
1168 ('type', True),
1169 ('extra_type', False),
1170 ('channel', False),
1171 ('ssid', False),
1172 ('wlan_mac', False),
1173 ('dhcp', True),
1174 ('compass', False),
1175 ('distance', False),
1176 ('ns_ip', False),
1177 ('repeater_ip', False),
1178 ('bullet2_ip', False),
1179 ('ns_mac', False),
1180 ('bullet2_mac', False),
1181 ('ns_type', False),
1182 ('bridge_type', False),
1183 ('members', True),
1184 ('status', True),
1185 )
1186
1187 for iface_key in sorted(iface_keys):
1188 try:
1189 remainder = set(datadump[iface_key].keys()) - set([x[0] for x in key_order])
1190 if remainder:
1191 raise KeyError("invalid keys: %s" % remainder)
1192
1193 output += "%s:\n" % iface_key
1194 for key,required in key_order:
1195 if datadump[iface_key].has_key(key):
1196 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
1197 output += "\n\n"
1198 except Exception:
1199 print "# Error while processing interface %s" % iface_key
1200 raise
1201
1202 return output
1203
1204
1205
1206def generate_wleiden_yaml(datadump, header=True):
1207 """ Generate (petty) version of wleiden.yaml"""
1208 output = generate_header(datadump, "#") if header else ''
1209
1210 for key in datadump.keys():
1211 if key.startswith('autogen_'):
1212 del datadump[key]
1213 # Interface autogen cleanups
1214 elif type(datadump[key]) == dict:
1215 for key2 in datadump[key].keys():
1216 if key2.startswith('autogen_'):
1217 del datadump[key][key2]
1218
1219 output += format_wleiden_yaml(datadump)
1220 return output
1221
1222def generate_nanostation_config(datadump, iface, ns_type):
1223 #TODO(rvdz): Make sure the proper nanostation IP and subnet is set
1224 datadump['iface_%s' % iface]['ns_ip'] = datadump['iface_%s' % iface]['ns_ip'].split('/')[0]
1225
1226 datadump.update(datadump['iface_%s' % iface])
1227
1228 return open(os.path.join(os.path.dirname(__file__), 'ns5m.cfg.tmpl'),'r').read() % datadump
1229
1230def generate_yaml(datadump):
1231 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
1232
1233
1234
1235def generate_config(node, config, datadump=None):
1236 """ Print configuration file 'config' of 'node' """
1237 output = ""
1238 try:
1239 # Load config file
1240 if datadump == None:
1241 datadump = get_yaml(node)
1242
1243 if config == 'wleiden.yaml':
1244 output += generate_wleiden_yaml(datadump)
1245 elif config == 'authorized_keys':
1246 f = open(os.path.join(NODE_DIR,"global_keys"), 'r')
1247 output += f.read()
1248 node_keys = os.path.join(NODE_DIR,node,'authorized_keys')
1249 # Fetch local keys if existing
1250 if os.path.exists(node_keys):
1251 output += open(node_keys, 'r').read()
1252 f.close()
1253 elif config == 'dnsmasq.conf':
1254 output += generate_dnsmasq_conf(datadump)
1255 elif config == 'dhcpd.conf':
1256 output += generate_dhcpd_conf(datadump)
1257 elif config == 'rc.conf.local':
1258 output += generate_rc_conf_local(datadump)
1259 elif config == 'resolv.conf':
1260 output += generate_resolv_conf(datadump)
1261 elif config == 'ntp.conf':
1262 output += generate_ntp_conf(datadump)
1263 elif config == 'motd':
1264 output += generate_motd(datadump)
1265 elif config == 'pf.hybrid.conf.local':
1266 output += generate_pf_hybrid_conf_local(datadump)
1267 elif config.startswith('vr'):
1268 interface, ns_type = config.strip('.yaml').split('-')
1269 output += generate_nanostation_config(datadump, interface, ns_type)
1270 else:
1271 assert False, "Config not found!"
1272 except IOError, e:
1273 output += "[ERROR] Config file not found"
1274 return output
1275
1276
1277
1278def process_cgi_request(environ=os.environ):
1279 """ When calling from CGI """
1280 response_headers = []
1281 content_type = 'text/plain'
1282
1283 # Update repository if requested
1284 form = urlparse.parse_qs(environ['QUERY_STRING']) if environ.has_key('QUERY_STRING') else None
1285 if form and form.has_key("action") and "update" in form["action"]:
1286 output = "[INFO] Updating subverion, please wait...\n"
1287 output += subprocess.Popen([SVN, 'cleanup', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
1288 output += subprocess.Popen([SVN, 'up', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
1289 output += "[INFO] All done, redirecting in 5 seconds"
1290 response_headers += [
1291 ('Refresh', '5; url=.'),
1292 ]
1293 reload_cache()
1294 else:
1295 base_uri = environ['PATH_INFO']
1296 uri = base_uri.strip('/').split('/')
1297
1298 output = "Template Holder"
1299 if base_uri.endswith('/create/network.kml'):
1300 content_type='application/vnd.google-earth.kml+xml'
1301 output = make_network_kml.make_graph()
1302 elif base_uri.endswith('/api/get/nodeplanner.json'):
1303 content_type='application/json'
1304 output = make_network_kml.make_nodeplanner_json()
1305 elif not uri[0]:
1306 if is_text_request(environ):
1307 output = '\n'.join(get_hostlist())
1308 else:
1309 content_type = 'text/html'
1310 output = generate_title(get_hostlist())
1311 elif len(uri) == 1:
1312 if is_text_request(environ):
1313 output = generate_node(uri[0])
1314 else:
1315 content_type = 'text/html'
1316 output = generate_node_overview(uri[0])
1317 elif len(uri) == 2:
1318 output = generate_config(uri[0], uri[1])
1319 else:
1320 assert False, "Invalid option"
1321
1322 # Return response
1323 response_headers += [
1324 ('Content-type', content_type),
1325 ('Content-Length', str(len(output))),
1326 ]
1327 return(response_headers, str(output))
1328
1329
1330def make_dns(output_dir = 'dns', external = False):
1331 items = dict()
1332
1333 # hostname is key, IP is value
1334 wleiden_zone = defaultdict(list)
1335 wleiden_cname = dict()
1336
1337 pool = dict()
1338 for node in get_hostlist():
1339 datadump = get_yaml(node)
1340
1341 # Proxy naming convention is special
1342 fqdn = datadump['nodename']
1343 if datadump['nodetype'] in ['CNode', 'Hybrid']:
1344 wleiden_cname[datadump['nodename']] = fqdn
1345
1346 if datadump.has_key('rdr_host'):
1347 remote_target = datadump['rdr_host']
1348 elif datadump.has_key('remote_access') and datadump['remote_access']:
1349 remote_target = datadump['remote_access'].split(':')[0]
1350 else:
1351 remote_target = None
1352
1353 if remote_target:
1354 try:
1355 parseaddr(remote_target)
1356 wleiden_zone[datadump['nodename'] + '.gw'].append((remote_target, False))
1357 except (IndexError, ValueError):
1358 wleiden_cname[datadump['nodename'] + '.gw'] = remote_target + '.'
1359
1360
1361 wleiden_zone[fqdn].append((datadump['masterip'], True))
1362
1363 # Hacking to get proper DHCP IPs and hostnames
1364 for iface_key in get_interface_keys(datadump):
1365 iface_name = iface_key.replace('_','-')
1366 (ip, cidr) = datadump[iface_key]['ip'].split('/')
1367 try:
1368 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
1369 datadump[iface_key]['autogen_netmask'] = cidr2netmask(cidr)
1370 dhcp_part = ".".join(ip.split('.')[0:3])
1371 if ip != datadump['masterip']:
1372 wleiden_zone["dhcp-gateway-%s.%s" % (iface_name, fqdn)].append((ip, False))
1373 for i in range(int(dhcp_start), int(dhcp_stop) + 1):
1374 wleiden_zone["dhcp-%s-%s.%s" % (i, iface_name, fqdn)].append(("%s.%s" % (dhcp_part, i), True))
1375 except (AttributeError, ValueError, KeyError):
1376 # First push it into a pool, to indentify the counter-part later on
1377 addr = parseaddr(ip)
1378 cidr = int(cidr)
1379 addr = addr & ~((1 << (32 - cidr)) - 1)
1380 if pool.has_key(addr):
1381 pool[addr] += [(iface_name, fqdn, ip)]
1382 else:
1383 pool[addr] = [(iface_name, fqdn, ip)]
1384 continue
1385
1386
1387
1388 # WL uses an /29 to configure an interface. IP's are ordered like this:
1389 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
1390
1391 sn = lambda x: re.sub(r'(?i)^cnode','',x)
1392
1393 # Automatic naming convention of interlinks namely 2 + remote.lower()
1394 for (key,value) in pool.iteritems():
1395 # Make sure they are sorted from low-ip to high-ip
1396 value = sorted(value, key=lambda x: parseaddr(x[2]))
1397
1398 if len(value) == 1:
1399 (iface_name, fqdn, ip) = value[0]
1400 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)].append((ip, True))
1401
1402 # Device DNS names
1403 if 'cnode' in fqdn.lower():
1404 wleiden_zone["d-at-%s.%s" % (iface_name, fqdn)].append((showaddr(parseaddr(ip) + 1), False))
1405 wleiden_cname["d-at-%s.%s" % (iface_name,sn(fqdn))] = "d-at-%s.%s" % ((iface_name, fqdn))
1406
1407 elif len(value) == 2:
1408 (a_iface_name, a_fqdn, a_ip) = value[0]
1409 (b_iface_name, b_fqdn, b_ip) = value[1]
1410 wleiden_zone["2%s.%s" % (b_fqdn,a_fqdn)].append((a_ip, True))
1411 wleiden_zone["2%s.%s" % (a_fqdn,b_fqdn)].append((b_ip, True))
1412
1413 # Device DNS names
1414 if 'cnode' in a_fqdn.lower() and 'cnode' in b_fqdn.lower():
1415 wleiden_zone["d-at-%s.%s" % (a_iface_name, a_fqdn)].append((showaddr(parseaddr(a_ip) + 1), False))
1416 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)].append((showaddr(parseaddr(b_ip) - 1), False))
1417 wleiden_cname["d-at-%s.%s" % (a_iface_name,sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1418 wleiden_cname["d-at-%s.%s" % (b_iface_name,sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1419 wleiden_cname["d2%s.%s" % (sn(b_fqdn),sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1420 wleiden_cname["d2%s.%s" % (sn(a_fqdn),sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1421
1422 else:
1423 pool_members = [k[1] for k in value]
1424 for item in value:
1425 (iface_name, fqdn, ip) = item
1426 wleiden_zone["2ring.%s" % (fqdn)].append((ip, True))
1427
1428 # Include static DNS entries
1429 # XXX: Should they override the autogenerated results?
1430 # XXX: Convert input to yaml more useable.
1431 # Format:
1432 ##; this is a comment
1433 ## roomburgh=CNodeRoomburgh1
1434 ## apkerk1.CNodeVosko=172.17.176.8 ;this as well
1435 dns_list = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
1436
1437 # Hack to allow special entries, for development
1438 wleiden_raw = {}
1439
1440 for line in dns_list:
1441 reverse = False
1442 k, items = line.items()[0]
1443 if type(items) == dict:
1444 if items.has_key('reverse'):
1445 reverse = items['reverse']
1446 items = items['a']
1447 else:
1448 items = items['cname']
1449 items = [items] if type(items) != list else items
1450 for item in items:
1451 if item.startswith('IN '):
1452 wleiden_raw[k] = item
1453 elif valid_addr(item):
1454 wleiden_zone[k].append((item, reverse))
1455 else:
1456 wleiden_cname[k] = item
1457
1458 # Hack to get dynamic pool listing
1459 def chunks(l, n):
1460 return [l[i:i+n] for i in range(0, len(l), n)]
1461
1462 ntp_servers = [x[0] for x in get_nameservers()]
1463 for id, chunk in enumerate(chunks(ntp_servers,(len(ntp_servers)/4))):
1464 for ntp_server in chunk:
1465 wleiden_zone['%i.pool.ntp' % id].append((ntp_server, False))
1466
1467 details = dict()
1468 # 24 updates a day allowed
1469 details['serial'] = time.strftime('%Y%m%d%H')
1470
1471 if external:
1472 dns_masters = ['siteview.wirelessleiden.nl', 'ns1.vanderzwet.net']
1473 else:
1474 dns_masters = ['sunny.wleiden.net'] + ["%s.wleiden.net" % x[1] for x in get_nameservers(max_servers=3)]
1475
1476 details['master'] = dns_masters[0]
1477 details['ns_servers'] = '\n'.join(['\tNS\t%s.' % x for x in dns_masters])
1478
1479 dns_header = '''
1480$TTL 3h
1481%(zone)s. SOA %(master)s. beheer.lijst.wirelessleiden.nl. ( %(serial)s 15m 15m 1w 60s )
1482 ; Serial, Refresh, Retry, Expire, Neg. cache TTL
1483
1484%(ns_servers)s
1485 \n'''
1486
1487
1488 if not os.path.isdir(output_dir):
1489 os.makedirs(output_dir)
1490 details['zone'] = 'wleiden.net'
1491 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
1492 f.write(dns_header % details)
1493
1494 for host,items in wleiden_zone.iteritems():
1495 for ip,reverse in items:
1496 if ip not in ['0.0.0.0']:
1497 f.write("%s.wleiden.net. IN A %s \n" % (host.lower(), ip))
1498 for source,dest in wleiden_cname.iteritems():
1499 dest = dest if dest.endswith('.') else dest + ".wleiden.net."
1500 f.write("%s.wleiden.net. IN CNAME %s\n" % (source.lower(), dest.lower()))
1501 for source, dest in wleiden_raw.iteritems():
1502 f.write("%s.wleiden.net. %s\n" % (source, dest))
1503 f.close()
1504
1505 # Create whole bunch of specific sub arpa zones. To keep it compliant
1506 for s in range(16,32):
1507 details['zone'] = '%i.172.in-addr.arpa' % s
1508 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
1509 f.write(dns_header % details)
1510
1511 #XXX: Not effient, fix to proper data structure and do checks at other
1512 # stages
1513 for host,items in wleiden_zone.iteritems():
1514 for ip,reverse in items:
1515 if not reverse:
1516 continue
1517 if valid_addr(ip):
1518 if valid_addr(ip):
1519 if int(ip.split('.')[1]) == s:
1520 rev_ip = '.'.join(reversed(ip.split('.')))
1521 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
1522 f.close()
1523
1524
1525def usage():
1526 print """Usage: %(prog)s <argument>
1527Argument:
1528\tcleanup = Cleanup all YAML files to specified format
1529\tstandalone [port] = Run configurator webserver [8000]
1530\tdns [outputdir] = Generate BIND compliant zone files in dns [./dns]
1531\tnagios-export [--heavy-load] = Generate basic nagios configuration file.
1532\tfull-export = Generate yaml export script for heatmap.
1533\tstatic [outputdir] = Generate all config files and store on disk
1534\t with format ./<outputdir>/%%NODE%%/%%FILE%% [./static]
1535\ttest <node> [<file>] = Receive output for certain node [all files].
1536\ttest-cgi <node> <file> = Receive output of CGI script [all files].
1537\tlist <status> <items> = List systems which have certain status
1538
1539Arguments:
1540\t<node> = NodeName (example: HybridRick)
1541\t<file> = %(files)s
1542\t<status> = all|up|down|planned
1543\t<items> = systems|nodes|proxies
1544
1545NOTE FOR DEVELOPERS; you can test your changes like this:
1546 BEFORE any changes in this code:
1547 $ ./gformat.py static /tmp/pre
1548 AFTER the changes:
1549 $ ./gformat.py static /tmp/post
1550 VIEW differences and VERIFY all are OK:
1551 $ diff -urI 'Generated' -r /tmp/pre /tmp/post
1552""" % { 'prog' : sys.argv[0], 'files' : '|'.join(files) }
1553 exit(0)
1554
1555
1556def is_text_request(environ=os.environ):
1557 """ Find out whether we are calling from the CLI or any text based CLI utility """
1558 try:
1559 return environ['HTTP_USER_AGENT'].split()[0] in ['curl', 'fetch', 'wget']
1560 except KeyError:
1561 return True
1562
1563def switchFormat(setting):
1564 if setting:
1565 return "YES"
1566 else:
1567 return "NO"
1568
1569def rlinput(prompt, prefill=''):
1570 import readline
1571 readline.set_startup_hook(lambda: readline.insert_text(prefill))
1572 try:
1573 return raw_input(prompt)
1574 finally:
1575 readline.set_startup_hook()
1576
1577def fix_conflict(left, right, default='i'):
1578 while True:
1579 print "## %-30s | %-30s" % (left, right)
1580 c = raw_input("## Solve Conflict (h for help) <l|r|e|i|> [%s]: " % default)
1581 if not c:
1582 c = default
1583
1584 if c in ['l','1']:
1585 return left
1586 elif c in ['r','2']:
1587 return right
1588 elif c in ['e', '3']:
1589 return rlinput("Edit: ", "%30s | %30s" % (left, right))
1590 elif c in ['i', '4']:
1591 return None
1592 else:
1593 print "#ERROR: '%s' is invalid input (left, right, edit or ignore)!" % c
1594
1595
1596
1597def print_cgi_response(response_headers, output):
1598 """Could we not use some kind of wsgi wrapper to make this output?"""
1599 for header in response_headers:
1600 print "%s: %s" % header
1601 print
1602 print output
1603
1604
1605def fill_cache():
1606 ''' Poor man re-loading of few cache items (the slow ones) '''
1607 for host in get_hostlist():
1608 get_yaml(host)
1609
1610
1611def reload_cache():
1612 clear_cache()
1613 fill_cache()
1614
1615
1616def main():
1617 """Hard working sub"""
1618 # Allow easy hacking using the CLI
1619 if not os.environ.has_key('PATH_INFO'):
1620 if len(sys.argv) < 2:
1621 usage()
1622
1623 if sys.argv[1] == "standalone":
1624 import SocketServer
1625 import CGIHTTPServer
1626 # Hop to the right working directory.
1627 os.chdir(os.path.dirname(__file__))
1628 try:
1629 PORT = int(sys.argv[2])
1630 except (IndexError,ValueError):
1631 PORT = 8000
1632
1633 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
1634 """ Serve this CGI from the root of the webserver """
1635 def is_cgi(self):
1636 if "favicon" in self.path:
1637 return False
1638
1639 self.cgi_info = (os.path.basename(__file__), self.path)
1640 self.path = ''
1641 return True
1642 handler = MyCGIHTTPRequestHandler
1643 SocketServer.TCPServer.allow_reuse_address = True
1644 httpd = SocketServer.TCPServer(("", PORT), handler)
1645 httpd.server_name = 'localhost'
1646 httpd.server_port = PORT
1647
1648 logger.info("serving at port %s", PORT)
1649 try:
1650 httpd.serve_forever()
1651 except KeyboardInterrupt:
1652 httpd.shutdown()
1653 logger.info("All done goodbye")
1654 elif sys.argv[1] == "test":
1655 # Basic argument validation
1656 try:
1657 node = sys.argv[2]
1658 except IndexError:
1659 print "Invalid argument"
1660 exit(1)
1661 except IOError as e:
1662 print e
1663 exit(1)
1664
1665 datadump = get_yaml(node)
1666
1667
1668 # Get files to generate
1669 gen_files = sys.argv[3:] if len(sys.argv) > 3 else files
1670
1671 # Actual config generation
1672 for config in gen_files:
1673 logger.info("## Generating %s %s", node, config)
1674 print generate_config(node, config, datadump)
1675 elif sys.argv[1] == "test-cgi":
1676 os.environ['PATH_INFO'] = "/".join(sys.argv[2:])
1677 os.environ['SCRIPT_NAME'] = __file__
1678 response_headers, output = process_cgi_request()
1679 print_cgi_response(response_headers, output)
1680 elif sys.argv[1] == "static":
1681 items = dict()
1682 items['output_dir'] = sys.argv[2] if len(sys.argv) > 2 else "./static"
1683 for node in get_hostlist():
1684 items['node'] = node
1685 items['wdir'] = "%(output_dir)s/%(node)s" % items
1686 if not os.path.isdir(items['wdir']):
1687 os.makedirs(items['wdir'])
1688 datadump = get_yaml(node)
1689 for config in files:
1690 items['config'] = config
1691 logger.info("## Generating %(node)s %(config)s" % items)
1692 f = open("%(wdir)s/%(config)s" % items, "w")
1693 f.write(generate_config(node, config, datadump))
1694 f.close()
1695 elif sys.argv[1] == "wind-export":
1696 items = dict()
1697 for node in get_hostlist():
1698 datadump = get_yaml(node)
1699 sql = """INSERT IGNORE INTO nodes (name, name_ns, longitude, latitude)
1700 VALUES ('%(nodename)s', '%(nodename)s', %(latitude)s, %(longitude)s);""" % datadump;
1701 sql = """INSERT IGNORE INTO users_nodes (user_id, node_id, owner)
1702 VALUES (
1703 (SELECT id FROM users WHERE username = 'rvdzwet'),
1704 (SELECT id FROM nodes WHERE name = '%(nodename)s'),
1705 'Y');""" % datadump
1706 #for config in files:
1707 # items['config'] = config
1708 # print "## Generating %(node)s %(config)s" % items
1709 # f = open("%(wdir)s/%(config)s" % items, "w")
1710 # f.write(generate_config(node, config, datadump))
1711 # f.close()
1712 for node in get_hostlist():
1713 datadump = get_yaml(node)
1714 for iface_key in sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]):
1715 ifacedump = datadump[iface_key]
1716 if ifacedump.has_key('mode') and ifacedump['mode'] == 'ap-wds':
1717 ifacedump['nodename'] = datadump['nodename']
1718 if not ifacedump.has_key('channel') or not ifacedump['channel']:
1719 ifacedump['channel'] = 0
1720 sql = """INSERT INTO links (node_id, type, ssid, protocol, channel, status)
1721 VALUES ((SELECT id FROM nodes WHERE name = '%(nodename)s'), 'ap',
1722 '%(ssid)s', 'IEEE 802.11b', %(channel)s, 'active');""" % ifacedump
1723 elif sys.argv[1] == "nagios-export":
1724 try:
1725 heavy_load = (sys.argv[2] == "--heavy-load")
1726 except IndexError:
1727 heavy_load = False
1728
1729 hostgroup_details = {
1730 'wleiden' : 'Stichting Wireless Leiden - FreeBSD Nodes',
1731 'wzoeterwoude' : 'Stichting Wireless Leiden - Afdeling Zoeterwoude - Free-WiFi Project',
1732 'walphen' : 'Stichting Wireless Alphen',
1733 'westeinder' : 'Westeinder Plassen',
1734 }
1735
1736 # Convert IP to Host
1737 ip2host = {'root' : 'root'}
1738 for host in get_hostlist():
1739 datadump = get_yaml(host)
1740 ip2host[datadump['masterip']] = datadump['autogen_fqdn']
1741 for iface in get_interface_keys(datadump):
1742 ip2host[datadump[iface]['autogen_gateway']] = datadump['autogen_fqdn']
1743
1744 # Find dependency tree based on output of lvrouted.mytree of nearest node
1745 parents = defaultdict(list)
1746 stack = ['root']
1747 prev_depth = 0
1748 for line in open('lvrouted.mytree').readlines():
1749 depth = line.count('\t')
1750 ip = line.strip().split()[0]
1751
1752 if prev_depth < depth:
1753 try:
1754 parents[ip2host[ip]].append(ip2host[stack[-1]])
1755 except KeyError as e:
1756 print >> stderr, "# Unable to find %s in configuration files" % e.args[0]
1757 stack.append(ip)
1758 elif prev_depth > depth:
1759 stack = stack[:(depth - prev_depth)]
1760 elif prev_depth == depth:
1761 try:
1762 parents[ip2host[ip]].append(ip2host[stack[-1]])
1763 except KeyError as e:
1764 print >> stderr, "# Unable to find %s in configuration files" % e.args[0]
1765
1766
1767 prev_depth = depth
1768 # Observe that some nodes has themself as parent or multiple parents
1769 # for now take only the first parent, other behaviour is yet to be explained
1770
1771
1772
1773 params = {
1774 'check_interval' : 5 if heavy_load else 120,
1775 'retry_interval' : 1 if heavy_load else 10,
1776 'max_check_attempts' : 10 if heavy_load else 6,
1777 'notification_interval': 120 if heavy_load else 240,
1778 }
1779
1780 print '''\
1781define host {
1782 name wleiden-node ; Default Node Template
1783 use generic-host ; Use the standard template as initial starting point
1784 check_period 24x7 ; By default, FreeBSD hosts are checked round the clock
1785 check_interval %(check_interval)s ; Actively check the host every 5 minutes
1786 retry_interval %(retry_interval)s ; Schedule host check retries at 1 minute intervals
1787 notification_interval %(notification_interval)s
1788 max_check_attempts %(max_check_attempts)s ; Check each FreeBSD host 10 times (max)
1789 check_command check-host-alive ; Default command to check FreeBSD hosts
1790 register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
1791}
1792
1793define service {
1794 name wleiden-service ; Default Service Template
1795 use generic-service ; Use the standard template as initial starting point
1796 check_period 24x7 ; By default, FreeBSD hosts are checked round the clock
1797 check_interval %(check_interval)s ; Actively check the host every 5 minutes
1798 retry_interval %(retry_interval)s ; Schedule host check retries at 1 minute intervals
1799 notification_interval %(notification_interval)s
1800 max_check_attempts %(max_check_attempts)s ; Check each FreeBSD host 10 times (max)
1801 register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
1802}
1803
1804# Please make sure to install:
1805# make -C /usr/ports/net-mgmt/nagios-check_netsnmp install clean
1806#
1807# Recompile net-mgmt/nagios-plugins to support check_snmp
1808# make -C /usr/ports/net-mgmt/nagios-plugins
1809#
1810# Install net/bind-tools to allow v2/check_dns_wl to work:
1811# pkg install bind-tools
1812#
1813define command{
1814 command_name check_snmp_disk
1815 command_line $USER1$/check_snmp_disk -H $HOSTADDRESS$ -C public
1816}
1817
1818define command{
1819 command_name check_netsnmp_load
1820 command_line $USER1$/check_snmp_load.pl -H $HOSTADDRESS$ -C public -w 80 -c 90
1821}
1822
1823define command{
1824 command_name check_netsnmp_proc
1825 command_line $USER1$/check_snmp_proc -H $HOSTADDRESS$ -C public
1826}
1827
1828define command{
1829 command_name check_by_ssh
1830 command_line $USER1$/check_by_ssh -H $HOSTADDRESS$ -p $ARG1$ -C "$ARG2$ $ARG3$ $ARG4$ $ARG5$ $ARG6$"
1831}
1832
1833define command{
1834 command_name check_dns_wl
1835 command_line $USER1$/v2/check_dns_wl $HOSTADDRESS$ $ARG1$
1836}
1837
1838define command{
1839 command_name check_snmp_uptime
1840 command_line $USER1$/check_snmp -H $HOSTADDRESS$ -C public -o .1.3.6.1.2.1.1.3.0
1841}
1842
1843
1844# TDB: dhcp leases
1845# /usr/local/libexec/nagios/check_netsnmp -H 192.168.178.47 --oid 1 exec
1846
1847# TDB: internet status
1848# /usr/local/libexec/nagios/check_netsnmp -H 192.168.178.47 --oid 1 file
1849
1850# TDB: Advanced local passive checks
1851# /usr/local/libexec/nagios/check_by_ssh
1852''' % params
1853
1854 print '''\
1855# Service Group, not displayed by default
1856define hostgroup {
1857 hostgroup_name srv_hybrid
1858 alias All Hybrid Nodes
1859 register 0
1860}
1861
1862define service {
1863 use wleiden-service
1864 hostgroup_name srv_hybrid
1865 service_description SSH
1866 check_command check_ssh
1867}
1868
1869define service {
1870 use wleiden-service,service-pnp
1871 hostgroup_name srv_hybrid
1872 service_description HTTP
1873 check_command check_http
1874}
1875
1876define service {
1877 use wleiden-service
1878 hostgroup_name srv_hybrid
1879 service_description DNS
1880 check_command check_dns_wl!"www.wirelessleiden.nl"
1881}
1882'''
1883
1884 if heavy_load:
1885 print '''\
1886define service {
1887 use wleiden-service
1888 hostgroup_name srv_hybrid
1889 service_description UPTIME
1890 check_command check_snmp_uptime
1891}
1892
1893#define service {
1894# use wleiden-service
1895# hostgroup_name srv_hybrid
1896# service_description NTP
1897# check_command check_ntp_peer
1898#}
1899
1900define service {
1901 use wleiden-service
1902 hostgroup_name srv_hybrid
1903 service_description LOAD
1904 check_command check_netsnmp_load
1905}
1906
1907define service {
1908 use wleiden-service
1909 hostgroup_name srv_hybrid
1910 service_description PROC
1911 check_command check_netsnmp_proc
1912}
1913
1914define service {
1915 use wleiden-service
1916 hostgroup_name srv_hybrid
1917 service_description DISK
1918 check_command check_snmp_disk
1919}
1920'''
1921 for node in get_hostlist():
1922 datadump = get_yaml(node)
1923 if not datadump['status'] == 'up':
1924 continue
1925 if not hostgroup_details.has_key(datadump['monitoring_group']):
1926 hostgroup_details[datadump['monitoring_group']] = datadump['monitoring_group']
1927 print '''\
1928define host {
1929 use wleiden-node,host-pnp
1930 contact_groups admins
1931 host_name %(autogen_fqdn)s
1932 address %(masterip)s
1933 hostgroups srv_hybrid,%(monitoring_group)s\
1934''' % datadump
1935 if (len(parents[datadump['autogen_fqdn']]) > 0) and parents[datadump['autogen_fqdn']][0] != 'root':
1936 print '''\
1937 parents %(parents)s\
1938''' % { 'parents' : parents[datadump['autogen_fqdn']][0] }
1939 print '''\
1940}
1941'''
1942
1943
1944 for name,alias in hostgroup_details.iteritems():
1945 print '''\
1946define hostgroup {
1947 hostgroup_name %s
1948 alias %s
1949} ''' % (name, alias)
1950
1951
1952 elif sys.argv[1] == "full-export":
1953 hosts = {}
1954 for node in get_hostlist():
1955 datadump = get_yaml(node)
1956 hosts[datadump['nodename']] = datadump
1957 print yaml.dump(hosts)
1958
1959 elif sys.argv[1] == "dns":
1960 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns', 'external' in sys.argv)
1961 elif sys.argv[1] == "cleanup":
1962 # First generate all datadumps
1963 datadumps = dict()
1964 ssid_to_node = dict()
1965 for host in get_hostlist():
1966 logger.info("# Processing: %s", host)
1967 # Set some boring default values
1968 datadump = { 'board' : 'UNKNOWN' }
1969 datadump.update(get_yaml(host))
1970 datadumps[datadump['nodename']] = datadump
1971
1972 (poel, errors) = make_relations()
1973 print "\n".join(["# WARNING: %s" % x for x in errors])
1974
1975 for host,datadump in datadumps.iteritems():
1976 try:
1977 # Convert all yes and no to boolean values
1978 def fix_boolean(dump):
1979 for key in dump.keys():
1980 if type(dump[key]) == dict:
1981 dump[key] = fix_boolean(dump[key])
1982 elif str(dump[key]).lower() in ["yes", "true"]:
1983 dump[key] = True
1984 elif str(dump[key]).lower() in ["no", "false"]:
1985 # Compass richting no (Noord Oost) is valid input
1986 if key != "compass": dump[key] = False
1987 return dump
1988 datadump = fix_boolean(datadump)
1989
1990 if 'rdnap_x' in datadump and 'rdnap_y' in datadump:
1991 datadump['latitude'], datadump['longitude'] = map(lambda x: "%.5f" % x, rd2etrs(datadump['rdnap_x'], datadump['rdnap_y']))
1992 elif 'latitude' in datadump and 'longitude' in datadump:
1993 datadump['rdnap_x'], datadump['rdnap_y'] = etrs2rd(datadump['latitude'], datadump['longitude'])
1994
1995 if datadump['nodename'].startswith('Proxy'):
1996 datadump['nodename'] = datadump['nodename'].lower()
1997
1998 for iface_key in get_interface_keys(datadump):
1999 try:
2000 # All our normal wireless cards are normal APs now
2001 if datadump[iface_key]['type'] in ['11a', '11b', '11g', 'wireless']:
2002 datadump[iface_key]['mode'] = 'ap'
2003 # Wireless Leiden SSID have an consistent lowercase/uppercase
2004 if datadump[iface_key].has_key('ssid'):
2005 ssid = datadump[iface_key]['ssid']
2006 prefix = 'ap-WirelessLeiden-'
2007 if ssid.lower().startswith(prefix.lower()):
2008 datadump[iface_key]['ssid'] = prefix + ssid[len(prefix)].upper() + ssid[len(prefix) + 1:]
2009 if datadump[iface_key].has_key('ns_ip') and not datadump[iface_key].has_key('mode'):
2010 datadump[iface_key]['mode'] = 'autogen-FIXME'
2011 if not datadump[iface_key].has_key('comment'):
2012 datadump[iface_key]['comment'] = 'autogen-FIXME'
2013
2014 if datadump[iface_key].has_key('ns_mac'):
2015 datadump[iface_key]['ns_mac'] = datadump[iface_key]['ns_mac'].lower()
2016
2017 if datadump[iface_key]['comment'].startswith('autogen-') and datadump[iface_key].has_key('comment'):
2018 datadump[iface_key] = datadump[iface_key]['desc']
2019
2020 # We are not using 802.11b anymore. OFDM is preferred over DSSS
2021 # due to better collision avoidance.
2022 if datadump[iface_key]['type'] == '11b':
2023 datadump[iface_key]['type'] = '11g'
2024
2025 # Setting 802.11g channels to de-facto standards, to avoid
2026 # un-detected sharing with other overlapping channels
2027 #
2028 # Technically we could also use channel 13 in NL, but this is not
2029 # recommended as foreign devices might not be able to select this
2030 # channel. Secondly using 1,5,9,13 instead is going to clash with
2031 # the de-facto usage of 1,6,11.
2032 #
2033 # See: https://en.wikipedia.org/wiki/List_of_WLAN_channels
2034 channels_at_2400Mhz = (1,6,11)
2035 if datadump[iface_key]['type'] == '11g' and datadump[iface_key].has_key('channel'):
2036 datadump[iface_key]['channel'] = int(datadump[iface_key]['channel'])
2037 if datadump[iface_key]['channel'] not in channels_at_2400Mhz:
2038 datadump[iface_key]['channel'] = random.choice(channels_at_2400Mhz)
2039
2040 # Mandatory interface keys
2041 if not datadump[iface_key].has_key('status'):
2042 datadump[iface_key]['status'] = 'planned'
2043
2044 x = datadump[iface_key]['comment']
2045 datadump[iface_key]['comment'] = x[0].upper() + x[1:]
2046
2047 # Fixing bridge_type if none is found
2048 if datadump[iface_key].get('extra_type', '') == 'eth2wifibridge':
2049 if not 'bridge_type' in datadump[iface_key]:
2050 datadump[iface_key]['bridge_type'] = 'NanoStation M5'
2051
2052 # Making sure description works
2053 if datadump[iface_key].has_key('desc'):
2054 if datadump[iface_key]['comment'].lower() == datadump[iface_key]['desc'].lower():
2055 del datadump[iface_key]['desc']
2056 else:
2057 print "# ERROR: At %s - %s" % (datadump['nodename'], iface_key)
2058 response = fix_conflict(datadump[iface_key]['comment'], datadump[iface_key]['desc'])
2059 if response:
2060 datadump[iface_key]['comment'] = response
2061 del datadump[iface_key]['desc']
2062
2063 # Check DHCP configuration
2064 dhcp_type(datadump[iface_key])
2065
2066 # Set the compass value based on the angle between the poels
2067 if datadump[iface_key].has_key('ns_ip'):
2068 my_pool = poel[network(datadump[iface_key]['ip'])]
2069 remote_hosts = list(set([x[0] for x in my_pool]) - set([host]))
2070 if remote_hosts:
2071 compass_target = remote_hosts[0]
2072 datadump[iface_key]['compass'] = cd_between_hosts(host, compass_target, datadumps)
2073
2074 # Monitoring Group default
2075 if not 'monitoring_group' in datadump:
2076 datadump['monitoring_group'] = 'wleiden'
2077
2078 except Exception:
2079 print "# Error while processing interface %s" % iface_key
2080 raise
2081 store_yaml(datadump)
2082 except Exception:
2083 print "# Error while processing %s" % host
2084 raise
2085 elif sys.argv[1] == "list":
2086 use_fqdn = False
2087 if len(sys.argv) < 4:
2088 usage()
2089 if not sys.argv[2] in ["up", "down", "planned", "all"]:
2090 usage()
2091 if not sys.argv[3] in ["nodes","proxies","systems"]:
2092 usage()
2093
2094 if len(sys.argv) > 4:
2095 if sys.argv[4] == "fqdn":
2096 use_fqdn = True
2097 else:
2098 usage()
2099
2100 for system in get_hostlist():
2101 datadump = get_yaml(system)
2102 if sys.argv[3] == 'proxies' and not datadump['service_proxy_ileiden']:
2103 continue
2104
2105 output = datadump['autogen_fqdn'] if use_fqdn else system
2106 if sys.argv[2] == "all":
2107 print output
2108 elif datadump['status'] == sys.argv[2]:
2109 print output
2110 elif sys.argv[1] == "create":
2111 if sys.argv[2] == "network.kml":
2112 print make_network_kml.make_graph()
2113 elif sys.argv[2] == "host-ips.txt":
2114 for system in get_hostlist():
2115 datadump = get_yaml(system)
2116 ips = [datadump['masterip']]
2117 for ifkey in get_interface_keys(datadump):
2118 ips.append(datadump[ifkey]['ip'].split('/')[0])
2119 print system, ' '.join(ips)
2120 elif sys.argv[2] == "host-pos.txt":
2121 for system in get_hostlist():
2122 datadump = get_yaml(system)
2123 print system, datadump['rdnap_x'], datadump['rdnap_y']
2124 elif sys.argv[2] == 'ssh_config':
2125 print '''
2126Host *.wleiden.net
2127 User root
2128
2129Host 172.16.*.*
2130 User root
2131'''
2132 for system in get_hostlist():
2133 datadump = get_yaml(system)
2134 print '''\
2135Host %s
2136 User root
2137
2138Host %s
2139 User root
2140
2141Host %s
2142 User root
2143
2144Host %s
2145 User root
2146''' % (system, system.lower(), datadump['nodename'], datadump['nodename'].lower())
2147 else:
2148 usage()
2149 else:
2150 usage()
2151 else:
2152 # Do not enable debugging for config requests as it highly clutters the output
2153 if not is_text_request():
2154 cgitb.enable()
2155 response_headers, output = process_cgi_request()
2156 print_cgi_response(response_headers, output)
2157
2158def application(environ, start_response):
2159 status = '200 OK'
2160 response_headers, output = process_cgi_request(environ)
2161 start_response(status, response_headers)
2162
2163 # Debugging only
2164 # output = 'wsgi.multithread = %s' % repr(environ['wsgi.multithread'])
2165 # soutput += '\nwsgi.multiprocess = %s' % repr(environ['wsgi.multiprocess'])
2166 return [output]
2167
2168if __name__ == "__main__":
2169 main()
Note: See TracBrowser for help on using the repository browser.