tags:

views:

326

answers:

2

Let's say your processing a message in an overridden class like:

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

I just want to bounce back to the sender immediately. Instead the sending service (say, GMail) just gives up eventually and warns the user many hours later. The documentation seems pretty sparse.

+2  A: 

As documented only in the sources (sorry!), process_message's specs include:

This function should return None, for a normal `250 Ok' response; otherwise it returns the desired response string in RFC 821 format.

So you could "return '554 bad recipients %s' % badrecipients" instead of using that raise statement -- not entirely satisfactory (doesn't properly account for a mix of good and bad, which by RFC 821 should return a '250 Ok' but also send a warning mail later) but it does seem to be the "bounce back immediately" effect that you're looking for with that raise.

Alex Martelli
A: 

The way to reject a message is to return a string with the error code from your process_message method; e.g.

return '550 No such user here'

However, RFC 821 doesn't allow error code 550 to be returned after the message data has been transfered (it should be returned after the RCPT command), and the smtpd module unfortunately doesn't provide an easy way to return an error code at that stage. Furthermore, smtpd.py makes it difficult to subclass its classes by using auto-mangling "private" double-underscore attributes.

You may be able to use the following custom subclasses of smtpd classes, but I haven't tested this code:

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
Miles