Merge branch 'v0.6' of https://github.com/surbhicis/PyBitmessage into UiChanges

This commit is contained in:
surbhi 2020-01-06 15:04:47 +05:30
commit 74036147e5
No known key found for this signature in database
GPG Key ID: 88928762974D3618
18 changed files with 219 additions and 127 deletions

View File

@ -1,5 +1,5 @@
Copyright (c) 2012-2016 Jonathan Warren 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2012-2016 Jonathan Warren 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 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 this software and associated documentation files (the "Software"), to deal in

View File

@ -164,7 +164,7 @@ if (not compiler or prereqs) and OPSYS in PACKAGE_MANAGER:
if not compiler: if not compiler:
compilerToPackages() compilerToPackages()
prereqToPackages() prereqToPackages()
if mandatory: if prereqs and mandatory:
sys.exit(1) sys.exit(1)
else: else:
print("All the dependencies satisfied, you can install PyBitmessage") print("All the dependencies satisfied, you can install PyBitmessage")

View File

@ -1,4 +1,3 @@
python_prctl python_prctl
psutil psutil
pycrypto pycrypto
stem

View File

@ -2,7 +2,7 @@
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# Copyright (c) 2012-2016 Jonathan Warren # 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 This is not what you run to run the Bitmessage API. Instead, enable the API

View File

@ -3,7 +3,7 @@
The PyBitmessage startup script The PyBitmessage startup script
""" """
# Copyright (c) 2012-2016 Jonathan Warren # 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 # Distributed under the MIT/X11 software license. See the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -31,7 +31,8 @@ import shutdown
from bmconfigparser import BMConfigParser from bmconfigparser import BMConfigParser
from debug import logger # this should go before any threads from debug import logger # this should go before any threads
from helper_startup import ( from helper_startup import (
isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections,
start_proxyconfig
) )
from inventory import Inventory from inventory import Inventory
from knownnodes import readKnownNodes from knownnodes import readKnownNodes
@ -166,30 +167,9 @@ def signal_handler(signum, frame):
class Main(object): class Main(object):
"""Main PyBitmessage class""" """Main PyBitmessage class"""
@staticmethod def start(self):
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
"""Start main application""" """Start main application"""
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
_fixSocket() _fixSocket()
config = BMConfigParser() config = BMConfigParser()
@ -346,7 +326,7 @@ class Main(object):
singleAPIThread.start() singleAPIThread.start()
# start network components if networking is enabled # start network components if networking is enabled
if state.enableNetwork: if state.enableNetwork:
self.start_proxyconfig(config) start_proxyconfig()
BMConnectionPool() BMConnectionPool()
asyncoreThread = BMNetworkThread() asyncoreThread = BMNetworkThread()
asyncoreThread.daemon = True asyncoreThread.daemon = True
@ -405,7 +385,7 @@ class Main(object):
self.stop() self.stop()
elif not state.enableGUI: elif not state.enableGUI:
from tests import core as test_core # pylint: disable=relative-import 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 state.enableGUI = True
self.stop() self.stop()
test_core.cleanup() test_core.cleanup()

View File

@ -46,7 +46,7 @@
<item alignment="Qt::AlignLeft"> <item alignment="Qt::AlignLeft">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright © 2012-2016 Jonathan Warren&lt;br/&gt;Copyright © 2012-2019 The Bitmessage Developers&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright © 2012-2016 Jonathan Warren&lt;br/&gt;Copyright © 2012-2020 The Bitmessage Developers&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignLeft</set> <set>Qt::AlignLeft</set>

View File

@ -47,7 +47,7 @@ class AboutDialog(QtGui.QDialog, RetranslateMixin):
try: try:
self.label_2.setText( self.label_2.setText(
self.label_2.text().replace( self.label_2.text().replace(
'2019', str(last_commit.get('time').year) '2020', str(last_commit.get('time').year)
)) ))
except AttributeError: except AttributeError:
pass pass

View File

@ -1,3 +1,4 @@
import ConfigParser
import os import os
import sys import sys
@ -16,10 +17,24 @@ import tempfile
import widgets import widgets
from bmconfigparser import BMConfigParser from bmconfigparser import BMConfigParser
from helper_sql import sqlExecute, sqlStoredProcedure from helper_sql import sqlExecute, sqlStoredProcedure
from helper_startup import start_proxyconfig
from network.asyncore_pollchoose import set_rates from network.asyncore_pollchoose import set_rates
from tr import _translate 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): class SettingsDialog(QtGui.QDialog):
"""The "Settings" dialog""" """The "Settings" dialog"""
def __init__(self, parent=None, firstrun=False): def __init__(self, parent=None, firstrun=False):
@ -32,6 +47,16 @@ class SettingsDialog(QtGui.QDialog):
self.net_restart_needed = False self.net_restart_needed = False
self.timer = QtCore.QTimer() 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( self.lineEditMaxOutboundConnections.setValidator(
QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections)) QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections))
@ -47,20 +72,33 @@ class SettingsDialog(QtGui.QDialog):
def adjust_from_config(self, config): def adjust_from_config(self, config):
"""Adjust all widgets state according to config settings""" """Adjust all widgets state according to config settings"""
# pylint: disable=too-many-branches,too-many-statements # pylint: disable=too-many-branches,too-many-statements
self.checkBoxStartOnLogon.setChecked( if not self.parent.tray.isSystemTrayAvailable():
config.getboolean('bitmessagesettings', 'startonlogon')) 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( self.checkBoxMinimizeToTray.setChecked(
config.getboolean('bitmessagesettings', 'minimizetotray')) config.getboolean('bitmessagesettings', 'minimizetotray'))
self.checkBoxTrayOnClose.setChecked( self.checkBoxTrayOnClose.setChecked(
config.safeGetBoolean('bitmessagesettings', 'trayonclose')) config.safeGetBoolean('bitmessagesettings', 'trayonclose'))
self.checkBoxHideTrayConnectionNotifications.setChecked(
config.getboolean("bitmessagesettings", "hidetrayconnectionnotifications"))
self.checkBoxShowTrayNotifications.setChecked(
config.getboolean('bitmessagesettings', 'showtraynotifications'))
self.checkBoxStartInTray.setChecked( self.checkBoxStartInTray.setChecked(
config.getboolean('bitmessagesettings', 'startintray')) config.getboolean('bitmessagesettings', 'startintray'))
self.checkBoxHideTrayConnectionNotifications.setChecked(
config.getboolean(
'bitmessagesettings', 'hidetrayconnectionnotifications'))
self.checkBoxShowTrayNotifications.setChecked(
config.getboolean('bitmessagesettings', 'showtraynotifications'))
self.checkBoxStartOnLogon.setChecked(
config.getboolean('bitmessagesettings', 'startonlogon'))
self.checkBoxWillinglySendToMobile.setChecked( self.checkBoxWillinglySendToMobile.setChecked(
config.safeGetBoolean('bitmessagesettings', 'willinglysendtomobile')) config.safeGetBoolean(
'bitmessagesettings', 'willinglysendtomobile'))
self.checkBoxUseIdenticons.setChecked( self.checkBoxUseIdenticons.setChecked(
config.safeGetBoolean('bitmessagesettings', 'useidenticons')) config.safeGetBoolean('bitmessagesettings', 'useidenticons'))
self.checkBoxReplyBelow.setChecked( self.checkBoxReplyBelow.setChecked(
@ -82,10 +120,12 @@ class SettingsDialog(QtGui.QDialog):
"MainWindow", "Start-on-login not yet supported on your OS.")) "MainWindow", "Start-on-login not yet supported on your OS."))
self.checkBoxMinimizeToTray.setDisabled(True) self.checkBoxMinimizeToTray.setDisabled(True)
self.checkBoxMinimizeToTray.setText(_translate( 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.setDisabled(True)
self.checkBoxShowTrayNotifications.setText(_translate( 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: elif 'linux' in sys.platform:
self.checkBoxStartOnLogon.setDisabled(True) self.checkBoxStartOnLogon.setDisabled(True)
self.checkBoxStartOnLogon.setText(_translate( self.checkBoxStartOnLogon.setText(_translate(
@ -102,21 +142,11 @@ class SettingsDialog(QtGui.QDialog):
self.checkBoxOnionOnly.setChecked( self.checkBoxOnionOnly.setChecked(
config.safeGetBoolean('bitmessagesettings', 'onionservicesonly')) config.safeGetBoolean('bitmessagesettings', 'onionservicesonly'))
proxy_type = config.safeGet( self._proxy_type = getSOCKSProxyType(config)
'bitmessagesettings', 'socksproxytype', 'none') self.comboBoxProxyType.setCurrentIndex(
if proxy_type == 'none': 0 if not self._proxy_type
self.comboBoxProxyType.setCurrentIndex(0) else self.comboBoxProxyType.findText(self._proxy_type))
self.lineEditSocksHostname.setEnabled(False) self.comboBoxProxyTypeChanged(self.comboBoxProxyType.currentIndex())
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.lineEditSocksHostname.setText( self.lineEditSocksHostname.setText(
config.get('bitmessagesettings', 'sockshostname')) config.get('bitmessagesettings', 'sockshostname'))
@ -204,7 +234,7 @@ class SettingsDialog(QtGui.QDialog):
self.checkBoxAuthentication.setEnabled(False) self.checkBoxAuthentication.setEnabled(False)
self.checkBoxSocksListen.setEnabled(False) self.checkBoxSocksListen.setEnabled(False)
self.checkBoxOnionOnly.setEnabled(False) self.checkBoxOnionOnly.setEnabled(False)
elif comboBoxIndex in (1, 2): else:
self.lineEditSocksHostname.setEnabled(True) self.lineEditSocksHostname.setEnabled(True)
self.lineEditSocksPort.setEnabled(True) self.lineEditSocksPort.setEnabled(True)
self.checkBoxAuthentication.setEnabled(True) self.checkBoxAuthentication.setEnabled(True)
@ -306,27 +336,22 @@ class SettingsDialog(QtGui.QDialog):
upnpThread = upnp.uPnPThread() upnpThread = upnp.uPnPThread()
upnpThread.start() upnpThread.start()
proxy_type = self.config.safeGet( proxytype_index = self.comboBoxProxyType.currentIndex()
'bitmessagesettings', 'socksproxytype', 'none') if proxytype_index == 0:
if ( if self._proxy_type and shared.statusIconColor != 'red':
proxy_type == 'none' and
self.comboBoxProxyType.currentText()[0:5] == 'SOCKS' and
shared.statusIconColor != 'red'
):
self.net_restart_needed = True self.net_restart_needed = True
if ( elif self.comboBoxProxyType.currentText() != self._proxy_type:
proxy_type[0:5] == 'SOCKS' and
self.comboBoxProxyType.currentText()[0:5] != 'SOCKS'
):
self.net_restart_needed = True self.net_restart_needed = True
self.parent.statusbar.clearMessage() self.parent.statusbar.clearMessage()
self.config.set( self.config.set(
'bitmessagesettings', 'socksproxytype', 'bitmessagesettings', 'socksproxytype',
str(self.comboBoxProxyType.currentText()) 'none' if self.comboBoxProxyType.currentIndex() == 0
if self.comboBoxProxyType.currentText()[0:5] == 'SOCKS' else str(self.comboBoxProxyType.currentText())
else 'none'
) )
if proxytype_index > 2: # last literal proxytype in ui
start_proxyconfig()
self.config.set('bitmessagesettings', 'socksauthentication', str( self.config.set('bitmessagesettings', 'socksauthentication', str(
self.checkBoxAuthentication.isChecked())) self.checkBoxAuthentication.isChecked()))
self.config.set('bitmessagesettings', 'sockshostname', str( self.config.set('bitmessagesettings', 'sockshostname', str(

View File

@ -75,7 +75,7 @@
<string>Minimize to tray</string> <string>Minimize to tray</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -419,12 +419,12 @@
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>SOCKS4a</string> <string notr="true">SOCKS4a</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>SOCKS5</string> <string notr="true">SOCKS5</string>
</property> </property>
</item> </item>
</widget> </widget>

View File

@ -15,6 +15,7 @@ from openclpow import openclAvailable, openclEnabled
import paths import paths
import proofofwork import proofofwork
from pyelliptic.openssl import OpenSSL from pyelliptic.openssl import OpenSSL
from settings import getSOCKSProxyType
import queues import queues
import network.stats import network.stats
import state import state
@ -118,8 +119,7 @@ def createSupportMessage(myapp):
BMConfigParser().safeGet('bitmessagesettings', 'opencl') BMConfigParser().safeGet('bitmessagesettings', 'opencl')
) if openclEnabled() else "None" ) if openclEnabled() else "None"
locale = getTranslationLanguage() locale = getTranslationLanguage()
socks = BMConfigParser().safeGet( socks = getSOCKSProxyType(BMConfigParser()) or "N/A"
'bitmessagesettings', 'socksproxytype', "N/A")
upnp = BMConfigParser().safeGet('bitmessagesettings', 'upnp', "N/A") upnp = BMConfigParser().safeGet('bitmessagesettings', 'upnp', "N/A")
connectedhosts = len(network.stats.connectedHostsList()) connectedhosts = len(network.stats.connectedHostsList())

View File

@ -470,8 +470,8 @@ class singleWorker(StoppableThread):
def sendOnionPeerObj(self, peer=None): def sendOnionPeerObj(self, peer=None):
"""Send onionpeer object representing peer""" """Send onionpeer object representing peer"""
if not peer: # find own onionhostname if not peer: # find own onionhostname
for peer_ in state.ownAddresses: for peer in state.ownAddresses:
if peer_.host.endswith('.onion'): if peer.host.endswith('.onion'):
break break
else: else:
return return

View File

@ -2,11 +2,12 @@
Startup operations. Startup operations.
""" """
# pylint: disable=too-many-branches,too-many-statements # pylint: disable=too-many-branches,too-many-statements
from __future__ import print_function
import logging
import os import os
import platform import platform
import sys import sys
import time
from distutils.version import StrictVersion from distutils.version import StrictVersion
import defaults import defaults
@ -15,6 +16,13 @@ import paths
import state import state
from bmconfigparser import BMConfigParser 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 user may de-select Portable Mode in the settings if they want
# the config files to stay in the application data folder. # the config files to stay in the application data folder.
@ -31,14 +39,14 @@ def loadConfig():
needToCreateKeysFile = config.safeGet( needToCreateKeysFile = config.safeGet(
'bitmessagesettings', 'settingsversion') is None 'bitmessagesettings', 'settingsversion') is None
if not needToCreateKeysFile: if not needToCreateKeysFile:
print( logger.info(
'Loading config files from directory specified' 'Loading config files from directory specified'
' on startup: %s' % state.appdata) ' on startup: %s', state.appdata)
else: else:
config.read(paths.lookupExeFolder() + 'keys.dat') config.read(paths.lookupExeFolder() + 'keys.dat')
try: try:
config.get('bitmessagesettings', 'settingsversion') 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 needToCreateKeysFile = False
state.appdata = paths.lookupExeFolder() state.appdata = paths.lookupExeFolder()
except: except:
@ -49,7 +57,8 @@ def loadConfig():
needToCreateKeysFile = config.safeGet( needToCreateKeysFile = config.safeGet(
'bitmessagesettings', 'settingsversion') is None 'bitmessagesettings', 'settingsversion') is None
if not needToCreateKeysFile: if not needToCreateKeysFile:
print('Loading existing config files from', state.appdata) logger.info(
'Loading existing config files from %s', state.appdata)
if needToCreateKeysFile: if needToCreateKeysFile:
@ -104,9 +113,10 @@ def loadConfig():
# Just use the same directory as the program and forget about # Just use the same directory as the program and forget about
# the appdata folder # the appdata folder
state.appdata = '' state.appdata = ''
print('Creating new config files in same directory as program.') logger.info(
'Creating new config files in same directory as program.')
else: 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): if not os.path.exists(state.appdata):
os.makedirs(state.appdata) os.makedirs(state.appdata)
if not sys.platform.startswith('win'): if not sys.platform.startswith('win'):
@ -256,7 +266,7 @@ def updateConfig():
'bitmessagesettings', 'hidetrayconnectionnotifications', 'false') 'bitmessagesettings', 'hidetrayconnectionnotifications', 'false')
if config.safeGetInt('bitmessagesettings', 'maxoutboundconnections') < 1: if config.safeGetInt('bitmessagesettings', 'maxoutboundconnections') < 1:
config.set('bitmessagesettings', 'maxoutboundconnections', '8') 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 # TTL is now user-specifiable. Let's add an option to save
# whatever the user selects. # whatever the user selects.
@ -279,3 +289,26 @@ def isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections():
return False return False
except Exception: except Exception:
pass 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)

View File

@ -146,6 +146,9 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
retval = self.socket.sendto( retval = self.socket.sendto(
self.write_buf, ('<broadcast>', self.port)) self.write_buf, ('<broadcast>', self.port))
except socket.error as e: 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 retval = 0
self.slice_write_buf(retval) self.slice_write_buf(retval)

View File

@ -0,0 +1,7 @@
"""
Simple plugin system based on setuptools
----------------------------------------
"""
# .. include:: pybitmessage.plugins.plugin.rst

View File

@ -1,19 +1,28 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
src/plugins/plugin.py Operating with plugins
===================================
""" """
import logging
import pkg_resources import pkg_resources
logger = logging.getLogger('default')
def get_plugins(group, point='', name=None, fallback=None): def get_plugins(group, point='', name=None, fallback=None):
""" """
Iterate through plugins (`connect_plugin` attribute of entry point) :param str group: plugin group
which name starts with `point` or equals to `name`. :param str point: plugin name prefix
If `fallback` kwarg specified, plugin with that name yield last. :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): 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: try:
plugin = ep.load().connect_plugin plugin = ep.load().connect_plugin
if ep.name == fallback: if ep.name == fallback:
@ -25,6 +34,8 @@ def get_plugins(group, point='', name=None, fallback=None):
ValueError, ValueError,
pkg_resources.DistributionNotFound, pkg_resources.DistributionNotFound,
pkg_resources.UnknownExtra): pkg_resources.UnknownExtra):
logger.debug(
'Problem while loading %s', ep.name, exc_info=True)
continue continue
try: try:
yield _fallback yield _fallback
@ -33,6 +44,8 @@ def get_plugins(group, point='', name=None, fallback=None):
def get_plugin(*args, **kwargs): 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): for plugin in get_plugins(*args, **kwargs):
return plugin return plugin

View File

@ -1,7 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
src/plugins/proxyconfig_stem.py 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.
""" """
import os import os
import logging import logging
@ -36,13 +44,20 @@ class DebugLogger(object):
def connect_plugin(config): # pylint: disable=too-many-branches 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() logwrite = DebugLogger()
if config.safeGet('bitmessagesettings', 'sockshostname') not in ( if config.safeGet('bitmessagesettings', 'sockshostname', '') not in (
'localhost', '127.0.0.1', '' 'localhost', '127.0.0.1', ''
): ):
# remote proxy is choosen for outbound connections, # remote proxy is choosen for outbound connections,
# nothing to do here, but need to set socksproxytype to SOCKS5! # nothing to do here, but need to set socksproxytype to SOCKS5!
config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5')
logwrite( logwrite(
'sockshostname is set to remote address,' 'sockshostname is set to remote address,'
' aborting stem proxy configuration') ' aborting stem proxy configuration')
@ -77,6 +92,8 @@ def connect_plugin(config): # pylint: disable=too-many-branches
logwrite('Started tor on port %s' % port) logwrite('Started tor on port %s' % port)
break break
config.setTemp('bitmessagesettings', 'socksproxytype', 'SOCKS5')
if config.safeGetBoolean('bitmessagesettings', 'sockslisten'): if config.safeGetBoolean('bitmessagesettings', 'sockslisten'):
# need a hidden service for inbound connections # need a hidden service for inbound connections
try: try:
@ -95,7 +112,8 @@ def connect_plugin(config): # pylint: disable=too-many-branches
onionkeytype = config.safeGet(onionhostname, 'keytype') onionkeytype = config.safeGet(onionhostname, 'keytype')
response = controller.create_ephemeral_hidden_service( 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_type=(onionkeytype or 'NEW'),
key_content=(onionkey or onionhostname and 'ED25519-V3' or 'BEST') 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( config.set(
onionhostname, 'keytype', response.private_key_type) onionhostname, 'keytype', response.private_key_type)
config.save() config.save()
config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5')
return True return True

View File

@ -15,14 +15,20 @@ import knownnodes
import state import state
from bmconfigparser import BMConfigParser from bmconfigparser import BMConfigParser
from helper_msgcoding import MsgEncode, MsgDecode from helper_msgcoding import MsgEncode, MsgDecode
from helper_startup import start_proxyconfig
from network import asyncore_pollchoose as asyncore from network import asyncore_pollchoose as asyncore
from network.connectionpool import BMConnectionPool from network.connectionpool import BMConnectionPool
from network.node import Peer from network.node import Peer
from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection
from queues import excQueue 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') knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat')
program = None
def pickle_knownnodes(): def pickle_knownnodes():
@ -170,12 +176,29 @@ class TestCore(unittest.TestCase):
self.assertIsInstance(con, connection_base) self.assertIsInstance(con, connection_base)
self.assertNotEqual(peer.host, '127.0.0.1') self.assertNotEqual(peer.host, '127.0.0.1')
return return
else: # pylint: disable=useless-else-on-loop
self.fail( self.fail(
'Failed to connect during %s sec' % (time.time() - _started)) 'Failed to connect during %s sec' % (time.time() - _started))
def test_onionservicesonly(self): def test_bootstrap(self):
"""test onionservicesonly networking mode""" """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') BMConfigParser().set('bitmessagesettings', 'onionservicesonly', 'true')
self._initiate_bootstrap() self._initiate_bootstrap()
BMConfigParser().remove_option('bitmessagesettings', 'dontconnect') BMConfigParser().remove_option('bitmessagesettings', 'dontconnect')
@ -184,26 +207,18 @@ class TestCore(unittest.TestCase):
for n, peer in enumerate(BMConnectionPool().outboundConnections): for n, peer in enumerate(BMConnectionPool().outboundConnections):
if n > 2: if n > 2:
return return
if not peer.host.endswith('.onion'): if (
not peer.host.endswith('.onion')
and not peer.host.startswith('bootstrap')
):
self.fail( self.fail(
'Found non onion hostname %s in outbound connections!' 'Found non onion hostname %s in outbound connections!'
% peer.host) % peer.host)
self.fail('Failed to connect to at least 3 nodes within 360 sec') 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():
def run(prog):
"""Starts all tests defined in this module""" """Starts all tests defined in this module"""
global program # pylint: disable=global-statement
program = prog
loader = unittest.TestLoader() loader = unittest.TestLoader()
loader.sortTestMethodsUsing = None loader.sortTestMethodsUsing = None
suite = loader.loadTestsFromTestCase(TestCore) suite = loader.loadTestsFromTestCase(TestCore)