Attachments a (#13) sq n mg

* Formatted protocol and its docstrings

* Fixed some docstrings in shared and state

* Fixed google style docstrings in addresses

* More docstrings and formatting fixes in highlevelcrypto and shutdown

* .readthedocs.yml

* When clicking a link in a message viewed in HTML mode, if the link represents a data blob, launch a "Save File" dialog and write the file directly, rather than opening the link in an external browser.

* Add "Attach File" button to message composition.

* pylint fixes.
This commit is contained in:
bug Lady 2019-11-29 14:36:36 +00:00 committed by GitHub
parent bb40d970cd
commit c4e5385554
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 228 additions and 107 deletions

9
.readthedocs.yml Normal file
View File

@ -0,0 +1,9 @@
version: 2
python:
version: 2.7
install:
- requirements: docs/requirements.txt
- method: setuptools
path: .
system_packages: true

View File

@ -1,7 +1,5 @@
""" """
src/addresses.py Operations with addresses
================
""" """
# pylint: disable=redefined-outer-name,inconsistent-return-statements # pylint: disable=redefined-outer-name,inconsistent-return-statements
@ -18,8 +16,9 @@ ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def encodeBase58(num, alphabet=ALPHABET): def encodeBase58(num, alphabet=ALPHABET):
"""Encode a number in Base X """Encode a number in Base X
`num`: The number to encode Args:
`alphabet`: The alphabet to use for encoding num: The number to encode
alphabet: The alphabet to use for encoding
""" """
if num == 0: if num == 0:
return alphabet[0] return alphabet[0]
@ -27,7 +26,6 @@ def encodeBase58(num, alphabet=ALPHABET):
base = len(alphabet) base = len(alphabet)
while num: while num:
rem = num % base rem = num % base
# print 'num is:', num
num = num // base num = num // base
arr.append(alphabet[rem]) arr.append(alphabet[rem])
arr.reverse() arr.reverse()
@ -37,9 +35,9 @@ def encodeBase58(num, alphabet=ALPHABET):
def decodeBase58(string, alphabet=ALPHABET): def decodeBase58(string, alphabet=ALPHABET):
"""Decode a Base X encoded string into the number """Decode a Base X encoded string into the number
Arguments: Args:
- `string`: The encoded string string: The encoded string
- `alphabet`: The alphabet to use for encoding alphabet: The alphabet to use for encoding
""" """
base = len(alphabet) base = len(alphabet)
num = 0 num = 0

View File

@ -11,6 +11,7 @@ import sys
import textwrap import textwrap
import threading import threading
import time import time
import base64
from datetime import datetime, timedelta from datetime import datetime, timedelta
from sqlite3 import register_adapter from sqlite3 import register_adapter
@ -161,6 +162,8 @@ class MyForm(settingsmixin.SMainWindow):
"clicked()"), self.click_pushButtonTTL) "clicked()"), self.click_pushButtonTTL)
QtCore.QObject.connect(self.ui.pushButtonClear, QtCore.SIGNAL( QtCore.QObject.connect(self.ui.pushButtonClear, QtCore.SIGNAL(
"clicked()"), self.click_pushButtonClear) "clicked()"), self.click_pushButtonClear)
QtCore.QObject.connect(self.ui.pushButtonAttach, QtCore.SIGNAL(
"clicked()"), self.click_pushButtonAttach)
QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL( QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL(
"clicked()"), self.click_pushButtonSend) "clicked()"), self.click_pushButtonSend)
QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL( QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL(
@ -1951,6 +1954,23 @@ class MyForm(settingsmixin.SMainWindow):
self.ui.textEditMessage.reset() self.ui.textEditMessage.reset()
self.ui.comboBoxSendFrom.setCurrentIndex(0) self.ui.comboBoxSendFrom.setCurrentIndex(0)
def click_pushButtonAttach(self):
"""Launch a file picker and append to the current message the base64-encoded contents of the chosen file."""
filename = QtGui.QFileDialog.getOpenFileName(self, "Attach File")
if filename:
f = open(filename, 'rb')
data = f.read()
f.close()
data_b64 = base64.b64encode(data)
html_data = '<a href="data:application/octet-stream;base64,' + data_b64 + '">' \
+ os.path.basename(unicode(filename)) + '</a>'
if self.ui.tabWidgetSend.currentIndex() == self.ui.tabWidgetSend.indexOf(self.ui.sendDirect):
# send direct message
self.ui.textEditMessage.insertPlainText(html_data)
else:
# send broadcast message
self.ui.textEditMessageBroadcast.insertPlainText(html_data)
def click_pushButtonSend(self): def click_pushButtonSend(self):
encoding = 3 if QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier else 2 encoding = 3 if QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier else 2

View File

@ -340,6 +340,9 @@ class Ui_MainWindow(object):
self.pushButtonClear = QtGui.QPushButton(self.send) self.pushButtonClear = QtGui.QPushButton(self.send)
self.pushButtonClear.setObjectName(_fromUtf8("pushButtonClear")) self.pushButtonClear.setObjectName(_fromUtf8("pushButtonClear"))
self.horizontalLayout_5.addWidget(self.pushButtonClear, 0, QtCore.Qt.AlignRight) self.horizontalLayout_5.addWidget(self.pushButtonClear, 0, QtCore.Qt.AlignRight)
self.pushButtonAttach = QtGui.QPushButton(self.send)
self.pushButtonAttach.setObjectName(_fromUtf8("pushButtonAttach"))
self.horizontalLayout_5.addWidget(self.pushButtonAttach, 0, QtCore.Qt.AlignRight)
self.pushButtonSend = QtGui.QPushButton(self.send) self.pushButtonSend = QtGui.QPushButton(self.send)
self.pushButtonSend.setObjectName(_fromUtf8("pushButtonSend")) self.pushButtonSend.setObjectName(_fromUtf8("pushButtonSend"))
self.horizontalLayout_5.addWidget(self.pushButtonSend, 0, QtCore.Qt.AlignRight) self.horizontalLayout_5.addWidget(self.pushButtonSend, 0, QtCore.Qt.AlignRight)
@ -713,6 +716,7 @@ class Ui_MainWindow(object):
pass pass
self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours)) self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours))
self.pushButtonClear.setText(_translate("MainWindow", "Clear", None)) self.pushButtonClear.setText(_translate("MainWindow", "Clear", None))
self.pushButtonAttach.setText(_translate("MainWindow", "Attach File", None))
self.pushButtonSend.setText(_translate("MainWindow", "Send", None)) self.pushButtonSend.setText(_translate("MainWindow", "Send", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), _translate("MainWindow", "Send", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), _translate("MainWindow", "Send", None))
self.treeWidgetSubscriptions.headerItem().setText(0, _translate("MainWindow", "Subscriptions", None)) self.treeWidgetSubscriptions.headerItem().setText(0, _translate("MainWindow", "Subscriptions", None))

