Add SMTP server
- you can now use SMTP to send messages - uses bmaddr.lan domain - runs on 127.0.0.8425 if you set "smtpd" to True - mandatory authentication with smtpdusername and smtpdpassword
This commit is contained in:
parent
8e066eaa97
commit
36a04fd7d9
|
@ -39,6 +39,7 @@ from class_singleListener import singleListener
|
||||||
from class_singleWorker import singleWorker
|
from class_singleWorker import singleWorker
|
||||||
from class_addressGenerator import addressGenerator
|
from class_addressGenerator import addressGenerator
|
||||||
from class_smtpDeliver import smtpDeliver
|
from class_smtpDeliver import smtpDeliver
|
||||||
|
from class_smtpServer import smtpServer
|
||||||
from debug import logger
|
from debug import logger
|
||||||
|
|
||||||
# Helper Functions
|
# Helper Functions
|
||||||
|
@ -191,6 +192,11 @@ class Main:
|
||||||
smtpDeliveryThread = smtpDeliver()
|
smtpDeliveryThread = smtpDeliver()
|
||||||
smtpDeliveryThread.start()
|
smtpDeliveryThread.start()
|
||||||
|
|
||||||
|
# SMTP daemon thread
|
||||||
|
if daemon and shared.safeConfigGetBoolean("bitmessagesettings", "smtpd"):
|
||||||
|
smtpServerThread = smtpServer()
|
||||||
|
smtpServerThread.start()
|
||||||
|
|
||||||
# Start the thread that calculates POWs
|
# Start the thread that calculates POWs
|
||||||
objectProcessorThread = objectProcessor()
|
objectProcessorThread = objectProcessor()
|
||||||
objectProcessorThread.daemon = False # DON'T close the main program even the thread remains. This thread checks the shutdown variable after processing each object.
|
objectProcessorThread.daemon = False # DON'T close the main program even the thread remains. This thread checks the shutdown variable after processing each object.
|
||||||
|
|
195
src/class_smtpServer.py
Normal file
195
src/class_smtpServer.py
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
import base64
|
||||||
|
import email
|
||||||
|
from email.parser import Parser
|
||||||
|
from email.header import decode_header
|
||||||
|
import re
|
||||||
|
import signal
|
||||||
|
import smtpd
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from addresses import decodeAddress
|
||||||
|
from debug import logger
|
||||||
|
from helper_sql import sqlExecute
|
||||||
|
from helper_threading import StoppableThread
|
||||||
|
from pyelliptic.openssl import OpenSSL
|
||||||
|
import shared
|
||||||
|
|
||||||
|
SMTPDOMAIN = "bmaddr.lan"
|
||||||
|
LISTENPORT = 8425
|
||||||
|
|
||||||
|
class smtpServerChannel(smtpd.SMTPChannel):
|
||||||
|
def smtp_EHLO(self, arg):
|
||||||
|
if not arg:
|
||||||
|
self.push('501 Syntax: HELO hostname')
|
||||||
|
return
|
||||||
|
self.push('250-PyBitmessage %s' % shared.softwareVersion)
|
||||||
|
self.push('250 AUTH PLAIN')
|
||||||
|
|
||||||
|
def smtp_AUTH(self, arg):
|
||||||
|
if not arg or arg[0:5] not in ["PLAIN"]:
|
||||||
|
self.push('501 Syntax: AUTH PLAIN')
|
||||||
|
return
|
||||||
|
authstring = arg[6:]
|
||||||
|
try:
|
||||||
|
decoded = base64.b64decode(authstring)
|
||||||
|
correctauth = "\x00" + shared.safeConfigGet("bitmessagesettings", "smtpdusername", "") + \
|
||||||
|
"\x00" + shared.safeConfigGet("bitmessagesettings", "smtpdpassword", "")
|
||||||
|
logger.debug("authstring: %s / %s", correctauth, decoded)
|
||||||
|
if correctauth == decoded:
|
||||||
|
self.auth = True
|
||||||
|
self.push('235 2.7.0 Authentication successful')
|
||||||
|
else:
|
||||||
|
raise Error("Auth fail")
|
||||||
|
except:
|
||||||
|
self.push('501 Authentication fail')
|
||||||
|
|
||||||
|
def smtp_DATA(self, arg):
|
||||||
|
if not hasattr(self, "auth") or not self.auth:
|
||||||
|
self.push ("530 Authentication required")
|
||||||
|
return
|
||||||
|
smtpd.SMTPChannel.smtp_DATA(self, arg)
|
||||||
|
|
||||||
|
|
||||||
|
class smtpServerPyBitmessage(smtpd.SMTPServer):
|
||||||
|
def handle_accept(self):
|
||||||
|
pair = self.accept()
|
||||||
|
if pair is not None:
|
||||||
|
conn, addr = pair
|
||||||
|
# print >> DEBUGSTREAM, 'Incoming connection from %s' % repr(addr)
|
||||||
|
self.channel = smtpServerChannel(self, conn, addr)
|
||||||
|
|
||||||
|
def send(self, fromAddress, toAddress, subject, message):
|
||||||
|
status, addressVersionNumber, streamNumber, ripe = decodeAddress(toAddress)
|
||||||
|
ackdata = OpenSSL.rand(32)
|
||||||
|
t = ()
|
||||||
|
sqlExecute(
|
||||||
|
'''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
|
||||||
|
'',
|
||||||
|
toAddress,
|
||||||
|
ripe,
|
||||||
|
fromAddress,
|
||||||
|
subject,
|
||||||
|
message,
|
||||||
|
ackdata,
|
||||||
|
int(time.time()), # sentTime (this will never change)
|
||||||
|
int(time.time()), # lastActionTime
|
||||||
|
0, # sleepTill time. This will get set when the POW gets done.
|
||||||
|
'msgqueued',
|
||||||
|
0, # retryNumber
|
||||||
|
'sent', # folder
|
||||||
|
2, # encodingtype
|
||||||
|
min(shared.config.getint('bitmessagesettings', 'ttl'), 86400 * 2) # not necessary to have a TTL higher than 2 days
|
||||||
|
)
|
||||||
|
|
||||||
|
shared.workerQueue.put(('sendmessage', toAddress))
|
||||||
|
|
||||||
|
def decode_header(self, hdr):
|
||||||
|
ret = []
|
||||||
|
for h in decode_header(self.msg_headers[hdr]):
|
||||||
|
if h[1]:
|
||||||
|
ret.append(unicode(h[0], h[1]))
|
||||||
|
else:
|
||||||
|
ret.append(h[0].decode("utf-8", errors='replace'))
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def process_message(self, peer, mailfrom, rcpttos, data):
|
||||||
|
# print 'Receiving message from:', peer
|
||||||
|
p = re.compile(".*<([^>]+)>")
|
||||||
|
if not hasattr(self.channel, "auth") or not self.channel.auth:
|
||||||
|
logger.error("Missing or invalid auth")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.msg_headers = Parser().parsestr(data)
|
||||||
|
except:
|
||||||
|
logger.error("Invalid headers")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
sender, domain = p.sub(r'\1', mailfrom).split("@")
|
||||||
|
if domain != SMTPDOMAIN:
|
||||||
|
raise Exception("Bad domain %s", domain)
|
||||||
|
if sender not in shared.config.sections():
|
||||||
|
raise Exception("Nonexisting user %s", sender)
|
||||||
|
except Exception as err:
|
||||||
|
logger.debug("Bad envelope from %s: %s", mailfrom, repr(err))
|
||||||
|
msg_from = self.decode_header("from")
|
||||||
|
try:
|
||||||
|
msg_from = p.sub(r'\1', self.decode_header("from")[0])
|
||||||
|
sender, domain = msg_from.split("@")
|
||||||
|
if domain != SMTPDOMAIN:
|
||||||
|
raise Exception("Bad domain %s", domain)
|
||||||
|
if sender not in shared.config.sections():
|
||||||
|
raise Exception("Nonexisting user %s", sender)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error("Bad headers from %s: %s", msg_from, repr(err))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg_subject = self.decode_header('subject')[0]
|
||||||
|
except:
|
||||||
|
msg_subject = "Subject missing..."
|
||||||
|
|
||||||
|
msg_tmp = email.message_from_string(data)
|
||||||
|
body = u''
|
||||||
|
for part in msg_tmp.walk():
|
||||||
|
if part and part.get_content_type() == "text/plain":
|
||||||
|
body += part.get_payload(decode=1).decode(part.get_content_charset('utf-8'), errors='replace')
|
||||||
|
|
||||||
|
for to in rcpttos:
|
||||||
|
try:
|
||||||
|
rcpt, domain = p.sub(r'\1', to).split("@")
|
||||||
|
if domain != SMTPDOMAIN:
|
||||||
|
raise Exception("Bad domain %s", domain)
|
||||||
|
logger.debug("Sending %s to %s about %s", sender, rcpt, msg_subject)
|
||||||
|
self.send(sender, rcpt, msg_subject, body)
|
||||||
|
logger.info("Relayed %s to %s", sender, rcpt)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error( "Bad to %s: %s", to, repr(err))
|
||||||
|
continue
|
||||||
|
return
|
||||||
|
|
||||||
|
class smtpServer(threading.Thread, StoppableThread):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
threading.Thread.__init__(self, name="smtpServerThread")
|
||||||
|
self.initStop()
|
||||||
|
self.server = smtpServerPyBitmessage(('127.0.0.1', LISTENPORT), None)
|
||||||
|
|
||||||
|
def stopThread(self):
|
||||||
|
super(smtpServer, self).stopThread()
|
||||||
|
self.server.close()
|
||||||
|
return
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
# for ip in ('127.0.0.1', shared.config.get('bitmessagesettings', 'onionbindip')):
|
||||||
|
for ip in ('127.0.0.1'):
|
||||||
|
try:
|
||||||
|
s.connect((ip, LISTENPORT))
|
||||||
|
s.shutdown(socket.SHUT_RDWR)
|
||||||
|
s.close()
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
asyncore.loop(1)
|
||||||
|
|
||||||
|
def signals(signal, frame):
|
||||||
|
print "Got signal, terminating"
|
||||||
|
for thread in threading.enumerate():
|
||||||
|
if thread.isAlive() and isinstance(thread, StoppableThread):
|
||||||
|
thread.stopThread()
|
||||||
|
|
||||||
|
def runServer():
|
||||||
|
print "Running SMTPd thread"
|
||||||
|
smtpThread = smtpServer()
|
||||||
|
smtpThread.start()
|
||||||
|
signal.signal(signal.SIGINT, signals)
|
||||||
|
signal.signal(signal.SIGTERM, signals)
|
||||||
|
print "Processing"
|
||||||
|
smtpThread.join()
|
||||||
|
print "The end"
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
runServer()
|
Reference in New Issue
Block a user