2015-09-08 01:34:54 -04:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
OnionShare | https://onionshare.org/
|
|
|
|
|
2016-02-16 01:37:28 -05:00
|
|
|
Copyright (C) 2016 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
|
2016-02-12 17:34:19 -05:00
|
|
|
from stem import SocketError
|
|
|
|
import os, sys, tempfile, shutil, urllib
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2016-02-12 17:34:19 -05:00
|
|
|
from . import socks
|
|
|
|
from . import helpers, strings
|
2015-09-08 01:34:54 -04:00
|
|
|
|
|
|
|
class NoTor(Exception):
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
|
|
|
This exception is raised if onionshare can't find a Tor control port
|
|
|
|
to connect to, or if it can't find a Tor socks5 proxy to proxy though.
|
|
|
|
"""
|
2015-09-08 01:34:54 -04:00
|
|
|
pass
|
|
|
|
|
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
|
|
|
|
creating onion services. OnionShare supports creating onion services
|
2015-11-15 22:01:20 -05:00
|
|
|
using two methods:
|
|
|
|
|
|
|
|
- Modifying the Tor configuration through the control port is the old
|
2016-09-04 20:23:06 -04:00
|
|
|
method, and will be deprecated in favor of ephemeral onion services.
|
|
|
|
- Using the control port to create ephemeral onion servers is the
|
2015-11-15 22:01:20 -05:00
|
|
|
preferred method.
|
|
|
|
|
|
|
|
This class detects the versions of Tor and stem to determine if ephemeral
|
2016-09-04 20:23:06 -04:00
|
|
|
onion services are supported. If not, it falls back to modifying the
|
2015-11-15 22:01:20 -05:00
|
|
|
Tor configuration.
|
|
|
|
"""
|
2015-09-08 01:34:54 -04:00
|
|
|
def __init__(self, transparent_torification=False):
|
|
|
|
self.transparent_torification = transparent_torification
|
|
|
|
|
|
|
|
# files and dirs to delete on shutdown
|
|
|
|
self.cleanup_filenames = []
|
2015-11-15 19:26:44 -05:00
|
|
|
self.service_id = None
|
2015-09-08 01:34:54 -04:00
|
|
|
|
|
|
|
# connect to the tor controlport
|
2016-02-12 17:34:19 -05:00
|
|
|
found_tor = False
|
2015-09-08 01:34:54 -04:00
|
|
|
self.c = None
|
2016-09-22 05:25:42 -04:00
|
|
|
env_port = os.environ.get('TOR_CONTROL_PORT')
|
|
|
|
if env_port:
|
|
|
|
ports = [int(env_port)]
|
|
|
|
else:
|
|
|
|
ports = [9151, 9153, 9051]
|
2015-09-08 01:34:54 -04:00
|
|
|
for port in ports:
|
|
|
|
try:
|
|
|
|
self.c = Controller.from_port(port=port)
|
|
|
|
self.c.authenticate()
|
2016-02-12 17:34:19 -05:00
|
|
|
found_tor = True
|
2015-09-08 01:34:54 -04:00
|
|
|
break
|
2016-02-12 17:34:19 -05:00
|
|
|
except SocketError:
|
2015-09-08 01:34:54 -04:00
|
|
|
pass
|
2016-02-12 17:34:19 -05:00
|
|
|
if not found_tor:
|
|
|
|
raise NoTor(strings._("cant_connect_ctrlport").format(str(ports)))
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2016-09-04 20:23:06 -04:00
|
|
|
# do the versions of stem and tor that I'm using support ephemeral onion services?
|
2015-09-08 01:34:54 -04:00
|
|
|
tor_version = self.c.get_version().version_str
|
|
|
|
list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None)
|
|
|
|
self.supports_ephemeral = callable(list_ephemeral_hidden_services) and tor_version >= '0.2.7.1'
|
|
|
|
|
|
|
|
def start(self, port):
|
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.
|
|
|
|
"""
|
2016-02-12 17:34:19 -05:00
|
|
|
print(strings._("connecting_ctrlport").format(int(port)))
|
2015-09-08 01:34:54 -04:00
|
|
|
if self.supports_ephemeral:
|
2016-02-12 17:34:19 -05:00
|
|
|
print(strings._('using_ephemeral'))
|
2016-09-05 14:29:12 -04:00
|
|
|
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication = True)
|
2015-11-15 19:26:44 -05:00
|
|
|
self.service_id = res.content()[0][2].split('=')[1]
|
2016-09-05 14:29:12 -04:00
|
|
|
onion_host = self.service_id + '.onion'
|
2015-09-08 01:34:54 -04:00
|
|
|
return onion_host
|
|
|
|
|
|
|
|
else:
|
2016-09-04 20:23:06 -04:00
|
|
|
# come up with a onion service directory name
|
2015-09-08 01:34:54 -04:00
|
|
|
if helpers.get_platform() == 'Windows':
|
2015-12-08 02:47:13 -05:00
|
|
|
self.hidserv_dir = tempfile.mkdtemp()
|
|
|
|
self.hidserv_dir = self.hidserv_dir.replace('\\', '/')
|
|
|
|
|
2015-09-08 01:34:54 -04:00
|
|
|
else:
|
2016-05-23 14:45:07 -04:00
|
|
|
self.hidserv_dir = tempfile.mkdtemp(suffix='onionshare',dir='/tmp')
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2015-12-06 16:51:42 -05:00
|
|
|
self.cleanup_filenames.append(self.hidserv_dir)
|
2015-09-08 01:34:54 -04:00
|
|
|
|
2016-09-04 20:23:06 -04:00
|
|
|
# set up onion service
|
2015-12-06 16:40:35 -05:00
|
|
|
hsdic = self.c.get_conf_map('HiddenServiceOptions') or {
|
|
|
|
'HiddenServiceDir': [], 'HiddenServicePort': []
|
|
|
|
}
|
2015-12-06 16:51:42 -05:00
|
|
|
if self.hidserv_dir in hsdic.get('HiddenServiceDir', []):
|
2015-12-06 16:40:35 -05:00
|
|
|
# Maybe a stale service with the wrong local port
|
2015-12-06 16:51:42 -05:00
|
|
|
dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir)
|
2015-12-06 16:40:35 -05:00
|
|
|
del hsdic['HiddenServiceDir'][dropme]
|
|
|
|
del hsdic['HiddenServicePort'][dropme]
|
2015-12-06 16:51:42 -05:00
|
|
|
hsdic['HiddenServiceDir'] = hsdic.get('HiddenServiceDir', [])+[self.hidserv_dir]
|
2015-12-06 16:40:35 -05:00
|
|
|
hsdic['HiddenServicePort'] = hsdic.get('HiddenServicePort', [])+[
|
|
|
|
'80 127.0.0.1:{0:d}'.format(port)]
|
|
|
|
|
|
|
|
self.c.set_options(self._hsdic2list(hsdic))
|
2015-09-08 01:34:54 -04:00
|
|
|
|
|
|
|
# figure out the .onion hostname
|
2015-12-06 16:51:42 -05:00
|
|
|
hostname_file = '{0:s}/hostname'.format(self.hidserv_dir)
|
2015-09-08 01:34:54 -04:00
|
|
|
onion_host = open(hostname_file, 'r').read().strip()
|
|
|
|
return onion_host
|
|
|
|
|
2015-09-08 20:48:16 -04:00
|
|
|
def wait_for_hs(self, onion_host):
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
2016-09-04 20:23:06 -04:00
|
|
|
This function is only required when using non-ephemeral onion services. After
|
|
|
|
creating a onion service, continually attempt to connect to it until it
|
2016-06-10 20:24:44 -04:00
|
|
|
successfully connects.
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
2016-09-04 20:23:06 -04:00
|
|
|
# legacy only, this function is no longer required with ephemeral onion services
|
2016-02-12 17:34:19 -05:00
|
|
|
print(strings._('wait_for_hs'))
|
2015-09-08 20:42:08 -04:00
|
|
|
|
|
|
|
ready = False
|
|
|
|
while not ready:
|
|
|
|
try:
|
|
|
|
sys.stdout.write('{0:s} '.format(strings._('wait_for_hs_trying')))
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
2015-09-08 20:48:16 -04:00
|
|
|
if self.transparent_torification:
|
2015-09-08 20:42:08 -04:00
|
|
|
# no need to set the socks5 proxy
|
2016-02-12 17:34:19 -05:00
|
|
|
urllib.request.urlopen('http://{0:s}'.format(onion_host))
|
2015-09-08 20:42:08 -04:00
|
|
|
else:
|
|
|
|
tor_exists = False
|
2015-11-20 15:43:08 -05:00
|
|
|
ports = [9150, 9152, 9050]
|
2015-09-08 20:42:08 -04:00
|
|
|
for port in ports:
|
|
|
|
try:
|
|
|
|
s = socks.socksocket()
|
|
|
|
s.setproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', port)
|
|
|
|
s.connect((onion_host, 80))
|
|
|
|
s.close()
|
|
|
|
tor_exists = True
|
|
|
|
break
|
|
|
|
except socks.ProxyConnectionError:
|
|
|
|
pass
|
|
|
|
if not tor_exists:
|
2016-02-12 17:34:19 -05:00
|
|
|
raise NoTor(strings._("cant_connect_socksport").format(str(ports)))
|
2015-09-08 20:42:08 -04:00
|
|
|
ready = True
|
|
|
|
|
|
|
|
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_yup')))
|
2016-02-12 17:34:19 -05:00
|
|
|
except socks.GeneralProxyError:
|
2015-09-08 20:42:08 -04:00
|
|
|
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
|
|
|
|
sys.stdout.flush()
|
2016-02-12 17:34:19 -05:00
|
|
|
except socks.SOCKS5Error:
|
2015-09-08 20:42:08 -04:00
|
|
|
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
|
|
|
|
sys.stdout.flush()
|
2016-02-12 17:34:19 -05:00
|
|
|
except urllib.error.HTTPError: # torification error
|
2015-09-08 20:42:08 -04:00
|
|
|
sys.stdout.write('{0:s}\n'.format(strings._('wait_for_hs_nope')))
|
|
|
|
sys.stdout.flush()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2015-09-08 01:34:54 -04:00
|
|
|
def cleanup(self):
|
2015-11-15 22:01:20 -05:00
|
|
|
"""
|
2016-09-04 20:23:06 -04:00
|
|
|
Stop onion services that were created earlier, and delete any temporary
|
2015-11-15 22:01:20 -05:00
|
|
|
files that were created.
|
|
|
|
"""
|
2015-09-08 01:34:54 -04:00
|
|
|
if self.supports_ephemeral:
|
2016-09-04 20:23:06 -04:00
|
|
|
# cleanup the ephemeral onion service
|
2015-11-15 19:26:44 -05:00
|
|
|
if self.service_id:
|
2016-12-20 04:44:20 -05:00
|
|
|
try:
|
|
|
|
self.c.remove_ephemeral_hidden_service(self.service_id)
|
|
|
|
except:
|
|
|
|
pass
|
2015-11-15 19:26:44 -05:00
|
|
|
self.service_id = None
|
|
|
|
|
2015-09-08 01:34:54 -04:00
|
|
|
else:
|
2016-09-04 20:23:06 -04:00
|
|
|
# cleanup onion service
|
2015-09-08 01:34:54 -04:00
|
|
|
try:
|
2015-12-06 16:40:35 -05:00
|
|
|
if self.controller:
|
2016-09-04 20:23:06 -04:00
|
|
|
# Get fresh onion services (maybe changed since last time)
|
2015-09-08 01:34:54 -04:00
|
|
|
# and remove ourselves
|
2015-12-06 16:40:35 -05:00
|
|
|
hsdic = self.controller.get_conf_map('HiddenServiceOptions') or {
|
|
|
|
'HiddenServiceDir': [], 'HiddenServicePort': []
|
|
|
|
}
|
|
|
|
if self.hidserv_dir and self.hidserv_dir in hsdic.get('HiddenServiceDir', []):
|
|
|
|
dropme = hsdic['HiddenServiceDir'].index(self.hidserv_dir)
|
|
|
|
del hsdic['HiddenServiceDir'][dropme]
|
|
|
|
del hsdic['HiddenServicePort'][dropme]
|
|
|
|
self.controller.set_options(self._hsdic2list(hsdic))
|
|
|
|
# Politely close the controller
|
|
|
|
self.controller.close()
|
2015-09-08 01:34:54 -04:00
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# cleanup files
|
|
|
|
for filename in self.cleanup_filenames:
|
|
|
|
if os.path.isfile(filename):
|
|
|
|
os.remove(filename)
|
|
|
|
elif os.path.isdir(filename):
|
|
|
|
shutil.rmtree(filename)
|
|
|
|
self.cleanup_filenames = []
|
2015-12-06 16:40:35 -05:00
|
|
|
|
|
|
|
def _hsdic2list(self, dic):
|
|
|
|
"""
|
|
|
|
Convert what we get from get_conf_map to what we need for set_options.
|
|
|
|
|
|
|
|
For example, if input looks like this:
|
|
|
|
{
|
|
|
|
'HiddenServicePort': [
|
|
|
|
'80 127.0.0.1:47906',
|
|
|
|
'80 127.0.0.1:33302'
|
|
|
|
],
|
|
|
|
'HiddenServiceDir': [
|
2016-05-23 14:45:07 -04:00
|
|
|
'/tmp/onionsharelTfZZu',
|
|
|
|
'/tmp/onionsharechDai3'
|
2015-12-06 16:40:35 -05:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Output will look like this:
|
|
|
|
[
|
2016-05-23 14:45:07 -04:00
|
|
|
('HiddenServiceDir', '/tmp/onionsharelTfZZu'),
|
2015-12-06 16:40:35 -05:00
|
|
|
('HiddenServicePort', '80 127.0.0.1:47906'),
|
2016-05-23 14:45:07 -04:00
|
|
|
('HiddenServiceDir', '/tmp/onionsharechDai3'),
|
2015-12-06 16:40:35 -05:00
|
|
|
('HiddenServicePort', '80 127.0.0.1:33302')
|
|
|
|
]
|
|
|
|
"""
|
|
|
|
l = []
|
2015-12-06 16:51:42 -05:00
|
|
|
for dir, port in zip(dic['HiddenServiceDir'], dic['HiddenServicePort']):
|
|
|
|
l.append(('HiddenServiceDir', dir))
|
|
|
|
l.append(('HiddenServicePort', port))
|
2015-12-06 16:40:35 -05:00
|
|
|
return l
|