如何在smtpd.SMTPServer.process_message中拒绝收件人?

1 投票
3 回答
2176 浏览
提问于 2025-04-15 12:26

假设你在一个重写的类里处理消息,比如:

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 个回答

0

下面的内容会直接丢弃邮件,而不会让它反弹回来。

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
1

拒绝一条消息的方法是从你的 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
5

根据只在源代码中记录的内容(抱歉!),process_message的规范包括:

这个函数应该返回None,表示正常的`250 Ok`响应;否则,它会返回一个符合RFC 821格式的响应字符串。

所以你可以用“return '554 bad recipients %s' % badrecipients”来代替那个raise语句——虽然这并不是完全理想的做法(因为它没有正确处理好和坏的混合情况,而根据RFC 821,这种情况下应该返回'250 Ok',但也要在之后发送一封警告邮件),不过这似乎确实能实现你想要的那种“立即退回”的效果,和那个raise语句的效果类似。

撰写回答