From d8da925b8d22f47755ea97f849f38a0c7b537875 Mon Sep 17 00:00:00 2001 From: Chuck Date: Fri, 12 Jul 2013 12:09:50 +0700 Subject: [PATCH] Change mail format to BMADDR@default. The word default can change. Also, get rid of the Bitmessage Mail tag in the subject line --- src/addresses.py | 20 ---- src/class_pop3Server.py | 216 ++++++++++++++++------------------------ src/class_smtpServer.py | 55 +++++----- 3 files changed, 112 insertions(+), 179 deletions(-) diff --git a/src/addresses.py b/src/addresses.py index f37a068e..a6a571f6 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -199,26 +199,6 @@ def addBMIfNotPresent(address): else: return address -def getBase58Capitaliation(address): - if address[:3] == 'BM-': - address = address[3:] - i = [] - for c in address: - i.append('1' if c.isupper() else '0') - return int(''.join(i), 2) - -def applyBase58Capitalization(address, capitalization): - address = address.lower() - if address.startswith('bm-'): - address = address[3:] - n = [] - for i, c in enumerate(address): - if (capitalization & (1 << (len(address)-i-1))) != 0: - n.append(c.upper()) - else: - n.append(c) - return 'BM-' + ''.join(n) - def addressStream(address): #returns the stream number of an address or False if there is a problem with the address. diff --git a/src/class_pop3Server.py b/src/class_pop3Server.py index 50d4b85a..c9be3aa2 100644 --- a/src/class_pop3Server.py +++ b/src/class_pop3Server.py @@ -55,7 +55,7 @@ class bitmessagePOP3Connection(asyncore.dispatcher): v = (self.address,) shared.sqlLock.acquire() # TODO LENGTH(message) needs to be the byte-length, not the character-length. - shared.sqlSubmitQueue.put('''SELECT msgid, fromaddress, subject, LENGTH(message) FROM inbox WHERE folder='inbox' AND toAddress=?''') + shared.sqlSubmitQueue.put('''SELECT msgid, fromaddress, subject, message FROM inbox WHERE folder='inbox' AND toAddress=?''') shared.sqlSubmitQueue.put(v) queryreturn = shared.sqlReturnQueue.get() shared.sqlLock.release() @@ -63,24 +63,25 @@ class bitmessagePOP3Connection(asyncore.dispatcher): self.storage_size = 0 self.messages = [] for row in queryreturn: - msgid, fromAddress, subject, size = row + msgid, fromAddress, subject, body = row subject = shared.fixPotentiallyInvalidUTF8Data(subject) - i = subject.find('= 0: - tmp = subject[i:] - i = tmp.find('>') - if i >= 0: - flags = tmp[i-21:i] # TODO - verify checksum? + body = shared.fixPotentiallyInvalidUTF8Data(body) - self.messages.append({ - 'msgid': msgid, - 'fromAddress': fromAddress, - 'subject': subject, - 'size': size, - }) + message = parser.Parser().parsestr(body) + if not ('Date' in message and 'From' in message and 'X-Bitmessage-Flags' in message): + continue - self.storage_size += size + flags = message['X-Bitmessage-Flags'] # TODO - verify checksum? + self.messages.append({ + 'msgid' : msgid, + 'fromAddress': fromAddress, + 'subject' : subject, + 'size' : len(body), + 'flags' : flags, + }) + + self.storage_size += len(body) def getMessageContent(self, msgid): if self.address is None: @@ -97,9 +98,9 @@ class bitmessagePOP3Connection(asyncore.dispatcher): fromAddress, received, message, encodingtype = row message = shared.fixPotentiallyInvalidUTF8Data(message) return { - 'fromAddress': fromAddress, - 'received': received, - 'message': message, + 'fromAddress' : fromAddress, + 'received' : received, + 'message' : message, 'encodingtype': encodingtype } @@ -165,13 +166,10 @@ class bitmessagePOP3Connection(asyncore.dispatcher): if self.loggedin: raise Exception("Cannot login twice") - if '@' not in data: - yield "-ERR access denied" - return - - capitalization, address = data.split('@', 1) - self.address = applyBase58Capitalization(address, int(capitalization)) + if '@' in data: + data, _ = data.split('@', 1) + self.address = data status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.address) if status != 'success': with shared.printLock: @@ -184,13 +182,6 @@ class bitmessagePOP3Connection(asyncore.dispatcher): print 'Error: Address version number too high (or zero) in address: ' + self.address raise Exception("Invalid Bitmessage address: {}".format(self.address)) - username = '{}@{}'.format(getBase58Capitaliation(self.address), self.address) - - # Must login with the full E-mail address and capitalization - if data != username: - yield "-ERR access denied" - return - # Each identity must be enabled independly by setting the smtppop3password for the identity # If no password is set, then the identity is not available for SMTP/POP3 access. try: @@ -295,6 +286,19 @@ class bitmessagePOP3Server(asyncore.dispatcher): sock = ssl.wrap_socket(sock, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23) _ = bitmessagePOP3Connection(sock, peer_address, debug=self.debug) + @staticmethod + def addMailingListNameToSubject(subject, mailingListName): + withoutre = subject = subject.strip() + re = '' + if subject[:3] == 'Re:' or subject[:3] == 'RE:': + re = subject[:3] + ' ' + withoutre = subject[3:].strip() + a = '[' + mailingListName + ']' + if withoutre.startswith(a): + return subject + else: + return re + a + ' ' + subject + @staticmethod def reformatMessageForReceipt(toAddress, fromAddress, body, subject, broadcast=False): originalBody = body @@ -332,23 +336,10 @@ class bitmessagePOP3Server(asyncore.dispatcher): print(message) print '--------' - i = subject.find('= 0: - tmp = subject[i:] - i = tmp.find('>') - if i >= 0: - flags = tmp[i-21:i] - checksum = int(flags[-4:], 16) - - # Checksum to make sure incoming message hasn't been tampered with - c = hashlib.sha256(body).digest()[:2] - #c = (ord(checksum[0]) << 8) | ord(checksum[1]) - print(c, checksum) - - # Valid Bitmessage subject line already - if c != checksum: - with shared.printLock: - print 'Got E-Mail formatted message with incorrect checksum...' + flags = '0' + if 'X-Bitmessage-Flags' in message: + flags = message['X-Bitmessage-Flags'] + del message['X-Bitmessage-Flags'] mailingListName = None if broadcast: @@ -366,14 +357,16 @@ class bitmessagePOP3Server(asyncore.dispatcher): if not body_is_valid: if broadcast: if ostensiblyFrom is not None: - fromLabel = '{}@{}'.format(getBase58Capitaliation(ostensiblyFrom), ostensiblyFrom) - # TODO - check address book? + fromLabel = '{} <{}@default>'.format(ostensiblyFrom, ostensiblyFrom) + # TODO - check address book for label? else: - fromLabel = '{}@{}'.format(getBase58Capitaliation(fromAddress), fromAddress) + fromLabel = '{}@default'.format(fromAddress) if mailingListName is not None: fromLabel = '{} <{}>'.format(mailingListName, fromLabel) + else: + fromLabel = '{} <{}>'.format(fromAddress, fromLabel) else: - fromLabel = '{}@{}'.format(getBase58Capitaliation(fromAddress), fromAddress) + fromLabel = '{}@default'.format(fromAddress) with shared.sqlLock: t = (fromAddress,) @@ -384,12 +377,12 @@ class bitmessagePOP3Server(asyncore.dispatcher): for row in queryreturn: fromLabel = '{} <{}>'.format(row[0], fromLabel) break + else: + fromLabel = '{} <{}>'.format(fromAddress, fromLabel) message['From'] = fromLabel message['Date'] = utils.formatdate(localtime=False) - message['X-Bitmessage-Subject'] = subject - if 'Subject' in message: if mailingListName is not None: s = message['Subject'] @@ -400,122 +393,92 @@ class bitmessagePOP3Server(asyncore.dispatcher): if broadcast: # The To: field on a broadcast is the mailing list, not you - toLabel = '{}@{}'.format(getBase58Capitaliation(fromAddress), fromAddress) + toLabel = '{}@default'.format(fromAddress) if mailingListName is not None: toLabel = '{} <{}>'.format(mailingListName, toLabel) if 'To' in message: del message['To'] message['To'] = toLabel - if 'Reply-To' not in message: + if 'Reply-To' in message: del message['Reply-To'] message['Reply-To'] = toLabel - elif 'To' not in message: - toLabel = '{}@{}'.format(getBase58Capitaliation(toAddress), toAddress) - try: - toLabel = '{} <{}>'.format(shared.config.get(toAddress, 'label'), toLabel) - except: - pass + else: + if 'To' not in message: + toLabel = '{}@default'.format(toAddress) + try: + toLabel = '{} <{}>'.format(shared.config.get(toAddress, 'label'), toLabel) + except: + pass - message['To'] = toLabel + message['To'] = toLabel # Return-Path - returnPath = "{}@{}".format(getBase58Capitaliation(fromAddress), fromAddress) + returnPath = "{}@default".format(fromAddress) message['Return-Path'] = returnPath # X-Delivered-To - deliveredTo = "{}@{}".format(getBase58Capitaliation(toAddress), toAddress) + deliveredTo = "{}@default".format(toAddress) message['X-Delivered-To'] = deliveredTo # X-Bitmessage-Receiving-Version message["X-Bitmessage-Receiving-Version"] = shared.softwareVersion + # Others... + message['X-Bitmessage-Subject'] = subject + message['X-Bitmessage-Flags'] = flags + fp = StringIO() gen = generator.Generator(fp, mangle_from_=False, maxheaderlen=128) gen.flatten(message) message_as_text = fp.getvalue() - - # Checksum to makesure incoming message hasn't been tampered with - # TODO - if subject_is_valid, then don't completely overwrite the subject, instead include all the data outside of <> too - checksum = hashlib.sha256(message_as_text).digest()[:2] - checksum = (ord(checksum[0]) << 8) | ord(checksum[1]) - subject = "".format(checksum) # Reserved flags. - return message_as_text, subject - @staticmethod - def addMailingListNameToSubject(subject, mailingListName): - withoutre = subject = subject.strip() - re = '' - if subject[:3] == 'Re:' or subject[:3] == 'RE:': - re = subject[:3] + ' ' - withoutre = subject[3:].strip() - a = '[' + mailingListName + ']' - if withoutre.startswith(a): - return subject - else: - return re + a + ' ' + subject - @staticmethod def reformatMessageForMailingList(toAddress, fromAddress, body, subject, mailingListName): message = parser.Parser().parsestr(body) with shared.printLock: print(message) - subject_is_valid = False + flags = '0' + if 'X-Bitmessage-Flags' in message: + flags = message['X-Bitmessage-Flags'] + del message['X-Bitmessage-Flags'] - i = subject.find('= 0: - tmp = subject[i:] - i = tmp.find('>') - if i >= 0: - flags = tmp[i-21:i] - checksum = int(flags[-4:], 16) - - # Checksum to make sure incoming message hasn't been tampered with - c = hashlib.sha256(body).digest()[:2] - c = (ord(checksum[0]) << 8) | ord(checksum[1]) - - # Valid Bitmessage subject line already - if c == checksum: - subject_is_valid = True - else: - with shared.printLock: - print 'Got E-Mail formatted message with incorrect checksum...' - - # The mailing list code will override some headers, including Date and From, so - # that the trust can be moved from the original sender to the mailing list owner. - fromLabel = '{}@{}'.format(getBase58Capitaliation(fromAddress), fromAddress) + # The mailing list code will override some headers, including Date and From + fromLabel = '{}@default'.format(fromAddress) if 'From' in message: originalFrom = message['From'] message['X-Original-From'] = originalFrom - i = originalFrom.find('<' + fromLabel + '>') + i = originalFrom.find('<' + fromAddress + '@') if i >= 0: fromLabel = '{} <{}>'.format(originalFrom[:i].strip(), fromLabel) + else: + fromLabel = '{} <{}>'.format(fromAddress, fromLabel) + + del message['From'] message['From'] = fromLabel - message['Date'] = utils.formatdate() + + if 'Date' in message: + del message['Date'] + + message['Date'] = utils.formatdate(localtime=False) message['X-Bitmessage-Subject'] = subject if 'Subject' not in message: - if not subject_is_valid: - message['Subject'] = bitmessagePOP3Server.addMailingListNameToSubject(subject, mailingListName) - else: - # TODO - strip from bitmessage subject? - message['Subject'] = bitmessagePOP3Server.addMailingListNameToSubject('', mailingListName) + message['Subject'] = bitmessagePOP3Server.addMailingListNameToSubject(subject, mailingListName) else: - message['Subject'] = bitmessagePOP3Server.addMailingListNameToSubject(message['Subject'], mailingListName) - - toLabel = '{}@{}'.format(getBase58Capitaliation(toAddress), toAddress) - try: - toLabel = '{} <{}>'.format(shared.config.get(toAddress, 'label'), toLabel) - except: - pass + s = message['Subject'] + del message['Subject'] + message['Subject'] = bitmessagePOP3Server.addMailingListNameToSubject(s, mailingListName) + toLabel = '"{}" <{}@default>'.format(mailingListName, toAddress) if 'To' in message: message['X-Original-To'] = message['To'] + del message['To'] message['To'] = toLabel # X-Bitmessage-MailingList-Name @@ -528,17 +491,12 @@ class bitmessagePOP3Server(asyncore.dispatcher): # X-Bitmessage-MailingList-Version message["X-Bitmessage-MailingList-Version"] = shared.softwareVersion + message["X-Bitmessage-Flags"] = flags + fp = StringIO() gen = generator.Generator(fp, mangle_from_=False, maxheaderlen=128) gen.flatten(message) message_as_text = fp.getvalue() - - # Checksum to makesure incoming message hasn't been tampered with - # TODO - if subject_is_valid, then don't completely overwrite the subject, instead include all the data outside of <> too - checksum = hashlib.sha256(message_as_text).digest()[:2] - checksum = (ord(checksum[0]) << 8) | ord(checksum[1]) - subject = bitmessagePOP3Server.addMailingListNameToSubject("".format(checksum), mailingListAddress) - return message_as_text, subject diff --git a/src/class_smtpServer.py b/src/class_smtpServer.py index 5d06e391..ffab5e00 100644 --- a/src/class_smtpServer.py +++ b/src/class_smtpServer.py @@ -172,13 +172,12 @@ class bitmessageSMTPChannel(asynchat.async_chat): self.invalid_command('501 authorization not understood') return - if '@' not in username: - self.invalid_command('530 Access denied.') - return - - capitalization, address = username.split('@', 1) - self.address = applyBase58Capitalization(address, int(capitalization)) + if '@' in username: + username, _ = username.split('@', 1) + self.address = username + with shared.printLock: + print 'Login request from {}'.format(self.address) status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.address) if status != 'success': with shared.printLock: @@ -191,13 +190,6 @@ class bitmessageSMTPChannel(asynchat.async_chat): print 'Error: Address version number too high (or zero) in address: ' + self.address raise Exception("Invalid Bitmessage address: {}".format(self.address)) - self.fullUsername = '{}@{}'.format(getBase58Capitaliation(self.address), address) - - # Must match full email address with capitalization - if username != self.fullUsername: - self.invalid_command('530 Access denied.') - return - # Each identity must be enabled independly by setting the smtppop3password for the identity # If no password is set, then the identity is not available for SMTP/POP3 access. try: @@ -242,7 +234,7 @@ class bitmessageSMTPChannel(asynchat.async_chat): print >> smtpd.DEBUGSTREAM, '===> MAIL', arg address = self.__getaddr('FROM:', arg) if arg else None - if not address: + if not address or '@' not in address: self.invalid_command('501 Syntax: MAIL FROM:
') return @@ -254,7 +246,8 @@ class bitmessageSMTPChannel(asynchat.async_chat): self.invalid_command('503 Not authenticated.') return - if address != self.fullUsername: + localPart, _ = address.split('@', 1) + if self.address != localPart: self.invalid_command('530 Access denied: address must be the same as the authorized account') return @@ -278,12 +271,15 @@ class bitmessageSMTPChannel(asynchat.async_chat): self.invalid_command('501 Syntax: RCPT TO: ') return try: - capitalization, address = address.split('@', 1) + localPart, dom = address.split('@', 1) except ValueError: self.invalid_command('501 Syntax: RCPT TO: ') return - realAddress = applyBase58Capitalization(address, int(capitalization)) - self.__rcpttos.append('{}@{}'.format(getBase58Capitaliation(realAddress), realAddress)) + status, addressVersionNumber, streamNumber, fromRipe = decodeAddress(localPart) + if status != 'success': + self.invalid_command('501 Bitmessage address is incorrect: {}'.format(status)) + return + self.__rcpttos.append(address) with shared.printLock: print >> smtpd.DEBUGSTREAM, 'recips:', self.__rcpttos self.push('250 Ok') @@ -341,17 +337,12 @@ class bitmessageSMTPServer(smtpd.SMTPServer): @staticmethod def stripMessageHeaders(message): - # Convert Date header into GMT + # Always convert Date header into GMT if 'Date' in message: oldDate = message['Date'] del message['Date'] - message['Date'] = utils.formatdate(utils.mktime_tz(utils.parsedate_tz(oldDate)), localtime=False) - with shared.printLock: - print 'old date --', oldDate - print 'new date --', message['Date'] - try: if not shared.config.getboolean('bitmessagesettings', 'stripmessageheadersenable'): return @@ -368,22 +359,24 @@ class bitmessageSMTPServer(smtpd.SMTPServer): if h in message: del message[h] - def process_message(self, peer, address, rcpttos, data): + def process_message(self, peer, fromAddress, rcpttos, data): #print("Peer", peer) - #print("Mail From", address) + #print("Mail From", fromAddress) #print("Rcpt To", rcpttos) #print("Data") #print(data) #print('--------') - #print(type(address)) + #print(type(fromAddress)) message = parser.Parser().parsestr(data) message['X-Bitmessage-Sending-Version'] = shared.softwareVersion + message['X-Bitmessage-Flags'] = '0' bitmessageSMTPServer.stripMessageHeaders(message) fp = StringIO() gen = generator.Generator(fp, mangle_from_=False, maxheaderlen=128) gen.flatten(message) + message_as_text = fp.getvalue() with shared.printLock: print(message_as_text) @@ -392,7 +385,6 @@ class bitmessageSMTPServer(smtpd.SMTPServer): checksum = (ord(checksum[0]) << 8) | ord(checksum[1]) # Determine the fromAddress and make sure it's an owned identity - fromAddress = address if not (fromAddress.startswith('BM-') and '.' not in fromAddress): raise Exception("From Address must be a Bitmessage address.") else: @@ -421,7 +413,7 @@ class bitmessageSMTPServer(smtpd.SMTPServer): raise Exception("The fromAddress is disabled: {}".format(fromAddress)) for recipient in rcpttos: - _, toAddress = recipient.split('@', 1) + toAddress, _ = recipient.split('@', 1) if not (toAddress.startswith('BM-') and '.' not in toAddress): # TODO - deliver message to another SMTP server.. # I think this feature would urge adoption: the ability to use the same bitmessage address @@ -461,7 +453,10 @@ class bitmessageSMTPServer(smtpd.SMTPServer): # The subject is specially formatted to identify it from non-E-mail messages. # TODO - The bitfield will be used to convey things like external attachments, etc. # Last 2 bytes are two bytes of the sha256 checksum of message - subject = "".format(checksum) # Reserved flags. + if 'Subject' in message: + subject = message['Subject'] + else: + subject = '' ackdata = OpenSSL.rand(32) t = ('', toAddress, toRipe, fromAddress, subject, message_as_text, ackdata, int(time.time()), 'msgqueued', 1, 1, 'sent', 2)