source: genesis/tools/gformat.py@ 13326

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

Checking if keys are defined and not if they have content

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