Skip to content

Commit c6291f5

Browse files
author
Jonathan Warren
committed
Merge branch 'master' of github.com:Atheros1/PyBitmessage
2 parents 3427bc5 + 08694ec commit c6291f5

File tree

7 files changed

+151
-59
lines changed

7 files changed

+151
-59
lines changed

src/bitmessagemain.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,6 @@ def run(self):
720720

721721
helper_bootstrap.knownNodes()
722722
helper_bootstrap.dns()
723-
724723
# Start the address generation thread
725724
addressGeneratorThread = addressGenerator()
726725
addressGeneratorThread.daemon = True # close the main program even if there are threads left

src/bitmessageqt/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
from pyelliptic.openssl import OpenSSL
3030
import pickle
3131
import platform
32+
import debug
33+
from debug import logger
3234

3335
try:
3436
from PyQt4 import QtCore, QtGui
@@ -1876,7 +1878,14 @@ def click_actionSettings(self):
18761878
shared.knownNodesLock.release()
18771879
os.remove(shared.appdata + 'keys.dat')
18781880
os.remove(shared.appdata + 'knownnodes.dat')
1881+
previousAppdataLocation = shared.appdata
18791882
shared.appdata = ''
1883+
debug.restartLoggingInUpdatedAppdataLocation()
1884+
try:
1885+
os.remove(previousAppdataLocation + 'debug.log')
1886+
os.remove(previousAppdataLocation + 'debug.log.1')
1887+
except:
1888+
pass
18801889

18811890
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...
18821891
shared.appdata = shared.lookupAppdataFolder()
@@ -1896,6 +1905,12 @@ def click_actionSettings(self):
18961905
shared.knownNodesLock.release()
18971906
os.remove('keys.dat')
18981907
os.remove('knownnodes.dat')
1908+
debug.restartLoggingInUpdatedAppdataLocation()
1909+
try:
1910+
os.remove('debug.log')
1911+
os.remove('debug.log.1')
1912+
except:
1913+
pass
18991914

19001915
def click_radioButtonBlacklist(self):
19011916
if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'white':

src/class_sqlThread.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import shutil # used for moving the messages.dat file
66
import sys
77
import os
8+
from debug import logger
89

910
# This thread exists because SQLITE3 is so un-threadsafe that we must
1011
# submit queries to it and it puts results back in a different queue. They

src/debug.py

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,48 +23,59 @@
2323
# TODO(xj9): Get from a config file.
2424
log_level = 'DEBUG'
2525

