POP3 server semi-functional

This commit is contained in:
Chuck 2013-07-03 21:01:11 +07:00
parent c9bdf4d0c4
commit e5157b50b3
4 changed files with 264 additions and 4 deletions

View File

@ -24,6 +24,7 @@ from class_singleWorker import *
from class_outgoingSynSender import * from class_outgoingSynSender import *
from class_singleListener import * from class_singleListener import *
from class_addressGenerator import * from class_addressGenerator import *
from class_asyncoreThread import *
# Helper Functions # Helper Functions
import helper_startup import helper_startup
@ -731,6 +732,17 @@ if __name__ == "__main__":
singleCleanerThread.daemon = True # close the main program even if there are threads left singleCleanerThread.daemon = True # close the main program even if there are threads left
singleCleanerThread.start() singleCleanerThread.start()
# Start the SMTP server
smtpServer = bitmessageSMTPServer()
# And the POP3 server...
pop3Server = bitmessagePOP3Server(debug=True)
# And finally launch asyncore
asyncoreThread = asyncoreThread()
asyncoreThread.daemon = True
asyncoreThread.start()
shared.reloadMyAddressHashes() shared.reloadMyAddressHashes()
shared.reloadBroadcastSendersForWhichImWatching() shared.reloadBroadcastSendersForWhichImWatching()

View File

