#!/usr/bin/env python """ procky.py - An SMTP Proxy License: GPL 2; share and enjoy! Author: Sean B. Palmer Usage: ./procky.py SMTP Specification: http://www.ietf.org/rfc/rfc821.txt Procky is to be found at: http://inamidst.com/misc/procky.py """ import sys, os, socket, smtplib bufsiz = 1024 smtpserver = os.environ.get('SMTP_SERVER') class Proxy(object): def __init__(self, sock): self.sock = sock self.state = None self.sender = '' self.recipients = [] self.exit = False def sendline(self, line): print '<-', '%r' % line self.sock.send('%s\r\n' % line) def run(self): domain, port = self.sock.getsockname() self.sendline('220 %s Service ready' % domain) while True: input = self.sock.recv(bufsiz) if not input: break print '->', '%r' % input if self.state == 'DATA': response = self.body(input) if response == 'QUIT': break continue command = input[:4].upper() # The following are RFC 821 4.5.1's minimum requirement commands = {'HELO': self.helo, 'MAIL': self.mail, 'RCPT': self.rcpt, 'DATA': self.data, 'RSET': self.rset, 'NOOP': self.noop, 'QUIT': self.quit} if commands.has_key(command): response = commands[command](input) if response == 'QUIT': break else: self.sendline('500 Syntax error, command unrecognized') def helo(self, input): self.state = 'HELO' self.sendline('250 OK') def mail(self, input): if self.state != 'HELO': self.sendline('503 Bad sequence of commands') return self.state = 'MAIL' i, j = input.find('<'), input.rfind('>') self.sender = input[(i + 1):j] self.sendline('250 OK') def rcpt(self, input): if self.state not in ('MAIL', 'RCPT'): self.sendline('503 Bad sequence of commands') return self.state = 'RCPT' self.recipients.append(input) self.sendline('250 OK') def data(self, input): if self.state != 'RCPT': self.sendline('503 Bad sequence of commands') return self.state = 'DATA' self.mailbody = '' self.sendline('354 Start mail input; end with .') def body(self, input): self.mailbody = self.mailbody + input if self.mailbody.endswith('\r\n.\r\n'): self.mailbody = self.mailbody[:-5] sent = self.sendmail(self.mailbody) self.state = 'HELO' if not sent: self.sendline('554 Transaction failed') self.sock.close() return 'QUIT' else: self.sendline('250 OK') def sendmail(self, input): input = self.munge(input) for recipient in self.recipients: i, j = recipient.find('<'), recipient.rfind('>') sendee = recipient[(i + 1):j] if sendee == 'py@sys.exit': self.exit = True return True if not smtpserver: print >> sys.stderr, 'E: No SMTP_SERVER specified!' self.recipients = [] return False sent = True try: smtp = smtplib.SMTP(smtpserver) smtp.set_debuglevel(0) smtp.helo(smtpserver) smtp.sendmail(self.sender, sendee, input) smtp.quit() break except Exception, e: print >> sys.stderr, 'E:', e continue else: sent = False self.recipients = [] return sent def munge(self, input): return input def rset(self, input): self.mailbody = '' self.state = 'INIT' self.sendline('250 OK') def noop(self, input): self.sendline('250 OK') def quit(self, input): self.sock.close() if self.exit: sys.exit(0) return 'QUIT' def runproxy(conn): p = Proxy(conn) p.run() def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((socket.gethostname(), 25)) sock.listen(5) # 5 is usually the maximum while True: conn, address = sock.accept() runproxy(conn) if __name__=="__main__": main()