Ejemplo n.º 1
0
    def process_multipart(self, dmarcemail):
        """Extract multipart report"""
        report = FBReport()
        dmarc_reporter = None
        try:
            dmarc_source = dmarcemail.get_payload()
            dmarc_reporter = dmarcemail.get('from')
            report.reporter = FBReporter.objects.get(email=dmarc_reporter)
            mimepart = dmarcemail.get_payload()
        except ObjectDoesNotExist:
            try:
                report.reporter = FBReporter.objects.create(
                    org_name=dmarc_reporter,
                    email=dmarc_reporter,
                )
            except:
                msg = 'Failed to find or create reporter {}'.format(
                    dmarc_reporter)
                logger.error(msg)
                raise CommandError(msg)
        except:
            msg = 'Unable to get rfc822 report'
            logger.error(msg)
            tf = tempfile.mkstemp(prefix='dmarc-', suffix='.eml')
            tmpf = os.fdopen(tf[0], 'w')
            tmpf.write(dmarcemail.get_payload())
            tmpf.close()
            msg = 'Saved as: {}'.format(tf[1])
            logger.error(msg)
            raise CommandError(msg)
        fp = StringIO()
        g = Generator(fp, maxheaderlen=0)
        g.flatten(dmarcemail)
        report.feedback_source = fp.getvalue()
        g = None
        fp = None

        # Get the human readable part
        try:
            mimepart = dmarcemail.get_payload(0)
            if mimepart.get_content_type() == 'text/plain':
                # get the human-readable part of the message
                report.description = mimepart
        except:
            msg = 'Unable to get human readable part'
            logger.warning(msg)

        # Get the feedback report
        try:
            mimepart = dmarcemail.get_payload(1)
            if mimepart.get_content_type() == 'message/feedback-report':
                fp = StringIO()
                g = Generator(fp, maxheaderlen=0)
                g.flatten(mimepart)
                report.feedback_report = fp.getvalue()
                g = None
                fp = None
            else:
                msg = 'Found {} instead of message/feedback-report'.format(
                    mimepart.get_content_type())
                logger.error(msg)
        except:
            msg = 'Unable to get feedback-report part'
            logger.error(msg)

        if report.feedback_report:
            for line in report.feedback_report.splitlines():
                line = line.lstrip()
                (ls0, ls1, ls2) = line.partition(':')
                ls0 = ls0.strip()
                ls2 = ls2.strip()
                if ls1:
                    if not report.domain:
                        if ls0 == 'Reported-Domain':
                            report.domain = ls2
                    if not report.source_ip:
                        if ls0 == 'Source-IP':
                            report.source_ip = ls2
                    if not report.email_from:
                        if ls0 == 'Original-Mail-From':
                            report.email_from = ls2
                    if not report.date:
                        if ls0 == 'Arrival-Date':
                            try:
                                # get tuples
                                t = parsedate_tz(ls2)
                                # get timestamp
                                t = mktime_tz(t)
                                report.date = datetime.fromtimestamp(t)
                                tz_utc = pytz.timezone('UTC')
                                report.date = report.date.replace(
                                    tzinfo=tz_utc)
                            except:
                                msg = 'Unable to get date from: {}'.format(ls2)
                                logger.error(msg)
                                report.date = datetime.now()
                    if not report.dmarc_result:
                        if ls0 == 'Delivery-Result':
                            report.dmarc_result = ls2
                    if ls0 == 'Authentication-Results':
                        ar = ls2.split()
                        for r in ar:
                            (r0, r1, r2) = r.partition('=')
                            if r1:
                                if not report.dkim_alignment and r0 == 'dkim':
                                    report.dkim_alignment = r2.rstrip(';')
                                if not report.spf_alignment and r0 == 'spf':
                                    report.spf_alignment = r2.rstrip(';')

        # Get the rfc822 headers and any message
        fp = StringIO()
        g = Generator(fp, maxheaderlen=0)
        try:
            mimepart = dmarcemail.get_payload(2, False)
            mimepart_type = mimepart.get_content_type()
            g.flatten(mimepart)
            if mimepart_type == 'message/rfc822':
                report.email_source = fp.getvalue()
            elif mimepart_type == 'message/rfc822-headers':
                report.email_source = fp.getvalue()
            elif mimepart_type == 'text/rfc822':
                report.email_source = fp.getvalue()
            elif mimepart_type == 'text/rfc822-headers':
                report.email_source = fp.getvalue()
            else:
                msg = 'Found {} instead of rfc822'.format(mimepart_type)
                logger.debug(msg)
        except:
            msg = 'Unable to get rfc822 part'
            logger.warning(msg)
        g = None
        fp = None
        if report.email_source:
            for line in report.email_source.splitlines():
                line = line.lstrip()
                (ls0, ls1, ls2) = line.partition(':')
                ls0 = ls0.strip()
                ls2 = ls2.strip()
                if ls1:
                    if not report.email_subject:
                        if ls0 == 'Subject':
                            report.email_subject = ls2

        try:
            report.save()
        except:
            msg = 'Failed save from {}'.format(report.reporter)
            logger.error(msg)
            tf = tempfile.mkstemp(prefix='dmarc-', suffix='.eml')
            tmpf = os.fdopen(tf[0], 'w')
            tmpf.write(dmarcemail.get_payload())
            tmpf.close()
            msg = 'Saved as: {}'.format(tf[1])
            logger.error(msg)
    def process_multipart(dmarcemail):
        """Extract multipart report"""
        # pylint: disable=too-many-branches,too-many-locals,too-many-statements
        # pylint: disable=too-many-nested-blocks

        # Get the human readable part
        try:
            mimepart = dmarcemail.get_payload(0)
            if mimepart.get_content_type() != "text/plain":
                raise ValueError("Wrong mime type for the first mime part")

            mimepart = dmarcemail.get_payload(1)
            if mimepart.get_content_type() != "message/feedback-report":
                raise ValueError("Wrong mime type for the second mime part")

            mimepart = dmarcemail.get_payload(2)
            if mimepart.get_content_type() not in (
                    "message/rfc822",
                    "text/rfc822-headers",
                    "message/rfc822-headers",
                    "text/rfc822",
            ):
                raise ValueError("Wrong mime type for the third mime part")

        except Exception:
            msg = "Mime-Types checking"
            logger.exception(msg)
            Command.save_email(dmarcemail)

        report = FBReport()
        dmarc_reporter = None
        try:
            dmarc_reporter = dmarcemail.get("from")
            report.reporter = FBReporter.objects.get(email=dmarc_reporter)
            mimepart = dmarcemail.get_payload()
        except ObjectDoesNotExist:
            try:
                report.reporter = FBReporter(
                    org_name=dmarc_reporter,
                    email=dmarc_reporter,
                )
            except Exception:
                msg = "Failed to find or create reporter {}".format(
                    dmarc_reporter)
                logger.exception(msg)
                raise CommandError(msg)
        except Exception:
            msg = "Unable to get rfc822 report"
            logger.exception(msg)
            Command.save_email(dmarcemail)

        out = StringIO()
        gen = Generator(out, maxheaderlen=0)
        gen.flatten(dmarcemail)
        report.feedback_source = out.getvalue()
        gen = None
        out = None

        try:
            # Get the human readable part
            mimepart = dmarcemail.get_payload(0)
            # get the human-readable part of the message
            report.description = mimepart
        except Exception:
            msg = "Unable to get human readable part"
            logger.exception(msg)
            raise CommandError(msg)

        # Get the feedback report
        reportpart = None
        try:
            mimepart = dmarcemail.get_payload(1)
            reportpart = mimepart
            out = StringIO()
            gen = Generator(out, maxheaderlen=0)
            gen.flatten(mimepart)
            report.feedback_report = out.getvalue()
            gen = None
            out = None
        except Exception:
            msg = "Unable to get feedback-report part"
            logger.exception(msg)
            Command.save_email(dmarcemail)

        # should check for:
        # Feedback-Type: auth-failure
        # Auth-Failure: dmarc
        # ... or something like that,
        # see: https://tools.ietf.org/html/rfc7489#page-36

        if report.feedback_report and reportpart:
            first = False
            headers = {}
            for p in reportpart.walk():
                if not first:
                    first = True
                else:
                    # XXX this mangles multiple
                    # keys with different values
                    for k in p.keys():
                        # print(k, p[k])
                        if k not in headers:
                            headers[k] = [
                                p[k],
                            ]
                        else:
                            headers[k].append(p[k])

            # print(headers)

            if "Feedback-Type" not in headers or "Auth-Failure" not in headers:
                logger.error(
                    "Probably not a DMARC feedback email, missing headers")
                Command.save_email(dmarcemail)

            if headers["Feedback-Type"][0] != "auth-failure":
                logger.error(
                    "Probably not a DMARC feedback email, not an auth-failure message: {}"
                    .format(headers["Feedback-Type"][0]))
                Command.save_email(dmarcemail)

            if headers["Auth-Failure"][0] != "dmarc":
                logger.error(
                    "Probably not a DMARC feedback email, auth-failure wasn't dmarc: {}"
                    .format(headers["Auth-Failure"][0]))
                Command.save_email(dmarcemail)

            if "Reported-Domain" in headers:
                report.domain = headers["Reported-Domain"][0]
            if "Source-IP" in headers:
                report.source_ip = headers["Source-IP"][0]
            if "Original-Mail-From" in headers:
                report.email_from = headers["Original-Mail-From"][0]
            if "Arrival-Date" in headers:
                arrival_date = headers["Arrival-Date"][0]
                try:
                    # get tuples
                    tuples = parsedate_tz(arrival_date)
                    # get timestamp
                    time = mktime_tz(tuples)
                    report.date = datetime.fromtimestamp(time)
                    tz_utc = pytz.timezone("UTC")
                    report.date = report.date.replace(tzinfo=tz_utc)
                except Exception:
                    msg = "Unable to get date from: {}".format(arrival_date)
                    logger.exception(msg)
                    report.date = datetime.now()

            if "Delivery-Result" in headers:
                report.dmarc_result = headers["Delivery-Result"][0]
            if "Authentication-Results" in headers:
                auth_results = headers["Authentication-Results"][0].split()
                for result in auth_results:
                    (typ, eq_sign, alignment) = result.partition("=")
                    if not eq_sign:
                        continue
                    if not report.dkim_alignment and typ == "dkim":
                        report.dkim_alignment = alignment.rstrip(";")
                    if not report.spf_alignment and typ == "spf":
                        report.spf_alignment = alignment.rstrip(";")

        # Get the rfc822 headers and any message
        out = StringIO()
        gen = Generator(out, maxheaderlen=0)
        try:
            mimepart = dmarcemail.get_payload(2)
            gen.flatten(mimepart)
            report.email_source = out.getvalue()
        except Exception:
            msg = "Unable to get rfc822 part"
            logger.exception(msg)
            Command.save_email(dmarcemail)
        gen = None
        out = None
        if report.email_source:
            # XXX also use the header parser part of the email module here.
            for line in report.email_source.splitlines():
                line = line.lstrip()
                (ls0, ls1, ls2) = line.partition(":")
                ls0 = ls0.strip()
                ls2 = ls2.strip()
                if ls1:
                    if not report.email_subject:
                        if ls0 == "Subject":
                            report.email_subject = ls2
        try:
            reporter = report.reporter
            reporter.save()
            report.reporter = reporter
            report.save()
        except Exception:
            msg = "Failed save from {}".format(report.reporter)
            logger.exception(msg)
            Command.save_email(dmarcemail)
