2015-09-08 02:34:54 -03:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
OnionShare | https://onionshare.org/
|
|
|
|
|
2017-01-06 23:58:15 -03:00
|
|
|
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
2015-09-08 02:34:54 -03: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-02-22 21:45:52 -03:00
|
|
|
from stem import ProtocolError
|
2017-01-07 22:31:26 -03:00
|
|
|
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
|
2016-12-29 18:36:29 -03:00
|
|
|
import os, sys, tempfile, shutil, urllib, platform
|
2015-09-08 02:34:54 -03:00
|
|
|
|
2016-02-12 19:34:19 -03:00
|
|
|
from . import socks
|
|
|
|
from . import helpers, strings
|
2016-12-29 00:52:21 -03:00
|
|
|
from .settings import Settings
|
2015-09-08 02:34:54 -03:00
|
|
|
|
2016-12-29 17:57:58 -03: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 14:58:13 -03: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 22:31:26 -03: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 21:45:52 -03: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 21:56:39 -03: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 22:10:17 -03: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.
|
|
|
|
"""
|
|
|
|
|
2016-09-05 15:16:54 -03:00
|
|
|
class Onion(object):
|
2015-11-16 00:01:20 -03:00
|
|
|
"""
|
2016-09-05 15:16:54 -03:00
|
|
|
Onion is an abstraction layer for connecting to the Tor control port and
|
2017-02-22 19:10:06 -03:00
|
|
|
creating onion services. OnionShare supports creating onion services by
|
|
|
|
connecting to the Tor controller and using ADD_ONION, DEL_ONION.
|
2015-11-16 00:01:20 -03:00
|
|
|
"""
|
2017-02-22 19:10:06 -03:00
|
|
|
def __init__(self, stealth=False, settings=False):
|
2016-12-22 21:56:39 -03:00
|
|
|
self.stealth = stealth
|
2017-02-22 19:10:06 -03:00
|
|
|
self.service_id = None
|
2015-09-08 02:34:54 -03:00
|
|
|
|
2016-12-29 13:02:32 -03:00
|
|
|
# Either use settings that are passed in, or load them from disk
|
|
|
|
if settings:
|
|
|
|
self.settings = settings
|
|
|
|
else:
|
|
|
|
self.settings = Settings()
|
|
|
|
self.settings.load()
|
2016-12-29 00:52:21 -03:00
|
|
|
|
2016-12-29 14:58:13 -03:00
|
|
|
# Try to connect to Tor
|
|
|
|
self.c = None
|
2016-12-22 18:39:32 -03:00
|
|
|
|
2017-04-08 22:10:17 -03:00
|
|
|
if self.settings.get('connection_type') == 'bundled':
|
|
|
|
dev_mode = getattr(sys, 'onionshare_dev_mode', False)
|
|
|
|
p = platform.system()
|
|
|
|
|
|
|
|
if (p != 'Windows' and p != 'Darwin') or dev_mode:
|
|
|
|
raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported'))
|
|
|
|
|
|
|
|
# TODO: actually implement bundled Tor
|
|
|
|
|
2016-12-29 14:58:13 -03:00
|
|
|
if self.settings.get('connection_type') == 'automatic':
|
|
|
|
# Automatically try to guess the right way to connect to Tor Browser
|
2017-01-06 16:46:41 -03:00
|
|
|
p = platform.system()
|
2016-12-29 14:58:13 -03:00
|
|
|
|
2016-12-29 18:36:29 -03:00
|
|
|
# Try connecting to control port
|
|
|
|
found_tor = False
|
2016-12-29 17:57:58 -03:00
|
|
|
|
2016-12-29 18:36:29 -03: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 16:46:41 -03:00
|
|
|
# If this still didn't work, try guessing the default socket file path
|
|
|
|
socket_file_path = ''
|
|
|
|
if not found_tor:
|
2017-01-06 16:54:42 -03:00
|
|
|
try:
|
|
|
|
if p == 'Darwin':
|
|
|
|
socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket')
|
2017-01-06 16:46:41 -03:00
|
|
|
|
2017-01-06 16:54:42 -03:00
|
|
|
self.c = Controller.from_socket_file(path=socket_file_path)
|
|
|
|
found_tor = True
|
|
|
|
except:
|
|
|
|
pass
|
2017-01-06 16:46:41 -03:00
|
|
|
|
2016-12-29 18:36:29 -03:00
|
|
|
# If connecting to default control ports failed, so let's try
|
|
|
|
# guessing the socket file name next
|
|
|
|
if not found_tor:
|
|
|
|
try:
|
|
|
|
if p == 'Linux':
|
|
|
|
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
|
|
|
elif p == 'Darwin':
|
|
|
|
# TODO: figure out the unix socket path in OS X
|
|
|
|
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
|
|
|
elif p == 'Windows':
|
|
|
|
# Windows doesn't support unix sockets
|
2016-12-29 17:57:58 -03:00
|
|
|
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
|
|
|
|
2016-12-29 18:36:29 -03:00
|
|
|
self.c = Controller.from_socket_file(path=socket_file_path)
|
|
|
|
|
|
|
|
except:
|
|
|
|
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
2016-12-29 17:57:58 -03:00
|
|
|
|
|
|
|
# Try authenticating
|
|
|
|
try:
|
|
|
|
self.c.authenticate()
|
|
|
|
except:
|
|
|
|
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
2016-12-22 18:39:32 -03:00
|
|
|
|
2016-12-29 14:58:13 -03:00
|
|
|
else:
|
|
|
|
# Use specific settings to connect to tor
|
|
|
|
|
|
|
|
# Try connecting
|
2015-09-08 02:34:54 -03:00
|
|
|
try:
|
2016-12-29 14:58:13 -03: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 22:31:26 -03:00
|
|
|
except:
|
2016-12-29 14:58:13 -03: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 22:31:26 -03:00
|
|
|
|
2016-12-29 14:58:13 -03: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 18:39:32 -03:00
|
|
|
except MissingPassword:
|
2016-12-29 14:58:13 -03:00
|
|
|
raise TorErrorMissingPassword(strings._('settings_error_missing_password'))
|
2016-12-22 18:39:32 -03:00
|
|
|
except UnreadableCookieFile:
|
2016-12-29 14:58:13 -03:00
|
|
|
raise TorErrorUnreadableCookieFile(strings._('settings_error_unreadable_cookie_file'))
|
2017-01-07 22:31:26 -03: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 14:58:13 -03:00
|
|
|
|
2017-02-22 19:10:06 -03:00
|
|
|
# Get the tor version
|
2016-12-29 15:03:29 -03:00
|
|
|
self.tor_version = self.c.get_version().version_str
|
2015-09-08 02:34:54 -03:00
|
|
|
|
2017-02-22 19:10:06 -03:00
|
|
|
# Do the versions of stem and tor that I'm using support ephemeral onion services?
|
2015-09-08 02:34:54 -03:00
|
|
|
list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None)
|
2016-12-29 15:03:29 -03:00
|
|
|
self.supports_ephemeral = callable(list_ephemeral_hidden_services) and self.tor_version >= '0.2.7.1'
|
2015-09-08 02:34:54 -03:00
|
|
|
|
2017-02-22 19:10:06 -03:00
|
|
|
# Do the versions of stem and tor that I'm using support stealth onion services?
|
2016-12-22 21:56:39 -03:00
|
|
|
try:
|
|
|
|
res = self.c.create_ephemeral_hidden_service({1:1}, basic_auth={'onionshare':None}, await_publication=False)
|
|
|
|
tmp_service_id = res.content()[0][2].split('=')[1]
|
|
|
|
self.c.remove_ephemeral_hidden_service(tmp_service_id)
|
|
|
|
self.supports_stealth = True
|
2016-12-22 22:07:01 -03:00
|
|
|
except:
|
2016-12-22 21:56:39 -03:00
|
|
|
# ephemeral stealth onion services are not supported
|
|
|
|
self.supports_stealth = False
|
|
|
|
|
2015-09-08 02:34:54 -03:00
|
|
|
def start(self, port):
|
2015-11-16 00:01:20 -03:00
|
|
|
"""
|
2016-09-04 21:23:06 -03:00
|
|
|
Start a onion service on port 80, pointing to the given port, and
|
2015-11-16 00:01:20 -03:00
|
|
|
return the onion hostname.
|
|
|
|
"""
|
2016-12-22 21:56:39 -03:00
|
|
|
self.auth_string = None
|
2017-02-22 19:10:06 -03:00
|
|
|
if not self.supports_ephemeral:
|
|
|
|
raise TorTooOld(strings._('error_ephemeral_not_supported'))
|
2016-12-22 21:56:39 -03:00
|
|
|
if self.stealth and not self.supports_stealth:
|
|
|
|
raise TorTooOld(strings._('error_stealth_not_supported'))
|
|
|
|
|
|
|
|
print(strings._("config_onion_service").format(int(port)))
|
2017-02-22 19:10:06 -03:00
|
|
|
print(strings._('using_ephemeral'))
|
2015-09-08 02:34:54 -03:00
|
|
|
|
2017-02-22 19:10:06 -03:00
|
|
|
if self.stealth:
|
|
|
|
basic_auth = {'onionshare':None}
|
2015-09-08 02:34:54 -03:00
|
|
|
else:
|
2017-02-22 19:10:06 -03:00
|
|
|
basic_auth = None
|
2015-12-08 04:47:13 -03:00
|
|
|
|
2017-02-22 21:45:52 -03:00
|
|
|
try:
|
|
|
|
if basic_auth != None :
|
|
|
|
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth)
|
|
|
|
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=True)
|
|
|
|
|
|
|
|
except ProtocolError:
|
|
|
|
raise TorErrorProtocolError(strings._('error_tor_protocol_error'))
|
2015-09-08 21:42:08 -03:00
|
|
|
|
2017-02-22 19:10:06 -03:00
|
|
|
self.service_id = res.content()[0][2].split('=')[1]
|
|
|
|
onion_host = self.service_id + '.onion'
|
2015-09-08 21:42:08 -03:00
|
|
|
|
2017-02-22 19:10:06 -03:00
|
|
|
if self.stealth:
|
|
|
|
auth_cookie = res.content()[2][2].split('=')[1].split(':')[1]
|
|
|
|
self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie)
|
|
|
|
|
|
|
|
return onion_host
|
2015-09-08 21:42:08 -03:00
|
|
|
|
2015-09-08 02:34:54 -03:00
|
|
|
def cleanup(self):
|
2015-11-16 00:01:20 -03:00
|
|
|
"""
|
2017-02-22 19:10:06 -03:00
|
|
|
Stop onion services that were created earlier.
|
2015-11-16 00:01:20 -03:00
|
|
|
"""
|
2017-02-22 19:10:06 -03:00
|
|
|
# cleanup the ephemeral onion service
|
|
|
|
if self.service_id:
|
2015-09-08 02:34:54 -03:00
|
|
|
try:
|
2017-02-22 19:10:06 -03:00
|
|
|
self.c.remove_ephemeral_hidden_service(self.service_id)
|
2015-09-08 02:34:54 -03:00
|
|
|
except:
|
|
|
|
pass
|
2017-02-22 19:10:06 -03:00
|
|
|
self.service_id = None
|