View File

@ -594,6 +594,19 @@ p, li { white-space: pre-wrap; }
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="pushButtonAttach">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Attach File</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@ -5,6 +5,8 @@ src/bitmessageqt/messageview.py
""" """
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
import re
import base64
from safehtmlparser import SafeHTMLParser from safehtmlparser import SafeHTMLParser
@ -64,6 +66,9 @@ class MessageView(QtGui.QTextBrowser):
def confirmURL(self, link): def confirmURL(self, link):
"""Show a dialog requesting URL opening confirmation""" """Show a dialog requesting URL opening confirmation"""
link_str = link.toString()
datablob_re = r'^data:.*/.*;base64,.*'
datablob_match = re.match(datablob_re, link_str)
if link.scheme() == "mailto": if link.scheme() == "mailto":
window = QtGui.QApplication.activeWindow() window = QtGui.QApplication.activeWindow()
window.ui.lineEditTo.setText(link.path()) window.ui.lineEditTo.setText(link.path())
@ -80,19 +85,29 @@ class MessageView(QtGui.QTextBrowser):
) )
window.ui.textEditMessage.setFocus() window.ui.textEditMessage.setFocus()
return return
reply = QtGui.QMessageBox.warning( if datablob_match:
self, name = QtGui.QFileDialog.getSaveFileName(self, 'Save File')
QtGui.QApplication.translate( if name:
"MessageView", f = open(name, 'wb')
"Follow external link"), data_begin_pos = re.finditer(";base64,", link_str).next()
QtGui.QApplication.translate( data_b64 = link_str[data_begin_pos.span()[1]:]
"MessageView", data = base64.b64decode(data_b64)
"The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you" f.write(data)
" or download malicious data. Are you sure?").arg(unicode(link.toString())), f.close()
QtGui.QMessageBox.Yes, else:
QtGui.QMessageBox.No) reply = QtGui.QMessageBox.warning(
if reply == QtGui.QMessageBox.Yes: self,
QtGui.QDesktopServices.openUrl(link) QtGui.QApplication.translate(
"MessageView",
"Follow external link"),
QtGui.QApplication.translate(
"MessageView",
"The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you"
" or download malicious data. Are you sure?").arg(unicode(link.toString())),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
QtGui.QDesktopServices.openUrl(link)
def loadResource(self, restype, name): def loadResource(self, restype, name):
""" """

View File

@ -1,6 +1,10 @@
""" """
src/highlevelcrypto.py High level cryptographic functions based on `.pyelliptic` OpenSSL bindings.
======================
.. note::
Upstream pyelliptic was upgraded from SHA1 to SHA256 for signing.
We must upgrade PyBitmessage gracefully.
`More discussion. <https://github.com/yann2192/pyelliptic/issues/32>`_
""" """
from binascii import hexlify from binascii import hexlify
@ -12,12 +16,13 @@ from pyelliptic import arithmetic as a
def makeCryptor(privkey): def makeCryptor(privkey):
"""Return a private pyelliptic.ECC() instance""" """Return a private `.pyelliptic.ECC` instance"""
private_key = a.changebase(privkey, 16, 256, minlen=32) private_key = a.changebase(privkey, 16, 256, minlen=32)
public_key = pointMult(private_key) public_key = pointMult(private_key)
privkey_bin = '\x02\xca\x00\x20' + private_key privkey_bin = '\x02\xca\x00\x20' + private_key
pubkey_bin = '\x02\xca\x00\x20' + public_key[1:-32] + '\x00\x20' + public_key[-32:] pubkey_bin = '\x02\xca\x00\x20' + public_key[1:-32] + '\x00\x20' + public_key[-32:]
cryptor = pyelliptic.ECC(curve='secp256k1', privkey=privkey_bin, pubkey=pubkey_bin) cryptor = pyelliptic.ECC(
curve='secp256k1', privkey=privkey_bin, pubkey=pubkey_bin)
return cryptor return cryptor
@ -29,7 +34,7 @@ def hexToPubkey(pubkey):
def makePubCryptor(pubkey): def makePubCryptor(pubkey):
"""Return a public pyelliptic.ECC() instance""" """Return a public `.pyelliptic.ECC` instance"""
pubkey_bin = hexToPubkey(pubkey) pubkey_bin = hexToPubkey(pubkey)
return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin) return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin)
@ -43,7 +48,8 @@ def privToPub(privkey):
def encrypt(msg, hexPubkey): def encrypt(msg, hexPubkey):
"""Encrypts message with hex public key""" """Encrypts message with hex public key"""
return pyelliptic.ECC(curve='secp256k1').encrypt(msg, hexToPubkey(hexPubkey)) return pyelliptic.ECC(curve='secp256k1').encrypt(
msg, hexToPubkey(hexPubkey))
def decrypt(msg, hexPrivkey): def decrypt(msg, hexPrivkey):
@ -52,36 +58,38 @@ def decrypt(msg, hexPrivkey):
def decryptFast(msg, cryptor): def decryptFast(msg, cryptor):
"""Decrypts message with an existing pyelliptic.ECC.ECC object""" """Decrypts message with an existing `.pyelliptic.ECC` object"""
return cryptor.decrypt(msg) return cryptor.decrypt(msg)
def sign(msg, hexPrivkey): def sign(msg, hexPrivkey):
"""Signs with hex private key""" """
# pyelliptic is upgrading from SHA1 to SHA256 for signing. We must Signs with hex private key using SHA1 or SHA256 depending on
# upgrade PyBitmessage gracefully. "digestalg" setting
# https://github.com/yann2192/pyelliptic/pull/33 """
# More discussion: https://github.com/yann2192/pyelliptic/issues/32 digestAlg = BMConfigParser().safeGet(
digestAlg = BMConfigParser().safeGet('bitmessagesettings', 'digestalg', 'sha1') 'bitmessagesettings', 'digestalg', 'sha1')
if digestAlg == "sha1": if digestAlg == "sha1":
# SHA1, this will eventually be deprecated # SHA1, this will eventually be deprecated
return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.digest_ecdsa_sha1) return makeCryptor(hexPrivkey).sign(
msg, digest_alg=OpenSSL.digest_ecdsa_sha1)
elif digestAlg == "sha256": elif digestAlg == "sha256":
# SHA256. Eventually this will become the default # SHA256. Eventually this will become the default
return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256) return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256)
else: else:
raise ValueError("Unknown digest algorithm %s" % (digestAlg)) raise ValueError("Unknown digest algorithm %s" % digestAlg)
def verify(msg, sig, hexPubkey): def verify(msg, sig, hexPubkey):
"""Verifies with hex public key""" """Verifies with hex public key using SHA1 or SHA256"""
# As mentioned above, we must upgrade gracefully to use SHA256. So # As mentioned above, we must upgrade gracefully to use SHA256. So
# let us check the signature using both SHA1 and SHA256 and if one # let us check the signature using both SHA1 and SHA256 and if one
# of them passes then we will be satisfied. Eventually this can # of them passes then we will be satisfied. Eventually this can
# be simplified and we'll only check with SHA256. # be simplified and we'll only check with SHA256.
try: try:
# old SHA1 algorithm. # old SHA1 algorithm.
sigVerifyPassed = makePubCryptor(hexPubkey).verify(sig, msg, digest_alg=OpenSSL.digest_ecdsa_sha1) sigVerifyPassed = makePubCryptor(hexPubkey).verify(
sig, msg, digest_alg=OpenSSL.digest_ecdsa_sha1)
except: except:
sigVerifyPassed = False sigVerifyPassed = False
if sigVerifyPassed: if sigVerifyPassed:
@ -89,7 +97,8 @@ def verify(msg, sig, hexPubkey):
return True return True
# The signature check using SHA1 failed. Let us try it with SHA256. # The signature check using SHA1 failed. Let us try it with SHA256.
try: try:
return makePubCryptor(hexPubkey).verify(sig, msg, digest_alg=OpenSSL.EVP_sha256) return makePubCryptor(hexPubkey).verify(
sig, msg, digest_alg=OpenSSL.EVP_sha256)
except: except:
return False return False
@ -106,7 +115,8 @@ def pointMult(secret):
""" """
while True: while True:
try: try:
k = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1')) k = OpenSSL.EC_KEY_new_by_curve_name(
OpenSSL.get_curve('secp256k1'))
priv_key = OpenSSL.BN_bin2bn(secret, 32, None) priv_key = OpenSSL.BN_bin2bn(secret, 32, None)
group = OpenSSL.EC_KEY_get0_group(k) group = OpenSSL.EC_KEY_get0_group(k)
pub_key = OpenSSL.EC_POINT_new(group) pub_key = OpenSSL.EC_POINT_new(group)

