diff --git a/locale/en.json b/locale/en.json index bd3d29ce..164a7844 100644 --- a/locale/en.json +++ b/locale/en.json @@ -43,5 +43,6 @@ "gui_starting_server3": "Waiting for Tor hidden service...", "gui_please_wait": "Please wait...", "error_hs_dir_cannot_create": "Cannot create hidden service dir {0:s}", - "error_hs_dir_not_writable": "Hidden service dir {0:s} is not writable" + "error_hs_dir_not_writable": "Hidden service dir {0:s} is not writable", + "using_ephemeral": "Using ephemeral Tor hidden service" } diff --git a/onionshare/hs.py b/onionshare/hs.py new file mode 100644 index 00000000..fd28b64c --- /dev/null +++ b/onionshare/hs.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2015 Micah Lee + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from stem.control import Controller +import os, tempfile, shutil + +import helpers, strings + +class NoTor(Exception): + pass + +class HSDirError(Exception): + pass + +class HS(object): + def __init__(self, transparent_torification=False): + self.transparent_torification = transparent_torification + + # files and dirs to delete on shutdown + self.cleanup_filenames = [] + + # connect to the tor controlport + self.c = None + ports = [9051, 9151] + for port in ports: + try: + self.c = Controller.from_port(port=port) + self.c.authenticate() + break + except: + pass + if not self.c: + raise NoTor(strings._("cant_connect_ctrlport").format(ports)) + + # do the versions of stem and tor that I'm using support ephemeral hidden services? + tor_version = self.c.get_version().version_str + list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None) + self.supports_ephemeral = callable(list_ephemeral_hidden_services) and tor_version >= '0.2.7.1' + + def start(self, port): + if self.supports_ephemeral: + print strings._('using_ephemeral') + res = self.c.create_ephemeral_hidden_service(port) + onion_host = res.content()[0][2].split('=')[1] + '.onion' + return onion_host + + else: + # come up with a hidden service directory name + if helpers.get_platform() == 'Windows': + path = '{0:s}/onionshare'.format(os.environ['Temp'].replace('\\', '/')) + else: + 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)) + + hidserv_dir = tempfile.mkdtemp(dir=path) + self.cleanup_filenames.append(hidserv_dir) + + # set up hidden service + hsdic = self.c.get_conf_map('HiddenServiceOptions') or { + 'HiddenServiceDir': [], 'HiddenServicePort': [] + } + if hidserv_dir in hsdic.get('HiddenServiceDir', []): + # Maybe a stale service with the wrong local port + dropme = hsdic['HiddenServiceDir'].index(hidserv_dir) + del hsdic['HiddenServiceDir'][dropme] + del hsdic['HiddenServicePort'][dropme] + hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir', [])+[hidserv_dir] + hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort', [])+[ + '80 127.0.0.1:{0:d}'.format(port)] + + self.c.set_options(self._hsdic2list(hsdic)) + + # figure out the .onion hostname + hostname_file = '{0:s}/hostname'.format(hidserv_dir) + onion_host = open(hostname_file, 'r').read().strip() + return onion_host + + def cleanup(self): + if self.supports_ephemeral: + # todo: cleanup the ephemeral hidden service + pass + else: + # cleanup hidden service + try: + if self.controller: + # Get fresh hidden services (maybe changed since last time) + # and remove ourselves + hsdic = self.controller.get_conf_map('HiddenServiceOptions') or { + 'HiddenServiceDir': [], 'HiddenServicePort': [] + } + if self.hidserv_dir and self.hidserv_dir in hsdic.get('HiddenServiceDir', []): + dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir) + del hsdic['HiddenServiceDir'][dropme] + del hsdic['HiddenServicePort'][dropme] + self.controller.set_options(self._hsdic2list(hsdic)) + # Politely close the controller + self.controller.close() + except: + pass + + # cleanup files + for filename in self.cleanup_filenames: + if os.path.isfile(filename): + os.remove(filename) + elif os.path.isdir(filename): + shutil.rmtree(filename) + self.cleanup_filenames = [] + + def _hsdic2list(self, dic): + """Convert what we get from get_conf_map to what we need for set_options""" + return [ + pair for pairs in [ + [('HiddenServiceDir', vals[0]), ('HiddenServicePort', vals[1])] + for vals in zip(dic.get('HiddenServiceDir', []), dic.get('HiddenServicePort', [])) + ] for pair in pairs + ] diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index cf1bbcef..34e57b43 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -17,38 +17,20 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import os, sys, subprocess, time, argparse, inspect, shutil, socket, threading, urllib2, httplib, tempfile +import os, sys, subprocess, time, argparse, inspect, shutil, socket, threading, urllib2, httplib import socks -from stem.control import Controller - -import strings, helpers, web - - -class NoTor(Exception): - pass - - -class HSDirError(Exception): - pass - - -def hsdic2list(dic): - """Convert what we get from get_conf_map to what we need for set_options""" - return [ - pair for pairs in [ - [('HiddenServiceDir', vals[0]), ('HiddenServicePort', vals[1])] - for vals in zip(dic.get('HiddenServiceDir', []), dic.get('HiddenServicePort', [])) - ] for pair in pairs - ] - +import strings, helpers, web, hs class OnionShare(object): def __init__(self, debug=False, local_only=False, stay_open=False, transparent_torification=False): self.port = None - self.controller = None + self.hs = None self.hidserv_dir = None + # files and dirs to delete on shutdown + self.cleanup_filenames = [] + # debug mode if debug: web.debug_mode() @@ -62,36 +44,6 @@ class OnionShare(object): # traffic automatically goes through Tor self.transparent_torification = transparent_torification - # files and dirs to delete on shutdown - self.cleanup_filenames = [] - - def cleanup(self): - # cleanup hidden service - try: - if self.controller: - # Get fresh hidden services (maybe changed since last time) - # and remove ourselves - hsdic = self.controller.get_conf_map('HiddenServiceOptions') or { - 'HiddenServiceDir': [], 'HiddenServicePort': [] - } - if self.hidserv_dir and self.hidserv_dir in hsdic.get('HiddenServiceDir', []): - dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir) - del hsdic['HiddenServiceDir'][dropme] - del hsdic['HiddenServicePort'][dropme] - self.controller.set_options(hsdic2list(hsdic)) - # Politely close the controller - self.controller.close() - except: - pass - - # cleanup files - for filename in self.cleanup_filenames: - if os.path.isfile(filename): - os.remove(filename) - elif os.path.isdir(filename): - shutil.rmtree(filename) - self.cleanup_filenames = [] - def choose_port(self): # let the OS choose a port tmpsock = socket.socket() @@ -107,54 +59,10 @@ class OnionShare(object): 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: - path = '/tmp/onionshare' + if not self.hs: + self.hs = hs.HS(self.transparent_torification) - 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) - - # 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)] - - 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() + self.onion_host = self.hs.start(self.port) def wait_for_hs(self): if self.local_only: @@ -173,11 +81,11 @@ class OnionShare(object): urllib2.urlopen('http://{0:s}'.format(self.onion_host)) else: tor_exists = False - tor_socks_ports = [9050, 9150] - for tor_socks_port in tor_socks_ports: + ports = [9050, 9150] + for port in ports: try: s = socks.socksocket() - s.setproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', tor_socks_port) + s.setproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', port) s.connect((self.onion_host, 80)) s.close() tor_exists = True @@ -202,6 +110,18 @@ class OnionShare(object): return False return True + def cleanup(self): + # cleanup files + for filename in self.cleanup_filenames: + if os.path.isfile(filename): + os.remove(filename) + elif os.path.isdir(filename): + shutil.rmtree(filename) + self.cleanup_filenames = [] + + # call hs's cleanup + self.hs.cleanup() + def main(cwd=None): strings.load_strings() @@ -244,9 +164,9 @@ def main(cwd=None): app.choose_port() print strings._("connecting_ctrlport").format(int(app.port)) app.start_hidden_service() - except NoTor as e: + except hs.NoTor as e: sys.exit(e.args[0]) - except HSDirError as e: + except hs.HSDirError as e: sys.exit(e.args[0]) # prepare files to share