summaryrefslogtreecommitdiffstats
path: root/Sshuttle VPN.app/Contents/Resources/sshuttle/hostwatch.py
diff options
context:
space:
mode:
Diffstat (limited to 'Sshuttle VPN.app/Contents/Resources/sshuttle/hostwatch.py')
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/hostwatch.py277
1 files changed, 277 insertions, 0 deletions
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/hostwatch.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/hostwatch.py
new file mode 100644
index 0000000..d77a58f
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/hostwatch.py
@@ -0,0 +1,277 @@
+import time, socket, re, select, errno
+if not globals().get('skip_imports'):
+ import compat.ssubprocess as ssubprocess
+ import helpers
+ from helpers import *
+
+POLL_TIME = 60*15
+NETSTAT_POLL_TIME = 30
+CACHEFILE=os.path.expanduser('~/.sshuttle.hosts')
+
+
+_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 write_host_cache():
+ tmpname = '%s.%d.tmp' % (CACHEFILE, os.getpid())
+ try:
+ f = open(tmpname, 'wb')
+ for name,ip in sorted(hostnames.items()):
+ f.write('%s,%s\n' % (name, ip))
+ f.close()
+ os.rename(tmpname, CACHEFILE)
+ finally:
+ try:
+ os.unlink(tmpname)
+ except:
+ pass
+
+
+def read_host_cache():
+ try:
+ f = open(CACHEFILE)
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ return
+ else:
+ raise
+ for line in f:
+ words = line.strip().split(',')
+ if len(words) == 2:
+ (name,ip) = words
+ name = re.sub(r'[^-\w]', '-', name).strip()
+ ip = re.sub(r'[^0-9.]', '', ip).strip()
+ if name and ip:
+ found_host(name, ip)
+
+
+def found_host(hostname, ip):
+ hostname = re.sub(r'\..*', '', hostname)
+ hostname = re.sub(r'[^-\w]', '_', hostname)
+ if (ip.startswith('127.') or ip.startswith('255.')
+ 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))
+ write_host_cache()
+
+
+def _check_etc_hosts():
+ debug2(' > 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):
+ debug3('< %s %r\n' % (ip, names))
+ for n in names:
+ check_host(n)
+ found_host(n, ip)
+
+
+def _check_revdns(ip):
+ debug2(' > 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):
+ debug2(' > 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_netstat():
+ debug2(' > netstat\n')
+ argv = ['netstat', '-n']
+ try:
+ p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
+ content = p.stdout.read()
+ p.wait()
+ except OSError, e:
+ log('%r failed: %r\n' % (argv, e))
+ return
+
+ for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content):
+ debug3('< %s\n' % ip)
+ check_host(ip)
+
+
+def _check_smb(hostname):
+ return
+ global _smb_ok
+ if not _smb_ok:
+ return
+ argv = ['smbclient', '-U', '%', '-L', hostname]
+ debug2(' > smb: %s\n' % hostname)
+ try:
+ p = ssubprocess.Popen(argv, stdout=ssubprocess.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):
+ return
+ 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 = ssubprocess.Popen(argv, stdout=ssubprocess.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: '
+
+ read_host_cache()
+
+ _enqueue(_check_etc_hosts)
+ _enqueue(_check_netstat)
+ 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():
+ (op,args) = t
+ if not _stdin_still_ok(0):
+ break
+ maxtime = POLL_TIME
+ if op == _check_netstat:
+ maxtime = NETSTAT_POLL_TIME
+ if now - last_polled > maxtime:
+ 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