diff --git a/src/plugins/proxyconfig_stem.py b/src/plugins/proxyconfig_stem.py new file mode 100644 index 00000000..567d8253 --- /dev/null +++ b/src/plugins/proxyconfig_stem.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +import os +import logging +import random # noseq +import tempfile + +import stem +import stem.control +import stem.process + + +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(__name__.split('.', 1)[0]) + 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: + self._logger.log(self._levels.get(level, 10), '(tor)' + line) + + +def connect_plugin(config): + """Run stem proxy configurator""" + logwrite = DebugLogger() + if config.safeGet('bitmessagesettings', 'sockshostname') not in ( + 'localhost', '127.0.0.1', '' + ): + # remote proxy is choosen for outbound connections, + # nothing to do here, but need to set socksproxytype to SOCKS5! + 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( + tor_config, take_ownership=True, init_msg_handler=logwrite) + except OSError: + continue + else: + logwrite('Started tor on port %s' % port) + break + + 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 'BEST') + ) + + if not response.is_ok(): + logwrite('Bad response from controller ):') + return + + if not onionkey: + if not onionhostname: + onionhostname = response.service_id + '.onion' + config.set( + 'bitmessagesettings', 'onionhostname', onionhostname) + else: + onionhostname = response.service_id + '.onion' + logwrite('Started hidden service %s' % onionhostname) + config.add_section(onionhostname) + config.set( + onionhostname, 'privsigningkey', response.private_key) + config.set( + onionhostname, 'keytype', response.private_key_type) + config.save() + config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5')