Based on suggestions by Jason Grossman and Ed Maste on the mailing list.
We now add a [local su] prefix to the 'su' password prompt (by cheating and
printing it before calling su), and we replace the 'sudo' password prompt
with '[local sudo] Password: ' (by using the little-known and
hopefully-portable -p option).
We no longer call sudo or su if the uid is already 0; otherwise the prefix
on the 'su' prompt would look weird, since su wouldn't ask for a password in
that case.
We don't add a prefix to the ssh password prompt, because it's too hard to
tell if there will *be* an ssh password prompt. But people will probably
assume that the password request is for the server anyway; few people are
likely to think that 'sshuttle -r myhost.com' is going to prompt for the
*local* password.
Of course none of this is a problem on a modern OS, like Debian, that would
say something like "Password for apenwarr@myhost.com:" instead of just
"Password:". MacOS doesn't do that, however, so I assume many other OSes
also don't. Let's try to help them out.
Instead, get a list of known sysctls in the interesting prefix (net.inet.ip)
and check if there's an entry in the list for each sysctl we want to change.
If there isn't, then don't try to change it.
This fixes a problem with FreeBSD, which doesn't have
net.inet.ip.scopedroute but also doesn't need it. Probably also fixes MacOS
10.5, which probably didn't have that either, but I don't know for sure.
Reported by Ed Maste.
'del' is an abbreviation that happened to work because of substring matching
in earlier versions of ipfw, but apparently they're planning to remove the
substring matching eventually. In any case, 'delete' has always worked, so
there's no downside to using that.
Reported by Ed Maste.
If hostwatch has a lot of stuff to say all at once, it would come in more
than one recv() packet, and server.py would send each packet individually as
a CMD_HOST_LIST message. Unfortunately, client.py (rightly) expects each
CMD_HOST_LIST message to be complete, ie. a correct sequence of rows.
So now server.py makes sure of this. If there's a leftover bit (ie. an
unterminated line), it saves it for later.
Bug reported by user "Duke" on the mailing list.
* python23:
Oops, missed another << operator to replace with _shl().
socket.SHUT_RD and socket.SHUT_WR don't exist in python 2.3.
compat/ssubprocess.py: some python versions don't have os.closerange().
_nb_clean: don't catch EPIPE after all.
Fix busy-waiting in two situations:
Factor out common mainloop code between client and server.
Implement our own left-shift operator to shut up python 2.3 warnings.
Don't use set() since it's not in python 2.3.
import and use subprocess.py from python 2.6.
Remove list comprehensions for python 2.3 compatibility.
Mercifully, socket.socket.shutdown() still does, but it uses hardcoded
integer parameters - and the integers correspond to the SHUT_RD and SHUT_WR
definitions in later versions - so let's just hardcode them ourselves.
See the carnage for yourself:
http://docs.python.org/release/2.3.5/lib/socket-objects.html
Like python2.5 on Debian. It might be a MacOS extension or something. So
much for the comment in subprocess.py that said "keep this compatible with
python 2.2."
EPIPE is a serious error from these places, so we have to actually do
something. Otherwise the client ends up busy waiting when the server
disconnects by surprise.
Bug noticed in a log from Chetan Kunte.
- If you tried to connect to a server that didn't exist, then disconnected
the client during the 60-second connection timeout, the server would
busy wait for 60 seconds.
- If you connected to a server and then sent data, but then the server
disconnected before reading all your data, the server would busy wait.
(example: yes | telnet servername 80)
Apparently left-shift in python 2.3 just *always* prints a warning, even if
we weren't doing anything wrong. Or maybe it only prints the warning
sometimes. Anyway, let's just multiply by 2**x instead of using <<x, since
we're not performance-sensitive anyway.
If your machine is a firewall/router, it affects whether people behind the
router can use your sshuttle connection - in the same way that it affects
whether they can route *anything* through you. And thus, it should be set
by the admin, not by sshuttle.
sshuttle works fine for the local user either way.
(This also affects MacOS since it's a BSD variant.)
It turns out 'established' doesn't work the way I expected it to from
iptables; it's not stateful. It just checks the TCP flags to see if the
connection *thinks* it's already established, and follows the rule if so.
That caused the first packet of each new connection to set sent to our
transproxy, but not the subsequent ones, so weird stuff happened.
With this change, any (matching) connection created *after* starting sshuttle
will get forwarded, but pre-existing ones - most importantly, sshuttle's own
ssh connection - will not.
And with this (plus the previous commit), sshuttle works on MacOS, including
10.6!
If you 'telnet localhost 12300' weird things happen; someday we should
probably auto-detect and avoid that altogether. But meanwhile, catch EPIPE
if it happens (it's irrelevant) and don't barf with a %d data type for a
value that can apparently sometimes be None.
It comes down to this:
sysctl_set('net.inet.ip.scopedroute', 0)
I say "mostly" because actually it doesn't fix it; sshuttle doesn't know
what to do with the received connection, so there must be a minor bug
remaining somewhere. I'll fix that next.
Thanks to dkf <dfortunato@gmail.com> on the sshuttle mailing list for
suggesting the magic fix. He points at this post in particular:
http://discussions.apple.com/thread.jspa?messageID=11558355�
that gave him the necessary clue.
If the ~/.sshuttle.hosts file does not exist, it triggers the following
error:
Traceback (most recent call last):
File "./sshuttle", line 80, in <module>
sys.exit(hostwatch.hw_main(extra))
File "/home/def/p/sshuttle/hostwatch.py", line 246, in hw_main
read_host_cache()
File "/home/def/p/sshuttle/hostwatch.py", line 41, in read_host_cache
if e.errno == errno.ENOENT:
NameError: global name 'errno' is not defined
(This only happened if you run 'sshuttle --hostwatch' from the command line
directly, without passing it through assembler.py.)
That only really stops --auto-nets from working; it's mostly harmless
otherwise. And apparently some locked-down shared hosts don't let you get
the list of routes.
Also, add 127.0.0.0/8 to the default list of excludes. If you want to route
0/0, you almost certainly *don't* want to route localhost to the remote ssh
server's localhost!
Thanks to Edward for the suggestion.
Failing to write to the log sucks, but not as much as failing to clean up
just because stderr disappeared. So let's catch any IOError exception from
log() and just ignore it.
This should fix a problem reported by Camille Moncelier, which is that
sshuttle firewall entries stick around if your tty dies strangely (eg. your
X server aborts for some reason).
Search the entire python sys.path, not just the directory that argv[0] is
in. That way if you symlink the sshuttle binary into (for example) ~/bin,
it'll be able to work correctly.
Pointed out by nisc on github. If people use an unusual umask or have funny
permissions on /etc/hosts, sshuttle would screw it up.
We also use hardlinks to atomically backup the original /etc/hosts to
/etc/hosts.sbak the first time, rather than manually copying it. Not sure
why I didn't think of that before.
Now if you use --auto-hosts (-H), the client will ask the server to spawn a
hostwatcher to add names. That, in turn, will send names back to the
server, which sends them back to the client, which sends them to the
firewall subprocess, which will write them to /etc/hosts. Whew!
Only the firewall process can write to /etc/hosts, of course, because only
he's running as root.
Since the name discovery process is kind of slow, we cache the names in
~/.sshuttle.hosts on the remote server.
Right now, most of the names are discovered using nmblookup and smbclient,
as well as by reading the existing entries in /etc/hosts. What would really
be nice would be to query active directory or mdns somehow... but I don't
really know how those work, so this is what you get for now :) It's pretty
neat, at least.
With this rule, we don't interfere with already-established (or incoming)
connections to routes that we're about to take over. This is what
happens by default in Linux/iptables.
Now if you do
./sshuttle -Nr username@myservername
It'll automatically route the "local" subnets (ie., stuff in the routing
table) from myservername. This is (hopefully a reasonable default setting
for most people.
Instead, grab our source code, send it over the link, and have python eval
it and then start the server.py main() function.
Strangely, there's now *less* horrible stuff in ssh.py, because we no longer
have to munge around with the PATH environment variable. And this
significantly reduces the setup required to get sshuttle going.
Based on a suggestion from Wayne Scott.
Basic forwarding now works on MacOS, assuming you set up ipfw correctly
(ha ha). I wasted a few hours today trying to figure this out, and I'm *so
very close*, but unfortunately it just didn't work. Think you can figure it
out?
Related changes:
- don't die if iptables is unavailable
- BSD uses getsockname() instead of SO_ORIGINAL_DST
- non-blocking connect() returns EISCONN once it's connected
- you can't setsockopt IP_TTL more than once