#!/usr/bin/env python # Laptops # # Douglas Thrift # # $Id$ from __init__ import lock import base64 from dt_dhcpd_leases import leases import getpass import ipaddr from M2Crypto import m2urllib2, SSL import optparse from pyparsing import ParseException import re import subprocess import urllib, urllib2 if __name__ == '__main__': parser = optparse.OptionParser() parser.add_option('-D', '--debug', action = 'store_true', dest = 'debug') parser.add_option('-d', '--dhcpd-leases', dest = 'dhcpd_leases') parser.add_option('-i', '--interface', dest = 'interface') parser.add_option('-r', '--range', dest = 'range') parser.add_option('-s', '--secret', action = 'store_true', dest = 'secret') options = parser.parse_args()[0] if not options.dhcpd_leases: parser.error('-d not specified') if not options.interface: parser.error('-i not specified') if not options.range: parser.error('-r not specified') minimum, maximum = map(lambda string: ipaddr.IPAddress(string), options.range.split('-', 1)) lock(options.debug) def in_range(ip_address): return ip_address >= minimum and ip_address <= maximum laptops = {} try: for declaration in leases.parseFile(options.dhcpd_leases, parseAll = True): if declaration[0] == 'lease': ip_address = ipaddr.IPAddress(declaration[1]) if in_range(ip_address): if options.debug: print '%s:' % ip_address if declaration.binding == 'active': laptop = (declaration.hardware, declaration['client-hostname'] if 'client-hostname' in declaration else '') if options.debug: print ' %s' % laptop[0] if laptop[1]: print ' %s' % laptop[1] laptops[ip_address] = laptop elif ip_address in laptops: laptops.pop(ip_address) if options.debug: print ' removed' elif options.debug: print ' ignored' except ParseException, exception: print exception.line print ' ' * (exception.column - 1) + '^' raise exception entry = re.compile(r'^(.+) \(([0-9]{1,3}(?:\.[0-9]{1,3}){3})\) at ([0-9a-f]{2}(?::[0-9a-f]{2}){5}) on %s(?: expires in [0-9]+ seconds)? \[.+\]$' % re.escape(options.interface)) arp = subprocess.Popen(('arp', '-ai', options.interface), 1, stdout = subprocess.PIPE, close_fds = True) for line in arp.stdout: match = entry.match(line) if match is not None: ip_address = ipaddr.IPAddress(match.group(2)) if in_range(ip_address) and ip_address not in laptops: if options.debug: print '%s:' % ip_address name = match.group(1).split('.', 1)[0] laptop = (match.group(3), name if name != '?' else '') if options.debug: print ' %s' % laptop[0] if laptop[1]: print ' %s' % laptop[1] laptops[ip_address] = laptop arp.stdout.close() arp.wait() for ip_address, (mac_address, name) in laptops.items(): args = ['arping', '-c', '2', '-i', options.interface, '-q', '-t', mac_address, str(ip_address)] if options.debug: args.remove('-q') if subprocess.Popen(args, close_fds = True).wait() != 0: laptops.pop(ip_address) context = SSL.Context() context.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 2) context.load_verify_locations(cafile = 'ssl/cacert.pem') realm = 'Laptops Update' url = 'https://cscl.creativestudies.org/laptops/update' handler = urllib2.HTTPBasicAuthHandler() if options.secret: with open('/ccs/etc/web.secret', 'rb') as file: handler.add_password(realm, url, *base64.b64decode(file.readline().rstrip().decode('rot13')).split(':', 1)) else: handler.add_password(realm, url, raw_input('User: '), getpass.getpass()) opener = m2urllib2.build_opener(context, handler) if laptops: for laptop in map(lambda laptop: {'i': str(laptop[0]), 'm': laptop[1][0], 'n': laptop[1][1]}.items(), laptops.iteritems()): if isinstance(laptops, list): laptops += laptop else: laptops = laptop else: laptops = {'i': '', 'm': '', 'n': ''} opener.open(url, urllib.urlencode(laptops, True))