From 73b0831b57dc944649fd618a610f3471aa269b67 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 24 Apr 2014 18:29:01 +0000 Subject: [PATCH] Replumbed how SMTP Exceptions are handled, which involved refactoring several mail functions - Legacy-Id: 7635 --- ietf/utils/mail.py | 95 ++++++++++++++++++++++++++++++++++++-------- ietf/utils/tests.py | 17 ++------ static/css/base2.css | 3 +- 3 files changed, 84 insertions(+), 31 deletions(-) diff --git a/ietf/utils/mail.py b/ietf/utils/mail.py index 3299b8523..84de77cf1 100644 --- a/ietf/utils/mail.py +++ b/ietf/utils/mail.py @@ -7,6 +7,7 @@ from email.MIMEMultipart import MIMEMultipart from email import message_from_string import smtplib from django.conf import settings +from django.contrib import messages from django.core.exceptions import ImproperlyConfigured from django.template.loader import render_to_string from django.template import Context,RequestContext @@ -169,17 +170,19 @@ def send_mail(request, to, frm, subject, template, context, *args, **kwargs): txt = render_to_string(template, context, context_instance=mail_context(request)) return send_mail_text(request, to, frm, subject, txt, *args, **kwargs) - -def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=False, bcc=None): - """Send plain text message.""" +def encode_message(txt): if isinstance(txt, unicode): msg = MIMEText(txt.encode('utf-8'), 'plain', 'UTF-8') else: msg = MIMEText(txt) + return msg + +def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=False, bcc=None): + """Send plain text message.""" + msg = encode_message(txt) send_mail_mime(request, to, frm, subject, msg, cc, extra, toUser, bcc) -def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=False, bcc=None): - """Send MIME message with content already filled in.""" +def condition_message(to, frm, subject, msg, cc, extra): if isinstance(frm, tuple): frm = formataddr(frm) if isinstance(to, list) or isinstance(to, tuple): @@ -200,13 +203,21 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F for k, v in extra.items(): if v: msg[k] = v + +def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=False, bcc=None): + """Send MIME message with content already filled in.""" + + condition_message(to, frm, subject, msg, cc, extra) + # start debug server with python -m smtpd -n -c DebuggingServer localhost:2025 # then put USING_DEBUG_EMAIL_SERVER=True and EMAIL_HOST='localhost' # and EMAIL_PORT=2025 in settings_local.py debugging = getattr(settings, "USING_DEBUG_EMAIL_SERVER", False) and settings.EMAIL_HOST == 'localhost' and settings.EMAIL_PORT == 2025 if test_mode or debugging or settings.SERVER_MODE == 'production': - send_smtp(msg, bcc) + with smtp_error_logging(send_smtp) as logging_send: + with smtp_error_user_warning(logging_send,request) as send: + send(msg, bcc) elif settings.SERVER_MODE == 'test': if toUser: copy_email(msg, to, toUser=True, originalBcc=bcc) @@ -219,12 +230,12 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F if copy_to and not test_mode and not debugging: # if we're running automated tests, this copy is just annoying if bcc: msg['X-Tracker-Bcc']=bcc - copy_email(msg, copy_to,originalBcc=bcc) + with smtp_error_logging(copy_email) as logging_copy: + with smtp_error_user_warning(logging_copy,request) as copy: + copy(msg, copy_to,originalBcc=bcc) -def send_mail_preformatted(request, preformatted, extra={}, override={}): - """Parse preformatted string containing mail with From:, To:, ..., - and send it through the standard IETF mail interface (inserting - extra headers as needed).""" +def parse_preformatted(preformatted, extra={}, override={}): + """Parse preformatted string containing mail with From:, To:, ...,""" msg = message_from_string(preformatted.encode("utf-8")) for k, v in override.iteritems(): @@ -242,7 +253,15 @@ def send_mail_preformatted(request, preformatted, extra={}, override={}): bcc = msg['Bcc'] del msg['Bcc'] - + + return (msg, headers, bcc) + +def send_mail_preformatted(request, preformatted, extra={}, override={}): + """Parse preformatted string containing mail with From:, To:, ..., + and send it through the standard IETF mail interface (inserting + extra headers as needed).""" + + (msg,headers,bcc) = parse_preformatted(preformatted, extra, override) send_mail_text(request, msg['To'], msg["From"], msg["Subject"], msg.get_payload(), extra=headers, bcc=bcc) return msg @@ -278,6 +297,26 @@ def log_smtp_exception(e): log(" Traceback: %s" % tb) return (extype, value, tb) +@contextmanager +def smtp_error_user_warning(thing,request): + try: + yield thing + except smtplib.SMTPException as e: + (extype, value, tb) = log_smtp_exception(e) + + warning = "An error occured while sending email with\n" + warning += "Subject: %s\n" % e.original_msg.get('Subject','[no subject]') + warning += "To: %s\n" % e.original_msg.get('To','[no to]') + warning += "Cc: %s\n" % e.original_msg.get('Cc','[no cc]') + if isinstance(e,SMTPSomeRefusedRecipients): + warning += e.detailed_refusals() + else: + warning += "SMTP Exception: %s\n"%extype + warning += "Error Message: %s\n\n"%value + warning += "The message was not delivered to anyone." + messages.warning(request,warning,extra_tags='preformatted',fail_silently=True) + raise + @contextmanager def smtp_error_logging(thing): try: @@ -324,9 +363,31 @@ def smtp_error_logging(thing): %s --------- END ORIGINAL MESSAGE --------- """) % e.original_msg.as_string() + + send_error_to_secretariat(msg) + +def send_error_to_secretariat(raw_msg): + + (parsed_msg , headers , bcc) = parse_preformatted(raw_msg) + send_msg = encode_message(parsed_msg.get_payload()) + cc = None + condition_message(parsed_msg['To'], parsed_msg['From'], parsed_msg['Subject'], send_msg, cc, headers) + + debugging = getattr(settings, "USING_DEBUG_EMAIL_SERVER", False) and settings.EMAIL_HOST == 'localhost' and settings.EMAIL_PORT == 2025 + + try: + if test_mode or debugging or settings.SERVER_MODE == 'production': + send_smtp(send_msg, bcc) try: - send_mail_preformatted(request=None, preformatted=msg) - except smtplib.SMTPException: - log("Exception encountered while sending a ticket to the secretariat") - (extype,value) = sys.exc_info()[:2] - log("SMTP Exception: %s : %s" % (extype,value)) + copy_to = settings.EMAIL_COPY_TO + except AttributeError: + copy_to = "ietf.tracker.archive+%s@gmail.com" % settings.SERVER_MODE + if copy_to and not test_mode and not debugging: # if we're running automated tests, this copy is just annoying + if bcc: + send_msg['X-Tracker-Bcc']=bcc + copy_email(send_msg, copy_to,originalBcc=bcc) + except smtplib.SMTPException: + log("Exception encountered while sending a ticket to the secretariat") + (extype,value) = sys.exc_info()[:2] + log("SMTP Exception: %s : %s" % (extype,value)) + diff --git a/ietf/utils/tests.py b/ietf/utils/tests.py index 02a0c8067..70553e952 100644 --- a/ietf/utils/tests.py +++ b/ietf/utils/tests.py @@ -1,11 +1,10 @@ import os.path -from smtplib import SMTPRecipientsRefused from django.conf import settings from django.test import TestCase from ietf.utils.management.commands import pyflakes -from ietf.utils.mail import send_mail_text, outbox, SMTPSomeRefusedRecipients, smtp_error_logging +from ietf.utils.mail import send_mail_text, outbox class PyFlakesTestCase(TestCase): @@ -22,21 +21,13 @@ class TestSMTPServer(TestCase): def send_mail(to): send_mail_text(None, to=to, frm=None, subject="Test for rejection", txt="dummy body") - with self.assertRaises(SMTPSomeRefusedRecipients): - send_mail('good@example.com,poison@example.com') - - with self.assertRaises(SMTPRecipientsRefused): - send_mail('poison@example.com') - len_before = len(outbox) - with smtp_error_logging(send_mail) as send: - send('good@example.com,poison@example.com') + send_mail('good@example.com,poison@example.com') self.assertEqual(len(outbox),len_before+2) self.assertTrue('Some recipients were refused' in outbox[-1]['Subject']) len_before = len(outbox) - with smtp_error_logging(send_mail) as send: - send('poison@example.com') + send_mail('poison@example.com') self.assertEqual(len(outbox),len_before+2) self.assertTrue('error while sending email' in outbox[-1]['Subject']) - \ No newline at end of file + diff --git a/static/css/base2.css b/static/css/base2.css index 14f7eb49f..44bbadaab 100644 --- a/static/css/base2.css +++ b/static/css/base2.css @@ -228,6 +228,7 @@ li.info { margin: 0.5em; background-color: #ff8; } li.success { margin: 0.5em; background-color: #4f4; } li.warning { margin: 0.5em; background-color: #fc8; color: black;} li.error { margin: 0.5em; background-color: #f44; } +li.preformatted { white-space:pre-wrap; } .errorlist, errorlist li { background: red; @@ -423,4 +424,4 @@ span.fieldRequired { .login { font-style: italic; -} \ No newline at end of file +}