From f05b0ec45ac667e85a610219d77906f049eb58cc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 29 Mar 2016 17:21:43 -0400 Subject: [feature] SMTP delivery bounces We catch any error on SMTP delivery and format it as a bounce message delivered to the user Inbox. this doesn't comply with the bounce format, but it's a nice first start. leaving proper structuring of the delivery failure report for future iterations. - Resolves: #7263 --- src/leap/mail/outgoing/service.py | 44 ++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 12 deletions(-) (limited to 'src/leap/mail/outgoing/service.py') diff --git a/src/leap/mail/outgoing/service.py b/src/leap/mail/outgoing/service.py index eeb5d32..335cae4 100644 --- a/src/leap/mail/outgoing/service.py +++ b/src/leap/mail/outgoing/service.py @@ -73,7 +73,7 @@ class SSLContextFactory(ssl.ClientContextFactory): return ctx -def outgoingFactory(userid, keymanager, opts, check_cert=True): +def outgoingFactory(userid, keymanager, opts, check_cert=True, bouncer=None): cert = unicode(opts.cert) key = unicode(opts.key) @@ -85,7 +85,9 @@ def outgoingFactory(userid, keymanager, opts, check_cert=True): raise errors.ConfigurationError( 'No valid SMTP certificate could be found for %s!' % userid) - return OutgoingMail(str(userid), keymanager, cert, key, hostname, port) + return OutgoingMail( + str(userid), keymanager, cert, key, hostname, port, + bouncer) class OutgoingMail(object): @@ -93,7 +95,8 @@ class OutgoingMail(object): Sends Outgoing Mail, encrypting and signing if needed. """ - def __init__(self, from_address, keymanager, cert, key, host, port): + def __init__(self, from_address, keymanager, cert, key, host, port, + bouncer=None): """ Initialize the outgoing mail service. @@ -133,6 +136,7 @@ class OutgoingMail(object): self._cert = cert self._from_address = from_address self._keymanager = keymanager + self._bouncer = bouncer def send_message(self, raw, recipient): """ @@ -145,8 +149,8 @@ class OutgoingMail(object): :return: a deferred which delivers the message when fired """ d = self._maybe_encrypt_and_sign(raw, recipient) - d.addCallback(self._route_msg) - d.addErrback(self.sendError) + d.addCallback(self._route_msg, raw) + d.addErrback(self.sendError, raw) return d def sendSuccess(self, smtp_sender_result): @@ -163,21 +167,36 @@ class OutgoingMail(object): emit_async(catalog.SMTP_SEND_MESSAGE_SUCCESS, fromaddr, dest_addrstr) - def sendError(self, failure): + def sendError(self, failure, origmsg): """ Callback for an unsuccessfull send. - :param e: The result from the last errback. - :type e: anything + :param failure: The result from the last errback. + :type failure: anything + :param origmsg: the original, unencrypted, raw message, to be passed to + the bouncer. + :type origmsg: str """ - # XXX: need to get the address from the exception to send signal + # XXX: need to get the address from the original message to send signal # emit_async(catalog.SMTP_SEND_MESSAGE_ERROR, self._from_address, # self._user.dest.addrstr) + + # TODO when we implement outgoing queues/long-term-retries, we could + # examine the error *here* and delay the notification if it's just a + # temporal error. We might want to notify the permanent errors + # differently. + err = failure.value log.err(err) - raise err - def _route_msg(self, encrypt_and_sign_result): + if self._bouncer: + self._bouncer.bounce_message( + err.message, to=self._from_address, + orig=origmsg) + else: + raise err + + def _route_msg(self, encrypt_and_sign_result, raw): """ Sends the msg using the ESMTPSenderFactory. @@ -191,7 +210,8 @@ class OutgoingMail(object): # we construct a defer to pass to the ESMTPSenderFactory d = defer.Deferred() - d.addCallbacks(self.sendSuccess, self.sendError) + d.addCallback(self.sendSuccess) + d.addErrback(self.sendError, raw) # we don't pass an ssl context factory to the ESMTPSenderFactory # because ssl will be handled by reactor.connectSSL() below. factory = smtp.ESMTPSenderFactory( -- cgit v1.2.3