source: genesis/tools/gformat.py@ 10880

Last change on this file since 10880 was 10880, checked in by rick, 13 years ago

Tot ziens 'desc' veld.

Related-To: beheer:ticket:218

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 49.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# Rick van der Zwet <info@rickvanderzwet.nl>
16#
17
18# Hack to make the script directory is also threated as a module search path.
19import sys
20import os
21import re
22sys.path.append(os.path.dirname(__file__))
23
24import cgi
25import cgitb
26import copy
27import glob
28import socket
29import string
30import subprocess
31import time
32import rdnap
33import math
34import make_network_kml
35from pprint import pprint
36from collections import defaultdict
37try:
38 import yaml
39except ImportError, e:
40 print e
41 print "[ERROR] Please install the python-yaml or devel/py-yaml package"
42 exit(1)
43
44try:
45 from yaml import CLoader as Loader
46 from yaml import CDumper as Dumper
47except ImportError:
48 from yaml import Loader, Dumper
49
50from jinja2 import Environment, Template
51def yesorno(value):
52 return "YES" if bool(value) else "NO"
53env = Environment()
54env.filters['yesorno'] = yesorno
55def render_template(datadump, template):
56 result = env.from_string(template).render(datadump)
57 # Make it look pretty to the naked eye, as jinja templates are not so
58 # friendly when it comes to whitespace formatting
59 ## Remove extra whitespace at end of line lstrip() style.
60 result = re.sub(r'\n[\ ]+','\n', result)
61 ## Include only a single newline between an definition and a comment
62 result = re.sub(r'(["\'])\n+([a-z]|\n#\n)',r'\1\n\2', result)
63 ## Remove extra newlines after single comment
64 result = re.sub(r'(#\n)\n+([a-z])',r'\1\2', result)
65 return result
66
67import logging
68logging.basicConfig(format='# %(levelname)s: %(message)s' )
69logger = logging.getLogger()
70logger.setLevel(logging.DEBUG)
71
72
73if os.environ.has_key('CONFIGROOT'):
74 NODE_DIR = os.environ['CONFIGROOT']
75else:
76 NODE_DIR = os.path.abspath(os.path.dirname(__file__)) + '/../nodes'
77__version__ = '$Id: gformat.py 10880 2012-05-16 18:55:25Z rick $'
78
79
80files = [
81 'authorized_keys',
82 'dnsmasq.conf',
83 'dhcpd.conf',
84 'rc.conf.local',
85 'resolv.conf',
86 'motd',
87 'ntp.conf',
88 'pf.hybrid.conf.local',
89 'wleiden.yaml',
90 ]
91
92# Global variables uses
93OK = 10
94DOWN = 20
95UNKNOWN = 90
96
97datadump_cache = {}
98
99def get_yaml(item):
100 try:
101 """ Get configuration yaml for 'item'"""
102 if datadump_cache.has_key(item):
103 return datadump_cache[item].copy()
104
105 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
106
107 datadump = {}
108 f = open(gfile, 'r')
109 datadump.update(yaml.load(f,Loader=Loader))
110 if datadump['nodetype'] == 'Hybrid':
111 # Some values are defined implicitly
112 if datadump.has_key('rdr_rules') and datadump['rdr_rules'] and not datadump.has_key('service_incoming_rdr'):
113 datadump['service_incoming_rdr'] = True
114 # Use some boring defaults
115 defaults = {
116 'service_proxy_normal' : False,
117 'service_proxy_ileiden' : False,
118 'service_accesspoint' : True,
119 'service_incoming_rdr' : False
120 }
121 for (key,value) in defaults.iteritems():
122 if not datadump.has_key(key):
123 datadump[key] = value
124 f.close()
125
126 # Preformat certain needed variables for formatting and push those into special object
127 datadump['autogen_iface_keys'] = get_interface_keys(datadump)
128
129 wlan_count=0
130 try:
131 for key in datadump['autogen_iface_keys']:
132 if datadump[key]['type'] in ['11a', '11b', '11g', 'wireless']:
133 datadump[key]['autogen_ifname'] = 'wlan%i' % wlan_count
134 wlan_count += 1
135 else:
136 datadump[key]['autogen_ifname'] = datadump[key]['interface'].split(':')[0]
137 except Exception as e:
138 print "# Error while processing interface %s" % key
139 raise
140
141 dhcp_interfaces = [datadump[key]['autogen_ifname'] for key in datadump['autogen_iface_keys'] if (datadump[key].has_key('dhcp') and datadump[key]['dhcp'])]
142 datadump['autogen_dhcp_interfaces'] = dhcp_interfaces
143 datadump['autogen_item'] = item
144
145 datadump['autogen_realname'] = get_realname(datadump)
146 datadump['autogen_domain'] = datadump['domain'] if datadump.has_key('domain') else 'wleiden.net.'
147 datadump['autogen_fqdn'] = datadump['autogen_realname'] + '.' + datadump['autogen_domain']
148 datadump_cache[item] = datadump.copy()
149 except Exception as e:
150 print "# Error while processing %s" % item
151 raise
152 return datadump
153
154
155def store_yaml(datadump, header=False):
156 """ Store configuration yaml for 'item'"""
157 item = datadump['autogen_item']
158 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
159
160 f = open(gfile, 'w')
161 f.write(generate_wleiden_yaml(datadump, header))
162 f.close()
163
164
165def network(ip):
166 addr, mask = ip.split('/')
167 # Not parsing of these folks please
168 addr = parseaddr(addr)
169 mask = int(mask)
170 network = addr & ~((1 << (32 - mask)) - 1)
171 return network
172
173
174
175def make_relations(datadumps=None):
176 """ Process _ALL_ yaml files to get connection relations """
177 errors = []
178 poel = defaultdict(list)
179
180 if not datadumps:
181 for host in get_hostlist():
182 datadumps[host] = get_yaml(host)
183
184 for host, datadump in datadumps.iteritems():
185 try:
186 for iface_key in datadump['autogen_iface_keys']:
187 net_addr = network(datadump[iface_key]['ip'])
188 poel[net_addr] += [(host,datadump[iface_key])]
189 except (KeyError, ValueError), e:
190 errors.append("[FOUT] in '%s' interface '%s' (%s)" % (host,iface_key, e))
191 continue
192 return (poel, errors)
193
194
195
196def valid_addr(addr):
197 """ Show which address is valid in which are not """
198 return str(addr).startswith('172.')
199
200def get_system_list(prefix):
201 return sorted([os.path.basename(os.path.dirname(x)) for x in glob.glob("%s/%s*/wleiden.yaml" % (NODE_DIR, prefix))])
202
203get_hybridlist = lambda: get_system_list("Hybrid")
204get_nodelist = lambda: get_system_list("CNode")
205get_proxylist = lambda: get_system_list("Proxy")
206
207def get_hostlist():
208 """ Combined hosts and proxy list"""
209 return get_nodelist() + get_proxylist() + get_hybridlist()
210
211def angle_between_points(lat1,lat2,long1,long2):
212 """
213 Return Angle in radians between two GPS coordinates
214 See: http://stackoverflow.com/questions/3809179/angle-between-2-gps-coordinates
215 """
216 dy = lat2 - lat1
217 dx = math.cos(lat1)*(long2 - long1)
218 angle = math.atan2(dy,dx)
219 return angle
220
221
222
223def angle_to_cd(angle):
224 """ Return Dutch Cardinal Direction estimation in 'one digit' of radian angle """
225
226 # For easy conversion get positive degree
227 degrees = math.degrees(angle)
228 abs_degrees = 360 + degrees if degrees < 0 else degrees
229
230 # Numbers can be confusing calculate from the 4 main directions
231 p = 22.5
232 if abs_degrees < p:
233 cd = "n"
234 elif abs_degrees < (90 - p):
235 cd = "no"
236 elif abs_degrees < (90 + p):
237 cd = "o"
238 elif abs_degrees < (180 - p):
239 cd = "zo"
240 elif abs_degrees < (180 + p):
241 cd = "z"
242 elif abs_degrees < (270 - p):
243 cd = "zw"
244 elif abs_degrees < (270 + p):
245 cd = "w"
246 elif abs_degrees < (360 - p):
247 cd = "nw"
248 else:
249 cd = "n"
250 return cd
251
252
253
254def cd_between_hosts(hostA, hostB, datadumps):
255 # Using RDNAP coordinates
256 dx = float(int(datadumps[hostA]['rdnap_x']) - int(datadumps[hostB]['rdnap_x'])) * -1
257 dy = float(int(datadumps[hostA]['rdnap_y']) - int(datadumps[hostB]['rdnap_y'])) * -1
258 return angle_to_cd(math.atan2(dx,dy))
259
260 # GPS coordinates seems to fail somehow
261 #latA = float(datadumps[hostA]['latitude'])
262 #latB = float(datadumps[hostB]['latitude'])
263 #lonA = float(datadumps[hostA]['longitude'])
264 #lonB = float(datadumps[hostB]['longitude'])
265 #return angle_to_cd(angle_between_points(latA, latB, lonA, lonB))
266
267
268def generate_title(nodelist):
269 """ Main overview page """
270 items = {'root' : "." }
271 def fl(spaces, line):
272 return (' ' * spaces) + line + '\n'
273
274 output = """
275<html>
276 <head>
277 <title>Wireless leiden Configurator - GFormat</title>
278 <style type="text/css">
279 th {background-color: #999999}
280 tr:nth-child(odd) {background-color: #cccccc}
281 tr:nth-child(even) {background-color: #ffffff}
282 th, td {padding: 0.1em 1em}
283 </style>
284 </head>
285 <body>
286 <center>
287 <form type="GET" action="%(root)s">
288 <input type="hidden" name="action" value="update">
289 <input type="submit" value="Update Configuration Database (SVN)">
290 </form>
291 <table>
292 <caption><h3>Wireless Leiden Configurator</h3></caption>
293 """ % items
294
295 for node in nodelist:
296 items['node'] = node
297 output += fl(5, '<tr>') + fl(7,'<td><a href="%(root)s/%(node)s">%(node)s</a></td>' % items)
298 for config in files:
299 items['config'] = config
300 output += fl(7,'<td><a href="%(root)s/%(node)s/%(config)s">%(config)s</a></td>' % items)
301 output += fl(5, "</tr>")
302 output += """
303 </table>
304 <hr />
305 <em>%s</em>
306 </center>
307 </body>
308</html>
309 """ % __version__
310
311 return output
312
313
314
315def generate_node(node):
316 """ Print overview of all files available for node """
317 return "\n".join(files)
318
319def generate_node_overview(host):
320 """ Print overview of all files available for node """
321 datadump = get_yaml(host)
322 params = { 'host' : host }
323 output = "<em><a href='..'>Back to overview</a></em><hr />"
324 output += "<h2>Available files:</h2><ul>"
325 for cf in files:
326 params['cf'] = cf
327 output += '<li><a href="%(host)s/%(cf)s">%(cf)s</a></li>\n' % params
328 output += "</ul>"
329
330 # Generate and connection listing
331 output += "<h2>Connected To:</h2><ul>"
332 (poel, errors) = make_relations()
333 for network, hosts in poel.iteritems():
334 if host in [x[0] for x in hosts]:
335 if len(hosts) == 1:
336 # Single not connected interface
337 continue
338 for remote,ifacedump in hosts:
339 if remote == host:
340 # This side of the interface
341 continue
342 params = { 'remote': remote, 'remote_ip' : ifacedump['ip'] }
343 output += '<li><a href="%(remote)s">%(remote)s</a> -- %(remote_ip)s</li>\n' % params
344 output += "</ul>"
345 output += "<h2>MOTD details:</h2><pre>" + generate_motd(datadump) + "</pre>"
346
347 output += "<hr /><em><a href='..'>Back to overview</a></em>"
348 return output
349
350
351def generate_header(ctag="#"):
352 return """\
353%(ctag)s
354%(ctag)s DO NOT EDIT - Automatically generated by 'gformat'
355%(ctag)s Generated at %(date)s by %(host)s
356%(ctag)s
357""" % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname() }
358
359
360
361def parseaddr(s):
362 """ Process IPv4 CIDR notation addr to a (binary) number """
363 f = s.split('.')
364 return (long(f[0]) << 24L) + \
365 (long(f[1]) << 16L) + \
366 (long(f[2]) << 8L) + \
367 long(f[3])
368
369
370
371def showaddr(a):
372 """ Display IPv4 addr in (dotted) CIDR notation """
373 return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff)
374
375
376def is_member(ip, mask, canidate):
377 """ Return True if canidate is part of ip/mask block"""
378 ip_addr = parseaddr(ip)
379 ip_canidate = parseaddr(canidate)
380 mask = int(mask)
381 ip_addr = ip_addr & ~((1 << (32 - mask)) - 1)
382 ip_canidate = ip_canidate & ~((1 << (32 - mask)) - 1)
383 return ip_addr == ip_canidate
384
385
386
387def cidr2netmask(netmask):
388 """ Given a 'netmask' return corresponding CIDR """
389 return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask))))
390
391def get_network(addr, mask):
392 return showaddr(parseaddr(addr) & ~((1 << (32 - int(mask))) - 1))
393
394
395def generate_dhcpd_conf(datadump):
396 """ Generate config file '/usr/local/etc/dhcpd.conf """
397 output = generate_header()
398 output += Template("""\
399# option definitions common to all supported networks...
400option domain-name "dhcp.{{ autogen_fqdn }}";
401
402default-lease-time 600;
403max-lease-time 7200;
404
405# Use this to enble / disable dynamic dns updates globally.
406#ddns-update-style none;
407
408# If this DHCP server is the official DHCP server for the local
409# network, the authoritative directive should be uncommented.
410authoritative;
411
412# Use this to send dhcp log messages to a different log file (you also
413# have to hack syslog.conf to complete the redirection).
414log-facility local7;
415
416#
417# Interface definitions
418#
419\n""").render(datadump)
420
421 dhcp_out = defaultdict(list)
422 for iface_key in datadump['autogen_iface_keys']:
423 ifname = datadump[iface_key]['autogen_ifname']
424 if not datadump[iface_key].has_key('comment'):
425 datadump[iface_key]['comment'] = None
426 dhcp_out[ifname].append(" ## %(interface)s - %(comment)s\n" % datadump[iface_key])
427
428 (addr, mask) = datadump[iface_key]['ip'].split('/')
429 datadump[iface_key]['addr'] = addr
430 datadump[iface_key]['netmask'] = cidr2netmask(mask)
431 datadump[iface_key]['subnet'] = get_network(addr, mask)
432 try:
433 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
434 except (AttributeError, ValueError, KeyError):
435 dhcp_out[ifname].append(" subnet %(subnet)s netmask %(netmask)s {\n ### not autoritive\n }\n" % datadump[iface_key])
436 continue
437
438 dhcp_part = ".".join(addr.split('.')[0:3])
439 datadump[iface_key]['dhcp_start'] = dhcp_part + "." + dhcp_start
440 datadump[iface_key]['dhcp_stop'] = dhcp_part + "." + dhcp_stop
441 dhcp_out[ifname].append("""\
442 subnet %(subnet)s netmask %(netmask)s {
443 range %(dhcp_start)s %(dhcp_stop)s;
444 option routers %(addr)s;
445 option domain-name-servers %(addr)s;
446 }
447""" % datadump[iface_key])
448
449 for ifname,value in dhcp_out.iteritems():
450 output += ("shared-network %s {\n" % ifname) + ''.join(value) + '}\n\n'
451 return output
452
453
454
455def generate_dnsmasq_conf(datadump):
456 """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """
457 output = generate_header()
458 output += Template("""\
459# DHCP server options
460dhcp-authoritative
461dhcp-fqdn
462domain=dhcp.{{ autogen_fqdn }}
463domain-needed
464expand-hosts
465log-async=100
466
467# Low memory footprint
468cache-size=10000
469
470\n""").render(datadump)
471
472 for iface_key in datadump['autogen_iface_keys']:
473 if not datadump[iface_key].has_key('comment'):
474 datadump[iface_key]['comment'] = None
475 output += "## %(interface)s - %(comment)s\n" % datadump[iface_key]
476
477 try:
478 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
479 (ip, cidr) = datadump[iface_key]['ip'].split('/')
480 datadump[iface_key]['netmask'] = cidr2netmask(cidr)
481 except (AttributeError, ValueError, KeyError):
482 output += "# not autoritive\n\n"
483 continue
484
485 dhcp_part = ".".join(ip.split('.')[0:3])
486 datadump[iface_key]['dhcp_start'] = dhcp_part + "." + dhcp_start
487 datadump[iface_key]['dhcp_stop'] = dhcp_part + "." + dhcp_stop
488 output += "dhcp-range=%(interface)s,%(dhcp_start)s,%(dhcp_stop)s,%(netmask)s,24h\n\n" % datadump[iface_key]
489
490 return output
491
492
493ileiden_proxies = []
494normal_proxies = []
495rc_conf_local_cache = {}
496
497def generate_rc_conf_local(datadump):
498 """ Generate configuration file '/etc/rc.conf.local' """
499 item = datadump['autogen_item']
500 if rc_conf_local_cache.has_key(item):
501 return rc_conf_local_cache[item]
502
503 if not datadump.has_key('ileiden'):
504 datadump['autogen_ileiden_enable'] = False
505 else:
506 datadump['autogen_ileiden_enable'] = datadump['ileiden']
507
508 datadump['autogen_ileiden_enable'] = switchFormat(datadump['autogen_ileiden_enable'])
509
510 if not ileiden_proxies or not normal_proxies:
511 for proxy in get_proxylist():
512 proxydump = get_yaml(proxy)
513 if proxydump['ileiden']:
514 ileiden_proxies.append(proxydump)
515 else:
516 normal_proxies.append(proxydump)
517 for host in get_hybridlist():
518 hostdump = get_yaml(host)
519 if hostdump['service_proxy_ileiden']:
520 ileiden_proxies.append(hostdump)
521 if hostdump['service_proxy_normal']:
522 normal_proxies.append(hostdump)
523
524 datadump['autogen_ileiden_proxies'] = ileiden_proxies
525 datadump['autogen_normal_proxies'] = normal_proxies
526 datadump['autogen_ileiden_proxies_ips'] = ','.join([x['masterip'] for x in ileiden_proxies])
527 datadump['autogen_ileiden_proxies_names'] = ','.join([x['autogen_item'] for x in ileiden_proxies])
528 datadump['autogen_normal_proxies_ips'] = ','.join([x['masterip'] for x in normal_proxies])
529 datadump['autogen_normal_proxies_names'] = ','.join([x['autogen_item'] for x in normal_proxies])
530
531 output = generate_header("#");
532 output += render_template(datadump, """\
533hostname='{{ autogen_fqdn }}'
534location='{{ location }}'
535nodetype="{{ nodetype }}"
536
537#
538# Configured listings
539#
540captive_portal_whitelist=""
541{% if nodetype == "Proxy" %}
542#
543# Proxy Configuration
544#
545{% if gateway -%}
546defaultrouter="{{ gateway }}"
547{% else -%}
548#defaultrouter="NOTSET"
549{% endif -%}
550internalif="{{ internalif }}"
551ileiden_enable="{{ autogen_ileiden_enable }}"
552gateway_enable="{{ autogen_ileiden_enable }}"
553pf_enable="yes"
554pf_rules="/etc/pf.conf"
555{% if autogen_ileiden_enable -%}
556pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={80,443}"
557lvrouted_enable="{{ autogen_ileiden_enable }}"
558lvrouted_flags="-u -s s00p3rs3kr3t -m 28"
559{% else -%}
560pf_flags="-D ext_if={{ externalif }} -D int_if={{ internalif }} -D publicnat={0}"
561{% endif -%}
562{% if internalroute -%}
563static_routes="wleiden"
564route_wleiden="-net 172.16.0.0/12 {{ internalroute }}"
565{% endif -%}
566
567{% elif nodetype == "Hybrid" %}
568 #
569 # Hybrid Configuration
570 #
571 list_ileiden_proxies="
572 {% for item in autogen_ileiden_proxies -%}
573 {{ "%-16s"|format(item.masterip) }} # {{ item.autogen_realname }}
574 {% endfor -%}
575 "
576 list_normal_proxies="
577 {% for item in autogen_normal_proxies -%}
578 {{ "%-16s"|format(item.masterip) }} # {{ item.autogen_realname }}
579 {% endfor -%}
580 "
581
582 captive_portal_interfaces="{{ autogen_dhcp_interfaces|join(',')|default('none', true) }}"
583 externalif="{{ externalif|default('vr0', true) }}"
584 masterip="{{ masterip }}"
585
586 # Defined services
587 service_proxy_ileiden="{{ service_proxy_ileiden|yesorno }}"
588 service_proxy_normal="{{ service_proxy_normal|yesorno }}"
589 service_accesspoint="{{ service_accesspoint|yesorno }}"
590 service_incoming_rdr="{{ service_incoming_rdr|yesorno }}"
591 #
592
593 {% if service_proxy_ileiden %}
594 pf_rules="/etc/pf.hybrid.conf"
595 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D masterip=$masterip"
596 pf_flags="$pf_flags -D publicnat=80,443"
597 {% elif service_proxy_normal or service_incoming_rdr %}
598 pf_rules="/etc/pf.hybrid.conf"
599 pf_flags="-D ext_if=$externalif -D ext_if_net=$externalif:network -D masterip=$masterip"
600 pf_flags="$pf_flags -D publicnat=0"
601 lvrouted_flags="$lvrouted_flags -z `make_list "$list_ileiden_proxies" ","`"
602 named_setfib="1"
603 tinyproxy_setfib="1"
604 dnsmasq_setfib="1"
605 sshd_setfib="1"
606 {% else %}
607 pf_rules="/etc/pf.node.conf"
608 pf_flags=""
609 lvrouted_flags="$lvrouted_flags -z `make_list "$list_ileiden_proxies" ","`"
610 {% endif %}
611
612 {% if service_proxy_normal %}
613 tinyproxy_enable="yes"
614 {% else %}
615 pen_wrapper_enable="yes"
616 {% endif %}
617
618 {% if service_accesspoint %}
619 pf_flags="$pf_flags -D captive_portal_interfaces=$captive_portal_interfaces"
620 {% endif %}
621
622 {% if board == "ALIX2" %}
623 #
624 # ''Fat'' configuration, board has 256MB RAM
625 #
626 dnsmasq_enable="NO"
627 named_enable="YES"
628 {% if autogen_dhcp_interfaces -%}
629 dhcpd_enable="YES"
630 dhcpd_flags="$dhcpd_flags {{ autogen_dhcp_interfaces|join(' ') }}"
631 {% endif -%}
632 {% endif -%}
633
634 {% if gateway %}
635 defaultrouter="{{ gateway }}"
636 {% endif %}
637{% elif nodetype == "CNode" %}
638#
639# NODE iLeiden Configuration
640#
641
642# iLeiden Proxies {{ autogen_ileiden_proxies_names }}
643list_ileiden_proxies="{{ autogen_ileiden_proxies_ips }}"
644# normal Proxies {{ autogen_normal_proxies_names }}
645list_normal_proxies="{{ autogen_normal_proxies_ips }}"
646
647captive_portal_interfaces="{{ autogen_dhcp_interfaces|join(',') }}"
648
649lvrouted_flags="-u -s s00p3rs3kr3t -m 28 -z $list_ileiden_proxies"
650{% endif %}
651
652#
653# Interface definitions
654#\n
655""")
656
657 # lo0 configuration:
658 # - 172.32.255.1/32 is the proxy.wleiden.net deflector
659 # - masterip is special as it needs to be assigned to at
660 # least one interface, so if not used assign to lo0
661 addrs_list = { 'lo0' : [("127.0.0.1/8", "LocalHost"), ("172.31.255.1/32","Proxy IP")] }
662 iface_map = {'lo0' : 'lo0'}
663 dhclient_if = {'lo0' : False}
664
665 masterip_used = False
666 for iface_key in datadump['autogen_iface_keys']:
667 if datadump[iface_key]['ip'].startswith(datadump['masterip']):
668 masterip_used = True
669 break
670 if not masterip_used:
671 addrs_list['lo0'].append((datadump['masterip'] + "/32", 'Master IP Not used in interface'))
672
673 for iface_key in datadump['autogen_iface_keys']:
674 ifacedump = datadump[iface_key]
675 ifname = ifacedump['autogen_ifname']
676
677 # Flag dhclient is possible
678 dhclient_if[ifname] = ifacedump.has_key('dhcpclient') and ifacedump['dhcpclient']
679
680 # Add interface IP to list
681 item = (ifacedump['ip'], ifacedump['comment'])
682 if addrs_list.has_key(ifname):
683 addrs_list[ifname].append(item)
684 else:
685 addrs_list[ifname] = [item]
686
687 # Alias only needs IP assignment for now, this might change if we
688 # are going to use virtual accesspoints
689 if "alias" in iface_key:
690 continue
691
692 # XXX: Might want to deduct type directly from interface name
693 if ifacedump['type'] in ['11a', '11b', '11g', 'wireless']:
694 # Default to station (client) mode
695 ifacedump['wlanmode'] = "sta"
696 if ifacedump['mode'] in ['master', 'master-wds', 'ap', 'ap-wds']:
697 ifacedump['wlanmode'] = "ap"
698 # Default to 802.11b mode
699 ifacedump['mode'] = '11b'
700 if ifacedump['type'] in ['11a', '11b' '11g']:
701 ifacedump['mode'] = ifacedump['type']
702
703 if not ifacedump.has_key('channel'):
704 if ifacedump['type'] == '11a':
705 ifacedump['channel'] = 36
706 else:
707 ifacedump['channel'] = 1
708
709 # Allow special hacks at the back like wds and stuff
710 if not ifacedump.has_key('extra'):
711 ifacedump['extra'] = 'regdomain ETSI country NL'
712
713 output += "wlans_%(interface)s='%(autogen_ifname)s'\n" % ifacedump
714 output += ("create_args_%(autogen_ifname)s='wlanmode %(wlanmode)s mode " +\
715 "%(mode)s ssid %(ssid)s %(extra)s channel %(channel)s'\n") % ifacedump
716
717 elif ifacedump['type'] in ['ethernet', 'eth']:
718 # No special config needed besides IP
719 pass
720 else:
721 assert False, "Unknown type " + ifacedump['type']
722
723 # Print IP address which needs to be assigned over here
724 output += "\n"
725 for iface,addrs in sorted(addrs_list.iteritems()):
726 for addr, comment in sorted(addrs,key=lambda x: parseaddr(x[0].split('/')[0])):
727 output += "# %s || %s || %s\n" % (iface, addr, comment)
728
729 # Write DHCLIENT entry
730 if dhclient_if[iface]:
731 output += "ifconfig_%s='SYNCDHCP'\n\n" % (iface)
732 else:
733 # Make sure the external address is always first as this is needed in the
734 # firewall setup
735 addrs = sorted(addrs,key=lambda x: x[0].split('.')[0], cmp=lambda x,y: cmp(1 if x == '172' else 0, 1 if y == '172' else 0))
736 output += "ipv4_addrs_%s='%s'\n\n" % (iface, " ".join([x[0] for x in addrs]))
737
738 rc_conf_local_cache[datadump['autogen_item']] = output
739 return output
740
741
742
743
744def get_all_configs():
745 """ Get dict with key 'host' with all configs present """
746 configs = dict()
747 for host in get_hostlist():
748 datadump = get_yaml(host)
749 configs[host] = datadump
750 return configs
751
752
753def get_interface_keys(config):
754 """ Quick hack to get all interface keys, later stage convert this to a iterator """
755 return sorted([elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)])
756
757
758def get_used_ips(configs):
759 """ Return array of all IPs used in config files"""
760 ip_list = []
761 for config in configs:
762 ip_list.append(config['masterip'])
763 for iface_key in get_interface_keys(config):
764 l = config[iface_key]['ip']
765 addr, mask = l.split('/')
766 # Special case do not process
767 if valid_addr(addr):
768 ip_list.append(addr)
769 else:
770 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
771 return sorted(ip_list)
772
773
774
775def generate_resolv_conf(datadump):
776 """ Generate configuration file '/etc/resolv.conf' """
777 # XXX: This should properly going to be an datastructure soon
778 datadump['autogen_header'] = generate_header("#")
779 datadump['autogen_edge_nameservers'] = ''
780 for host in get_proxylist():
781 hostdump = get_yaml(host)
782 datadump['autogen_edge_nameservers'] += "nameserver %(masterip)-15s # %(autogen_realname)s\n" % hostdump
783 for host in get_hybridlist():
784 hostdump = get_yaml(host)
785 if hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']:
786 datadump['autogen_edge_nameservers'] += "nameserver %(masterip)-15s # %(autogen_realname)s\n" % hostdump
787
788 return Template("""\
789{{ autogen_header }}
790search wleiden.net
791
792# Try local (cache) first
793nameserver 127.0.0.1
794
795{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
796nameserver 8.8.8.8 # Google Public NameServer
797nameserver 8.8.4.4 # Google Public NameServer
798{% else -%}
799# START DYNAMIC LIST - updated by /tools/nameserver-shuffle
800{{ autogen_edge_nameservers }}
801{% endif -%}
802""").render(datadump)
803
804
805
806def generate_ntp_conf(datadump):
807 """ Generate configuration file '/etc/ntp.conf' """
808 # XXX: This should properly going to be an datastructure soon
809
810 datadump['autogen_header'] = generate_header("#")
811 datadump['autogen_ntp_servers'] = ''
812 for host in get_proxylist():
813 hostdump = get_yaml(host)
814 datadump['autogen_ntp_servers'] += "server %(masterip)-15s iburst maxpoll 9 # %(autogen_realname)s\n" % hostdump
815 for host in get_hybridlist():
816 hostdump = get_yaml(host)
817 if hostdump['service_proxy_ileiden'] or hostdump['service_proxy_normal']:
818 datadump['autogen_ntp_servers'] += "server %(masterip)-15s iburst maxpoll 9 # %(autogen_realname)s\n" % hostdump
819
820 return Template("""\
821{{ autogen_header }}
822
823{% if service_proxy_normal or service_proxy_ileiden or nodetype == 'Proxy' -%}
824# Machine hooked to internet.
825server 0.nl.pool.ntp.org iburst maxpoll 9
826server 1.nl.pool.ntp.org iburst maxpoll 9
827server 2.nl.pool.ntp.org iburst maxpoll 9
828server 3.nl.pool.ntp.org iburst maxpoll 9
829{% else -%}
830# Local Wireless Leiden NTP Servers.
831server 0.pool.ntp.wleiden.net iburst maxpoll 9
832server 1.pool.ntp.wleiden.net iburst maxpoll 9
833server 2.pool.ntp.wleiden.net iburst maxpoll 9
834server 3.pool.ntp.wleiden.net iburst maxpoll 9
835
836# All the configured NTP servers
837{{ autogen_ntp_servers }}
838{% endif %}
839
840# If a server loses sync with all upstream servers, NTP clients
841# no longer follow that server. The local clock can be configured
842# to provide a time source when this happens, but it should usually
843# be configured on just one server on a network. For more details see
844# http://support.ntp.org/bin/view/Support/UndisciplinedLocalClock
845# The use of Orphan Mode may be preferable.
846#
847server 127.127.1.0
848fudge 127.127.1.0 stratum 10
849""").render(datadump)
850
851
852def generate_pf_hybrid_conf_local(datadump):
853 """ Generate configuration file '/etc/pf.hybrid.conf.local' """
854 datadump['autogen_header'] = generate_header("#")
855 return Template("""\
856{{ autogen_header }}
857
858# Redirect some internal facing services outside (7)
859# INFO: {{ rdr_rules|count }} rdr_rules (outside to internal redirect rules) defined.
860{% for protocol, src_port,dest_ip,dest_port in rdr_rules -%}
861rdr on $ext_if inet proto {{ protocol }} from any to $ext_if port {{ src_port }} tag SRV -> {{ dest_ip }} port {{ dest_port }}
862{% endfor -%}
863""").render(datadump)
864
865def generate_motd(datadump):
866 """ Generate configuration file '/etc/motd' """
867 output = Template("""\
868FreeBSD run ``service motd onestart'' to make me look normal
869
870 WWW: {{ autogen_fqdn }} - http://www.wirelessleiden.nl
871 Loc: {{ location }}
872
873Services:
874{% if board == "ALIX2" -%}
875 - Core Node ({{ board }})
876{% else -%}
877 - Hulp Node ({{ board }})
878{% endif -%}
879{% if service_proxy_normal -%}
880 - Normal Proxy
881{% endif -%}
882{% if service_proxy_ileiden -%}
883 - iLeiden Proxy
884{% endif -%}
885{% if service_incoming_rdr -%}
886 - Incoming port redirects
887{% endif %}
888Interlinks:\n
889""").render(datadump)
890
891 # XXX: This is a hacky way to get the required data
892 for line in generate_rc_conf_local(datadump).split('\n'):
893 if '||' in line and not line[1:].split()[0] in ['lo0', 'ath0'] :
894 output += " - %s \n" % line[1:]
895 output += """\
896Attached bridges:
897"""
898 for iface_key in datadump['autogen_iface_keys']:
899 ifacedump = datadump[iface_key]
900 if ifacedump.has_key('ns_ip'):
901 output += " - %(interface)s || %(mode)s || %(ns_ip)s\n" % ifacedump
902
903 return output
904
905
906def format_yaml_value(value):
907 """ Get yaml value in right syntax for outputting """
908 if isinstance(value,str):
909 output = '"%s"' % value
910 else:
911 output = value
912 return output
913
914
915
916def format_wleiden_yaml(datadump):
917 """ Special formatting to ensure it is editable"""
918 output = "# Genesis config yaml style\n"
919 output += "# vim:ts=2:et:sw=2:ai\n"
920 output += "#\n"
921 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
922 for key in sorted(set(datadump.keys()) - set(iface_keys)):
923 if key == 'rdr_rules':
924 output += '%-10s:\n' % 'rdr_rules'
925 for rdr_rule in datadump[key]:
926 output += '- %s\n' % rdr_rule
927 else:
928 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
929
930 output += "\n\n"
931
932 key_order = [ 'comment', 'interface', 'ip', 'sdesc', 'mode', 'type',
933 'extra_type', 'channel', 'ssid', 'dhcp' ]
934
935 for iface_key in sorted(iface_keys):
936 output += "%s:\n" % iface_key
937 for key in key_order + list(sorted(set(datadump[iface_key].keys()) - set(key_order))):
938 if datadump[iface_key].has_key(key):
939 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
940 output += "\n\n"
941
942 return output
943
944
945
946def generate_wleiden_yaml(datadump, header=True):
947 """ Generate (petty) version of wleiden.yaml"""
948 for key in datadump.keys():
949 if key.startswith('autogen_'):
950 del datadump[key]
951 # Interface autogen cleanups
952 elif type(datadump[key]) == dict:
953 for key2 in datadump[key].keys():
954 if key2.startswith('autogen_'):
955 del datadump[key][key2]
956
957 output = generate_header("#") if header else ''
958 output += format_wleiden_yaml(datadump)
959 return output
960
961
962def generate_yaml(datadump):
963 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
964
965
966
967def generate_config(node, config, datadump=None):
968 """ Print configuration file 'config' of 'node' """
969 output = ""
970 try:
971 # Load config file
972 if datadump == None:
973 datadump = get_yaml(node)
974
975 if config == 'wleiden.yaml':
976 output += generate_wleiden_yaml(datadump)
977 elif config == 'authorized_keys':
978 f = open(os.path.join(NODE_DIR,"global_keys"), 'r')
979 output += f.read()
980 f.close()
981 elif config == 'dnsmasq.conf':
982 output += generate_dnsmasq_conf(datadump)
983 elif config == 'dhcpd.conf':
984 output += generate_dhcpd_conf(datadump)
985 elif config == 'rc.conf.local':
986 output += generate_rc_conf_local(datadump)
987 elif config == 'resolv.conf':
988 output += generate_resolv_conf(datadump)
989 elif config == 'ntp.conf':
990 output += generate_ntp_conf(datadump)
991 elif config == 'motd':
992 output += generate_motd(datadump)
993 elif config == 'pf.hybrid.conf.local':
994 output += generate_pf_hybrid_conf_local(datadump)
995 else:
996 assert False, "Config not found!"
997 except IOError, e:
998 output += "[ERROR] Config file not found"
999 return output
1000
1001
1002
1003def process_cgi_request():
1004 """ When calling from CGI """
1005 # Update repository if requested
1006 form = cgi.FieldStorage()
1007 if form.getvalue("action") == "update":
1008 print "Refresh: 5; url=."
1009 print "Content-type:text/plain\r\n\r\n",
1010 print "[INFO] Updating subverion, please wait..."
1011 print subprocess.Popen(['svn', 'cleanup', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
1012 print subprocess.Popen(['svn', 'up', "%s/.." % NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
1013 print "[INFO] All done, redirecting in 5 seconds"
1014 sys.exit(0)
1015
1016
1017 base_uri = os.environ['PATH_INFO']
1018 uri = base_uri.strip('/').split('/')
1019
1020 output = "Template Holder"
1021 content_type='text/plain'
1022 if base_uri.endswith('/create/network.kml'):
1023 content_type='application/vnd.google-earth.kml+xml'
1024 output = make_network_kml.make_graph()
1025 elif not uri[0]:
1026 if is_text_request():
1027 content_type = 'text/plain'
1028 output = '\n'.join(get_hostlist())
1029 else:
1030 content_type = 'text/html'
1031 output = generate_title(get_hostlist())
1032 elif len(uri) == 1:
1033 if is_text_request():
1034 content_type = 'text/plain'
1035 output = generate_node(uri[0])
1036 else:
1037 content_type = 'text/html'
1038 output = generate_node_overview(uri[0])
1039 elif len(uri) == 2:
1040 content_type = 'text/plain'
1041 output = generate_config(uri[0], uri[1])
1042 else:
1043 assert False, "Invalid option"
1044
1045 print "Content-Type: %s" % content_type
1046 print "Content-Length: %s" % len(output)
1047 print ""
1048 print output
1049
1050def get_realname(datadump):
1051 # Proxy naming convention is special, as the proxy name is also included in
1052 # the nodename, when it comes to the numbered proxies.
1053 if datadump['nodetype'] == 'Proxy':
1054 realname = datadump['nodetype'] + datadump['nodename'].replace('proxy','')
1055 else:
1056 # By default the full name is listed and also a shortname CNAME for easy use.
1057 realname = datadump['nodetype'] + datadump['nodename']
1058 return(realname)
1059
1060
1061
1062def make_dns(output_dir = 'dns', external = False):
1063 items = dict()
1064
1065 # hostname is key, IP is value
1066 wleiden_zone = defaultdict(list)
1067 wleiden_cname = dict()
1068
1069 pool = dict()
1070 for node in get_hostlist():
1071 datadump = get_yaml(node)
1072
1073 # Proxy naming convention is special
1074 fqdn = datadump['autogen_realname']
1075 if datadump['nodetype'] in ['CNode', 'Hybrid']:
1076 wleiden_cname[datadump['nodename']] = fqdn
1077
1078 if datadump.has_key('rdr_host'):
1079 remote_target = datadump['rdr_host']
1080 elif datadump.has_key('remote_access') and datadump['remote_access']:
1081 remote_target = datadump['remote_access'].split(':')[0]
1082 else:
1083 remote_target = None
1084
1085 if remote_target:
1086 try:
1087 parseaddr(remote_target)
1088 wleiden_zone[datadump['nodename'] + '.gw'].append((remote_target, False))
1089 except (IndexError, ValueError):
1090 wleiden_cname[datadump['nodename'] + '.gw'] = remote_target + '.'
1091
1092
1093 wleiden_zone[fqdn].append((datadump['masterip'], True))
1094
1095 # Hacking to get proper DHCP IPs and hostnames
1096 for iface_key in get_interface_keys(datadump):
1097 iface_name = datadump[iface_key]['interface'].replace(':',"-alias-")
1098 (ip, cidr) = datadump[iface_key]['ip'].split('/')
1099 try:
1100 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
1101 datadump[iface_key]['netmask'] = cidr2netmask(cidr)
1102 dhcp_part = ".".join(ip.split('.')[0:3])
1103 if ip != datadump['masterip']:
1104 wleiden_zone["dhcp-gateway-%s.%s" % (iface_name, fqdn)].append((ip, False))
1105 for i in range(int(dhcp_start), int(dhcp_stop) + 1):
1106 wleiden_zone["dhcp-%s-%s.%s" % (i, iface_name, fqdn)].append(("%s.%s" % (dhcp_part, i), True))
1107 except (AttributeError, ValueError, KeyError):
1108 # First push it into a pool, to indentify the counter-part later on
1109 addr = parseaddr(ip)
1110 cidr = int(cidr)
1111 addr = addr & ~((1 << (32 - cidr)) - 1)
1112 if pool.has_key(addr):
1113 pool[addr] += [(iface_name, fqdn, ip)]
1114 else:
1115 pool[addr] = [(iface_name, fqdn, ip)]
1116 continue
1117
1118
1119 def pool_to_name(fqdn, pool_members):
1120 """Convert the joined name to a usable pool name"""
1121
1122 def isplit(item):
1123 (prefix, name, number) = re.match('^(cnode|hybrid|proxy)([a-z]+)([0-9]*)$',item.lower()).group(1,2,3)
1124 return (prefix, name, number)
1125
1126 my_name = isplit(fqdn.split('.')[0])[1]
1127
1128 short_names = defaultdict(list)
1129 for node in sorted(pool_members):
1130 (prefix, name, number) = isplit(node)
1131 short_names[name].append((prefix,number))
1132
1133 return '-'.join(sorted(short_names.keys()))
1134
1135
1136 # WL uses an /29 to configure an interface. IP's are ordered like this:
1137 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
1138
1139 sn = lambda x: re.sub(r'(?i)^cnode','',x)
1140
1141 # Automatic naming convention of interlinks namely 2 + remote.lower()
1142 for (key,value) in pool.iteritems():
1143 # Make sure they are sorted from low-ip to high-ip
1144 value = sorted(value, key=lambda x: parseaddr(x[2]))
1145
1146 if len(value) == 1:
1147 (iface_name, fqdn, ip) = value[0]
1148 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)].append((ip, True))
1149
1150 # Device DNS names
1151 if 'cnode' in fqdn.lower():
1152 wleiden_zone["d-at-%s.%s" % (iface_name, fqdn)].append((showaddr(parseaddr(ip) + 1), False))
1153 wleiden_cname["d-at-%s.%s" % (iface_name,sn(fqdn))] = "d-at-%s.%s" % ((iface_name, fqdn))
1154
1155 elif len(value) == 2:
1156 (a_iface_name, a_fqdn, a_ip) = value[0]
1157 (b_iface_name, b_fqdn, b_ip) = value[1]
1158 wleiden_zone["2%s.%s" % (b_fqdn,a_fqdn)].append((a_ip, True))
1159 wleiden_zone["2%s.%s" % (a_fqdn,b_fqdn)].append((b_ip, True))
1160
1161 # Device DNS names
1162 if 'cnode' in a_fqdn.lower() and 'cnode' in b_fqdn.lower():
1163 wleiden_zone["d-at-%s.%s" % (a_iface_name, a_fqdn)].append((showaddr(parseaddr(a_ip) + 1), False))
1164 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)].append((showaddr(parseaddr(b_ip) - 1), False))
1165 wleiden_cname["d-at-%s.%s" % (a_iface_name,sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1166 wleiden_cname["d-at-%s.%s" % (b_iface_name,sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1167 wleiden_cname["d2%s.%s" % (sn(b_fqdn),sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
1168 wleiden_cname["d2%s.%s" % (sn(a_fqdn),sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
1169
1170 else:
1171 pool_members = [k[1] for k in value]
1172 for item in value:
1173 (iface_name, fqdn, ip) = item
1174 pool_name = "2pool-" + pool_to_name(fqdn,pool_members)
1175 wleiden_zone["%s.%s" % (pool_name, fqdn)].append((ip, True))
1176
1177 # Include static DNS entries
1178 # XXX: Should they override the autogenerated results?
1179 # XXX: Convert input to yaml more useable.
1180 # Format:
1181 ##; this is a comment
1182 ## roomburgh=CNodeRoomburgh1
1183 ## apkerk1.CNodeVosko=172.17.176.8 ;this as well
1184 dns_list = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
1185
1186 # Hack to allow special entries, for development
1187 wleiden_raw = {}
1188
1189 for line in dns_list:
1190 reverse = False
1191 k, items = line.items()[0]
1192 if type(items) == dict:
1193 if items.has_key('reverse'):
1194 reverse = items['reverse']
1195 items = items['a']
1196 else:
1197 items = items['cname']
1198 items = [items] if type(items) != list else items
1199 for item in items:
1200 if item.startswith('IN '):
1201 wleiden_raw[k] = item
1202 elif valid_addr(item):
1203 wleiden_zone[k].append((item, reverse))
1204 else:
1205 wleiden_cname[k] = item
1206
1207 details = dict()
1208 # 24 updates a day allowed
1209 details['serial'] = time.strftime('%Y%m%d%H')
1210
1211 if external:
1212 dns_masters = ['siteview.wirelessleiden.nl', 'ns1.vanderzwet.net']
1213 else:
1214 dns_masters = ['sunny.wleiden.net']
1215
1216 details['master'] = dns_masters[0]
1217 details['ns_servers'] = '\n'.join(['\tNS\t%s.' % x for x in dns_masters])
1218
1219 dns_header = '''
1220$TTL 3h
1221%(zone)s. SOA %(master)s. beheer.lijst.wirelessleiden.nl. ( %(serial)s 1d 12h 1w 60s )
1222 ; Serial, Refresh, Retry, Expire, Neg. cache TTL
1223
1224%(ns_servers)s
1225 \n'''
1226
1227
1228 if not os.path.isdir(output_dir):
1229 os.makedirs(output_dir)
1230 details['zone'] = 'wleiden.net'
1231 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
1232 f.write(dns_header % details)
1233
1234 for host,items in wleiden_zone.iteritems():
1235 for ip,reverse in items:
1236 if ip not in ['0.0.0.0']:
1237 f.write("%s.wleiden.net. IN A %s \n" % (host.lower(), ip))
1238 for source,dest in wleiden_cname.iteritems():
1239 dest = dest if dest.endswith('.') else dest + ".wleiden.net."
1240 f.write("%s.wleiden.net. IN CNAME %s\n" % (source.lower(), dest.lower()))
1241 for source, dest in wleiden_raw.iteritems():
1242 f.write("%s.wleiden.net. %s\n" % (source, dest))
1243 f.close()
1244
1245 # Create whole bunch of specific sub arpa zones. To keep it compliant
1246 for s in range(16,32):
1247 details['zone'] = '%i.172.in-addr.arpa' % s
1248 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
1249 f.write(dns_header % details)
1250
1251 #XXX: Not effient, fix to proper data structure and do checks at other
1252 # stages
1253 for host,items in wleiden_zone.iteritems():
1254 for ip,reverse in items:
1255 if not reverse:
1256 continue
1257 if valid_addr(ip):
1258 if valid_addr(ip):
1259 if int(ip.split('.')[1]) == s:
1260 rev_ip = '.'.join(reversed(ip.split('.')))
1261 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
1262 f.close()
1263
1264
1265def usage():
1266 print """Usage: %(prog)s <argument>
1267Argument:
1268\tstandalone [port] = Run configurator webserver [8000]
1269\tdns [outputdir] = Generate BIND compliant zone files in dns [./dns]
1270\tfull-export = Generate yaml export script for heatmap.
1271\tstatic [outputdir] = Generate all config files and store on disk
1272\t with format ./<outputdir>/%%NODE%%/%%FILE%% [./static]
1273\ttest <node> [<file>] = Receive output for certain node [all files].
1274\ttest-cgi <node> <file> = Receive output of CGI script [all files].
1275\tlist <status> <items> = List systems which have certain status
1276
1277Arguments:
1278\t<node> = NodeName (example: HybridRick)
1279\t<file> = %(files)s
1280\t<status> = all|up|down|planned
1281\t<items> = systems|nodes|proxies
1282
1283NOTE FOR DEVELOPERS; you can test your changes like this:
1284 BEFORE any changes in this code:
1285 $ ./gformat.py static /tmp/pre
1286 AFTER the changes:
1287 $ ./gformat.py static /tmp/post
1288 VIEW differences and VERIFY all are OK:
1289 $ diff -urI 'Generated' -r /tmp/pre /tmp/post
1290""" % { 'prog' : sys.argv[0], 'files' : '|'.join(files) }
1291 exit(0)
1292
1293
1294def is_text_request():
1295 """ Find out whether we are calling from the CLI or any text based CLI utility """
1296 try:
1297 return os.environ['HTTP_USER_AGENT'].split()[0] in ['curl', 'fetch', 'wget']
1298 except KeyError:
1299 return True
1300
1301def switchFormat(setting):
1302 if setting:
1303 return "YES"
1304 else:
1305 return "NO"
1306
1307def main():
1308 """Hard working sub"""
1309 # Allow easy hacking using the CLI
1310 if not os.environ.has_key('PATH_INFO'):
1311 if len(sys.argv) < 2:
1312 usage()
1313
1314 if sys.argv[1] == "standalone":
1315 import SocketServer
1316 import CGIHTTPServer
1317 # Hop to the right working directory.
1318 os.chdir(os.path.dirname(__file__))
1319 try:
1320 PORT = int(sys.argv[2])
1321 except (IndexError,ValueError):
1322 PORT = 8000
1323
1324 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
1325 """ Serve this CGI from the root of the webserver """
1326 def is_cgi(self):
1327 if "favicon" in self.path:
1328 return False
1329
1330 self.cgi_info = (os.path.basename(__file__), self.path)
1331 self.path = ''
1332 return True
1333 handler = MyCGIHTTPRequestHandler
1334 SocketServer.TCPServer.allow_reuse_address = True
1335 httpd = SocketServer.TCPServer(("", PORT), handler)
1336 httpd.server_name = 'localhost'
1337 httpd.server_port = PORT
1338
1339 logger.info("serving at port %s", PORT)
1340 try:
1341 httpd.serve_forever()
1342 except KeyboardInterrupt:
1343 httpd.shutdown()
1344 logger.info("All done goodbye")
1345 elif sys.argv[1] == "test":
1346 # Basic argument validation
1347 try:
1348 node = sys.argv[2]
1349 datadump = get_yaml(node)
1350 except IndexError:
1351 print "Invalid argument"
1352 exit(1)
1353 except IOError as e:
1354 print e
1355 exit(1)
1356
1357
1358 # Get files to generate
1359 gen_files = sys.argv[3:] if len(sys.argv) > 3 else files
1360
1361 # Actual config generation
1362 for config in gen_files:
1363 logger.info("## Generating %s %s", node, config)
1364 print generate_config(node, config, datadump)
1365 elif sys.argv[1] == "test-cgi":
1366 os.environ['PATH_INFO'] = "/".join(sys.argv[2:])
1367 os.environ['SCRIPT_NAME'] = __file__
1368 process_cgi_request()
1369 elif sys.argv[1] == "static":
1370 items = dict()
1371 items['output_dir'] = sys.argv[2] if len(sys.argv) > 2 else "./static"
1372 for node in get_hostlist():
1373 items['node'] = node
1374 items['wdir'] = "%(output_dir)s/%(node)s" % items
1375 if not os.path.isdir(items['wdir']):
1376 os.makedirs(items['wdir'])
1377 datadump = get_yaml(node)
1378 for config in files:
1379 items['config'] = config
1380 logger.info("## Generating %(node)s %(config)s" % items)
1381 f = open("%(wdir)s/%(config)s" % items, "w")
1382 f.write(generate_config(node, config, datadump))
1383 f.close()
1384 elif sys.argv[1] == "wind-export":
1385 items = dict()
1386 for node in get_hostlist():
1387 datadump = get_yaml(node)
1388 sql = """INSERT IGNORE INTO nodes (name, name_ns, longitude, latitude)
1389 VALUES ('%(nodename)s', '%(nodename)s', %(latitude)s, %(longitude)s);""" % datadump;
1390 sql = """INSERT IGNORE INTO users_nodes (user_id, node_id, owner)
1391 VALUES (
1392 (SELECT id FROM users WHERE username = 'rvdzwet'),
1393 (SELECT id FROM nodes WHERE name = '%(nodename)s'),
1394 'Y');""" % datadump
1395 #for config in files:
1396 # items['config'] = config
1397 # print "## Generating %(node)s %(config)s" % items
1398 # f = open("%(wdir)s/%(config)s" % items, "w")
1399 # f.write(generate_config(node, config, datadump))
1400 # f.close()
1401 for node in get_hostlist():
1402 datadump = get_yaml(node)
1403 for iface_key in sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]):
1404 ifacedump = datadump[iface_key]
1405 if ifacedump.has_key('mode') and ifacedump['mode'] == 'ap-wds':
1406 ifacedump['nodename'] = datadump['nodename']
1407 if not ifacedump.has_key('channel') or not ifacedump['channel']:
1408 ifacedump['channel'] = 0
1409 sql = """INSERT INTO links (node_id, type, ssid, protocol, channel, status)
1410 VALUES ((SELECT id FROM nodes WHERE name = '%(nodename)s'), 'ap',
1411 '%(ssid)s', 'IEEE 802.11b', %(channel)s, 'active');""" % ifacedump
1412 elif sys.argv[1] == "full-export":
1413 hosts = {}
1414 for node in get_hostlist():
1415 datadump = get_yaml(node)
1416 hosts[datadump['nodename']] = datadump
1417 print yaml.dump(hosts)
1418
1419 elif sys.argv[1] == "dns":
1420 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns', 'external' in sys.argv)
1421 elif sys.argv[1] == "cleanup":
1422 # First generate all datadumps
1423 datadumps = dict()
1424 ssid_to_node = dict()
1425 for host in get_hostlist():
1426 logger.info("# Processing: %s", host)
1427 # Set some boring default values
1428 datadump = { 'board' : 'UNKNOWN' }
1429 datadump.update(get_yaml(host))
1430 datadumps[datadump['autogen_realname']] = datadump
1431
1432 (poel, errors) = make_relations(datadumps)
1433 print "\n".join(["# WARNING: %s" % x for x in errors])
1434
1435 for host,datadump in datadumps.iteritems():
1436 # Convert all yes and no to boolean values
1437 def fix_boolean(dump):
1438 for key in dump.keys():
1439 if type(dump[key]) == dict:
1440 dump[key] = fix_boolean(dump[key])
1441 elif str(dump[key]).lower() in ["yes", "true"]:
1442 dump[key] = True
1443 elif str(dump[key]).lower() in ["no", "false"]:
1444 # Compass richting no (Noord Oost) is valid input
1445 if key != "compass": dump[key] = False
1446 return dump
1447 datadump = fix_boolean(datadump)
1448
1449 if datadump['rdnap_x'] and datadump['rdnap_y']:
1450 datadump['latitude'], datadump['longitude'] = rdnap.rd2etrs(datadump['rdnap_x'], datadump['rdnap_y'])
1451 elif datadump['latitude'] and datadump['longitude']:
1452 datadump['rdnap_x'], datadump['rdnap_y'] = rdnap.etrs2rd(datadump['latitude'], datadump['longitude'])
1453
1454 if datadump['nodename'].startswith('Proxy'):
1455 datadump['nodename'] = datadump['nodename'].lower()
1456
1457 for iface_key in datadump['autogen_iface_keys']:
1458 # All our normal wireless cards are normal APs now
1459 if datadump[iface_key]['type'] in ['11a', '11b', '11g', 'wireless']:
1460 datadump[iface_key]['mode'] = 'ap'
1461 # Wireless Leiden SSID have an consistent lowercase/uppercase
1462 if datadump[iface_key].has_key('ssid'):
1463 ssid = datadump[iface_key]['ssid']
1464 prefix = 'ap-WirelessLeiden-'
1465 if ssid.lower().startswith(prefix.lower()):
1466 datadump[iface_key]['ssid'] = prefix + ssid[len(prefix)].upper() + ssid[len(prefix) + 1:]
1467 if datadump[iface_key].has_key('ns_ip') and not datadump[iface_key].has_key('mode'):
1468 datadump[iface_key]['mode'] = 'autogen-FIXME'
1469 if not datadump[iface_key].has_key('comment'):
1470 datadump[iface_key]['comment'] = 'autogen-FIXME'
1471 # Set the compass value based on the angle between the poels
1472 if datadump[iface_key].has_key('ns_ip'):
1473 my_pool = poel[network(datadump[iface_key]['ip'])]
1474 remote_hosts = list(set([x[0] for x in my_pool]) - set([host]))
1475 if remote_hosts:
1476 compass_target = remote_hosts[0]
1477 datadump[iface_key]['compass'] = cd_between_hosts(host, compass_target, datadumps)
1478
1479 store_yaml(datadump)
1480 elif sys.argv[1] == "list":
1481 use_fqdn = False
1482 if len(sys.argv) < 4 or not sys.argv[2] in ["up", "down", "planned", "all"]:
1483 usage()
1484 if sys.argv[3] == "nodes":
1485 systems = get_nodelist()
1486 elif sys.argv[3] == "proxies":
1487 systems = get_proxylist()
1488 elif sys.argv[3] == "systems":
1489 systems = get_hostlist()
1490 else:
1491 usage()
1492 if len(sys.argv) > 4:
1493 if sys.argv[4] == "fqdn":
1494 use_fqdn = True
1495 else:
1496 usage()
1497
1498 for system in systems:
1499 datadump = get_yaml(system)
1500
1501 output = datadump['autogen_fqdn'] if use_fqdn else system
1502 if sys.argv[2] == "all":
1503 print output
1504 elif datadump['status'] == sys.argv[2]:
1505 print output
1506 elif sys.argv[1] == "create":
1507 if sys.argv[2] == "network.kml":
1508 print make_network_kml.make_graph()
1509 else:
1510 usage()
1511 usage()
1512 else:
1513 # Do not enable debugging for config requests as it highly clutters the output
1514 if not is_text_request():
1515 cgitb.enable()
1516 process_cgi_request()
1517
1518
1519if __name__ == "__main__":
1520 main()
Note: See TracBrowser for help on using the repository browser.