Changeset 10419 in hybrid for branches/releng-9.0/nanobsd
- Timestamp:
- Apr 10, 2012, 7:35:39 PM (13 years ago)
- Location:
- branches/releng-9.0/nanobsd/files
- Files:
-
- 1 added
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/releng-9.0/nanobsd/files/etc/crontab
r10417 r10419 4 4 # 5 5 SHELL=/bin/sh 6 PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin 6 PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin 7 7 HOME=/var/log 8 8 # … … 33 33 # Nagios checks 34 34 */15 * * * * root /usr/local/sbin/check-inet-alive 35 # 36 */5 * * * * root /usr/local/www/wlportal/index.cgi cleanup 37 -
branches/releng-9.0/nanobsd/files/etc/rc.conf
r10418 r10419 1 1 # No kernel dumps as we don't have a place to store them 2 2 dumpdev="NO" 3 # We are an router/gateway (wireless to be precise) 3 4 # We are an router/gateway (wireless to be precise) running the lvrouted 5 # routing daemon. 4 6 gateway_enable="YES" 7 lvrouted_enable="YES" 8 lvrouted_flags="-u -s s00p3rs3kr3t -m 28" 5 9 6 10 # NTP server needs working config with WL network or internet on boot … … 30 34 31 35 # HTTP(S) proxy server 32 tinyproxy_enable=" YES"36 tinyproxy_enable="NO" 33 37 34 38 # Make sure generated ssh keys are saved … … 54 58 pf_enable="YES" 55 59 pf_rules="/etc/pf.open.conf" 60 pf_flags='' 61 # Used with /etc/pf.proxy.conf 62 # pf_flags="-D ext_if=vr0 -D int_if=vr1 -D publicnat={80,443}" 63 # Used with /etc/pf.node.conf 64 # pf_flags="-D captive_portal_interfaces=wlan0,wlan1" -
branches/releng-9.0/nanobsd/files/usr/local/www/wlportal/index.cgi
r10201 r10419 8 8 # = Usage = 9 9 # This is a wrapper script which does very basic HTML parsing and altering of 10 # ipfwtables rules to build a basic Captive Portal, with basic sanity10 # pfctl tables rules to build a basic Captive Portal, with basic sanity 11 11 # checking. The ACL is IP based (this is a poor mans solution, layer2 12 12 # ACL would be much better), so don't take security very seriously. 13 13 # 14 14 # To get traffic by default to the portal iI requires a few special rules in 15 # ipfw to work properly (ajust IP details if needed):16 # - Rule 10010-10099 needs to be free.17 # - add 10100 fwd 172.20.145.1,8081 tcp from any to not 172.16.0.0/12 dst-port 80 in via wlan015 # pfctl to work properly: 16 # no rdr on { $captive_ifs } proto tcp from <wlportal> to !$wl_net port 80 17 # rdr on { $captive_ifs } proto tcp from $wl_net to !$wl_net port 80 -> 127.0.0.1 port 8082 18 18 # 19 19 # Enties older than 5 minutes not being used will be removed if the (hidden) … … 26 26 # get added during boot and will never disappear. 27 27 # 28 # The program has uses a file based persistent cache to save authenticated 29 # ACLs, this will NOT get synced after a reboot. 30 # 31 # State : ALPHA 28 # State : v0.6.0 32 29 # Version : $Id$ 33 30 # Author : Rick van der Zwet <info@rickvanderzwet.nl> 34 31 # Licence : BSDLike http://wirelessleiden.nl/LICENSE 35 32 36 import logging37 33 import os 38 import pickle39 import re40 34 import signal 41 35 import subprocess … … 47 41 48 42 # XXX: Make me dynamic for example put me in the conf file 49 c onf= {43 cfg = { 50 44 'autologin' : False, 51 45 'cmd_arp' : '/usr/sbin/arp', 52 ' cmd_fw' : '/sbin/ipfw',46 'pfctl' : '/sbin/pfctl', 53 47 'portal_sponsor': 'Sponsor van Stichting Wireless Leiden', 54 48 'portal_url' : 'http://www.wirelessleiden.nl', … … 58 52 'tmpl_login' : '/usr/local/etc/wlportal/login.tmpl', 59 53 'whitelist' : [], 54 'config_file' : '/usr/local/etc/wlportal/config.yaml', 55 'expire_time' : None, 56 'accessdb' : '/var/db/clients', 60 57 } 61 58 62 63 logging.basicConfig(stream=open('/var/log/wlportal.log','a'),level=logging.DEBUG)64 59 65 60 # No failback if config does not exist, to really make sure the user knows if 66 61 # the config file failed to parse properly or is non-existing 67 # XXX: 5xx error code perhaps? 68 try: 69 conf.update(yaml.load(open('/usr/local/etc/wlportal/config.yaml'))) 70 except Exception,e: 71 logging.error(traceback.format_exc()) 72 73 74 class ItemCache: 75 """ 76 Very basic ItemCache used for caching registered entries and other foo, no 77 way recurrent, so use with care! 62 if os.path.isfile(cfg['config_file']): 63 cfg.update(yaml.load(open(cfg['config_file']))) 64 65 def log_registered_host(remote_mac, remote_host): 78 66 """ 79 80 def __init__(self, authentication_timeout=60): 81 self.cachefile='/tmp/portal.cache' 82 # cache[mac_address] = (ipaddr, registered_at, last_seen) 83 self.cache = None 84 self.arp_cache = None 85 self.now = time.time() 86 self.authentication_timeout = authentication_timeout 87 88 def delete_all(self): 89 self.cache = {} 90 self.save() 91 92 def delete(self,ipaddr): 93 self.load() 94 for mac in self.cache.keys(): 95 if self.cache[mac][0] == ipaddr: 96 del self.cache[mac] 97 self.save() 98 99 100 def load(self): 101 """ Request cached file entries """ 102 if self.cache == None: 103 try: 104 self.cache = pickle.load(open(self.cachefile,'r')) 105 except IOError: 106 self.cache = {} 107 pass 108 109 def load_arp_cache(self): 110 """ Provide with listing of MAC to IP numbers """ 111 if self.arp_cache == None: 112 output = subprocess.Popen([conf['cmd_arp'],'-na'], stdout=subprocess.PIPE).communicate()[0] 113 self.arp_cache = {} 114 for line in output.strip().split('\n'): 115 # ? (172.20.145.30) at 00:21:e9:e2:7c:c6 on wlan0 expires in 605 seconds [ethernet] 116 if not 'expires' in line: 117 continue 118 t = re.split('[ ()]',line) 119 ip, mac = t[2],t[5] 120 self.arp_cache[ip] = mac 121 122 def get_mac(self,ipaddr): 123 self.load_arp_cache() 124 try: 125 return self.arp_cache[ipaddr] 126 except KeyError: 127 return None 128 129 def add(self,ipaddr): 130 """ Add entry to cache (on file) and return entry""" 131 self.load() 132 self.load_arp_cache() 133 self.cache[self.arp_cache[ipaddr]] = (ipaddr, self.now, self.now) 134 logging.debug("Adding Entry to Cache %s -> %s" % (ipaddr, self.arp_cache[ipaddr])) 135 self.save() 136 137 def save(self): 138 """ Sync entries to disk """ 139 # XXX: Should actually check if entry has changed at all 140 pickle.dump(self.cache, open(self.cachefile,'w')) 141 142 def update(): 143 """ Update entries with relevant ARP cache """ 144 self.load() 145 self.load_arp_cache() 146 # Update last_seen time for currently active entries 147 for ip,mac in self.arp_cache.iteritems(): 148 if self.cache.has_key(mac): 149 self.cache[mac][3] = now 67 Write statistics file, used for (nagios) monitoring purposes 68 """ 69 with open(cfg['accessdb'],"a") as fh: 70 epoch = int(time.time()) 71 fh.write("%s %s %s \n" % (epoch, remote_mac, remote_host) ) 72 73 class MACnotFound(Exception): 74 pass 75 76 def get_mac(ipaddr): 77 """ Find out the MAC for a certain IP address """ 78 try: 79 return subprocess.check_output(['/usr/sbin/arp', '-n' ,ipaddr], shell=False).split()[3][1:-1] 80 except subprocess.CalledProcessError: 81 raise MACnotFound 82 83 def get_active_MACs(): 84 """ Return dictionary with active IPs as keys """ 85 output = subprocess.check_output(['/usr/sbin/arp', '-n' ,'-a'], shell=False) 86 db = {} 87 for line in output.strip().split('\n'): 88 i = line.split() 89 mac = i[3][1:-1] 90 ip = i[5] 91 db[ip] = mac 92 return db 150 93 151 # cleanup no longer used entries, after authentication_timeout seconds. 152 for mac in self.cache: 153 if self.cache[mac][3] < self.now - self.authentication_timeout: 154 del self.cache[mac] 155 156 # Sync results to disk 157 self.save() 158 return self.cache 159 160 def get_cache(self): 161 self.load() 162 return self.cache 163 164 def get_arp_cache(self): 165 self.load_arp_cache() 166 return self.arp_cache 167 168 169 class FirewallControl: 170 def __init__(self): 171 self.first_rule = 10010 172 self.last_rule = 10099 173 self.available_rule = self.first_rule 174 self.logger = '' 175 176 177 def load(self): 178 # Get all registered ips 179 sp = subprocess.Popen([conf['cmd_fw'],'show','%i-%i' % (self.first_rule, self.last_rule)], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 180 output = sp.communicate()[0] 181 self.ip_in_firewall = {} 182 if sp.returncode == 0: 183 # 10010 32 1920 allow tcp from 172.20.145.30 to not 172.16.0.0/12 dst-port 80,443 184 for line in output.strip().split('\n'): 185 t = line.split() 186 rule, ip = t[0], t[6] 187 self.ip_in_firewall[ip] = rule 188 189 if self.available_rule == int(rule): 190 self.available_rule += 1 191 else: 192 # XXX: Some nagging about no rules beeing found perhaps? 193 pass 194 195 def cleanup(self): 196 """ Cleanup Old Entries, mostly used for maintenance runs """ 197 self.load() 198 # Make sure cache matches the latest ARP version 199 itemdb = ItemCache() 200 cache = itemdb.get_cache() 201 valid_ip = itemdb.get_arp_cache() 94 95 class PacketFilterControl(): 96 """ Manage an Packet Filter using pfctl and table wlportal""" 97 def add(self, ipaddr): 98 """ Add Allow Entry in Firewall""" 99 output = subprocess.Popen([cfg['pfctl'],'-t','wlportal', '-T', 'add', ipaddr], stderr=subprocess.PIPE).communicate()[1] 100 is_added = '1/1 addresses added.' in output 101 return is_added 102 def delete(self, ipaddr): 103 """ Delete one Allow Entry to Firewall""" 104 output = subprocess.Popen([cfg['pfctl'],'-t','wlportal', '-T', 'delete', ipaddr], stderr=subprocess.PIPE).communicate()[1] 105 def flush(self): 106 """ Delete all Allow Entries from Firewall""" 107 output = subprocess.Popen([cfg['pfctl'],'-t','wlportal', '-T', 'flush'], stderr=subprocess.PIPE).communicate()[1] 108 #0 addresses deleted. 109 return int(output.strip().split('\n')[-1].split()[0]) 110 def cleanup(self, expire_time=None): 111 """ Delete obsolete entries and expired entries from the Firewall""" 112 deleted_entries = 0 113 # Delete entries older than certain time 114 if expire_time: 115 output = subprocess.Popen([cfg['pfctl'],'-t','wlportal', '-T', 'expire', expire_time], stdout=subprocess.PIPE).communicate()[0] 116 # 0/0 addresses expired. 117 deleted_entries += int(output.strip.split()[-1].split('/')[0]) 118 119 # Delete entries which the MAC<>IP mapping does no longer hold. The 120 # ``rogue'' clients, commonly seen when DHCP scope is small and IPs get 121 # re-used frequently, are wipped and require an re-connect. 122 stored_mac = {} 123 if os.path.isfile(cfg['accessdb']): 124 for line in open(cfg['accessdb'],'r'): 125 (epoch, mac, ipaddr) = line.split() 126 stored_mac[ipaddr] = mac 127 # Live configuration 128 active_mac = get_active_MACs() 129 # Process all active ip addresses from firewall and compare changes 130 output = subprocess.Popen([cfg['pfctl'],'-t','wlportal', '-T', 'show'], stdout=subprocess.PIPE).communicate()[0] 131 for ip in output.split(): 132 if ip in cfg['whitelist']: 133 # IP is whitelisted 134 continue 135 elif active_mac.has_key(ip) and active_mac[ip] in cfg['whitelist']: 136 # MAC is whitelisted 137 continue 138 elif stored_mac.has_key(ip) and active_mac[ip] == stored_mac[ip]: 139 # previous record found 140 # Stored v.s. Active happy 141 continue 142 else: 143 self.delete(ip) 144 deleted_entries =+ 1 145 return deleted_entries 202 146 203 # Check if all ipfw allowed entries still have the same registered MAC address 204 # else assume different user and delete. 205 for ip,rule in self.ip_in_firewall.iteritems(): 206 delete_entry = False 207 208 # Make sure IP is still valid 209 if not valid_ip.has_key(ip): 210 delete_entry = True 211 # Also MAC needs to exists in Cache 212 elif not cache.has_key(valid_ip[ip]): 213 delete_entry = True 214 # IP need to match up with registered one 215 elif not cache[valid_ip[ip]][0] == ip: 216 delete_entry = True 217 218 # Delete entry if needed 219 if delete_entry: 220 output = subprocess.Popen([conf['cmd_fw'],'delete',str(rule)], stdout=subprocess.PIPE).communicate()[0] 221 self.logger += "Deleting ipfw entry %s %s\n" % (rule, ip) 222 logging.debug('Deleting ipfw entry %s %s\n' % (rule, ip)) 223 224 225 def add(self,ipaddr): 226 """ Add Entry to Firewall, False if already exists """ 227 self.load() 228 if not self.ip_in_firewall.has_key(ipaddr): 229 rule = "NUMBER allow tcp from IPADDR to not 172.16.0.0/12 dst-port 80,443".split() 230 rule[0] = str(self.available_rule) 231 rule[4] = str(ipaddr) 232 logging.debug("Addding %s" % " ".join(rule)) 233 output = subprocess.Popen([conf['cmd_fw'],'add'] + rule, stdout=subprocess.PIPE).communicate()[0] 234 itemdb = ItemCache() 235 itemdb.add(ipaddr) 236 self.register(ipaddr) 237 return True 238 else: 239 return False 240 241 def register(self, ipaddr): 242 epoch = int(time.time()) 243 244 itemdb = ItemCache() 245 mac = itemdb.get_mac(ipaddr) 246 247 filename = "/var/db/clients" 248 file = open(filename,"a") 249 file.write("%s %s %s \n" % (epoch, mac, ipaddr) ) 250 file.close() 251 252 253 def delete(self, ipaddr): 254 itemdb = ItemCache() 255 itemdb.delete(ipaddr) 256 self.cleanup() 257 258 def delete_all(self): 259 itemdb = ItemCache() 260 itemdb.delete_all() 261 self.cleanup() 262 263 def get_log(self): 264 return self.logger 265 266 267 147 148 # Call from crontab 149 if sys.argv[1:]: 150 if sys.argv[1] == 'cleanup': 151 fw = PacketFilterControl() 152 fw.cleanup() 153 sys.exit(0) 154 155 ### BEGIN STANDALONE/CGI PARSING ### 156 # 268 157 # Query String Dictionaries 269 158 qs_post = None 270 159 qs = None 271 160 header = [] 272 273 # Hybrid Setup.274 # a) We are not wrapped around in a HTTP server, so this _is_ the275 # HTTP server, so act like one.276 161 if not os.environ.has_key('REQUEST_METHOD'): 162 # a) We are not wrapped around in a HTTP server, so this _is_ the 163 # HTTP server, so act like one. 277 164 class TimeoutException(Exception): 278 165 """ Helper for alarm signal handling""" … … 313 200 314 201 # Capture Portal, make sure to redirect all to portal 315 if hostname != c onf['portalroot']:202 if hostname != cfg['portalroot']: 316 203 print "HTTP/1.1 302 Moved Temponary\r\n", 317 print "Location: http://%(portalroot)s/\r\n" % c onf,204 print "Location: http://%(portalroot)s/\r\n" % cfg, 318 205 sys.exit(0) 319 206 … … 340 227 341 228 remote_host = os.environ['REMOTE_ADDR'] 229 # 230 ### END STANDALONE/CGI PARSING ### 342 231 343 232 344 233 # Helpers for HTML 'templates' 345 content = c onf.copy()234 content = cfg.copy() 346 235 content.update(extra_header='',tech_footer='',status_msg='') 347 236 … … 351 240 # This assumes that devices will re-connect if they are not able to connect 352 241 # to their original host, as we do not preserve the original URI. 353 ic = ItemCache()354 if c onf['autologin'] or remote_host in conf['whitelist'] or ic.get_mac(remote_host) in conf['whitelist']:242 remote_mac = get_mac(remote_host) 243 if cfg['autologin'] or remote_host in cfg['whitelist'] or remote_mac in cfg['whitelist']: 355 244 qs_post = { 'action' : 'login' } 356 245 357 246 try: 247 fw = PacketFilterControl() 248 358 249 # Put authenticate use and process response 359 250 if qs and qs.has_key('action'): 360 if 'deleteall' in qs['action']: 361 content['status_msg'] += "# [INFO] Deleting all entries\n" 362 fw = FirewallControl() 363 fw.delete_all() 364 content['status_msg'] += fw.get_log() 251 if 'flush' in qs['action']: 252 retval = fw.flush() 253 content['status_msg'] += "# [INFO] Deleted %s entries\n" % retval 365 254 elif 'update' in qs['action']: 366 255 tech_footer = "# [INFO] Update timestamp of all entries\n" 367 fw = FirewallControl()368 256 fw.update() 369 257 content['status_msg'] += fw.get_log() 370 258 elif 'cleanup' in qs['action']: 371 content['status_msg'] += "# [INFO] Deleting all entries" 372 fw = FirewallControl() 373 fw.delete_all() 259 retval = fw.cleanup(cfg['expire_time']) 260 content['status_msg'] += "# [INFO] Deleted %s entries\n" % retval 374 261 elif qs_post and qs_post.has_key('action'): 375 262 if 'login' in qs_post['action']: 376 fw = FirewallControl()377 263 if fw.add(remote_host): 378 264 content['extra_header'] = "Refresh: %(refresh_delay)s; url=%(portal_url)s\r" % content 379 265 content['status_msg'] = "Sucessfully Logged In! || " +\ 380 266 """ Will redirect you in %(refresh_delay)s seconds to <a href="%(portal_url)s">%(portal_url)s</a> """ % content 267 log_registered_host(remote_mac, remote_host) 381 268 else: 382 269 content['status_msg'] = "ERROR! Already Logged On" 383 270 elif 'logout' in qs_post['action']: 384 fw = FirewallControl()385 271 fw.delete(remote_host) 386 272 content['status_msg'] = "Succesfully logged out!" 387 273 388 except Exception, e:274 except Exception, e: 389 275 content['tech_footer'] += traceback.format_exc() 390 content['status_msg'] = e276 content['status_msg'] = "<div class='error'>Internal error!<pre>%s</pre></div>" % traceback.format_exc() 391 277 pass 392 278 … … 399 285 400 286 try: 401 tmpl_file = c onf['tmpl_autologin'] if conf['autologin'] else conf['tmpl_login']287 tmpl_file = cfg['tmpl_autologin'] if cfg['autologin'] else cfg['tmpl_login'] 402 288 page = open(tmpl_file,'r').read() 403 289 except IOError:
Note:
See TracChangeset
for help on using the changeset viewer.