#!/usr/bin/env python # encoding: utf-8 """ MailServer.py Created by Rui Carmo on 2007-04-21. Published under the MIT license. """ import sys, asyncore, asynchat, threading, socket import smtpd, email, mimetypes import base64, time, StringIO #import Store NEWLINE = '\n' EMPTYSTRING = '' COMMASPACE = ', ' __version__ = 'Yaki SMTP Mail Listener' class SMTPAuthChannel(asynchat.async_chat): """ Replace SMTPChannel completely for future expansion """ COMMAND = 0 DATA = 1 def __init__(self, server, conn, addr, authdata): asynchat.async_chat.__init__(self, conn) self.authenticated = False self.authdata = authdata self.server = server self.conn = conn self.addr = addr self.line = [] self.state = self.COMMAND self.greeting = 0 self.mailfrom = None self.rcpttos = [] self.data = '' self.fqdn = socket.getfqdn() self.peer = conn.getpeername() self.push('220 %s ESMTP' % (self.fqdn)) self.set_terminator('\r\n') # Overrides base class for convenience def push(self, msg): asynchat.async_chat.push(self, msg + '\r\n') # Implementation of base class abstract method def collect_incoming_data(self, data): self.line.append(data) # Implementation of base class abstract method def found_terminator(self): line = EMPTYSTRING.join(self.line) self.line = [] if self.state == self.COMMAND: if not line: self.push('500 Error: bad syntax') return method = None i = line.find(' ') if i < 0: command = line.upper() arg = None else: command = line[:i].upper() arg = line[i+1:].strip() method = getattr(self, 'smtp_' + command, None) if not method: self.push('502 Error: command "%s" not implemented' % command) return method(arg) return else: if self.state != self.DATA: self.push('451 Internal confusion') return # Remove extraneous carriage returns and de-transparency according # to RFC 821, Section 4.5.2. data = [] for text in line.split('\r\n'): if text and text[0] == '.': data.append(text[1:]) else: data.append(text) self.data = NEWLINE.join(data) status = self.server.process_message(self.peer, self.mailfrom, self.rcpttos, self.data) self.rcpttos = [] self.mailfrom = None self.state = self.COMMAND self.set_terminator('\r\n') if not status: self.push('250 Ok') else: self.push(status) def smtp_AUTH(self, arg): try: (method,data) = arg.split(' ') if method.lower() == 'plain': (authorize,user,password) = base64.decodestring(data).split('\x00') if self.authdata[user] == password: self.authenticated = True self.push('235 Authentication successful.') return else: self.push('530 Access Denied') return except: self.push('535 Invalid Auth Data') def smtp_MAIL(self, arg): if not self.authenticated: self.push('530 Authentication Required') return address = self.getaddr('FROM:', arg) if not address: self.push('501 Syntax: MAIL FROM:
') return if self.mailfrom: self.push('503 Error: nested MAIL command') return self.mailfrom = address self.push('250 Ok') def smtp_EHLO(self, arg): """Patch initial greeting""" if not arg: self.push('501 Syntax: EHLO hostname') return if self.greeting: self.push('503 Duplicate HELO/EHLO') else: self.greeting = arg self.push('250-%s' % self.fqdn) self.push('250 AUTH PLAIN') def smtp_RCPT(self, arg): if not self.mailfrom: self.push('503 Error: need MAIL command') return address = self.getaddr('TO:', arg) if not address: self.push('501 Syntax: RCPT TO: ') return self.rcpttos.append(address) self.push('250 Ok') def smtp_RSET(self, arg): if arg: self.push('501 Syntax: RSET') return self.mailfrom = None self.rcpttos = [] self.data = '' self.state = self.COMMAND self.push('250 Ok') def smtp_DATA(self, arg): if not self.rcpttos: self.push('503 Error: need RCPT command') return if arg: self.push('501 Syntax: DATA') return self.state = self.DATA self.set_terminator('\r\n.\r\n') self.push('354 End data with