diff options
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 Binary files differnew file mode 100755 index 0000000..76f25e2 --- /dev/null +++ b/Sshuttle VPN.app/Contents/MacOS/Sshuttle diff --git a/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib b/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib Binary files differnew file mode 100644 index 0000000..302f9fb --- /dev/null +++ b/Sshuttle VPN.app/Contents/Resources/English.lproj/MainMenu.nib 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 Binary files differnew file mode 100644 index 0000000..620b977 --- /dev/null +++ b/Sshuttle VPN.app/Contents/Resources/app.icns 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 Binary files differnew file mode 100644 index 0000000..7ef418d --- /dev/null +++ b/Sshuttle VPN.app/Contents/Resources/chicken-tiny-bw.png diff --git a/Sshuttle VPN.app/Contents/Resources/chicken-tiny-err.png b/Sshuttle VPN.app/Contents/Resources/chicken-tiny-err.png Binary files differnew file mode 100644 index 0000000..2984513 --- /dev/null +++ b/Sshuttle VPN.app/Contents/Resources/chicken-tiny-err.png diff --git a/Sshuttle VPN.app/Contents/Resources/chicken-tiny.png b/Sshuttle VPN.app/Contents/Resources/chicken-tiny.png Binary files differnew file mode 100644 index 0000000..b1d9ab0 --- /dev/null +++ b/Sshuttle VPN.app/Contents/Resources/chicken-tiny.png 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) + + ['--firewall', str(port)]) + if ssyslog._p: + argvbase += ['--syslog'] + argv_tries = [ + ['sudo', '-p', '[local sudo] Password: '] + argvbase, + ['su', '-c', ' '.join(argvbase)], + argvbase + ] + + # we can't use stdin/stdout=subprocess.PIPE here, as we normally would, + # because stupid Linux 'su' requires that stdin be attached to a tty. + # Instead, attach a *bidirectional* socket to its stdout, and use + # that for talking in both directions. + (s1,s2) = socket.socketpair() + def setup(): + # run in the child process + s2.close() + e = None + if os.getuid() == 0: + argv_tries = argv_tries[-1:] # last entry only + for argv in argv_tries: + try: + if argv[0] == 'su': + sys.stderr.write('[local su] ') + self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup) + e = None + break + except OSError, e: + pass + self.argv = argv + s1.close() + self.pfile = s2.makefile('wb+') + if e: + log('Spawning firewall manager: %r\n' % self.argv) + raise Fatal(e) |