#! python import sys import os import pyelliptic.openssl #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 if not hasattr(sys, 'hexversion') or sys.hexversion < 0x20300F0: sys.stdout.write('Python version: ' + sys.version) sys.stdout.write('PyBitmessage requires Python 2.7.3 or greater (but not Python 3)') sys.exit() #We can now use logging so set up a simple configuration 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) #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. def check_hashlib(): if sys.hexversion < 0x020500F0: logger.error('The hashlib module is not included in this version of Python.') return False import hashlib if '_hashlib' not in hashlib.__dict__: logger.error('The RIPEMD-160 hash algorithm is not available. The hashlib module is not linked against OpenSSL.') return False try: hashlib.new('ripemd160') except ValueError: logger.error('The RIPEMD-160 hash algorithm is not available. The hashlib module utilizes an OpenSSL library with RIPEMD disabled.') return False return True def check_sqlite(): if sys.hexversion < 0x020500F0: logger.error('The sqlite3 module is not included in this version of Python.') if sys.platform.startswith('freebsd'): logger.error('On FreeBSD, try running "pkg install py27-sqlite3" as root.') 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) #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] conn = None try: try: conn = sqlite3.connect(':memory:') if sqlite_version_number >= 3006018: sqlite_source_id = conn.execute('SELECT sqlite_source_id();').fetchone()[0] logger.info('SQLite Library Source ID: ' + sqlite_source_id) if sqlite_version_number >= 3006023: 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. if sqlite_version_number < 3000008: logger.error('This version of SQLite is too old. PyBitmessage requires SQLite 3.0.8 or later') return False return True except sqlite3.Error: logger.exception('An exception occured while checking sqlite.') return False finally: if conn: conn.close() def check_openssl(): try: import ctypes except ImportError: logger.error('Unable to check OpenSSL. The ctypes module is not available.') return False #We need to emulate the way PyElliptic searches for OpenSSL. if sys.platform == 'win32': paths = ['libeay32.dll'] if getattr(sys, 'frozen', False): import os.path paths.insert(0, os.path.join(sys._MEIPASS, 'libeay32.dll')) else: paths = ['libcrypto.so'] 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) except: pass openssl_version = None openssl_hexversion = None openssl_cflags = None 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) openssl_version, openssl_hexversion, openssl_cflags = pyelliptic.openssl.get_version(library) if not openssl_version: logger.error('Cannot determine version of this OpenSSL library.') return False logger.info('OpenSSL Version: ' + openssl_version) logger.info('OpenSSL Compile Options: ' + openssl_cflags) #PyElliptic uses EVP_CIPHER_CTX_new and EVP_CIPHER_CTX_free which were #introduced in 0.9.8b. if openssl_hexversion < 0x90802F: 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.') return False matches = cflags_regex.findall(openssl_cflags) if len(matches) > 0: 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.') return False return True return False #TODO: The minimum versions of pythondialog and dialog need to be determined def check_curses(): if sys.hexversion < 0x20600F0: logger.error('The curses interface requires the pythondialog package and the dialog utility.') return False try: import curses except ImportError: logger.error('The curses interface can not be used. The curses module is not available.') return False logger.info('curses Module Version: ' + curses.version) try: import dialog except ImportError: logger.error('The curses interface can not be used. The pythondialog package is not available.') return False logger.info('pythondialog Package Version: ' + dialog.__version__) dialog_util_version = dialog.Dialog().cached_backend_version #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. logger.info('dialog Utility Version' + unicode(dialog_util_version)) return True def check_pyqt(): try: import PyQt4.QtCore except ImportError: logger.error('The PyQt4 package is not available. PyBitmessage requires PyQt 4.8 or later and Qt 4.7 or later.') if sys.platform.startswith('openbsd'): logger.error('On OpenBSD, try running "pkg_add py-qt4" as root.') elif sys.platform.startswith('freebsd'): logger.error('On FreeBSD, try running "pkg install py27-qt4" as root.') 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(): logger.error('On Fedora, try running "dnf install PyQt4" as root.') elif "opensuse" in line.lower(): logger.error('On openSUSE, try running "zypper install python-qt" as root.') elif "ubuntu" in line.lower(): logger.error('On Ubuntu, try running "apt-get install python-qt4" as root.') elif "debian" in line.lower(): logger.error('On Debian, try running "apt-get install python-qt4" as root.') else: logger.error('If your package manager does not have this package, try running "pip install PyQt4".') 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: logger.error('This version of PyQt is too old. PyBitmessage requries PyQt 4.8 or later.') passed = False if PyQt4.QtCore.QT_VERSION < 0x40700: logger.error('This version of Qt is too old. PyBitmessage requries Qt 4.7 or later.') passed = False return passed def check_msgpack(): try: import msgpack except ImportError: logger.error('The msgpack package is not available. PyBitmessage requires msgpack.') if sys.platform.startswith('openbsd'): logger.error('On OpenBSD, try running "pkg_add py-msgpack" as root.') elif sys.platform.startswith('freebsd'): logger.error('On FreeBSD, try running "pkg install py27-msgpack-python" as root.') 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(): logger.error('On Fedora, try running "dnf install python2-msgpack" as root.') elif "opensuse" in line.lower(): logger.error('On openSUSE, try running "zypper install python-msgpack-python" as root.') elif "ubuntu" in line.lower(): logger.error('On Ubuntu, try running "apt-get install python-msgpack" as root.') elif "debian" in line.lower(): logger.error('On Debian, try running "apt-get install python-msgpack" as root.') else: logger.error('If your package manager does not have this package, try running "pip install msgpack-python".') return False return True def check_dependencies(verbose = False, optional = False): if verbose: logger.setLevel(logging.INFO) has_all_dependencies = True #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. logger.info('Python version: %s', sys.version) if sys.hexversion < 0x20703F0: logger.error('PyBitmessage requires Python 2.7.3 or greater (but not Python 3+)') has_all_dependencies = False if sys.hexversion >= 0x3000000: logger.error('PyBitmessage does not support Python 3+. Python 2.7.3 or greater is required.') has_all_dependencies = False check_functions = [check_hashlib, check_sqlite, check_openssl, check_msgpack] if optional: check_functions.extend([check_pyqt, check_curses]) #Unexpected exceptions are handled here for check in check_functions: try: has_all_dependencies &= check() except: logger.exception(check.__name__ + ' failed unexpectedly.') has_all_dependencies = False if not has_all_dependencies: logger.critical('PyBitmessage cannot start. One or more dependencies are unavailable.') sys.exit() if __name__ == '__main__': check_dependencies(True, True)