26-
logging.config.dictConfig({
27-
'version': 1,
28-
'formatters': {
29-
'default': {
30-
'format': '%(asctime)s - %(levelname)s - %(message)s',
26+
def configureLogging():
27+
logging.config.dictConfig({
28+
'version': 1,
29+
'formatters': {
30+
'default': {
31+
'format': '%(asctime)s - %(levelname)s - %(message)s',
32+
},
3133
},
32-
},
33-
'handlers': {
34-
'console': {
35-
'class': 'logging.StreamHandler',
36-
'formatter': 'default',
37-
'level': log_level,
38-
'stream': 'ext://sys.stdout'
34+
'handlers': {
35+
'console': {
36+
'class': 'logging.StreamHandler',
37+
'formatter': 'default',
38+
'level': log_level,
39+
'stream': 'ext://sys.stdout'
40+
},
41+
'file': {
42+
'class': 'logging.handlers.RotatingFileHandler',
43+
'formatter': 'default',
44+
'level': log_level,
45+
'filename': shared.appdata + 'debug.log',
46+
'maxBytes': 2097152, # 2 MiB
47+
'backupCount': 1,
48+
}
49+
},
50+
'loggers': {
51+
'console_only': {
52+
'handlers': ['console'],
53+
'propagate' : 0
54+
},
55+
'file_only': {
56+
'handlers': ['file'],
57+
'propagate' : 0
58+
},
59+
'both': {
60+
'handlers': ['console', 'file'],
61+
'propagate' : 0
62+
},
3963
},
40-
'file': {
41-
'class': 'logging.handlers.RotatingFileHandler',
42-
'formatter': 'default',
64+
'root': {
4365
'level': log_level,
44-
'filename': shared.appdata + 'debug.log',
45-
'maxBytes': 2097152, # 2 MiB
46-
'backupCount': 1,
47-
}
48-
},
49-
'loggers': {
50-
'console_only': {
5166
'handlers': ['console'],
52-
'propagate' : 0
53-
},
54-
'file_only': {
55-
'handlers': ['file'],
56-
'propagate' : 0
5767
},
58-
'both': {
59-
'handlers': ['console', 'file'],
60-
'propagate' : 0
61-
},
62-
},
63-
'root': {
64-
'level': log_level,
65-
'handlers': ['console'],
66-
},
67-
})
68+
})
6869
# TODO (xj9): Get from a config file.
6970
#logger = logging.getLogger('console_only')
71+
configureLogging()
7072
logger = logging.getLogger('both')
73+
74+
def restartLoggingInUpdatedAppdataLocation():
75+
global logger
76+
for i in list(logger.handlers):
77+
logger.removeHandler(i)
78+
i.flush()
79+
i.close()
80+
configureLogging()
81+
logger = logging.getLogger('both')

src/helper_bootstrap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def dns():
3333
print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method'
3434
shared.knownNodes[1][item[4][0]] = (8080, int(time.time()))
3535
except:
36-
print 'bootstrap8080.bitmessage.org DNS bootstraping failed.'
36+
print 'bootstrap8080.bitmessage.org DNS bootstrapping failed.'
3737
try:
3838
for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80):
3939
print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method'

src/helper_startup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ def loadConfig():
7676
print 'Creating new config files in', shared.appdata
7777
if not os.path.exists(shared.appdata):
7878
os.makedirs(shared.appdata)
79+
if not sys.platform.startswith('win'):
80+
os.umask(0o077)
7981
with open(shared.appdata + 'keys.dat', 'wb') as configfile:
8082
shared.config.write(configfile)
8183

src/shared.py

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@
88
useVeryEasyProofOfWorkForTesting = False # If you set this to True while on the normal network, you won't be able to send or sometimes receive messages.
99

1010

11-
import threading
12-
import sys
13-
from addresses import *
14-
import highlevelcrypto
15-
import Queue
16-
import pickle
17-
import os
18-
import time
11+
# Libraries.
1912
import ConfigParser
20-
import socket
13+
import os
14+
import pickle
15+
import Queue
2116
import random
17+
import socket
18+
import sys
19+
import stat
20+
import threading
21+
import time
22+
23+
# Project imports.
24+
from addresses import *
2225
import highlevelcrypto
2326
import shared
2427
import helper_startup
2528

2629

30+
2731
config = ConfigParser.SafeConfigParser()
2832
myECCryptorObjects = {}
2933
MyECSubscriptionCryptorObjects = {}
@@ -118,8 +122,11 @@ def lookupAppdataFolder():
118122
if "HOME" in environ:
119123
dataFolder = path.join(os.environ["HOME"], "Library/Application Support/", APPNAME) + '/'
120124
else:
121-
logger.critical('Could not find home folder, please report this message and your '
122-
'OS X version to the BitMessage Github.')
125+
stringToLog = 'Could not find home folder, please report this message and your OS X version to the BitMessage Github.'
126+
if 'logger' in globals():
127+
logger.critical(stringToLog)
128+
else:
129+
print stringToLog
123130
sys.exit()
124131

125132
elif 'win32' in sys.platform or 'win64' in sys.platform:
@@ -133,9 +140,14 @@ def lookupAppdataFolder():
133140

134141
# Migrate existing data to the proper location if this is an existing install
135142
try:
136-
logger.info("Moving data folder to %s" % (dataFolder))
137143
move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder)
144+
stringToLog = "Moving data folder to %s" % (dataFolder)
145+
if 'logger' in globals():
146+
logger.info(stringToLog)
147+
else:
148+
print stringToLog
138149
except IOError:
150+
# Old directory may not exist.
139151
pass
140152
dataFolder = dataFolder + '/'
141153
return dataFolder
@@ -181,23 +193,26 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address):
181193
return False
182194

183195
def safeConfigGetBoolean(section,field):
184-
try:
185-
return config.getboolean(section,field)
186-
except:
187-
return False
196+
try:
197+
return config.getboolean(section,field)
198+
except:
199+
return False
188200

