source: genesis/tools/gformat.py@ 10820

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

richard rightfully mentioned that it should not matter which role if is, if the
gateway is set it is set.

fixes nodefactory:ticket:168

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