summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvery Pennarun <apenwarr@gmail.com>2011-01-22 16:52:07 -0800
committerAvery Pennarun <apenwarr@gmail.com>2011-01-22 16:52:07 -0800
commit34ea1ed8b7ec3188af378100dc43616e8d304856 (patch)
tree5ed2c48454902a95a95944fa4ba1e84e6811862e
MacOS precompiled app package for sshuttle-0.45sshuttle-0.45-macos-bin
-rw-r--r--Sshuttle VPN.app/Contents/Info.plist40
-rwxr-xr-xSshuttle VPN.app/Contents/MacOS/Sshuttlebin0 -> 8896 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nibbin0 -> 27923 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/UserDefaults.plist10
-rw-r--r--Sshuttle VPN.app/Contents/Resources/app.icnsbin0 -> 110343 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/askpass.py28
-rw-r--r--Sshuttle VPN.app/Contents/Resources/chicken-tiny-bw.pngbin0 -> 821 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/chicken-tiny-err.pngbin0 -> 789 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/chicken-tiny.pngbin0 -> 810 bytes
-rw-r--r--Sshuttle VPN.app/Contents/Resources/main.py352
-rw-r--r--Sshuttle VPN.app/Contents/Resources/models.py131
-rw-r--r--Sshuttle VPN.app/Contents/Resources/my.py62
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/assembler.py26
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/client.py356
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/compat/__init__.py0
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/compat/ssubprocess.py1305
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/firewall.py304
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/helpers.py37
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/hostwatch.py277
-rwxr-xr-xSshuttle VPN.app/Contents/Resources/sshuttle/main.py122
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/options.py201
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/server.py176
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/ssh.py95
-rwxr-xr-xSshuttle VPN.app/Contents/Resources/sshuttle/sshuttle122
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/ssnet.py520
-rw-r--r--Sshuttle VPN.app/Contents/Resources/sshuttle/ssyslog.py16
-rw-r--r--Sshuttle VPN.app/Contents/Resources/stupid.py14
27 files changed, 4194 insertions, 0 deletions
diff --git a/Sshuttle VPN.app/Contents/Info.plist b/Sshuttle VPN.app/Contents/Info.plist
new file mode 100644
index 0000000..b495531
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Info.plist
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleDisplayName</key>
+ <string>Sshuttle VPN</string>
+ <key>CFBundleExecutable</key>
+ <string>Sshuttle</string>
+ <key>CFBundleIconFile</key>
+ <string>app.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>ca.apenwarr.Sshuttle</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>Sshuttle VPN</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>0.0.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>0.0.0</string>
+ <key>LSUIElement</key>
+ <string>1</string>
+ <key>LSHasLocalizedDisplayName</key>
+ <false/>
+ <key>NSAppleScriptEnabled</key>
+ <false/>
+ <key>NSHumanReadableCopyright</key>
+ <string>GNU LGPL Version 2</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/Sshuttle VPN.app/Contents/MacOS/Sshuttle b/Sshuttle VPN.app/Contents/MacOS/Sshuttle
new file mode 100755
index 0000000..76f25e2
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/MacOS/Sshuttle
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib b/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib
new file mode 100644
index 0000000..302f9fb
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/UserDefaults.plist b/Sshuttle VPN.app/Contents/Resources/UserDefaults.plist
new file mode 100644
index 0000000..7467434
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/UserDefaults.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>startAtLogin</key>
+ <false/>
+ <key>autoReconnect</key>
+ <true/>
+</dict>
+</plist>
diff --git a/Sshuttle VPN.app/Contents/Resources/app.icns b/Sshuttle VPN.app/Contents/Resources/app.icns
new file mode 100644
index 0000000..620b977
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/app.icns
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/askpass.py b/Sshuttle VPN.app/Contents/Resources/askpass.py
new file mode 100644
index 0000000..9690c0d
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/askpass.py
@@ -0,0 +1,28 @@
+import sys, os, re, subprocess
+
+def askpass(prompt):
+ prompt = prompt.replace('"', "'")
+
+ if 'yes/no' in prompt:
+ return "yes"
+
+ script="""
+ tell application "Finder"
+ activate
+ display dialog "%s" \
+ with title "Sshuttle SSH Connection" \
+ default answer "" \
+ with icon caution \
+ with hidden answer
+ end tell
+ """ % prompt
+
+ p = subprocess.Popen(['osascript', '-e', script], stdout=subprocess.PIPE)
+ out = p.stdout.read()
+ rv = p.wait()
+ if rv:
+ return None
+ g = re.match("text returned:(.*), button returned:.*", out)
+ if not g:
+ return None
+ return g.group(1)
diff --git a/Sshuttle VPN.app/Contents/Resources/chicken-tiny-bw.png b/Sshuttle VPN.app/Contents/Resources/chicken-tiny-bw.png
new file mode 100644
index 0000000..7ef418d
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/chicken-tiny-bw.png
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/chicken-tiny-err.png b/Sshuttle VPN.app/Contents/Resources/chicken-tiny-err.png
new file mode 100644
index 0000000..2984513
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/chicken-tiny-err.png
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/chicken-tiny.png b/Sshuttle VPN.app/Contents/Resources/chicken-tiny.png
new file mode 100644
index 0000000..b1d9ab0
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/chicken-tiny.png
Binary files differ
diff --git a/Sshuttle VPN.app/Contents/Resources/main.py b/Sshuttle VPN.app/Contents/Resources/main.py
new file mode 100644
index 0000000..baa290d
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/main.py
@@ -0,0 +1,352 @@
+import sys, os, pty
+from AppKit import *
+import my, models, askpass
+
+def sshuttle_args(host, auto_nets, auto_hosts, nets, debug):
+ argv = [my.bundle_path('sshuttle/sshuttle', ''), '-r', host]
+ assert(argv[0])
+ if debug:
+ argv.append('-v')
+ if auto_nets:
+ argv.append('--auto-nets')
+ if auto_hosts:
+ argv.append('--auto-hosts')
+ argv += nets
+ return argv
+
+
+class _Callback(NSObject):
+ def initWithFunc_(self, func):
+ self = super(_Callback, self).init()
+ self.func = func
+ return self
+ def func_(self, obj):
+ return self.func(obj)
+
+
+class Callback:
+ def __init__(self, func):
+ self.obj = _Callback.alloc().initWithFunc_(func)
+ self.sel = self.obj.func_
+
+
+class Runner:
+ def __init__(self, argv, logfunc, promptfunc, serverobj):
+ print 'in __init__'
+ self.id = argv
+ self.rv = None
+ self.pid = None
+ self.fd = None
+ self.logfunc = logfunc
+ self.promptfunc = promptfunc
+ self.serverobj = serverobj
+ self.buf = ''
+ self.logfunc('\nConnecting to %s.\n' % self.serverobj.host())
+ print 'will run: %r' % argv
+ self.serverobj.setConnected_(False)
+ pid,fd = pty.fork()
+ if pid == 0:
+ # child
+ try:
+ os.execvp(argv[0], argv)
+ except Exception, e:
+ sys.stderr.write('failed to start: %r\n' % e)
+ raise
+ finally:
+ os._exit(42)
+ # parent
+ self.pid = pid
+ self.file = NSFileHandle.alloc()\
+ .initWithFileDescriptor_closeOnDealloc_(fd, True)
+ self.cb = Callback(self.gotdata)
+ NSNotificationCenter.defaultCenter()\
+ .addObserver_selector_name_object_(self.cb.obj, self.cb.sel,
+ NSFileHandleDataAvailableNotification, self.file)
+ self.file.waitForDataInBackgroundAndNotify()
+
+ def __del__(self):
+ self.wait()
+
+ def _try_wait(self, options):
+ if self.rv == None and self.pid > 0:
+ pid,code = os.waitpid(self.pid, options)
+ if pid == self.pid:
+ if os.WIFEXITED(code):
+ self.rv = os.WEXITSTATUS(code)
+ else:
+ self.rv = -os.WSTOPSIG(code)
+ self.serverobj.setConnected_(False)
+ self.serverobj.setError_('VPN process died')
+ self.logfunc('Disconnected.\n')
+ print 'wait_result: %r' % self.rv
+ return self.rv
+
+ def wait(self):
+ return self._try_wait(0)
+
+ def poll(self):
+ return self._try_wait(os.WNOHANG)
+
+ def kill(self):
+ assert(self.pid > 0)
+ print 'killing: pid=%r rv=%r' % (self.pid, self.rv)
+ if self.rv == None:
+ self.logfunc('Disconnecting from %s.\n' % self.serverobj.host())
+ os.kill(self.pid, 15)
+ self.wait()
+
+ def gotdata(self, notification):
+ print 'gotdata!'
+ d = str(self.file.availableData())
+ if d:
+ self.logfunc(d)
+ self.buf = self.buf + d
+ if 'Connected.\r\n' in self.buf:
+ self.serverobj.setConnected_(True)
+ self.buf = self.buf[-4096:]
+ if self.buf.strip().endswith(':'):
+ lastline = self.buf.rstrip().split('\n')[-1]
+ resp = self.promptfunc(lastline)
+ add = ' (response)\n'
+ self.buf += add
+ self.logfunc(add)
+ self.file.writeData_(my.Data(resp + '\n'))
+ self.file.waitForDataInBackgroundAndNotify()
+ self.poll()
+ #print 'gotdata done!'
+
+
+class SshuttleApp(NSObject):
+ def initialize(self):
+ d = my.PList('UserDefaults')
+ my.Defaults().registerDefaults_(d)
+
+
+class SshuttleController(NSObject):
+ # Interface builder outlets
+ startAtLoginField = objc.IBOutlet()
+ autoReconnectField = objc.IBOutlet()
+ debugField = objc.IBOutlet()
+ routingField = objc.IBOutlet()
+ prefsWindow = objc.IBOutlet()
+ serversController = objc.IBOutlet()
+ logField = objc.IBOutlet()
+
+ servers = []
+ conns = {}
+
+ def _connect(self, server):
+ host = server.host()
+ print 'connecting %r' % host
+ self.fill_menu()
+ def logfunc(msg):
+ print 'log! (%d bytes)' % len(msg)
+ self.logField.textStorage()\
+ .appendAttributedString_(NSAttributedString.alloc()\
+ .initWithString_(msg))
+ self.logField.didChangeText()
+ def promptfunc(prompt):
+ print 'prompt! %r' % prompt
+ return askpass.askpass(prompt)
+ nets_mode = server.autoNets()
+ if nets_mode == models.NET_MANUAL:
+ manual_nets = ["%s/%d" % (i.subnet(), i.width())
+ for i in server.nets()]
+ elif nets_mode == models.NET_ALL:
+ manual_nets = ['0/0']
+ else:
+ manual_nets = []
+ conn = Runner(sshuttle_args(host,
+ auto_nets = nets_mode == models.NET_AUTO,
+ auto_hosts = server.autoHosts(),
+ nets = manual_nets,
+ debug = self.debugField.state()),
+ logfunc=logfunc, promptfunc=promptfunc,
+ serverobj=server)
+ self.conns[host] = conn
+
+ def _disconnect(self, server):
+ host = server.host()
+ print 'disconnecting %r' % host
+ conn = self.conns.get(host)
+ if conn:
+ conn.kill()
+ self.fill_menu()
+ self.logField.textStorage().setAttributedString_(
+ NSAttributedString.alloc().initWithString_(''))
+
+ @objc.IBAction
+ def cmd_connect(self, sender):
+ server = sender.representedObject()
+ server.setWantConnect_(True)
+
+ @objc.IBAction
+ def cmd_disconnect(self, sender):
+ server = sender.representedObject()
+ server.setWantConnect_(False)
+
+ @objc.IBAction
+ def cmd_show(self, sender):
+ self.prefsWindow.makeKeyAndOrderFront_(self)
+ NSApp.activateIgnoringOtherApps_(True)
+
+ @objc.IBAction
+ def cmd_quit(self, sender):
+ NSApp.performSelector_withObject_afterDelay_(NSApp.terminate_,
+ None, 0.0)
+
+ def fill_menu(self):
+ menu = self.menu
+ menu.removeAllItems()
+
+ def additem(name, func, obj):
+ it = menu.addItemWithTitle_action_keyEquivalent_(name, None, "")
+ it.setRepresentedObject_(obj)
+ it.setTarget_(self)
+ it.setAction_(func)
+ def addnote(name):
+ additem(name, None, None)
+
+ any_inprogress = None
+ any_conn = None
+ any_err = None
+ if len(self.servers):
+ for i in self.servers:
+ host = i.host()
+ want = i.wantConnect()
+ connected = i.connected()
+ numnets = len(list(i.nets()))
+ if not host:
+ additem('Connect Untitled', None, i)
+ elif i.autoNets() == models.NET_MANUAL and not numnets:
+ additem('Connect %s (no routes)' % host, None, i)
+ elif want:
+ any_conn = i
+ additem('Disconnect %s' % host, self.cmd_disconnect, i)
+ else:
+ additem('Connect %s' % host, self.cmd_connect, i)
+ if not want:
+ msg = 'Off'
+ elif i.error():
+ msg = 'ERROR - try reconnecting'
+ any_err = i
+ elif connected:
+ msg = 'Connected'
+ else:
+ msg = 'Connecting...'
+ any_inprogress = i
+ addnote(' State: %s' % msg)
+ if i.autoNets() == 0:
+ addnote(' Routes: All')
+ elif i.autoNets() == 2:
+ addnote(' Routes: Auto')
+ else:
+ addnote(' Routes: Custom')
+ else:
+ addnote('No servers defined yet')
+
+ menu.addItem_(NSMenuItem.separatorItem())
+ additem('Preferences...', self.cmd_show, None)
+ additem('Quit Sshuttle VPN', self.cmd_quit, None)
+
+ if any_err:
+ self.statusitem.setImage_(self.img_err)
+ self.statusitem.setTitle_('Error!')
+ elif any_conn:
+ self.statusitem.setImage_(self.img_running)
+ if any_inprogress:
+ self.statusitem.setTitle_('Connecting...')
+ else:
+ self.statusitem.setTitle_('')
+ else:
+ self.statusitem.setImage_(self.img_idle)
+ self.statusitem.setTitle_('')
+
+ def load_servers(self):
+ l = my.Defaults().arrayForKey_('servers') or []
+ sl = []
+ for s in l:
+ host = s.get('host', None)
+ if not host: continue
+
+ nets = s.get('nets', [])
+ nl = []
+ for n in nets:
+ subnet = n[0]
+ width = n[1]
+ net = models.SshuttleNet.alloc().init()
+ net.setSubnet_(subnet)
+ net.setWidth_(width)
+ nl.append(net)
+
+ autoNets = s.get('autoNets', 1)
+ autoHosts = s.get('autoHosts', 1)
+ srv = models.SshuttleServer.alloc().init()
+ srv.setHost_(host)
+ srv.setAutoNets_(autoNets)
+ srv.setAutoHosts_(autoHosts)
+ srv.setNets_(nl)
+ sl.append(srv)
+ self.serversController.addObjects_(sl)
+ self.serversController.setSelectionIndex_(0)
+
+ def save_servers(self):
+ l = []
+ for s in self.servers:
+ host = s.host()
+ if not host: continue
+ nets = []
+ for n in s.nets():
+ subnet = n.subnet()
+ if not subnet: continue
+ nets.append((subnet, n.width()))
+ d = dict(host=s.host(),
+ nets=nets,
+ autoNets=s.autoNets(),
+ autoHosts=s.autoHosts())
+ l.append(d)
+ my.Defaults().setObject_forKey_(l, 'servers')
+ self.fill_menu()
+
+ def awakeFromNib(self):
+ self.routingField.removeAllItems()
+ tf = self.routingField.addItemWithTitle_
+ tf('Send all traffic through this server')
+ tf('Determine automatically')
+ tf('Custom...')
+
+ # Hmm, even when I mark this as !enabled in the .nib, it still comes
+ # through as enabled. So let's just disable it here (since we don't
+ # support this feature yet).
+ self.startAtLoginField.setEnabled_(False)
+ self.startAtLoginField.setState_(False)
+ self.autoReconnectField.setEnabled_(False)
+ self.autoReconnectField.setState_(False)
+
+ self.load_servers()
+
+ # Initialize our menu item
+ self.menu = NSMenu.alloc().initWithTitle_('Sshuttle')
+ bar = NSStatusBar.systemStatusBar()
+ statusitem = bar.statusItemWithLength_(NSVariableStatusItemLength)
+ self.statusitem = statusitem
+ self.img_idle = my.Image('chicken-tiny-bw', 'png')
+ self.img_running = my.Image('chicken-tiny', 'png')
+ self.img_err = my.Image('chicken-tiny-err', 'png')
+ statusitem.setImage_(self.img_idle)
+ statusitem.setHighlightMode_(True)
+ statusitem.setMenu_(self.menu)
+ self.fill_menu()
+
+ models.configchange_callback = my.DelayedCallback(self.save_servers)
+
+ def sc(server):
+ if server.wantConnect():
+ self._connect(server)
+ else:
+ self._disconnect(server)
+ models.setconnect_callback = sc
+
+
+# Note: NSApplicationMain calls sys.exit(), so this never returns.
+NSApplicationMain(sys.argv)
diff --git a/Sshuttle VPN.app/Contents/Resources/models.py b/Sshuttle VPN.app/Contents/Resources/models.py
new file mode 100644
index 0000000..858975e
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/models.py
@@ -0,0 +1,131 @@
+from AppKit import *
+import my
+
+
+configchange_callback = setconnect_callback = None
+
+
+def config_changed():
+ if configchange_callback:
+ configchange_callback()
+
+
+def _validate_ip(v):
+ parts = v.split('.')[:4]
+ if len(parts) < 4:
+ parts += ['0'] * (4 - len(parts))
+ for i in range(4):
+ n = my.atoi(parts[i])
+ if n < 0:
+ n = 0
+ elif n > 255:
+ n = 255
+ parts[i] = str(n)
+ return '.'.join(parts)
+
+
+def _validate_width(v):
+ n = my.atoi(v)
+ if n < 0:
+ n = 0
+ elif n > 32:
+ n = 32
+ return n
+
+
+class SshuttleNet(NSObject):
+ def subnet(self):
+ return getattr(self, '_k_subnet', None)
+ def setSubnet_(self, v):
+ self._k_subnet = v
+ config_changed()
+ @objc.accessor
+ def validateSubnet_error_(self, value, error):
+ #print 'validateSubnet!'
+ return True, _validate_ip(value), error
+
+ def width(self):
+ return getattr(self, '_k_width', 24)
+ def setWidth_(self, v):
+ self._k_width = v
+ config_changed()
+ @objc.accessor
+ def validateWidth_error_(self, value, error):
+ #print 'validateWidth!'
+ return True, _validate_width(value), error
+
+NET_ALL = 0
+NET_AUTO = 1
+NET_MANUAL = 2
+
+class SshuttleServer(NSObject):
+ def init(self):
+ self = super(SshuttleServer, self).init()
+ config_changed()
+ return self
+
+ def wantConnect(self):
+ return getattr(self, '_k_wantconnect', False)
+ def setWantConnect_(self, v):
+ self._k_wantconnect = v
+ self.setError_(None)
+ config_changed()
+ if setconnect_callback: setconnect_callback(self)
+
+ def connected(self):
+ return getattr(self, '_k_connected', False)
+ def setConnected_(self, v):
+ print 'setConnected of %r to %r' % (self, v)
+ self._k_connected = v
+ if v: self.setError_(None) # connected ok, so no error
+ config_changed()
+
+ def error(self):
+ return getattr(self, '_k_error', None)
+ def setError_(self, v):
+ self._k_error = v
+ config_changed()
+
+ def isValid(self):
+ if not self.host():
+ return False
+ if self.autoNets() == NET_MANUAL and not len(list(self.nets())):
+ return False
+ return True
+
+ def host(self):
+ return getattr(self, '_k_host', None)
+ def setHost_(self, v):
+ self._k_host = v
+ config_changed()
+ @objc.accessor
+ def validateHost_error_(self, value, error):
+ #print 'validatehost! %r %r %r' % (self, value, error)
+ while value.startswith('-'):
+ value = value[1:]
+ return True, value, error
+
+ def nets(self):
+ return getattr(self, '_k_nets', [])
+ def setNets_(self, v):
+ self._k_nets = v
+ config_changed()
+ def netsHidden(self):
+ #print 'checking netsHidden'
+ return self.autoNets() != NET_MANUAL
+ def setNetsHidden_(self, v):
+ config_changed()
+ #print 'setting netsHidden to %r' % v
+
+ def autoNets(self):
+ return getattr(self, '_k_autoNets', NET_AUTO)
+ def setAutoNets_(self, v):
+ self._k_autoNets = v
+ self.setNetsHidden_(-1)
+ config_changed()
+
+ def autoHosts(self):
+ return getattr(self, '_k_autoHosts', True)
+ def setAutoHosts_(self, v):
+ self._k_autoHosts = v
+ config_changed()
diff --git a/Sshuttle VPN.app/Contents/Resources/my.py b/Sshuttle VPN.app/Contents/Resources/my.py
new file mode 100644
index 0000000..cd701eb
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/my.py
@@ -0,0 +1,62 @@
+import sys, os
+from AppKit import *
+import PyObjCTools.AppHelper
+
+
+def bundle_path(name, typ):
+ if typ:
+ return NSBundle.mainBundle().pathForResource_ofType_(name, typ)
+ else:
+ return os.path.join(NSBundle.mainBundle().resourcePath(), name)
+
+
+# Load an NSData using a python string
+def Data(s):
+ return NSData.alloc().initWithBytes_length_(s, len(s))
+
+
+# Load a property list from a file in the application bundle.
+def PList(name):
+ path = bundle_path(name, 'plist')
+ return NSDictionary.dictionaryWithContentsOfFile_(path)
+
+
+# Load an NSImage from a file in the application bundle.
+def Image(name, ext):
+ bytes = open(bundle_path(name, ext)).read()
+ img = NSImage.alloc().initWithData_(Data(bytes))
+ return img
+
+
+# Return the NSUserDefaults shared object.
+def Defaults():
+ return NSUserDefaults.standardUserDefaults()
+
+
+# Usage:
+# f = DelayedCallback(func, args...)
+# later:
+# f()
+#
+# When you call f(), it will schedule a call to func() next time the
+# ObjC event loop iterates. Multiple calls to f() in a single iteration
+# will only result in one call to func().
+#
+def DelayedCallback(func, *args, **kwargs):
+ flag = [0]
+ def _go():
+ if flag[0]:
+ print 'running %r (flag=%r)' % (func, flag)
+ flag[0] = 0
+ func(*args, **kwargs)
+ def call():
+ flag[0] += 1
+ PyObjCTools.AppHelper.callAfter(_go)
+ return call
+
+
+def atoi(s):
+ try:
+ return int(s)
+ except ValueError:
+ return 0
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/assembler.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/assembler.py
new file mode 100644
index 0000000..c478e37
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/assembler.py
@@ -0,0 +1,26 @@
+import sys, zlib
+
+z = zlib.decompressobj()
+mainmod = sys.modules[__name__]
+while 1:
+ name = sys.stdin.readline().strip()
+ if name:
+ nbytes = int(sys.stdin.readline())
+ if verbosity >= 2:
+ sys.stderr.write('server: assembling %r (%d bytes)\n'
+ % (name, nbytes))
+ content = z.decompress(sys.stdin.read(nbytes))
+ exec compile(content, name, "exec")
+
+ # FIXME: this crushes everything into a single module namespace,
+ # then makes each of the module names point at this one. Gross.
+ assert(name.endswith('.py'))
+ modname = name[:-3]
+ mainmod.__dict__[modname] = mainmod
+ else:
+ break
+
+verbose = verbosity
+sys.stderr.flush()
+sys.stdout.flush()
+main()
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/client.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/client.py
new file mode 100644
index 0000000..dbd11de
--- /dev/null
+++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/client.py
@@ -0,0 +1,356 @@
+import struct, socket, select, errno, re, signal
+import compat.ssubprocess as ssubprocess
+import helpers, ssnet, ssh, ssyslog
+from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
+from helpers import *
+
+_extra_fd = os.open('/dev/null', os.O_RDONLY)
+
+def _islocal(ip):
+ sock = socket.socket()
+ try:
+ try:
+ sock.bind((ip, 0))
+ except socket.error, e:
+ if e.args[0] == errno.EADDRNOTAVAIL:
+ return False # not a local IP
+ else:
+ raise
+ finally:
+ sock.close()
+ return True # it's a local IP, or there would have been an error
+
+
+def got_signal(signum, frame):
+ log('exiting on signal %d\n' % signum)
+ sys.exit(1)
+
+
+_pidname = None
+def check_daemon(pidfile):
+ global _pidname
+ _pidname = os.path.abspath(pidfile)
+ try:
+ oldpid = open(_pidname).read(1024)
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ return # no pidfile, ok
+ else:
+ raise Fatal("can't read %s: %s" % (_pidname, e))
+ if not oldpid:
+ os.unlink(_pidname)
+ return # invalid pidfile, ok
+ oldpid = int(oldpid.strip() or 0)
+ if oldpid <= 0:
+ os.unlink(_pidname)
+ return # invalid pidfile, ok
+ try:
+ os.kill(oldpid, 0)
+ except OSError, e:
+ if e.errno == errno.ESRCH:
+ os.unlink(_pidname)
+ return # outdated pidfile, ok
+ elif e.errno == errno.EPERM:
+ pass
+ else:
+ raise
+ raise Fatal("%s: sshuttle is already running (pid=%d)"
+ % (_pidname, oldpid))
+
+
+def daemonize():
+ if os.fork():
+ os._exit(0)
+ os.setsid()
+ if os.fork():
+ os._exit(0)
+
+ outfd = os.open(_pidname, os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0666)
+ try:
+ os.write(outfd, '%d\n' % os.getpid())
+ finally:
+ os.close(outfd)
+ os.chdir("/")
+
+ # Normal exit when killed, or try/finally won't work and the pidfile won't
+ # be deleted.
+ signal.signal(signal.SIGTERM, got_signal)
+
+ si = open('/dev/null', 'r+')
+ os.dup2(si.fileno(), 0)
+ os.dup2(si.fileno(), 1)
+ si.close()
+
+ ssyslog.stderr_to_syslog()
+
+
+def daemon_cleanup():
+ try:
+ os.unlink(_pidname)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ pass
+ else:
+ raise
+
+
+def original_dst(sock):
+ try:
+ SO_ORIGINAL_DST = 80
+ SOCKADDR_MIN = 16
+ sockaddr_in = sock.getsockopt(socket.SOL_IP,
+ SO_ORIGINAL_DST, SOCKADDR_MIN)
+ (proto, port, a,b,c,d) = struct.unpack('!HHBBBB', sockaddr_in[:8])
+ assert(socket.htons(proto) == socket.AF_INET)
+ ip = '%d.%d.%d.%d' % (a,b,c,d)
+ return (ip,port)
+ except socket.error, e:
+ if e.args[0] == errno.ENOPROTOOPT:
+ return sock.getsockname()
+ raise
+
+
+class FirewallClient:
+ def __init__(self, port, subnets_include, subnets_exclude):
+ self.port = port
+ self.auto_nets = []
+ self.subnets_include = subnets_include
+ self.subnets_exclude = subnets_exclude
+ argvbase = ([sys.argv[0]] +
+ ['-v'] * (helpers.verbose or 0) +
+