Skip to content

Commit 64727c1

Browse files
committed
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 ietf-tools#1208 - Legacy-Id: 7138
1 parent e3d9fba commit 64727c1

4 files changed

Lines changed: 110 additions & 32 deletions

File tree

ietf/bin/send-scheduled-mail

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import syslog
66
from ietf import settings
77
from django.core import management
88
management.setup_environ(settings)
9+
from ietf.utils.mail import smtp_error_logging
10+
from smtplib import SMTPException
911

1012
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER)
1113

@@ -28,6 +30,6 @@ if mode == "specific":
2830
needs_sending = needs_sending.exclude(send_at=None).filter(send_at__lte=now)
2931

3032
for s in needs_sending:
31-
send_scheduled_message_from_send_queue(s)
32-
33-
syslog.syslog(u'Sent scheduled message %s "%s"' % (s.id, s.message.subject))
33+
with smtp_error_logging(send_scheduled_message_from_send_queue) as send:
34+
send(s)
35+
syslog.syslog(u'Sent scheduled message %s "%s"' % (s.id, s.message.subject))

ietf/middleware.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.template import RequestContext
66
from django.http import HttpResponsePermanentRedirect
77
from ietf.utils import log
8+
from ietf.utils.mail import log_smtp_exception
89
import re
910
import smtplib
1011
import sys
@@ -21,20 +22,8 @@ def process_response(self, request, response):
2122
class SMTPExceptionMiddleware(object):
2223
def process_exception(self, request, exception):
2324
if isinstance(exception, smtplib.SMTPException):
24-
type = sys.exc_info()[0]
25-
value = sys.exc_info()[1]
26-
# See if it's a non-smtplib exception that we faked
27-
if type == smtplib.SMTPException and len(value.args) == 1 and isinstance(value.args[0], dict) and value.args[0].has_key('really'):
28-
orig = value.args[0]
29-
type = orig['really']
30-
tb = traceback.format_tb(orig['tb'])
31-
value = orig['value']
32-
else:
33-
tb = traceback.format_tb(sys.exc_info()[2])
34-
log("SMTP Exception: %s" % type)
35-
log("SMTP Exception: args: %s" % value)
36-
log("SMTP Exception: tb: %s" % tb)
37-
return render_to_response('email_failed.html', {'exception': type, 'args': value, 'traceback': "".join(tb)},
25+
(extype, value, tb) = log_smtp_exception(exception)
26+
return render_to_response('email_failed.html', {'exception': extype, 'args': value, 'traceback': "".join(tb)},
3827
context_instance=RequestContext(request))
3928
return None
4029

@@ -56,4 +45,4 @@ def process_request(self, request):
5645
request.META["PATH_INFO"] = unicodedata.normalize('NFKC', request.META["PATH_INFO"])
5746
request.path_info = unicodedata.normalize('NFKC', request.path_info)
5847
return None
59-
48+

ietf/utils/log.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
pass
1212

13+
import sys
1314
import inspect
1415
import os.path
1516
import ietf
@@ -44,4 +45,4 @@ def log(msg):
4445
file, line, where = "/<UNKNOWN>", 0, ""
4546
logger("ietf%s(%d)%s: %s" % (file, line, where, msg))
4647

47-
log("IETFdb v%s started" % ietf.__version__)
48+
log("IETFdb v%s started (as %s)" % (ietf.__version__,sys.argv[0]))

ietf/utils/mail.py

Lines changed: 99 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import sys
1616
import time
1717
import copy
18+
import textwrap
19+
import traceback
20+
from contextlib import contextmanager
1821

1922
# Testing mode:
2023
# import ietf.utils.mail
@@ -37,6 +40,22 @@ def add_headers(msg):
3740
msg['From'] = settings.DEFAULT_FROM_EMAIL
3841
return msg
3942

43+
class SMTPRefusedRecipients(smtplib.SMTPException):
44+
45+
def __init__(self, message, original_msg, refusals):
46+
smtplib.SMTPException.__init__(self, message)
47+
self.original_msg = original_msg
48+
self.refusals = refusals
49+
50+
def detailed_refusals(self):
51+
details = "The following recipients were refused:\n"
52+
for recipient in self.refusals:
53+
details += "\n%s: %s" % (recipient,self.refusals[recipient])
54+
return details
55+
56+
def summary_refusals(self):
57+
return ", ".join(["%s (%s)"%(x,self.refusals[x][0]) for x in self.refusals])
58+
4059
def send_smtp(msg, bcc=None):
4160
'''
4261
Send a Message via SMTP, based on the django email server settings.
@@ -62,11 +81,11 @@ def send_smtp(msg, bcc=None):
6281
server = None
6382
try:
6483
server = smtplib.SMTP()
65-
log("SMTP server: %s" % repr(server))
84+
#log("SMTP server: %s" % repr(server))
6685
#if settings.DEBUG:
6786
# server.set_debuglevel(1)
6887
conn_code, conn_msg = server.connect(settings.EMAIL_HOST, settings.EMAIL_PORT)
69-
log("SMTP connect: code: %s; msg: %s" % (conn_code, conn_msg))
88+
#log("SMTP connect: code: %s; msg: %s" % (conn_code, conn_msg))
7089
if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:
7190
server.ehlo()
7291
if 'starttls' not in server.esmtp_features:
@@ -78,19 +97,21 @@ def send_smtp(msg, bcc=None):
7897
# advertise the AUTH capability.
7998
server.ehlo()
8099
server.login(settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD)
81-
server.sendmail(frm, to, msg.as_string())
82-
# note: should pay attention to the return code, as it may
83-
# indicate that someone didn't get the email.
84-
except:
85-
if server:
86-
server.quit()
100+
unhandled = server.sendmail(frm, to, msg.as_string())
101+
if unhandled != {}:
102+
raise SMTPRefusedRecipients(message="%d addresses were refused"%len(unhandled),original_msg=msg,refusals=unhandled)
103+
except Exception as e:
87104
# need to improve log message
88-
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]')))
89-
if isinstance(sys.exc_info()[0], smtplib.SMTPException):
90-
raise
105+
log("Exception while trying to send email from '%s' to %s subject '%s'" % (frm, to, msg.get('Subject', '[no subject]')))
106+
if isinstance(e, smtplib.SMTPException):
107+
raise
91108
else:
92-
raise smtplib.SMTPException({'really': sys.exc_info()[0], 'value': sys.exc_info()[1], 'tb': sys.exc_info()[2]})
93-
server.quit()
109+
raise smtplib.SMTPException({'really': sys.exc_info()[0], 'value': sys.exc_info()[1], 'tb': traceback.format_tb(sys.exc_info()[2])})
110+
finally:
111+
try:
112+
server.quit()
113+
except smtplib.SMTPServerDisconnected:
114+
pass
94115
log("sent email from '%s' to %s subject '%s'" % (frm, to, msg.get('Subject', '[no subject]')))
95116

96117
def copy_email(msg, to, toUser=False, originalBcc=None):
@@ -233,3 +254,68 @@ def send_mail_message(request, message, extra={}):
233254

234255
send_mail_text(request, message.to, message.frm, message.subject,
235256
message.body, cc=message.cc, bcc=message.bcc, extra=e)
257+
258+
def log_smtp_exception(e):
259+
260+
# See if it's a non-smtplib exception that we faked
261+
262+
if len(e.args)==1 and isinstance(e.args[0],dict) and e.args[0].has_key('really'):
263+
orig = e.args[0]
264+
extype = orig['really']
265+
tb = orig['tb']
266+
value = orig['value']
267+
else:
268+
extype = sys.exc_info()[0]
269+
value = sys.exc_info()[1]
270+
tb = traceback.format_tb(sys.exc_info()[2])
271+
272+
273+
log("SMTP Exception: %s : %s" % (extype,value))
274+
if isinstance(e,SMTPRefusedRecipients):
275+
log(" Refused: %s"%(e.summary_refusals()))
276+
log(" Traceback: %s" % tb)
277+
return (extype, value, tb)
278+
279+
@contextmanager
280+
def smtp_error_logging(thing):
281+
try:
282+
yield thing
283+
except smtplib.SMTPException as e:
284+
(extype, value, tb) = log_smtp_exception(e)
285+
286+
msg = textwrap.dedent("""\
287+
To: <action@ietf.org>
288+
From: %s
289+
""") % settings.SERVER_EMAIL
290+
if isinstance(e,SMTPRefusedRecipients):
291+
msg += textwrap.dedent("""\
292+
Subject: Recipients were refused while sending mail with Subject: %s
293+
294+
This is a message from the datatracker to IETF-Action about an email
295+
delivery failure, when sending email from the datatracker.
296+
297+
%s
298+
299+
The original message follows:
300+
-------- BEGIN ORIGINAL MESSAGE --------
301+
%s
302+
--------- END ORIGINAL MESSAGE ---------
303+
""") % (e.original_msg.get('Subject', '[no subject]'),e.detailed_refusals(),e.original_msg.as_string())
304+
305+
else:
306+
msg += textwrap.dedent("""\
307+
Subject: Datatracker error while sending email
308+
309+
This is a message from the datatracker to IETF-Action about an email
310+
delivery failure, when sending email from the datatracker.
311+
312+
SMTP Exception: %s
313+
314+
Error Message: %s
315+
""") % (extype,value)
316+
try:
317+
send_mail_preformatted(request=None, preformatted=msg)
318+
except smtplib.SMTPException as ticket_mail_error:
319+
log("Exception encountered while send a ticket to the secretariat")
320+
(extype,value) = sys.exc_info()[:2]
321+
log("SMTP Exception: %s : %s" % (extype,value))

0 commit comments

Comments
 (0)