From e3d9fbabb14d4d8e40a8753d7aa6f73cc805134b Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Wed, 8 Jan 2014 18:56:25 +0000 Subject: [PATCH 1/3] Moved the target date for ending the ietf-announce header changes forward since we missed the start date. Commit ready for merge. Ryan - could you apply this to the current production branch as a patch and start a cron job that runs this file daily until the 24th please? - Legacy-Id: 7085 --- ietf/bin/announce-header-change | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/bin/announce-header-change b/ietf/bin/announce-header-change index 08eb7c40e..1cc12fea0 100755 --- a/ietf/bin/announce-header-change +++ b/ietf/bin/announce-header-change @@ -11,7 +11,7 @@ from ietf.utils.mail import send_mail management.setup_environ(settings) -target_date=datetime.date(year=2014,month=1,day=20) +target_date=datetime.date(year=2014,month=1,day=24) send_mail(request = None, to = "IETF-Announce ", From 64727c1c33b6ee675d87ecb86e96145dfc53b2ce Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Wed, 15 Jan 2014 21:29:27 +0000 Subject: [PATCH 2/3] 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: 7138 --- ietf/bin/send-scheduled-mail | 8 ++- ietf/middleware.py | 19 ++---- ietf/utils/log.py | 3 +- ietf/utils/mail.py | 112 +++++++++++++++++++++++++++++++---- 4 files changed, 110 insertions(+), 32 deletions(-) diff --git a/ietf/bin/send-scheduled-mail b/ietf/bin/send-scheduled-mail index 882e8369c..12fb328cb 100755 --- a/ietf/bin/send-scheduled-mail +++ b/ietf/bin/send-scheduled-mail @@ -6,6 +6,8 @@ import syslog from ietf import settings from django.core import management management.setup_environ(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) @@ -28,6 +30,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 784a68722..ce5dc5815 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 @@ -56,4 +45,4 @@ class UnicodeNfkcNormalization(object): request.META["PATH_INFO"] = unicodedata.normalize('NFKC', request.META["PATH_INFO"]) request.path_info = unicodedata.normalize('NFKC', request.path_info) return None - \ No newline at end of file + 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)) From 99c01fa54a6ac6529bd94645e47b3cc1b73e1ed1 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 21 Jan 2014 20:53:07 +0000 Subject: [PATCH 3/3] Fixed a typo in code that looks for the IRTF chair email address while adding replaces relationships. Made that code slightly more robust. Fixes ticket #1253 Commit ready for merge - Legacy-Id: 7153 --- ietf/doc/views_draft.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index 058197b4b..cde92aa2c 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -293,9 +293,9 @@ def collect_email_addresses(emails, doc): if address not in emails: emails[address] = '"%s-ads"' % (doc.group.acronym) elif doc.group.type.slug == 'rg': - email = doc.group.parent.role_set.filter(name='char')[0].email - if email.address not in emails: - emails[email.address] = '"%s"' % (email.person.name) + for role in doc.group.parent.role_set.filter(name='chair'): + if role.email.address not in emails: + emails[role.email.address] = '"%s"' % (role.person.name) if doc.shepherd: address = doc.shepherd.email_address(); if address not in emails: