From 5d37d9b7b240a4b17d00a04db91cc117c5725078 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 Sep 2015 21:44:29 -0700 Subject: [PATCH] Removed all of the Tails-specific code. If we use ephemeral hidden services, we no longer need to touch the filesystem, and hopefully no longer need root in Tails. --- locale/en.json | 1 + onionshare/helpers.py | 5 +- onionshare/onionshare.py | 190 ++++++++++--------------------- onionshare/strings.py | 2 +- onionshare/web.py | 17 +-- onionshare_gui/onionshare_gui.py | 7 +- test/onionshare_helpers_test.py | 8 -- 7 files changed, 71 insertions(+), 159 deletions(-) diff --git a/locale/en.json b/locale/en.json index c7c9ffd6..bd3d29ce 100644 --- a/locale/en.json +++ b/locale/en.json @@ -23,6 +23,7 @@ "help_tails_port": "Tails only: port for opening firewall, starting hidden service", "help_local_only": "Do not attempt to use tor: for development only", "help_stay_open": "Keep hidden service running after download has finished", + "help_transparent_torification": "My system is transparently torified", "help_debug": "Log errors to disk", "help_filename": "List of files or folders to share", "gui_drag_and_drop": "Drag and drop\nfiles here", diff --git a/onionshare/helpers.py b/onionshare/helpers.py index 8c30c2e2..c3853511 100644 --- a/onionshare/helpers.py +++ b/onionshare/helpers.py @@ -27,10 +27,7 @@ sys.setdefaultencoding("utf-8") def get_platform(): - p = platform.system() - if p == 'Linux' and platform.uname()[0:2] == ('Linux', 'amnesia'): - p = 'Tails' - return p + return platform.system() if get_platform() == 'Darwin': # this is hacky, but it ultimate ends up returning the absolute path to diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index d29b09c5..f2f75145 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -29,10 +29,6 @@ class NoTor(Exception): pass -class TailsError(Exception): - pass - - class HSDirError(Exception): pass @@ -48,7 +44,7 @@ def hsdic2list(dic): class OnionShare(object): - def __init__(self, debug=False, local_only=False, stay_open=False): + def __init__(self, debug=False, local_only=False, stay_open=False, transparent_torification=False): self.port = None self.controller = None self.hidserv_dir = None @@ -63,6 +59,9 @@ class OnionShare(object): # automatically close when download is finished self.stay_open = stay_open + # traffic automatically goes through Tor + self.transparent_torification = transparent_torification + # files and dirs to delete on shutdown self.cleanup_filenames = [] @@ -100,92 +99,62 @@ class OnionShare(object): self.port = tmpsock.getsockname()[1] tmpsock.close() - def start_hidden_service(self, gui=False, tails_root=False): + def start_hidden_service(self, gui=False): if not self.port: self.choose_port() - if helpers.get_platform() == 'Tails' and not tails_root: - # in Tails, start the hidden service in a root process - if gui: - args = ['/usr/bin/gksudo', '-D', 'OnionShare', '--', '/usr/bin/onionshare'] - else: - args = ['/usr/bin/sudo', '--', '/usr/bin/onionshare'] - print "Executing: {0:s}".format(args+[str(self.port)]) - p = subprocess.Popen(args+[str(self.port)], stderr=subprocess.PIPE, stdout=subprocess.PIPE) - stdout = p.stdout.read(22) # .onion URLs are 22 chars long - - if stdout: - self.onion_host = stdout - print 'Got onion_host: {0:s}'.format(self.onion_host) - else: - if p.poll() == -1: - raise TailsError(o.stderr.read()) - else: - raise TailsError(strings._("error_tails_unknown_root")) + if self.local_only: + self.onion_host = '127.0.0.1:{0:d}'.format(self.port) + return + # come up with a hidden service directory name + if helpers.get_platform() == 'Windows': + path = '{0:s}/onionshare'.format(os.environ['Temp'].replace('\\', '/')) else: - if self.local_only: - self.onion_host = '127.0.0.1:{0:d}'.format(self.port) + path = '/tmp/onionshare' - else: - # come up with a hidden service directory name - if helpers.get_platform() == 'Tails': - # need to create HS directory in /var/lib/tor because of AppArmor rules included in Tails - self.hidserv_dir = tempfile.mkdtemp(dir='/var/lib/tor') + try: + if not os.path.exists(path): + os.makedirs(path, 0700) + except: + raise HSDirError(strings._("error_hs_dir_cannot_create").format(path)) + if not os.access(path, os.W_OK): + raise HSDirError(strings._("error_hs_dir_not_writable").format(path)) - # change owner to debian-tor - import pwd - import grp - uid = pwd.getpwnam("debian-tor").pw_uid - gid = grp.getgrnam("debian-tor").gr_gid - os.chown(self.hidserv_dir, uid, gid) - else: - # in non-Tails linux, onionshare will create HS dir in /tmp/onionshare/* - path = '/tmp/onionshare' - try: - if not os.path.exists(path): - os.makedirs(path, 0700) - except: - raise HSDirError(strings._("error_hs_dir_cannot_create").format(path)) - if not os.access(path, os.W_OK): - raise HSDirError(strings._("error_hs_dir_not_writable").format(path)) + self.hidserv_dir = tempfile.mkdtemp(dir=path) + self.cleanup_filenames.append(self.hidserv_dir) - self.hidserv_dir = tempfile.mkdtemp(dir=path) - self.cleanup_filenames.append(self.hidserv_dir) + # connect to the tor controlport + self.controller = None + tor_control_ports = [9051, 9151] + for tor_control_port in tor_control_ports: + try: + self.controller = Controller.from_port(port=tor_control_port) + self.controller.authenticate() + break + except: + pass + if not self.controller: + raise NoTor(strings._("cant_connect_ctrlport").format(tor_control_ports)) - # connect to the tor controlport - self.controller = None - tor_control_ports = [9051, 9151] - for tor_control_port in tor_control_ports: - try: - self.controller = Controller.from_port(port=tor_control_port) - self.controller.authenticate() - break - except: - pass - if not self.controller: - raise NoTor(strings._("cant_connect_ctrlport").format(tor_control_ports)) + # set up hidden service + hsdic = self.controller.get_conf_map('HiddenServiceOptions') or { + 'HiddenServiceDir': [], 'HiddenServicePort': [] + } + if self.hidserv_dir in hsdic.get('HiddenServiceDir', []): + # Maybe a stale service with the wrong local port + dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir) + del hsdic['HiddenServiceDir'][dropme] + del hsdic['HiddenServicePort'][dropme] + hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir', [])+[self.hidserv_dir] + hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort', [])+[ + '80 127.0.0.1:{0:d}'.format(self.port)] - # set up hidden service - if helpers.get_platform() == 'Windows': - self.hidserv_dir = self.hidserv_dir.replace('\\', '/') - hsdic = self.controller.get_conf_map('HiddenServiceOptions') or { - 'HiddenServiceDir': [], 'HiddenServicePort': [] - } - if self.hidserv_dir in hsdic.get('HiddenServiceDir', []): - # Maybe a stale service with the wrong local port - dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir) - del hsdic['HiddenServiceDir'][dropme] - del hsdic['HiddenServicePort'][dropme] - hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir', [])+[self.hidserv_dir] - hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort', [])+[ - '80 127.0.0.1:{0:d}'.format(self.port)] + self.controller.set_options(hsdic2list(hsdic)) - self.controller.set_options(hsdic2list(hsdic)) - - # figure out the .onion hostname - hostname_file = '{0:s}/hostname'.format(self.hidserv_dir) - self.onion_host = open(hostname_file, 'r').read().strip() + # figure out the .onion hostname + hostname_file = '{0:s}/hostname'.format(self.hidserv_dir) + self.onion_host = open(hostname_file, 'r').read().strip() def wait_for_hs(self): if self.local_only: @@ -199,9 +168,8 @@ class OnionShare(object): sys.stdout.write('{0:s} '.format(strings._('wait_for_hs_trying'))) sys.stdout.flush() - if helpers.get_platform() == 'Tails': - # in Tails everything is proxies over Tor already - # so no need to set the socks5 proxy + if self.transparent_torification: + # no need to set the socks5 proxy urllib2.urlopen('http://{0:s}'.format(self.onion_host)) else: tor_exists = False @@ -221,13 +189,13 @@ class OnionShare(object): ready = True sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_yup'))) - except socks.SOCKS5Error: # non-Tails error + except socks.SOCKS5Error: sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope'))) sys.stdout.flush() - except urllib2.HTTPError: # Tails error + except urllib2.HTTPError: # torification error sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope'))) sys.stdout.flush() - except httplib.BadStatusLine: # Tails (with bridge) error + except httplib.BadStatusLine: # torification (with bridge) error sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope'))) sys.stdout.flush() except KeyboardInterrupt: @@ -235,46 +203,6 @@ class OnionShare(object): return True -def tails_root(): - # if running in Tails and as root, do only the things that require root - if helpers.get_platform() == 'Tails' and helpers.is_root(): - parser = argparse.ArgumentParser() - parser.add_argument('port', nargs=1, help=strings._("help_tails_port")) - args = parser.parse_args() - - try: - port = int(args.port[0]) - except ValueError: - sys.stderr.write('{0:s}\n'.format(strings._("error_tails_invalid_port"))) - sys.exit(-1) - - # open hole in firewall - subprocess.call(['/sbin/iptables', '-I', 'OUTPUT', '-o', 'lo', - '-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT']) - - # start hidden service - app = OnionShare() - app.choose_port() - app.port = port - app.start_hidden_service(False, True) - sys.stdout.write(app.onion_host) - sys.stdout.flush() - - # close hole in firewall on shutdown - import signal - - def handler(signum=None, frame=None): - subprocess.call(['/sbin/iptables', '-D', 'OUTPUT', '-o', 'lo', - '-p', 'tcp', '--dport', str(port), '-j', 'ACCEPT']) - sys.exit() - for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT]: - signal.signal(sig, handler) - - # stay open until killed - while True: - time.sleep(1) - - def main(cwd=None): strings.load_strings() @@ -283,12 +211,11 @@ def main(cwd=None): if cwd: os.chdir(cwd) - tails_root() - # parse arguments parser = argparse.ArgumentParser() parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) + parser.add_argument('--transparent', action='store_true', dest='transparent_torification', help=strings._("help_transparent_torification")) parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename')) args = parser.parse_args() @@ -300,6 +227,7 @@ def main(cwd=None): local_only = bool(args.local_only) debug = bool(args.debug) stay_open = bool(args.stay_open) + transparent_torification = bool(args.transparent_torification) # validation valid = True @@ -312,14 +240,12 @@ def main(cwd=None): # start the onionshare app try: - app = OnionShare(debug, local_only, stay_open) + app = OnionShare(debug, local_only, stay_open, transparent_torification) app.choose_port() print strings._("connecting_ctrlport").format(int(app.port)) app.start_hidden_service() except NoTor as e: sys.exit(e.args[0]) - except TailsError as e: - sys.exit(e.args[0]) except HSDirError as e: sys.exit(e.args[0]) @@ -335,7 +261,7 @@ def main(cwd=None): print '' # start onionshare service in new thread - t = threading.Thread(target=web.start, args=(app.port, app.stay_open)) + t = threading.Thread(target=web.start, args=(app.port, app.stay_open, app.transparent_torification)) t.daemon = True t.start() diff --git a/onionshare/strings.py b/onionshare/strings.py index fe4fc0e4..ea61eb0e 100644 --- a/onionshare/strings.py +++ b/onionshare/strings.py @@ -28,7 +28,7 @@ def load_strings(default="en"): p = helpers.get_platform() # find locale dir - if p == 'Linux' or p == 'Tails': + if p == 'Linux': locale_dir = os.path.join(sys.prefix, 'share/onionshare/locale') elif p == 'Darwin': locale_dir = os.path.join(helpers.osx_resources_dir, 'locale') diff --git a/onionshare/web.py b/onionshare/web.py index 059475aa..83c42e05 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -83,16 +83,18 @@ slug = helpers.random_string(16) download_count = 0 stay_open = False - - def set_stay_open(new_stay_open): global stay_open stay_open = new_stay_open - - def get_stay_open(): return stay_open +transparent_torification = False +def set_transparent_torification(new_transparent_torification): + global transparent_torification + stay_open = new_transparent_torification +def get_transparent_torification(): + return transparent_torification def debug_mode(): import logging @@ -224,16 +226,15 @@ def shutdown(shutdown_slug_candidate): return "" -def start(port, stay_open=False): +def start(port, stay_open=False, transparent_torification=False): set_stay_open(stay_open) + set_transparent_torification(transparent_torification) app.run(port=port, threaded=True) def stop(port): # to stop flask, load http://127.0.0.1://shutdown - if helpers.get_platform() == 'Tails': - # in Tails everything is proxies over Tor, so we need to get lower level - # to connect not over the proxy + if transparent_torification: import socket s = socket.socket() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index b2642dbf..9f8e48ae 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -39,7 +39,7 @@ from options import Options class Application(QtGui.QApplication): def __init__(self): platform = helpers.get_platform() - if platform == 'Tails' or platform == 'Linux': + if platform == 'Linux': self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) QtGui.QApplication.__init__(self, sys.argv) @@ -128,11 +128,6 @@ class OnionShareGui(QtGui.QWidget): self.server_status.stop_server() self.status_bar.clearMessage() return - except onionshare.TailsError as e: - alert(e.args[0], QtGui.QMessageBox.Warning) - self.server_status.stop_server() - self.status_bar.clearMessage() - return # start onionshare service in new thread t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open)) diff --git a/test/onionshare_helpers_test.py b/test/onionshare_helpers_test.py index a20e3e9a..f7fa13e3 100644 --- a/test/onionshare_helpers_test.py +++ b/test/onionshare_helpers_test.py @@ -21,14 +21,6 @@ from nose import with_setup import test_helpers - -def test_get_platform_on_tails(): - """get_platform() returns 'Tails' when hostname is 'amnesia'""" - helpers.platform.uname = lambda: ('Linux', 'amnesia', '3.14-1-amd64', - '#1 SMP Debian 3.14.4-1 (2014-05-13)', 'x86_64', '') - assert helpers.get_platform() == 'Tails' - - def test_get_platform_returns_platform_system(): """get_platform() returns platform.system() when ONIONSHARE_PLATFORM is not defined""" helpers.platform.system = lambda: 'Sega Saturn'