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.

This commit is contained in:
Micah Lee 2015-09-07 21:44:29 -07:00
parent d50601f5bd
commit 5d37d9b7b2
7 changed files with 71 additions and 159 deletions

View file

@ -23,6 +23,7 @@
"help_tails_port": "Tails only: port for opening firewall, starting hidden service", "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_local_only": "Do not attempt to use tor: for development only",
"help_stay_open": "Keep hidden service running after download has finished", "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_debug": "Log errors to disk",
"help_filename": "List of files or folders to share", "help_filename": "List of files or folders to share",
"gui_drag_and_drop": "Drag and drop\nfiles here", "gui_drag_and_drop": "Drag and drop\nfiles here",

View file

@ -27,10 +27,7 @@ sys.setdefaultencoding("utf-8")
def get_platform(): def get_platform():
p = platform.system() return platform.system()
if p == 'Linux' and platform.uname()[0:2] == ('Linux', 'amnesia'):
p = 'Tails'
return p
if get_platform() == 'Darwin': if get_platform() == 'Darwin':
# this is hacky, but it ultimate ends up returning the absolute path to # this is hacky, but it ultimate ends up returning the absolute path to

View file

@ -29,10 +29,6 @@ class NoTor(Exception):
pass pass
class TailsError(Exception):
pass
class HSDirError(Exception): class HSDirError(Exception):
pass pass
@ -48,7 +44,7 @@ def hsdic2list(dic):
class OnionShare(object): 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.port = None
self.controller = None self.controller = None
self.hidserv_dir = None self.hidserv_dir = None
@ -63,6 +59,9 @@ class OnionShare(object):
# automatically close when download is finished # automatically close when download is finished
self.stay_open = stay_open self.stay_open = stay_open
# traffic automatically goes through Tor
self.transparent_torification = transparent_torification
# files and dirs to delete on shutdown # files and dirs to delete on shutdown
self.cleanup_filenames = [] self.cleanup_filenames = []
@ -100,92 +99,62 @@ class OnionShare(object):
self.port = tmpsock.getsockname()[1] self.port = tmpsock.getsockname()[1]
tmpsock.close() tmpsock.close()
def start_hidden_service(self, gui=False, tails_root=False): def start_hidden_service(self, gui=False):
if not self.port: if not self.port:
self.choose_port() self.choose_port()
if helpers.get_platform() == 'Tails' and not tails_root: if self.local_only:
# in Tails, start the hidden service in a root process self.onion_host = '127.0.0.1:{0:d}'.format(self.port)
if gui: return
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"))
# come up with a hidden service directory name
if helpers.get_platform() == 'Windows':
path = '{0:s}/onionshare'.format(os.environ['Temp'].replace('\\', '/'))
else: else:
if self.local_only: path = '/tmp/onionshare'
self.onion_host = '127.0.0.1:{0:d}'.format(self.port)
else: try:
# come up with a hidden service directory name if not os.path.exists(path):
if helpers.get_platform() == 'Tails': os.makedirs(path, 0700)
# need to create HS directory in /var/lib/tor because of AppArmor rules included in Tails except:
self.hidserv_dir = tempfile.mkdtemp(dir='/var/lib/tor') 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 self.hidserv_dir = tempfile.mkdtemp(dir=path)
import pwd self.cleanup_filenames.append(self.hidserv_dir)
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) # connect to the tor controlport
self.cleanup_filenames.append(self.hidserv_dir) 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 # set up hidden service
self.controller = None hsdic = self.controller.get_conf_map('HiddenServiceOptions') or {
tor_control_ports = [9051, 9151] 'HiddenServiceDir': [], 'HiddenServicePort': []
for tor_control_port in tor_control_ports: }
try: if self.hidserv_dir in hsdic.get('HiddenServiceDir', []):
self.controller = Controller.from_port(port=tor_control_port) # Maybe a stale service with the wrong local port
self.controller.authenticate() dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir)
break del hsdic['HiddenServiceDir'][dropme]
except: del hsdic['HiddenServicePort'][dropme]
pass hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir', [])+[self.hidserv_dir]
if not self.controller: hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort', [])+[
raise NoTor(strings._("cant_connect_ctrlport").format(tor_control_ports)) '80 127.0.0.1:{0:d}'.format(self.port)]
# set up hidden service self.controller.set_options(hsdic2list(hsdic))
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)) # figure out the .onion hostname
hostname_file = '{0:s}/hostname'.format(self.hidserv_dir)
# figure out the .onion hostname self.onion_host = open(hostname_file, 'r').read().strip()
hostname_file = '{0:s}/hostname'.format(self.hidserv_dir)
self.onion_host = open(hostname_file, 'r').read().strip()
def wait_for_hs(self): def wait_for_hs(self):
if self.local_only: if self.local_only:
@ -199,9 +168,8 @@ class OnionShare(object):
sys.stdout.write('{0:s} '.format(strings._('wait_for_hs_trying'))) sys.stdout.write('{0:s} '.format(strings._('wait_for_hs_trying')))
sys.stdout.flush() sys.stdout.flush()
if helpers.get_platform() == 'Tails': if self.transparent_torification:
# in Tails everything is proxies over Tor already # no need to set the socks5 proxy
# so no need to set the socks5 proxy
urllib2.urlopen('http://{0:s}'.format(self.onion_host)) urllib2.urlopen('http://{0:s}'.format(self.onion_host))
else: else:
tor_exists = False tor_exists = False
@ -221,13 +189,13 @@ class OnionShare(object):
ready = True ready = True
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_yup'))) 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.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush() 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.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush() 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.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
sys.stdout.flush() sys.stdout.flush()
except KeyboardInterrupt: except KeyboardInterrupt:
@ -235,46 +203,6 @@ class OnionShare(object):
return True 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): def main(cwd=None):
strings.load_strings() strings.load_strings()
@ -283,12 +211,11 @@ def main(cwd=None):
if cwd: if cwd:
os.chdir(cwd) os.chdir(cwd)
tails_root()
# parse arguments # parse arguments
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) 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('--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('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename')) parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename'))
args = parser.parse_args() args = parser.parse_args()
@ -300,6 +227,7 @@ def main(cwd=None):
local_only = bool(args.local_only) local_only = bool(args.local_only)
debug = bool(args.debug) debug = bool(args.debug)
stay_open = bool(args.stay_open) stay_open = bool(args.stay_open)
transparent_torification = bool(args.transparent_torification)
# validation # validation
valid = True valid = True
@ -312,14 +240,12 @@ def main(cwd=None):
# start the onionshare app # start the onionshare app
try: try:
app = OnionShare(debug, local_only, stay_open) app = OnionShare(debug, local_only, stay_open, transparent_torification)
app.choose_port() app.choose_port()
print strings._("connecting_ctrlport").format(int(app.port)) print strings._("connecting_ctrlport").format(int(app.port))
app.start_hidden_service() app.start_hidden_service()
except NoTor as e: except NoTor as e:
sys.exit(e.args[0]) sys.exit(e.args[0])
except TailsError as e:
sys.exit(e.args[0])
except HSDirError as e: except HSDirError as e:
sys.exit(e.args[0]) sys.exit(e.args[0])
@ -335,7 +261,7 @@ def main(cwd=None):
print '' print ''
# start onionshare service in new thread # 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.daemon = True
t.start() t.start()

