Commit d5c3aa61 authored by Avery Pennarun's avatar Avery Pennarun

MacOS precompiled app package for sshuttle-0.60

parent 5b57de24
...@@ -78,6 +78,11 @@ class Runner: ...@@ -78,6 +78,11 @@ class Runner:
if pid == self.pid: if pid == self.pid:
if os.WIFEXITED(code): if os.WIFEXITED(code):
self.rv = os.WEXITSTATUS(code) self.rv = os.WEXITSTATUS(code)
if self.rv == 111:
NSRunAlertPanel('Sshuttle',
'Please restart your computer to finish '
'installing Sshuttle.',
'Restart Later', None, None)
else: else:
self.rv = -os.WSTOPSIG(code) self.rv = -os.WSTOPSIG(code)
self.serverobj.setConnected_(False) self.serverobj.setConnected_(False)
...@@ -87,7 +92,10 @@ class Runner: ...@@ -87,7 +92,10 @@ class Runner:
return self.rv return self.rv
def wait(self): def wait(self):
return self._try_wait(0) rv = None
while rv is None:
self.gotdata(None)
rv = self._try_wait(os.WNOHANG)
def poll(self): def poll(self):
return self._try_wait(os.WNOHANG) return self._try_wait(os.WNOHANG)
......
...@@ -3,6 +3,7 @@ import my ...@@ -3,6 +3,7 @@ import my
configchange_callback = setconnect_callback = None configchange_callback = setconnect_callback = None
objc_validator = objc.signature('@@:N^@o^@')
def config_changed(): def config_changed():
...@@ -39,7 +40,7 @@ class SshuttleNet(NSObject): ...@@ -39,7 +40,7 @@ class SshuttleNet(NSObject):
def setSubnet_(self, v): def setSubnet_(self, v):
self._k_subnet = v self._k_subnet = v
config_changed() config_changed()
@objc.accessor @objc_validator
def validateSubnet_error_(self, value, error): def validateSubnet_error_(self, value, error):
#print 'validateSubnet!' #print 'validateSubnet!'
return True, _validate_ip(value), error return True, _validate_ip(value), error
...@@ -49,7 +50,7 @@ class SshuttleNet(NSObject): ...@@ -49,7 +50,7 @@ class SshuttleNet(NSObject):
def setWidth_(self, v): def setWidth_(self, v):
self._k_width = v self._k_width = v
config_changed() config_changed()
@objc.accessor @objc_validator
def validateWidth_error_(self, value, error): def validateWidth_error_(self, value, error):
#print 'validateWidth!' #print 'validateWidth!'
return True, _validate_width(value), error return True, _validate_width(value), error
...@@ -118,7 +119,7 @@ class SshuttleServer(NSObject): ...@@ -118,7 +119,7 @@ class SshuttleServer(NSObject):
self._k_host = v self._k_host = v
self.setTitle_(None) self.setTitle_(None)
config_changed() config_changed()
@objc.accessor @objc_validator
def validateHost_error_(self, value, error): def validateHost_error_(self, value, error):
#print 'validatehost! %r %r %r' % (self, value, error) #print 'validatehost! %r %r %r' % (self, value, error)
while value.startswith('-'): while value.startswith('-'):
......
...@@ -171,10 +171,71 @@ class FirewallClient: ...@@ -171,10 +171,71 @@ class FirewallClient:
def done(self): def done(self):
self.pfile.close() self.pfile.close()
rv = self.p.wait() rv = self.p.wait()
if rv: if rv == EXITCODE_NEEDS_REBOOT:
raise FatalNeedsReboot()
elif rv:
raise Fatal('cleanup: %r returned %d' % (self.argv, rv)) raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
def onaccept(listener, mux, handlers):
global _extra_fd
try:
sock,srcip = listener.accept()
except socket.error, e:
if e.args[0] in [errno.EMFILE, errno.ENFILE]:
debug1('Rejected incoming connection: too many open files!\n')
# free up an fd so we can eat the connection
os.close(_extra_fd)
try:
sock,srcip = listener.accept()
sock.close()
finally:
_extra_fd = os.open('/dev/null', os.O_RDONLY)
return
else:
raise
dstip = original_dst(sock)
debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1],
dstip[0],dstip[1]))
if dstip[1] == listener.getsockname()[1] and islocal(dstip[0]):
debug1("-- ignored: that's my address!\n")
sock.close()
return
chan = mux.next_channel()
if not chan:
log('warning: too many open channels. Discarded connection.\n')
sock.close()
return
mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
outwrap = MuxWrapper(mux, chan)
handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
dnsreqs = {}
def dns_done(chan, data):
peer,sock,timeout = dnsreqs.get(chan) or (None,None,None)
debug3('dns_done: channel=%r peer=%r\n' % (chan, peer))
if peer:
del dnsreqs[chan]
debug3('doing sendto %r\n' % (peer,))
sock.sendto(data, peer)
def ondns(listener, mux, handlers):
pkt,peer = listener.recvfrom(4096)
now = time.time()
if pkt:
debug1('DNS request from %r: %d bytes\n' % (peer, len(pkt)))
chan = mux.next_channel()
dnsreqs[chan] = peer,listener,now+30
mux.send(chan, ssnet.CMD_DNS_REQ, pkt)
mux.channels[chan] = lambda cmd,data: dns_done(chan,data)
for chan,(peer,sock,timeout) in dnsreqs.items():
if timeout < now:
del dnsreqs[chan]
debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
def _main(listener, fw, ssh_cmd, remotename, python, latency_control, def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
dnslistener, seed_hosts, auto_nets, dnslistener, seed_hosts, auto_nets,
syslog, daemon): syslog, daemon):
...@@ -255,63 +316,10 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control, ...@@ -255,63 +316,10 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
fw.sethostip(name, ip) fw.sethostip(name, ip)
mux.got_host_list = onhostlist mux.got_host_list = onhostlist
def onaccept(): handlers.append(Handler([listener], lambda: onaccept(listener, mux, handlers)))
global _extra_fd
try:
sock,srcip = listener.accept()
except socket.error, e:
if e.args[0] in [errno.EMFILE, errno.ENFILE]:
debug1('Rejected incoming connection: too many open files!\n')
# free up an fd so we can eat the connection
os.close(_extra_fd)
try:
sock,srcip = listener.accept()
sock.close()
finally:
_extra_fd = os.open('/dev/null', os.O_RDONLY)
return
else:
raise
dstip = original_dst(sock)
debug1('Accept: %s:%r -> %s:%r.\n' % (srcip[0],srcip[1],
dstip[0],dstip[1]))
if dstip[1] == listener.getsockname()[1] and islocal(dstip[0]):
debug1("-- ignored: that's my address!\n")
sock.close()
return
chan = mux.next_channel()
if not chan:
log('warning: too many open channels. Discarded connection.\n')
sock.close()
return
mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
outwrap = MuxWrapper(mux, chan)
handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
handlers.append(Handler([listener], onaccept))
dnsreqs = {}
def dns_done(chan, data):
peer,timeout = dnsreqs.get(chan) or (None,None)
debug3('dns_done: channel=%r peer=%r\n' % (chan, peer))
if peer:
del dnsreqs[chan]
debug3('doing sendto %r\n' % (peer,))
dnslistener.sendto(data, peer)
def ondns():
pkt,peer = dnslistener.recvfrom(4096)
now = time.time()
if pkt:
debug1('DNS request from %r: %d bytes\n' % (peer, len(pkt)))
chan = mux.next_channel()
dnsreqs[chan] = peer,now+30
mux.send(chan, ssnet.CMD_DNS_REQ, pkt)
mux.channels[chan] = lambda cmd,data: dns_done(chan,data)
for chan,(peer,timeout) in dnsreqs.items():
if timeout < now:
del dnsreqs[chan]
debug3('Remaining DNS requests: %d\n' % len(dnsreqs))
if dnslistener: if dnslistener:
handlers.append(Handler([dnslistener], ondns)) handlers.append(Handler([dnslistener], lambda: ondns(dnslistener, mux, handlers)))
if seed_hosts != None: if seed_hosts != None:
debug1('seed_hosts: %r\n' % seed_hosts) debug1('seed_hosts: %r\n' % seed_hosts)
......
import re, errno, socket, select, struct import re, errno, socket, select, signal, struct
import compat.ssubprocess as ssubprocess import compat.ssubprocess as ssubprocess
import helpers, ssyslog import helpers, ssyslog
from helpers import * from helpers import *
...@@ -6,6 +6,12 @@ from helpers import * ...@@ -6,6 +6,12 @@ from helpers import *
# python doesn't have a definition for this # python doesn't have a definition for this
IPPROTO_DIVERT = 254 IPPROTO_DIVERT = 254
# return values from sysctl_set
SUCCESS = 0
SAME = 1
FAILED = -1
NONEXIST = -2
def nonfatal(func, *args): def nonfatal(func, *args):
try: try:
...@@ -14,6 +20,14 @@ def nonfatal(func, *args): ...@@ -14,6 +20,14 @@ def nonfatal(func, *args):
log('error: %s\n' % e) log('error: %s\n' % e)
def _call(argv):
debug1('>> %s\n' % ' '.join(argv))
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
return rv
def ipt_chain_exists(name): def ipt_chain_exists(name):
argv = ['iptables', '-t', 'nat', '-nL'] argv = ['iptables', '-t', 'nat', '-nL']
p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE) p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
...@@ -27,10 +41,7 @@ def ipt_chain_exists(name): ...@@ -27,10 +41,7 @@ def ipt_chain_exists(name):
def ipt(*args): def ipt(*args):
argv = ['iptables', '-t', 'nat'] + list(args) argv = ['iptables', '-t', 'nat'] + list(args)
debug1('>> %s\n' % ' '.join(argv)) _call(argv)
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
_no_ttl_module = False _no_ttl_module = False
...@@ -135,6 +146,42 @@ def _fill_oldctls(prefix): ...@@ -135,6 +146,42 @@ def _fill_oldctls(prefix):
raise Fatal('%r returned no data' % (argv,)) raise Fatal('%r returned no data' % (argv,))
KERNEL_FLAGS_PATH = '/Library/Preferences/SystemConfiguration/com.apple.Boot'
KERNEL_FLAGS_NAME = 'Kernel Flags'
def _defaults_read_kernel_flags():
argv = ['defaults', 'read', KERNEL_FLAGS_PATH, KERNEL_FLAGS_NAME]
debug1('>> %s\n' % ' '.join(argv))
p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
flagstr = p.stdout.read().strip()
rv = p.wait()
if rv:
raise Fatal('%r returned %d' % (argv, rv))
flags = flagstr and flagstr.split(' ') or []
return flags
def _defaults_write_kernel_flags(flags):
flagstr = ' '.join(flags)
argv = ['defaults', 'write', KERNEL_FLAGS_PATH, KERNEL_FLAGS_NAME,
flagstr]
_call(argv)
argv = ['plutil', '-convert', 'xml1', KERNEL_FLAGS_PATH + '.plist']
_call(argv)
def defaults_write_kernel_flag(name, val):
flags = _defaults_read_kernel_flags()
found = 0
for i in range(len(flags)):
if flags[i].startswith('%s=' % name):
found += 1
flags[i] = '%s=%s' % (name, val)
if not found:
flags.insert(0, '%s=%s' % (name, val))
_defaults_write_kernel_flags(flags)
def _sysctl_set(name, val): def _sysctl_set(name, val):
argv = ['sysctl', '-w', '%s=%s' % (name, val)] argv = ['sysctl', '-w', '%s=%s' % (name, val)]
debug1('>> %s\n' % ' '.join(argv)) debug1('>> %s\n' % ' '.join(argv))
...@@ -150,20 +197,24 @@ def sysctl_set(name, val, permanent=False): ...@@ -150,20 +197,24 @@ def sysctl_set(name, val, permanent=False):
_fill_oldctls(PREFIX) _fill_oldctls(PREFIX)
if not (name in _oldctls): if not (name in _oldctls):
debug1('>> No such sysctl: %r\n' % name) debug1('>> No such sysctl: %r\n' % name)
return False return NONEXIST
oldval = _oldctls[name] oldval = _oldctls[name]
if val != oldval: if val == oldval:
rv = _sysctl_set(name, val) return SAME
if rv==0 and permanent:
debug1('>> ...saving permanently in /etc/sysctl.conf\n') rv = _sysctl_set(name, val)
f = open('/etc/sysctl.conf', 'a') if rv != 0:
f.write('\n' return FAILED
'# Added by sshuttle\n' if permanent:
'%s=%s\n' % (name, val)) debug1('>> ...saving permanently in /etc/sysctl.conf\n')
f.close() f = open('/etc/sysctl.conf', 'a')
else: f.write('\n'
_changedctls.append(name) '# Added by sshuttle\n'
return True '%s=%s\n' % (name, val))
f.close()
else:
_changedctls.append(name)
return SUCCESS
def _udp_unpack(p): def _udp_unpack(p):
...@@ -201,10 +252,7 @@ def _handle_diversion(divertsock, dnsport): ...@@ -201,10 +252,7 @@ def _handle_diversion(divertsock, dnsport):
def ipfw(*args): def ipfw(*args):
argv = ['ipfw', '-q'] + list(args) argv = ['ipfw', '-q'] + list(args)
debug1('>> %s\n' % ' '.join(argv)) _call(argv)
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
def do_ipfw(port, dnsport, subnets): def do_ipfw(port, dnsport, subnets):
...@@ -222,8 +270,14 @@ def do_ipfw(port, dnsport, subnets): ...@@ -222,8 +270,14 @@ def do_ipfw(port, dnsport, subnets):
if subnets or dnsport: if subnets or dnsport:
sysctl_set('net.inet.ip.fw.enable', 1) sysctl_set('net.inet.ip.fw.enable', 1)
changed = sysctl_set('net.inet.ip.scopedroute', 0, permanent=True)
if changed: # This seems to be needed on MacOS 10.6 and 10.7. For more
# information, see:
# http://groups.google.com/group/sshuttle/browse_thread/thread/bc32562e17987b25/6d3aa2bb30a1edab
# and
# http://serverfault.com/questions/138622/transparent-proxying-leaves-sockets-with-syn-rcvd-in-macos-x-10-6-snow-leopard
changeflag = sysctl_set('net.inet.ip.scopedroute', 0, permanent=True)
if changeflag == SUCCESS:
log("\n" log("\n"
" WARNING: ONE-TIME NETWORK DISRUPTION:\n" " WARNING: ONE-TIME NETWORK DISRUPTION:\n"
" =====================================\n" " =====================================\n"
...@@ -234,6 +288,21 @@ def do_ipfw(port, dnsport, subnets): ...@@ -234,6 +288,21 @@ def do_ipfw(port, dnsport, subnets):
"ethernet port) NOW, then restart sshuttle. The fix is\n" "ethernet port) NOW, then restart sshuttle. The fix is\n"
"permanent; you only have to do this once.\n\n") "permanent; you only have to do this once.\n\n")
sys.exit(1) sys.exit(1)
elif changeflag == FAILED:
# On MacOS 10.7, the scopedroute sysctl became read-only, so
# we have to fix it using a kernel boot parameter instead,
# which requires rebooting. For more, see:
# http://groups.google.com/group/sshuttle/browse_thread/thread/a42505ca33e1de80/e5e8f3e5a92d25f7
log('Updating kernel boot flags.\n')
defaults_write_kernel_flag('net.inet.ip.scopedroute', 0)
log("\n"
" YOU MUST REBOOT TO USE SSHUTTLE\n"
" ===============================\n"
"sshuttle has changed a MacOS kernel boot-time setting\n"
"to work around a bug in MacOS 10.7 Lion. You will need\n"
"to reboot before it takes effect. You only have to\n"
"do this once.\n\n")
sys.exit(EXITCODE_NEEDS_REBOOT)
ipfw('add', sport, 'check-state', 'ip', ipfw('add', sport, 'check-state', 'ip',
'from', 'any', 'to', 'any') 'from', 'any', 'to', 'any')
...@@ -243,11 +312,11 @@ def do_ipfw(port, dnsport, subnets): ...@@ -243,11 +312,11 @@ def do_ipfw(port, dnsport, subnets):
for swidth,sexclude,snet in sorted(subnets, reverse=True): for swidth,sexclude,snet in sorted(subnets, reverse=True):
if sexclude: if sexclude:
ipfw('add', sport, 'skipto', xsport, ipfw('add', sport, 'skipto', xsport,
'log', 'tcp', 'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth)) 'from', 'any', 'to', '%s/%s' % (snet,swidth))
else: else:
ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port, ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port,
'log', 'tcp', 'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth), 'from', 'any', 'to', '%s/%s' % (snet,swidth),
'not', 'ipttl', '42', 'keep-state', 'setup') 'not', 'ipttl', '42', 'keep-state', 'setup')
...@@ -289,12 +358,12 @@ def do_ipfw(port, dnsport, subnets): ...@@ -289,12 +358,12 @@ def do_ipfw(port, dnsport, subnets):
for ip in nslist: for ip in nslist:
# relabel and then catch outgoing DNS requests # relabel and then catch outgoing DNS requests
ipfw('add', sport, 'divert', sport, ipfw('add', sport, 'divert', sport,
'log', 'udp', 'udp',
'from', 'any', 'to', '%s/32' % ip, '53', 'from', 'any', 'to', '%s/32' % ip, '53',
'not', 'ipttl', '42') 'not', 'ipttl', '42')
# relabel DNS responses # relabel DNS responses
ipfw('add', sport, 'divert', sport, ipfw('add', sport, 'divert', sport,
'log', 'udp', 'udp',
'from', 'any', str(dnsport), 'to', 'any', 'from', 'any', str(dnsport), 'to', 'any',
'not', 'ipttl', '42') 'not', 'ipttl', '42')
...@@ -398,6 +467,11 @@ def main(port, dnsport, syslog): ...@@ -398,6 +467,11 @@ def main(port, dnsport, syslog):
sys.stdout.write('READY\n') sys.stdout.write('READY\n')
sys.stdout.flush() sys.stdout.flush()
# don't disappear if our controlling terminal or stdout/stderr
# disappears; we still have to clean up.
signal.signal(signal.SIGHUP, signal.SIG_IGN)
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
# ctrl-c shouldn't be passed along to me. When the main sshuttle dies, # ctrl-c shouldn't be passed along to me. When the main sshuttle dies,
# I'll die automatically. # I'll die automatically.
os.setsid() os.setsid()
......
import sys, os, socket import sys, os, socket, errno
logprefix = '' logprefix = ''
verbose = 0 verbose = 0
...@@ -30,6 +30,11 @@ class Fatal(Exception): ...@@ -30,6 +30,11 @@ class Fatal(Exception):
pass pass
EXITCODE_NEEDS_REBOOT = 111
class FatalNeedsReboot(Fatal):
pass
def list_contains_any(l, sub): def list_contains_any(l, sub):
for i in sub: for i in sub:
if i in l: if i in l:
......
...@@ -63,6 +63,7 @@ seed-hosts= with -H, use these hostnames for initial scan (comma-separated) ...@@ -63,6 +63,7 @@ seed-hosts= with -H, use these hostnames for initial scan (comma-separated)
no-latency-control sacrifice latency to improve bandwidth benchmarks no-latency-control sacrifice latency to improve bandwidth benchmarks
wrap= restart counting channel numbers after this number (for testing) wrap= restart counting channel numbers after this number (for testing)
D,daemon run in the background as a daemon D,daemon run in the background as a daemon
V,version print sshuttle's version number
syslog send log messages to syslog (default if you use --daemon) syslog send log messages to syslog (default if you use --daemon)
pidfile= pidfile name (only if using --daemon) [./sshuttle.pid] pidfile= pidfile name (only if using --daemon) [./sshuttle.pid]
server (internal use only) server (internal use only)
...@@ -72,6 +73,10 @@ hostwatch (internal use only) ...@@ -72,6 +73,10 @@ hostwatch (internal use only)
o = options.Options(optspec) o = options.Options(optspec)
(opt, flags, extra) = o.parse(sys.argv[2:]) (opt, flags, extra) = o.parse(sys.argv[2:])
if opt.version:
import version
print version.TAG
sys.exit(0)
if opt.daemon: if opt.daemon:
opt.syslog = 1 opt.syslog = 1
if opt.wrap: if opt.wrap:
...@@ -121,6 +126,9 @@ try: ...@@ -121,6 +126,9 @@ try:
parse_subnets(includes), parse_subnets(includes),
parse_subnets(excludes), parse_subnets(excludes),
opt.syslog, opt.daemon, opt.pidfile)) opt.syslog, opt.daemon, opt.pidfile))
except FatalNeedsReboot, e:
log('You must reboot before using sshuttle.\n')
sys.exit(EXITCODE_NEEDS_REBOOT)
except Fatal, e: except Fatal, e:
log('fatal: %s\n' % e) log('fatal: %s\n' % e)
sys.exit(99) sys.exit(99)
......
...@@ -152,6 +152,17 @@ class SockWrapper: ...@@ -152,6 +152,17 @@ class SockWrapper:
debug3('%r: fixed connect result: %s\n' % (self, e)) debug3('%r: fixed connect result: %s\n' % (self, e))
if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]: if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
pass # not connected yet pass # not connected yet
elif e.args[0] == 0:
# connected successfully (weird Linux bug?)
# Sometimes Linux seems to return EINVAL when it isn't
# invalid. This *may* be caused by a race condition
# between connect() and getsockopt(SO_ERROR) (ie. it
# finishes connecting in between the two, so there is no
# longer an error). However, I'm not sure of that.
#
# I did get at least one report that the problem went away
# when we added this, however.
self.connect_to = None
elif e.args[0] == errno.EISCONN: elif e.args[0] == errno.EISCONN:
# connected successfully (BSD) # connected successfully (BSD)
self.connect_to = None self.connect_to = None
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment