Compare commits

...

38 Commits

Author SHA1 Message Date
29d2e06bf5 Added --exclude-from feature.
(Slightly modified by apenwarr)
2012-07-06 15:13:30 -04:00
bff1610050 Document missing --dns option in sshuttle manpage 2012-07-06 15:06:07 -04:00
cce6a9d96d firewall.py: catch SIGINT and SIGTERM too.
There were still a few conditions under some OSes that would cause
firewall.py to terminate without cleaning up the firewall settings.  'pkill
sshuttle' was one of them.  Ignore a couple more signals to further ensure a
correct cleanup.

(This only affects sshuttle --firewall, which is a subprocess of the main
sshuttle process.  The firewall is supposed to exit automatically whenever
the client exits, and so far that part seems to work reliably.)
2012-07-06 15:00:28 -04:00
5743f29ed6 server.py: slightly rearrange previous commit.
Add some documentation about the int() vs long() and the reason behind
_shl().  Instead of "from __future__ import generators", just don't use
generators.
2012-07-06 15:00:28 -04:00
42bc6d62db Two small changes to server.py that allow it to run on python2.2 2012-04-19 23:00:29 -07:00
274ee854d4 clean.do: don't forget to do version/clean. 2012-02-07 12:17:56 -05:00
12f6a52ec6 Fix runpython.do for systems with unxpected configurations.
If the expected arch directory doesn't exist, give up and don't specify arch at
all. Currently it expands to '*' which fails.

[slightly modified by apenwarr]
2012-02-07 12:16:31 -05:00
e737f4b944 firewall.py: add comments about sysctl problems. 2012-01-08 19:13:39 -05:00
d9f761a8a3 ui-macos: tell the user that we need to reboot on MacOS Lion. 2012-01-08 19:01:18 -05:00
bd20841782 firewall.py: clean up repeated calls to ssubprocess.call().
And make sshuttle exit with a well-defined exit code (111) if it needs to
reboot.
2012-01-08 19:01:18 -05:00
4c1a505e37 firewall.py: workaround MacOS 10.7 Lion bug.
On top of the bug that already existed in 10.6, Lion also makes the sysctl
needed to fix the problem into a read-only variable, so we have to actually
change it at kernel boot time and force people to reboot.  Nice job, Apple.
2012-01-08 19:01:18 -05:00
41d1f73dc2 Add a --version (-V) option.
Now that we imported the feature from redo, might as well use it.
2012-01-06 13:45:43 -05:00
cbc32ff8d8 Import the non-pandoc manpage generator from redo.
This makes it easier (possible?) to generate sshuttle.8 from sshuttle.md on
MacOS.  We also import the git-enhanced version numbering magic so the
generated manpage can have a real version number.
2012-01-06 13:35:12 -05:00
6698992f4f Use the new arguments from redo v0.10.
(apenwarr: also updates to the matching, latest minimal/do)
2012-01-06 13:24:56 -05:00
e2c682084c firewall: catch SIGHUP and SIGPIPE.
Not sure if this will fix anything, but it might stop the problem reported
on some MacOS versions where the firewall doesn't get cleaned up correctly.
2012-01-06 13:14:55 -05:00
89e914e9d1 ui-macos/main.py: fix wait() to avoid deadlock.
If the subprocess was trying to write to its stdout/stderr, its process
would never actually finish because it was blocked waiting for us to read
it, but we were blocked on waitpid().  Instead, use waitpid(WNOHANG) and
continually read from the subprocess (which should be a blocking operation)
until it exits.
2012-01-02 18:46:30 -05:00
2268e76771 ipfw: don't use 'log' parameter.
I guess we were causing the kernel to syslog on every single packet on
MacOS.  Oops.
2012-01-02 18:19:19 -05:00
a8b71f6387 Move nested functions to top level. 2011-05-31 00:42:48 -04:00
4bfcd7091d Send DNS request back on same sock we received it on. 2011-05-31 00:39:17 -04:00
bd489b3319 Pass socket through to handlers. Required for IPv6 support. 2011-05-31 00:39:17 -04:00
8ab5ef283d ssnet.py: deal with a possible connect/getsockopt(SO_ERROR) race.
Seems to affect Linux servers.  Ed Maste says the patch fixes it for him.
2011-05-29 22:42:16 -04:00
e67208a294 helpers.py: errno is used by this module, but not imported. 2011-05-15 17:35:53 -04:00
7859be13c2 ui-macos/bits/runpython.do: skip ppc64 architecture.
I don't have a Mac that can build it.  Hopefully ppc will run fine on ppc64.
2011-05-07 23:19:52 -04:00
f313d50690 ui-macos/bits/runpython.do: report which platforms we're compiling for.
Just as a quick reminder, in case you're building a fat binary and you don't
have all the architectures actually installed.
2011-05-07 23:16:42 -04:00
15e26d2e0e README.md: fix little bug
The ssh hostname should immediately follow the -r parameter.
2011-05-07 23:16:42 -04:00
e2ec475de3 ui-macos/models.py: fix a compatibility problem on MacOS for PPC.
@objc.accessor isn't the right thing to use for a Core Data Validation
function.  Yowee, PyObjc sure is non-obvious.
2011-05-07 23:16:42 -04:00
57e744dadd ./do: use the latest minimal/do from the redo project. 2011-05-03 14:19:45 -07:00
c13be0b817 ui-macos/bits/runpython.do: auto-determine arches to build for.
Some people don't have all of them installed, so auto-detect them by
looking at the available arches in /usr/libexec.
2011-05-03 14:18:37 -07:00
da2c6273f6 Add some friendly info to the README 2011-05-03 14:03:19 -07:00
7712c60c36 Insert two binary NUL bytes (\0) before SSHUTTLE0001 sync string.
...and search for those null bytes before looking for the sync string.

This helps when people have misconfigured .bashrc to print messages even in
non-interactive mode.  (On my Debian Lenny system, .bashrc doesn't seem to
run when you do 'ssh localhost ls', but on MacOS servers, it does.  Hmm...)
2011-05-03 13:59:25 -07:00
65b0390fe9 ssh.py: use 'exec python -c' instead of just 'python -c'.
This gets rid of an extra intermediate sh process on the server that we were
keeping for no good reason, since it would exit as soon as python exited
anyway.
2011-05-03 13:51:09 -07:00
c5834a9773 Handle EHOSTDOWN, ENETDOWN.
Someone on the mailing list reported getting these.

Also centralize the list of these errors, so we don't have different parts
of the code supporting a different subset of them.  Now just use
ssnet.NET_ERRS.
2011-05-03 13:32:25 -07:00
e2474543fc runpython.do: also compile for ppc architecture. 2011-04-24 22:51:27 -04:00
8636378870 Dereference symlink for sshuttle launch script
(Modified slightly by apenwarr)
2011-04-24 22:42:50 -04:00
f5eed4c809 Don't try to connect to remote IPs that start with zero.
For some reason, on Linux servers this returns EINVAL.  I don't like just
treating EINVAL as non-fatal in general, so let's catch this specific case
and ignore it.

Reported by Reza Mohammadi on the mailing list.  Interestingly, it's kind of
hard to trigger this crash since the client would have to request the
connection, and that connection shouldn't exist because the original client
program would have already gotten EINVAL.  But my MacOS machine can generate
such a connection, so a MacOS->Linux sshuttle could trigger this.
2011-04-24 22:15:20 -04:00
783d33cada DNS: auto-retry if we get an error on send/recv to DNS server.
A few people have reported that they have one or more invalid DNS servers in
/etc/resolv.conf, which they don't notice because the normal resolver
library just skips the broken ones.  sshuttle would abort because it got an
unexpected socket error, which isn't so good.
2011-04-06 12:30:12 -04:00
94241b938b On FreeBSD, avoid a crash caused by buggy socket.connect() in python pre-2.5.
Bug reported by Ed Maste.  The fix in later versions of python is documented
here:
http://mail.python.org/pipermail/python-bugs-list/2006-August/034667.html

We're basically just doing the same thing when we see EINVAL.  Note that
this doesn't happen on Linux because connect() is more forgiving.
2011-03-21 03:15:11 -07:00
9031de1527 repr(socket.error) is useless in some versions of python.
So let's use %s instead of %r to print it, so that log messages can be more
useful.  This only affects one message at debug3 for now, so it's not too
exciting.
2011-03-21 03:15:11 -07:00
38 changed files with 799 additions and 172 deletions

3
Documentation/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.8
/md-to-man
/*.md.tmp

5
Documentation/all.do Normal file
View File

@ -0,0 +1,5 @@
/bin/ls *.md |
sed 's/\.md/.8/' |
xargs redo-ifchange
redo-always

1
Documentation/clean.do Normal file
View File

@ -0,0 +1 @@
rm -f *~ .*~ *.8 t/*.8 md-to-man *.tmp t/*.tmp

View File

@ -0,0 +1,2 @@
redo-ifchange md-to-man $2.md.tmp
. ./md-to-man $1 $2 $3

View File

@ -0,0 +1,3 @@
redo-ifchange ../version/vars $2.md
. ../version/vars
sed -e "s/%VERSION%/$TAG/" -e "s/%DATE%/$DATE/" $2.md

View File

@ -0,0 +1,8 @@
redo-ifchange md2man.py
if ./md2man.py </dev/null >/dev/null; then
echo './md2man.py $2.md.tmp'
else
echo "Warning: md2man.py missing modules; can't generate manpages." >&2
echo "Warning: try this: sudo easy_install markdown BeautifulSoup" >&2
echo 'echo Skipping: $2.1 >&2'
fi

278
Documentation/md2man.py Executable file
View File

@ -0,0 +1,278 @@
#!/usr/bin/env python
import sys, os, markdown, re
from BeautifulSoup import BeautifulSoup
def _split_lines(s):
return re.findall(r'([^\n]*\n?)', s)
class Writer:
def __init__(self):
self.started = False
self.indent = 0
self.last_wrote = '\n'
def _write(self, s):
if s:
self.last_wrote = s
sys.stdout.write(s)
def writeln(self, s):
if s:
self.linebreak()
self._write('%s\n' % s)
def write(self, s):
if s:
self.para()
for line in _split_lines(s):
if line.startswith('.'):
self._write('\\&' + line)
else:
self._write(line)
def linebreak(self):
if not self.last_wrote.endswith('\n'):
self._write('\n')
def para(self, bullet=None):
if not self.started:
if not bullet:
bullet = ' '
if not self.indent:
self.writeln(_macro('.PP'))
else:
assert(self.indent >= 2)
prefix = ' '*(self.indent-2) + bullet + ' '
self.writeln('.IP "%s" %d' % (prefix, self.indent))
self.started = True
def end_para(self):
self.linebreak()
self.started = False
def start_bullet(self):
self.indent += 3
self.para(bullet='\\[bu]')
def end_bullet(self):
self.indent -= 3
self.end_para()
w = Writer()
def _macro(name, *args):
if not name.startswith('.'):
raise ValueError('macro names must start with "."')
fixargs = []
for i in args:
i = str(i)
i = i.replace('\\', '')
i = i.replace('"', "'")
if (' ' in i) or not i:
i = '"%s"' % i
fixargs.append(i)
return ' '.join([name] + list(fixargs))
def macro(name, *args):
w.writeln(_macro(name, *args))
def _force_string(owner, tag):
if tag.string:
return tag.string
else:
out = ''
for i in tag:
if not (i.string or i.name in ['a', 'br']):
raise ValueError('"%s" tags must contain only strings: '
'got %r: %r' % (owner.name, tag.name, tag))
out += _force_string(owner, i)
return out
def _clean(s):
s = s.replace('\\', '\\\\')
return s
def _bitlist(tag):
if getattr(tag, 'contents', None) == None:
for i in _split_lines(str(tag)):
yield None,_clean(i)
else:
for e in tag:
name = getattr(e, 'name', None)
if name in ['a', 'br']:
name = None # just treat as simple text
s = _force_string(tag, e)
if name:
yield name,_clean(s)
else:
for i in _split_lines(s):
yield None,_clean(i)
def _bitlist_simple(tag):
for typ,text in _bitlist(tag):
if typ and not typ in ['em', 'strong', 'code']:
raise ValueError('unexpected tag %r inside %r' % (typ, tag.name))
yield text
def _text(bitlist):
out = ''
for typ,text in bitlist:
if not typ:
out += text
elif typ == 'em':
out += '\\fI%s\\fR' % text
elif typ in ['strong', 'code']:
out += '\\fB%s\\fR' % text
else:
raise ValueError('unexpected tag %r inside %r' % (typ, tag.name))
out = out.strip()
out = re.sub(re.compile(r'^\s+', re.M), '', out)
return out
def text(tag):
w.write(_text(_bitlist(tag)))
# This is needed because .BI (and .BR, .RB, etc) are weird little state
# machines that alternate between two fonts. So if someone says something
# like foo<b>chicken</b><b>wicken</b>dicken we have to convert that to
# .BI foo chickenwicken dicken
def _boldline(l):
out = ['']
last_bold = False
for typ,text in l:
nonzero = not not typ
if nonzero != last_bold:
last_bold = not last_bold
out.append('')
out[-1] += re.sub(r'\s+', ' ', text)
macro('.BI', *out)
def do_definition(tag):
w.end_para()
macro('.TP')
w.started = True
split = 0
pre = []
post = []
for typ,text in _bitlist(tag):
if split:
post.append((typ,text))
elif text.lstrip().startswith(': '):
split = 1
post.append((typ,text.lstrip()[2:].lstrip()))
else:
pre.append((typ,text))
_boldline(pre)
w.write(_text(post))
def do_list(tag):
for i in tag:
name = getattr(i, 'name', '').lower()
if not name and not str(i).strip():
pass
elif name != 'li':
raise ValueError('only <li> is allowed inside <ul>: got %r' % i)
else:
w.start_bullet()
for xi in i:
do(xi)
w.end_para()
w.end_bullet()
def do(tag):
name = getattr(tag, 'name', '').lower()
if not name:
text(tag)
elif name == 'h1':
macro('.SH', _force_string(tag, tag).upper())
w.started = True
elif name == 'h2':
macro('.SS', _force_string(tag, tag))
w.started = True
elif name.startswith('h') and len(name)==2:
raise ValueError('%r invalid - man page headers must be h1 or h2'
% name)
elif name == 'pre':
t = _force_string(tag.code, tag.code)
if t.strip():
macro('.RS', '+4n')
macro('.nf')
w.write(_clean(t).rstrip())
macro('.fi')
macro('.RE')
w.end_para()
elif name == 'p' or name == 'br':
g = re.match(re.compile(r'([^\n]*)\n +: +(.*)', re.S), str(tag))
if g:
# it's a definition list (which some versions of python-markdown
# don't support, including the one in Debian-lenny, so we can't
# enable that markdown extension). Fake it up.
do_definition(tag)
else:
text(tag)
w.end_para()
elif name == 'ul':
do_list(tag)
else:
raise ValueError('non-man-compatible html tag %r' % name)
PROD='Untitled'
VENDOR='Vendor Name'
SECTION='9'
GROUPNAME='User Commands'
DATE=''
AUTHOR=''
lines = []
if len(sys.argv) > 1:
for n in sys.argv[1:]:
lines += open(n).read().decode('utf8').split('\n')
else:
lines += sys.stdin.read().decode('utf8').split('\n')
# parse pandoc-style document headers (not part of markdown)
g = re.match(r'^%\s+(.*?)\((.*?)\)\s+(.*)$', lines[0])
if g:
PROD = g.group(1)
SECTION = g.group(2)
VENDOR = g.group(3)
lines.pop(0)
g = re.match(r'^%\s+(.*?)$', lines[0])
if g:
AUTHOR = g.group(1)
lines.pop(0)
g = re.match(r'^%\s+(.*?)$', lines[0])
if g:
DATE = g.group(1)
lines.pop(0)
g = re.match(r'^%\s+(.*?)$', lines[0])
if g:
GROUPNAME = g.group(1)
lines.pop(0)
inp = '\n'.join(lines)
if AUTHOR:
inp += ('\n# AUTHOR\n\n%s\n' % AUTHOR).replace('<', '\\<')
html = markdown.markdown(inp)
soup = BeautifulSoup(html, convertEntities=BeautifulSoup.HTML_ENTITIES)
macro('.TH', PROD.upper(), SECTION, DATE, VENDOR, GROUPNAME)
macro('.ad', 'l') # left justified
macro('.nh') # disable hyphenation
for e in soup:
do(e)

View File

@ -1,6 +1,6 @@
% sshuttle(8) Sshuttle 0.46
% sshuttle(8) Sshuttle %VERSION%
% Avery Pennarun <apenwarr@gmail.com>
% 2011-01-25
% %DATE%
# NAME
@ -71,6 +71,10 @@ entire subnet to the VPN.
are taken automatically from the server's routing
table.
--dns
: capture local DNS requests and forward to the remote DNS
server.
--python
: specify the name/path of the remote python interpreter.
The default is just `python`, which means to use the
@ -90,6 +94,10 @@ entire subnet to the VPN.
`0/0 -x 1.2.3.0/24` to forward everything except the
local subnet over the VPN, for example.
--exclude-from=*file*
: exclude the subnets specified in a file, one subnet per
line. Useful when you have lots of subnets to exclude.
-v, --verbose
: print more information about the session. This option
can be used more than once for increased verbosity. By

View File

@ -63,7 +63,19 @@ This is how you use it:
on your client machine. You'll need root or sudo
access, and python needs to be installed.
- <tt>./sshuttle -r username@sshserver 0.0.0.0/0 -vv</tt>
- The most basic use of sshuttle looks like:
<tt>./sshuttle -r username@sshserver 0.0.0.0/0 -vv</tt>
- There is a shortcut for 0.0.0.0/0 for those that value
their wrists
<tt>./sshuttle -r username@sshserver 0/0 -vv</tt>
- If you would also like your DNS queries to be proxied
through the DNS server of the server you are connect to:
<tt>./sshuttle --dns -vvr username@sshserver 0/0</tt>
The above is probably what you want to use to prevent
local network attacks such as Firesheep and friends.
(You may be prompted for one or more passwords; first, the
local password to become root using either sudo or su, and

4
all.do
View File

@ -1,11 +1,11 @@
exec >&2
UI=
[ "$(uname)" = "Darwin" ] && UI=ui-macos/all
redo-ifchange sshuttle.8 $UI
redo-ifchange Documentation/all version/all $UI
echo
echo "What now?"
[ -z "$UI" ] || echo "- Try the MacOS GUI: open ui-macos/Sshuttle*.app"
echo "- Run sshuttle: ./sshuttle --dns -r HOSTNAME 0/0"
echo "- Read the README: less README.md"
echo "- Read the man page: less sshuttle.md"
echo "- Read the man page: less Documentation/sshuttle.md"

View File

@ -1,2 +1,2 @@
redo ui-macos/clean
redo ui-macos/clean Documentation/clean version/clean
rm -f *~ */*~ .*~ */.*~ *.8 *.tmp */*.tmp *.pyc */*.pyc

127
client.py
View File

@ -171,10 +171,71 @@ class FirewallClient:
def done(self):
self.pfile.close()
rv = self.p.wait()
if rv:
if rv == EXITCODE_NEEDS_REBOOT:
raise FatalNeedsReboot()
elif 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,
dnslistener, seed_hosts, auto_nets,
syslog, daemon):
@ -198,7 +259,14 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
handlers.append(mux)
expected = 'SSHUTTLE0001'
try:
v = 'x'
while v and v != '\0':
v = serversock.recv(1)
v = 'x'
while v and v != '\0':
v = serversock.recv(1)
initstring = serversock.recv(len(expected))
except socket.error, e:
if e.args[0] == errno.ECONNRESET:
@ -248,63 +316,10 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
fw.sethostip(name, ip)
mux.got_host_list = onhostlist
def onaccept():
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))
handlers.append(Handler([listener], lambda: onaccept(listener, mux, handlers)))
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:
handlers.append(Handler([dnslistener], ondns))
handlers.append(Handler([dnslistener], lambda: ondns(dnslistener, mux, handlers)))
if seed_hosts != None:
debug1('seed_hosts: %r\n' % seed_hosts)

View File

@ -1,6 +1,6 @@
exec >&2
if pandoc </dev/null 2>/dev/null; then
pandoc -s -r markdown -w man -o $3 $1.md
pandoc -s -r markdown -w man -o $3 $2.md
else
echo "Warning: pandoc not installed; can't generate manpages."
redo-always

115
do
View File

