2014-08-06 08:40:41 +02:00
|
|
|
#! python
|
|
|
|
|
|
|
|
import sys
|
2017-02-14 01:33:16 +01:00
|
|
|
import os
|
2017-01-13 12:02:34 +01:00
|
|
|
import pyelliptic.openssl
|
2014-08-06 08:40:41 +02:00
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
# Only really old versions of Python don't have sys.hexversion. We don't
|
|
|
|
# support them. The logging module was introduced in Python 2.3
|
2014-08-06 08:40:41 +02:00
|
|
|
if not hasattr(sys, 'hexversion') or sys.hexversion < 0x20300F0:
|
|
|
|
sys.stdout.write('Python version: ' + sys.version)
|
2018-03-17 13:09:40 +01:00
|
|
|
sys.stdout.write(
|
|
|
|
'PyBitmessage requires Python 2.7.3\
|
|
|
|
or greater (but not Python 3)')
|
2014-08-06 08:40:41 +02:00
|
|
|
sys.exit()
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
# We can now use logging so set up a simple configuration
|
2014-08-06 08:40:41 +02:00
|
|
|
import logging
|
|
|
|
formatter = logging.Formatter(
|
|
|
|
'%(levelname)s: %(message)s'
|
|
|
|
)
|
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
|
|
handler.setFormatter(formatter)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
logger.addHandler(handler)
|
|
|
|
logger.setLevel(logging.ERROR)
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
# We need to check hashlib for RIPEMD-160, as it won't be
|
|
|
|
# available if OpenSSL # is not linked against or the
|
|
|
|
# linked OpenSSL has RIPEMD disabled.
|
|
|
|
|
|
|
|
|
2014-08-06 08:40:41 +02:00
|
|
|
def check_hashlib():
|
|
|
|
if sys.hexversion < 0x020500F0:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'The hashlib module is not included\
|
|
|
|
in this version of Python.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
import hashlib
|
|
|
|
if '_hashlib' not in hashlib.__dict__:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'The RIPEMD-160 hash algorithm is not available\
|
|
|
|
. The hashlib module is not linked against OpenSSL.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
try:
|
|
|
|
hashlib.new('ripemd160')
|
|
|
|
except ValueError:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'The RIPEMD-160 hash algorithm is not available\
|
|
|
|
. The hashlib module utilizes an OpenSSL\
|
|
|
|
library with RIPEMD disabled.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
|
2014-08-06 08:40:41 +02:00
|
|
|
def check_sqlite():
|
|
|
|
if sys.hexversion < 0x020500F0:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'The sqlite3 module is not included in this\
|
|
|
|
version of Python.')
|
2017-02-14 01:33:16 +01:00
|
|
|
if sys.platform.startswith('freebsd'):
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On FreeBSD, try running\
|
|
|
|
"pkg install py27-sqlite3" as root.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
try:
|
|
|
|
import sqlite3
|
|
|
|
except ImportError:
|
|
|
|
logger.error('The sqlite3 module is not available')
|
|
|
|
return False
|
|
|
|
|
|
|
|
logger.info('sqlite3 Module Version: ' + sqlite3.version)
|
|
|
|
logger.info('SQLite Library Version: ' + sqlite3.sqlite_version)
|
2018-03-17 13:09:40 +01:00
|
|
|
# sqlite_version_number formula: https://sqlite.org/c3ref/c_source_id.html
|
|
|
|
sqlite_version_number = sqlite3.sqlite_version_info[0] * 1000000 + \
|
|
|
|
sqlite3.sqlite_version_info[1] * 1000 + sqlite3.sqlite_version_info[2]
|
2014-08-06 08:40:41 +02:00
|
|
|
|
|
|
|
conn = None
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
conn = sqlite3.connect(':memory:')
|
|
|
|
if sqlite_version_number >= 3006018:
|
2018-03-17 13:09:40 +01:00
|
|
|
sqlite_source_id = conn.execute(
|
|
|
|
'SELECT sqlite_source_id();').fetchone()[0]
|
2014-08-06 08:40:41 +02:00
|
|
|
logger.info('SQLite Library Source ID: ' + sqlite_source_id)
|
|
|
|
if sqlite_version_number >= 3006023:
|
2018-03-17 13:09:40 +01:00
|
|
|
compile_options = ', '.join(
|
|
|
|
map(lambda row: row[0],
|
|
|
|
conn.execute('PRAGMA compile_options;')))
|
|
|
|
logger.info(
|
|
|
|
'SQLite Library Compile Options: ' +
|
|
|
|
compile_options)
|
|
|
|
# There is no specific version requirement as yet, so we just use
|
|
|
|
# the first version that was included with Python.
|
2014-08-06 08:40:41 +02:00
|
|
|
if sqlite_version_number < 3000008:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'This version of SQLite is too old\
|
|
|
|
. PyBitmessage requires SQLite 3.0.8 or later')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
except sqlite3.Error:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.exception('An exception occured while \
|
|
|
|
checking sqlite.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
finally:
|
|
|
|
if conn:
|
|
|
|
conn.close()
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
|
2014-08-06 08:40:41 +02:00
|
|
|
def check_openssl():
|
|
|
|
try:
|
|
|
|
import ctypes
|
|
|
|
except ImportError:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'Unable to check OpenSSL. The ctypes module\
|
|
|
|
is not available.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
# We need to emulate the way PyElliptic searches for OpenSSL.
|
2014-08-06 08:40:41 +02:00
|
|
|
if sys.platform == 'win32':
|
|
|
|
paths = ['libeay32.dll']
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
import os.path
|
2016-01-20 22:31:15 +01:00
|
|
|
paths.insert(0, os.path.join(sys._MEIPASS, 'libeay32.dll'))
|
2014-08-06 08:40:41 +02:00
|
|
|
else:
|
2018-03-09 15:41:20 +01:00
|
|
|
paths = ['libcrypto.so', 'libcrypto.so.1.0.0']
|
2014-08-06 08:40:41 +02:00
|
|
|
if sys.platform == 'darwin':
|
|
|
|
paths.extend([
|
|
|
|
'libcrypto.dylib',
|
|
|
|
'/usr/local/opt/openssl/lib/libcrypto.dylib',
|
|
|
|
'./../Frameworks/libcrypto.dylib'
|
|
|
|
])
|
|
|
|
import re
|
|
|
|
if re.match(r'linux|darwin|freebsd', sys.platform):
|
|
|
|
try:
|
|
|
|
import ctypes.util
|
|
|
|
path = ctypes.util.find_library('ssl')
|
|
|
|
if path not in paths:
|
|
|
|
paths.append(path)
|
2018-03-17 13:09:40 +01:00
|
|
|
except BaseException:
|
2014-08-06 08:40:41 +02:00
|
|
|
pass
|
|
|
|
|
2017-01-13 12:02:34 +01:00
|
|
|
openssl_version = None
|
|
|
|
openssl_hexversion = None
|
|
|
|
openssl_cflags = None
|
2014-08-06 08:40:41 +02:00
|
|
|
|
|
|
|
cflags_regex = re.compile(r'(?:OPENSSL_NO_)(AES|EC|ECDH|ECDSA)(?!\w)')
|
|
|
|
|
|
|
|
for path in paths:
|
|
|
|
logger.info('Checking OpenSSL at ' + path)
|
|
|
|
try:
|
|
|
|
library = ctypes.CDLL(path)
|
|
|
|
except OSError:
|
|
|
|
continue
|
|
|
|
logger.info('OpenSSL Name: ' + library._name)
|
2018-03-17 13:09:40 +01:00
|
|
|
openssl_version, openssl_hexversion, openssl_cflags = pyelliptic.openssl.get_version(
|
|
|
|
library)
|
2017-01-13 12:02:34 +01:00
|
|
|
if not openssl_version:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error('Cannot determine version\
|
|
|
|
of this OpenSSL library.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
2017-01-13 12:02:34 +01:00
|
|
|
logger.info('OpenSSL Version: ' + openssl_version)
|
|
|
|
logger.info('OpenSSL Compile Options: ' + openssl_cflags)
|
2018-03-17 13:09:40 +01:00
|
|
|
# PyElliptic uses EVP_CIPHER_CTX_new and EVP_CIPHER_CTX_free which were
|
|
|
|
# introduced in 0.9.8b.
|
2014-08-06 08:40:41 +02:00
|
|
|
if openssl_hexversion < 0x90802F:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'This OpenSSL library is too old. \
|
|
|
|
PyBitmessage requires OpenSSL 0.9.8b\
|
|
|
|
or later with AES, Elliptic Curves (EC)\
|
|
|
|
, ECDH, and ECDSA enabled.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
2017-01-13 12:02:34 +01:00
|
|
|
matches = cflags_regex.findall(openssl_cflags)
|
2014-08-06 08:40:41 +02:00
|
|
|
if len(matches) > 0:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'This OpenSSL library is missing the\
|
|
|
|
following required features: ' +
|
|
|
|
', '.join(matches) +
|
|
|
|
'. PyBitmessage requires OpenSSL 0.9.8b\
|
|
|
|
or later with AES, Elliptic Curves (EC)\
|
|
|
|
, ECDH, and ECDSA enabled.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
# TODO: The minimum versions of pythondialog and dialog need to be determined
|
|
|
|
|
|
|
|
|
2014-08-06 08:40:41 +02:00
|
|
|
def check_curses():
|
|
|
|
if sys.hexversion < 0x20600F0:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'The curses interface requires the pythondialog\
|
|
|
|
package and the dialog utility.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
try:
|
|
|
|
import curses
|
|
|
|
except ImportError:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'The curses interface can not be used.\
|
|
|
|
The curses module is not available.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
logger.info('curses Module Version: ' + curses.version)
|
|
|
|
try:
|
|
|
|
import dialog
|
|
|
|
except ImportError:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'The curses interface can not be used\
|
|
|
|
. The pythondialog package is not available.')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
logger.info('pythondialog Package Version: ' + dialog.__version__)
|
|
|
|
dialog_util_version = dialog.Dialog().cached_backend_version
|
2018-03-17 13:09:40 +01:00
|
|
|
# The pythondialog author does not like Python2 str, so we have to use
|
|
|
|
# unicode for just the version otherwise we get the repr form which
|
|
|
|
# includes the module and class names along with the actual version.
|
2014-08-06 08:40:41 +02:00
|
|
|
logger.info('dialog Utility Version' + unicode(dialog_util_version))
|
|
|
|
return True
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
|
2014-08-06 08:40:41 +02:00
|
|
|
def check_pyqt():
|
|
|
|
try:
|
|
|
|
import PyQt4.QtCore
|
|
|
|
except ImportError:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'The PyQt4 package is not available. PyBitmessage\
|
|
|
|
requires PyQt 4.8 or later and Qt 4.7 or later.')
|
2017-02-14 01:33:16 +01:00
|
|
|
if sys.platform.startswith('openbsd'):
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error('On OpenBSD, try running\
|
|
|
|
"pkg_add py-qt4" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif sys.platform.startswith('freebsd'):
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On FreeBSD, try running\
|
|
|
|
"pkg install py27-qt4" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif os.path.isfile("/etc/os-release"):
|
|
|
|
with open("/etc/os-release", 'rt') as osRelease:
|
|
|
|
for line in osRelease:
|
|
|
|
if line.startswith("NAME="):
|
|
|
|
if "fedora" in line.lower():
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On Fedora, try running\
|
|
|
|
"dnf install PyQt4" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif "opensuse" in line.lower():
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On openSUSE, try running\
|
|
|
|
"zypper install python-qt" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif "ubuntu" in line.lower():
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On Ubuntu, try running\
|
|
|
|
"apt-get install python-qt4" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif "debian" in line.lower():
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On Debian, try running\
|
|
|
|
"apt-get install python-qt4" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
else:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'If your package manager does not\
|
|
|
|
have this package, try running\
|
|
|
|
"pip install PyQt4".')
|
2014-08-06 08:40:41 +02:00
|
|
|
return False
|
|
|
|
logger.info('PyQt Version: ' + PyQt4.QtCore.PYQT_VERSION_STR)
|
|
|
|
logger.info('Qt Version: ' + PyQt4.QtCore.QT_VERSION_STR)
|
|
|
|
passed = True
|
|
|
|
if PyQt4.QtCore.PYQT_VERSION < 0x40800:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'This version of PyQt is too old. PyBitmessage\
|
|
|
|
requries PyQt 4.8 or later.')
|
2014-08-06 08:40:41 +02:00
|
|
|
passed = False
|
|
|
|
if PyQt4.QtCore.QT_VERSION < 0x40700:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'This version of Qt is too old. PyBitmessage\
|
|
|
|
requries Qt 4.7 or later.')
|
2014-08-06 08:40:41 +02:00
|
|
|
passed = False
|
|
|
|
return passed
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
|
2017-02-14 01:33:16 +01:00
|
|
|
def check_msgpack():
|
|
|
|
try:
|
|
|
|
import msgpack
|
|
|
|
except ImportError:
|
2017-06-12 12:54:44 +02:00
|
|
|
logger.error(
|
|
|
|
'The msgpack package is not available.'
|
|
|
|
'It is highly recommended for messages coding.')
|
2017-02-14 01:33:16 +01:00
|
|
|
if sys.platform.startswith('openbsd'):
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On OpenBSD, try running\
|
|
|
|
"pkg_add py-msgpack" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif sys.platform.startswith('freebsd'):
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On FreeBSD, try running\
|
|
|
|
"pkg install py27-msgpack-python" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif os.path.isfile("/etc/os-release"):
|
|
|
|
with open("/etc/os-release", 'rt') as osRelease:
|
|
|
|
for line in osRelease:
|
|
|
|
if line.startswith("NAME="):
|
|
|
|
if "fedora" in line.lower():
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On Fedora, try running\
|
|
|
|
"dnf install python2-msgpack" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif "opensuse" in line.lower():
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On openSUSE, try running\
|
|
|
|
"zypper install \
|
|
|
|
python-msgpack-python" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif "ubuntu" in line.lower():
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On Ubuntu, try running \
|
|
|
|
"apt-get install python-msgpack" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
elif "debian" in line.lower():
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'On Debian, try running \
|
|
|
|
"apt-get install python-msgpack" as root.')
|
2017-02-14 01:33:16 +01:00
|
|
|
else:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'If your package manager does \
|
|
|
|
not have this package, \
|
|
|
|
try running "pip install msgpack-python".')
|
2017-02-14 01:33:16 +01:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
|
|
|
|
def check_dependencies(verbose=False, optional=False):
|
2014-08-06 08:40:41 +02:00
|
|
|
if verbose:
|
|
|
|
logger.setLevel(logging.INFO)
|
|
|
|
|
|
|
|
has_all_dependencies = True
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
# Python 2.7.3 is the required minimum. Python 3+ is not supported, but it
|
|
|
|
# is still useful to provide information about our other requirements.
|
2014-08-06 08:40:41 +02:00
|
|
|
logger.info('Python version: %s', sys.version)
|
|
|
|
if sys.hexversion < 0x20703F0:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'PyBitmessage requires Python 2.7.3 or \
|
|
|
|
greater (but not Python 3+)')
|
2014-08-06 08:40:41 +02:00
|
|
|
has_all_dependencies = False
|
|
|
|
if sys.hexversion >= 0x3000000:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.error(
|
|
|
|
'PyBitmessage does not support Python 3+.\
|
|
|
|
Python 2.7.3 or greater is required.')
|
2014-08-06 08:40:41 +02:00
|
|
|
has_all_dependencies = False
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
check_functions = [
|
|
|
|
check_hashlib,
|
|
|
|
check_sqlite,
|
|
|
|
check_openssl,
|
|
|
|
check_msgpack]
|
2014-08-06 08:40:41 +02:00
|
|
|
if optional:
|
|
|
|
check_functions.extend([check_pyqt, check_curses])
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
# Unexpected exceptions are handled here
|
2014-08-06 08:40:41 +02:00
|
|
|
for check in check_functions:
|
|
|
|
try:
|
|
|
|
has_all_dependencies &= check()
|
2018-03-17 13:09:40 +01:00
|
|
|
except BaseException:
|
2014-08-06 08:40:41 +02:00
|
|
|
logger.exception(check.__name__ + ' failed unexpectedly.')
|
|
|
|
has_all_dependencies = False
|
2018-03-17 13:09:40 +01:00
|
|
|
|
2014-08-06 08:40:41 +02:00
|
|
|
if not has_all_dependencies:
|
2018-03-17 13:09:40 +01:00
|
|
|
logger.critical(
|
|
|
|
'PyBitmessage cannot start. One or \
|
|
|
|
more dependencies are unavailable.')
|
2014-08-06 08:40:41 +02:00
|
|
|
sys.exit()
|
|
|
|
|
2018-03-17 13:09:40 +01:00
|
|
|
|
2014-08-06 08:40:41 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
check_dependencies(True, True)
|