如何在smtpd.SMTPServer.process_message中拒绝收件人?
假设你在一个重写的类里处理消息,比如:
class MailProcessorServer(smtpd.SMTPServer):
def process_message(self, peer, sender, rcpttos, data):
badrecipients = []
for rcpt in rcpttos:
badrecipients.append(rcpt)
#Here I want to warn the sender via a bounced email
# that the recipient does not exist
raise smtplib.SMTPRecipientsRefused(badrecipients)
#but this just crashes the process and eventually the sender times out,
# not good enough
我只想立刻把消息回复给发送者。但实际上,发送服务(比如GMail)最终会放弃处理,并在很多小时后才警告用户。看起来文档的信息很少。
3 个回答
下面的内容会直接丢弃邮件,而不会让它反弹回来。
return '554-5.7.1'
问题是:如果你拒绝一封邮件而不让它反弹,发件方的邮件服务器会不断尝试重新发送这封邮件。
错误代码 550
会让邮件反弹,这可能不是个好主意,因为你不想让垃圾邮件发送者知道你的邮件服务器的一些信息。对此要小心。
return '550'
这两种错误都会引发一个 smtplib.SMTPException
。下面是我用来处理这些异常的简化代码。
try:
if bounce:
return '550 Bad address'
else:
self.send_and_quit(sender, recipients, data)
except smtplib.SMTPException as e:
raise e
except Exception as e:
# Catch any other exception
logging.error(traceback.format_exc())
if not isinstance(e, smtplib.SMTPException):
self.send_and_quit(sender, recipients, data)
else:
raise e
拒绝一条消息的方法是从你的 process_message
方法中返回一个带有错误代码的字符串;比如说:
return '550 No such user here'
不过,根据RFC 821的规定,在消息数据传输完成后是不允许返回550错误代码的(应该在 RCPT
命令之后返回),而且smtpd模块在这个阶段也没有提供简单的方法来返回错误代码。此外,smtpd.py通过使用自动修改的“私有”双下划线属性,使得继承它的类变得比较困难。
你可能可以使用以下自定义的smtpd类子类,但我没有测试过这段代码:
class RecipientValidatingSMTPChannel(smtpd.SMTPChannel):
def smtp_RCPT(self, arg):
print >> smtpd.DEBUGSTREAM, '===> RCPT', arg
if not self._SMTPChannel__mailfrom:
self.push('503 Error: need MAIL command')
return
address = self._SMTPChannel__getaddr('TO:', arg)
if not address:
self.push('501 Syntax: RCPT TO: <address>')
return
if self._SMTPChannel__server.is_valid_recipient(address):
self._SMTPChannel__rcpttos.append(address)
print >> smtpd.DEBUGSTREAM, 'recips:', self._SMTPChannel__rcpttos
self.push('250 Ok')
else:
self.push('550 No such user here')
class MailProcessorServer(smtpd.SMTPServer):
def handle_accept(self):
conn, addr = self.accept()
print >> smtpd.DEBUGSTREAM, 'Incoming connection from %s' % repr(addr)
channel = RecipientValidatingSMTPChannel(self, conn, addr)
def is_valid_recipient(self, address):
# insert your own tests here, return True if it's valid
return False
根据只在源代码中记录的内容(抱歉!),process_message
的规范包括:
这个函数应该返回None,表示正常的`250 Ok`响应;否则,它会返回一个符合RFC 821格式的响应字符串。
所以你可以用“return '554 bad recipients %s' % badrecipients”来代替那个raise
语句——虽然这并不是完全理想的做法(因为它没有正确处理好和坏的混合情况,而根据RFC 821,这种情况下应该返回'250 Ok',但也要在之后发送一封警告邮件),不过这似乎确实能实现你想要的那种“立即退回”的效果,和那个raise
语句的效果类似。