View File

@ -1,7 +1,8 @@
# pylint: disable=too-many-boolean-expressions,too-many-return-statements,too-many-locals,too-many-statements
""" """
Low-level protocol-related functions. Low-level protocol-related functions.
""" """
# pylint: disable=too-many-boolean-expressions,too-many-return-statements
# pylint: disable=too-many-locals,too-many-statements
import base64 import base64
import hashlib import hashlib
@ -9,7 +10,6 @@ import random
import socket import socket
import sys import sys
import time import time
import traceback
from binascii import hexlify from binascii import hexlify
from struct import pack, unpack, Struct from struct import pack, unpack, Struct
@ -24,10 +24,18 @@ from fallback import RIPEMD160Hash
from helper_sql import sqlExecute from helper_sql import sqlExecute
from version import softwareVersion from version import softwareVersion
# Service flags # Service flags
#: This is a normal network node
NODE_NETWORK = 1 NODE_NETWORK = 1
#: This node supports SSL/TLS in the current connect (python < 2.7.9
#: only supports an SSL client, so in that case it would only have this
#: on when the connection is a client).
NODE_SSL = 2 NODE_SSL = 2
# (Proposal) This node may do PoW on behalf of some its peers
# (PoW offloading/delegating), but it doesn't have to. Clients may have
# to meet additional requirements (e.g. TLS authentication)
# NODE_POW = 4
#: Node supports dandelion
NODE_DANDELION = 8 NODE_DANDELION = 8
# Bitfield flags # Bitfield flags
@ -89,7 +97,8 @@ def isBitSetWithinBitfield(fourByteString, n):
def encodeHost(host): def encodeHost(host):
"""Encode a given host to be used in low-level socket operations""" """Encode a given host to be used in low-level socket operations"""
if host.find('.onion') > -1: if host.find('.onion') > -1:
return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(host.split(".")[0], True) return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(
host.split(".")[0], True)
elif host.find(':') == -1: elif host.find(':') == -1:
return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \ return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
socket.inet_aton(host) socket.inet_aton(host)
@ -134,7 +143,10 @@ def network_group(host):
def checkIPAddress(host, private=False): def checkIPAddress(host, private=False):
"""Returns hostStandardFormat if it is a valid IP address, otherwise returns False""" """
Returns hostStandardFormat if it is a valid IP address,
otherwise returns False
"""
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':
hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:]) hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:])
return checkIPv4Address(host[12:], hostStandardFormat, private) return checkIPv4Address(host[12:], hostStandardFormat, private)
@ -150,35 +162,46 @@ def checkIPAddress(host, private=False):
except ValueError: except ValueError:
return False return False
if hostStandardFormat == "": if hostStandardFormat == "":
# This can happen on Windows systems which are not 64-bit compatible # This can happen on Windows systems which are
# so let us drop the IPv6 address. # not 64-bit compatible so let us drop the IPv6 address.
return False return False
return checkIPv6Address(host, hostStandardFormat, private) return checkIPv6Address(host, hostStandardFormat, private)
def checkIPv4Address(host, hostStandardFormat, private=False): def checkIPv4Address(host, hostStandardFormat, private=False):
"""Returns hostStandardFormat if it is an IPv4 address, otherwise returns False""" """
Returns hostStandardFormat if it is an IPv4 address,
otherwise returns False
"""
if host[0] == '\x7F': # 127/8 if host[0] == '\x7F': # 127/8
if not private: if not private:
logger.debug('Ignoring IP address in loopback range: %s', hostStandardFormat) logger.debug(
'Ignoring IP address in loopback range: %s',
hostStandardFormat)
return hostStandardFormat if private else False return hostStandardFormat if private else False
if host[0] == '\x0A': # 10/8 if host[0] == '\x0A': # 10/8
if not private: if not private:
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat) logger.debug(
'Ignoring IP address in private range: %s', hostStandardFormat)
return hostStandardFormat if private else False return hostStandardFormat if private else False
if host[0:2] == '\xC0\xA8': # 192.168/16 if host[0:2] == '\xC0\xA8': # 192.168/16
if not private: if not private:
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat) logger.debug(
'Ignoring IP address in private range: %s', hostStandardFormat)
return hostStandardFormat if private else False return hostStandardFormat if private else False
if host[0:2] >= '\xAC\x10' and host[0:2] < '\xAC\x20': # 172.16/12 if host[0:2] >= '\xAC\x10' and host[0:2] < '\xAC\x20': # 172.16/12
if not private: if not private:
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat) logger.debug(
'Ignoring IP address in private range: %s', hostStandardFormat)
return hostStandardFormat if private else False return hostStandardFormat if private else False
return False if private else hostStandardFormat return False if private else hostStandardFormat
def checkIPv6Address(host, hostStandardFormat, private=False): def checkIPv6Address(host, hostStandardFormat, private=False):
"""Returns hostStandardFormat if it is an IPv6 address, otherwise returns False""" """
Returns hostStandardFormat if it is an IPv6 address,
otherwise returns False
"""
if host == ('\x00' * 15) + '\x01': if host == ('\x00' * 15) + '\x01':
if not private: if not private:
logger.debug('Ignoring loopback address: %s', hostStandardFormat) logger.debug('Ignoring loopback address: %s', hostStandardFormat)
@ -189,7 +212,8 @@ def checkIPv6Address(host, hostStandardFormat, private=False):
return hostStandardFormat if private else False return hostStandardFormat if private else False
if (ord(host[0]) & 0xfe) == 0xfc: if (ord(host[0]) & 0xfe) == 0xfc:
if not private: if not private:
logger.debug('Ignoring unique local address: %s', hostStandardFormat) logger.debug(
'Ignoring unique local address: %s', hostStandardFormat)
return hostStandardFormat if private else False return hostStandardFormat if private else False
return False if private else hostStandardFormat return False if private else hostStandardFormat
@ -210,31 +234,29 @@ def haveSSL(server=False):
def checkSocksIP(host): def checkSocksIP(host):
"""Predicate to check if we're using a SOCKS proxy""" """Predicate to check if we're using a SOCKS proxy"""
sockshostname = BMConfigParser().safeGet(
'bitmessagesettings', 'sockshostname')
try: try:
if state.socksIP is None or not state.socksIP: if not state.socksIP:
state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname")) state.socksIP = socket.gethostbyname(sockshostname)
# uninitialised except NameError: # uninitialised
except NameError: state.socksIP = socket.gethostbyname(sockshostname)
state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname")) except (TypeError, socket.gaierror): # None, resolving failure
# resolving failure state.socksIP = sockshostname
except socket.gaierror:
state.socksIP = BMConfigParser().get("bitmessagesettings", "sockshostname")
return state.socksIP == host return state.socksIP == host
def isProofOfWorkSufficient(data, def isProofOfWorkSufficient(
nonceTrialsPerByte=0, data, nonceTrialsPerByte=0, payloadLengthExtraBytes=0, recvTime=0):
payloadLengthExtraBytes=0,
recvTime=0):
""" """
Validate an object's Proof of Work using method described in: Validate an object's Proof of Work using method described
https://bitmessage.org/wiki/Proof_of_work `here <https://bitmessage.org/wiki/Proof_of_work>`_
Arguments: Arguments:
int nonceTrialsPerByte (default: from default.py) int nonceTrialsPerByte (default: from `.defaults`)
int payloadLengthExtraBytes (default: from default.py) int payloadLengthExtraBytes (default: from `.defaults`)
float recvTime (optional) UNIX epoch time when object was float recvTime (optional) UNIX epoch time when object was
received from the network (default: current system time) received from the network (default: current system time)
Returns: Returns:
True if PoW valid and sufficient, False in all other cases True if PoW valid and sufficient, False in all other cases
""" """
@ -246,18 +268,20 @@ def isProofOfWorkSufficient(data,
TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time())) TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time()))
if TTL < 300: if TTL < 300:
TTL = 300 TTL = 300
POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(data[ POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(
:8] + hashlib.sha512(data[8:]).digest()).digest()).digest()[0:8]) data[:8] + hashlib.sha512(data[8:]).digest()
return POW <= 2 ** 64 / (nonceTrialsPerByte * ).digest()).digest()[0:8])
(len(data) + payloadLengthExtraBytes + return POW <= 2 ** 64 / (
((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16)))) nonceTrialsPerByte * (
len(data) + payloadLengthExtraBytes +
((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16))))
# Packet creation # Packet creation
def CreatePacket(command, payload=''): def CreatePacket(command, payload=''):
"""Construct and return a number of bytes from a payload""" """Construct and return a packet"""
payload_length = len(payload) payload_length = len(payload)
checksum = hashlib.sha512(payload).digest()[0:4] checksum = hashlib.sha512(payload).digest()[0:4]
@ -267,8 +291,13 @@ def CreatePacket(command, payload=''):
return bytes(b) return bytes(b)
def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=False, nodeid=None): def assembleVersionMessage(
"""Construct the payload of a version message, return the resultng bytes of running CreatePacket() on it""" remoteHost, remotePort, participatingStreams, server=False, nodeid=None
):
"""
Construct the payload of a version message,
return the resulting bytes of running `CreatePacket` on it
"""
payload = '' payload = ''
payload += pack('>L', 3) # protocol version. payload += pack('>L', 3) # protocol version.
# bitflags of the services I offer. # bitflags of the services I offer.
@ -280,9 +309,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
) )
payload += pack('>q', int(time.time())) payload += pack('>q', int(time.time()))
payload += pack( # boolservices of remote connection; ignored by the remote host.
'>q', 1) # boolservices of remote connection; ignored by the remote host. payload += pack('>q', 1)
if checkSocksIP(remoteHost) and server: # prevent leaking of tor outbound IP if checkSocksIP(remoteHost) and server:
# prevent leaking of tor outbound IP
payload += encodeHost('127.0.0.1') payload += encodeHost('127.0.0.1')
payload += pack('>H', 8444) payload += pack('>H', 8444)
else: else:
@ -301,21 +331,25 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
(NODE_SSL if haveSSL(server) else 0) | (NODE_SSL if haveSSL(server) else 0) |
(NODE_DANDELION if state.dandelion else 0) (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. # = 127.0.0.1. This will be ignored by the remote host.
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack('>L', 2130706433) # The actual remote connected IP will be used.
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack(
'>L', 2130706433)
# we have a separate extPort and incoming over clearnet # we have a separate extPort and incoming over clearnet
# or outgoing through clearnet # or outgoing through clearnet
extport = BMConfigParser().safeGetInt('bitmessagesettings', 'extport') extport = BMConfigParser().safeGetInt('bitmessagesettings', 'extport')
if ( if (
extport and ((server and not checkSocksIP(remoteHost)) or ( extport and ((server and not checkSocksIP(remoteHost)) or (
BMConfigParser().get('bitmessagesettings', 'socksproxytype') == BMConfigParser().get('bitmessagesettings', 'socksproxytype')
'none' and not server)) == 'none' and not server))
): ):
payload += pack('>H', extport) payload += pack('>H', extport)
elif checkSocksIP(remoteHost) and server: # incoming connection over Tor elif checkSocksIP(remoteHost) and server: # incoming connection over Tor
payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'onionport')) payload += pack(
'>H', BMConfigParser().getint('bitmessagesettings', 'onionport'))
else: # no extport and not incoming over Tor else: # no extport and not incoming over Tor
payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'port')) payload += pack(
'>H', BMConfigParser().getint('bitmessagesettings', 'port'))
if nodeid is not None: if nodeid is not None:
payload += nodeid[0:8] payload += nodeid[0:8]
@ -339,7 +373,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''): def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''):
"""Construct the payload of an error message, return the resultng bytes of running CreatePacket() on it""" """
Construct the payload of an error message,
return the resulting bytes of running `CreatePacket` on it
"""
payload = encodeVarint(fatal) payload = encodeVarint(fatal)
payload += encodeVarint(banTime) payload += encodeVarint(banTime)
payload += encodeVarint(len(inventoryVector)) payload += encodeVarint(len(inventoryVector))
@ -476,7 +513,7 @@ def decryptAndCheckPubkeyPayload(data, address):
except Exception: except Exception:
logger.critical( logger.critical(
'Pubkey decryption was UNsuccessful because of' 'Pubkey decryption was UNsuccessful because of'
' an unhandled exception! This is definitely a bug! \n%s', ' an unhandled exception! This is definitely a bug!',
traceback.format_exc() exc_info=True
) )
return 'failed' return 'failed'