View file

@ -28,7 +28,7 @@ def load_strings(default="en"):
p = helpers.get_platform() p = helpers.get_platform()
# find locale dir # find locale dir
if p == 'Linux' or p == 'Tails': if p == 'Linux':
locale_dir = os.path.join(sys.prefix, 'share/onionshare/locale') locale_dir = os.path.join(sys.prefix, 'share/onionshare/locale')
elif p == 'Darwin': elif p == 'Darwin':
locale_dir = os.path.join(helpers.osx_resources_dir, 'locale') locale_dir = os.path.join(helpers.osx_resources_dir, 'locale')

View file

@ -83,16 +83,18 @@ slug = helpers.random_string(16)
download_count = 0 download_count = 0
stay_open = False stay_open = False
def set_stay_open(new_stay_open): def set_stay_open(new_stay_open):
global stay_open global stay_open
stay_open = new_stay_open stay_open = new_stay_open
def get_stay_open(): def get_stay_open():
return 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(): def debug_mode():
import logging import logging
@ -224,16 +226,15 @@ def shutdown(shutdown_slug_candidate):
return "" return ""
def start(port, stay_open=False): def start(port, stay_open=False, transparent_torification=False):
set_stay_open(stay_open) set_stay_open(stay_open)
set_transparent_torification(transparent_torification)
app.run(port=port, threaded=True) app.run(port=port, threaded=True)
def stop(port): def stop(port):
# to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown # to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
if helpers.get_platform() == 'Tails': if transparent_torification:
# in Tails everything is proxies over Tor, so we need to get lower level
# to connect not over the proxy
import socket import socket
s = socket.socket() s = socket.socket()

View file

@ -39,7 +39,7 @@ from options import Options
class Application(QtGui.QApplication): class Application(QtGui.QApplication):
def __init__(self): def __init__(self):
platform = helpers.get_platform() platform = helpers.get_platform()
if platform == 'Tails' or platform == 'Linux': if platform == 'Linux':
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
QtGui.QApplication.__init__(self, sys.argv) QtGui.QApplication.__init__(self, sys.argv)
@ -128,11 +128,6 @@ class OnionShareGui(QtGui.QWidget):
self.server_status.stop_server() self.server_status.stop_server()
self.status_bar.clearMessage() self.status_bar.clearMessage()
return 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 # start onionshare service in new thread
t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open)) t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open))

View file

@ -21,14 +21,6 @@ from nose import with_setup
import test_helpers 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(): def test_get_platform_returns_platform_system():
"""get_platform() returns platform.system() when ONIONSHARE_PLATFORM is not defined""" """get_platform() returns platform.system() when ONIONSHARE_PLATFORM is not defined"""
helpers.platform.system = lambda: 'Sega Saturn' helpers.platform.system = lambda: 'Sega Saturn'