From 23b55652b9c6d58730fe570c2501eec9aa771248 Mon Sep 17 00:00:00 2001 From: bmng-dev Date: Wed, 6 Aug 2014 06:40:41 +0000 Subject: [PATCH 1/2] New depends module to check dependencies. Moved version check to new depends module. --- src/bitmessagemain.py | 36 ++----- src/depends.py | 219 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 27 deletions(-) create mode 100755 src/depends.py diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index dc0a13f4..732c16c5 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -9,25 +9,8 @@ # The software version variable is now held in shared.py - -import sys -#Version check -#Older versions of Python don't support the print function while Python 3 doesn't -#like the print statement, so we use sys.stdout for the version check. After this -#check we can then use the print function in the remainder of this file. Currently -#in order to use logging, a lot of unnecessary code needs to be executed which could -#potentially render this version check useless. So logging won't be used here until -#there is a more efficient way to configure logging -if sys.hexversion >= 0x3000000: - msg = "PyBitmessage does not support Python 3. Python 2.7.3 or later is required. Your version: %s" % sys.version - #logger.critical(msg) - sys.stdout.write(msg) - sys.exit(0) -if sys.hexversion < 0x20703F0: - msg = "You should use Python 2.7.3 or greater (but not Python 3). Your version: %s" % sys.version - #logger.critical(msg) - sys.stdout.write(msg) - sys.exit(0) +import depends +depends.check_dependencies() import signal # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. # The next 3 are used for the API @@ -36,6 +19,7 @@ import os import socket import ctypes from struct import pack +import sys from SimpleXMLRPCServer import SimpleXMLRPCServer from api import MySimpleXMLRPCRequestHandler @@ -227,20 +211,18 @@ class Main: if daemon == False and shared.safeConfigGetBoolean('bitmessagesettings', 'daemon') == False: if curses == False: - try: - from PyQt4 import QtCore, QtGui - except Exception as err: + if not depends.check_pyqt(): print('PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download PyQt from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\'. If you want to run in daemon mode, see https://bitmessage.org/wiki/Daemon') - print('Error message:', err) print('You can also run PyBitmessage with the new curses interface by providing \'-c\' as a commandline argument.') - os._exit(0) + sys.exit() import bitmessageqt bitmessageqt.run() else: - print('Running with curses') - import bitmessagecurses - bitmessagecurses.runwrapper() + if depends.check_curses(): + print('Running with curses') + import bitmessagecurses + bitmessagecurses.runwrapper() else: shared.config.remove_option('bitmessagesettings', 'dontconnect') diff --git a/src/depends.py b/src/depends.py new file mode 100755 index 00000000..c61f0bc7 --- /dev/null +++ b/src/depends.py @@ -0,0 +1,219 @@ +#! python + +import sys + +#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.5 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.') + 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.append(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 + + SSLEAY_VERSION = 0 + SSLEAY_CFLAGS = 2 + + 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) + try: + library.SSLeay.restype = ctypes.c_long + library.SSLeay_version.restype = ctypes.c_char_p + library.SSLeay_version.argtypes = [ctypes.c_int] + except AttributeError: + logger.error('Cannot determine version of this OpenSSL library.') + return False + logger.info('OpenSSL Version: ' + library.SSLeay_version(SSLEAY_VERSION)) + compile_options = library.SSLeay_version(SSLEAY_CFLAGS) + logger.info('OpenSSL Compile Options: ' + compile_options) + openssl_hexversion = library.SSLeay() + #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(compile_options) + 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 Python 2.6 or later, 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.') + 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_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.5 or greater is required.') + has_all_dependencies = False + + check_functions = [check_hashlib, check_sqlite, check_openssl] + 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) + From 29e75ac7c60a7bed918f4753c7233cac46ed01b9 Mon Sep 17 00:00:00 2001 From: bmng-dev Date: Sat, 16 Aug 2014 04:34:06 +0000 Subject: [PATCH 2/2] Update the required Python version to 2.7.3 --- src/depends.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/depends.py b/src/depends.py index c61f0bc7..553a5031 100755 --- a/src/depends.py +++ b/src/depends.py @@ -6,7 +6,7 @@ import sys #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.5 or greater (but not Python 3)') + 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 @@ -144,7 +144,7 @@ def check_openssl(): #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 Python 2.6 or later, the pythondialog package and the dialog utility.') + logger.error('The curses interface requires the pythondialog package and the dialog utility.') return False try: import curses @@ -195,7 +195,7 @@ def check_dependencies(verbose = False, optional = False): 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.5 or greater is required.') + 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]