View File

@ -80,7 +80,9 @@ def isAddressInMySubscriptionsList(address):
def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address):
"""Am I subscribed to this address, is it in my addressbook or whitelist?""" """
Am I subscribed to this address, is it in my addressbook or whitelist?
"""
if isAddressInMyAddressBook(address): if isAddressInMyAddressBook(address):
return True return True
@ -100,8 +102,12 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address):
return False return False
def decodeWalletImportFormat(WIFstring): # pylint: disable=inconsistent-return-statements def decodeWalletImportFormat(WIFstring):
"""Convert private key from base58 that's used in the config file to 8-bit binary string""" # pylint: disable=inconsistent-return-statements
"""
Convert private key from base58 that's used in the config file to
8-bit binary string
"""
fullString = arithmetic.changebase(WIFstring, 58, 256) fullString = arithmetic.changebase(WIFstring, 58, 256)
privkey = fullString[:-4] privkey = fullString[:-4]
if fullString[-4:] != \ if fullString[-4:] != \
@ -126,14 +132,15 @@ def decodeWalletImportFormat(WIFstring): # pylint: disable=inconsistent-retur
def reloadMyAddressHashes(): def reloadMyAddressHashes():
"""Reinitialise runtime data (e.g. encryption objects, address hashes) from the config file""" """Reload keys for user's addresses from the config file"""
logger.debug('reloading keys from keys.dat file') logger.debug('reloading keys from keys.dat file')
myECCryptorObjects.clear() myECCryptorObjects.clear()
myAddressesByHash.clear() myAddressesByHash.clear()
myAddressesByTag.clear() myAddressesByTag.clear()
# myPrivateKeys.clear() # myPrivateKeys.clear()
keyfileSecure = checkSensitiveFilePermissions(state.appdata + 'keys.dat') keyfileSecure = checkSensitiveFilePermissions(os.path.join(
state.appdata, 'keys.dat'))
hasEnabledKeys = False hasEnabledKeys = False
for addressInKeysFile in BMConfigParser().addresses(): for addressInKeysFile in BMConfigParser().addresses():
isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled') isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled')
@ -162,11 +169,15 @@ def reloadMyAddressHashes():
) )
if not keyfileSecure: if not keyfileSecure:
fixSensitiveFilePermissions(state.appdata + 'keys.dat', hasEnabledKeys) fixSensitiveFilePermissions(os.path.join(
state.appdata, 'keys.dat'), hasEnabledKeys)
def reloadBroadcastSendersForWhichImWatching(): def reloadBroadcastSendersForWhichImWatching():
"""Reinitialise runtime data for the broadcasts I'm subscribed to from the config file""" """
Reinitialize runtime data for the broadcasts I'm subscribed to
from the config file
"""
broadcastSendersForWhichImWatching.clear() broadcastSendersForWhichImWatching.clear()
MyECSubscriptionCryptorObjects.clear() MyECSubscriptionCryptorObjects.clear()
queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1') queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1')

