2015-09-08 01:34:54 -04:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
OnionShare | https://onionshare.org/
|
|
|
|
|
2018-04-24 13:07:59 -04:00
|
|
|
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
2015-09-08 01:34:54 -04:00
|
|
|
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from stem.control import Controller
|
2017-04-17 23:49:06 -04:00
|
|
|
from stem import ProtocolError, SocketClosed
|
2017-01-07 20:31:26 -05:00
|
|
|
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
|
2018-10-11 22:38:05 -04:00
|
|
|
from Crypto.PublicKey import RSA
|
2018-08-21 05:31:02 -04:00
|
|
|
import base64, os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2018-08-21 05:31:02 -04:00
|
|
|
from distutils.version import LooseVersion as Version
|
2017-05-16 14:05:48 -04:00
|
|
|
from . import common, strings
|
2016-12-28 22:52:21 -05:00
|
|
|
from .settings import Settings
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2016-12-29 15:57:58 -05:00
|
|
|
class TorErrorAutomatic(Exception):
|
|
|
|
"""
|
|
|
|
OnionShare is failing to connect and authenticate to the Tor controller,
|
|
|
|
using automatic settings that should work with Tor Browser.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2016-12-29 12:58:13 -05:00
|
|
|
class TorErrorInvalidSetting(Exception):
|
|
|
|
"""
|
|
|
|
This exception is raised if the settings just don't make sense.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class TorErrorSocketPort(Exception):
|
|
|
|
"""
|
|
|
|
OnionShare can't connect to the Tor controller using the supplied address and port.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class TorErrorSocketFile(Exception):
|
|
|
|
"""
|
|
|
|
OnionShare can't connect to the Tor controller using the supplied socket file.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class TorErrorMissingPassword(Exception):
|
|
|
|
"""
|
|
|
|
OnionShare connected to the Tor controller, but it requires a password.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class TorErrorUnreadableCookieFile(Exception):
|
|
|
|
"""
|
|
|
|
OnionShare connected to the Tor controller, but your user does not have permission
|
|
|
|
to access the cookie file.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2017-01-07 20:31:26 -05:00
|
|
|
class TorErrorAuthError(Exception):
|
|
|
|
"""
|
|
|
|
OnionShare connected to the address and port, but can't authenticate. It's possible
|
|
|
|
that a Tor controller isn't listening on this port.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2017-02-22 19:45:52 -05:00
|
|
|
class TorErrorProtocolError(Exception):
|
|
|
|
"""
|
|
|
|
This exception is raised if onionshare connects to the Tor controller, but it
|
|
|
|
isn't acting like a Tor controller (such as in Whonix).
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2016-12-22 19:56:39 -05:00
|
|
|
class TorTooOld(Exception):
|
|
|
|
"""
|
|
|
|
This exception is raised if onionshare needs to use a feature of Tor or stem
|
|
|
|
(like stealth ephemeral onion services) but the version you have installed
|
|
|
|
is too old.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2017-04-08 21:10:17 -04:00
|
|
|
class BundledTorNotSupported(Exception):
|
|
|
|
"""
|
|
|
|
This exception is raised if onionshare is set to use the bundled Tor binary,
|
|
|
|
but it's not supported on that platform, or in dev mode.
|
|
|
|
"""
|
|
|
|
|
2017-04-14 13:00:56 -04:00
|
|
|
class BundledTorTimeout(Exception):
|
|
|
|
"""
|
|
|
|
This exception is raised if onionshare is set to use the bundled Tor binary,
|
|
|
|
but Tor doesn't finish connecting promptly.
|
|
|
|
"""
|
|
|
|
|
2017-04-17 23:49:06 -04:00
|
|
|
class BundledTorCanceled(Exception):
|
|
|
|
"""
|
|
|
|
This exception is raised if onionshare is set to use the bundled Tor binary,
|
|
|
|
and the user cancels connecting to Tor
|
|
|
|
"""
|
|
|
|
|
2017-05-17 15:00:42 -04:00
|
|
|
class BundledTorBroken(Exception):
|
|
|
|
"""
|
|
|
|
This exception is raised if onionshare is set to use the bundled Tor binary,
|
|
|
|
but the process seems to fail to run.
|
|
|
|
"""
|
|
|
|
|
2016-09-05 14:16:54 -04:00
|
|
|
class Onion(object):
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
2016-09-05 14:16:54 -04:00
|
|
|
Onion is an abstraction layer for connecting to the Tor control port and
|
2017-02-22 17:10:06 -05:00
|
|
|
creating onion services. OnionShare supports creating onion services by
|
|
|
|
connecting to the Tor controller and using ADD_ONION, DEL_ONION.
|
2017-04-14 01:56:47 -04:00
|
|
|
|
|
|
|
stealth: Should the onion service be stealth?
|
|
|
|
|
|
|
|
settings: A Settings object. If it's not passed in, load from disk.
|
|
|
|
|
|
|
|
bundled_connection_func: If the tor connection type is bundled, optionally
|
|
|
|
call this function and pass in a status string while connecting to tor. This
|
|
|
|
is necessary for status updates to reach the GUI.
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
2018-03-08 13:18:31 -05:00
|
|
|
def __init__(self, common):
|
|
|
|
self.common = common
|
|
|
|
|
|
|
|
self.common.log('Onion', '__init__')
|
2017-05-16 14:23:18 -04:00
|
|
|
|
2017-04-17 22:13:53 -04:00
|
|
|
self.stealth = False
|
2017-02-22 17:10:06 -05:00
|
|
|
self.service_id = None
|
2019-03-04 18:28:27 -05:00
|
|
|
self.scheduled_key = None
|
2019-03-11 00:55:17 -04:00
|
|
|
self.scheduled_auth_cookie = None
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2017-04-14 01:22:34 -04:00
|
|
|
# Is bundled tor supported?
|
2018-03-08 13:18:31 -05:00
|
|
|
if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
|
2017-04-17 22:13:53 -04:00
|
|
|
self.bundle_tor_supported = False
|
2017-04-14 01:22:34 -04:00
|
|
|
else:
|
2017-04-17 22:13:53 -04:00
|
|
|
self.bundle_tor_supported = True
|
2017-04-14 01:22:34 -04:00
|
|
|
|
|
|
|
# Set the path of the tor binary, for bundled tor
|
2018-03-08 13:18:31 -05:00
|
|
|
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
|
2017-04-14 01:22:34 -04:00
|
|
|
|
|
|
|
# The tor process
|
2017-04-15 14:43:19 -04:00
|
|
|
self.tor_proc = None
|
2017-04-14 01:22:34 -04:00
|
|
|
|
2018-12-06 02:05:25 -05:00
|
|
|
# The Tor controller
|
|
|
|
self.c = None
|
|
|
|
|
2017-05-16 16:09:27 -04:00
|
|
|
# Start out not connected to Tor
|
|
|
|
self.connected_to_tor = False
|
|
|
|
|
2019-03-12 00:29:07 -04:00
|
|
|
def connect(self, custom_settings=False, config=False, tor_status_update_func=None, connect_timeout=120):
|
2018-03-08 13:18:31 -05:00
|
|
|
self.common.log('Onion', 'connect')
|
2017-05-16 14:23:18 -04:00
|
|
|
|
2018-03-13 06:28:47 -04:00
|
|
|
# Either use settings that are passed in, or use them from common
|
|
|
|
if custom_settings:
|
|
|
|
self.settings = custom_settings
|
2019-04-20 00:43:04 -04:00
|
|
|
elif config:
|
|
|
|
self.common.load_settings(config)
|
|
|
|
self.settings = self.common.settings
|
2017-04-17 22:13:53 -04:00
|
|
|
else:
|
2019-04-19 20:31:34 -04:00
|
|
|
self.common.load_settings()
|
2018-03-13 06:28:47 -04:00
|
|
|
self.settings = self.common.settings
|
2017-04-17 22:13:53 -04:00
|
|
|
|
2019-04-19 20:31:34 -04:00
|
|
|
strings.load_strings(self.common)
|
2017-04-17 22:13:53 -04:00
|
|
|
# The Tor controller
|
2016-12-29 12:58:13 -05:00
|
|
|
self.c = None
|
2016-12-22 16:39:32 -05:00
|
|
|
|
2017-04-08 21:10:17 -04:00
|
|
|
if self.settings.get('connection_type') == 'bundled':
|
2017-04-17 22:13:53 -04:00
|
|
|
if not self.bundle_tor_supported:
|
2017-04-08 21:10:17 -04:00
|
|
|
raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported'))
|
|
|
|
|
2017-04-14 01:22:34 -04:00
|
|
|
# Create a torrc for this session
|
2018-11-25 20:17:56 -05:00
|
|
|
self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir())
|
|
|
|
self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name))
|
2017-04-14 21:33:44 -04:00
|
|
|
|
2018-11-25 19:50:20 -05:00
|
|
|
# Create the torrc
|
|
|
|
with open(self.common.get_resource_path('torrc_template')) as f:
|
|
|
|
torrc_template = f.read()
|
|
|
|
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
|
|
|
try:
|
|
|
|
self.tor_socks_port = self.common.get_available_port(1000, 65535)
|
|
|
|
except:
|
|
|
|
raise OSError(strings._('no_available_port'))
|
|
|
|
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
|
|
|
|
|
|
|
if self.common.platform == 'Windows' or self.common.platform == "Darwin":
|
|
|
|
# Windows doesn't support unix sockets, so it must use a network port.
|
|
|
|
# macOS can't use unix sockets either because socket filenames are limited to
|
|
|
|
# 100 chars, and the macOS sandbox forces us to put the socket file in a place
|
|
|
|
# with a really long path.
|
|
|
|
torrc_template += 'ControlPort {{control_port}}\n'
|
2017-12-06 16:49:10 -05:00
|
|
|
try:
|
2018-03-08 13:18:31 -05:00
|
|
|
self.tor_control_port = self.common.get_available_port(1000, 65535)
|
2017-12-06 16:49:10 -05:00
|
|
|
except:
|
|
|
|
raise OSError(strings._('no_available_port'))
|
2017-04-14 21:33:44 -04:00
|
|
|
self.tor_control_socket = None
|
|
|
|
else:
|
2018-11-25 19:50:20 -05:00
|
|
|
# Linux and BSD can use unix sockets
|
|
|
|
torrc_template += 'ControlSocket {{control_socket}}\n'
|
2017-04-14 21:33:44 -04:00
|
|
|
self.tor_control_port = None
|
|
|
|
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
|
|
|
|
|
2017-04-14 01:22:34 -04:00
|
|
|
torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name)
|
2017-04-14 21:33:44 -04:00
|
|
|
torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port))
|
|
|
|
torrc_template = torrc_template.replace('{{control_socket}}', str(self.tor_control_socket))
|
2017-04-14 01:22:34 -04:00
|
|
|
torrc_template = torrc_template.replace('{{cookie_auth_file}}', self.tor_cookie_auth_file)
|
|
|
|
torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path)
|
|
|
|
torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path)
|
2017-04-14 21:33:44 -04:00
|
|
|
torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port))
|
2018-11-25 19:50:20 -05:00
|
|
|
|
2017-05-23 07:22:14 -04:00
|
|
|
with open(self.tor_torrc, 'w') as f:
|
2017-05-23 18:16:27 -04:00
|
|
|
f.write(torrc_template)
|
2017-04-14 01:22:34 -04:00
|
|
|
|
2017-12-10 22:53:13 -05:00
|
|
|
# Bridge support
|
|
|
|
if self.settings.get('tor_bridges_use_obfs4'):
|
2018-01-14 20:49:29 -05:00
|
|
|
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
|
2018-03-08 13:18:31 -05:00
|
|
|
with open(self.common.get_resource_path('torrc_template-obfs4')) as o:
|
2017-12-10 22:53:13 -05:00
|
|
|
for line in o:
|
|
|
|
f.write(line)
|
2018-02-15 18:19:53 -05:00
|
|
|
elif self.settings.get('tor_bridges_use_meek_lite_azure'):
|
|
|
|
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
|
2018-03-08 13:18:31 -05:00
|
|
|
with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o:
|
2018-02-15 18:19:53 -05:00
|
|
|
for line in o:
|
|
|
|
f.write(line)
|
|
|
|
|
2017-12-10 22:53:13 -05:00
|
|
|
if self.settings.get('tor_bridges_use_custom_bridges'):
|
2018-01-18 23:31:11 -05:00
|
|
|
if 'obfs4' in self.settings.get('tor_bridges_use_custom_bridges'):
|
|
|
|
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
|
2018-02-15 18:19:53 -05:00
|
|
|
elif 'meek_lite' in self.settings.get('tor_bridges_use_custom_bridges'):
|
|
|
|
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
|
2017-12-10 22:53:13 -05:00
|
|
|
f.write(self.settings.get('tor_bridges_use_custom_bridges'))
|
2017-12-11 00:20:42 -05:00
|
|
|
f.write('\nUseBridges 1')
|
2017-12-10 22:53:13 -05:00
|
|
|
|
2017-04-15 14:34:03 -04:00
|
|
|
# Execute a tor subprocess
|
2017-04-14 13:00:56 -04:00
|
|
|
start_ts = time.time()
|
2018-03-08 13:18:31 -05:00
|
|
|
if self.common.platform == 'Windows':
|
2017-04-15 14:34:03 -04:00
|
|
|
# In Windows, hide console window when opening tor.exe subprocess
|
|
|
|
startupinfo = subprocess.STARTUPINFO()
|
|
|
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
|
|
self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
|
|
|
|
else:
|
|
|
|
self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
|
|
|
|
# Wait for the tor controller to start
|
2017-05-18 20:34:36 -04:00
|
|
|
time.sleep(2)
|
2017-04-14 01:22:34 -04:00
|
|
|
|
|
|
|
# Connect to the controller
|
2017-05-17 15:00:42 -04:00
|
|
|
try:
|
2018-11-25 19:50:20 -05:00
|
|
|
if self.common.platform == 'Windows' or self.common.platform == "Darwin":
|
2017-05-17 15:00:42 -04:00
|
|
|
self.c = Controller.from_port(port=self.tor_control_port)
|
|
|
|
self.c.authenticate()
|
|
|
|
else:
|
|
|
|
self.c = Controller.from_socket_file(path=self.tor_control_socket)
|
|
|
|
self.c.authenticate()
|
|
|
|
except Exception as e:
|
2018-09-30 20:47:10 -04:00
|
|
|
raise BundledTorBroken(strings._('settings_error_bundled_tor_broken').format(e.args[0]))
|
2017-04-14 01:22:34 -04:00
|
|
|
|
|
|
|
while True:
|
2017-04-17 23:49:06 -04:00
|
|
|
try:
|
|
|
|
res = self.c.get_info("status/bootstrap-phase")
|
|
|
|
except SocketClosed:
|
|
|
|
raise BundledTorCanceled()
|
|
|
|
|
2017-04-14 01:22:34 -04:00
|
|
|
res_parts = shlex.split(res)
|
|
|
|
progress = res_parts[2].split('=')[1]
|
|
|
|
summary = res_parts[4].split('=')[1]
|
|
|
|
|
|
|
|
# "\033[K" clears the rest of the line
|
2019-04-21 15:58:53 -04:00
|
|
|
print("Connecting to the Tor network: {}% - {}{}".format(progress, summary, "\033[K"), end="\r")
|
2017-04-14 01:56:47 -04:00
|
|
|
|
2017-04-17 23:26:35 -04:00
|
|
|
if callable(tor_status_update_func):
|
2017-05-14 21:30:45 -04:00
|
|
|
if not tor_status_update_func(progress, summary):
|
|
|
|
# If the dialog was canceled, stop connecting to Tor
|
2018-03-08 13:18:31 -05:00
|
|
|
self.common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor')
|
2017-05-14 21:30:45 -04:00
|
|
|
print()
|
|
|
|
return False
|
2017-04-14 01:56:47 -04:00
|
|
|
|
2017-04-14 01:22:34 -04:00
|
|
|
if summary == 'Done':
|
|
|
|
print("")
|
|
|
|
break
|
2017-05-16 14:47:18 -04:00
|
|
|
time.sleep(0.2)
|
2017-04-14 01:22:34 -04:00
|
|
|
|
2017-12-14 01:31:15 -05:00
|
|
|
# If using bridges, it might take a bit longer to connect to Tor
|
2018-02-15 18:19:53 -05:00
|
|
|
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'):
|
2019-03-12 00:36:49 -04:00
|
|
|
# Only override timeout if a custom timeout has not been passed in
|
|
|
|
if connect_timeout == 120:
|
|
|
|
connect_timeout = 150
|
2017-12-14 01:31:15 -05:00
|
|
|
if time.time() - start_ts > connect_timeout:
|
2017-04-14 13:00:56 -04:00
|
|
|
print("")
|
2019-03-12 00:29:07 -04:00
|
|
|
try:
|
|
|
|
self.tor_proc.terminate()
|
|
|
|
raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout'))
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
2017-04-14 13:00:56 -04:00
|
|
|
|
2017-04-14 01:22:34 -04:00
|
|
|
elif self.settings.get('connection_type') == 'automatic':
|
2016-12-29 12:58:13 -05:00
|
|
|
# Automatically try to guess the right way to connect to Tor Browser
|
|
|
|
|
2016-12-29 16:36:29 -05:00
|
|
|
# Try connecting to control port
|
|
|
|
found_tor = False
|
2016-12-29 15:57:58 -05:00
|
|
|
|
2016-12-29 16:36:29 -05:00
|
|
|
# If the TOR_CONTROL_PORT environment variable is set, use that
|
|
|
|
env_port = os.environ.get('TOR_CONTROL_PORT')
|
|
|
|
if env_port:
|
|
|
|
try:
|
|
|
|
self.c = Controller.from_port(port=int(env_port))
|
|
|
|
found_tor = True
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Otherwise, try default ports for Tor Browser, Tor Messenger, and system tor
|
|
|
|
try:
|
|
|
|
ports = [9151, 9153, 9051]
|
|
|
|
for port in ports:
|
|
|
|
self.c = Controller.from_port(port=port)
|
|
|
|
found_tor = True
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
2017-01-06 14:46:41 -05:00
|
|
|
# If this still didn't work, try guessing the default socket file path
|
|
|
|
socket_file_path = ''
|
|
|
|
if not found_tor:
|
2017-01-06 14:54:42 -05:00
|
|
|
try:
|
2018-03-08 13:18:31 -05:00
|
|
|
if self.common.platform == 'Darwin':
|
2017-01-06 14:54:42 -05:00
|
|
|
socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket')
|
2017-01-06 14:46:41 -05:00
|
|
|
|
2017-01-06 14:54:42 -05:00
|
|
|
self.c = Controller.from_socket_file(path=socket_file_path)
|
|
|
|
found_tor = True
|
|
|
|
except:
|
|
|
|
pass
|
2017-01-06 14:46:41 -05:00
|
|
|
|
2016-12-29 16:36:29 -05:00
|
|
|
# If connecting to default control ports failed, so let's try
|
|
|
|
# guessing the socket file name next
|
|
|
|
if not found_tor:
|
|
|
|
try:
|
2018-03-08 13:18:31 -05:00
|
|
|
if self.common.platform == 'Linux' or self.common.platform == 'BSD':
|
2016-12-29 16:36:29 -05:00
|
|
|
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
2018-03-08 13:18:31 -05:00
|
|
|
elif self.common.platform == 'Darwin':
|
2016-12-29 16:36:29 -05:00
|
|
|
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
2018-03-08 13:18:31 -05:00
|
|
|
elif self.common.platform == 'Windows':
|
2016-12-29 16:36:29 -05:00
|
|
|
# Windows doesn't support unix sockets
|
2016-12-29 15:57:58 -05:00
|
|
|
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
|
|
|
|
2016-12-29 16:36:29 -05:00
|
|
|
self.c = Controller.from_socket_file(path=socket_file_path)
|
|
|
|
|
|
|
|
except:
|
|
|
|
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
2016-12-29 15:57:58 -05:00
|
|
|
|
|
|
|
# Try authenticating
|
|
|
|
try:
|
|
|
|
self.c.authenticate()
|
|
|
|
except:
|
|
|
|
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
2016-12-22 16:39:32 -05:00
|
|
|
|
2016-12-29 12:58:13 -05:00
|
|
|
else:
|
|
|
|
# Use specific settings to connect to tor
|
|
|
|
|
|
|
|
# Try connecting
|
2015-09-08 01:34:54 -04:00
|
|
|
try:
|
2016-12-29 12:58:13 -05:00
|
|
|
if self.settings.get('connection_type') == 'control_port':
|
|
|
|
self.c = Controller.from_port(address=self.settings.get('control_port_address'), port=self.settings.get('control_port_port'))
|
|
|
|
elif self.settings.get('connection_type') == 'socket_file':
|
|
|
|
self.c = Controller.from_socket_file(path=self.settings.get('socket_file_path'))
|
|
|
|
else:
|
|
|
|
raise TorErrorInvalidSetting(strings._("settings_error_unknown"))
|
|
|
|
|
2017-01-07 20:31:26 -05:00
|
|
|
except:
|
2016-12-29 12:58:13 -05:00
|
|
|
if self.settings.get('connection_type') == 'control_port':
|
|
|
|
raise TorErrorSocketPort(strings._("settings_error_socket_port").format(self.settings.get('control_port_address'), self.settings.get('control_port_port')))
|
|
|
|
else:
|
|
|
|
raise TorErrorSocketFile(strings._("settings_error_socket_file").format(self.settings.get('socket_file_path')))
|
|
|
|
|
2017-01-07 20:31:26 -05:00
|
|
|
|
2016-12-29 12:58:13 -05:00
|
|
|
# Try authenticating
|
|
|
|
try:
|
|
|
|
if self.settings.get('auth_type') == 'no_auth':
|
|
|
|
self.c.authenticate()
|
|
|
|
elif self.settings.get('auth_type') == 'password':
|
|
|
|
self.c.authenticate(self.settings.get('auth_password'))
|
|
|
|
else:
|
|
|
|
raise TorErrorInvalidSetting(strings._("settings_error_unknown"))
|
|
|
|
|
2016-12-22 16:39:32 -05:00
|
|
|
except MissingPassword:
|
2016-12-29 12:58:13 -05:00
|
|
|
raise TorErrorMissingPassword(strings._('settings_error_missing_password'))
|
2016-12-22 16:39:32 -05:00
|
|
|
except UnreadableCookieFile:
|
2016-12-29 12:58:13 -05:00
|
|
|
raise TorErrorUnreadableCookieFile(strings._('settings_error_unreadable_cookie_file'))
|
2017-01-07 20:31:26 -05:00
|
|
|
except AuthenticationFailure:
|
|
|
|
raise TorErrorAuthError(strings._('settings_error_auth').format(self.settings.get('control_port_address'), self.settings.get('control_port_port')))
|
2016-12-29 12:58:13 -05:00
|
|
|
|
2017-05-16 16:09:27 -04:00
|
|
|
# If we made it this far, we should be connected to Tor
|
|
|
|
self.connected_to_tor = True
|
|
|
|
|
2017-02-22 17:10:06 -05:00
|
|
|
# Get the tor version
|
2016-12-29 13:03:29 -05:00
|
|
|
self.tor_version = self.c.get_version().version_str
|
2018-12-05 23:33:45 -05:00
|
|
|
self.common.log('Onion', 'connect', 'Connected to tor {}'.format(self.tor_version))
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2017-02-22 17:10:06 -05:00
|
|
|
# Do the versions of stem and tor that I'm using support ephemeral onion services?
|
2015-09-08 01:34:54 -04:00
|
|
|
list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None)
|
2016-12-29 13:03:29 -05:00
|
|
|
self.supports_ephemeral = callable(list_ephemeral_hidden_services) and self.tor_version >= '0.2.7.1'
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2017-02-22 17:10:06 -05:00
|
|
|
# Do the versions of stem and tor that I'm using support stealth onion services?
|
2016-12-22 19:56:39 -05:00
|
|
|
try:
|
|
|
|
res = self.c.create_ephemeral_hidden_service({1:1}, basic_auth={'onionshare':None}, await_publication=False)
|
2017-12-08 14:49:34 -05:00
|
|
|
tmp_service_id = res.service_id
|
2016-12-22 19:56:39 -05:00
|
|
|
self.c.remove_ephemeral_hidden_service(tmp_service_id)
|
|
|
|
self.supports_stealth = True
|
2016-12-22 20:07:01 -05:00
|
|
|
except:
|
2016-12-22 19:56:39 -05:00
|
|
|
# ephemeral stealth onion services are not supported
|
|
|
|
self.supports_stealth = False
|
|
|
|
|
2018-09-25 01:26:19 -04:00
|
|
|
# Does this version of Tor support next-gen ('v3') onions?
|
2018-12-05 23:33:45 -05:00
|
|
|
# Note, this is the version of Tor where this bug was fixed:
|
|
|
|
# https://trac.torproject.org/projects/tor/ticket/28619
|
2019-01-20 15:15:27 -05:00
|
|
|
self.supports_v3_onions = self.tor_version >= Version('0.3.5.7')
|
2017-12-20 20:15:17 -05:00
|
|
|
|
2017-12-20 17:22:53 -05:00
|
|
|
def is_authenticated(self):
|
|
|
|
"""
|
|
|
|
Returns True if the Tor connection is still working, or False otherwise.
|
|
|
|
"""
|
2017-12-20 22:08:13 -05:00
|
|
|
if self.c is not None:
|
2017-12-20 20:15:17 -05:00
|
|
|
return self.c.is_authenticated()
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2017-12-20 17:22:53 -05:00
|
|
|
|
2019-03-04 18:28:27 -05:00
|
|
|
def start_onion_service(self, port, await_publication, save_scheduled_key=False):
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
2016-09-04 20:23:06 -04:00
|
|
|
Start a onion service on port 80, pointing to the given port, and
|
2015-11-15 22:01:20 -05:00
|
|
|
return the onion hostname.
|
|
|
|
"""
|
2018-03-08 13:18:31 -05:00
|
|
|
self.common.log('Onion', 'start_onion_service')
|
2019-09-16 00:51:16 -04:00
|
|
|
# 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()
|
|
|
|
|
2016-12-22 19:56:39 -05:00
|
|
|
self.auth_string = None
|
2019-03-11 00:55:17 -04:00
|
|
|
|
2017-02-22 17:10:06 -05:00
|
|
|
if not self.supports_ephemeral:
|
|
|
|
raise TorTooOld(strings._('error_ephemeral_not_supported'))
|
2016-12-22 19:56:39 -05:00
|
|
|
if self.stealth and not self.supports_stealth:
|
|
|
|
raise TorTooOld(strings._('error_stealth_not_supported'))
|
|
|
|
|
2019-03-11 00:55:17 -04:00
|
|
|
if not save_scheduled_key:
|
2019-04-21 15:58:53 -04:00
|
|
|
print("Setting up onion service on port {0:d}.".format(int(port)))
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2017-02-22 17:10:06 -05:00
|
|
|
if self.stealth:
|
2017-12-08 14:49:34 -05:00
|
|
|
if self.settings.get('hidservauth_string'):
|
|
|
|
hidservauth_string = self.settings.get('hidservauth_string').split()[2]
|
|
|
|
basic_auth = {'onionshare':hidservauth_string}
|
|
|
|
else:
|
2019-03-11 00:55:17 -04:00
|
|
|
if self.scheduled_auth_cookie:
|
|
|
|
basic_auth = {'onionshare':self.scheduled_auth_cookie}
|
|
|
|
else:
|
|
|
|
basic_auth = {'onionshare':None}
|
2015-09-08 01:34:54 -04:00
|
|
|
else:
|
2017-02-22 17:10:06 -05:00
|
|
|
basic_auth = None
|
2015-12-08 02:47:13 -05:00
|
|
|
|
2017-12-06 20:45:29 -05:00
|
|
|
if self.settings.get('private_key'):
|
2018-09-13 02:35:24 -04:00
|
|
|
key_content = self.settings.get('private_key')
|
2018-10-11 22:38:05 -04:00
|
|
|
if self.is_v2_key(key_content):
|
2018-08-21 05:31:02 -04:00
|
|
|
key_type = "RSA1024"
|
2018-09-13 02:35:24 -04:00
|
|
|
else:
|
2019-02-10 19:36:18 -05:00
|
|
|
# Assume it was a v3 key. Stem will throw an error if it's something illegible
|
|
|
|
key_type = "ED25519-V3"
|
2018-10-11 22:38:05 -04:00
|
|
|
|
2019-03-04 18:28:27 -05:00
|
|
|
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"
|
|
|
|
|
2017-12-06 20:45:29 -05:00
|
|
|
else:
|
2018-10-11 22:38:05 -04:00
|
|
|
key_type = "NEW"
|
2018-08-21 05:31:02 -04:00
|
|
|
# Work out if we can support v3 onion services, which are preferred
|
2018-12-05 23:46:01 -05:00
|
|
|
if self.supports_v3_onions and not self.settings.get('use_legacy_v2_onions'):
|
2018-10-11 22:38:05 -04:00
|
|
|
key_content = "ED25519-V3"
|
2018-08-21 05:31:02 -04:00
|
|
|
else:
|
|
|
|
# fall back to v2 onion services
|
2018-10-11 22:38:05 -04:00
|
|
|
key_content = "RSA1024"
|
2018-08-21 05:31:02 -04:00
|
|
|
|
|
|
|
# v3 onions don't yet support basic auth. Our ticket:
|
|
|
|
# https://github.com/micahflee/onionshare/issues/697
|
2018-10-11 22:38:05 -04:00
|
|
|
if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'):
|
2018-08-21 05:31:02 -04:00
|
|
|
basic_auth = None
|
|
|
|
self.stealth = False
|
2017-12-08 14:49:34 -05:00
|
|
|
|
2018-10-11 22:38:05 -04:00
|
|
|
debug_message = 'key_type={}'.format(key_type)
|
|
|
|
if key_type == "NEW":
|
|
|
|
debug_message += ', key_content={}'.format(key_content)
|
|
|
|
self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message))
|
2017-02-22 19:45:52 -05:00
|
|
|
try:
|
2017-12-08 14:49:34 -05:00
|
|
|
if basic_auth != None:
|
2018-10-11 22:38:05 -04:00
|
|
|
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)
|
2017-12-08 14:49:34 -05:00
|
|
|
else:
|
2017-02-22 19:45:52 -05:00
|
|
|
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
|
2018-10-11 22:38:05 -04:00
|
|
|
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, key_type=key_type, key_content=key_content)
|
2017-02-22 19:45:52 -05:00
|
|
|
|
2018-09-18 20:17:25 -04:00
|
|
|
except ProtocolError as e:
|
|
|
|
raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0]))
|
2015-09-08 20:42:08 -04:00
|
|
|
|
2018-01-13 22:18:33 -05:00
|
|
|
self.service_id = res.service_id
|
2017-02-22 17:10:06 -05:00
|
|
|
onion_host = self.service_id + '.onion'
|
2015-09-08 20:42:08 -04:00
|
|
|
|
2017-12-08 14:49:34 -05:00
|
|
|
# A new private key was generated and is in the Control port response.
|
2018-01-13 04:58:24 -05:00
|
|
|
if self.settings.get('save_private_key'):
|
|
|
|
if not self.settings.get('private_key'):
|
2018-11-25 16:14:56 -05:00
|
|
|
self.settings.set('private_key', res.private_key)
|
2015-09-08 20:42:08 -04:00
|
|
|
|
2019-03-04 18:28:27 -05:00
|
|
|
# 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
|
|
|
|
|
2017-02-22 17:10:06 -05:00
|
|
|
if self.stealth:
|
2017-12-08 14:49:34 -05:00
|
|
|
# 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.
|
2018-01-13 04:58:24 -05:00
|
|
|
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 = 'HidServAuth {} {}'.format(onion_host, auth_cookie)
|
|
|
|
self.settings.set('hidservauth_string', self.auth_string)
|
2017-12-08 14:49:34 -05:00
|
|
|
else:
|
2019-03-11 00:55:17 -04:00
|
|
|
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
|
2017-02-22 17:10:06 -05:00
|
|
|
|
2018-01-01 22:31:50 -05:00
|
|
|
if onion_host is not None:
|
2018-02-25 01:44:27 -05:00
|
|
|
self.settings.save()
|
2018-01-01 22:31:50 -05:00
|
|
|
return onion_host
|
|
|
|
else:
|
2018-09-18 20:17:25 -04:00
|
|
|
raise TorErrorProtocolError(strings._('error_tor_protocol_error_unknown'))
|
2015-09-08 20:42:08 -04:00
|
|
|
|
2017-12-07 00:10:52 -05:00
|
|
|
def cleanup(self, stop_tor=True):
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
2017-04-14 01:22:34 -04:00
|
|
|
Stop onion services that were created earlier. If there's a tor subprocess running, kill it.
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
2018-03-08 13:18:31 -05:00
|
|
|
self.common.log('Onion', 'cleanup')
|
2017-05-16 14:23:18 -04:00
|
|
|
|
2018-02-25 01:44:27 -05:00
|
|
|
# Cleanup the ephemeral onion services, if we have any
|
|
|
|
try:
|
|
|
|
onions = self.c.list_ephemeral_hidden_services()
|
|
|
|
for onion in onions:
|
|
|
|
try:
|
2018-03-08 13:18:31 -05:00
|
|
|
self.common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion))
|
2018-02-25 01:44:27 -05:00
|
|
|
self.c.remove_ephemeral_hidden_service(onion)
|
|
|
|
except:
|
2018-03-08 13:18:31 -05:00
|
|
|
self.common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion))
|
2018-02-25 01:44:27 -05:00
|
|
|
pass
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
self.service_id = None
|
2017-04-14 01:22:34 -04:00
|
|
|
|
2017-12-07 00:10:52 -05:00
|
|
|
if stop_tor:
|
|
|
|
# Stop tor process
|
|
|
|
if self.tor_proc:
|
|
|
|
self.tor_proc.terminate()
|
|
|
|
time.sleep(0.2)
|
|
|
|
if not self.tor_proc.poll():
|
|
|
|
try:
|
|
|
|
self.tor_proc.kill()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
self.tor_proc = None
|
2017-04-14 21:33:44 -04:00
|
|
|
|
2017-12-07 00:10:52 -05:00
|
|
|
# Reset other Onion settings
|
|
|
|
self.connected_to_tor = False
|
|
|
|
self.stealth = False
|
2017-05-16 19:50:33 -04:00
|
|
|
|
2017-12-07 00:10:52 -05:00
|
|
|
try:
|
|
|
|
# Delete the temporary tor data directory
|
|
|
|
self.tor_data_directory.cleanup()
|
|
|
|
except AttributeError:
|
|
|
|
# Skip if cleanup was somehow run before connect
|
|
|
|
pass
|
2018-01-17 21:53:45 -05:00
|
|
|
except PermissionError:
|
|
|
|
# Skip if the directory is still open (#550)
|
|
|
|
# TODO: find a better solution
|
|
|
|
pass
|
2017-07-13 20:14:49 -04:00
|
|
|
|
2017-04-15 19:33:41 -04:00
|
|
|
def get_tor_socks_port(self):
|
|
|
|
"""
|
|
|
|
Returns a (address, port) tuple for the Tor SOCKS port
|
|
|
|
"""
|
2018-03-08 13:18:31 -05:00
|
|
|
self.common.log('Onion', 'get_tor_socks_port')
|
2017-05-16 14:44:34 -04:00
|
|
|
|
2017-04-15 19:33:41 -04:00
|
|
|
if self.settings.get('connection_type') == 'bundled':
|
|
|
|
return ('127.0.0.1', self.tor_socks_port)
|
|
|
|
elif self.settings.get('connection_type') == 'automatic':
|
|
|
|
return ('127.0.0.1', 9150)
|
|
|
|
else:
|
|
|
|
return (self.settings.get('socks_address'), self.settings.get('socks_port'))
|
2018-10-11 22:38:05 -04:00
|
|
|
|
2018-11-25 16:14:56 -05:00
|
|
|
def is_v2_key(self, key):
|
2018-10-11 22:38:05 -04:00
|
|
|
"""
|
|
|
|
Helper function for determining if a key is RSA1024 (v2) or not.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# Import the key
|
|
|
|
key = RSA.importKey(base64.b64decode(key))
|
|
|
|
# Is this a v2 Onion key? (1024 bits) If so, we should keep using it.
|
|
|
|
if key.n.bit_length() == 1024:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
except:
|
|
|
|
return False
|