PyBitmessage/src/plugins/proxyconfig_stem.py

139 lines
5.0 KiB
Python
Raw Normal View History

2019-07-23 15:57:46 +00:00
# -*- coding: utf-8 -*-
2019-09-23 10:38:34 +00:00
"""
2019-10-22 07:10:41 +00:00
Configure tor proxy and hidden service with
`stem <https://stem.torproject.org/>`_ depending on *bitmessagesettings*:
* try to start own tor instance on *socksport* if *sockshostname*
is unset or set to localhost;
* if *socksport* is already in use that instance is used only for
hidden service (if *sockslisten* is also set True);
* create ephemeral hidden service v3 if there is already *onionhostname*;
* otherwise use stem's 'BEST' version and save onion keys to the new
section using *onionhostname* as name for future use.
2019-09-23 10:38:34 +00:00
"""
2019-07-23 15:57:46 +00:00
import os
import logging
import random # noseq
import tempfile
import stem
import stem.control
import stem.process
2019-08-09 14:15:00 +00:00
import stem.version
2019-07-23 15:57:46 +00:00
class DebugLogger(object):
"""Safe logger wrapper for tor and plugin's logs"""
# pylint: disable=too-few-public-methods
def __init__(self):
self._logger = logging.getLogger('default')
2019-07-23 15:57:46 +00:00
self._levels = {
'err': 40,
'warn': 30,
'notice': 20
}
def __call__(self, line):
try:
level, line = line.split('[', 1)[1].split(']')
except IndexError:
# Plugin's debug or unexpected log line from tor
self._logger.debug(line)
else:
2019-09-23 10:38:34 +00:00
self._logger.log(self._levels.get(level, 10), '(tor) %s', line)
2019-07-23 15:57:46 +00:00
2019-08-09 14:15:00 +00:00
def connect_plugin(config): # pylint: disable=too-many-branches
2019-10-22 07:10:41 +00:00
"""
Run stem proxy configurator
:param config: current configuration instance
:type config: :class:`pybitmessage.bmconfigparser.BMConfigParser`
:return: True if configuration was done successfully
"""
2019-07-23 15:57:46 +00:00
logwrite = DebugLogger()
2019-10-22 07:10:41 +00:00
if config.safeGet('bitmessagesettings', 'sockshostname', '') not in (
'localhost', '127.0.0.1', ''
2019-07-23 15:57:46 +00:00
):
# remote proxy is choosen for outbound connections,
# nothing to do here, but need to set socksproxytype to SOCKS5!
config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5')
2019-07-23 15:57:46 +00:00
logwrite(
'sockshostname is set to remote address,'
' aborting stem proxy configuration')
return
datadir = tempfile.mkdtemp()
control_socket = os.path.join(datadir, 'control')
tor_config = {
'SocksPort': '9050',
# 'DataDirectory': datadir, # had an exception with control socket
'ControlSocket': control_socket
}
port = config.safeGet('bitmessagesettings', 'socksport', '9050')
for attempt in range(50):
if attempt > 0:
port = random.randint(32767, 65535)
tor_config['SocksPort'] = str(port)
# It's recommended to use separate tor instance for hidden services.
# So if there is a system wide tor, use it for outbound connections.
try:
stem.process.launch_tor_with_config(
2019-08-09 14:15:00 +00:00
tor_config, take_ownership=True, timeout=20,
init_msg_handler=logwrite)
2019-07-23 15:57:46 +00:00
except OSError:
2019-08-09 14:15:00 +00:00
if not attempt:
try:
stem.version.get_system_tor_version()
except IOError:
return
2019-07-23 15:57:46 +00:00
continue
else:
logwrite('Started tor on port %s' % port)
break
config.setTemp('bitmessagesettings', 'socksproxytype', 'SOCKS5')
2019-07-23 15:57:46 +00:00
if config.safeGetBoolean('bitmessagesettings', 'sockslisten'):
# need a hidden service for inbound connections
try:
controller = stem.control.Controller.from_socket_file(
control_socket)
controller.authenticate()
except stem.SocketError:
# something goes wrong way
logwrite('Failed to instantiate or authenticate on controller')
return
onionhostname = config.safeGet('bitmessagesettings', 'onionhostname')
onionkey = config.safeGet(onionhostname, 'privsigningkey')
if onionhostname and not onionkey:
logwrite('The hidden service found in config ): %s' % onionhostname)
onionkeytype = config.safeGet(onionhostname, 'keytype')
response = controller.create_ephemeral_hidden_service(
config.safeGetInt('bitmessagesettings', 'onionport', 8444),
key_type=(onionkeytype or 'NEW'),
key_content=(onionkey or onionhostname and 'ED25519-V3' or 'BEST')
2019-07-23 15:57:46 +00:00
)
if not response.is_ok():
logwrite('Bad response from controller ):')
return
if not onionkey:
logwrite('Started hidden service %s.onion' % response.service_id)
# only save new service keys if onionhostname was not set previously
2019-07-23 15:57:46 +00:00
if not onionhostname:
onionhostname = response.service_id + '.onion'
config.set(
'bitmessagesettings', 'onionhostname', onionhostname)
config.add_section(onionhostname)
config.set(
onionhostname, 'privsigningkey', response.private_key)
config.set(
onionhostname, 'keytype', response.private_key_type)
config.save()
2019-08-09 14:15:00 +00:00
return True