summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvery Pennarun <apenwarr@gmail.com>2010-05-08 01:30:34 -0400
committerAvery Pennarun <apenwarr@gmail.com>2010-05-08 03:00:05 -0400
commita2ea5ab455c8471cd792f660772a9f7aa9bee4b4 (patch)
tree277e00d6f65141f089da294480c4858c3fae14b6
parent680941cb0c928577eee6eb1c1ba246e020ec4086 (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.py4
-rw-r--r--hostwatch.py215
-rwxr-xr-xmain.py7
3 files changed, 225 insertions, 1 deletions
diff --git a/helpers.py b/helpers.py
index 23f3c84..e43eea2 100644
--- a/helpers.py
+++ b/helpers.py
@@ -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
diff --git a/main.py b/main.py
index 2811ff7..88b85c5 100755
--- a/main.py
+++ b/main.py
@@ -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')