@ -1,12 +1,16 @@
from collections import deque
from pyelliptic.openssl import OpenSSL from pyelliptic.openssl import OpenSSL
import asyncore import asyncore
import shared import shared
import smtpd import smtpd
import socket
import sys
import threading import threading
import time import time
import traceback import traceback
from addresses import * from addresses import *
import helper_inbox
import helper_sent import helper_sent
class bitmessageSMTPServer(smtpd.SMTPServer): class bitmessageSMTPServer(smtpd.SMTPServer):
@ -41,13 +45,13 @@ class bitmessageSMTPServer(smtpd.SMTPServer):
status, addressVersionNumber, streamNumber, fromRipe = decodeAddress(fromAddress) status, addressVersionNumber, streamNumber, fromRipe = decodeAddress(fromAddress)
if status != 'success': if status != 'success':
shared.printLock.acquire() shared.printLock.acquire()
print 'Error: Could not decode address: ' + toAddress + ' : ' + status print 'Error: Could not decode address: ' + fromAddress + ' : ' + status
if status == 'checksumfailed': if status == 'checksumfailed':
print 'Error: Checksum failed for address: ' + toAddress print 'Error: Checksum failed for address: ' + fromAddress
if status == 'invalidcharacters': if status == 'invalidcharacters':
print 'Error: Invalid characters in address: ' + toAddress print 'Error: Invalid characters in address: ' + fromAddress
if status == 'versiontoohigh': if status == 'versiontoohigh':
print 'Error: Address version number too high (or zero) in address: ' + toAddress print 'Error: Address version number too high (or zero) in address: ' + fromAddress
shared.printLock.release() shared.printLock.release()
raise Exception("Invalid Bitmessage address: {}".format(fromAddress)) raise Exception("Invalid Bitmessage address: {}".format(fromAddress))
#fromAddress = addBMIfNotPresent(fromAddress) # I know there's a BM-, because it's required when using SMTP #fromAddress = addBMIfNotPresent(fromAddress) # I know there's a BM-, because it's required when using SMTP
@ -125,6 +129,248 @@ class bitmessageSMTPServer(smtpd.SMTPServer):
# TODO - what should we do with ackdata.encode('hex') ? # TODO - what should we do with ackdata.encode('hex') ?
class bitmessagePOP3Connection(asyncore.dispatcher):
END = b"\r\n"
def __init__(self, sock, peer_address, debug=False):
asyncore.dispatcher.__init__(self, sock)
self.peer_address = peer_address
self.data_buffer = []
self.commands = deque()
self.debug = debug
self.dispatch = dict(
USER=self.handleUser,
PASS=self.handlePass,
STAT=self.handleStat,
LIST=self.handleList,
#TOP=self.handleTop,
RETR=self.handleRetr,
DELE=self.handleDele,
NOOP=self.handleNoop,
QUIT=self.handleQuit,
)
#self.bitmessage = Bitmessage()
self.messages = None #TODO self.bitmessage.getInboxMessagesByAddress(BITMAIL_ADDRESS)
self.storage_size = 0
self.address = None
#for msg in self.messages:
# msg['message'] = base64.b64decode(msg['message'].encode('ascii'))
# msg['subject'] = base64.b64decode(msg['subject'].encode('ascii'))
# msg['size'] = len(msg['message'])
# self.storage_size += msg['size']
#print(self.messages)
self.sendline("+OK Bitmessage POP3 server ready")
def populateMessageIndex(self):
if self.address is None:
raise Exception("Invalid address: {}".format(self.address))
if self.messages is not None:
return
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(v)
queryreturn = shared.sqlReturnQueue.get()
shared.sqlLock.release()
self.storage_size = 0
self.messages = []
for row in queryreturn:
msgid, fromAddress, subject, size = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
if subject.startswith("<Bitmessage Mail: ") and subject[-1] == '>':
subject = "<Bitmessage Mail: 00000000000000000000>" # Reserved, flags.
flags = subject[-21:-1]
# TODO - checksum?
self.messages.append({
'msgid': msgid,
'fromAddress': fromAddress,
'subject': subject,
'size': size,
})
self.storage_size += size
def getMessageContent(self, msgid):
if self.address is None:
raise Exception("Invalid address: {}".format(self.address))
v = (msgid,)
shared.sqlLock.acquire()
shared.sqlSubmitQueue.put('''SELECT fromaddress, received, message, encodingtype FROM inbox WHERE msgid=?''')
shared.sqlSubmitQueue.put(v)
queryreturn = shared.sqlReturnQueue.get()
shared.sqlLock.release()
for row in queryreturn:
fromAddress, received, message, encodingtype = row
message = shared.fixPotentiallyInvalidUTF8Data(message)
return {
'fromAddress': fromAddress,
'received': received,
'message': message,
'encodingtype': encodingtype
}
def trashMessage(self, msgid):
# TODO - how to determine if the update succeeded?
if not self.debug:
helper_inbox.trash(msgid)
return True
def sendline(self, data, END=END):
if self.debug:
shared.printLock.acquire()
sys.stdout.write("sending ")
sys.stdout.write(data)
sys.stdout.write("\n")
shared.printLock.release()
data = data + END
while len(data) > 4096:
self.send(data[:4096])
data = data[4096:]
if len(data):
self.send(data)
def handle_read(self):
chunk = self.recv(4096)
while bitmessagePOP3Connection.END in chunk:
# Join all the data up to the END and throw it in commands
command = b''.join(self.data_buffer) + chunk[:chunk.index(bitmessagePOP3Connection.END)]
chunk = chunk[chunk.index(bitmessagePOP3Connection.END)+2:]
self.data_buffer = []
self.commands.append(command)
if len(chunk):
self.data_buffer.append(chunk)
if self.debug:
shared.printLock.acquire()
print('data_buffer', self.data_buffer)
print('commands', self.commands)
print('-')
shared.printLock.release()
while len(self.commands):
line = self.commands.popleft()
if b' ' in line:
cmd, data = line.split(b' ', 1)
else:
cmd, data = line, b''
try:
cmd = self.dispatch[cmd.decode('ascii').upper()]
except KeyError:
self.sendline('-ERR unknown command')
continue
for response in cmd(data):
self.sendline(response)
if cmd is self.handleQuit:
self.close()
break
def handleUser(self, data):
self.address = data
status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.address)
if status != 'success':
shared.printLock.acquire()
print 'Error: Could not decode address: ' + self.address + ' : ' + status
if status == 'checksumfailed':
print 'Error: Checksum failed for address: ' + self.address
if status == 'invalidcharacters':
print 'Error: Invalid characters in address: ' + self.address
if status == 'versiontoohigh':
print 'Error: Address version number too high (or zero) in address: ' + self.address
shared.printLock.release()
raise Exception("Invalid Bitmessage address: {}".format(self.address))
return ["+OK user accepted"]
def handlePass(self, data):
# TODO
return ["+OK pass accepted"]
def handleStat(self, data):
self.populateMessageIndex()
return ["+OK {} {}".format(len(self.messages), self.storage_size)]
def handleList(self, data):
self.populateMessageIndex()
yield "+OK {} messages ({} octets)".format(len(self.messages), self.storage_size)
for i, msg in enumerate(self.messages):
yield "{} {}".format(i + 1, msg['size'])
yield "."
#def handleTop(self, data):
# cmd, num, lines = data.split()
# assert num == "1", "unknown message number: {}".format(num)
# lines = int(lines)
# text = msg.top + "\r\n\r\n" + "\r\n".join(msg.bot[:lines])
# return "+OK top of message follows\r\n{}\r\n.".format(text)
def handleRetr(self, data):
index = int(data.decode('ascii')) - 1
assert index >= 0
self.populateMessageIndex()
msg = self.messages[index]
content = self.getMessageContent(msg['msgid'])
if self.debug:
shared.printLock.acquire()
sys.stdout.write(str(msg) + ": " + str(content))
shared.printLock.release()
yield "+OK {} octets".format(msg['size'])
yield content['message']
yield '.'
def handleDele(self, data):
index = int(data.decode('ascii')) - 1
assert index >= 0
self.populateMessageIndex()
msg = self.messages[index]
if self.trashMessage(msg['msgid']):
return ["+OK deleted"]
else:
return ["-ERR internal error"]
def handleNoop(self, data):
return ["+OK"]
def handleQuit(self, data):
return ["+OK Bitmessage POP3 server signing off"]
class bitmessagePOP3Server(asyncore.dispatcher):
def __init__(self, debug=False):
asyncore.dispatcher.__init__(self)
self.debug = debug
pop3port = shared.config.getint('bitmessagesettings', 'pop3port')
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(('127.0.0.1', pop3port))
self.listen(10)
shared.printLock.acquire()
print "POP3 server started"
shared.printLock.release()
def handle_accept(self):
sock, peer_address = self.accept()
_ = bitmessagePOP3Connection(sock, peer_address, debug=self.debug)
class asyncoreThread(threading.Thread): class asyncoreThread(threading.Thread):
def __init__(self): def __init__(self):

