import asyncore import base64 import email # from email.parser import Parser from email.header import decode_header import re import signal import smtpd import socket import threading import time from addresses import decodeAddress from bmconfigparser import BMConfigParser from debug import logger from helper_sql import sqlExecute from helper_ackPayload import genAckPayload from helper_threading import StoppableThread from pyelliptic.openssl import OpenSSL import queues from version import softwareVersion 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' % 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" + BMConfigParser().safeGet("bitmessagesettings", "smtpdusername", "") + \ # "\x00" + BMConfigParser().safeGet("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 Exception("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) stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') ackdata = genAckPayload(streamNumber, stealthLevel) 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(BMConfigParser().getint('bitmessagesettings', 'ttl'), 86400 * 2) # not necessary to have a TTL higher than 2 days ) queues.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 BMConfigParser().addresses(): # 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 BMConfigParser().addresses(): # 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', BMConfigParser().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()