@ -8,14 +8,14 @@
#
# By default, no output coloring.
GREEN=""
BOLD=""
PLAIN=""
green=""
bold=""
plain=""
if [ -n "$TERM" -a "$TERM" != "dumb" ] && tty <&2 >/dev/null 2>&1; then
GREEN="$(printf '\033[32m')"
BOLD="$(printf '\033[1m')"
PLAIN="$(printf '\033[m')"
green="$(printf '\033[32m')"
bold="$(printf '\033[1m')"
plain="$(printf '\033[m')"
fi
_dirsplit()
@ -24,6 +24,13 @@ _dirsplit()
dir=${1%$base}
}
dirname()
(
_dirsplit "$1"
dir=${dir%/}
echo "${dir:-.}"
)
_dirsplit "$0"
export REDO=$(cd "${dir:-.}" && echo "$PWD/$base")
@ -54,87 +61,105 @@ fi
_find_dofile_pwd()
{
DOFILE=default.$1.do
dofile=default.$1.do
while :; do
DOFILE=default.${DOFILE#default.*.}
[ -e "$DOFILE" -o "$DOFILE" = default.do ] && break
dofile=default.${dofile#default.*.}
[ -e "$dofile" -o "$dofile" = default.do ] && break
done
EXT=${DOFILE#default}
EXT=${EXT%.do}
BASE=${1%$EXT}
ext=${dofile#default}
ext=${ext%.do}
base=${1%$ext}
}
_find_dofile()
{
PREFIX=
local prefix=
while :; do
_find_dofile_pwd "$1"
[ -e "$DOFILE" ] && break
[ -e "$dofile" ] && break
[ "$PWD" = "/" ] && break
TARGET=${PWD##*/}/$TARGET
PREFIX=${PWD##*/}/$PREFIX
target=${PWD##*/}/$target
tmp=${PWD##*/}/$tmp
prefix=${PWD##*/}/$prefix
cd ..
done
BASE=$PREFIX$BASE
base=$prefix$base
}
_run_dofile()
{
export DO_DEPTH="$DO_DEPTH "
export REDO_TARGET=$PWD/$TARGET
export REDO_TARGET=$PWD/$target
local line1
set -e
read line1 <"$PWD/$DOFILE"
read line1 <"$PWD/$dofile"
cmd=${line1#"#!/"}
if [ "$cmd" != "$line1" ]; then
/$cmd "$PWD/$DOFILE" "$@" >"$TARGET.tmp2"
/$cmd "$PWD/$dofile" "$@" >"$tmp.tmp2"
else
. "$PWD/$DOFILE" >"$TARGET.tmp2"
:; . "$PWD/$dofile" >"$tmp.tmp2"
fi
}
_do()
{
DIR=$1
TARGET=$2
if [ ! -e "$TARGET" ] || [ -e "$TARGET/." -a ! -e "$TARGET.did" ]; then
local dir=$1 target=$2 tmp=$3
if [ ! -e "$target" ] || [ -d "$target" -a ! -e "$target.did" ]; then
printf '%sdo %s%s%s%s\n' \
"$GREEN" "$DO_DEPTH" "$BOLD" "$DIR$TARGET" "$PLAIN" >&2
echo "$PWD/$TARGET" >>"$DO_BUILT"
DOFILE=$TARGET.do
BASE=$TARGET
EXT=
[ -e "$TARGET.do" ] || _find_dofile "$TARGET"
if [ ! -e "$DOFILE" ]; then
echo "do: $TARGET: no .do file" >&2
"$green" "$DO_DEPTH" "$bold" "$dir$target" "$plain" >&2
echo "$PWD/$target" >>"$DO_BUILT"
dofile=$target.do
base=$target
ext=
[ -e "$target.do" ] || _find_dofile "$target"
if [ ! -e "$dofile" ]; then
echo "do: $target: no .do file" >&2
return 1
fi
[ ! -e "$DO_BUILD" ] || : >>"$TARGET.did"
( _run_dofile "$BASE" "$EXT" "$TARGET.tmp" )
RV=$?
if [ $RV != 0 ]; then
[ ! -e "$DO_BUILT" ] || [ ! -d "$(dirname "$target")" ] ||
: >>"$target.did"
( _run_dofile "$target" "$base" "$tmp.tmp" )
rv=$?
if [ $rv != 0 ]; then
printf "do: %s%s\n" "$DO_DEPTH" \
"$DIR$TARGET: got exit code $RV" >&2
rm -f "$TARGET.tmp" "$TARGET.tmp2"
return $RV
"$dir$target: got exit code $rv" >&2
rm -f "$tmp.tmp" "$tmp.tmp2"
return $rv
fi
mv "$TARGET.tmp" "$TARGET" 2>/dev/null ||
! test -s "$TARGET.tmp2" ||
mv "$TARGET.tmp2" "$TARGET" 2>/dev/null
rm -f "$TARGET.tmp2"
mv "$tmp.tmp" "$target" 2>/dev/null ||
! test -s "$tmp.tmp2" ||
mv "$tmp.tmp2" "$target" 2>/dev/null
rm -f "$tmp.tmp2"
else
echo "do $DO_DEPTH$TARGET exists." >&2
echo "do $DO_DEPTH$target exists." >&2
fi
}
# Make corrections for directories that don't actually exist yet.
_dir_shovel()
{
local dir base
xdir=$1 xbase=$2 xbasetmp=$2
while [ ! -d "$xdir" -a -n "$xdir" ]; do
_dirsplit "${xdir%/}"
xbasetmp=${base}__$xbase
xdir=$dir xbase=$base/$xbase
echo "xbasetmp='$xbasetmp'" >&2
done
}
redo()
{
for i in "$@"; do
_dirsplit "$i"
( cd "$dir" && _do "$dir" "$base" ) || return 1
_dir_shovel "$dir" "$base"
dir=$xdir base=$xbase basetmp=$xbasetmp
( cd "$dir" && _do "$dir" "$base" "$basetmp" ) || return 1
done
}

View File

@ -1,4 +1,4 @@
import re, errno, socket, select, struct
import re, errno, socket, select, signal, struct
import compat.ssubprocess as ssubprocess
import helpers, ssyslog
from helpers import *
@ -6,6 +6,12 @@ from helpers import *
# python doesn't have a definition for this
IPPROTO_DIVERT = 254
# return values from sysctl_set
SUCCESS = 0
SAME = 1
FAILED = -1
NONEXIST = -2
def nonfatal(func, *args):
try:
@ -14,6 +20,14 @@ def nonfatal(func, *args):
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):
argv = ['iptables', '-t', 'nat', '-nL']
p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
@ -27,10 +41,7 @@ def ipt_chain_exists(name):
def ipt(*args):
argv = ['iptables', '-t', 'nat'] + list(args)
debug1('>> %s\n' % ' '.join(argv))
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
_call(argv)
_no_ttl_module = False
@ -135,6 +146,42 @@ def _fill_oldctls(prefix):
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):
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
debug1('>> %s\n' % ' '.join(argv))
@ -150,20 +197,24 @@ def sysctl_set(name, val, permanent=False):
_fill_oldctls(PREFIX)
if not (name in _oldctls):
debug1('>> No such sysctl: %r\n' % name)
return False
return NONEXIST
oldval = _oldctls[name]
if val != oldval:
rv = _sysctl_set(name, val)
if rv==0 and permanent:
debug1('>> ...saving permanently in /etc/sysctl.conf\n')
f = open('/etc/sysctl.conf', 'a')
f.write('\n'
'# Added by sshuttle\n'
'%s=%s\n' % (name, val))
f.close()
else:
_changedctls.append(name)
return True
if val == oldval:
return SAME
rv = _sysctl_set(name, val)
if rv != 0:
return FAILED
if permanent:
debug1('>> ...saving permanently in /etc/sysctl.conf\n')
f = open('/etc/sysctl.conf', 'a')
f.write('\n'
'# Added by sshuttle\n'
'%s=%s\n' % (name, val))
f.close()
else:
_changedctls.append(name)
return SUCCESS
def _udp_unpack(p):
@ -201,10 +252,7 @@ def _handle_diversion(divertsock, dnsport):
def ipfw(*args):
argv = ['ipfw', '-q'] + list(args)
debug1('>> %s\n' % ' '.join(argv))
rv = ssubprocess.call(argv)
if rv:
raise Fatal('%r returned %d' % (argv, rv))
_call(argv)
def do_ipfw(port, dnsport, subnets):
@ -222,8 +270,14 @@ def do_ipfw(port, dnsport, subnets):
if subnets or dnsport:
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"
" WARNING: ONE-TIME NETWORK DISRUPTION:\n"
" =====================================\n"
@ -234,6 +288,21 @@ def do_ipfw(port, dnsport, subnets):
"ethernet port) NOW, then restart sshuttle. The fix is\n"
"permanent; you only have to do this once.\n\n")
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',
'from', 'any', 'to', 'any')
@ -243,11 +312,11 @@ def do_ipfw(port, dnsport, subnets):
for swidth,sexclude,snet in sorted(subnets, reverse=True):
if sexclude:
ipfw('add', sport, 'skipto', xsport,
'log', 'tcp',
'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth))
else:
ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port,
'log', 'tcp',
'tcp',
'from', 'any', 'to', '%s/%s' % (snet,swidth),
'not', 'ipttl', '42', 'keep-state', 'setup')
@ -289,12 +358,12 @@ def do_ipfw(port, dnsport, subnets):
for ip in nslist:
# relabel and then catch outgoing DNS requests
ipfw('add', sport, 'divert', sport,
'log', 'udp',
'udp',
'from', 'any', 'to', '%s/32' % ip, '53',
'not', 'ipttl', '42')
# relabel DNS responses
ipfw('add', sport, 'divert', sport,
'log', 'udp',
'udp',
'from', 'any', str(dnsport), 'to', 'any',
'not', 'ipttl', '42')
@ -398,6 +467,13 @@ def main(port, dnsport, syslog):
sys.stdout.write('READY\n')
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)
signal.signal(signal.SIGTERM, signal.SIG_IGN)
signal.signal(signal.SIGINT, signal.SIG_IGN)
# ctrl-c shouldn't be passed along to me. When the main sshuttle dies,
# I'll die automatically.
os.setsid()

View File

@ -1,4 +1,4 @@
import sys, os, socket
import sys, os, socket, errno
logprefix = ''
verbose = 0
@ -30,6 +30,11 @@ class Fatal(Exception):
pass
EXITCODE_NEEDS_REBOOT = 111
class FatalNeedsReboot(Fatal):
pass
def list_contains_any(l, sub):
for i in sub:
if i in l:

11
main.py Normal file → Executable file
View File

@ -57,12 +57,14 @@ dns capture local DNS requests and forward to the remote DNS server
python= path to python interpreter on the remote server
r,remote= ssh hostname (and optional username) of remote sshuttle server
x,exclude= exclude this subnet (can be used more than once)
exclude-from= exclude the subnets in a file (whitespace separated)
v,verbose increase debug message verbosity
e,ssh-cmd= the command to use to connect to the remote [ssh]
seed-hosts= with -H, use these hostnames for initial scan (comma-separated)
no-latency-control sacrifice latency to improve bandwidth benchmarks
wrap= restart counting channel numbers after this number (for testing)
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)
pidfile= pidfile name (only if using --daemon) [./sshuttle.pid]
server (internal use only)
@ -72,6 +74,10 @@ hostwatch (internal use only)
o = options.Options(optspec)
(opt, flags, extra) = o.parse(sys.argv[2:])
if opt.version:
import version
print version.TAG
sys.exit(0)
if opt.daemon:
opt.syslog = 1
if opt.wrap:
@ -99,6 +105,8 @@ try:
for k,v in flags:
if k in ('-x','--exclude'):
excludes.append(v)
if k in ('-X', '--exclude-from'):
excludes += open(v).read().split()
remotename = opt.remote
if remotename == '' or remotename == '-':
remotename = None
@ -121,6 +129,9 @@ try:
parse_subnets(includes),
parse_subnets(excludes),
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:
log('fatal: %s\n' % e)
sys.exit(99)

View File

@ -43,7 +43,12 @@ def _maskbits(netmask):
def _shl(n, bits):
return n * int(2**bits)
# we use our own implementation of left-shift because
# results may be different between older and newer versions
# of python for numbers like 1<<32. We use long() because
# int(2**32) doesn't work in older python, which has limited
# int sizes.
return n * long(2**bits)
def _list_routes():
@ -68,9 +73,11 @@ def _list_routes():
def list_routes():
l = []
for (ip,width) in _list_routes():
if not ip.startswith('0.') and not ip.startswith('127.'):
yield (ip,width)
l.append((ip,width))
return l
def _exc_dump():
@ -110,23 +117,51 @@ class DnsProxy(Handler):
def __init__(self, mux, chan, request):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Handler.__init__(self, [sock])
self.sock = sock
self.timeout = time.time()+30
self.mux = mux
self.chan = chan
self.tries = 0
self.peer = None
self.request = request
self.sock = sock
self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
self.sock.connect((resolvconf_random_nameserver(), 53))
self.sock.send(request)
self.try_send()
def try_send(self):
if self.tries >= 3:
return
self.tries += 1
self.peer = resolvconf_random_nameserver()
self.sock.connect((self.peer, 53))
debug2('DNS: sending to %r\n' % self.peer)
try:
self.sock.send(self.request)
except socket.error, e:
if e.args[0] in ssnet.NET_ERRS:
# might have been spurious; try again.
# Note: these errors sometimes are reported by recv(),
# and sometimes by send(). We have to catch both.
debug2('DNS send to %r: %s\n' % (self.peer, e))
self.try_send()
return
else:
log('DNS send to %r: %s\n' % (self.peer, e))
return
def callback(self):
try:
data = self.sock.recv(4096)
except socket.error, e:
if e.args[0] == errno.ECONNREFUSED:
debug2('DNS response: ignoring ECONNREFUSED.\n')
return # might have been spurious; wait for a real answer
if e.args[0] in ssnet.NET_ERRS:
# might have been spurious; try again.
# Note: these errors sometimes are reported by recv(),
# and sometimes by send(). We have to catch both.
debug2('DNS recv from %r: %s\n' % (self.peer, e))
self.try_send()
return
else:
raise
log('DNS recv from %r: %s\n' % (self.peer, e))
return
debug2('DNS response: %d bytes\n' % len(data))
self.mux.send(self.chan, ssnet.CMD_DNS_RESPONSE, data)
self.ok = False
@ -145,7 +180,7 @@ def main():
debug1(' %s/%d\n' % r)
# synchronization header
sys.stdout.write('SSHUTTLE0001')
sys.stdout.write('\0\0SSHUTTLE0001')
sys.stdout.flush()
handlers = []

2
ssh.py
View File

@ -85,7 +85,7 @@ def connect(ssh_cmd, rhostport, python, stderr, options):
pycmd = "'%s' -c '%s'" % (python, pyscript)
else:
pycmd = ("P=python2; $P -V 2>/dev/null || P=python; "
"\"$P\" -c '%s'") % pyscript
"exec \"$P\" -c '%s'") % pyscript
argv = (sshl +
portl +
ipv6flag +

View File

@ -1,5 +1,10 @@
#!/bin/sh
DIR=$(dirname "$0")
EXE=$0
for i in 1 2 3 4 5 6 7 8 9 10; do
[ -L "$EXE" ] || break
EXE=$(readlink "$EXE")
done
DIR=$(dirname "$EXE")
if python2 -V 2>/dev/null; then
exec python2 "$DIR/main.py" python2 "$@"
else

View File

@ -40,7 +40,11 @@ cmd_to_name = {
CMD_DNS_REQ: 'DNS_REQ',
CMD_DNS_RESPONSE: 'DNS_RESPONSE',
}
NET_ERRS = [errno.ECONNREFUSED, errno.ETIMEDOUT,
errno.EHOSTUNREACH, errno.ENETUNREACH,
errno.EHOSTDOWN, errno.ENETDOWN]
def _add(l, elem):
@ -86,7 +90,7 @@ class SockWrapper:
def __init__(self, rsock, wsock, connect_to=None, peername=None):
global _swcount
_swcount += 1
debug3('creating new SockWrapper (%d now exist\n)' % _swcount)
debug3('creating new SockWrapper (%d now exist)\n' % _swcount)
self.exc = None
self.rsock = rsock
self.wsock = wsock
@ -101,7 +105,7 @@ class SockWrapper:
_swcount -= 1
debug1('%r: deleting (%d remain)\n' % (self, _swcount))
if self.exc:
debug1('%r: error was: %r\n' % (self, self.exc))
debug1('%r: error was: %s\n' % (self, self.exc))
def __repr__(self):
if self.rsock == self.wsock:
@ -124,20 +128,45 @@ class SockWrapper:
return # already connected
self.rsock.setblocking(False)
debug3('%r: trying connect to %r\n' % (self, self.connect_to))
if socket.inet_aton(self.connect_to[0])[0] == '\0':
self.seterr(Exception("Can't connect to %r: "
"IP address starts with zero\n"
% (self.connect_to,)))
self.connect_to = None
return
try:
self.rsock.connect(self.connect_to)
# connected successfully (Linux)
self.connect_to = None
except socket.error, e:
debug3('%r: connect result: %r\n' % (self, e))
debug3('%r: connect result: %s\n' % (self, e))
if e.args[0] == errno.EINVAL:
# this is what happens when you call connect() on a socket
# that is now connected but returned EINPROGRESS last time,
# on BSD, on python pre-2.5.1. We need to use getsockopt()
# to get the "real" error. Later pythons do this
# automatically, so this code won't run.
realerr = self.rsock.getsockopt(socket.SOL_SOCKET,
socket.SO_ERROR)
e = socket.error(realerr, os.strerror(realerr))
debug3('%r: fixed connect result: %s\n' % (self, e))
if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
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:
# connected successfully (BSD)
self.connect_to = None
elif e.args[0] in [errno.ECONNREFUSED, errno.ETIMEDOUT,
errno.EHOSTUNREACH, errno.ENETUNREACH,
errno.EACCES, errno.EPERM]:
elif e.args[0] in NET_ERRS + [errno.EACCES, errno.EPERM]:
# a "normal" kind of error
self.connect_to = None
self.seterr(e)

View File

@ -1,5 +1,17 @@
exec >&2
redo-ifchange runpython.c
gcc -Wall -o $3 runpython.c \
ARCHES=""
printf "Platforms: "
if [ -d /usr/libexec/gcc/darwin ]; then
for d in /usr/libexec/gcc/darwin/*; do
PLAT=$(basename "$d")
[ "$PLAT" != "ppc64" ] || continue # fails for some reason on my Mac
ARCHES="$ARCHES -arch $PLAT"
printf "$PLAT "
done
fi
printf "\n"
gcc $ARCHES \
-Wall -o $3 runpython.c \
-I/usr/include/python2.5 \
-lpython2.5

View File

@ -3,9 +3,9 @@ redo-ifchange sources.list
redo-ifchange Info.plist bits/runpython \
$(while read name newname; do echo "$name"; done <sources.list)
rm -rf "$1.app"
mkdir "$1.app" "$1.app/Contents"
cd "$1.app/Contents"
rm -rf "$2.app"
mkdir "$2.app" "$2.app/Contents"
cd "$2.app/Contents"
cp "$TOP/Info.plist" .
@ -18,11 +18,11 @@ cd "$TOP"
while read name newname; do
[ -z "$name" ] && continue
: "${newname:=$name}"
outname=$1.app/Contents/Resources/$newname
outname=$2.app/Contents/Resources/$newname
outdir=$(dirname "$outname")
[ -d "$outdir" ] || mkdir "$outdir"
cp "${name-$newname}" "$outname"
done <sources.list
cd "$1.app"
cd "$2.app"
redo-ifchange $(find . -type f)

View File

@ -1,5 +1,5 @@
exec >&2
IFS="
"
redo-ifchange $1.app
tar -czf $3 $1.app/
redo-ifchange $2.app
tar -czf $3 $2.app/

View File

@ -1,5 +1,5 @@
exec >&2
IFS="
"
redo-ifchange $1.app
zip -q -r $3 $1.app/
redo-ifchange $2.app
zip -q -r $3 $2.app/

View File

@ -1,2 +1,2 @@
redo-ifchange $1.xib
ibtool --compile $3 $1.xib
redo-ifchange $2.xib
ibtool --compile $3 $2.xib

View File

@ -78,6 +78,11 @@ class Runner:
if pid == self.pid:
if os.WIFEXITED(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:
self.rv = -os.WSTOPSIG(code)
self.serverobj.setConnected_(False)
@ -87,7 +92,10 @@ class Runner:
return self.rv
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):
return self._try_wait(os.WNOHANG)

View File

@ -3,6 +3,7 @@ import my
configchange_callback = setconnect_callback = None
objc_validator = objc.signature('@@:N^@o^@')
def config_changed():
@ -39,7 +40,7 @@ class SshuttleNet(NSObject):
def setSubnet_(self, v):
self._k_subnet = v
config_changed()
@objc.accessor
@objc_validator
def validateSubnet_error_(self, value, error):
#print 'validateSubnet!'
return True, _validate_ip(value), error
@ -49,7 +50,7 @@ class SshuttleNet(NSObject):
def setWidth_(self, v):
self._k_width = v
config_changed()
@objc.accessor
@objc_validator
def validateWidth_error_(self, value, error):
#print 'validateWidth!'
return True, _validate_width(value), error
@ -118,7 +119,7 @@ class SshuttleServer(NSObject):
self._k_host = v
self.setTitle_(None)
config_changed()
@objc.accessor
@objc_validator
def validateHost_error_(self, value, error):
#print 'validatehost! %r %r %r' % (self, value, error)
while value.startswith('-'):

1
version/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
gitvars.pre export-subst

3
version/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/vars
/gitvars
/_version.py

1
version/__init__.py Normal file
View File

@ -0,0 +1 @@
from _version import COMMIT, TAG, DATE

3
version/_version.py.do Normal file
View File

@ -0,0 +1,3 @@
redo-ifchange vars
cat vars

2
version/all.do Normal file
View File

@ -0,0 +1,2 @@
redo-ifchange vars _version.py

3
version/clean.do Normal file
View File

@ -0,0 +1,3 @@
rm -f *~ .*~ *.pyc _version.py vars gitvars

28
version/gitvars.do Normal file
View File

@ -0,0 +1,28 @@
redo-ifchange gitvars.pre prodname
read PROD <prodname
exec >$3
# Fix each line from gitvars.pre where git may or may not have already
# substituted the variables. If someone generated a tarball with 'git archive',
# then the data will have been substituted already. If we're in a checkout of
# the git repo, then it won't, but we can just ask git to do the substitutions
# right now.
while read line; do
# Lines *may* be of the form: $Format: ... $
x=${line#\$Format:} # remove prefix
if [ "$x" != "$line" ]; then
# git didn't substitute it
redo-always # git this from the git repo
x=${x%\$} # remove trailing $
if [ "$x" = "%d" ]; then
tag=$(git describe --match="$PROD-*")
x="(tag: $tag)"
else
x=$(git log -1 --pretty=format:"$x")
fi
fi
echo "$x"
done <gitvars.pre
redo-stamp <$3

3
version/gitvars.pre Normal file
View File

@ -0,0 +1,3 @@
$Format:%H$
$Format:%d$
$Format:%ci$

1
version/prodname Normal file
View File

@ -0,0 +1 @@
sshuttle

40
version/vars.do Normal file
View File

@ -0,0 +1,40 @@
redo-ifchange gitvars prodname
read PROD <prodname
exec <gitvars
read COMMIT
read NAMES
read DATE
# the list of names is of the form:
# (x,y,tag: $PROD-####,tag: $PROD-####,a,b)
# The entries we want are the ones starting with "tag: $PROD-" since those
# refer to the right actual git tags.
names_to_tag()
{
x=${1#\(}
x=${x%\)}
cur=
while [ "$cur" != "$x" ]; do
x=${x# }
x=${x#tag: }
cur=${x%%,*}
tagpost=${cur#$PROD-}
if [ "$cur" != "$tagpost" ]; then
echo "$tagpost"
return 0
fi
x=${x#*,}
done
commitpost=${COMMIT#???????}
commitpre=${COMMIT%$commitpost}
echo "unknown-$commitpre"
}
sTAG=$(names_to_tag "$NAMES")
echo "COMMIT='$COMMIT'"
echo "TAG='$sTAG'"
echo "DATE='${DATE%% *}'"