Change mail format to BMADDR@default. The word default can change. Also, get rid of the Bitmessage Mail tag in the subject line

This commit is contained in:
Chuck 2013-07-12 12:09:50 +07:00
parent 1e3cd7fc88
commit d8da925b8d
3 changed files with 112 additions and 179 deletions

View File

@ -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.

View File

@ -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('<Bitmessage Mail: ')
if i >= 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('<Bitmessage Mail: ')
if i >= 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 = "<Bitmessage Mail: 0000000000000000{:04x}>".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('<Bitmessage Mail: ')
if i >= 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 <Bitmessage Mail: ...> 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("<Bitmessage Mail: 0000000000000000{:04x}>".format(checksum), mailingListAddress)
return message_as_text, subject

View File

@ -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: <address>')
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: <user@address>')
return
try:
capitalization, address = address.split('@', 1)
localPart, dom = address.split('@', 1)
except ValueError:
self.invalid_command('501 Syntax: RCPT TO: <user@address>')
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 = "<Bitmessage Mail: 0000000000000000{:04x}>".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)