From 00a988744ec3813150256c50eb98467968292b36 Mon Sep 17 00:00:00 2001 From: David Baer Date: Wed, 22 Jan 2020 22:46:21 -0500 Subject: [PATCH] Email-based exception handler --- dmarcreceiver/commands.py | 6 +++++- dmarcreceiver/util.py | 34 ++++++++++++++++++++++++++++++++-- setup.py | 2 +- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/dmarcreceiver/commands.py b/dmarcreceiver/commands.py index cd8114a..c6083ae 100644 --- a/dmarcreceiver/commands.py +++ b/dmarcreceiver/commands.py @@ -8,7 +8,7 @@ from lxml.etree import parse as parse_xml from dmarcreceiver.model import DBSession, Report, ReportError, ReportRecord, OverrideReason, DKIMResult, SPFResult, metadata, init_model import transaction -from .util import sendmail +from .util import sendmail, install_exception_handler from .config import config def parse_metadata(tree): @@ -89,6 +89,10 @@ def receive_report(args): # read email message from stdin msg = message_from_file(sys.stdin) + # if not running on a tty, install email-based exception handler + if not sys.stderr.isatty(): + install_exception_handler(msg) + # check for zip file content_type = msg['content-type'] if content_type.find(';') != -1: diff --git a/dmarcreceiver/util.py b/dmarcreceiver/util.py index 335847d..8cfc203 100644 --- a/dmarcreceiver/util.py +++ b/dmarcreceiver/util.py @@ -1,5 +1,11 @@ -import os +import sys, os import subprocess +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.message import MIMEMessage +from traceback import format_exception, format_exception_only + +from .config import config def sendmail(msg): sendmail_executable = None @@ -11,6 +17,30 @@ def sendmail(msg): raise FileNotFoundError('Could not find sendmail executable') pipe = subprocess.Popen([ sendmail_executable, '-t' ], stdin=subprocess.PIPE) - stdout, stderr = pipe.communicate(msg.as_bytes) + stdout, stderr = pipe.communicate(msg.as_bytes()) return pipe.wait() +def install_exception_handler(incoming_msg): + bounce_address = config.get('bounce_address') + if bounce_address is None: + return + + def exception_handler(exctype, value, traceback): + exc_text = ''.join(format_exception(exctype, value, traceback)) + msg = MIMEMultipart() + txt_msg = MIMEText('''There was a problem processing an incoming DMARC report: + +{}'''.format(exc_text)) + txt_msg['Content-Disposition'] = 'inline' + msg.attach(txt_msg) + att = MIMEMessage(incoming_msg) + att['Content-Disposition'] = 'attachment' + att['Content-Description'] = 'Original DMARC report message' + msg.attach(att) + msg['To'] = bounce_address + msg['From'] = bounce_address + msg['Subject'] = 'Exception: ' + format_exception_only(exctype, value)[-1][:70] + sendmail(msg) + + sys.excepthook = exception_handler + diff --git a/setup.py b/setup.py index d10d14a..ddbdb4e 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ install_requires=[ setup( name='DMARCReceiver', - version='0.9', + version='1.0', description='Receive DMARC reports', author='David Baer', author_email='david@amyanddavid.net',