source: genesis/tools/gformat.py@ 10696

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

tproxy is no longer used, so get rid of the old config variables.

Fixes: nodefactory#141

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