diff --git a/CHANGELOG.md b/CHANGELOG.md index 17f2e3d..d86072e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ even if only `--hec` was used - Add options to save reports as a Kafka topic (mikesiegel - #21) - Major refactoring of functions - - Functions that might be useful to other projects are now stored in - `parsedmarc.utils`: +- Support parsing forensic reports generated by IBM software +- Make `sample_headers_only` flag more reliable +- Functions that might be useful to other projects are now stored in + `parsedmarc.utils`: - `get_base_domain(domain)` - `get_filename_safe_string(string)` - `get_ip_address_country(ip_address)` diff --git a/README.rst b/README.rst index 942ecb9..ac9fbfe 100644 --- a/README.rst +++ b/README.rst @@ -248,7 +248,7 @@ JSON CSV --- -:: +.. code-block:: xml_schema,org_name,org_email,org_extra_contact_info,report_id,begin_date,end_date,errors,domain,adkim,aspf,p,sp,pct,fo,source_ip_address,source_country,source_reverse_dns,source_base_domain,count,disposition,dkim_alignment,spf_alignment,policy_override_reasons,policy_override_comments,envelope_from,header_from,envelope_to,dkim_domains,dkim_selectors,dkim_results,spf_domains,spf_scopes,spf_results draft,acme.com,noreply-dmarc-support@acme.com,http://acme.com/dmarc/support,9391651994964116463,2012-04-27 20:00:00,2012-04-28 19:59:59,,example.com,r,r,none,none,100,0,72.150.241.94,US,adsl-72-150-241-94.shv.bellsouth.net,bellsouth.net,2,none,fail,pass,,,example.com,example.com,,example.com,none,fail,example.com,mfrom,pass @@ -257,8 +257,111 @@ CSV Sample forensic report output ============================= -I don't have a sample I can share for privacy reasons. If you have a sample -forensic report that you can share publicly, please contact me! +Thanks to Github user `xennn `_ for the anonymized +`forensic report email sample `_ + +JSON +---- + + +.. code-block:: json + + { + "feedback_type": "auth-failure", + "user_agent": "Lua/1.0", + "version": "1.0", + "original_mail_from": "sharepoint@domain.de", + "original_rcpt_to": "peter.pan@domain.de", + "arrival_date": "Mon, 01 Oct 2018 11:20:27 +0200", + "message_id": "<38.E7.30937.BD6E1BB5@ mailrelay.de>", + "authentication_results": "dmarc=fail (p=none, dis=none) header.from=domain.de", + "delivery_result": "smg-policy-action", + "auth_failure": [ + "dmarc" + ], + "reported_domain": "domain.de", + "arrival_date_utc": "2018-10-01 09:20:27", + "source": { + "ip_address": "10.10.10.10", + "country": null, + "reverse_dns": null, + "base_domain": null + }, + "authentication_mechanisms": [], + "original_envelope_id": null, + "dkim_domain": null, + "sample_headers_only": false, + "sample": "Content-Type: message/rfc822\nContent-Disposition: inline\nReceived: from Servernameone.domain.local (Servernameone.domain.local [10.10.10.10])\n by mailrelay.de (mail.DOMAIN.de) with SMTP id 38.E7.30937.BD6E1BB5; Mon, 1 Oct 2018 11:20:27 +0200 (CEST)\nDate: 01 Oct 2018 11:20:27 +0200\nMessage-ID: <38.E7.30937.BD6E1BB5@ mailrelay.de>\nTo: \nfrom: \"=?utf-8?B?SW50ZXJha3RpdmUgV2V0dGJld2VyYmVyLcOcYmVyc2ljaHQ=?=\" \nSubject: Subject\nMIME-Version: 1.0\nX-Mailer: Microsoft SharePoint Foundation 2010\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\n\n\n ", + "parsed_sample": { + "content-transfer-encoding": "quoted-printable", + "x-mailer": "Microsoft SharePoint Foundation 2010", + "message-id": "<38.E7.30937.BD6E1BB5@ mailrelay.de>", + "body": "", + "to": [ + { + "display_name": null, + "address": "peter.pan@domain.de", + "local": "peter.pan", + "domain": "domain.de" + } + ], + "date": "2018-10-01 09:20:27", + "to_domains": [ + "domain.de" + ], + "received": [ + { + "from": "Servernameone.domain.local Servernameone.domain.local 10.10.10.10", + "by": "mailrelay.de mail.DOMAIN.de", + "with": "SMTP id 38.E7.30937.BD6E1BB5", + "date": "Mon, 1 Oct 2018 11:20:27 +0200 CEST", + "hop": 1, + "date_utc": "2018-10-01 09:20:27", + "delay": 0 + } + ], + "content-disposition": "inline", + "mime-version": "1.0", + "subject": "Subject", + "timezone": "+2", + "from": { + "display_name": "Interaktive Wettbewerber-Übersicht", + "address": "sharepoint@domain.de", + "local": "sharepoint", + "domain": "domain.de" + }, + "content-type": "message/rfc822", + "has_defects": false, + "headers": { + "Content-Type": "text/html; charset=utf-8", + "Content-Disposition": "inline", + "Received": "from Servernameone.domain.local (Servernameone.domain.local [10.10.10.10])\n by mailrelay.de (mail.DOMAIN.de) with SMTP id 38.E7.30937.BD6E1BB5; Mon, 1 Oct 2018 11:20:27 +0200 (CEST)", + "Date": "01 Oct 2018 11:20:27 +0200", + "Message-ID": "<38.E7.30937.BD6E1BB5@ mailrelay.de>", + "To": "", + "from": "\"Interaktive Wettbewerber-Übersicht\" ", + "Subject": "Subject", + "MIME-Version": "1.0", + "X-Mailer": "Microsoft SharePoint Foundation 2010", + "Content-Transfer-Encoding": "quoted-printable" + }, + "reply_to": [], + "cc": [], + "bcc": [], + "attachments": [], + "filename_safe_subject": "Subject" + } + } + + +CSV +--- + +.. code-block:: + + feedback_type,user_agent,version,original_envelope_id,original_mail_from,original_rcpt_to,arrival_date,arrival_date_utc,subject,message_id,authentication_results,dkim_domain,source_ip_address,source_country,source_reverse_dns,source_base_domain,delivery_result,auth_failure,reported_domain,authentication_mechanisms,sample_headers_only + auth-failure,Lua/1.0,1.0,,sharepoint@domain.de,peter.pan@domain.de,"Mon, 01 Oct 2018 11:20:27 +0200",2018-10-01 09:20:27,Subject,<38.E7.30937.BD6E1BB5@ mailrelay.de>,"dmarc=fail (p=none, dis=none) header.from=domain.de",,10.10.10.10,,,,smg-policy-action,dmarc,domain.de,,False + Documentation ============= diff --git a/docs/index.rst b/docs/index.rst index 4330888..64daeb9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -253,7 +253,7 @@ JSON CSV --- -:: +.. code-block:: xml_schema,org_name,org_email,org_extra_contact_info,report_id,begin_date,end_date,errors,domain,adkim,aspf,p,sp,pct,fo,source_ip_address,source_country,source_reverse_dns,source_base_domain,count,disposition,dkim_alignment,spf_alignment,policy_override_reasons,policy_override_comments,envelope_from,header_from,envelope_to,dkim_domains,dkim_selectors,dkim_results,spf_domains,spf_scopes,spf_results draft,acme.com,noreply-dmarc-support@acme.com,http://acme.com/dmarc/support,9391651994964116463,2012-04-27 20:00:00,2012-04-28 19:59:59,,example.com,r,r,none,none,100,0,72.150.241.94,US,adsl-72-150-241-94.shv.bellsouth.net,bellsouth.net,2,none,fail,pass,,,example.com,example.com,,example.com,none,fail,example.com,mfrom,pass @@ -262,8 +262,111 @@ CSV Sample forensic report output ============================= -I don't have a sample I can share for privacy reasons. If you have a sample -forensic report that you can share publicly, please contact me! +Thanks to Github user `xennn `_ for the anonymized +`forensic report email sample +`_. + +JSON +---- + + +.. code-block:: json + + { + "feedback_type": "auth-failure", + "user_agent": "Lua/1.0", + "version": "1.0", + "original_mail_from": "sharepoint@domain.de", + "original_rcpt_to": "peter.pan@domain.de", + "arrival_date": "Mon, 01 Oct 2018 11:20:27 +0200", + "message_id": "<38.E7.30937.BD6E1BB5@ mailrelay.de>", + "authentication_results": "dmarc=fail (p=none, dis=none) header.from=domain.de", + "delivery_result": "smg-policy-action", + "auth_failure": [ + "dmarc" + ], + "reported_domain": "domain.de", + "arrival_date_utc": "2018-10-01 09:20:27", + "source": { + "ip_address": "10.10.10.10", + "country": null, + "reverse_dns": null, + "base_domain": null + }, + "authentication_mechanisms": [], + "original_envelope_id": null, + "dkim_domain": null, + "sample_headers_only": false, + "sample": "Content-Type: message/rfc822\nContent-Disposition: inline\nReceived: from Servernameone.domain.local (Servernameone.domain.local [10.10.10.10])\n by mailrelay.de (mail.DOMAIN.de) with SMTP id 38.E7.30937.BD6E1BB5; Mon, 1 Oct 2018 11:20:27 +0200 (CEST)\nDate: 01 Oct 2018 11:20:27 +0200\nMessage-ID: <38.E7.30937.BD6E1BB5@ mailrelay.de>\nTo: \nfrom: \"=?utf-8?B?SW50ZXJha3RpdmUgV2V0dGJld2VyYmVyLcOcYmVyc2ljaHQ=?=\" \nSubject: Subject\nMIME-Version: 1.0\nX-Mailer: Microsoft SharePoint Foundation 2010\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\n\n\n ", + "parsed_sample": { + "content-transfer-encoding": "quoted-printable", + "x-mailer": "Microsoft SharePoint Foundation 2010", + "message-id": "<38.E7.30937.BD6E1BB5@ mailrelay.de>", + "body": "", + "to": [ + { + "display_name": null, + "address": "peter.pan@domain.de", + "local": "peter.pan", + "domain": "domain.de" + } + ], + "date": "2018-10-01 09:20:27", + "to_domains": [ + "domain.de" + ], + "received": [ + { + "from": "Servernameone.domain.local Servernameone.domain.local 10.10.10.10", + "by": "mailrelay.de mail.DOMAIN.de", + "with": "SMTP id 38.E7.30937.BD6E1BB5", + "date": "Mon, 1 Oct 2018 11:20:27 +0200 CEST", + "hop": 1, + "date_utc": "2018-10-01 09:20:27", + "delay": 0 + } + ], + "content-disposition": "inline", + "mime-version": "1.0", + "subject": "Subject", + "timezone": "+2", + "from": { + "display_name": "Interaktive Wettbewerber-Übersicht", + "address": "sharepoint@domain.de", + "local": "sharepoint", + "domain": "domain.de" + }, + "content-type": "message/rfc822", + "has_defects": false, + "headers": { + "Content-Type": "text/html; charset=utf-8", + "Content-Disposition": "inline", + "Received": "from Servernameone.domain.local (Servernameone.domain.local [10.10.10.10])\n by mailrelay.de (mail.DOMAIN.de) with SMTP id 38.E7.30937.BD6E1BB5; Mon, 1 Oct 2018 11:20:27 +0200 (CEST)", + "Date": "01 Oct 2018 11:20:27 +0200", + "Message-ID": "<38.E7.30937.BD6E1BB5@ mailrelay.de>", + "To": "", + "from": "\"Interaktive Wettbewerber-Übersicht\" ", + "Subject": "Subject", + "MIME-Version": "1.0", + "X-Mailer": "Microsoft SharePoint Foundation 2010", + "Content-Transfer-Encoding": "quoted-printable" + }, + "reply_to": [], + "cc": [], + "bcc": [], + "attachments": [], + "filename_safe_subject": "Subject" + } + } + + +CSV +--- + +.. code-block:: + + feedback_type,user_agent,version,original_envelope_id,original_mail_from,original_rcpt_to,arrival_date,arrival_date_utc,subject,message_id,authentication_results,dkim_domain,source_ip_address,source_country,source_reverse_dns,source_base_domain,delivery_result,auth_failure,reported_domain,authentication_mechanisms,sample_headers_only + auth-failure,Lua/1.0,1.0,,sharepoint@domain.de,peter.pan@domain.de,"Mon, 01 Oct 2018 11:20:27 +0200",2018-10-01 09:20:27,Subject,<38.E7.30937.BD6E1BB5@ mailrelay.de>,"dmarc=fail (p=none, dis=none) header.from=domain.de",,10.10.10.10,,,,smg-policy-action,dmarc,domain.de,,False Bug reports =========== @@ -398,7 +501,7 @@ Elasticsearch and Kibana .. note:: - Splunk is also supported starting with ``parsedmarc`` 4.1.3 + Splunk is also supported starting with ``parsedmarc`` 4.3.0 To set up visual dashboards of DMARC data, install Elasticsearch and Kibana. @@ -632,11 +735,17 @@ select ``dmarc_aggregate`` for the other saved objects, as shown below. :align: center :target: _static/screenshots/index-pattern-conflicts.png +Records retention +~~~~~~~~~~~~~~~~~ + +To prevent your indexes from growing too large, or to comply with records +retention regulations such as GDPR, you need to use `time-based indexes +`_. Splunk ------ -Starting in version 4.1.3 ``parsedmarc`` supports sending aggregate and/or +Starting in version 4.3.0 ``parsedmarc`` supports sending aggregate and/or forensic DMARC data to a Splunk `HTTP Event collector (HEC)`_. Simply use the following command line options, along with ``--save-aggregate`` and/or ``--save-forensic``: diff --git a/parsedmarc/__init__.py b/parsedmarc/__init__.py index a20dc65..ea42797 100644 --- a/parsedmarc/__init__.py +++ b/parsedmarc/__init__.py @@ -494,15 +494,14 @@ def parsed_aggregate_reports_to_csv(reports): return csv_file_object.getvalue() -def parse_forensic_report(feedback_report, sample, sample_headers_only, - msg_date, nameservers=None, timeout=2.0): +def parse_forensic_report(feedback_report, sample, msg_date, + nameservers=None, timeout=2.0): """ Converts a DMARC forensic report and sample to a ``OrderedDict`` Args: feedback_report (str): A message's feedback report as a string sample (str): The RFC 822 headers or RFC 822 message sample - sample_headers_only (bool): Set true if the sample is only headers msg_date (str): The message's date header nameservers (list): A list of one or more nameservers to use (Cloudflare's public DNS resolvers by default) @@ -518,11 +517,14 @@ def parse_forensic_report(feedback_report, sample, sample_headers_only, key = report_value[0].lower().replace("-", "_") parsed_report[key] = report_value[1] + if "content_type" in parsed_report: + del parsed_report["content_type"] + if "arrival_date" not in parsed_report: parsed_report["arrival_date"] = msg_date - arrival_utc = dateparser.parse(parsed_report["arrival_date"], - settings={"TO_TIMEZONE": "UTC"}) + arrival_utc = human_timestamp_to_datetime( + parsed_report["arrival_date"], to_utc=True) arrival_utc = arrival_utc.strftime("%Y-%m-%d %H:%M:%S") parsed_report["arrival_date_utc"] = arrival_utc @@ -559,6 +561,10 @@ def parse_forensic_report(feedback_report, sample, sample_headers_only, if "reported_domain" not in parsed_report: parsed_report["reported_domain"] = parsed_sample["from"]["domain"] + sample_headers_only = False + number_of_attachments = len(parsed_sample["attachments"]) + if number_of_attachments < 1 and parsed_sample["body"] is None: + sample_headers_only = True if sample_headers_only and parsed_sample["has_defects"]: del parsed_sample["defects"] del parsed_sample["defects_categories"] @@ -643,52 +649,57 @@ def parse_report_email(input_, nameservers=None, timeout=2.0): msg = mailparser.parse_from_string(input_) msg_headers = json.loads(msg.headers_json) date = email.utils.format_datetime(datetime.utcnow()) - if "date" in msg_headers: + if "Date" in msg_headers: date = human_timestamp_to_datetime( - msg_headers["date"].replace("T", "")) + msg_headers["Date"]) msg = email.message_from_string(input_) except Exception as e: raise ParserError(e.__str__()) subject = None feedback_report = None - sample_headers_only = False sample = None - if "subject" in msg_headers: - subject = msg_headers["subject"] + if "Subject" in msg_headers: + subject = msg_headers["Subject"] for part in msg.walk(): content_type = part.get_content_type() payload = part.get_payload() if type(payload) == list: - payload = payload[0].__str__() - if content_type == "message/feedback-report": - try: - if "Feedback-Type" in payload: - feedback_report = payload - else: - feedback_report = b64decode(payload).__str__() - feedback_report = feedback_report.lstrip("b'").rstrip("'") - feedback_report = feedback_report.replace("\\r", "") - feedback_report = feedback_report.replace("\\n", "\n") - except (ValueError, TypeError, binascii.Error): - feedback_report = payload + for payload_ in payload: + payload_ = payload_.__str__() + if content_type == "message/feedback-report": + try: + if "Feedback-Type" in payload_: + feedback_report = payload_ + else: + feedback_report = b64decode(payload_).__str__() + feedback_report = feedback_report.lstrip( + "b'").rstrip("'") + feedback_report = feedback_report.replace("\\r", "") + feedback_report = feedback_report.replace("\\n", "\n") + except (ValueError, TypeError, binascii.Error): + feedback_report = payload - elif content_type == "text/rfc822-headers": - sample = payload - sample_headers_only = True - elif content_type == "message/rfc822": - sample = payload - sample_headers_only = False + elif content_type == "text/rfc822-headers": + sample = payload + elif content_type == "message/rfc822": + sample = payload[0].__str__() + elif content_type == "multipart/report": + if "Feedback-Type" in payload_: + feedback_report = payload_ + elif "message/rfc822" in payload_: + sample = payload_ + elif "text/rfc822-headers" in payload_: + sample = payload_ if feedback_report and sample: try: forensic_report = parse_forensic_report( feedback_report, sample, - sample_headers_only, date, nameservers=nameservers, timeout=timeout) - except EmailParserError as e: + except Exception as e: raise ParserError(e.__str__()) result = OrderedDict([("report_type", "forensic"), diff --git a/parsedmarc/utils.py b/parsedmarc/utils.py index 4a74796..0e0a611 100644 --- a/parsedmarc/utils.py +++ b/parsedmarc/utils.py @@ -13,7 +13,7 @@ import shutil import mailparser import json - +import dateparser import dns.reversename import dns.resolver import dns.exception @@ -166,18 +166,24 @@ def timestamp_to_human(timestamp): return timestamp_to_datetime(timestamp).strftime("%Y-%m-%d %H:%M:%S") -def human_timestamp_to_datetime(human_timestamp): +def human_timestamp_to_datetime(human_timestamp, to_utc=False): """ Converts a human-readable timestamp into a Python ``DateTime`` object Args: - human_timestamp (str): A timestamp in `YYYY-MM-DD HH:MM:SS`` format + human_timestamp (str): A timestamp string + to_utc (bool): Convert the timestamp to UTC Returns: DateTime: The converted timestamp """ - human_timestamp = human_timestamp.replace("T", " ") - return datetime.strptime(human_timestamp, "%Y-%m-%d %H:%M:%S") + + settings = {} + + if to_utc: + settings = {"TO_TIMEZONE": "UTC"} + + return dateparser.parse(human_timestamp, settings=settings) def human_timestamp_to_timestamp(human_timestamp): diff --git a/samples/forensic/subject.eml b/samples/forensic/DMARC Failure Report for domain.de (mail-from=sharepoint@domain.de, ip=10.10.10.10).eml similarity index 100% rename from samples/forensic/subject.eml rename to samples/forensic/DMARC Failure Report for domain.de (mail-from=sharepoint@domain.de, ip=10.10.10.10).eml diff --git a/samples/forensic/[Netease DMARC Failure Report] Rent Reminder.eml b/samples/forensic/[Netease DMARC Failure Report] Rent Reminder.eml new file mode 100644 index 0000000..77dff7c --- /dev/null +++ b/samples/forensic/[Netease DMARC Failure Report] Rent Reminder.eml @@ -0,0 +1,90 @@ +Authentication-Results-Original: smtp.cardhealth.com; spf=Pass + smtp.mailfrom=abuse@163.com +Received-SPF: Pass (smtp.cardhealth.com: domain of abuse@163.com + designates 220.181.12.184 as permitted sender) + identity=mailfrom; client-ip=220.181.12.184; + receiver=smtp.cardhealth.com; envelope-from="abuse@163.com"; + x-sender="abuse@163.com"; x-conformance=spf_only; + x-record-type="v=spf1" +Received: from m12-184.163.com ([220.181.12.184]) by smtp.cardhealth.com with + ESMTP; 28 Sep 2018 04:48:45 -0400 +Received: from 163.com (unknown [192.168.201.141]) by mfast9 (Coremail) with + SMTP id uMCowEAJ6lTs6q1bwLXEEg--.1184S2; Fri, 28 Sep 2018 16:48:44 +0800 + (CST) +MIME-Version: 1.0 +From: +Subject: [Netease DMARC Failure Report] Rent Reminder +Date: Fri, 28 Sep 2018 16:48:43 +0800 +To: +Message-ID: <5BADEAEC.AC2A83.17156@m12-184.163.com> +X-Coremail-Antispam: 1Uf129KBjDUn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73 + VFW2AGmfu7bjvjm3AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvj4RJUUUUUUUU +X-Originating-IP: [192.168.201.141] +X-CM-SenderInfo: 5dex2vi6rwjhhfrp/ +Return-Path: abuse@163.com +Content-type: multipart/mixed; + boundary="B_3622128044_155770608" + +> This message is in MIME format. Since your mail reader does not understand +this format, some or all of this message may not be legible. + +--B_3622128044_155770608 +Content-type: text/plain; + charset="UTF-8" +Content-transfer-encoding: 7bit + +This is a spf/dkim authentication-failure report for an email message received from IP 167.89.69.24 on Fri, 28 Sep 2018 16:48:42 +0800. +Below is some detail information about this message: + 1. SPF-authenticated Identifiers: email.entrata.com; + 2. DKIM-authenticated Identifiers: entrata.com; + 3. DMARC Mechanism Check Result: Identifier non-aligned, DMARC mechanism check failures; + +For more information please check Aggregate Reports or mail to abuse@163.com. + + +--B_3622128044_155770608 +Content-type: message/feedback-report; name="ATT00001"; + x-mac-creator="4F50494D" +Content-ID: +Content-disposition: attachment; + filename="ATT00001" +Content-transfer-encoding: base64 + + +RmVlZGJhY2stVHlwZTogYXV0aC1mYWlsdXJlDQpVc2VyLUFnZW50OiBOdGVzRG1hcmNSZXBv +cnRlci8xLjANClZlcnNpb246IDENCk9yaWdpbmFsLU1haWwtRnJvbTogPGJvdW5jZXMrMTEz +NzYxNi1jMWFkLXhzajM5OT0xNjMuY29tQGVtYWlsLmVudHJhdGEuY29tPg0KQXJyaXZhbC1E +YXRlOiBGcmksIDI4IFNlcCAyMDE4IDE2OjQ4OjQyICswODAwDQpTb3VyY2UtSVA6IDE2Ny44 +OS42OS4yNA0KUmVwb3J0ZWQtRG9tYWluOiBjYXJkaW5hbC5jb20NCk9yaWdpbmFsLUVudmVs +b3BlLUlkOiBOOENvd0VBcGNVUG82cTFiblhsTUFBLS0uNDQzOTJTMw0KQXV0aGVudGljYXRp +b24tUmVzdWx0czogMTYzLmNvbTsgZGtpbT1wYXNzICh2ZXJpZnkgcmVzdWx0OiBhbGwgc2ln +bmF0dXJlcyB2ZXJpZmllZCkgaGVhZGVyLmQ9ZW50cmF0YS5jb207IHNwZj1wYXNzIHNtdHAu +bWFpbGZyb209Ym91bmNlcysxMTM3NjE2LWMxYWQteHNqMzk5PTE2My5jb21AZW1haWwuZW50 +cmF0YS5jb20NCkRLSU0tRG9tYWluOiBlbnRyYXRhLmNvbQ0KRGVsaXZlcnktUmVzdWx0OiBk +ZWxpdmVyZWQNCklkZW50aXR5LUFsaWdubWVudDogc3BmLGRraW0= +--B_3622128044_155770608 +Content-type: message/rfc822 +Content-disposition: attachment + +Received: by filter1356p1mdw1.sendgrid.net with SMTP id filter1356p1mdw1-30359-5BADEAE4-E + 2018-09-28 08:48:36.858842501 +0000 UTC m=+1856335.516585242 +Received: from MTEzNzYxNg (unknown [198.190.14.10]) + by ismtpd0007p1las1.sendgrid.net (SG) with HTTP id oguVx1AxQayV5Sv2nYK-rA + for ; Fri, 28 Sep 2018 08:48:36.716 +0000 (UTC) +From: 700 on Washington +To: "redacted@163.com" +Subject: Rent Reminder +Thread-Topic: Rent Reminder +Thread-Index: AQHUVwgr2Hz5Jp/wMEGsfVZ94P1m5Q== +X-MS-Exchange-MessageSentRepresentingType: 1 +Date: Fri, 28 Sep 2018 04:48:39 -0400 +Message-ID: +X-MS-Has-Attach: +X-MS-TNEF-Correlator: +X-MS-Exchange-Organization-RecordReviewCfmType: 0 +Content-Type: text/plain; charset="us-ascii" +MIME-Version: 1.0 + + +--B_3622128044_155770608-- + diff --git a/tests.py b/tests.py index ddcb915..f840aad 100644 --- a/tests.py +++ b/tests.py @@ -8,28 +8,30 @@ import parsedmarc class Test(unittest.TestCase): - class Test(unittest.TestCase): - def testAggregateSamples(self): - """Test sample aggregate.rua DMARC reports""" - sample_paths = glob("samples/aggregate/*") - for sample_path in sample_paths: - print("Testing {0}...\n".format(sample_path)) - parsed_report = parsedmarc.parse_aggregate_report_file( - sample_path) - print(json.dumps(parsed_report, ensure_ascii=False, indent=2)) - print("\n") - print( - parsedmarc.parsed_aggregate_reports_to_csv(parsed_report)) + def testAggregateSamples(self): + """Test sample aggregate/rua DMARC reports""" + sample_paths = glob("samples/aggregate/*") + for sample_path in sample_paths: + print("Testing {0}...\n".format(sample_path)) + parsed_report = parsedmarc.parse_aggregate_report_file( + sample_path) + print(json.dumps(parsed_report, ensure_ascii=False, indent=2)) + print("\n") + print( + parsedmarc.parsed_aggregate_reports_to_csv(parsed_report)) - def testForensicSamples(self): - """Test sample forensic/ruf/failure DMARC reports""" - sample_paths = glob("samples/forensic/*.eml") - for sample_path in sample_paths: - print("Testing {0}...\n".format(sample_path)) - parsed_report = parsedmarc.parse_report_email(sample_path) - print(json.dumps(parsed_report, ensure_ascii=False, indent=2)) - print("\n") - print(parsedmarc.parsed_forensic_reports_to_csv(parsed_report)) + def testForensicSamples(self): + """Test sample forensic/ruf/failure DMARC reports""" + sample_paths = glob("samples/forensic/*.eml") + for sample_path in sample_paths: + print("Testing {0}...\n".format(sample_path)) + with open(sample_path) as sample_file: + sample_content = sample_file.read() + parsed_report = parsedmarc.parse_report_email( + sample_content)["report"] + print(json.dumps(parsed_report, ensure_ascii=False, indent=2)) + print("\n") + print(parsedmarc.parsed_forensic_reports_to_csv(parsed_report)) if __name__ == "__main__":