diff options
author | Avery Pennarun <apenwarr@gmail.com> | 2010-05-08 01:30:34 -0400 |
---|---|---|
committer | Avery Pennarun <apenwarr@gmail.com> | 2010-05-08 03:00:05 -0400 |
commit | a2ea5ab455c8471cd792f660772a9f7aa9bee4b4 (patch) | |
tree | 277e00d6f65141f089da294480c4858c3fae14b6 | |
parent | 680941cb0c928577eee6eb1c1ba246e020ec4086 (diff) |
Add 'sshuttle --hostwatch' subcommand.
This tries to discover local hostnames and prints them to stdout. Will be
used by the server for auto-hostname tracking.
-rw-r--r-- | helpers.py | 4 | ||||
-rw-r--r-- | hostwatch.py | 215 | ||||
-rwxr-xr-x | main.py | 7 |
3 files changed, 225 insertions, 1 deletions
@@ -16,6 +16,10 @@ def debug2(s): if verbose >= 2: log(s) +def debug3(s): + if verbose >= 3: + log(s) + class Fatal(Exception): pass diff --git a/hostwatch.py b/hostwatch.py new file mode 100644 index 0000000..1504486 --- /dev/null +++ b/hostwatch.py @@ -0,0 +1,215 @@ +import subprocess, time, socket, re, select +if not globals().get('skip_imports'): + import helpers + from helpers import * + +POLL_TIME = 60*15 + + +_nmb_ok = True +_smb_ok = True +hostnames = {} +queue = {} +null = open('/dev/null', 'rb+') + + +def _is_ip(s): + return re.match(r'\d+\.\d+\.\d+\.\d+$', s) + + +def found_host(hostname, ip): + hostname = re.sub(r'\..*', '', hostname) + hostname = re.sub(r'[^-\w]', '_', hostname) + if ip.startswith('127.') or hostname == 'localhost': + return + oldip = hostnames.get(hostname) + if oldip != ip: + hostnames[hostname] = ip + debug1('Found: %s: %s\n' % (hostname, ip)) + sys.stdout.write('%s,%s\n' % (hostname, ip)) + + +def _check_etc_hosts(): + debug1(' > hosts\n') + for line in open('/etc/hosts'): + line = re.sub(r'#.*', '', line) + words = line.strip().split() + if not words: + continue + ip = words[0] + names = words[1:] + if _is_ip(ip): + debug2('< %s %r\n' % (ip, names)) + for n in names: + check_host(n) + found_host(n, ip) + + +def _check_revdns(ip): + debug1(' > rev: %s\n' % ip) + try: + r = socket.gethostbyaddr(ip) + debug3('< %s\n' % r[0]) + check_host(r[0]) + found_host(r[0], ip) + except socket.herror, e: + pass + + +def _check_dns(hostname): + debug1(' > dns: %s\n' % hostname) + try: + ip = socket.gethostbyname(hostname) + debug3('< %s\n' % ip) + check_host(ip) + found_host(hostname, ip) + except socket.gaierror, e: + pass + + +def _check_smb(hostname): + global _smb_ok + if not _smb_ok: + return + argv = ['smbclient', '-U', '%', '-L', hostname] + debug2(' > smb: %s\n' % hostname) + try: + p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=null) + lines = p.stdout.readlines() + p.wait() + except OSError, e: + log('%r failed: %r\n' % (argv, e)) + _smb_ok = False + return + + lines.reverse() + + # junk at top + while lines: + line = lines.pop().strip() + if re.match(r'Server\s+', line): + break + + # server list section: + # Server Comment + # ------ ------- + while lines: + line = lines.pop().strip() + if not line or re.match(r'-+\s+-+', line): + continue + if re.match(r'Workgroup\s+Master', line): + break + words = line.split() + hostname = words[0].lower() + debug3('< %s\n' % hostname) + check_host(hostname) + + # workgroup list section: + # Workgroup Master + # --------- ------ + while lines: + line = lines.pop().strip() + if re.match(r'-+\s+', line): + continue + if not line: + break + words = line.split() + (workgroup, hostname) = (words[0].lower(), words[1].lower()) + debug3('< group(%s) -> %s\n' % (workgroup, hostname)) + check_host(hostname) + check_workgroup(workgroup) + + if lines: + assert(0) + + +def _check_nmb(hostname, is_workgroup, is_master): + global _nmb_ok + if not _nmb_ok: + return + argv = ['nmblookup'] + ['-M']*is_master + ['--', hostname] + debug2(' > n%d%d: %s\n' % (is_workgroup, is_master, hostname)) + try: + p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=null) + lines = p.stdout.readlines() + rv = p.wait() + except OSError, e: + log('%r failed: %r\n' % (argv, e)) + _nmb_ok = False + return + if rv: + log('%r returned %d\n' % (argv, rv)) + return + for line in lines: + m = re.match(r'(\d+\.\d+\.\d+\.\d+) (\w+)<\w\w>\n', line) + if m: + g = m.groups() + (ip, name) = (g[0], g[1].lower()) + debug3('< %s -> %s\n' % (name, ip)) + if is_workgroup: + _enqueue(_check_smb, ip) + else: + found_host(name, ip) + check_host(name) + + +def check_host(hostname): + if _is_ip(hostname): + _enqueue(_check_revdns, hostname) + else: + _enqueue(_check_dns, hostname) + _enqueue(_check_smb, hostname) + _enqueue(_check_nmb, hostname, False, False) + + +def check_workgroup(hostname): + _enqueue(_check_nmb, hostname, True, False) + _enqueue(_check_nmb, hostname, True, True) + + +def _enqueue(op, *args): + t = (op,args) + if queue.get(t) == None: + queue[t] = 0 + + +def _stdin_still_ok(timeout): + r,w,x = select.select([sys.stdin.fileno()], [], [], timeout) + if r: + b = os.read(sys.stdin.fileno(), 4096) + if not b: + return False + return True + + +def hw_main(seed_hosts): + if helpers.verbose >= 2: + helpers.logprefix = 'HH: ' + else: + helpers.logprefix = 'hostwatch: ' + + _enqueue(_check_etc_hosts) + check_host('localhost') + check_host(socket.gethostname()) + check_workgroup('workgroup') + check_workgroup('-') + for h in seed_hosts: + check_host(h) + + while 1: + now = time.time() + for t,last_polled in queue.items(): + if not _stdin_still_ok(0): + break + if now - last_polled > POLL_TIME: + (op,args) = t + queue[t] = time.time() + op(*args) + try: + sys.stdout.flush() + except IOError: + break + + # FIXME: use a smarter timeout based on oldest last_polled + if not _stdin_still_ok(1): + break @@ -1,6 +1,6 @@ #!/usr/bin/env python import sys, os, re -import helpers, options, client, server, firewall +import helpers, options, client, server, firewall, hostwatch from helpers import * @@ -56,6 +56,7 @@ v,verbose increase debug message verbosity noserver don't use a separate server process (mostly for debugging) server [internal use only] firewall [internal use only] +hostwatch [internal use only] """ o = options.Options('sshuttle', optspec) (opt, flags, extra) = o.parse(sys.argv[1:]) @@ -64,11 +65,15 @@ helpers.verbose = opt.verbose try: if opt.server: + if len(extra) != 0: + o.fatal('no arguments expected') sys.exit(server.main()) elif opt.firewall: if len(extra) != 1: o.fatal('exactly one argument expected') sys.exit(firewall.main(int(extra[0]))) + elif opt.hostwatch: + sys.exit(hostwatch.hw_main(extra)) else: if len(extra) < 1 and not opt.auto_nets: o.fatal('at least one subnet (or -N) expected') |