summaryrefslogtreecommitdiff
path: root/onionshare
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2019-04-18 19:57:46 -0700
committerMicah Lee <micah@micahflee.com>2019-04-18 19:57:46 -0700
commited1ada9c065881740a97416b70132067fed06029 (patch)
tree1e87a069dc3cd638958042e76ba476ecdec142e5 /onionshare
parent436dd3052d8137689802705e5e301cd9aa60ae92 (diff)
parent77e0d76bc1b2de6ba72e90336bf8f0111416a8ef (diff)
downloadonionshare-ed1ada9c065881740a97416b70132067fed06029.tar.gz
onionshare-ed1ada9c065881740a97416b70132067fed06029.zip
Merge branch 'develop' into 918_old_linux
Diffstat (limited to 'onionshare')
-rw-r--r--onionshare/__init__.py116
-rw-r--r--onionshare/common.py4
-rw-r--r--onionshare/onion.py60
-rw-r--r--onionshare/onionshare.py18
-rw-r--r--onionshare/settings.py3
-rw-r--r--onionshare/web/receive_mode.py13
-rw-r--r--onionshare/web/web.py8
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: