mirror of
https://github.com/onionshare/onionshare.git
synced 2025-01-10 11:47:27 -03:00
Start refactoring Onion to allow for managing a separate onion service for each tab
This commit is contained in:
parent
81584e12ff
commit
28bc37d16f
5 changed files with 82 additions and 114 deletions
|
@ -36,7 +36,7 @@ class ModeSettings:
|
||||||
"enabled": False,
|
"enabled": False,
|
||||||
"mode": None,
|
"mode": None,
|
||||||
"private_key": None,
|
"private_key": None,
|
||||||
"hidservauth": None,
|
"hidservauth_string": None,
|
||||||
"password": None,
|
"password": None,
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
@ -45,6 +45,7 @@ class ModeSettings:
|
||||||
"autostop_timer": False,
|
"autostop_timer": False,
|
||||||
"legacy": False,
|
"legacy": False,
|
||||||
"client_auth": False,
|
"client_auth": False,
|
||||||
|
"service_id": None,
|
||||||
},
|
},
|
||||||
"share": {"autostop_sharing": True, "filenames": []},
|
"share": {"autostop_sharing": True, "filenames": []},
|
||||||
"receive": {"data_dir": self.build_default_receive_data_dir()},
|
"receive": {"data_dir": self.build_default_receive_data_dir()},
|
||||||
|
|
|
@ -152,14 +152,8 @@ class Onion(object):
|
||||||
|
|
||||||
def __init__(self, common):
|
def __init__(self, common):
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log("Onion", "__init__")
|
self.common.log("Onion", "__init__")
|
||||||
|
|
||||||
self.stealth = False
|
|
||||||
self.service_id = None
|
|
||||||
self.scheduled_key = None
|
|
||||||
self.scheduled_auth_cookie = None
|
|
||||||
|
|
||||||
# Is bundled tor supported?
|
# Is bundled tor supported?
|
||||||
if (
|
if (
|
||||||
self.common.platform == "Windows" or self.common.platform == "Darwin"
|
self.common.platform == "Windows" or self.common.platform == "Darwin"
|
||||||
|
@ -570,48 +564,32 @@ class Onion(object):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def start_onion_service(self, port, await_publication, save_scheduled_key=False):
|
def start_onion_service(
|
||||||
|
self, mode_settings, port, await_publication, save_scheduled_key=False
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Start a onion service on port 80, pointing to the given port, and
|
Start a onion service on port 80, pointing to the given port, and
|
||||||
return the onion hostname.
|
return the onion hostname.
|
||||||
"""
|
"""
|
||||||
self.common.log("Onion", "start_onion_service")
|
self.common.log("Onion", "start_onion_service", f"port={port}")
|
||||||
# Settings may have changed in the frontend but not updated in our settings object,
|
|
||||||
# such as persistence. Reload the settings now just to be sure.
|
|
||||||
self.settings.load()
|
|
||||||
|
|
||||||
self.auth_string = None
|
|
||||||
|
|
||||||
if not self.supports_ephemeral:
|
if not self.supports_ephemeral:
|
||||||
raise TorTooOld(strings._("error_ephemeral_not_supported"))
|
raise TorTooOld(strings._("error_ephemeral_not_supported"))
|
||||||
if self.stealth and not self.supports_stealth:
|
if mode_settings.get("general", "client_auth") and not self.supports_stealth:
|
||||||
raise TorTooOld(strings._("error_stealth_not_supported"))
|
raise TorTooOld(strings._("error_stealth_not_supported"))
|
||||||
|
|
||||||
if not save_scheduled_key:
|
if mode_settings.get("general", "client_auth") and mode_settings.get(
|
||||||
print(f"Setting up onion service on port {port}.")
|
"general", "hidservauth_string"
|
||||||
|
):
|
||||||
if self.stealth:
|
auth_cookie = mode_settings.get("persistent", "hidservauth_string").split()[
|
||||||
if self.settings.get("hidservauth_string"):
|
2
|
||||||
hidservauth_string = self.settings.get("hidservauth_string").split()[2]
|
]
|
||||||
basic_auth = {"onionshare": hidservauth_string}
|
basic_auth = {"onionshare": auth_cookie}
|
||||||
else:
|
|
||||||
if self.scheduled_auth_cookie:
|
|
||||||
basic_auth = {"onionshare": self.scheduled_auth_cookie}
|
|
||||||
else:
|
|
||||||
basic_auth = {"onionshare": None}
|
|
||||||
else:
|
else:
|
||||||
basic_auth = None
|
basic_auth = None
|
||||||
|
|
||||||
if self.settings.get("private_key"):
|
if mode_settings.get("persistent", "private_key"):
|
||||||
key_content = self.settings.get("private_key")
|
key_content = mode_settings.get("persistent", "private_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"
|
|
||||||
|
|
||||||
elif self.scheduled_key:
|
|
||||||
key_content = self.scheduled_key
|
|
||||||
if self.is_v2_key(key_content):
|
if self.is_v2_key(key_content):
|
||||||
key_type = "RSA1024"
|
key_type = "RSA1024"
|
||||||
else:
|
else:
|
||||||
|
@ -621,9 +599,7 @@ class Onion(object):
|
||||||
else:
|
else:
|
||||||
key_type = "NEW"
|
key_type = "NEW"
|
||||||
# Work out if we can support v3 onion services, which are preferred
|
# Work out if we can support v3 onion services, which are preferred
|
||||||
if self.supports_v3_onions and not self.settings.get(
|
if self.supports_v3_onions and not mode_settings.get("general", "legacy"):
|
||||||
"use_legacy_v2_onions"
|
|
||||||
):
|
|
||||||
key_content = "ED25519-V3"
|
key_content = "ED25519-V3"
|
||||||
else:
|
else:
|
||||||
# fall back to v2 onion services
|
# fall back to v2 onion services
|
||||||
|
@ -634,87 +610,62 @@ class Onion(object):
|
||||||
if (
|
if (
|
||||||
key_type == "NEW"
|
key_type == "NEW"
|
||||||
and key_content == "ED25519-V3"
|
and key_content == "ED25519-V3"
|
||||||
and not self.settings.get("use_legacy_v2_onions")
|
and not mode_settings.get("general", "legacy")
|
||||||
):
|
):
|
||||||
basic_auth = None
|
basic_auth = None
|
||||||
self.stealth = False
|
|
||||||
|
|
||||||
debug_message = f"key_type={key_type}"
|
debug_message = f"key_type={key_type}"
|
||||||
if key_type == "NEW":
|
if key_type == "NEW":
|
||||||
debug_message += f", key_content={key_content}"
|
debug_message += f", key_content={key_content}"
|
||||||
self.common.log("Onion", "start_onion_service", debug_message)
|
self.common.log("Onion", "start_onion_service", debug_message)
|
||||||
try:
|
try:
|
||||||
if basic_auth != None:
|
res = self.c.create_ephemeral_hidden_service(
|
||||||
res = self.c.create_ephemeral_hidden_service(
|
{80: port},
|
||||||
{80: port},
|
await_publication=await_publication,
|
||||||
await_publication=await_publication,
|
basic_auth=basic_auth,
|
||||||
basic_auth=basic_auth,
|
key_type=key_type,
|
||||||
key_type=key_type,
|
key_content=key_content,
|
||||||
key_content=key_content,
|
)
|
||||||
)
|
|
||||||
else:
|
|
||||||
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
|
|
||||||
res = self.c.create_ephemeral_hidden_service(
|
|
||||||
{80: port},
|
|
||||||
await_publication=await_publication,
|
|
||||||
key_type=key_type,
|
|
||||||
key_content=key_content,
|
|
||||||
)
|
|
||||||
|
|
||||||
except ProtocolError as e:
|
except ProtocolError as e:
|
||||||
raise TorErrorProtocolError(
|
raise TorErrorProtocolError(
|
||||||
strings._("error_tor_protocol_error").format(e.args[0])
|
strings._("error_tor_protocol_error").format(e.args[0])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.service_id = res.service_id
|
onion_host = res.service_id + ".onion"
|
||||||
onion_host = self.service_id + ".onion"
|
|
||||||
|
|
||||||
# A new private key was generated and is in the Control port response.
|
# Save the service_id
|
||||||
if self.settings.get("save_private_key"):
|
if not mode_settings.get("general", "service_id"):
|
||||||
if not self.settings.get("private_key"):
|
mode_settings.set("general", "service_id", res.service_id)
|
||||||
self.settings.set("private_key", res.private_key)
|
|
||||||
|
|
||||||
# If we were scheduling a future share, register the private key for later re-use
|
# Save the private key and hidservauth string if persistence is enabled
|
||||||
if save_scheduled_key:
|
if mode_settings.get("persistent", "enabled"):
|
||||||
self.scheduled_key = res.private_key
|
if not mode_settings.get("persistent", "private_key"):
|
||||||
else:
|
mode_settings.set("persistent", "private_key", res.private_key)
|
||||||
self.scheduled_key = None
|
if mode_settings.get("general", "client_auth") and not mode_settings.get(
|
||||||
|
"persistent", "hidservauth_string"
|
||||||
|
):
|
||||||
|
auth_cookie = list(res.client_auth.values())[0]
|
||||||
|
auth_string = f"HidServAuth {onion_host} {auth_cookie}"
|
||||||
|
mode_settings.set("persistent", "hidservauth_string", auth_string)
|
||||||
|
|
||||||
if self.stealth:
|
return onion_host
|
||||||
# Similar to the PrivateKey, the Control port only returns the ClientAuth
|
|
||||||
# in the response if it was responsible for creating the basic_auth password
|
|
||||||
# in the first place.
|
|
||||||
# If we sent the basic_auth (due to a saved hidservauth_string in the settings),
|
|
||||||
# there is no response here, so use the saved value from settings.
|
|
||||||
if self.settings.get("save_private_key"):
|
|
||||||
if self.settings.get("hidservauth_string"):
|
|
||||||
self.auth_string = self.settings.get("hidservauth_string")
|
|
||||||
else:
|
|
||||||
auth_cookie = list(res.client_auth.values())[0]
|
|
||||||
self.auth_string = f"HidServAuth {onion_host} {auth_cookie}"
|
|
||||||
self.settings.set("hidservauth_string", self.auth_string)
|
|
||||||
else:
|
|
||||||
if not self.scheduled_auth_cookie:
|
|
||||||
auth_cookie = list(res.client_auth.values())[0]
|
|
||||||
self.auth_string = f"HidServAuth {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 = (
|
|
||||||
f"HidServAuth {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:
|
def stop_onion_service(self, mode_settings):
|
||||||
self.settings.save()
|
"""
|
||||||
return onion_host
|
Stop a specific onion service
|
||||||
else:
|
"""
|
||||||
raise TorErrorProtocolError(strings._("error_tor_protocol_error_unknown"))
|
onion_host = mode_settings.get("general", "service_id")
|
||||||
|
self.common.log("Onion", "stop_onion_service", f"onion host: {onion_host}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.c.remove_ephemeral_hidden_service(
|
||||||
|
mode_settings.get("general", "service_id")
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
self.common.log(
|
||||||
|
"Onion", "stop_onion_service", f"failed to remove {onion_host}"
|
||||||
|
)
|
||||||
|
|
||||||
def cleanup(self, stop_tor=True):
|
def cleanup(self, stop_tor=True):
|
||||||
"""
|
"""
|
||||||
|
@ -725,22 +676,20 @@ class Onion(object):
|
||||||
# Cleanup the ephemeral onion services, if we have any
|
# Cleanup the ephemeral onion services, if we have any
|
||||||
try:
|
try:
|
||||||
onions = self.c.list_ephemeral_hidden_services()
|
onions = self.c.list_ephemeral_hidden_services()
|
||||||
for onion in onions:
|
for service_id in onions:
|
||||||
|
onion_host = f"{service_id}.onion"
|
||||||
try:
|
try:
|
||||||
self.common.log(
|
self.common.log(
|
||||||
"Onion", "cleanup", f"trying to remove onion {onion}"
|
"Onion", "cleanup", f"trying to remove onion {onion_host}"
|
||||||
)
|
)
|
||||||
self.c.remove_ephemeral_hidden_service(onion)
|
self.c.remove_ephemeral_hidden_service(service_id)
|
||||||
except:
|
except:
|
||||||
self.common.log(
|
self.common.log(
|
||||||
"Onion",
|
"Onion", "cleanup", f"failed to remove onion {onion_host}"
|
||||||
"cleanup",
|
|
||||||
f"could not remove onion {onion}.. moving on anyway",
|
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.service_id = None
|
|
||||||
|
|
||||||
if stop_tor:
|
if stop_tor:
|
||||||
# Stop tor process
|
# Stop tor process
|
||||||
|
|
|
@ -82,12 +82,18 @@ class OnionShare(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.onion_host = self.onion.start_onion_service(
|
self.onion_host = self.onion.start_onion_service(
|
||||||
self.port, await_publication, save_scheduled_key
|
mode_settings, self.port, await_publication, save_scheduled_key
|
||||||
)
|
)
|
||||||
|
|
||||||
if mode_settings.get("general", "client_auth"):
|
if mode_settings.get("general", "client_auth"):
|
||||||
self.auth_string = self.onion.auth_string
|
self.auth_string = self.onion.auth_string
|
||||||
|
|
||||||
|
def stop_onion_service(self, mode_settings):
|
||||||
|
"""
|
||||||
|
Stop the onion service
|
||||||
|
"""
|
||||||
|
self.onion.stop_onion_service(mode_settings)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""
|
"""
|
||||||
Shut everything down and clean up temporary files, etc.
|
Shut everything down and clean up temporary files, etc.
|
||||||
|
|
|
@ -362,7 +362,7 @@ class Tab(QtWidgets.QWidget):
|
||||||
|
|
||||||
def stop_server_finished(self):
|
def stop_server_finished(self):
|
||||||
# When the server stopped, cleanup the ephemeral onion service
|
# When the server stopped, cleanup the ephemeral onion service
|
||||||
self.common.gui.onion.cleanup(stop_tor=False)
|
self.get_mode().app.stop_onion_service(self.settings)
|
||||||
|
|
||||||
def timer_callback(self):
|
def timer_callback(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -20,7 +20,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import time
|
import time
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from onionshare.onion import *
|
from onionshare import strings
|
||||||
|
from onionshare.onion import (
|
||||||
|
TorTooOld,
|
||||||
|
TorErrorInvalidSetting,
|
||||||
|
TorErrorAutomatic,
|
||||||
|
TorErrorSocketPort,
|
||||||
|
TorErrorSocketFile,
|
||||||
|
TorErrorMissingPassword,
|
||||||
|
TorErrorUnreadableCookieFile,
|
||||||
|
TorErrorAuthError,
|
||||||
|
TorErrorProtocolError,
|
||||||
|
BundledTorTimeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OnionThread(QtCore.QThread):
|
class OnionThread(QtCore.QThread):
|
||||||
|
@ -64,7 +76,7 @@ class OnionThread(QtCore.QThread):
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
self.success_early.emit()
|
self.success_early.emit()
|
||||||
# Unregister the onion so we can use it in the next OnionThread
|
# Unregister the onion so we can use it in the next OnionThread
|
||||||
self.mode.app.onion.cleanup(False)
|
self.mode.app.start_onion_service(self.mode.settings)
|
||||||
else:
|
else:
|
||||||
self.mode.app.start_onion_service(
|
self.mode.app.start_onion_service(
|
||||||
self.mode.settings, await_publication=True
|
self.mode.settings, await_publication=True
|
||||||
|
|
Loading…
Reference in a new issue