View File

@ -16,7 +16,9 @@ from queues import (
def doCleanShutdown(): def doCleanShutdown():
"""Used to tell proof of work worker threads and the objectProcessorThread to exit.""" """
Used to tell all the treads to finish work and exit.
"""
state.shutdown = 1 state.shutdown = 1
objectProcessorQueue.put(('checkShutdownVariable', 'no data')) objectProcessorQueue.put(('checkShutdownVariable', 'no data'))
@ -52,9 +54,11 @@ def doCleanShutdown():
time.sleep(.25) time.sleep(.25)
for thread in threading.enumerate(): for thread in threading.enumerate():
if (thread is not threading.currentThread() and if (
isinstance(thread, StoppableThread) and thread is not threading.currentThread()
thread.name != 'SQL'): and isinstance(thread, StoppableThread)
and thread.name != 'SQL'
):
logger.debug("Waiting for thread %s", thread.name) logger.debug("Waiting for thread %s", thread.name)
thread.join() thread.join()

View File

@ -16,8 +16,8 @@ appdata = ''
shutdown = 0 shutdown = 0
""" """
Set to 1 by the doCleanShutdown function. Set to 1 by the `.shutdown.doCleanShutdown` function.
Used to tell the proof of work worker threads to exit. Used to tell the threads to exit.
""" """
# Component control flags - set on startup, do not change during runtime # Component control flags - set on startup, do not change during runtime
@ -25,7 +25,7 @@ shutdown = 0
enableNetwork = True enableNetwork = True
"""enable network threads""" """enable network threads"""
enableObjProc = True enableObjProc = True
"""enable object processing threads""" """enable object processing thread"""
enableAPI = True enableAPI = True
"""enable API (if configured)""" """enable API (if configured)"""
enableGUI = True enableGUI = True
@ -35,7 +35,7 @@ enableSTDIO = False
curses = False curses = False
sqlReady = False sqlReady = False
"""set to true by sqlTread when ready for processing""" """set to true by `.threads.sqlThread` when ready for processing"""
maximumNumberOfHalfOpenConnections = 0 maximumNumberOfHalfOpenConnections = 0