source: genesis/tools/gformat.py@ 13435

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

All IP ranges of shared network should be combined into a shared block. Using
ifbase will group them properly.

Related-To: beheer#1035

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