openmedialibrary/oml/tor.py

286 lines
9.9 KiB
Python

import os
import sys
import subprocess
from threading import Thread
import distutils
import ox
import stem
from stem.control import Controller
import settings
import state
import utils
import logging
logging.getLogger('stem').setLevel(logging.ERROR)
logger = logging.getLogger(__name__)
class TorDaemon(Thread):
def __init__(self):
self._status = []
Thread.__init__(self)
self.daemon = True
self.start()
def create_torrc(self):
defaults = os.path.join(settings.config_path, 'torrc-defaults')
torrc = os.path.join(settings.config_path, 'torrc')
if not os.path.exists(defaults):
with open(defaults, 'w') as fd:
fd.write('''
AvoidDiskWrites 1
# Where to send logging messages. Format is minSeverity[-maxSeverity]
# (stderr|stdout|syslog|file FILENAME).
Log notice stdout
SocksPort 9830
ControlPort 9831
CookieAuthentication 1
'''.strip())
if not os.path.exists(torrc):
with open(torrc, 'w') as fd:
fd.write('''
DataDirectory {base}/TorData
DirReqStatistics 0
'''.strip().format(base=settings.config_path))
return defaults, torrc
def run(self):
defaults, torrc = self.create_torrc()
tor = get_tor()
if not tor:
self._status.append('No tor binary found. Please install TorBrowser or tor')
install_tor()
tor = get_tor()
if tor:
cmd = [tor, '--defaults-torrc', defaults, '-f', torrc]
self.p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1,
universal_newlines=True, close_fds=True, start_new_session=True)
for line in self.p.stdout:
self._status.append(line)
logger.debug(line)
self.p = None
def shutdown(self):
if self.p:
self.p.kill()
def status(self, max_lines=50):
return ''.join(self._status[-max_lines:])
class Tor(object):
_shutdown = False
connected = False
controller = None
daemon = None
socks_port = 9150
def __init__(self):
if not self.connect():
self.reconnect()
def connect(self):
self.connected = False
self.dir = os.path.join(settings.config_path, 'tor')
connected = False
for port in (9831, 9151):
try:
self.controller = Controller.from_port('127.0.0.1', port)
connected = True
break
except stem.SocketError:
pass
if not connected:
if not self.daemon:
logger.debug("start own tor process")
self.daemon = TorDaemon()
logger.debug("daemon %s", self.daemon)
return self.connect()
logger.debug("Failed to connect to system or own tor process.")
return False
try:
self.controller.authenticate()
except stem.connection.MissingPassword:
logger.debug("TOR requires a password")
return False
except stem.connection.PasswordAuthFailed:
logger.debug("invalid tor password")
return False
self.controller.add_event_listener(self.event_listener)
self.controller.add_status_listener(self.status_listener)
self.connected = True
self.socks_port = int(self.controller.get_conf('SocksPort').split(' ')[0])
self.publish()
state.online = self.is_online()
return True
def reconnect(self):
if not self.connect():
if state.main:
state.main.call_later(1, self.reconnect)
def status_listener(self, controller, status, timestamp):
if status == 'Closed':
if not self._shutdown:
self.connected = False
state.online = False
self.reconnect()
else:
logger.debug('unknonw change %s', status)
def event_listener(self, event):
print('EVENT', event)
def shutdown(self):
self._shutdown = True
try:
self.depublish()
if self.controller:
#self.controller.remove_event_listener(self.connection_change)
self.controller.close()
if self.daemon:
self.daemon.shutdown()
except:
logger.debug('shutdown exception', exc_info=1)
pass
self.connected = False
def publish(self):
logger.debug("publish tor node")
if not self.connected:
return False
controller = self.controller
logger.debug("FIXME: dont remove/add service if already defined")
controller.remove_hidden_service(self.dir)
result = controller.create_hidden_service(
self.dir,
settings.server_defaults['node_port'],
target_port=settings.server['node_port']
)
logger.debug('published node as https://%s:%s', result.hostname, settings.server_defaults['node_port'])
'''
with open(settings.ssl_key_path) as fd:
key_content = fd.read()
ports = {9851: settings.server['node_port']}
response = controller.create_ephemeral_hidden_service(ports,
key_type='RSA1024', key_content=key_content,
detached=True, await_publication = True)
logger.debug('published node as https://%s.onion:%s',
settings.USER_ID, settings.server_defaults['node_port'])
'''
def depublish(self):
if not self.connected:
return False
if self.controller:
try:
self.controller.remove_hidden_service(self.dir)
except:
logger.debug('self.controller.remove_hidden_service fail', exc_info=1)
state.online = False
def is_online(self):
return self.connected and self.controller.is_alive() and utils.can_connect_dns()
def torbrowser_url():
import re
from ox.cache import read_url
base_url = 'https://dist.torproject.org/torbrowser/'
r = re.compile('href="(\d\.\d\.\d/)"')
current = sorted(r.findall(read_url(base_url, timeout=3*24*60*60).decode()))[-1]
url = base_url + current
if sys.platform.startswith('linux'):
import platform
if platform.architecture()[0] == '64bit':
osname = 'linux64'
else:
osname = 'linux32'
ext = 'xz'
elif sys.platform == 'darwin':
osname = 'osx64'
ext = 'dmg'
elif sys.platform == 'win32':
osname = 'install'
ext = 'exe'
else:
logger.debug('no way to get torbrowser url for %s', sys.platform)
return None
r = re.compile('href="(.*?{osname}.*?en.*?{ext})"'.format(osname=osname,ext=ext))
torbrowser = sorted(r.findall(read_url(url).decode()))[-1]
url += torbrowser
return url
def get_tor():
if sys.platform == 'darwin':
for path in (
'/Applications/TorBrowser.app/TorBrowser/Tor/tor',
os.path.join(settings.base_dir, 'tor', 'TorBrowser.app', 'TorBrowser', 'Tor', 'tor')
):
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
start = os.path.expanduser('~/.local/share/applications/start-tor-browser.desktop')
if os.path.exists(start):
with open(start) as fd:
e = [line for line in fd.read().split('\n') if line.startswith('Exec')]
if e:
try:
base = os.path.dirname(e[0].split('"')[1])
path = os.path.join(base, 'TorBrowser', 'Tor', 'tor')
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
except:
pass
local_tor = os.path.normpath(os.path.join(settings.base_dir, '..',
'tor', 'tor-browser_en-US', 'Browser', 'TorBrowser', 'Tor', 'tor'))
if os.path.exists(local_tor):
return local_tor
return distutils.spawn.find_executable('tor')
def install_tor():
import tarfile
import update
import shutil
# only install if tor can not be found
if get_tor():
logger.debug('found existing tor installation')
return
url = torbrowser_url()
target = os.path.normpath(os.path.join(settings.base_dir, '..', 'tor'))
if url:
logger.debug('downloading and installing tor')
if sys.platform.startswith('linux'):
ox.makedirs(target)
try:
tar_file = os.path.join(target, os.path.basename(url))
update.get(url, tar_file)
tar = tarfile.open(tar_file)
tar.extractall(target)
desktop = os.path.join(target, tar.members[0].path, 'start-tor-browser.desktop')
tar.close()
subprocess.call([desktop, '--register-app'], cwd=os.path.dirname(desktop))
os.unlink(tar_file)
except:
logger.debug('tor installation failed', exc_info=1)
elif sys.platform == 'darwin':
ox.makedirs(target)
dmg = os.path.join(target, os.path.basename(url))
name = 'TorBrowser.app'
if os.access('/Applications', os.W_OK):
target = os.path.join('/Applications', name)
else:
target = os.path.join(target, name)
if not os.path.exists(target):
try:
update.get(url, dmg)
r = subprocess.check_output(['hdiutil', 'mount', dmg])
volume = r.decode().strip().split('\t')[-1]
app = os.path.join(volume, name)
shutil.copytree(app, target)
subprocess.call(['hdiutil', 'unmount', volume])
os.unlink(dmg)
except:
logger.debug('tor installation failed', exc_info=1)
else:
logger.debug('no way to install TorBrowser on %s so far', sys.platform)