From 14bf35421bf75d26fe4799286c026a0cb7679615 Mon Sep 17 00:00:00 2001 From: Gregor Robinson Date: Wed, 26 Jun 2013 12:28:01 +0000 Subject: [PATCH 01/13] Fixing issue #258, bad keyfile permissions. This spits out a warning to the console, but ideally it would also issue a warning to the GUI for those who didn't start it from the console. N.B. the warning is a one shot thing, since it fixes the problem in a way essentially undetectable in the future, so it should be done right if it is to be done at all. Maybe we should even disable all keys automatically if the keyfile is found in an insecure state. --- src/shared.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/shared.py b/src/shared.py index 5dc6964b..09d0bae8 100644 --- a/src/shared.py +++ b/src/shared.py @@ -21,6 +21,7 @@ import socket import random import highlevelcrypto import shared +import stat config = ConfigParser.SafeConfigParser() myECCryptorObjects = {} @@ -196,8 +197,10 @@ def reloadMyAddressHashes(): myAddressesByHash.clear() #myPrivateKeys.clear() configSections = config.sections() + hasExistingKeys = False for addressInKeysFile in configSections: if addressInKeysFile <> 'bitmessagesettings': + hasExistingKeys = True isEnabled = config.getboolean(addressInKeysFile, 'enabled') if isEnabled: status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) @@ -208,6 +211,7 @@ def reloadMyAddressHashes(): myAddressesByHash[hash] = addressInKeysFile else: sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2 or 3.\n') + fixKeyfilePermissions(appdata + 'keys.dat', hasExistingKeys) def reloadBroadcastSendersForWhichImWatching(): printLock.acquire() @@ -298,3 +302,26 @@ def fixPotentiallyInvalidUTF8Data(text): except: output = 'Part of the message is corrupt. The message cannot be displayed the normal way.\n\n' + repr(text) return output + +# Fix keyfile permissions due to inappropriate umask during keys.dat creation. +def fixKeyfilePermissions(keyfile, hasExistingKeys): + present_keyfile_permissions = os.stat(keyfile)[0] + keyfile_disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO + if (present_keyfile_permissions & keyfile_disallowed_permissions) != 0: + allowed_keyfile_permissions = ((1<<32)-1) ^ keyfile_disallowed_permissions + new_keyfile_permissions = ( + allowed_keyfile_permissions & present_keyfile_permissions) + os.chmod(keyfile, new_keyfile_permissions) + if hasExistingKeys: + print + print '******************************************************************' + print '** !! WARNING !! **' + print '******************************************************************' + print '** Possibly major security problem: **' + print '** Your keyfiles were vulnerable to being read by other users **' + print '** (including some untrusted daemons). You may wish to consider **' + print '** generating new keys and discontinuing use of your old ones. **' + print '** The problem has been automatically fixed. **' + print '******************************************************************' + print + From db3120f65530310f92a23f482be02853d1661089 Mon Sep 17 00:00:00 2001 From: Gregor Robinson Date: Thu, 27 Jun 2013 10:02:52 +0000 Subject: [PATCH 02/13] Fix #263 & #262: insecure keyfile permissions. * Added conditional to keyfile fix code that excludes windows. * Cleaned up old keyfile permissions fix. * Added umask (not conditional against Windows, because I don't think that is necessary). --- src/helper_startup.py | 2 + src/shared.py | 97 +++++++++++++++++++++++++++++++------------ 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/helper_startup.py b/src/helper_startup.py index df27fd6e..f9eefc01 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -74,6 +74,8 @@ def loadConfig(): print 'Creating new config files in', shared.appdata if not os.path.exists(shared.appdata): os.makedirs(shared.appdata) + if not sys.platform.startswith('win'): + os.umask(0o077) with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) diff --git a/src/shared.py b/src/shared.py index 09d0bae8..57067334 100644 --- a/src/shared.py +++ b/src/shared.py @@ -169,10 +169,10 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): return False def safeConfigGetBoolean(section,field): - try: - return config.getboolean(section,field) - except: - return False + try: + return config.getboolean(section,field) + except: + return False def decodeWalletImportFormat(WIFstring): fullString = arithmetic.changebase(WIFstring,58,256) @@ -196,22 +196,37 @@ def reloadMyAddressHashes(): myECCryptorObjects.clear() myAddressesByHash.clear() #myPrivateKeys.clear() + + keyfileSecure = checkSensitiveFilePermissions(appdata + 'keys.dat') configSections = config.sections() - hasExistingKeys = False + hasEnabledKeys = False for addressInKeysFile in configSections: if addressInKeysFile <> 'bitmessagesettings': - hasExistingKeys = True + hasEnabledKeys = True isEnabled = config.getboolean(addressInKeysFile, 'enabled') if isEnabled: - status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) - if addressVersionNumber == 2 or addressVersionNumber == 3: - privEncryptionKey = decodeWalletImportFormat(config.get(addressInKeysFile, 'privencryptionkey')).encode('hex') #returns a simple 32 bytes of information encoded in 64 Hex characters, or null if there was an error - if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters - myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) - myAddressesByHash[hash] = addressInKeysFile + if keyfileSecure: + status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) + if addressVersionNumber == 2 or addressVersionNumber == 3: + # Returns a simple 32 bytes of information encoded in 64 Hex characters, or null if there was an error. + privEncryptionKey = decodeWalletImportFormat( + config.get(addressInKeysFile, 'privencryptionkey')).encode('hex') + + if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters + myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) + myAddressesByHash[hash] = addressInKeysFile + else: + sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2 or 3.\n') else: - sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2 or 3.\n') - fixKeyfilePermissions(appdata + 'keys.dat', hasExistingKeys) + # Insecure keyfile permissions. Disable key. + config.set(addressInKeysFile, 'enabled', 'false') + try: + if not keyfileSecure: + with open(appdata + 'keys.dat', 'wb') as keyfile: + config.write(keyfile) + except: + print 'Failed to disable vulnerable keyfiles.' + raise def reloadBroadcastSendersForWhichImWatching(): printLock.acquire() @@ -303,16 +318,14 @@ def fixPotentiallyInvalidUTF8Data(text): output = 'Part of the message is corrupt. The message cannot be displayed the normal way.\n\n' + repr(text) return output -# Fix keyfile permissions due to inappropriate umask during keys.dat creation. -def fixKeyfilePermissions(keyfile, hasExistingKeys): - present_keyfile_permissions = os.stat(keyfile)[0] - keyfile_disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO - if (present_keyfile_permissions & keyfile_disallowed_permissions) != 0: - allowed_keyfile_permissions = ((1<<32)-1) ^ keyfile_disallowed_permissions - new_keyfile_permissions = ( - allowed_keyfile_permissions & present_keyfile_permissions) - os.chmod(keyfile, new_keyfile_permissions) - if hasExistingKeys: +def checkSensitiveFilePermissions(filename): + if sys.platform == 'win32': + # TODO: This might deserve extra checks by someone familiar with + # Windows systems. + fileSecure = True + else: + fileSecure = secureFilePermissions(filename) + if not fileSecure: print print '******************************************************************' print '** !! WARNING !! **' @@ -321,7 +334,37 @@ def fixKeyfilePermissions(keyfile, hasExistingKeys): print '** Your keyfiles were vulnerable to being read by other users **' print '** (including some untrusted daemons). You may wish to consider **' print '** generating new keys and discontinuing use of your old ones. **' - print '** The problem has been automatically fixed. **' - print '******************************************************************' - print + print '** Your private keys have been disabled for your security, but **' + print '** you may re-enable them using the "Your Identities" tab in **' + print '** the default GUI or by modifying keys.dat such that your keys **' + print '** show "enabled = true". **' + try: + fixSensitiveFilePermissions(filename) + print '** The problem has been automatically fixed. **' + print '******************************************************************' + print + except Exception, e: + print '** Could NOT automatically fix permissions. **' + print '******************************************************************' + print + raise + return fileSecure + + +# Checks sensitive file permissions for inappropriate umask during keys.dat creation. +# (Or unwise subsequent chmod.) +# Returns true iff file appears to have appropriate permissions. +def secureFilePermissions(filename): + present_permissions = os.stat(filename)[0] + disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO + return present_permissions & disallowed_permissions == 0 + +# Fixes permissions on a sensitive file. +def fixSensitiveFilePermissions(filename): + present_permissions = os.stat(filename)[0] + disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO + allowed_permissions = ((1<<32)-1) ^ disallowed_permissions + new_permissions = ( + allowed_permissions & present_permissions) + os.chmod(filename, new_permissions) From 1ed34b00848c07e5266ac4575474dbf3c42bc168 Mon Sep 17 00:00:00 2001 From: Gregor Robinson Date: Thu, 27 Jun 2013 10:44:49 +0000 Subject: [PATCH 03/13] Make warning message more specific. --- src/shared.py | 136 +++++++++++++++++++++++++++----------------------- 1 file changed, 73 insertions(+), 63 deletions(-) diff --git a/src/shared.py b/src/shared.py index 57067334..ea1e3848 100644 --- a/src/shared.py +++ b/src/shared.py @@ -202,31 +202,36 @@ def reloadMyAddressHashes(): hasEnabledKeys = False for addressInKeysFile in configSections: if addressInKeysFile <> 'bitmessagesettings': - hasEnabledKeys = True isEnabled = config.getboolean(addressInKeysFile, 'enabled') if isEnabled: - if keyfileSecure: - status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) - if addressVersionNumber == 2 or addressVersionNumber == 3: - # Returns a simple 32 bytes of information encoded in 64 Hex characters, or null if there was an error. - privEncryptionKey = decodeWalletImportFormat( - config.get(addressInKeysFile, 'privencryptionkey')).encode('hex') + hasEnabledKeys = True + status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) + if addressVersionNumber == 2 or addressVersionNumber == 3: + # Returns a simple 32 bytes of information encoded in 64 Hex characters, + # or null if there was an error. + privEncryptionKey = decodeWalletImportFormat( + config.get(addressInKeysFile, 'privencryptionkey')).encode('hex') - if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters - myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) - myAddressesByHash[hash] = addressInKeysFile - else: - sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2 or 3.\n') + if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters + myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) + myAddressesByHash[hash] = addressInKeysFile + + if not keyfileSecure: + # Insecure keyfile permissions. Disable key. + config.set(addressInKeysFile, 'enabled', 'false') else: - # Insecure keyfile permissions. Disable key. - config.set(addressInKeysFile, 'enabled', 'false') - try: - if not keyfileSecure: - with open(appdata + 'keys.dat', 'wb') as keyfile: - config.write(keyfile) - except: - print 'Failed to disable vulnerable keyfiles.' - raise + sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address ' + 'versions other than 2 or 3.\n') + + if not keyfileSecure: + fixSensitiveFilePermissions(appdata + 'keys.dat', hasEnabledKeys) + if hasEnabledKeys: + try: + with open(appdata + 'keys.dat', 'wb') as keyfile: + config.write(keyfile) + except: + print 'Failed to disable vulnerable keys.' + raise def reloadBroadcastSendersForWhichImWatching(): printLock.acquire() @@ -318,53 +323,58 @@ def fixPotentiallyInvalidUTF8Data(text): output = 'Part of the message is corrupt. The message cannot be displayed the normal way.\n\n' + repr(text) return output +# Checks sensitive file permissions for inappropriate umask during keys.dat creation. +# (Or unwise subsequent chmod.) +# Returns true iff file appears to have appropriate permissions. def checkSensitiveFilePermissions(filename): if sys.platform == 'win32': # TODO: This might deserve extra checks by someone familiar with # Windows systems. - fileSecure = True + return True else: - fileSecure = secureFilePermissions(filename) - if not fileSecure: - print - print '******************************************************************' - print '** !! WARNING !! **' - print '******************************************************************' - print '** Possibly major security problem: **' - print '** Your keyfiles were vulnerable to being read by other users **' - print '** (including some untrusted daemons). You may wish to consider **' - print '** generating new keys and discontinuing use of your old ones. **' - print '** Your private keys have been disabled for your security, but **' - print '** you may re-enable them using the "Your Identities" tab in **' - print '** the default GUI or by modifying keys.dat such that your keys **' - print '** show "enabled = true". **' - try: - fixSensitiveFilePermissions(filename) - print '** The problem has been automatically fixed. **' - print '******************************************************************' - print - except Exception, e: - print '** Could NOT automatically fix permissions. **' - print '******************************************************************' - print - raise - return fileSecure - - -# Checks sensitive file permissions for inappropriate umask during keys.dat creation. -# (Or unwise subsequent chmod.) -# Returns true iff file appears to have appropriate permissions. -def secureFilePermissions(filename): - present_permissions = os.stat(filename)[0] - disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO - return present_permissions & disallowed_permissions == 0 + present_permissions = os.stat(filename)[0] + disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO + return present_permissions & disallowed_permissions == 0 # Fixes permissions on a sensitive file. -def fixSensitiveFilePermissions(filename): - present_permissions = os.stat(filename)[0] - disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO - allowed_permissions = ((1<<32)-1) ^ disallowed_permissions - new_permissions = ( - allowed_permissions & present_permissions) - os.chmod(filename, new_permissions) +def fixSensitiveFilePermissions(filename, hasEnabledKeys): + if hasEnabledKeys: + print + print '******************************************************************' + print '** !! WARNING !! **' + print '******************************************************************' + print '** Possibly major security problem: **' + print '** Your keyfile was vulnerable to being read by other users **' + print '** (including some untrusted daemons). You may wish to consider **' + print '** generating new keys and discontinuing use of your old ones. **' + print '** Your private keys have been disabled for your security, but **' + print '** you may re-enable them using the "Your Identities" tab in **' + print '** the default GUI or by modifying keys.dat such that your keys **' + print '** show "enabled = true". **' + else: + print '******************************************************************' + print '** !! WARNING !! **' + print '******************************************************************' + print '** Possibly major security problem: **' + print '** Your keyfile was vulnerable to being read by other users. **' + print '** Fortunately, you don\'t have any enabled keys, but be aware **' + print '** that any disabled keys may have been compromised by malware **' + print '** running by other users and that you should reboot before **' + print '** generating any new keys. **' + try: + present_permissions = os.stat(filename)[0] + disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO + allowed_permissions = ((1<<32)-1) ^ disallowed_permissions + new_permissions = ( + allowed_permissions & present_permissions) + os.chmod(filename, new_permissions) + + print '** The file permissions have been automatically fixed. **' + print '******************************************************************' + print + except Exception, e: + print '** Could NOT automatically fix permissions. **' + print '******************************************************************' + print + raise From ebaa1bf3468d617cc9ec5353f34e32facb992840 Mon Sep 17 00:00:00 2001 From: Gregor Robinson Date: Mon, 8 Jul 2013 23:21:29 +0300 Subject: [PATCH 04/13] No paranoid key disable for bad keyfile perms. --- src/shared.py | 81 ++++++++++++++++++--------------------------------- 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/src/shared.py b/src/shared.py index 9a1cd428..2930784e 100644 --- a/src/shared.py +++ b/src/shared.py @@ -8,20 +8,24 @@ maximumAgeOfNodesThatIAdvertiseToOthers = 10800 # Equals three hours useVeryEasyProofOfWorkForTesting = False # If you set this to True while on the normal network, you won't be able to send or sometimes receive messages. -import threading -import sys -from addresses import * -import highlevelcrypto -import Queue -import pickle -import os -import time +# Libraries. import ConfigParser -import socket +import os +import pickle +import Queue import random +import socket +import sys +import stat +import threading +import time + +# Project imports. +from addresses import * +from debug import logger import highlevelcrypto import shared -import stat + config = ConfigParser.SafeConfigParser() myECCryptorObjects = {} @@ -131,12 +135,14 @@ def lookupAppdataFolder(): except KeyError: dataFolder = path.join(environ["HOME"], ".config", APPNAME) # Migrate existing data to the proper location if this is an existing install - try: - print "Moving data folder to ~/.config/%s" % APPNAME - move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) - dataFolder = dataFolder + '/' - except IOError: - dataFolder = dataFolder + '/' + if not os.path.exists(dataFolder): + try: + print "Moving data folder to ~/.config/%s" % APPNAME + move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) + dataFolder = dataFolder + except IOError: + dataFolder = dataFolder + dataFolder = dataFolder + '/' return dataFolder def isAddressInMyAddressBook(address): @@ -227,22 +233,12 @@ def reloadMyAddressHashes(): myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) myAddressesByHash[hash] = addressInKeysFile - if not keyfileSecure: - # Insecure keyfile permissions. Disable key. - config.set(addressInKeysFile, 'enabled', 'false') else: sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address ' 'versions other than 2 or 3.\n') if not keyfileSecure: fixSensitiveFilePermissions(appdata + 'keys.dat', hasEnabledKeys) - if hasEnabledKeys: - try: - with open(appdata + 'keys.dat', 'wb') as keyfile: - config.write(keyfile) - except: - print 'Failed to disable vulnerable keys.' - raise def reloadBroadcastSendersForWhichImWatching(): printLock.acquire() @@ -350,28 +346,10 @@ def checkSensitiveFilePermissions(filename): # Fixes permissions on a sensitive file. def fixSensitiveFilePermissions(filename, hasEnabledKeys): if hasEnabledKeys: - print - print '******************************************************************' - print '** !! WARNING !! **' - print '******************************************************************' - print '** Possibly major security problem: **' - print '** Your keyfile was vulnerable to being read by other users **' - print '** (including some untrusted daemons). You may wish to consider **' - print '** generating new keys and discontinuing use of your old ones. **' - print '** Your private keys have been disabled for your security, but **' - print '** you may re-enable them using the "Your Identities" tab in **' - print '** the default GUI or by modifying keys.dat such that your keys **' - print '** show "enabled = true". **' + logger.warning('Keyfile had insecure permissions, and there were enabled keys. ' + 'The truly paranoid should stop using them immediately.') else: - print '******************************************************************' - print '** !! WARNING !! **' - print '******************************************************************' - print '** Possibly major security problem: **' - print '** Your keyfile was vulnerable to being read by other users. **' - print '** Fortunately, you don\'t have any enabled keys, but be aware **' - print '** that any disabled keys may have been compromised by malware **' - print '** running by other users and that you should reboot before **' - print '** generating any new keys. **' + logger.warning('Keyfile had insecure permissions, but there were no enabled keys.') try: present_permissions = os.stat(filename)[0] disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO @@ -380,12 +358,9 @@ def fixSensitiveFilePermissions(filename, hasEnabledKeys): allowed_permissions & present_permissions) os.chmod(filename, new_permissions) - print '** The file permissions have been automatically fixed. **' - print '******************************************************************' - print + logger.info('Keyfile permissions automatically fixed.') + except Exception, e: - print '** Could NOT automatically fix permissions. **' - print '******************************************************************' - print + logger.exception('Keyfile permissions could not be fixed.') raise From 1ff1c1b8a5b8d523971f69e3b646742362f35bce Mon Sep 17 00:00:00 2001 From: Gregor Robinson Date: Mon, 8 Jul 2013 23:33:15 +0300 Subject: [PATCH 05/13] Spelling. --- src/helper_bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py index 296dda6b..c3d5c1fd 100644 --- a/src/helper_bootstrap.py +++ b/src/helper_bootstrap.py @@ -33,7 +33,7 @@ def dns(): print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method' shared.knownNodes[1][item[4][0]] = (8080, int(time.time())) except: - print 'bootstrap8080.bitmessage.org DNS bootstraping failed.' + print 'bootstrap8080.bitmessage.org DNS bootstrapping failed.' try: for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80): print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method' From a579e8f1d324dac0c547c2d18cbce6e2f4bee7d9 Mon Sep 17 00:00:00 2001 From: Gregor Robinson Date: Wed, 10 Jul 2013 11:43:18 +0300 Subject: [PATCH 06/13] Logging fixes. --- src/debug.py | 3 +++ src/shared.py | 57 +++++++++++++++++++++++++-------------------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/debug.py b/src/debug.py index d8033f2d..14214686 100644 --- a/src/debug.py +++ b/src/debug.py @@ -48,12 +48,15 @@ logging.config.dictConfig({ 'loggers': { 'console_only': { 'handlers': ['console'], + 'propagate' : 0 }, 'file_only': { 'handlers': ['file'], + 'propagate' : 0 }, 'both': { 'handlers': ['console', 'file'], + 'propagate' : 0 }, }, 'root': { diff --git a/src/shared.py b/src/shared.py index 2930784e..e7a412ac 100644 --- a/src/shared.py +++ b/src/shared.py @@ -123,7 +123,8 @@ def lookupAppdataFolder(): if "HOME" in environ: dataFolder = path.join(os.environ["HOME"], "Library/Application Support/", APPNAME) + '/' else: - print 'Could not find home folder, please report this message and your OS X version to the BitMessage Github.' + logger.critical('Could not find home folder, please report this message and your ' + 'OS X version to the BitMessage Github.') sys.exit() elif 'win32' in sys.platform or 'win64' in sys.platform: @@ -137,7 +138,7 @@ def lookupAppdataFolder(): # Migrate existing data to the proper location if this is an existing install if not os.path.exists(dataFolder): try: - print "Moving data folder to ~/.config/%s" % APPNAME + logger.info("Moving data folder to %s" % (dataFolder)) move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) dataFolder = dataFolder except IOError: @@ -195,21 +196,22 @@ def decodeWalletImportFormat(WIFstring): fullString = arithmetic.changebase(WIFstring,58,256) privkey = fullString[:-4] if fullString[-4:] != hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - sys.stderr.write('Major problem! When trying to decode one of your private keys, the checksum failed. Here is the PRIVATE key: %s\n' % str(WIFstring)) + logger.error('Major problem! When trying to decode one of your private keys, the checksum ' + 'failed. Here is the PRIVATE key: %s\n' % str(WIFstring)) return "" else: #checksum passed if privkey[0] == '\x80': return privkey[1:] else: - sys.stderr.write('Major problem! When trying to decode one of your private keys, the checksum passed but the key doesn\'t begin with hex 80. Here is the PRIVATE key: %s\n' % str(WIFstring)) + logger.error('Major problem! When trying to decode one of your private keys, the ' + 'checksum passed but the key doesn\'t begin with hex 80. Here is the ' + 'PRIVATE key: %s\n' % str(WIFstring)) return "" def reloadMyAddressHashes(): - printLock.acquire() - print 'reloading keys from keys.dat file' - printLock.release() + logger.debug('reloading keys from keys.dat file') myECCryptorObjects.clear() myAddressesByHash.clear() #myPrivateKeys.clear() @@ -241,9 +243,7 @@ def reloadMyAddressHashes(): fixSensitiveFilePermissions(appdata + 'keys.dat', hasEnabledKeys) def reloadBroadcastSendersForWhichImWatching(): - printLock.acquire() - print 'reloading subscriptions...' - printLock.release() + logger.debug('reloading subscriptions...') broadcastSendersForWhichImWatching.clear() MyECSubscriptionCryptorObjects.clear() sqlLock.acquire() @@ -266,46 +266,44 @@ def doCleanShutdown(): knownNodesLock.acquire() UISignalQueue.put(('updateStatusBar','Saving the knownNodes list of peers to disk...')) output = open(appdata + 'knownnodes.dat', 'wb') - print 'finished opening knownnodes.dat. Now pickle.dump' + logger.info('finished opening knownnodes.dat. Now pickle.dump') pickle.dump(knownNodes, output) - print 'Completed pickle.dump. Closing output...' + logger.info('Completed pickle.dump. Closing output...') output.close() knownNodesLock.release() - printLock.acquire() - print 'Finished closing knownnodes.dat output file.' - printLock.release() + logger.info('Finished closing knownnodes.dat output file.') UISignalQueue.put(('updateStatusBar','Done saving the knownNodes list of peers to disk.')) broadcastToSendDataQueues((0, 'shutdown', 'all')) - printLock.acquire() - print 'Flushing inventory in memory out to disk...' - printLock.release() - UISignalQueue.put(('updateStatusBar','Flushing inventory in memory out to disk. This should normally only take a second...')) + logger.info('Flushing inventory in memory out to disk...') + UISignalQueue.put(('updateStatusBar','Flushing inventory in memory out to disk. ' + 'This should normally only take a second...')) flushInventory() - #This one last useless query will guarantee that the previous flush committed before we close the program. + # This one last useless query will guarantee that the previous flush committed before we close + # the program. sqlLock.acquire() sqlSubmitQueue.put('SELECT address FROM subscriptions') sqlSubmitQueue.put('') sqlReturnQueue.get() sqlSubmitQueue.put('exit') sqlLock.release() - printLock.acquire() - print 'Finished flushing inventory.' - printLock.release() + logger.info('Finished flushing inventory.') - time.sleep(.25) #Wait long enough to guarantee that any running proof of work worker threads will check the shutdown variable and exit. If the main thread closes before they do then they won't stop. + # Wait long enough to guarantee that any running proof of work worker threads will check the + # shutdown variable and exit. If the main thread closes before they do then they won't stop. + time.sleep(.25) if safeConfigGetBoolean('bitmessagesettings','daemon'): - printLock.acquire() - print 'Done.' - printLock.release() + logger.info('Clean shutdown complete.') os._exit(0) -#When you want to command a sendDataThread to do something, like shutdown or send some data, this function puts your data into the queues for each of the sendDataThreads. The sendDataThreads are responsible for putting their queue into (and out of) the sendDataQueues list. +# When you want to command a sendDataThread to do something, like shutdown or send some data, this +# function puts your data into the queues for each of the sendDataThreads. The sendDataThreads are +# responsible for putting their queue into (and out of) the sendDataQueues list. def broadcastToSendDataQueues(data): - #print 'running broadcastToSendDataQueues' + # logger.debug('running broadcastToSendDataQueues') for q in sendDataQueues: q.put((data)) @@ -332,6 +330,7 @@ def fixPotentiallyInvalidUTF8Data(text): # Checks sensitive file permissions for inappropriate umask during keys.dat creation. # (Or unwise subsequent chmod.) +# # Returns true iff file appears to have appropriate permissions. def checkSensitiveFilePermissions(filename): if sys.platform == 'win32': From e8fa5aaefe32a0d77c9a0c634ee07623ae1bc1bd Mon Sep 17 00:00:00 2001 From: Gregor Robinson Date: Wed, 10 Jul 2013 20:29:07 +0100 Subject: [PATCH 07/13] Switch an stderr message to logger. --- src/shared.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared.py b/src/shared.py index 0dbeef5d..816cea2a 100644 --- a/src/shared.py +++ b/src/shared.py @@ -236,8 +236,8 @@ def reloadMyAddressHashes(): myAddressesByHash[hash] = addressInKeysFile else: - sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address ' - 'versions other than 2 or 3.\n') + logger.error('Error in reloadMyAddressHashes: Can\'t handle address ' + 'versions other than 2 or 3.\n') if not keyfileSecure: fixSensitiveFilePermissions(appdata + 'keys.dat', hasEnabledKeys) From fa53eb370ca7584bd0d88de2ad956d8894ab7a8e Mon Sep 17 00:00:00 2001 From: Gregor Robinson Date: Thu, 11 Jul 2013 23:58:10 +0100 Subject: [PATCH 08/13] Clarify IOError handling with comment. --- src/shared.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared.py b/src/shared.py index 816cea2a..85246906 100644 --- a/src/shared.py +++ b/src/shared.py @@ -142,6 +142,7 @@ def lookupAppdataFolder(): logger.info("Moving data folder to %s" % (dataFolder)) move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) except IOError: + # Old directory may not exist. pass dataFolder = dataFolder + '/' return dataFolder From 3a06edbbd829df465d86b061ffd728ef38d44800 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sun, 14 Jul 2013 17:10:37 -0400 Subject: [PATCH 09/13] manual merge, fix minor import issue --- src/shared.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shared.py b/src/shared.py index 897a1cf0..03ee1752 100644 --- a/src/shared.py +++ b/src/shared.py @@ -22,7 +22,6 @@ import time # Project imports. from addresses import * -from debug import logger import highlevelcrypto import shared import helper_startup From d900b9de7074d5d2298139406835d2ebf75028fa Mon Sep 17 00:00:00 2001 From: DivineOmega Date: Mon, 15 Jul 2013 10:49:01 +0100 Subject: [PATCH 10/13] Added check for logger global before attempting to log in places where logging may occur before the logger is ready --- src/shared.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/shared.py b/src/shared.py index 5abeeb96..ddedf3d0 100644 --- a/src/shared.py +++ b/src/shared.py @@ -118,8 +118,9 @@ def lookupAppdataFolder(): if "HOME" in environ: dataFolder = path.join(os.environ["HOME"], "Library/Application Support/", APPNAME) + '/' else: - logger.critical('Could not find home folder, please report this message and your ' - 'OS X version to the BitMessage Github.') + if 'logger' in globals(): + logger.critical('Could not find home folder, please report this message and your ' + 'OS X version to the BitMessage Github.') sys.exit() elif 'win32' in sys.platform or 'win64' in sys.platform: @@ -133,7 +134,8 @@ def lookupAppdataFolder(): # Migrate existing data to the proper location if this is an existing install try: - logger.info("Moving data folder to %s" % (dataFolder)) + if 'logger' in globals(): + logger.info("Moving data folder to %s" % (dataFolder)) move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) except IOError: pass From 3107150ace725a4928e1d05e8e2b94af4ba20729 Mon Sep 17 00:00:00 2001 From: DivineOmega Date: Mon, 15 Jul 2013 10:56:13 +0100 Subject: [PATCH 11/13] Added fall back print statements in case logger is unavailable --- src/shared.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/shared.py b/src/shared.py index ddedf3d0..ff39ea2e 100644 --- a/src/shared.py +++ b/src/shared.py @@ -118,9 +118,11 @@ def lookupAppdataFolder(): if "HOME" in environ: dataFolder = path.join(os.environ["HOME"], "Library/Application Support/", APPNAME) + '/' else: + stringToLog = 'Could not find home folder, please report this message and your OS X version to the BitMessage Github.' if 'logger' in globals(): - logger.critical('Could not find home folder, please report this message and your ' - 'OS X version to the BitMessage Github.') + logger.critical(stringToLog) + else: + print stringToLog sys.exit() elif 'win32' in sys.platform or 'win64' in sys.platform: @@ -134,8 +136,11 @@ def lookupAppdataFolder(): # Migrate existing data to the proper location if this is an existing install try: + stringToLog = "Moving data folder to %s" % (dataFolder) if 'logger' in globals(): - logger.info("Moving data folder to %s" % (dataFolder)) + logger.info(stringToLog) + else: + print stringToLog move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) except IOError: pass From 52caec5e2b4a04c893e953d3fc8019e3a9547365 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Mon, 15 Jul 2013 12:19:53 -0400 Subject: [PATCH 12/13] Move one line of code so that correct program activity is logged --- src/bitmessagemain.py | 1 - src/shared.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index cfbfdd6c..15acf545 100644 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -720,7 +720,6 @@ if __name__ == "__main__": helper_bootstrap.knownNodes() helper_bootstrap.dns() - # Start the address generation thread addressGeneratorThread = addressGenerator() addressGeneratorThread.daemon = True # close the main program even if there are threads left diff --git a/src/shared.py b/src/shared.py index ff39ea2e..72badbc7 100644 --- a/src/shared.py +++ b/src/shared.py @@ -136,12 +136,12 @@ def lookupAppdataFolder(): # Migrate existing data to the proper location if this is an existing install try: + move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) stringToLog = "Moving data folder to %s" % (dataFolder) if 'logger' in globals(): logger.info(stringToLog) else: print stringToLog - move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) except IOError: pass dataFolder = dataFolder + '/' From 08694ecc38b0d3a18a01fe2b50f0cfacc26c4a0e Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Mon, 15 Jul 2013 15:45:03 -0400 Subject: [PATCH 13/13] Portable mode moves debug.log --- src/bitmessageqt/__init__.py | 15 +++++++ src/class_sqlThread.py | 1 + src/debug.py | 85 ++++++++++++++++++++---------------- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 316f4205..b4ce6725 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -29,6 +29,8 @@ import os from pyelliptic.openssl import OpenSSL import pickle import platform +import debug +from debug import logger try: from PyQt4 import QtCore, QtGui @@ -1874,7 +1876,14 @@ class MyForm(QtGui.QMainWindow): shared.knownNodesLock.release() os.remove(shared.appdata + 'keys.dat') os.remove(shared.appdata + 'knownnodes.dat') + previousAppdataLocation = shared.appdata shared.appdata = '' + debug.restartLoggingInUpdatedAppdataLocation() + try: + os.remove(previousAppdataLocation + 'debug.log') + os.remove(previousAppdataLocation + 'debug.log.1') + except: + pass if shared.appdata == '' and not self.settingsDialogInstance.ui.checkBoxPortableMode.isChecked(): # If we ARE using portable mode now but the user selected that we shouldn't... shared.appdata = shared.lookupAppdataFolder() @@ -1894,6 +1903,12 @@ class MyForm(QtGui.QMainWindow): shared.knownNodesLock.release() os.remove('keys.dat') os.remove('knownnodes.dat') + debug.restartLoggingInUpdatedAppdataLocation() + try: + os.remove('debug.log') + os.remove('debug.log.1') + except: + pass def click_radioButtonBlacklist(self): if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'white': diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 84014a8c..a9de7042 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -5,6 +5,7 @@ import time import shutil # used for moving the messages.dat file import sys import os +from debug import logger # This thread exists because SQLITE3 is so un-threadsafe that we must # submit queries to it and it puts results back in a different queue. They diff --git a/src/debug.py b/src/debug.py index 034d3102..fe7815e7 100644 --- a/src/debug.py +++ b/src/debug.py @@ -23,48 +23,59 @@ import shared # TODO(xj9): Get from a config file. log_level = 'DEBUG' -logging.config.dictConfig({ - 'version': 1, - 'formatters': { - 'default': { - 'format': '%(asctime)s - %(levelname)s - %(message)s', +def configureLogging(): + logging.config.dictConfig({ + 'version': 1, + 'formatters': { + 'default': { + 'format': '%(asctime)s - %(levelname)s - %(message)s', + }, }, - }, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'default', - 'level': log_level, - 'stream': 'ext://sys.stdout' + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'default', + 'level': log_level, + 'stream': 'ext://sys.stdout' + }, + 'file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'formatter': 'default', + 'level': log_level, + 'filename': shared.appdata + 'debug.log', + 'maxBytes': 2097152, # 2 MiB + 'backupCount': 1, + } }, - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'formatter': 'default', + 'loggers': { + 'console_only': { + 'handlers': ['console'], + 'propagate' : 0 + }, + 'file_only': { + 'handlers': ['file'], + 'propagate' : 0 + }, + 'both': { + 'handlers': ['console', 'file'], + 'propagate' : 0 + }, + }, + 'root': { 'level': log_level, - 'filename': shared.appdata + 'debug.log', - 'maxBytes': 2097152, # 2 MiB - 'backupCount': 1, - } - }, - 'loggers': { - 'console_only': { 'handlers': ['console'], - 'propagate' : 0 }, - 'file_only': { - 'handlers': ['file'], - 'propagate' : 0 - }, - 'both': { - 'handlers': ['console', 'file'], - 'propagate' : 0 - }, - }, - 'root': { - 'level': log_level, - 'handlers': ['console'], - }, -}) + }) # TODO (xj9): Get from a config file. #logger = logging.getLogger('console_only') +configureLogging() logger = logging.getLogger('both') + +def restartLoggingInUpdatedAppdataLocation(): + global logger + for i in list(logger.handlers): + logger.removeHandler(i) + i.flush() + i.close() + configureLogging() + logger = logging.getLogger('both') \ No newline at end of file