View File

@ -268,6 +268,7 @@ class sqlThread(threading.Thread):
item) + '" Here are the parameters; you might want to censor this data with asterisks (***) as it can contain private information: ' + str(repr(parameters)) + '\nHere is the actual error message thrown by the sqlThread: ' + str(err) + '\n') item) + '" Here are the parameters; you might want to censor this data with asterisks (***) as it can contain private information: ' + str(repr(parameters)) + '\nHere is the actual error message thrown by the sqlThread: ' + str(err) + '\n')
sys.stderr.write('This program shall now abruptly exit!\n') sys.stderr.write('This program shall now abruptly exit!\n')
shared.printLock.release() shared.printLock.release()
raw_input('Press enter to exit...')
os._exit(0) os._exit(0)
shared.sqlReturnQueue.put(self.cur.fetchall()) shared.sqlReturnQueue.put(self.cur.fetchall())

View File

@ -28,6 +28,7 @@ def loadConfig():
shared.config.add_section('bitmessagesettings') shared.config.add_section('bitmessagesettings')
shared.config.set('bitmessagesettings', 'settingsversion', '6') shared.config.set('bitmessagesettings', 'settingsversion', '6')
shared.config.set('bitmessagesettings', 'port', '8444') shared.config.set('bitmessagesettings', 'port', '8444')
shared.config.set('bitmessagesettings', 'smtpport', '10025')
shared.config.set( shared.config.set(
'bitmessagesettings', 'timeformat', '%%a, %%d %%b %%Y %%I:%%M %%p') 'bitmessagesettings', 'timeformat', '%%a, %%d %%b %%Y %%I:%%M %%p')
shared.config.set('bitmessagesettings', 'blackwhitelist', 'black') shared.config.set('bitmessagesettings', 'blackwhitelist', 'black')