SMTP Engine in Python


Coding , ,



Vor kurzem war ich dabei einen Contentfilter für Postfix zu schreiben. Einfache Contentfilter lassen sich ja sogar mit Shell Scripts machen, diese werden dann aber per Pipe aufgerufen, was mir nicht wirklich gefällt…

Viel interessanter ist es einen Contentfilter per spawn einzubinden, was ein Program startet und es an einen Socket bindet, etwa so wie inetd. Die Kommunikation zwischen Postfix und dem Contentfilter läuft dann per STDIN bzw. STDOUT…

Der Contentfilter muss aber SMTP sprechen können, da Postfix per STDIN Smtp Kommandos schickt. Somit musste ich eine SMTPD Klasse in Python schreiben. Da es allgemein wenig Dokumentiert ist, wie so etwas programmiert werden kann, stelle ich hier die Basis-Klasse zur Verfügung.

import sys, string, os
 
#---------------------------------------------------------------------------------------------
class SMTPServerEngine:
	ST_INIT = 0
	ST_HELO = 1
	ST_MAIL = 2
	ST_RCPT = 3
	ST_DATA = 4
	ST_QUIT = 5
 
	def __init__(self):
		self.state = SMTPServerEngine.ST_INIT
 
	def chug(self):
		while 1:
			data = ''
			completeLine = 0
			while not completeLine:
				lump = os.read(sys.stdin.fileno(), 1024)
				if len(lump):
					data += lump
					if (len(data) >= 2) and data[-2:] == '\r\n':
						completeLine = 1
						if self.state != SMTPServerEngine.ST_DATA:
							rsp, keep = self.doCommand(data)
						else:
							rsp = self.doData(data)
 
						if rsp == None:
							continue
 
						sys.stdout.write(rsp + "\r\n")
						sys.stdout.flush()
 
						if keep == 0:	
							sys.exit(0)
							return	
				else:
					return
 
		return
 
	def doCommand(self, data):
		"""Process a single SMTP Command"""
		cmd = data[0:4]
		cmd = string.upper(cmd)
		keep = 1
		if cmd == "HELO":
			self.state = SMTPServerEngine.ST_HELO
		elif cmd == "RSET":
			self.dataAccum = ""
			self.state = SMTPServerEngine.ST_INIT
		elif cmd == "NOOP":
			pass
		elif cmd == "QUIT":
			keep = 0
		elif cmd == "MAIL":
			if self.state != SMTPServerEngine.ST_HELO:
				return ("503 Bad command sequence", 1)
			self.state = SMTPServerEngine.ST_MAIL
		elif cmd == "RCPT":
			if (self.state != SMTPServerEngine.ST_MAIL) and (self.state != SMTPServerEngine.ST_RCPT):
				return ("503 Bad command sequence", 1)
			self.state = SMTPServerEngine.ST_RCPT
		elif cmd == "DATA":
			if self.state != SMTPServerEngine.ST_RCPT:
				return ("503 Bad command sequence", 1)
			self.state = SMTPServerEngine.ST_DATA
			self.dataAccum = ""
			return ("354 OK, Enter data, terminated with a \\r\\n.\\r\\n", 1)
		else:
			return ("505 Eh? WTF was that?", 1)
 
		return("250 OK", keep)
 
	def doData(self, data):
		"""
		Process SMTP Data. Accumulates client DATA until the
		terminator is found.
		"""
		self.dataAccum = self.dataAccum + data
		if len(self.dataAccum) > 4 and self.dataAccum[-5:] == '\r\n.\r\n':
			self.dataAccum = self.dataAccum[:-5]
			self.state = SMTPServerEngine.ST_HELO
 
			return "250 OK - message accepted"
		else:
			return None
 
 
#---------------------------------------------------------------------------------------------
if __name__ == '__main__':
	try:
		sys.stdout.write("220 localhost\r\n")
		sys.stdout.flush()
		while 1:
			engine = SMTPServerEngine()
			engine.chug()
	except SystemExit:
		pass


Kommentar hinzufügen