diff options
author | Micah Lee <micah@micahflee.com> | 2019-04-18 19:57:46 -0700 |
---|---|---|
committer | Micah Lee <micah@micahflee.com> | 2019-04-18 19:57:46 -0700 |
commit | ed1ada9c065881740a97416b70132067fed06029 (patch) | |
tree | 1e87a069dc3cd638958042e76ba476ecdec142e5 /onionshare | |
parent | 436dd3052d8137689802705e5e301cd9aa60ae92 (diff) | |
parent | 77e0d76bc1b2de6ba72e90336bf8f0111416a8ef (diff) | |
download | onionshare-ed1ada9c065881740a97416b70132067fed06029.tar.gz onionshare-ed1ada9c065881740a97416b70132067fed06029.zip |
Merge branch 'develop' into 918_old_linux
Diffstat (limited to 'onionshare')
-rw-r--r-- | onionshare/__init__.py | 116 | ||||
-rw-r--r-- | onionshare/common.py | 4 | ||||
-rw-r--r-- | onionshare/onion.py | 60 | ||||
-rw-r--r-- | onionshare/onionshare.py | 18 | ||||
-rw-r--r-- | onionshare/settings.py | 3 | ||||
-rw-r--r-- | onionshare/web/receive_mode.py | 13 | ||||
-rw-r--r-- | onionshare/web/web.py | 8 |
7 files changed, 153 insertions, 69 deletions
diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 2f44c846..db97f46d 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. """ import os, sys, time, argparse, threading +from datetime import datetime +from datetime import timedelta from . import strings from .common import Common @@ -53,7 +55,9 @@ def main(cwd=None): parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=28)) 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('--shutdown-timeout', metavar='<int>', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) + parser.add_argument('--auto-start-timer', metavar='<int>', dest='autostart_timer', default=0, help=strings._("help_autostart_timer")) + parser.add_argument('--auto-stop-timer', metavar='<int>', dest='autostop_timer', default=0, help=strings._("help_autostop_timer")) + parser.add_argument('--connect-timeout', metavar='<int>', dest='connect_timeout', default=120, help=strings._("help_connect_timeout")) parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth")) parser.add_argument('--receive', action='store_true', dest='receive', help=strings._("help_receive")) parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config')) @@ -68,7 +72,9 @@ def main(cwd=None): local_only = bool(args.local_only) debug = bool(args.debug) stay_open = bool(args.stay_open) - shutdown_timeout = int(args.shutdown_timeout) + autostart_timer = int(args.autostart_timer) + autostop_timer = int(args.autostop_timer) + connect_timeout = int(args.connect_timeout) stealth = bool(args.stealth) receive = bool(args.receive) config = args.config @@ -111,7 +117,7 @@ def main(cwd=None): # Start the Onion object onion = Onion(common) try: - onion.connect(custom_settings=False, config=config) + onion.connect(custom_settings=False, config=config, connect_timeout=connect_timeout) except KeyboardInterrupt: print("") sys.exit() @@ -120,10 +126,51 @@ def main(cwd=None): # Start the onionshare app try: - app = OnionShare(common, onion, local_only, shutdown_timeout) + common.settings.load() + if not common.settings.get('public_mode'): + web.generate_slug(common.settings.get('slug')) + else: + web.slug = None + app = OnionShare(common, onion, local_only, autostop_timer) app.set_stealth(stealth) app.choose_port() - app.start_onion_service() + # Delay the startup if a startup timer was set + if autostart_timer > 0: + # Can't set a schedule that is later than the auto-stop timer + if app.autostop_timer > 0 and app.autostop_timer < autostart_timer: + print(strings._('gui_autostop_timer_cant_be_earlier_than_autostart_timer')) + sys.exit() + + app.start_onion_service(False, True) + if common.settings.get('public_mode'): + url = 'http://{0:s}'.format(app.onion_host) + else: + url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) + schedule = datetime.now() + timedelta(seconds=autostart_timer) + if mode == 'receive': + print(strings._('receive_mode_data_dir').format(common.settings.get('data_dir'))) + print('') + print(strings._('receive_mode_warning')) + print('') + if stealth: + print(strings._("give_this_scheduled_url_receive_stealth").format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print(app.auth_string) + else: + print(strings._("give_this_scheduled_url_receive").format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + else: + if stealth: + print(strings._("give_this_scheduled_url_share_stealth").format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print(app.auth_string) + else: + print(strings._("give_this_scheduled_url_share").format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print(url) + print('') + print(strings._("waiting_for_scheduled_time")) + app.onion.cleanup(False) + time.sleep(autostart_timer) + app.start_onion_service() + else: + app.start_onion_service() except KeyboardInterrupt: print("") sys.exit() @@ -149,7 +196,7 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), common.settings.get('slug'))) + t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.slug)) t.daemon = True t.start() @@ -157,9 +204,9 @@ def main(cwd=None): # Wait for web.generate_slug() to finish running time.sleep(0.2) - # start shutdown timer thread - if app.shutdown_timeout > 0: - app.shutdown_timer.start() + # start auto-stop timer thread + if app.autostop_timer > 0: + app.autostop_timer_thread.start() # Save the web slug if we are using a persistent private key if common.settings.get('save_private_key'): @@ -174,44 +221,47 @@ def main(cwd=None): url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) print('') - if mode == 'receive': - print(strings._('receive_mode_data_dir').format(common.settings.get('data_dir'))) - print('') - print(strings._('receive_mode_warning')) - print('') - - if stealth: - print(strings._("give_this_url_receive_stealth")) - print(url) - print(app.auth_string) - else: - print(strings._("give_this_url_receive")) - print(url) + if autostart_timer > 0: + print(strings._('server_started')) else: - if stealth: - print(strings._("give_this_url_stealth")) - print(url) - print(app.auth_string) + if mode == 'receive': + print(strings._('receive_mode_data_dir').format(common.settings.get('data_dir'))) + print('') + print(strings._('receive_mode_warning')) + print('') + + if stealth: + print(strings._("give_this_url_receive_stealth")) + print(url) + print(app.auth_string) + else: + print(strings._("give_this_url_receive")) + print(url) else: - print(strings._("give_this_url")) - print(url) + if stealth: + print(strings._("give_this_url_stealth")) + print(url) + print(app.auth_string) + else: + print(strings._("give_this_url")) + print(url) print('') print(strings._("ctrlc_to_stop")) # Wait for app to close while t.is_alive(): - if app.shutdown_timeout > 0: - # if the shutdown timer was set and has run out, stop the server - if not app.shutdown_timer.is_alive(): + if app.autostop_timer > 0: + # if the auto-stop timer was set and has run out, stop the server + if not app.autostop_timer_thread.is_alive(): if mode == 'share': # If there were no attempts to download the share, or all downloads are done, we can stop if web.share_mode.download_count == 0 or web.done: - print(strings._("close_on_timeout")) + print(strings._("close_on_autostop_timer")) web.stop(app.port) break if mode == 'receive': if web.receive_mode.upload_count == 0 or not web.receive_mode.uploads_in_progress: - print(strings._("close_on_timeout")) + print(strings._("close_on_autostop_timer")) web.stop(app.port) break else: diff --git a/onionshare/common.py b/onionshare/common.py index fcb9ca6d..02668507 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -485,7 +485,7 @@ class Common(object): return total_size -class ShutdownTimer(threading.Thread): +class AutoStopTimer(threading.Thread): """ Background thread sleeps t hours and returns. """ @@ -498,6 +498,6 @@ class ShutdownTimer(threading.Thread): self.time = time def run(self): - self.common.log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time)) + self.common.log('AutoStopTimer', 'Server will shut down after {} seconds'.format(self.time)) time.sleep(self.time) return 1 diff --git a/onionshare/onion.py b/onionshare/onion.py index ed4fde7b..51336df9 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -133,6 +133,8 @@ class Onion(object): self.stealth = False self.service_id = None + self.scheduled_key = None + self.scheduled_auth_cookie = None # Is bundled tor supported? if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): @@ -152,7 +154,7 @@ class Onion(object): # Start out not connected to Tor self.connected_to_tor = False - def connect(self, custom_settings=False, config=False, tor_status_update_func=None): + def connect(self, custom_settings=False, config=False, tor_status_update_func=None, connect_timeout=120): self.common.log('Onion', 'connect') # Either use settings that are passed in, or use them from common @@ -283,14 +285,16 @@ class Onion(object): if self.settings.get('tor_bridges_use_custom_bridges') or \ self.settings.get('tor_bridges_use_obfs4') or \ self.settings.get('tor_bridges_use_meek_lite_azure'): - connect_timeout = 150 - else: - # Timeout after 120 seconds - connect_timeout = 120 + # Only override timeout if a custom timeout has not been passed in + if connect_timeout == 120: + connect_timeout = 150 if time.time() - start_ts > connect_timeout: print("") - self.tor_proc.terminate() - raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout')) + try: + self.tor_proc.terminate() + raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout')) + except FileNotFoundError: + pass elif self.settings.get('connection_type') == 'automatic': # Automatically try to guess the right way to connect to Tor Browser @@ -423,27 +427,31 @@ class Onion(object): return False - def start_onion_service(self, port): + def start_onion_service(self, port, await_publication, save_scheduled_key=False): """ Start a onion service on port 80, pointing to the given port, and return the onion hostname. """ self.common.log('Onion', 'start_onion_service') - self.auth_string = None + if not self.supports_ephemeral: raise TorTooOld(strings._('error_ephemeral_not_supported')) if self.stealth and not self.supports_stealth: raise TorTooOld(strings._('error_stealth_not_supported')) - print(strings._("config_onion_service").format(int(port))) + if not save_scheduled_key: + print(strings._("config_onion_service").format(int(port))) if self.stealth: if self.settings.get('hidservauth_string'): hidservauth_string = self.settings.get('hidservauth_string').split()[2] basic_auth = {'onionshare':hidservauth_string} else: - basic_auth = {'onionshare':None} + if self.scheduled_auth_cookie: + basic_auth = {'onionshare':self.scheduled_auth_cookie} + else: + basic_auth = {'onionshare':None} else: basic_auth = None @@ -455,6 +463,14 @@ class Onion(object): # Assume it was a v3 key. Stem will throw an error if it's something illegible key_type = "ED25519-V3" + elif self.scheduled_key: + key_content = self.scheduled_key + if self.is_v2_key(key_content): + key_type = "RSA1024" + else: + # Assume it was a v3 key. Stem will throw an error if it's something illegible + key_type = "ED25519-V3" + else: key_type = "NEW" # Work out if we can support v3 onion services, which are preferred @@ -474,7 +490,6 @@ class Onion(object): if key_type == "NEW": debug_message += ', key_content={}'.format(key_content) self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message)) - await_publication = True try: if basic_auth != None: res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content) @@ -493,6 +508,12 @@ class Onion(object): if not self.settings.get('private_key'): self.settings.set('private_key', res.private_key) + # If we were scheduling a future share, register the private key for later re-use + if save_scheduled_key: + self.scheduled_key = res.private_key + else: + self.scheduled_key = None + if self.stealth: # Similar to the PrivateKey, the Control port only returns the ClientAuth # in the response if it was responsible for creating the basic_auth password @@ -507,8 +528,19 @@ class Onion(object): self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) self.settings.set('hidservauth_string', self.auth_string) else: - auth_cookie = list(res.client_auth.values())[0] - self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) + if not self.scheduled_auth_cookie: + auth_cookie = list(res.client_auth.values())[0] + self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) + if save_scheduled_key: + # Register the HidServAuth for the scheduled share + self.scheduled_auth_cookie = auth_cookie + else: + self.scheduled_auth_cookie = None + else: + self.auth_string = 'HidServAuth {} {}'.format(onion_host, self.scheduled_auth_cookie) + if not save_scheduled_key: + # We've used the scheduled share's HidServAuth. Reset it to None for future shares + self.scheduled_auth_cookie = None if onion_host is not None: self.settings.save() diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 551b8314..e746bae1 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -22,14 +22,14 @@ import os, shutil from . import common, strings from .onion import TorTooOld, TorErrorProtocolError -from .common import ShutdownTimer +from .common import AutoStopTimer class OnionShare(object): """ OnionShare is the main application class. Pass in options and run start_onion_service and it will do the magic. """ - def __init__(self, common, onion, local_only=False, shutdown_timeout=0): + def __init__(self, common, onion, local_only=False, autostop_timer=0): self.common = common self.common.log('OnionShare', '__init__') @@ -49,9 +49,9 @@ class OnionShare(object): self.local_only = local_only # optionally shut down after N hours - self.shutdown_timeout = shutdown_timeout - # init timing thread - self.shutdown_timer = None + self.autostop_timer = autostop_timer + # init auto-stop timer thread + self.autostop_timer_thread = None def set_stealth(self, stealth): self.common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth)) @@ -68,7 +68,7 @@ class OnionShare(object): except: raise OSError(strings._('no_available_port')) - def start_onion_service(self): + def start_onion_service(self, await_publication=True, save_scheduled_key=False): """ Start the onionshare onion service. """ @@ -77,14 +77,14 @@ class OnionShare(object): if not self.port: self.choose_port() - if self.shutdown_timeout > 0: - self.shutdown_timer = ShutdownTimer(self.common, self.shutdown_timeout) + if self.autostop_timer > 0: + self.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer) if self.local_only: self.onion_host = '127.0.0.1:{0:d}'.format(self.port) return - self.onion_host = self.onion.start_onion_service(self.port) + self.onion_host = self.onion.start_onion_service(self.port, await_publication, save_scheduled_key) if self.stealth: self.auth_string = self.onion.auth_string diff --git a/onionshare/settings.py b/onionshare/settings.py index 68cbb857..1eaa4e40 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -84,7 +84,8 @@ class Settings(object): 'auth_type': 'no_auth', 'auth_password': '', 'close_after_first_download': True, - 'shutdown_timeout': False, + 'autostop_timer': False, + 'autostart_timer': False, 'use_stealth': False, 'use_autoupdate': True, 'autoupdate_timestamp': None, diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index d6ef86ad..dcf69a96 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -112,12 +112,14 @@ class ReceiveModeWeb(object): else: flash(msg, 'info') else: + msg = 'Sent ' for filename in filenames: - msg = 'Sent {}'.format(filename) - if ajax: - info_flashes.append(msg) - else: - flash(msg, 'info') + msg += '{}, '.format(filename) + msg = msg.rstrip(', ') + if ajax: + info_flashes.append(msg) + else: + flash(msg, 'info') if self.can_upload: if ajax: @@ -297,6 +299,7 @@ class ReceiveModeRequest(Request): new_receive_mode_dir = '{}-{}'.format(self.receive_mode_dir, i) try: os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False) + self.receive_mode_dir = new_receive_mode_dir break except OSError: pass diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 26694e59..b61d2fb3 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -231,13 +231,11 @@ class Web(object): pass self.running = False - def start(self, port, stay_open=False, public_mode=False, persistent_slug=None): + def start(self, port, stay_open=False, public_mode=False, slug=None): """ Start the flask web server. """ - self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug)) - if not public_mode: - self.generate_slug(persistent_slug) + self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, slug={}'.format(port, stay_open, public_mode, slug)) self.stay_open = stay_open @@ -267,7 +265,7 @@ class Web(object): self.stop_q.put(True) # Reset any slug that was in use - self.slug = '' + self.slug = None # To stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown if self.running: |