diff --git a/COPYING b/COPYING index 2e0ae6c1..078bf213 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Copyright (c) 2012-2016 Jonathan Warren -Copyright (c) 2012-2019 The Bitmessage Developers +Copyright (c) 2012-2020 The Bitmessage Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE b/LICENSE index 3be738c0..6bb86242 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) Copyright (c) 2012-2016 Jonathan Warren -Copyright (c) 2012-2019 The Bitmessage Developers +Copyright (c) 2012-2020 The Bitmessage Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/checkdeps.py b/checkdeps.py index 03782037..45dc2fc9 100755 --- a/checkdeps.py +++ b/checkdeps.py @@ -164,7 +164,7 @@ if (not compiler or prereqs) and OPSYS in PACKAGE_MANAGER: if not compiler: compilerToPackages() prereqToPackages() - if mandatory: + if prereqs and mandatory: sys.exit(1) else: print("All the dependencies satisfied, you can install PyBitmessage") diff --git a/requirements.txt b/requirements.txt index be429a9f..c55e5cf1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ python_prctl psutil pycrypto -stem diff --git a/src/api.py b/src/api.py index f9d0a518..3201fba5 100644 --- a/src/api.py +++ b/src/api.py @@ -2,7 +2,7 @@ # pylint: disable=too-many-statements # Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2019 The Bitmessage developers +# Copyright (c) 2012-2020 The Bitmessage developers """ This is not what you run to run the Bitmessage API. Instead, enable the API diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 69c3468b..c5c3998d 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -3,7 +3,7 @@ The PyBitmessage startup script """ # Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2019 The Bitmessage developers +# Copyright (c) 2012-2020 The Bitmessage developers # Distributed under the MIT/X11 software license. See the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -31,7 +31,8 @@ import shutdown from bmconfigparser import BMConfigParser from debug import logger # this should go before any threads from helper_startup import ( - isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections + isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections, + start_proxyconfig ) from inventory import Inventory from knownnodes import readKnownNodes @@ -166,30 +167,9 @@ def signal_handler(signum, frame): class Main(object): """Main PyBitmessage class""" - @staticmethod - def start_proxyconfig(config): - """Check socksproxytype and start any proxy configuration plugin""" - proxy_type = config.safeGet('bitmessagesettings', 'socksproxytype') - if proxy_type not in ('none', 'SOCKS4a', 'SOCKS5'): - # pylint: disable=relative-import - from plugins.plugin import get_plugin - try: - proxyconfig_start = time.time() - if not get_plugin('proxyconfig', name=proxy_type)(config): - raise TypeError - except TypeError: - logger.error( - 'Failed to run proxy config plugin %s', - proxy_type, exc_info=True) - shutdown.doCleanShutdown() - sys.exit(2) - else: - logger.info( - 'Started proxy config plugin %s in %s sec', - proxy_type, time.time() - proxyconfig_start) - - def start(self): # pylint: disable=too-many-statements, too-many-branches, too-many-locals + def start(self): """Start main application""" + # pylint: disable=too-many-statements,too-many-branches,too-many-locals _fixSocket() config = BMConfigParser() @@ -346,7 +326,7 @@ class Main(object): singleAPIThread.start() # start network components if networking is enabled if state.enableNetwork: - self.start_proxyconfig(config) + start_proxyconfig() BMConnectionPool() asyncoreThread = BMNetworkThread() asyncoreThread.daemon = True @@ -405,7 +385,7 @@ class Main(object): self.stop() elif not state.enableGUI: from tests import core as test_core # pylint: disable=relative-import - test_core_result = test_core.run(self) + test_core_result = test_core.run() state.enableGUI = True self.stop() test_core.cleanup() diff --git a/src/bitmessageqt/about.ui b/src/bitmessageqt/about.ui index a912927a..7073bbd1 100644 --- a/src/bitmessageqt/about.ui +++ b/src/bitmessageqt/about.ui @@ -46,7 +46,7 @@ - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2012-2019 The Bitmessage Developers</p></body></html> + <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2012-2020 The Bitmessage Developers</p></body></html> Qt::AlignLeft diff --git a/src/bitmessageqt/dialogs.py b/src/bitmessageqt/dialogs.py index b4bcd2fd..c667edb1 100644 --- a/src/bitmessageqt/dialogs.py +++ b/src/bitmessageqt/dialogs.py @@ -47,7 +47,7 @@ class AboutDialog(QtGui.QDialog, RetranslateMixin): try: self.label_2.setText( self.label_2.text().replace( - '2019', str(last_commit.get('time').year) + '2020', str(last_commit.get('time').year) )) except AttributeError: pass diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 982328cc..011d38ed 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -1,3 +1,4 @@ +import ConfigParser import os import sys @@ -16,10 +17,24 @@ import tempfile import widgets from bmconfigparser import BMConfigParser from helper_sql import sqlExecute, sqlStoredProcedure +from helper_startup import start_proxyconfig from network.asyncore_pollchoose import set_rates from tr import _translate +def getSOCKSProxyType(config): + """Get user socksproxytype setting from *config*""" + try: + result = ConfigParser.SafeConfigParser.get( + config, 'bitmessagesettings', 'socksproxytype') + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + return + else: + if result.lower() in ('', 'none', 'false'): + result = None + return result + + class SettingsDialog(QtGui.QDialog): """The "Settings" dialog""" def __init__(self, parent=None, firstrun=False): @@ -32,6 +47,16 @@ class SettingsDialog(QtGui.QDialog): self.net_restart_needed = False self.timer = QtCore.QTimer() + try: + import pkg_resources + except ImportError: + pass + else: + # Append proxy types defined in plugins + for ep in pkg_resources.iter_entry_points( + 'bitmessage.proxyconfig'): + self.comboBoxProxyType.addItem(ep.name) + self.lineEditMaxOutboundConnections.setValidator( QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections)) @@ -47,20 +72,33 @@ class SettingsDialog(QtGui.QDialog): def adjust_from_config(self, config): """Adjust all widgets state according to config settings""" # pylint: disable=too-many-branches,too-many-statements - self.checkBoxStartOnLogon.setChecked( - config.getboolean('bitmessagesettings', 'startonlogon')) - self.checkBoxMinimizeToTray.setChecked( - config.getboolean('bitmessagesettings', 'minimizetotray')) - self.checkBoxTrayOnClose.setChecked( - config.safeGetBoolean('bitmessagesettings', 'trayonclose')) + if not self.parent.tray.isSystemTrayAvailable(): + self.groupBoxTray.setEnabled(False) + self.groupBoxTray.setTitle(_translate( + "MainWindow", "Tray (not available in your system)")) + for setting in ( + 'minimizetotray', 'trayonclose', 'startintray'): + config.set('bitmessagesettings', setting, 'false') + else: + self.checkBoxMinimizeToTray.setChecked( + config.getboolean('bitmessagesettings', 'minimizetotray')) + self.checkBoxTrayOnClose.setChecked( + config.safeGetBoolean('bitmessagesettings', 'trayonclose')) + self.checkBoxStartInTray.setChecked( + config.getboolean('bitmessagesettings', 'startintray')) + self.checkBoxHideTrayConnectionNotifications.setChecked( - config.getboolean("bitmessagesettings", "hidetrayconnectionnotifications")) + config.getboolean( + 'bitmessagesettings', 'hidetrayconnectionnotifications')) self.checkBoxShowTrayNotifications.setChecked( config.getboolean('bitmessagesettings', 'showtraynotifications')) - self.checkBoxStartInTray.setChecked( - config.getboolean('bitmessagesettings', 'startintray')) + + self.checkBoxStartOnLogon.setChecked( + config.getboolean('bitmessagesettings', 'startonlogon')) + self.checkBoxWillinglySendToMobile.setChecked( - config.safeGetBoolean('bitmessagesettings', 'willinglysendtomobile')) + config.safeGetBoolean( + 'bitmessagesettings', 'willinglysendtomobile')) self.checkBoxUseIdenticons.setChecked( config.safeGetBoolean('bitmessagesettings', 'useidenticons')) self.checkBoxReplyBelow.setChecked( @@ -82,10 +120,12 @@ class SettingsDialog(QtGui.QDialog): "MainWindow", "Start-on-login not yet supported on your OS.")) self.checkBoxMinimizeToTray.setDisabled(True) self.checkBoxMinimizeToTray.setText(_translate( - "MainWindow", "Minimize-to-tray not yet supported on your OS.")) + "MainWindow", + "Minimize-to-tray not yet supported on your OS.")) self.checkBoxShowTrayNotifications.setDisabled(True) self.checkBoxShowTrayNotifications.setText(_translate( - "MainWindow", "Tray notifications not yet supported on your OS.")) + "MainWindow", + "Tray notifications not yet supported on your OS.")) elif 'linux' in sys.platform: self.checkBoxStartOnLogon.setDisabled(True) self.checkBoxStartOnLogon.setText(_translate( @@ -102,21 +142,11 @@ class SettingsDialog(QtGui.QDialog): self.checkBoxOnionOnly.setChecked( config.safeGetBoolean('bitmessagesettings', 'onionservicesonly')) - proxy_type = config.safeGet( - 'bitmessagesettings', 'socksproxytype', 'none') - if proxy_type == 'none': - self.comboBoxProxyType.setCurrentIndex(0) - self.lineEditSocksHostname.setEnabled(False) - self.lineEditSocksPort.setEnabled(False) - self.lineEditSocksUsername.setEnabled(False) - self.lineEditSocksPassword.setEnabled(False) - self.checkBoxAuthentication.setEnabled(False) - self.checkBoxSocksListen.setEnabled(False) - self.checkBoxOnionOnly.setEnabled(False) - elif proxy_type == 'SOCKS4a': - self.comboBoxProxyType.setCurrentIndex(1) - elif proxy_type == 'SOCKS5': - self.comboBoxProxyType.setCurrentIndex(2) + self._proxy_type = getSOCKSProxyType(config) + self.comboBoxProxyType.setCurrentIndex( + 0 if not self._proxy_type + else self.comboBoxProxyType.findText(self._proxy_type)) + self.comboBoxProxyTypeChanged(self.comboBoxProxyType.currentIndex()) self.lineEditSocksHostname.setText( config.get('bitmessagesettings', 'sockshostname')) @@ -204,7 +234,7 @@ class SettingsDialog(QtGui.QDialog): self.checkBoxAuthentication.setEnabled(False) self.checkBoxSocksListen.setEnabled(False) self.checkBoxOnionOnly.setEnabled(False) - elif comboBoxIndex in (1, 2): + else: self.lineEditSocksHostname.setEnabled(True) self.lineEditSocksPort.setEnabled(True) self.checkBoxAuthentication.setEnabled(True) @@ -306,27 +336,22 @@ class SettingsDialog(QtGui.QDialog): upnpThread = upnp.uPnPThread() upnpThread.start() - proxy_type = self.config.safeGet( - 'bitmessagesettings', 'socksproxytype', 'none') - if ( - proxy_type == 'none' and - self.comboBoxProxyType.currentText()[0:5] == 'SOCKS' and - shared.statusIconColor != 'red' - ): - self.net_restart_needed = True - if ( - proxy_type[0:5] == 'SOCKS' and - self.comboBoxProxyType.currentText()[0:5] != 'SOCKS' - ): + proxytype_index = self.comboBoxProxyType.currentIndex() + if proxytype_index == 0: + if self._proxy_type and shared.statusIconColor != 'red': + self.net_restart_needed = True + elif self.comboBoxProxyType.currentText() != self._proxy_type: self.net_restart_needed = True self.parent.statusbar.clearMessage() self.config.set( 'bitmessagesettings', 'socksproxytype', - str(self.comboBoxProxyType.currentText()) - if self.comboBoxProxyType.currentText()[0:5] == 'SOCKS' - else 'none' + 'none' if self.comboBoxProxyType.currentIndex() == 0 + else str(self.comboBoxProxyType.currentText()) ) + if proxytype_index > 2: # last literal proxytype in ui + start_proxyconfig() + self.config.set('bitmessagesettings', 'socksauthentication', str( self.checkBoxAuthentication.isChecked())) self.config.set('bitmessagesettings', 'sockshostname', str( diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 963f2e64..0ffbf442 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -75,7 +75,7 @@ Minimize to tray - true + false @@ -419,12 +419,12 @@ - SOCKS4a + SOCKS4a - SOCKS5 + SOCKS5 diff --git a/src/bitmessageqt/support.py b/src/bitmessageqt/support.py index 2a1ddb18..d6d4543d 100644 --- a/src/bitmessageqt/support.py +++ b/src/bitmessageqt/support.py @@ -15,6 +15,7 @@ from openclpow import openclAvailable, openclEnabled import paths import proofofwork from pyelliptic.openssl import OpenSSL +from settings import getSOCKSProxyType import queues import network.stats import state @@ -118,8 +119,7 @@ def createSupportMessage(myapp): BMConfigParser().safeGet('bitmessagesettings', 'opencl') ) if openclEnabled() else "None" locale = getTranslationLanguage() - socks = BMConfigParser().safeGet( - 'bitmessagesettings', 'socksproxytype', "N/A") + socks = getSOCKSProxyType(BMConfigParser()) or "N/A" upnp = BMConfigParser().safeGet('bitmessagesettings', 'upnp', "N/A") connectedhosts = len(network.stats.connectedHostsList()) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 6007412d..0e8f6caa 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -470,8 +470,8 @@ class singleWorker(StoppableThread): def sendOnionPeerObj(self, peer=None): """Send onionpeer object representing peer""" if not peer: # find own onionhostname - for peer_ in state.ownAddresses: - if peer_.host.endswith('.onion'): + for peer in state.ownAddresses: + if peer.host.endswith('.onion'): break else: return diff --git a/src/helper_startup.py b/src/helper_startup.py index c3fdb38f..8374261d 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -2,11 +2,12 @@ Startup operations. """ # pylint: disable=too-many-branches,too-many-statements -from __future__ import print_function +import logging import os import platform import sys +import time from distutils.version import StrictVersion import defaults @@ -15,6 +16,13 @@ import paths import state from bmconfigparser import BMConfigParser +try: + from plugins.plugin import get_plugin +except ImportError: + get_plugin = None + + +logger = logging.getLogger('default') # The user may de-select Portable Mode in the settings if they want # the config files to stay in the application data folder. @@ -31,14 +39,14 @@ def loadConfig(): needToCreateKeysFile = config.safeGet( 'bitmessagesettings', 'settingsversion') is None if not needToCreateKeysFile: - print( + logger.info( 'Loading config files from directory specified' - ' on startup: %s' % state.appdata) + ' on startup: %s', state.appdata) else: config.read(paths.lookupExeFolder() + 'keys.dat') try: config.get('bitmessagesettings', 'settingsversion') - print('Loading config files from same directory as program.') + logger.info('Loading config files from same directory as program.') needToCreateKeysFile = False state.appdata = paths.lookupExeFolder() except: @@ -49,7 +57,8 @@ def loadConfig(): needToCreateKeysFile = config.safeGet( 'bitmessagesettings', 'settingsversion') is None if not needToCreateKeysFile: - print('Loading existing config files from', state.appdata) + logger.info( + 'Loading existing config files from %s', state.appdata) if needToCreateKeysFile: @@ -104,9 +113,10 @@ def loadConfig(): # Just use the same directory as the program and forget about # the appdata folder state.appdata = '' - print('Creating new config files in same directory as program.') + logger.info( + 'Creating new config files in same directory as program.') else: - print('Creating new config files in', state.appdata) + logger.info('Creating new config files in %s', state.appdata) if not os.path.exists(state.appdata): os.makedirs(state.appdata) if not sys.platform.startswith('win'): @@ -256,7 +266,7 @@ def updateConfig(): 'bitmessagesettings', 'hidetrayconnectionnotifications', 'false') if config.safeGetInt('bitmessagesettings', 'maxoutboundconnections') < 1: config.set('bitmessagesettings', 'maxoutboundconnections', '8') - print('WARNING: your maximum outbound connections must be a number.') + logger.warning('Your maximum outbound connections must be a number.') # TTL is now user-specifiable. Let's add an option to save # whatever the user selects. @@ -279,3 +289,26 @@ def isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections(): return False except Exception: pass + + +def start_proxyconfig(): + """Check socksproxytype and start any proxy configuration plugin""" + if not get_plugin: + return + config = BMConfigParser() + proxy_type = config.safeGet('bitmessagesettings', 'socksproxytype') + if proxy_type and proxy_type not in ('none', 'SOCKS4a', 'SOCKS5'): + try: + proxyconfig_start = time.time() + if not get_plugin('proxyconfig', name=proxy_type)(config): + raise TypeError() + except TypeError: + # cannot import shutdown here ): + logger.error( + 'Failed to run proxy config plugin %s', + proxy_type, exc_info=True) + os._exit(0) # pylint: disable=protected-access + else: + logger.info( + 'Started proxy config plugin %s in %s sec', + proxy_type, time.time() - proxyconfig_start) diff --git a/src/network/udp.py b/src/network/udp.py index cf694567..d8c5f340 100644 --- a/src/network/udp.py +++ b/src/network/udp.py @@ -146,6 +146,9 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes retval = self.socket.sendto( self.write_buf, ('', self.port)) except socket.error as e: - logger.error("socket error on sendato: %s", e) + logger.error("socket error on sendto: %s", e) + if e.errno == 101: + self.announcing = False + self.socket.close() retval = 0 self.slice_write_buf(retval) diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py index e69de29b..285009df 100644 --- a/src/plugins/__init__.py +++ b/src/plugins/__init__.py @@ -0,0 +1,7 @@ +""" +Simple plugin system based on setuptools +---------------------------------------- + + +""" +# .. include:: pybitmessage.plugins.plugin.rst diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index e671a73f..629de0a6 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -1,19 +1,28 @@ # -*- coding: utf-8 -*- """ -src/plugins/plugin.py -=================================== +Operating with plugins """ +import logging + import pkg_resources +logger = logging.getLogger('default') + + def get_plugins(group, point='', name=None, fallback=None): """ - Iterate through plugins (`connect_plugin` attribute of entry point) - which name starts with `point` or equals to `name`. - If `fallback` kwarg specified, plugin with that name yield last. + :param str group: plugin group + :param str point: plugin name prefix + :param name: exact plugin name + :param fallback: fallback plugin name + + Iterate through plugins (``connect_plugin`` attribute of entry point) + which name starts with ``point`` or equals to ``name``. + If ``fallback`` kwarg specified, plugin with that name yield last. """ for ep in pkg_resources.iter_entry_points('bitmessage.' + group): - if name and ep.name == name or ep.name.startswith(point): + if name and ep.name == name or not point or ep.name.startswith(point): try: plugin = ep.load().connect_plugin if ep.name == fallback: @@ -25,6 +34,8 @@ def get_plugins(group, point='', name=None, fallback=None): ValueError, pkg_resources.DistributionNotFound, pkg_resources.UnknownExtra): + logger.debug( + 'Problem while loading %s', ep.name, exc_info=True) continue try: yield _fallback @@ -33,6 +44,8 @@ def get_plugins(group, point='', name=None, fallback=None): def get_plugin(*args, **kwargs): - """Returns first available plugin `from get_plugins()` if any.""" + """ + :return: first available plugin from :func:`get_plugins` if any. + """ for plugin in get_plugins(*args, **kwargs): return plugin diff --git a/src/plugins/proxyconfig_stem.py b/src/plugins/proxyconfig_stem.py index bdbfe8ca..494519f2 100644 --- a/src/plugins/proxyconfig_stem.py +++ b/src/plugins/proxyconfig_stem.py @@ -1,7 +1,15 @@ # -*- coding: utf-8 -*- """ -src/plugins/proxyconfig_stem.py -=================================== +Configure tor proxy and hidden service with +`stem `_ 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. """ import os import logging @@ -36,13 +44,20 @@ class DebugLogger(object): def connect_plugin(config): # pylint: disable=too-many-branches - """Run stem proxy configurator""" + """ + Run stem proxy configurator + + :param config: current configuration instance + :type config: :class:`pybitmessage.bmconfigparser.BMConfigParser` + :return: True if configuration was done successfully + """ logwrite = DebugLogger() - if config.safeGet('bitmessagesettings', 'sockshostname') not in ( - 'localhost', '127.0.0.1', '' + 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! + config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') logwrite( 'sockshostname is set to remote address,' ' aborting stem proxy configuration') @@ -77,6 +92,8 @@ def connect_plugin(config): # pylint: disable=too-many-branches logwrite('Started tor on port %s' % port) break + config.setTemp('bitmessagesettings', 'socksproxytype', 'SOCKS5') + if config.safeGetBoolean('bitmessagesettings', 'sockslisten'): # need a hidden service for inbound connections try: @@ -95,7 +112,8 @@ def connect_plugin(config): # pylint: disable=too-many-branches onionkeytype = config.safeGet(onionhostname, 'keytype') response = controller.create_ephemeral_hidden_service( - config.safeGetInt('bitmessagesettings', 'onionport', 8444), + {config.safeGetInt('bitmessagesettings', 'onionport', 8444): + config.safeGetInt('bitmessagesettings', 'port', 8444)}, key_type=(onionkeytype or 'NEW'), key_content=(onionkey or onionhostname and 'ED25519-V3' or 'BEST') ) @@ -117,6 +135,5 @@ def connect_plugin(config): # pylint: disable=too-many-branches config.set( onionhostname, 'keytype', response.private_key_type) config.save() - config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') - return True + return True diff --git a/src/tests/core.py b/src/tests/core.py index d2456064..d56076c3 100644 --- a/src/tests/core.py +++ b/src/tests/core.py @@ -15,14 +15,20 @@ import knownnodes import state from bmconfigparser import BMConfigParser from helper_msgcoding import MsgEncode, MsgDecode +from helper_startup import start_proxyconfig from network import asyncore_pollchoose as asyncore from network.connectionpool import BMConnectionPool from network.node import Peer from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection from queues import excQueue +try: + import stem.version as stem_version +except ImportError: + stem_version = None + + knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat') -program = None def pickle_knownnodes(): @@ -170,12 +176,29 @@ class TestCore(unittest.TestCase): self.assertIsInstance(con, connection_base) self.assertNotEqual(peer.host, '127.0.0.1') return - else: # pylint: disable=useless-else-on-loop - self.fail( - 'Failed to connect during %s sec' % (time.time() - _started)) + self.fail( + 'Failed to connect during %s sec' % (time.time() - _started)) - def test_onionservicesonly(self): - """test onionservicesonly networking mode""" + def test_bootstrap(self): + """test bootstrapping""" + self._initiate_bootstrap() + self._check_bootstrap() + + @unittest.skipUnless(stem_version, 'No stem, skipping tor dependent test') + def test_bootstrap_tor(self): + """test bootstrapping with tor""" + self._initiate_bootstrap() + BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'stem') + start_proxyconfig() + self._check_bootstrap() + + @unittest.skipUnless(stem_version, 'No stem, skipping tor dependent test') + def test_onionservicesonly(self): # this should start after bootstrap + """ + set onionservicesonly, wait for 3 connections and check them all + are onions + """ + BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'SOCKS5') BMConfigParser().set('bitmessagesettings', 'onionservicesonly', 'true') self._initiate_bootstrap() BMConfigParser().remove_option('bitmessagesettings', 'dontconnect') @@ -184,26 +207,18 @@ class TestCore(unittest.TestCase): for n, peer in enumerate(BMConnectionPool().outboundConnections): if n > 2: return - if not peer.host.endswith('.onion'): + if ( + not peer.host.endswith('.onion') + and not peer.host.startswith('bootstrap') + ): self.fail( 'Found non onion hostname %s in outbound connections!' % peer.host) self.fail('Failed to connect to at least 3 nodes within 360 sec') - def test_bootstrap(self): - """test bootstrapping""" - self._initiate_bootstrap() - self._check_bootstrap() - self._initiate_bootstrap() - BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'stem') - program.start_proxyconfig(BMConfigParser()) - self._check_bootstrap() - -def run(prog): +def run(): """Starts all tests defined in this module""" - global program # pylint: disable=global-statement - program = prog loader = unittest.TestLoader() loader.sortTestMethodsUsing = None suite = loader.loadTestsFromTestCase(TestCore)