189201
def decodeWalletImportFormat(WIFstring):
190202
fullString = arithmetic.changebase(WIFstring,58,256)
191203
privkey = fullString[:-4]
192204
if fullString[-4:] != hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]:
193-
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))
205+
logger.error('Major problem! When trying to decode one of your private keys, the checksum '
206+
'failed. Here is the PRIVATE key: %s\n' % str(WIFstring))
194207
return ""
195208
else:
196209
#checksum passed
197210
if privkey[0] == '\x80':
198211
return privkey[1:]
199212
else:
200-
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))
213+
logger.error('Major problem! When trying to decode one of your private keys, the '
214+
'checksum passed but the key doesn\'t begin with hex 80. Here is the '
215+
'PRIVATE key: %s\n' % str(WIFstring))
201216
return ""
202217

203218

@@ -206,19 +221,32 @@ def reloadMyAddressHashes():
206221
myECCryptorObjects.clear()
207222
myAddressesByHash.clear()
208223
#myPrivateKeys.clear()
224+
225+
keyfileSecure = checkSensitiveFilePermissions(appdata + 'keys.dat')
209226
configSections = config.sections()
227+
hasEnabledKeys = False
210228
for addressInKeysFile in configSections:
211229
if addressInKeysFile <> 'bitmessagesettings':
212230
isEnabled = config.getboolean(addressInKeysFile, 'enabled')
213231
if isEnabled:
232+
hasEnabledKeys = True
214233
status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile)
215234
if addressVersionNumber == 2 or addressVersionNumber == 3:
216-
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
235+
# Returns a simple 32 bytes of information encoded in 64 Hex characters,
236+
# or null if there was an error.
237+
privEncryptionKey = decodeWalletImportFormat(
238+
config.get(addressInKeysFile, 'privencryptionkey')).encode('hex')
239+
217240
if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters
218241
myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey)
219242
myAddressesByHash[hash] = addressInKeysFile
243+
220244
else:
221-
sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2 or 3.\n')
245+
logger.error('Error in reloadMyAddressHashes: Can\'t handle address '
246+
'versions other than 2 or 3.\n')
247+
248+
if not keyfileSecure:
249+
fixSensitiveFilePermissions(appdata + 'keys.dat', hasEnabledKeys)
222250

223251
def reloadBroadcastSendersForWhichImWatching():
224252
logger.debug('reloading subscriptions...')
@@ -269,6 +297,7 @@ def doCleanShutdown():
269297
sqlSubmitQueue.put('exit')
270298
sqlLock.release()
271299
logger.info('Finished flushing inventory.')
300+
272301
# Wait long enough to guarantee that any running proof of work worker threads will check the
273302
# shutdown variable and exit. If the main thread closes before they do then they won't stop.
274303
time.sleep(.25)
@@ -306,5 +335,40 @@ def fixPotentiallyInvalidUTF8Data(text):
306335
output = 'Part of the message is corrupt. The message cannot be displayed the normal way.\n\n' + repr(text)
307336
return output
308337

338+
# Checks sensitive file permissions for inappropriate umask during keys.dat creation.
339+
# (Or unwise subsequent chmod.)
340+
#
341+
# Returns true iff file appears to have appropriate permissions.
342+
def checkSensitiveFilePermissions(filename):
343+
if sys.platform == 'win32':
344+
# TODO: This might deserve extra checks by someone familiar with
345+
# Windows systems.
346+
return True
347+
else:
348+
present_permissions = os.stat(filename)[0]
349+
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
350+
return present_permissions & disallowed_permissions == 0
351+
352+
# Fixes permissions on a sensitive file.
353+
def fixSensitiveFilePermissions(filename, hasEnabledKeys):
354+
if hasEnabledKeys:
355+
logger.warning('Keyfile had insecure permissions, and there were enabled keys. '
356+
'The truly paranoid should stop using them immediately.')
357+
else:
358+
logger.warning('Keyfile had insecure permissions, but there were no enabled keys.')
359+
try:
360+
present_permissions = os.stat(filename)[0]
361+
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
362+
allowed_permissions = ((1<<32)-1) ^ disallowed_permissions
363+
new_permissions = (
364+
allowed_permissions & present_permissions)
365+
os.chmod(filename, new_permissions)
366+
367+
logger.info('Keyfile permissions automatically fixed.')
368+
369+
except Exception, e:
370+
logger.exception('Keyfile permissions could not be fixed.')
371+
raise
372+
309373
helper_startup.loadConfig()
310374
from debug import logger

0 commit comments

Comments
 (0)