source: genesis/tools/gformat.py@ 10860

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

Use some basic caching to speedup generation of config files while doing static
runs and/or unit-testing.

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