Ejemplo n.º 3
0
    def process_822(self, dmarcemail):
        """Extract report from rfc822 email, non standard"""
        report = FBReport()
        dmarc_reporter = None
        try:
            dmarc_reporter = dmarcemail.get('from')
            report.reporter = FBReporter.objects.get(email=dmarc_reporter)
        except ObjectDoesNotExist:
            try:
                report.reporter = FBReporter.objects.create(
                    org_name=dmarc_reporter,
                    email=dmarc_reporter,
                )
            except:
                msg = 'Failed to find or create reporter {}'.format(
                    dmarc_reporter)
                logger.error(msg)
                raise CommandError(msg)
        except:
            msg = 'Unable to get feedback report'
            logger.warning(msg)
            tf = tempfile.mkstemp(prefix='dmarc-', suffix='.eml')
            tmpf = os.fdopen(tf[0], 'w')
            tmpf.write(dmarcemail.get_payload())
            tmpf.close()
            msg = 'Saved as: {}'.format(tf[1])
            logger.error(msg)
            raise CommandError(msg)
        report.feedback_source = dmarcemail.get_payload()
        fp = StringIO()
        g = Generator(fp, maxheaderlen=0)
        g.flatten(dmarcemail)
        report.email_source = fp.getvalue()
        g = None
        fp = None

        print report.feedback_source
        for line in report.feedback_source.splitlines():
            line = line.lstrip()
            (ls0, ls1, ls2) = line.partition(':')
            ls0 = ls0.strip()
            ls2 = ls2.strip()
            if ls1:
                if not report.domain:
                    if ls0 == 'Sender Domain':
                        report.domain = ls2
                if not report.source_ip:
                    if ls0 == 'Sender IP Address':
                        report.source_ip = ls2
                if not report.date:
                    if ls0 == 'Received Date':
                        try:
                            # get tuples
                            t = parsedate_tz(ls2)
                            # get timestamp
                            t = mktime_tz(t)
                            report.date = datetime.fromtimestamp(t)
                            tz_utc = pytz.timezone('UTC')
                            report.date = report.date.replace(tzinfo=tz_utc)
                        except:
                            msg = 'Unable to get date from: {}'.format(ls2)
                            logger.error(msg)
                            report.date = datetime.now()
                if not report.spf_alignment:
                    if ls0 == 'SPF Alignment':
                        report.spf_alignment = ls2
                if not report.dkim_alignment:
                    if ls0 == 'DKIM Alignment':
                        report.dkim_alignment = ls2
                if not report.dmarc_result:
                    if ls0 == 'DMARC Results':
                        report.dmarc_result = ls2
                if not report.email_from:
                    if ls0 == 'From':
                        report.email_from = ls2
                if not report.email_subject:
                    if ls0 == 'Subject':
                        report.email_subject = ls2
        try:
            report.save()
        except:
            msg = 'Failed save from {}'.format(dmarc_reporter)
            logger.error(msg)
            tf = tempfile.mkstemp(prefix='dmarc-', suffix='.eml')
            tmpf = os.fdopen(tf[0], 'w')
            tmpf.write(dmarcemail.get_payload())
            tmpf.close()
            msg = 'Saved as: {}'.format(tf[1])
            logger.error(msg)
    def process_822(dmarcemail):
        """Extract report from rfc822 email, non standard"""
        # pylint: disable=too-many-branches,too-many-locals,too-many-statements
        report = FBReport()
        dmarc_reporter = None
        try:
            dmarc_reporter = dmarcemail.get("from")
            report.reporter = FBReporter.objects.get(email=dmarc_reporter)
        except ObjectDoesNotExist:
            try:
                report.reporter = FBReporter.objects.create(
                    org_name=dmarc_reporter,
                    email=dmarc_reporter,
                )
            except Exception:
                msg = "Failed to find or create reporter {}".format(
                    dmarc_reporter)
                logger.exception(msg)
                raise CommandError(msg)
        except Exception:
            msg = "Unable to get feedback report"
            logger.exception(msg)
            Command.save_email(dmarcemail)
        report.feedback_source = dmarcemail.get_payload()
        out = StringIO()
        gen = Generator(out, maxheaderlen=0)
        gen.flatten(dmarcemail)
        report.email_source = out.getvalue()
        gen = None
        out = None
        logger.info("Feedback report source: %s", report.feedback_source)

        for line in report.feedback_source.splitlines():
            line = line.lstrip()
            (ls0, ls1, ls2) = line.partition(":")
            ls0 = ls0.strip()
            ls2 = ls2.strip()
            if ls1:
                if not report.domain:
                    if ls0 == "Sender Domain":
                        report.domain = ls2
                if not report.source_ip:
                    if ls0 == "Sender IP Address":
                        report.source_ip = ls2
                if not report.date:
                    if ls0 == "Received Date":
                        try:
                            # get tuples
                            tuples = parsedate_tz(ls2)
                            # get timestamp
                            time = mktime_tz(tuples)
                            report.date = datetime.fromtimestamp(time)
                            tz_utc = pytz.timezone("UTC")
                            report.date = report.date.replace(tzinfo=tz_utc)
                        except Exception:
                            msg = "Unable to get date from: {}".format(ls2)
                            logger.exception(msg)
                            report.date = datetime.now()
                if not report.spf_alignment:
                    if ls0 == "SPF Alignment":
                        report.spf_alignment = ls2
                if not report.dkim_alignment:
                    if ls0 == "DKIM Alignment":
                        report.dkim_alignment = ls2
                if not report.dmarc_result:
                    if ls0 == "DMARC Results":
                        report.dmarc_result = ls2
                if not report.email_from:
                    if ls0 == "From":
                        report.email_from = ls2
                if not report.email_subject:
                    if ls0 == "Subject":
                        report.email_subject = ls2
        try:
            report.save()
        except Exception:
            msg = "Failed save from {}".format(dmarc_reporter)
            logger.exception(msg)
            Command.save_email(dmarcemail)