From 9ea9ba1d6610464a5a15690a4d06ed8ed0359506 Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Mon, 17 Feb 2014 18:55:37 +0000 Subject: [PATCH] Merged in [7138] from rjsparks@nostrum.com: Reworked SMTP Exception handling, adding sending a ticket to the secretariat when there are errors handing messages off for delivery. Added SMTP exception handling to send-scheduled-mail. This is related to ticket #1208 - Legacy-Id: 7256 Note: SVN reference [7138] has been migrated to Git commit 64727c1c33b6ee675d87ecb86e96145dfc53b2ce --- ietf/bin/send-scheduled-mail | 8 ++- ietf/middleware.py | 17 +----- ietf/utils/log.py | 3 +- ietf/utils/mail.py | 112 +++++++++++++++++++++++++++++++---- 4 files changed, 109 insertions(+), 31 deletions(-) diff --git a/ietf/bin/send-scheduled-mail b/ietf/bin/send-scheduled-mail index 04069789d..15a8f4c4f 100755 --- a/ietf/bin/send-scheduled-mail +++ b/ietf/bin/send-scheduled-mail @@ -4,6 +4,8 @@ import datetime, os, sys import syslog os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings") +from ietf.utils.mail import smtp_error_logging +from smtplib import SMTPException syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER) @@ -27,6 +29,6 @@ if mode == "specific": needs_sending = needs_sending.exclude(send_at=None).filter(send_at__lte=now) for s in needs_sending: - send_scheduled_message_from_send_queue(s) - - syslog.syslog(u'Sent scheduled message %s "%s"' % (s.id, s.message.subject)) + with smtp_error_logging(send_scheduled_message_from_send_queue) as send: + send(s) + syslog.syslog(u'Sent scheduled message %s "%s"' % (s.id, s.message.subject)) diff --git a/ietf/middleware.py b/ietf/middleware.py index 484673566..749adf291 100644 --- a/ietf/middleware.py +++ b/ietf/middleware.py @@ -5,6 +5,7 @@ from django.shortcuts import render_to_response from django.template import RequestContext from django.http import HttpResponsePermanentRedirect from ietf.utils import log +from ietf.utils.mail import log_smtp_exception import re import smtplib import sys @@ -21,20 +22,8 @@ class SQLLogMiddleware(object): class SMTPExceptionMiddleware(object): def process_exception(self, request, exception): if isinstance(exception, smtplib.SMTPException): - type = sys.exc_info()[0] - value = sys.exc_info()[1] - # See if it's a non-smtplib exception that we faked - if type == smtplib.SMTPException and len(value.args) == 1 and isinstance(value.args[0], dict) and value.args[0].has_key('really'): - orig = value.args[0] - type = orig['really'] - tb = traceback.format_tb(orig['tb']) - value = orig['value'] - else: - tb = traceback.format_tb(sys.exc_info()[2]) - log("SMTP Exception: %s" % type) - log("SMTP Exception: args: %s" % value) - log("SMTP Exception: tb: %s" % tb) - return render_to_response('email_failed.html', {'exception': type, 'args': value, 'traceback': "".join(tb)}, + (extype, value, tb) = log_smtp_exception(exception) + return render_to_response('email_failed.html', {'exception': extype, 'args': value, 'traceback': "".join(tb)}, context_instance=RequestContext(request)) return None diff --git a/ietf/utils/log.py b/ietf/utils/log.py index 250c6eff9..2d610925c 100644 --- a/ietf/utils/log.py +++ b/ietf/utils/log.py @@ -10,6 +10,7 @@ except ImportError: # import syslog will fail on Windows box pass +import sys import inspect import os.path import ietf @@ -44,4 +45,4 @@ def log(msg): file, line, where = "/", 0, "" logger("ietf%s(%d)%s: %s" % (file, line, where, msg)) -log("IETFdb v%s started" % ietf.__version__) +log("IETFdb v%s started (as %s)" % (ietf.__version__,sys.argv[0])) diff --git a/ietf/utils/mail.py b/ietf/utils/mail.py index 669fda92b..cfbc108c2 100644 --- a/ietf/utils/mail.py +++ b/ietf/utils/mail.py @@ -15,6 +15,9 @@ from ietf.utils import log import sys import time import copy +import textwrap +import traceback +from contextlib import contextmanager # Testing mode: # import ietf.utils.mail @@ -37,6 +40,22 @@ def add_headers(msg): msg['From'] = settings.DEFAULT_FROM_EMAIL return msg +class SMTPRefusedRecipients(smtplib.SMTPException): + + def __init__(self, message, original_msg, refusals): + smtplib.SMTPException.__init__(self, message) + self.original_msg = original_msg + self.refusals = refusals + + def detailed_refusals(self): + details = "The following recipients were refused:\n" + for recipient in self.refusals: + details += "\n%s: %s" % (recipient,self.refusals[recipient]) + return details + + def summary_refusals(self): + return ", ".join(["%s (%s)"%(x,self.refusals[x][0]) for x in self.refusals]) + def send_smtp(msg, bcc=None): ''' Send a Message via SMTP, based on the django email server settings. @@ -62,11 +81,11 @@ def send_smtp(msg, bcc=None): server = None try: server = smtplib.SMTP() - log("SMTP server: %s" % repr(server)) + #log("SMTP server: %s" % repr(server)) #if settings.DEBUG: # server.set_debuglevel(1) conn_code, conn_msg = server.connect(settings.EMAIL_HOST, settings.EMAIL_PORT) - log("SMTP connect: code: %s; msg: %s" % (conn_code, conn_msg)) + #log("SMTP connect: code: %s; msg: %s" % (conn_code, conn_msg)) if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD: server.ehlo() if 'starttls' not in server.esmtp_features: @@ -78,19 +97,21 @@ def send_smtp(msg, bcc=None): # advertise the AUTH capability. server.ehlo() server.login(settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD) - server.sendmail(frm, to, msg.as_string()) - # note: should pay attention to the return code, as it may - # indicate that someone didn't get the email. - except: - if server: - server.quit() + unhandled = server.sendmail(frm, to, msg.as_string()) + if unhandled != {}: + raise SMTPRefusedRecipients(message="%d addresses were refused"%len(unhandled),original_msg=msg,refusals=unhandled) + except Exception as e: # need to improve log message - log("got exception '%s' (%s) trying to send email from '%s' to %s subject '%s'" % (sys.exc_info()[0], sys.exc_info()[1], frm, to, msg.get('Subject', '[no subject]'))) - if isinstance(sys.exc_info()[0], smtplib.SMTPException): - raise + log("Exception while trying to send email from '%s' to %s subject '%s'" % (frm, to, msg.get('Subject', '[no subject]'))) + if isinstance(e, smtplib.SMTPException): + raise else: - raise smtplib.SMTPException({'really': sys.exc_info()[0], 'value': sys.exc_info()[1], 'tb': sys.exc_info()[2]}) - server.quit() + raise smtplib.SMTPException({'really': sys.exc_info()[0], 'value': sys.exc_info()[1], 'tb': traceback.format_tb(sys.exc_info()[2])}) + finally: + try: + server.quit() + except smtplib.SMTPServerDisconnected: + pass log("sent email from '%s' to %s subject '%s'" % (frm, to, msg.get('Subject', '[no subject]'))) def copy_email(msg, to, toUser=False, originalBcc=None): @@ -233,3 +254,68 @@ def send_mail_message(request, message, extra={}): send_mail_text(request, message.to, message.frm, message.subject, message.body, cc=message.cc, bcc=message.bcc, extra=e) + +def log_smtp_exception(e): + + # See if it's a non-smtplib exception that we faked + + if len(e.args)==1 and isinstance(e.args[0],dict) and e.args[0].has_key('really'): + orig = e.args[0] + extype = orig['really'] + tb = orig['tb'] + value = orig['value'] + else: + extype = sys.exc_info()[0] + value = sys.exc_info()[1] + tb = traceback.format_tb(sys.exc_info()[2]) + + + log("SMTP Exception: %s : %s" % (extype,value)) + if isinstance(e,SMTPRefusedRecipients): + log(" Refused: %s"%(e.summary_refusals())) + log(" Traceback: %s" % tb) + return (extype, value, tb) + +@contextmanager +def smtp_error_logging(thing): + try: + yield thing + except smtplib.SMTPException as e: + (extype, value, tb) = log_smtp_exception(e) + + msg = textwrap.dedent("""\ + To: + From: %s + """) % settings.SERVER_EMAIL + if isinstance(e,SMTPRefusedRecipients): + msg += textwrap.dedent("""\ + Subject: Recipients were refused while sending mail with Subject: %s + + This is a message from the datatracker to IETF-Action about an email + delivery failure, when sending email from the datatracker. + + %s + + The original message follows: + -------- BEGIN ORIGINAL MESSAGE -------- + %s + --------- END ORIGINAL MESSAGE --------- + """) % (e.original_msg.get('Subject', '[no subject]'),e.detailed_refusals(),e.original_msg.as_string()) + + else: + msg += textwrap.dedent("""\ + Subject: Datatracker error while sending email + + This is a message from the datatracker to IETF-Action about an email + delivery failure, when sending email from the datatracker. + + SMTP Exception: %s + + Error Message: %s + """) % (extype,value) + try: + send_mail_preformatted(request=None, preformatted=msg) + except smtplib.SMTPException as ticket_mail_error: + log("Exception encountered while send a ticket to the secretariat") + (extype,value) = sys.exc_info()[:2] + log("SMTP Exception: %s : %s" % (extype,value))