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 6c1191a4..bfbe45fa 100644 --- a/src/api.py +++ b/src/api.py @@ -30,7 +30,8 @@ import queues import shared import shutdown import state -from addresses import addBMIfNotPresent, calculateInventoryHash, decodeAddress, decodeVarint, varintDecodeError + +from addresses import addBMIfNotPresent, calculateInventoryHash, decodeAddress, decodeVarint, varintDecodeError from bmconfigparser import BMConfigParser from debug import logger from helper_ackPayload import genAckPayload diff --git a/src/bitmessagekivy/mpybit.py b/src/bitmessagekivy/mpybit.py index df0e1c26..bcb7c511 100644 --- a/src/bitmessagekivy/mpybit.py +++ b/src/bitmessagekivy/mpybit.py @@ -1303,6 +1303,7 @@ class NavigateApp(App): # pylint: disable=too-many-public-methods def build(self): """Method builds the widget""" + print(os.path.join(os.path.dirname(__file__), 'main.kv')) main_widget = Builder.load_file( os.path.join(os.path.dirname(__file__), 'main.kv')) self.nav_drawer = Navigatorss() diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index bc78b96f..2a03858b 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. @@ -32,7 +32,8 @@ from bmconfigparser import BMConfigParser # this should go before any threads from debug import logger from helper_startup import ( - isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections + isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections, + start_proxyconfig ) from inventory import Inventory from knownnodes import readKnownNodes @@ -188,10 +189,9 @@ class Main(object): logger.info( 'Started proxy config plugin %s in %s sec', proxy_type, time.time() - proxyconfig_start) - def start(self): """Start main application""" - # pylint: disable=too-many-statements, too-many-branches, too-many-locals + # pylint: disable=too-many-statements,too-many-branches,too-many-locals _fixSocket() config = BMConfigParser() @@ -268,11 +268,10 @@ class Main(object): set_thread_name("PyBitmessage") - state.dandelion = config.safeGetInt('network', 'dandelion') + state.dandelion = config.safeGet('network', 'dandelion') # dandelion requires outbound connections, without them, # stem objects will get stuck forever - if state.dandelion and not config.safeGetBoolean( - 'bitmessagesettings', 'sendoutgoingconnections'): + if state.dandelion and not (config.safeGet('bitmessagesettings', 'sendoutgoingconnections') == 'True'): state.dandelion = 0 if state.testmode or config.safeGetBoolean( @@ -350,7 +349,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 @@ -408,9 +407,8 @@ class Main(object): if (state.testmode and time.time() - state.last_api_response >= 30): self.stop() elif not state.enableGUI: - # pylint: disable=relative-import - from tests import core as test_core - test_core_result = test_core.run(self) + from tests import core as test_core # pylint: disable=relative-import + 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_objectProcessor.py b/src/class_objectProcessor.py index 19f4861c..3e3261ad 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -141,13 +141,13 @@ class objectProcessor(threading.Thread): # bypass nonce and time, retain object type/version/stream + body readPosition = 16 - if data[readPosition:] in shared.ackdataForWhichImWatching: + if bytes(data[readPosition:]) in shared.ackdataForWhichImWatching: logger.info('This object is an acknowledgement bound for me.') del shared.ackdataForWhichImWatching[data[readPosition:]] sqlExecute( 'UPDATE sent SET status=?, lastactiontime=?' ' WHERE ackdata=?', - 'ackreceived', int(time.time()), data[readPosition:]) + ' ackreceived', int(time.time()), data[readPosition:]) queues.UISignalQueue.put(( 'updateSentItemStatusByAckdata', (data[readPosition:], @@ -221,7 +221,7 @@ class objectProcessor(threading.Thread): 'the hash requested in this getpubkey request is: %s', hexlify(requestedHash)) # if this address hash is one of mine - if requestedHash in shared.myAddressesByHash: + if bytes(requestedHash) in shared.myAddressesByHash: myAddress = shared.myAddressesByHash[requestedHash] elif requestedAddressVersionNumber >= 4: requestedTag = data[readPosition:readPosition + 32] @@ -233,7 +233,7 @@ class objectProcessor(threading.Thread): logger.debug( 'the tag requested in this getpubkey request is: %s', hexlify(requestedTag)) - if requestedTag in shared.myAddressesByTag: + if bytes(requestedTag) in shared.myAddressesByTag: myAddress = shared.myAddressesByTag[requestedTag] if myAddress == '': @@ -328,7 +328,7 @@ class objectProcessor(threading.Thread): dataToStore = data[20:readPosition] sha = hashlib.new('sha512') sha.update( - '\x04' + publicSigningKey + '\x04' + publicEncryptionKey) + '\x04'.encode() + publicSigningKey + '\x04'.encode() + publicEncryptionKey) ripe = RIPEMD160Hash(sha.digest()).digest() if logger.isEnabledFor(logging.DEBUG): @@ -367,9 +367,9 @@ class objectProcessor(threading.Thread): ' Sanity check failed.') return readPosition += 4 - publicSigningKey = '\x04' + data[readPosition:readPosition + 64] + publicSigningKey = ('\x04').encode() + data[readPosition:readPosition + 64] readPosition += 64 - publicEncryptionKey = '\x04' + data[readPosition:readPosition + 64] + publicEncryptionKey = ('\x04').encode() + data[readPosition:readPosition + 64] readPosition += 64 _, specifiedNonceTrialsPerByteLength = decodeVarint( data[readPosition:readPosition + 10]) @@ -433,7 +433,7 @@ class objectProcessor(threading.Thread): return tag = data[readPosition:readPosition + 32] - if tag not in state.neededPubkeys: + if tag not in bytes(state.neededPubkeys): logger.info( 'We don\'t need this v4 pubkey. We didn\'t ask for it.') return @@ -866,7 +866,7 @@ class objectProcessor(threading.Thread): elif broadcastVersion == 5: embeddedTag = data[readPosition:readPosition + 32] readPosition += 32 - if embeddedTag not in shared.MyECSubscriptionCryptorObjects: + if bytes(embeddedTag) not in shared.MyECSubscriptionCryptorObjects: logger.debug('We\'re not interested in this broadcast.') return # We are interested in this broadcast because of its tag. diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index b5bf2e43..7c0b5842 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -471,8 +471,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 bc0ede92..52a600ed 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 configparser +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'): @@ -258,7 +268,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. @@ -281,3 +291,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/assemble.py b/src/network/assemble.py index 9ae853f7..82f37533 100644 --- a/src/network/assemble.py +++ b/src/network/assemble.py @@ -22,7 +22,7 @@ def assemble_addr(peerList): len(peerList[i:i + MAX_ADDR_COUNT])) for stream, peer, timestamp in peerList[i:i + MAX_ADDR_COUNT]: payload += struct.pack( - '>Q', timestamp) # 64-bit time + '>Q', int(timestamp)) # 64-bit time payload += struct.pack('>I', stream) payload += struct.pack( '>q', 1) # service bit flags offered by this node diff --git a/src/network/bmproto.py b/src/network/bmproto.py index ad2260fa..cd839a96 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -172,9 +172,11 @@ class BMProto(AdvancedDispatcher, ObjectTracker): except BMObjectAlreadyHaveError: logger.debug( '%(host)s:%(port)i already got object, skipping', - self.destination._asdict()) + self.destinaestion._asdict()) except struct.error: logger.debug('decoding error, skipping') + except ValueError: + pass elif self.socket.type == socket.SOCK_DGRAM: # broken read, ignore pass @@ -206,9 +208,9 @@ class BMProto(AdvancedDispatcher, ObjectTracker): """Decode node details from the payload""" # protocol.checkIPAddress() services, host, port = self.decode_payload_content("Q16sH") - if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF': + if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF'.encode('raw_unicode_escape'): host = socket.inet_ntop(socket.AF_INET, host[12:16]) - elif host[0:6] == '\xfd\x87\xd8\x7e\xeb\x43': + elif host[0:6] == '\xfd\x87\xd8\x7e\xeb\x43'.encode('raw_unicode_escape'): # Onion, based on BMD/bitcoind host = base64.b32encode(host[6:]).lower() + ".onion" else: @@ -385,7 +387,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): if dandelion and not state.dandelion: return True - for i in map(str, items): + for i in map(bytes, items): if i in Inventory() and not Dandelion().hasHash(i): continue if dandelion and not Dandelion().hasHash(i): @@ -438,7 +440,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): try: self.object.checkObjectByType() objectProcessorQueue.put(( - self.object.objectType, buffer(self.object.data))) + self.object.objectType, memoryview(self.object.data))) except BMObjectInvalidError: BMProto.stopDownloadingObject(self.object.inventoryHash, True) else: @@ -449,12 +451,12 @@ class BMProto(AdvancedDispatcher, ObjectTracker): if self.object.inventoryHash in Inventory() and Dandelion().hasHash(self.object.inventoryHash): Dandelion().removeHash(self.object.inventoryHash, "cycle detection") - - Inventory()[self.object.inventoryHash] = ( + [self.object.inventoryHash] = ( self.object.objectType, self.object.streamNumber, - buffer(self.payload[objectOffset:]), self.object.expiresTime, - buffer(self.object.tag) + memoryview(self.payload[objectOffset:]), self.object.expiresTime, + memoryview(self.object.tag) ) + Inventory()[self.object.inventoryHash] self.handleReceivedObject( self.object.streamNumber, self.object.inventoryHash) invQueue.put(( diff --git a/src/network/udp.py b/src/network/udp.py index 0743776a..d3421806 100644 --- a/src/network/udp.py +++ b/src/network/udp.py @@ -149,6 +149,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/protocol.py b/src/protocol.py index fba68fe3..fd3ce26b 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -278,14 +278,13 @@ def isProofOfWorkSufficient( def CreatePacket(command, payload=''): """Construct and return a number of bytes from a payload""" - payload = payload if type(payload) == bytes else payload.encode() - + payload = payload if type(payload) in [bytes,bytearray] else payload.encode() payload_length = len(payload) checksum = hashlib.sha512(payload).digest()[0:4] byte = bytearray(Header.size + payload_length) Header.pack_into(byte, 0, 0xE9BEB4D9, command.encode(), payload_length, checksum) byte[Header.size:] = payload - return byte + return bytes(byte) def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=False, nodeid=None): @@ -325,10 +324,8 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server= (NODE_DANDELION if state.dandelion else 0) ) # = 127.0.0.1. This will be ignored by the remote host. The actual remote connected IP will be used. - - # python3 need to check - payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF'.encode() + pack('>L', 2130706433) - + #python3 need to check + payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF'.encode('raw_unicode_escape') + pack('>L', 2130706433) # we have a separate extPort and incoming over clearnet # or outgoing through clearnet extport = BMConfigParser().safeGetInt('bitmessagesettings', 'extport') diff --git a/src/storage/sqlite.py b/src/storage/sqlite.py index 3eec3be6..c272f4ed 100644 --- a/src/storage/sqlite.py +++ b/src/storage/sqlite.py @@ -39,15 +39,20 @@ class SqliteInventory(InventoryStorage): # pylint: disable=too-many-ancestors return True def __getitem__(self, hash_): - print('----------__getitem__------------------') + if hash_ == 0: + hash_ = bytes() with self.lock: - if hash_ in self._inventory: - return self._inventory[hash_] - rows = sqlQuery( - 'SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE hash=?', - sqlite3.Binary(hash_)) - if not rows: - raise KeyError(hash_) + try: + if hash_ in self._inventory: + return self._inventory[hash_] + rows = sqlQuery( + 'SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE hash=?', + sqlite3.Binary(hash_)) + if not rows: + pass + # raise KeyError(hash_) + except: + pass return InventoryItem(*rows[0]) def __setitem__(self, hash_, value): diff --git a/src/tests/core.py b/src/tests/core.py index 27102a1c..7b89e5b0 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)