diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 8168fd52..51b9a82d 100644 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -341,8 +341,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): subject = shared.fixPotentiallyInvalidUTF8Data(subject) message = shared.fixPotentiallyInvalidUTF8Data(message) data += json.dumps({'msgid':msgid.encode('hex'), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':subject.encode('base64'), 'message':message.encode('base64'), 'encodingType':encodingtype, 'receivedTime':received, 'read': read}, indent=4, separators=(',', ': ')) - data += ']}' - return data + data += ']}' + return data elif method == 'getAllSentMessages': shared.sqlLock.acquire() shared.sqlSubmitQueue.put('''SELECT msgid, toaddress, fromaddress, subject, lastactiontime, message, encodingtype, status, ackdata FROM sent where folder='sent' ORDER BY lastactiontime''') @@ -395,8 +395,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): subject = shared.fixPotentiallyInvalidUTF8Data(subject) message = shared.fixPotentiallyInvalidUTF8Data(message) data += json.dumps({'msgid':msgid.encode('hex'), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':subject.encode('base64'), 'message':message.encode('base64'), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':ackdata.encode('hex')}, indent=4, separators=(',', ': ')) - data += ']}' - return data + data += ']}' + return data elif method == 'getSentMessagesByAddress' or method == 'getSentMessagesBySender': if len(params) == 0: return 'API Error 0000: I need parameters!' @@ -677,25 +677,10 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): shared.UISignalQueue.put(('rerenderInboxFromLabels', '')) shared.UISignalQueue.put(('rerenderSubscriptions', '')) return 'Deleted subscription if it existed.' - elif method == 'listSubscriptions': - shared.sqlLock.acquire() - shared.sqlSubmitQueue.put('''SELECT label, address, enabled FROM subscriptions''') - shared.sqlSubmitQueue.put('') - queryreturn = shared.sqlReturnQueue.get() - shared.sqlLock.release() - data = '{"subscriptions":[' - for row in queryreturn: - label, address, enabled = row - label = shared.fixPotentiallyInvalidUTF8Data(label) - if len(data) > 20: - data += ',' - data += json.dumps({'label':label.encode('base64'), 'address': address, 'enabled': enabled == 1}, indent=4, separators=(',',': ')) - data += ']}' - return data elif method == 'clientStatus': return '{ "networkConnections" : "%s" }' % str(len(shared.connectedHostsList)) else: - return 'API Error 0020: Invalid method: %s' % method + return 'Invalid Method: %s' % method # This thread, of which there is only one, runs the API. diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 96db1f92..1d115af1 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -34,7 +34,7 @@ import platform import debug from debug import logger import subprocess - +import datetime try: from PyQt4 import QtCore, QtGui @@ -65,6 +65,12 @@ class MyForm(QtGui.QMainWindow): SOUND_DISCONNECTED = 4 SOUND_CONNECTION_GREEN = 5 + # the last time that a message arrival sound was played + lastSoundTime = datetime.datetime.now() - datetime.timedelta(days=1) + + # the maximum frequency of message sounds in seconds + maxSoundFrequencySec = 60 + str_broadcast_subscribers = '[Broadcast subscribers]' str_chan = '[chan]' @@ -429,7 +435,6 @@ class MyForm(QtGui.QMainWindow): self.rerenderComboBoxSendFrom() - # Show or hide the application window after clicking an item within the # tray icon or, on Windows, the try icon itself. def appIndicatorShowOrHideWindow(self): @@ -982,29 +987,63 @@ class MyForm(QtGui.QMainWindow): # update the menu entries self.ubuntuMessagingMenuUnread(drawAttention) + # returns true if the given sound category is a connection sound + # rather than a received message sound + def isConnectionSound(self, category): + if (category is self.SOUND_CONNECTED or + category is self.SOUND_DISCONNECTED or + category is self.SOUND_CONNECTION_GREEN): + return True + return False + # play a sound def playSound(self, category, label): + # filename of the sound to be played soundFilename = None + # whether to play a sound or not + play = True + + # if the address had a known label in the address book if label is not None: - # does a sound file exist for this particular contact? + # Does a sound file exist for this particular contact? if (os.path.isfile(shared.appdata + 'sounds/' + label + '.wav') or os.path.isfile(shared.appdata + 'sounds/' + label + '.mp3')): soundFilename = shared.appdata + 'sounds/' + label + # Avoid making sounds more frequently than the threshold. + # This suppresses playing sounds repeatedly when there + # are many new messages + if (soundFilename is None and + not self.isConnectionSound(category)): + # elapsed time since the last sound was played + dt = datetime.datetime.now() - self.lastSoundTime + # suppress sounds which are more frequent than the threshold + if dt.total_seconds() < self.maxSoundFrequencySec: + play = False + if soundFilename is None: + # the sound is for an address which exists in the address book if category is self.SOUND_KNOWN: soundFilename = shared.appdata + 'sounds/known' + # the sound is for an unknown address elif category is self.SOUND_UNKNOWN: soundFilename = shared.appdata + 'sounds/unknown' + # initial connection sound elif category is self.SOUND_CONNECTED: soundFilename = shared.appdata + 'sounds/connected' + # disconnected sound elif category is self.SOUND_DISCONNECTED: soundFilename = shared.appdata + 'sounds/disconnected' + # sound when the connection status becomes green elif category is self.SOUND_CONNECTION_GREEN: - soundFilename = shared.appdata + 'sounds/green' + soundFilename = shared.appdata + 'sounds/green' + + if soundFilename is not None and play is True: + if not self.isConnectionSound(category): + # record the last time that a received message sound was played + self.lastSoundTime = datetime.datetime.now() - if soundFilename is not None: # if not wav then try mp3 format if not os.path.isfile(soundFilename + '.wav'): soundFilename = soundFilename + '.mp3' diff --git a/src/build_osx.py b/src/build_osx.py index 901d1ca6..dd3d7259 100644 --- a/src/build_osx.py +++ b/src/build_osx.py @@ -1,16 +1,80 @@ -from setuptools import setup +""" +py2app/py2exe build script for Bitmessage + +Usage (Mac OS X): + python setup.py py2app + +Usage (Windows): + python setup.py py2exe +""" + +import sys, os, shutil, re +from setuptools import setup # @UnresolvedImport + name = "Bitmessage" -version = "0.3.4" -mainscript = ["bitmessagemain.py"] +mainscript = 'bitmessagemain.py' +version = "0.3.5" + +if sys.platform == 'darwin': + extra_options = dict( + setup_requires=['py2app'], + app=[mainscript], + options=dict(py2app=dict(argv_emulation=True, + includes = ['PyQt4.QtCore','PyQt4.QtGui', 'sip', 'sqlite3'], + packages = ['bitmessageqt'], + frameworks = ['/usr/local/opt/openssl/lib/libcrypto.dylib'], + iconfile='images/bitmessage.icns', + resources=["images"])), + ) +elif sys.platform == 'win32': + extra_options = dict( + setup_requires=['py2exe'], + app=[mainscript], + ) +else: + extra_options = dict( + # Normally unix-like platforms will use "setup.py install" + # and install the main script as such + scripts=[mainscript], + ) setup( - name = name, - version = version, - app = mainscript, - setup_requires = ["py2app"], - options = dict(py2app=dict( - resources = ["images"], - iconfile = "images/bitmessage.icns" - )) + name = name, + version = version, + **extra_options ) +from distutils import dir_util +import glob + +if sys.platform == 'darwin': + resource = "dist/" + name + ".app/Contents/Resources/" + framework = "dist/" + name + ".app/Contents/Frameworks/" + + # The pyElliptive module only works with hardcoded libcrypto paths so rename it so it can actually find it. + libs = glob.glob(framework + "libcrypto*.dylib") + for lib in libs: + os.rename(lib, framework + "libcrypto.dylib") + break + + # Try to locate qt_menu + # Let's try the port version first! + if os.path.isfile("/opt/local/lib/Resources/qt_menu.nib"): + qt_menu_location = "/opt/local/lib/Resources/qt_menu.nib" + else: + # No dice? Then let's try the brew version + qt_menu_location = os.popen("find /usr/local/Cellar -name qt_menu.nib | tail -n 1").read() + qt_menu_location = re.sub('\n','', qt_menu_location) + + if(len(qt_menu_location) == 0): + print "Sorry couldn't find your qt_menu.nib this probably won't work" + else: + print "Found your qib: " + qt_menu_location + + # Need to include a copy of qt_menu.nib + shutil.copytree(qt_menu_location, resource + "qt_menu.nib") + # Need to touch qt.conf to avoid loading 2 sets of Qt libraries + fname = resource + "qt.conf" + with file(fname, 'a'): + os.utime(fname, None) + diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 56f23859..2ba34eeb 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -271,99 +271,172 @@ class singleWorker(threading.Thread): fromaddress, subject, body, ackdata = row status, addressVersionNumber, streamNumber, ripe = decodeAddress( fromaddress) - if addressVersionNumber <= 1: + """if addressVersionNumber == 2 and int(time.time()) < shared.encryptedBroadcastSwitchoverTime: + # We need to convert our private keys to public keys in order + # to include them. + try: + privSigningKeyBase58 = shared.config.get( + fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get( + fromaddress, 'privencryptionkey') + except: + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) + continue + + privSigningKeyHex = shared.decodeWalletImportFormat( + privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat( + privEncryptionKeyBase58).encode('hex') + + pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode( + 'hex') # At this time these pubkeys are 65 bytes long because they include the encoding byte which we won't be sending in the broadcast message. + pubEncryptionKey = highlevelcrypto.privToPub( + privEncryptionKeyHex).decode('hex') + + payload = pack('>Q', (int(time.time()) + random.randrange( + -300, 300))) # the current time plus or minus five minutes + payload += encodeVarint(1) # broadcast version + payload += encodeVarint(addressVersionNumber) + payload += encodeVarint(streamNumber) + payload += '\x00\x00\x00\x01' # behavior bitfield + payload += pubSigningKey[1:] + payload += pubEncryptionKey[1:] + payload += ripe + payload += '\x02' # message encoding type + payload += encodeVarint(len( + 'Subject:' + subject + '\n' + 'Body:' + body)) # Type 2 is simple UTF-8 message encoding. + payload += 'Subject:' + subject + '\n' + 'Body:' + body + + signature = highlevelcrypto.sign(payload, privSigningKeyHex) + payload += encodeVarint(len(signature)) + payload += signature + + target = 2 ** 64 / ((len( + payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + print '(For broadcast message) Doing proof of work...' + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr.translateText("MainWindow", "Doing work necessary to send broadcast...")))) + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + print '(For broadcast message) Found proof of work', trialValue, 'Nonce:', nonce + + payload = pack('>Q', nonce) + payload + + inventoryHash = calculateInventoryHash(payload) + objectType = 'broadcast' + shared.inventory[inventoryHash] = ( + objectType, streamNumber, payload, int(time.time())) + print 'Broadcasting inv for my broadcast (within sendBroadcast function):', inventoryHash.encode('hex') + shared.broadcastToSendDataQueues(( + streamNumber, 'sendinv', inventoryHash)) + + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Broadcast sent on %1").arg(unicode( + strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) + + # Update the status of the message in the 'sent' table to have + # a 'broadcastsent' status + shared.sqlLock.acquire() + t = ('broadcastsent', int( + time.time()), fromaddress, subject, body, 'broadcastqueued') + shared.sqlSubmitQueue.put( + 'UPDATE sent SET status=?, lastactiontime=? WHERE fromaddress=? AND subject=? AND message=? AND status=?') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release()""" + if addressVersionNumber == 2 or addressVersionNumber == 3: + # We need to convert our private keys to public keys in order + # to include them. + try: + privSigningKeyBase58 = shared.config.get( + fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get( + fromaddress, 'privencryptionkey') + except: + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) + continue + + privSigningKeyHex = shared.decodeWalletImportFormat( + privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat( + privEncryptionKeyBase58).encode('hex') + + pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode( + 'hex') # At this time these pubkeys are 65 bytes long because they include the encoding byte which we won't be sending in the broadcast message. + pubEncryptionKey = highlevelcrypto.privToPub( + privEncryptionKeyHex).decode('hex') + + payload = pack('>Q', (int(time.time()) + random.randrange( + -300, 300))) # the current time plus or minus five minutes + payload += encodeVarint(2) # broadcast version + payload += encodeVarint(streamNumber) + + dataToEncrypt = encodeVarint(2) # broadcast version + dataToEncrypt += encodeVarint(addressVersionNumber) + dataToEncrypt += encodeVarint(streamNumber) + dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield + dataToEncrypt += pubSigningKey[1:] + dataToEncrypt += pubEncryptionKey[1:] + if addressVersionNumber >= 3: + dataToEncrypt += encodeVarint(shared.config.getint(fromaddress,'noncetrialsperbyte')) + dataToEncrypt += encodeVarint(shared.config.getint(fromaddress,'payloadlengthextrabytes')) + dataToEncrypt += '\x02' # message encoding type + dataToEncrypt += encodeVarint(len('Subject:' + subject + '\n' + 'Body:' + body)) #Type 2 is simple UTF-8 message encoding per the documentation on the wiki. + dataToEncrypt += 'Subject:' + subject + '\n' + 'Body:' + body + signature = highlevelcrypto.sign( + dataToEncrypt, privSigningKeyHex) + dataToEncrypt += encodeVarint(len(signature)) + dataToEncrypt += signature + + # Encrypt the broadcast with the information contained in the broadcaster's address. Anyone who knows the address can generate + # the private encryption key to decrypt the broadcast. This provides virtually no privacy; its purpose is to keep questionable + # and illegal content from flowing through the Internet connections and being stored on the disk of 3rd parties. + privEncryptionKey = hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()[:32] + pubEncryptionKey = pointMult(privEncryptionKey) + payload += highlevelcrypto.encrypt( + dataToEncrypt, pubEncryptionKey.encode('hex')) + + target = 2 ** 64 / ((len( + payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) + print '(For broadcast message) Doing proof of work...' + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr.translateText("MainWindow", "Doing work necessary to send broadcast...")))) + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + print '(For broadcast message) Found proof of work', trialValue, 'Nonce:', nonce + + payload = pack('>Q', nonce) + payload + + inventoryHash = calculateInventoryHash(payload) + objectType = 'broadcast' + shared.inventory[inventoryHash] = ( + objectType, streamNumber, payload, int(time.time())) + print 'sending inv (within sendBroadcast function)' + shared.broadcastToSendDataQueues(( + streamNumber, 'sendinv', inventoryHash)) + + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Broadcast sent on %1").arg(unicode( + strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) + + # Update the status of the message in the 'sent' table to have + # a 'broadcastsent' status + shared.sqlLock.acquire() + t = (inventoryHash,'broadcastsent', int( + time.time()), fromaddress, subject, body, 'broadcastqueued') + shared.sqlSubmitQueue.put( + 'UPDATE sent SET msgid=?, status=?, lastactiontime=? WHERE fromaddress=? AND subject=? AND message=? AND status=?') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlSubmitQueue.put('commit') + shared.sqlLock.release() + else: with shared.printLock: sys.stderr.write( 'Error: In the singleWorker thread, the sendBroadcast function doesn\'t understand the address version.\n') - return - # We need to convert our private keys to public keys in order - # to include them. - try: - privSigningKeyBase58 = shared.config.get( - fromaddress, 'privsigningkey') - privEncryptionKeyBase58 = shared.config.get( - fromaddress, 'privencryptionkey') - except: - shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( - ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) - continue - privSigningKeyHex = shared.decodeWalletImportFormat( - privSigningKeyBase58).encode('hex') - privEncryptionKeyHex = shared.decodeWalletImportFormat( - privEncryptionKeyBase58).encode('hex') - - pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode( - 'hex') # At this time these pubkeys are 65 bytes long because they include the encoding byte which we won't be sending in the broadcast message. - pubEncryptionKey = highlevelcrypto.privToPub( - privEncryptionKeyHex).decode('hex') - - payload = pack('>Q', (int(time.time()) + random.randrange( - -300, 300))) # the current time plus or minus five minutes - payload += encodeVarint(2) # broadcast version - payload += encodeVarint(streamNumber) - - dataToEncrypt = encodeVarint(2) # broadcast version - dataToEncrypt += encodeVarint(addressVersionNumber) - dataToEncrypt += encodeVarint(streamNumber) - dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield - dataToEncrypt += pubSigningKey[1:] - dataToEncrypt += pubEncryptionKey[1:] - if addressVersionNumber >= 3: - dataToEncrypt += encodeVarint(shared.config.getint(fromaddress,'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(shared.config.getint(fromaddress,'payloadlengthextrabytes')) - dataToEncrypt += '\x02' # message encoding type - dataToEncrypt += encodeVarint(len('Subject:' + subject + '\n' + 'Body:' + body)) #Type 2 is simple UTF-8 message encoding per the documentation on the wiki. - dataToEncrypt += 'Subject:' + subject + '\n' + 'Body:' + body - signature = highlevelcrypto.sign( - dataToEncrypt, privSigningKeyHex) - dataToEncrypt += encodeVarint(len(signature)) - dataToEncrypt += signature - - # Encrypt the broadcast with the information contained in the broadcaster's address. Anyone who knows the address can generate - # the private encryption key to decrypt the broadcast. This provides virtually no privacy; its purpose is to keep questionable - # and illegal content from flowing through the Internet connections and being stored on the disk of 3rd parties. - privEncryptionKey = hashlib.sha512(encodeVarint( - addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()[:32] - pubEncryptionKey = pointMult(privEncryptionKey) - payload += highlevelcrypto.encrypt( - dataToEncrypt, pubEncryptionKey.encode('hex')) - - target = 2 ** 64 / ((len( - payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) - print '(For broadcast message) Doing proof of work...' - shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( - ackdata, tr.translateText("MainWindow", "Doing work necessary to send broadcast...")))) - initialHash = hashlib.sha512(payload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) - print '(For broadcast message) Found proof of work', trialValue, 'Nonce:', nonce - - payload = pack('>Q', nonce) + payload - - inventoryHash = calculateInventoryHash(payload) - objectType = 'broadcast' - shared.inventory[inventoryHash] = ( - objectType, streamNumber, payload, int(time.time())) - with shared.printLock: - print 'sending inv (within sendBroadcast function) for object:', inventoryHash.encode('hex') - shared.broadcastToSendDataQueues(( - streamNumber, 'sendinv', inventoryHash)) - - shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Broadcast sent on %1").arg(unicode( - strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) - - # Update the status of the message in the 'sent' table to have - # a 'broadcastsent' status - shared.sqlLock.acquire() - t = (inventoryHash,'broadcastsent', int( - time.time()), ackdata) - shared.sqlSubmitQueue.put( - 'UPDATE sent SET msgid=?, status=?, lastactiontime=? WHERE ackdata=?') - shared.sqlSubmitQueue.put(t) - queryreturn = shared.sqlReturnQueue.get() - shared.sqlSubmitQueue.put('commit') - shared.sqlLock.release() - def sendMsg(self): # Check to see if there are any messages queued to be sent