2014-08-06 08:40:41 +02:00
#! python
2018-05-26 14:43:21 +02:00
import subprocess
2014-08-06 08:40:41 +02:00
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-21 11:17:55 +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 )
2014-08-16 06:34:06 +02: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-21 11:17:55 +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-21 11:17:55 +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 ( ) :
2018-03-21 11:17:55 +01:00
""" Do hashlib check.
The hashlib module check with version as if it included or not
in The Python Standard library , its a module containing an
interface to the most popular hashing algorithms . hashlib
implements some of the algorithms , however if OpenSSL
installed , hashlib is able to use this algorithms as well .
"""
2014-08-06 08:40:41 +02:00
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
2018-03-22 10:57:37 +01:00
2014-08-06 08:40:41 +02:00
def check_sqlite ( ) :
2018-03-22 10:57:37 +01:00
""" Do sqlite check.
2018-03-21 11:17:55 +01:00
Simply check sqlite3 module if exist or not with hexversion
support in python version for specifieed platform .
"""
2014-08-06 08:40:41 +02:00
if sys . hexversion < 0x020500F0 :
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 ' ) :
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 )
#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 ( ) :
2018-03-21 11:17:55 +01:00
""" Do openssl dependency check.
Here we are checking for openssl with its all dependent libraries
and version checking .
"""
2014-08-06 08:40:41 +02:00
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
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 )
except :
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-05-26 14:43:21 +02:00
try :
openssl_version , openssl_hexversion , openssl_cflags = pyelliptic . openssl . get_version ( library )
except AttributeError : # sphinx chokes
return True
2017-01-13 12:02:34 +01:00
if not openssl_version :
2014-08-06 08:40:41 +02:00
logger . error ( ' Cannot determine version of this OpenSSL library. ' )
return False
2017-01-13 12:02:34 +01:00
logger . info ( ' OpenSSL Version: ' + openssl_version )
logger . info ( ' OpenSSL Compile Options: ' + openssl_cflags )
2014-08-06 08:40:41 +02:00
#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
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 :
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 ( ) :
2018-03-21 11:17:55 +01:00
""" Do curses dependency check.
2018-05-26 14:43:21 +02:00
Here we are checking for curses if available or not with check
2018-03-21 11:17:55 +01:00
as interface requires the pythondialog \ package and the dialog
utility .
"""
2014-08-06 08:40:41 +02:00
if sys . hexversion < 0x20600F0 :
2014-08-16 06:34:06 +02: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 :
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
2018-05-26 14:43:21 +02:00
try :
subprocess . check_call ( ' which dialog ' )
except subprocess . CalledProcessError :
logger . error ( ' Curses requires the `dialog` command to be installed as well as the python library. ' )
return False
2014-08-06 08:40:41 +02:00
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 ( ) :
2018-03-21 11:17:55 +01:00
""" Do pyqt dependency check.
Here we are checking for PyQt4 with its version , as for it require
PyQt 4.7 or later .
"""
2014-08-06 08:40:41 +02:00
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. ' )
2017-02-14 01:33:16 +01:00
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 " . ' )
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 :
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
2017-02-14 01:33:16 +01:00
def check_msgpack ( ) :
2018-03-21 11:17:55 +01:00
""" Do sgpack module check.
2018-05-26 14:43:21 +02:00
simply checking if msgpack package with all its dependency
2018-03-21 11:17:55 +01:00
is available or not as recommended for messages coding .
"""
2017-02-14 01:33:16 +01:00
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 ' ) :
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 True
2014-08-06 08:40:41 +02:00
def check_dependencies ( verbose = False , optional = False ) :
2018-03-21 11:17:55 +01:00
""" Do dependency check.
It identifies project dependencies and checks if there are
any known , publicly disclosed , vulnerabilities . basically
scan applications ( and their dependent libraries ) so that
easily identify any known vulnerable components .
"""
2014-08-06 08:40:41 +02:00
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 :
2014-08-16 06:34:06 +02: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
2017-02-14 01:33:16 +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 ] )
#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
2018-05-26 14:43:21 +02:00
2014-08-06 08:40:41 +02:00
if not has_all_dependencies :
logger . critical ( ' PyBitmessage cannot start. One or more dependencies are unavailable. ' )
sys . exit ( )
if __name__ == ' __main__ ' :
2018-03-21 11:17:55 +01:00
""" Check Dependencies """
2014-08-06 08:40:41 +02:00
check_dependencies ( True , True )