From ef1d001c167bba3a19a97c4127e220cd304066e8 Mon Sep 17 00:00:00 2001 From: Sean Whalen Date: Thu, 25 Dec 2025 17:22:50 -0500 Subject: [PATCH] Update docs --- _modules/index.html | 4 +- _modules/parsedmarc.html | 652 ++++++++++++++++------------ _modules/parsedmarc/elastic.html | 117 +++-- _modules/parsedmarc/opensearch.html | 59 ++- _modules/parsedmarc/splunk.html | 21 +- _modules/parsedmarc/utils.html | 229 +++++----- _static/documentation_options.js | 2 +- api.html | 138 +++--- contributing.html | 4 +- davmail.html | 4 +- dmarc.html | 4 +- elasticsearch.html | 4 +- genindex.html | 15 +- index.html | 4 +- installation.html | 4 +- kibana.html | 4 +- mailing-lists.html | 4 +- objects.inv | Bin 1106 -> 1130 bytes opensearch.html | 4 +- output.html | 4 +- py-modindex.html | 4 +- search.html | 4 +- searchindex.js | 2 +- splunk.html | 4 +- usage.html | 4 +- 25 files changed, 711 insertions(+), 584 deletions(-) diff --git a/_modules/index.html b/_modules/index.html index ea7d6fba..b27da967 100644 --- a/_modules/index.html +++ b/_modules/index.html @@ -5,14 +5,14 @@ - Overview: module code — parsedmarc 9.0.5 documentation + Overview: module code — parsedmarc 9.0.6 documentation - + diff --git a/_modules/parsedmarc.html b/_modules/parsedmarc.html index 579ff198..1fade3b9 100644 --- a/_modules/parsedmarc.html +++ b/_modules/parsedmarc.html @@ -5,14 +5,14 @@ - parsedmarc — parsedmarc 9.0.5 documentation + parsedmarc — parsedmarc 9.0.6 documentation - + @@ -85,8 +85,6 @@ from __future__ import annotations -from typing import Dict, List, Any, Union, Optional, IO, Callable - import binascii import email import email.utils @@ -100,31 +98,51 @@ import zipfile import zlib from base64 import b64decode -from collections import OrderedDict from csv import DictWriter -from datetime import datetime, timedelta, timezone, tzinfo +from datetime import date, datetime, timedelta, timezone, tzinfo from io import BytesIO, StringIO +from typing import ( + Any, + BinaryIO, + Callable, + Dict, + List, + Optional, + Sequence, + Union, + cast, +) +import lxml.etree as etree import mailparser import xmltodict from expiringdict import ExpiringDict -from lxml import etree from mailsuite.smtp import send_email +from parsedmarc.constants import __version__ from parsedmarc.log import logger from parsedmarc.mail import ( - MailboxConnection, - IMAPConnection, - MSGraphConnection, GmailConnection, + IMAPConnection, + MailboxConnection, + MSGraphConnection, +) +from parsedmarc.types import ( + AggregateReport, + ForensicReport, + ParsedReport, + ParsingResults, + SMTPTLSReport, +) +from parsedmarc.utils import ( + convert_outlook_msg, + get_base_domain, + get_ip_address_info, + human_timestamp_to_datetime, + is_outlook_msg, + parse_email, + timestamp_to_human, ) - -from parsedmarc.constants import __version__ -from parsedmarc.utils import get_base_domain, get_ip_address_info -from parsedmarc.utils import is_outlook_msg, convert_outlook_msg -from parsedmarc.utils import parse_email -from parsedmarc.utils import timestamp_to_human, human_timestamp_to_datetime - logger.debug("parsedmarc v{0}".format(__version__)) @@ -327,8 +345,8 @@ def _append_parsed_record( - parsed_record: OrderedDict[str, Any], - records: List[OrderedDict[str, Any]], + parsed_record: dict[str, Any], + records: list[dict[str, Any]], begin_dt: datetime, end_dt: datetime, normalize: bool, @@ -371,22 +389,22 @@ def _parse_report_record( - record: OrderedDict, + record: dict[str, Any], *, ip_db_path: Optional[str] = None, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, - offline: Optional[bool] = False, + offline: bool = False, nameservers: Optional[list[str]] = None, - dns_timeout: Optional[float] = 2.0, -) -> OrderedDict[str, Any]: + dns_timeout: float = 2.0, +) -> dict[str, Any]: """ Converts a record from a DMARC aggregate report into a more consistent format Args: - record (OrderedDict): The record to convert + record (dict): The record to convert always_use_local_files (bool): Do not download files reverse_dns_map_path (str): Path to a reverse DNS map file reverse_dns_map_url (str): URL to a reverse DNS map file @@ -397,10 +415,10 @@ dns_timeout (float): Sets the DNS timeout in seconds Returns: - OrderedDict: The converted record + dict: The converted record """ record = record.copy() - new_record = OrderedDict() + new_record: dict[str, Any] = {} if record["row"]["source_ip"] is None: raise ValueError("Source IP address is empty") new_record_source = get_ip_address_info( @@ -418,14 +436,12 @@ new_record["source"] = new_record_source new_record["count"] = int(record["row"]["count"]) policy_evaluated = record["row"]["policy_evaluated"].copy() - new_policy_evaluated = OrderedDict( - [ - ("disposition", "none"), - ("dkim", "fail"), - ("spf", "fail"), - ("policy_override_reasons", []), - ] - ) + new_policy_evaluated: dict[str, Any] = { + "disposition": "none", + "dkim": "fail", + "spf": "fail", + "policy_override_reasons": [], + } if "disposition" in policy_evaluated: new_policy_evaluated["disposition"] = policy_evaluated["disposition"] if new_policy_evaluated["disposition"].strip().lower() == "pass": @@ -462,7 +478,7 @@ new_record["identifiers"] = record["identities"].copy() else: new_record["identifiers"] = record["identifiers"].copy() - new_record["auth_results"] = OrderedDict([("dkim", []), ("spf", [])]) + new_record["auth_results"] = {"dkim": [], "spf": []} if type(new_record["identifiers"]["header_from"]) is str: lowered_from = new_record["identifiers"]["header_from"].lower() else: @@ -481,7 +497,7 @@ auth_results["dkim"] = [auth_results["dkim"]] for result in auth_results["dkim"]: if "domain" in result and result["domain"] is not None: - new_result = OrderedDict([("domain", result["domain"])]) + new_result: dict[str, Any] = {"domain": result["domain"]} if "selector" in result and result["selector"] is not None: new_result["selector"] = result["selector"] else: @@ -496,7 +512,7 @@ auth_results["spf"] = [auth_results["spf"]] for result in auth_results["spf"]: if "domain" in result and result["domain"] is not None: - new_result = OrderedDict([("domain", result["domain"])]) + new_result: dict[str, Any] = {"domain": result["domain"]} if "scope" in result and result["scope"] is not None: new_result["scope"] = result["scope"] else: @@ -536,10 +552,10 @@ def _parse_smtp_tls_failure_details(failure_details: dict[str, Any]): try: - new_failure_details = OrderedDict( - result_type=failure_details["result-type"], - failed_session_count=failure_details["failed-session-count"], - ) + new_failure_details: dict[str, Any] = { + "result_type": failure_details["result-type"], + "failed_session_count": failure_details["failed-session-count"], + } if "sending-mta-ip" in failure_details: new_failure_details["sending_mta_ip"] = failure_details["sending-mta-ip"] @@ -570,7 +586,7 @@ raise InvalidSMTPTLSReport(str(e)) -def _parse_smtp_tls_report_policy(policy: OrderedDict[str, Any]): +def _parse_smtp_tls_report_policy(policy: dict[str, Any]): policy_types = ["tlsa", "sts", "no-policy-found"] try: policy_domain = policy["policy"]["policy-domain"] @@ -578,7 +594,10 @@ failure_details = [] if policy_type not in policy_types: raise InvalidSMTPTLSReport(f"Invalid policy type {policy_type}") - new_policy = OrderedDict(policy_domain=policy_domain, policy_type=policy_type) + new_policy: dict[str, Any] = { + "policy_domain": policy_domain, + "policy_type": policy_type, + } if "policy-string" in policy["policy"]: if isinstance(policy["policy"]["policy-string"], list): if len(policy["policy"]["policy-string"]) > 0: @@ -609,7 +628,7 @@
[docs] -def parse_smtp_tls_report_json(report: str): +def parse_smtp_tls_report_json(report: Union[str, bytes]) -> SMTPTLSReport: """Parses and validates an SMTP TLS report""" required_fields = [ "organization-name", @@ -620,6 +639,9 @@ ] try: + if isinstance(report, bytes): + report = report.decode("utf-8", errors="replace") + policies = [] report_dict = json.loads(report) for required_field in required_fields: @@ -631,19 +653,19 @@ for policy in report_dict["policies"]: policies.append(_parse_smtp_tls_report_policy(policy)) - new_report = OrderedDict( - organization_name=report_dict["organization-name"], - begin_date=report_dict["date-range"]["start-datetime"], - end_date=report_dict["date-range"]["end-datetime"], - contact_info=report_dict["contact-info"], - report_id=report_dict["report-id"], - policies=policies, - ) + new_report: SMTPTLSReport = { + "organization_name": report_dict["organization-name"], + "begin_date": report_dict["date-range"]["start-datetime"], + "end_date": report_dict["date-range"]["end-datetime"], + "contact_info": report_dict["contact-info"], + "report_id": report_dict["report-id"], + "policies": policies, + } return new_report except KeyError as e: - InvalidSMTPTLSReport(f"Missing required field: {e}") + raise InvalidSMTPTLSReport(f"Missing required field: {e}") except Exception as e: raise InvalidSMTPTLSReport(str(e))
@@ -652,22 +674,22 @@
[docs] def parsed_smtp_tls_reports_to_csv_rows( - reports: Union[OrderedDict[str, Any], List[OrderedDict[str, Any]]], -): + reports: Union[SMTPTLSReport, list[SMTPTLSReport]], +) -> list[dict[str, Any]]: """Converts one oor more parsed SMTP TLS reports into a list of single - layer OrderedDict objects suitable for use in a CSV""" - if type(reports) is OrderedDict: + layer dict objects suitable for use in a CSV""" + if isinstance(reports, dict): reports = [reports] rows = [] for report in reports: - common_fields = OrderedDict( - organization_name=report["organization_name"], - begin_date=report["begin_date"], - end_date=report["end_date"], - report_id=report["report_id"], - ) - record = common_fields.copy() + common_fields: dict[str, Any] = { + "organization_name": report["organization_name"], + "begin_date": report["begin_date"], + "end_date": report["end_date"], + "report_id": report["report_id"], + } + record: dict[str, Any] = common_fields.copy() for policy in report["policies"]: if "policy_strings" in policy: record["policy_strings"] = "|".join(policy["policy_strings"]) @@ -691,7 +713,9 @@
[docs] -def parsed_smtp_tls_reports_to_csv(reports: OrderedDict[str, Any]) -> str: +def parsed_smtp_tls_reports_to_csv( + reports: Union[SMTPTLSReport, list[SMTPTLSReport]], +) -> str: """ Converts one or more parsed SMTP TLS reports to flat CSV format, including headers @@ -743,16 +767,16 @@ xml: str, *, ip_db_path: Optional[str] = None, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, - offline: Optional[bool] = False, + offline: bool = False, nameservers: Optional[list[str]] = None, - timeout: Optional[float] = 2.0, + timeout: float = 2.0, keep_alive: Optional[Callable] = None, normalize_timespan_threshold_hours: float = 24.0, -) -> OrderedDict[str, Any]: - """Parses a DMARC XML report string and returns a consistent OrderedDict +) -> AggregateReport: + """Parses a DMARC XML report string and returns a consistent dict Args: xml (str): A string of DMARC aggregate report XML @@ -768,7 +792,7 @@ normalize_timespan_threshold_hours (float): Normalize timespans beyond this Returns: - OrderedDict: The parsed aggregate DMARC report + dict: The parsed aggregate DMARC report """ errors = [] # Parse XML and recover from errors @@ -800,8 +824,8 @@ schema = "draft" if "version" in report: schema = report["version"] - new_report = OrderedDict([("xml_schema", schema)]) - new_report_metadata = OrderedDict() + new_report: dict[str, Any] = {"xml_schema": schema} + new_report_metadata: dict[str, Any] = {} if report_metadata["org_name"] is None: if report_metadata["email"] is not None: report_metadata["org_name"] = report_metadata["email"].split("@")[-1] @@ -862,7 +886,7 @@ policy_published = report["policy_published"] if type(policy_published) is list: policy_published = policy_published[0] - new_policy_published = OrderedDict() + new_policy_published: dict[str, Any] = {} new_policy_published["domain"] = policy_published["domain"] adkim = "r" if "adkim" in policy_published: @@ -940,7 +964,7 @@ new_report["records"] = records - return new_report + return cast(AggregateReport, new_report) except expat.ExpatError as error: raise InvalidAggregateReport("Invalid XML: {0}".format(error.__str__())) @@ -957,7 +981,7 @@
[docs] -def extract_report(content: Union[bytes, str, IO[Any]]) -> str: +def extract_report(content: Union[bytes, str, BinaryIO]) -> str: """ Extracts text from a zip or gzip file, as a base64-encoded string, file-like object, or bytes. @@ -970,43 +994,73 @@ str: The extracted text """ - file_object = None + file_object: Optional[BinaryIO] = None + header: bytes try: if isinstance(content, str): try: file_object = BytesIO(b64decode(content)) except binascii.Error: return content - elif type(content) is bytes: - file_object = BytesIO(content) + header = file_object.read(6) + file_object.seek(0) + elif isinstance(content, (bytes)): + file_object = BytesIO(bytes(content)) + header = file_object.read(6) + file_object.seek(0) else: - file_object = content + stream = cast(BinaryIO, content) + seekable = getattr(stream, "seekable", None) + can_seek = False + if callable(seekable): + try: + can_seek = bool(seekable()) + except Exception: + can_seek = False - header = file_object.read(6) - file_object.seek(0) - if header.startswith(MAGIC_ZIP): + if can_seek: + header_raw = stream.read(6) + if isinstance(header_raw, str): + raise ParserError("File objects must be opened in binary (rb) mode") + header = bytes(header_raw) + stream.seek(0) + file_object = stream + else: + header_raw = stream.read(6) + if isinstance(header_raw, str): + raise ParserError("File objects must be opened in binary (rb) mode") + header = bytes(header_raw) + remainder = stream.read() + file_object = BytesIO(header + bytes(remainder)) + + if file_object is None: + raise ParserError("Invalid report content") + + if header[: len(MAGIC_ZIP)] == MAGIC_ZIP: _zip = zipfile.ZipFile(file_object) report = _zip.open(_zip.namelist()[0]).read().decode(errors="ignore") - elif header.startswith(MAGIC_GZIP): + elif header[: len(MAGIC_GZIP)] == MAGIC_GZIP: report = zlib.decompress(file_object.read(), zlib.MAX_WBITS | 16).decode( errors="ignore" ) - elif header.startswith(MAGIC_XML) or header.startswith(MAGIC_JSON): + elif ( + header[: len(MAGIC_XML)] == MAGIC_XML + or header[: len(MAGIC_JSON)] == MAGIC_JSON + ): report = file_object.read().decode(errors="ignore") else: - file_object.close() raise ParserError("Not a valid zip, gzip, json, or xml file") - file_object.close() - except UnicodeDecodeError: - if file_object: - file_object.close() raise ParserError("File objects must be opened in binary (rb) mode") except Exception as error: - if file_object: - file_object.close() raise ParserError("Invalid archive file: {0}".format(error.__str__())) + finally: + if file_object: + try: + file_object.close() + except Exception: + pass return report
@@ -1027,18 +1081,18 @@
[docs] def parse_aggregate_report_file( - _input: Union[str, bytes, IO[Any]], + _input: Union[str, bytes, BinaryIO], *, - offline: Optional[bool] = False, - always_use_local_files: Optional[bool] = None, + offline: bool = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, ip_db_path: Optional[str] = None, nameservers: Optional[list[str]] = None, - dns_timeout: Optional[float] = 2.0, + dns_timeout: float = 2.0, keep_alive: Optional[Callable] = None, - normalize_timespan_threshold_hours: Optional[float] = 24.0, -) -> OrderedDict[str, any]: + normalize_timespan_threshold_hours: float = 24.0, +) -> AggregateReport: """Parses a file at the given path, a file-like object. or bytes as an aggregate DMARC report @@ -1056,7 +1110,7 @@ normalize_timespan_threshold_hours (float): Normalize timespans beyond this Returns: - OrderedDict: The parsed DMARC aggregate report + dict: The parsed DMARC aggregate report """ try: @@ -1082,7 +1136,7 @@
[docs] def parsed_aggregate_reports_to_csv_rows( - reports: list[OrderedDict[str, Any]], + reports: Union[AggregateReport, list[AggregateReport]], ) -> list[dict[str, Any]]: """ Converts one or more parsed aggregate reports to list of dicts in flat CSV @@ -1099,7 +1153,7 @@ def to_str(obj): return str(obj).lower() - if type(reports) is OrderedDict: + if isinstance(reports, dict): reports = [reports] rows = [] @@ -1124,7 +1178,7 @@ pct = report["policy_published"]["pct"] fo = report["policy_published"]["fo"] - report_dict = dict( + report_dict: dict[str, Any] = dict( xml_schema=xml_schema, org_name=org_name, org_email=org_email, @@ -1144,7 +1198,7 @@ ) for record in report["records"]: - row = report_dict.copy() + row: dict[str, Any] = report_dict.copy() row["begin_date"] = record["interval_begin"] row["end_date"] = record["interval_end"] row["source_ip_address"] = record["source"]["ip_address"] @@ -1210,7 +1264,9 @@
[docs] -def parsed_aggregate_reports_to_csv(reports: list[OrderedDict[str, Any]]) -> str: +def parsed_aggregate_reports_to_csv( + reports: Union[AggregateReport, list[AggregateReport]], +) -> str: """ Converts one or more parsed aggregate reports to flat CSV format, including headers @@ -1284,17 +1340,17 @@ sample: str, msg_date: datetime, *, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, - offline: Optional[bool] = False, + offline: bool = False, ip_db_path: Optional[str] = None, nameservers: Optional[list[str]] = None, - dns_timeout: Optional[float] = 2.0, - strip_attachment_payloads: Optional[bool] = False, -) -> OrderedDict[str, Any]: + dns_timeout: float = 2.0, + strip_attachment_payloads: bool = False, +) -> ForensicReport: """ - Converts a DMARC forensic report and sample to a ``OrderedDict`` + Converts a DMARC forensic report and sample to a dict Args: feedback_report (str): A message's feedback report as a string @@ -1312,12 +1368,12 @@ forensic report results Returns: - OrderedDict: A parsed report and sample + dict: A parsed report and sample """ delivery_results = ["delivered", "spam", "policy", "reject", "other"] try: - parsed_report = OrderedDict() + parsed_report: dict[str, Any] = {} report_values = feedback_report_regex.findall(feedback_report) for report_value in report_values: key = report_value[0].lower().replace("-", "_") @@ -1411,7 +1467,7 @@ parsed_report["sample"] = sample parsed_report["parsed_sample"] = parsed_sample - return parsed_report + return cast(ForensicReport, parsed_report) except KeyError as error: raise InvalidForensicReport("Missing value: {0}".format(error.__str__())) @@ -1423,7 +1479,9 @@
[docs] -def parsed_forensic_reports_to_csv_rows(reports: list[OrderedDict[str, Any]]): +def parsed_forensic_reports_to_csv_rows( + reports: Union[ForensicReport, list[ForensicReport]], +) -> list[dict[str, Any]]: """ Converts one or more parsed forensic reports to a list of dicts in flat CSV format @@ -1434,13 +1492,13 @@ Returns: list: Parsed forensic report data as a list of dicts in flat CSV format """ - if type(reports) is OrderedDict: + if isinstance(reports, dict): reports = [reports] rows = [] for report in reports: - row = report.copy() + row: dict[str, Any] = dict(report) row["source_ip_address"] = report["source"]["ip_address"] row["source_reverse_dns"] = report["source"]["reverse_dns"] row["source_base_domain"] = report["source"]["base_domain"] @@ -1448,7 +1506,7 @@ row["source_type"] = report["source"]["type"] row["source_country"] = report["source"]["country"] del row["source"] - row["subject"] = report["parsed_sample"]["subject"] + row["subject"] = report["parsed_sample"].get("subject") row["auth_failure"] = ",".join(report["auth_failure"]) authentication_mechanisms = report["authentication_mechanisms"] row["authentication_mechanisms"] = ",".join(authentication_mechanisms) @@ -1462,7 +1520,9 @@
[docs] -def parsed_forensic_reports_to_csv(reports: list[dict[str, Any]]) -> str: +def parsed_forensic_reports_to_csv( + reports: Union[ForensicReport, list[ForensicReport]], +) -> str: """ Converts one or more parsed forensic reports to flat CSV format, including headers @@ -1506,9 +1566,9 @@ rows = parsed_forensic_reports_to_csv_rows(reports) for row in rows: - new_row = {} - for key in new_row.keys(): - new_row[key] = row[key] + new_row: dict[str, Any] = {} + for key in fields: + new_row[key] = row.get(key) csv_writer.writerow(new_row) return csv_file.getvalue()
@@ -1520,17 +1580,17 @@ def parse_report_email( input_: Union[bytes, str], *, - offline: Optional[bool] = False, + offline: bool = False, ip_db_path: Optional[str] = None, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, - nameservers: list[str] = None, - dns_timeout: Optional[float] = 2.0, - strip_attachment_payloads: Optional[bool] = False, - keep_alive: Optional[callable] = None, - normalize_timespan_threshold_hours: Optional[float] = 24.0, -) -> OrderedDict[str, Any]: + nameservers: Optional[list[str]] = None, + dns_timeout: float = 2.0, + strip_attachment_payloads: bool = False, + keep_alive: Optional[Callable] = None, + normalize_timespan_threshold_hours: float = 24.0, +) -> ParsedReport: """ Parses a DMARC report from an email @@ -1549,23 +1609,36 @@ normalize_timespan_threshold_hours (float): Normalize timespans beyond this Returns: - OrderedDict: + dict: * ``report_type``: ``aggregate`` or ``forensic`` * ``report``: The parsed report """ - result = None + result: Optional[ParsedReport] = None + msg_date: datetime = datetime.now(timezone.utc) try: - if is_outlook_msg(input_): - input_ = convert_outlook_msg(input_) - if type(input_) is bytes: - input_ = input_.decode(encoding="utf8", errors="replace") - msg = mailparser.parse_from_string(input_) + input_data: Union[str, bytes, bytearray, memoryview] = input_ + if isinstance(input_data, (bytes, bytearray, memoryview)): + input_bytes = bytes(input_data) + if is_outlook_msg(input_bytes): + converted = convert_outlook_msg(input_bytes) + if isinstance(converted, str): + input_str = converted + else: + input_str = bytes(converted).decode( + encoding="utf8", errors="replace" + ) + else: + input_str = input_bytes.decode(encoding="utf8", errors="replace") + else: + input_str = input_data + + msg = mailparser.parse_from_string(input_str) msg_headers = json.loads(msg.headers_json) - date = email.utils.format_datetime(datetime.now(timezone.utc)) if "Date" in msg_headers: - date = human_timestamp_to_datetime(msg_headers["Date"]) - msg = email.message_from_string(input_) + msg_date = human_timestamp_to_datetime(msg_headers["Date"]) + date = email.utils.format_datetime(msg_date) + msg = email.message_from_string(input_str) except Exception as e: raise ParserError(e.__str__()) @@ -1580,10 +1653,10 @@ subject = msg_headers["Subject"] for part in msg.walk(): content_type = part.get_content_type().lower() - payload = part.get_payload() - if not isinstance(payload, list): - payload = [payload] - payload = payload[0].__str__() + payload_obj = part.get_payload() + if not isinstance(payload_obj, list): + payload_obj = [payload_obj] + payload = str(payload_obj[0]) if content_type.startswith("multipart/"): continue if content_type == "text/html": @@ -1604,17 +1677,13 @@ sample = payload elif content_type == "application/tlsrpt+json": if not payload.strip().startswith("{"): - payload = str(b64decode(payload)) + payload = b64decode(payload).decode("utf-8", errors="replace") smtp_tls_report = parse_smtp_tls_report_json(payload) - return OrderedDict( - [("report_type", "smtp_tls"), ("report", smtp_tls_report)] - ) + return {"report_type": "smtp_tls", "report": smtp_tls_report} elif content_type == "application/tlsrpt+gzip": payload = extract_report(payload) smtp_tls_report = parse_smtp_tls_report_json(payload) - return OrderedDict( - [("report_type", "smtp_tls"), ("report", smtp_tls_report)] - ) + return {"report_type": "smtp_tls", "report": smtp_tls_report} elif content_type == "text/plain": if "A message claiming to be from you has failed" in payload: try: @@ -1638,19 +1707,21 @@ logger.debug(sample) else: try: - payload = b64decode(payload) - if payload.startswith(MAGIC_ZIP) or payload.startswith(MAGIC_GZIP): - payload = extract_report(payload) - if isinstance(payload, bytes): - payload = payload.decode("utf-8", errors="replace") - if payload.strip().startswith("{"): - smtp_tls_report = parse_smtp_tls_report_json(payload) - result = OrderedDict( - [("report_type", "smtp_tls"), ("report", smtp_tls_report)] - ) - elif payload.strip().startswith("<"): + payload_bytes = b64decode(payload) + if payload_bytes.startswith(MAGIC_ZIP) or payload_bytes.startswith( + MAGIC_GZIP + ): + payload_text = extract_report(payload_bytes) + else: + payload_text = payload_bytes.decode("utf-8", errors="replace") + + if payload_text.strip().startswith("{"): + smtp_tls_report = parse_smtp_tls_report_json(payload_text) + result = {"report_type": "smtp_tls", "report": smtp_tls_report} + return result + elif payload_text.strip().startswith("<"): aggregate_report = parse_aggregate_report_xml( - payload, + payload_text, ip_db_path=ip_db_path, always_use_local_files=always_use_local_files, reverse_dns_map_path=reverse_dns_map_path, @@ -1661,9 +1732,7 @@ keep_alive=keep_alive, normalize_timespan_threshold_hours=normalize_timespan_threshold_hours, ) - result = OrderedDict( - [("report_type", "aggregate"), ("report", aggregate_report)] - ) + result = {"report_type": "aggregate", "report": aggregate_report} return result @@ -1687,7 +1756,7 @@ forensic_report = parse_forensic_report( feedback_report, sample, - date, + msg_date, offline=offline, ip_db_path=ip_db_path, always_use_local_files=always_use_local_files, @@ -1707,36 +1776,38 @@ except Exception as e: raise InvalidForensicReport(e.__str__()) - result = OrderedDict([("report_type", "forensic"), ("report", forensic_report)]) + result = {"report_type": "forensic", "report": forensic_report} return result if result is None: error = 'Message with subject "{0}" is not a valid report'.format(subject) - raise InvalidDMARCReport(error)
+ raise InvalidDMARCReport(error) + + return result
[docs] def parse_report_file( - input_: Union[bytes, str, IO[Any]], + input_: Union[bytes, str, BinaryIO], *, nameservers: Optional[list[str]] = None, - dns_timeout: Optional[float] = 2.0, - strip_attachment_payloads: Optional[bool] = False, + dns_timeout: float = 2.0, + strip_attachment_payloads: bool = False, ip_db_path: Optional[str] = None, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, - offline: Optional[bool] = False, + offline: bool = False, keep_alive: Optional[Callable] = None, - normalize_timespan_threshold_hours: Optional[float] = 24, -) -> OrderedDict[str, Any]: + normalize_timespan_threshold_hours: float = 24, +) -> ParsedReport: """Parses a DMARC aggregate or forensic file at the given path, a file-like object. or bytes Args: - input_ (str | bytes | IO): A path to a file, a file like object, or bytes + input_ (str | bytes | BinaryIO): A path to a file, a file like object, or bytes nameservers (list): A list of one or more nameservers to use (Cloudflare's public DNS resolvers by default) dns_timeout (float): Sets the DNS timeout in seconds @@ -1750,13 +1821,14 @@ keep_alive (callable): Keep alive function Returns: - OrderedDict: The parsed DMARC report + dict: The parsed DMARC report """ - if type(input_) is str: + file_object: BinaryIO + if isinstance(input_, str): logger.debug("Parsing {0}".format(input_)) file_object = open(input_, "rb") - elif type(input_) is bytes: - file_object = BytesIO(input_) + elif isinstance(input_, (bytes, bytearray, memoryview)): + file_object = BytesIO(bytes(input_)) else: file_object = input_ @@ -1764,6 +1836,9 @@ file_object.close() if content.startswith(MAGIC_ZIP) or content.startswith(MAGIC_GZIP): content = extract_report(content) + + results: Optional[ParsedReport] = None + try: report = parse_aggregate_report_file( content, @@ -1777,14 +1852,13 @@ keep_alive=keep_alive, normalize_timespan_threshold_hours=normalize_timespan_threshold_hours, ) - results = OrderedDict([("report_type", "aggregate"), ("report", report)]) + results = {"report_type": "aggregate", "report": report} except InvalidAggregateReport: try: report = parse_smtp_tls_report_json(content) - results = OrderedDict([("report_type", "smtp_tls"), ("report", report)]) + results = {"report_type": "smtp_tls", "report": report} except InvalidSMTPTLSReport: try: - sa = strip_attachment_payloads results = parse_report_email( content, ip_db_path=ip_db_path, @@ -1794,12 +1868,15 @@ offline=offline, nameservers=nameservers, dns_timeout=dns_timeout, - strip_attachment_payloads=sa, + strip_attachment_payloads=strip_attachment_payloads, keep_alive=keep_alive, normalize_timespan_threshold_hours=normalize_timespan_threshold_hours, ) except InvalidDMARCReport: raise ParserError("Not a valid report") + + if results is None: + raise ParserError("Not a valid report") return results
@@ -1810,15 +1887,15 @@ input_: str, *, nameservers: Optional[list[str]] = None, - dns_timeout: Optional[float] = 2.0, - strip_attachment_payloads: Optional[bool] = False, + dns_timeout: float = 2.0, + strip_attachment_payloads: bool = False, ip_db_path: Optional[str] = None, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, - offline: Optional[bool] = False, - normalize_timespan_threshold_hours: Optional[float] = 24.0, -) -> OrderedDict[str, OrderedDict[str, Any]]: + offline: bool = False, + normalize_timespan_threshold_hours: float = 24.0, +) -> ParsingResults: """Parses a mailbox in mbox format containing e-mails with attached DMARC reports @@ -1837,12 +1914,12 @@ normalize_timespan_threshold_hours (float): Normalize timespans beyond this Returns: - OrderedDict: Lists of ``aggregate_reports``, ``forensic_reports``, and ``smtp_tls_reports`` + dict: Lists of ``aggregate_reports``, ``forensic_reports``, and ``smtp_tls_reports`` """ - aggregate_reports = [] - forensic_reports = [] - smtp_tls_reports = [] + aggregate_reports: list[AggregateReport] = [] + forensic_reports: list[ForensicReport] = [] + smtp_tls_reports: list[SMTPTLSReport] = [] try: mbox = mailbox.mbox(input_) message_keys = mbox.keys() @@ -1886,13 +1963,11 @@ logger.warning(error.__str__()) except mailbox.NoSuchMailboxError: raise InvalidDMARCReport("Mailbox {0} does not exist".format(input_)) - return OrderedDict( - [ - ("aggregate_reports", aggregate_reports), - ("forensic_reports", forensic_reports), - ("smtp_tls_reports", smtp_tls_reports), - ] - )
+ return { + "aggregate_reports": aggregate_reports, + "forensic_reports": forensic_reports, + "smtp_tls_reports": smtp_tls_reports, + }
@@ -1901,24 +1976,24 @@ def get_dmarc_reports_from_mailbox( connection: MailboxConnection, *, - reports_folder: Optional[str] = "INBOX", - archive_folder: Optional[str] = "Archive", - delete: Optional[bool] = False, - test: Optional[bool] = False, + reports_folder: str = "INBOX", + archive_folder: str = "Archive", + delete: bool = False, + test: bool = False, ip_db_path: Optional[str] = None, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, - offline: Optional[bool] = False, + offline: bool = False, nameservers: Optional[list[str]] = None, - dns_timeout: Optional[float] = 6.0, - strip_attachment_payloads: Optional[bool] = False, - results: Optional[OrderedDict[str, Any]] = None, - batch_size: Optional[int] = 10, - since: Optional[datetime] = None, - create_folders: Optional[bool] = True, - normalize_timespan_threshold_hours: Optional[float] = 24, -) -> OrderedDict[str, OrderedDict[str, Any]]: + dns_timeout: float = 6.0, + strip_attachment_payloads: bool = False, + results: Optional[ParsingResults] = None, + batch_size: int = 10, + since: Optional[Union[datetime, date, str]] = None, + create_folders: bool = True, + normalize_timespan_threshold_hours: float = 24, +) -> ParsingResults: """ Fetches and parses DMARC reports from a mailbox @@ -1947,7 +2022,7 @@ normalize_timespan_threshold_hours (float): Normalize timespans beyond this Returns: - OrderedDict: Lists of ``aggregate_reports``, ``forensic_reports``, and ``smtp_tls_reports`` + dict: Lists of ``aggregate_reports``, ``forensic_reports``, and ``smtp_tls_reports`` """ if delete and test: raise ValueError("delete and test options are mutually exclusive") @@ -1956,11 +2031,11 @@ raise ValueError("Must supply a connection") # current_time useful to fetch_messages later in the program - current_time = None + current_time: Optional[Union[datetime, date, str]] = None - aggregate_reports = [] - forensic_reports = [] - smtp_tls_reports = [] + aggregate_reports: list[AggregateReport] = [] + forensic_reports: list[ForensicReport] = [] + smtp_tls_reports: list[SMTPTLSReport] = [] aggregate_report_msg_uids = [] forensic_report_msg_uids = [] smtp_tls_msg_uids = [] @@ -1981,7 +2056,7 @@ connection.create_folder(smtp_tls_reports_folder) connection.create_folder(invalid_reports_folder) - if since: + if since and isinstance(since, str): _since = 1440 # default one day if re.match(r"\d+[mhdw]$", since): s = re.split(r"(\d+)", since) @@ -2042,13 +2117,16 @@ i + 1, message_limit, msg_uid ) ) - if isinstance(mailbox, MSGraphConnection): - if test: - msg_content = connection.fetch_message(msg_uid, mark_read=False) - else: - msg_content = connection.fetch_message(msg_uid, mark_read=True) + message_id: Union[int, str] + if isinstance(connection, IMAPConnection): + message_id = int(msg_uid) + msg_content = connection.fetch_message(message_id) + elif isinstance(connection, MSGraphConnection): + message_id = str(msg_uid) + msg_content = connection.fetch_message(message_id, mark_read=not test) else: - msg_content = connection.fetch_message(msg_uid) + message_id = str(msg_uid) if not isinstance(msg_uid, str) else msg_uid + msg_content = connection.fetch_message(message_id) try: sa = strip_attachment_payloads parsed_email = parse_report_email( @@ -2075,26 +2153,32 @@ logger.debug( f"Skipping duplicate aggregate report with ID: {report_id}" ) - aggregate_report_msg_uids.append(msg_uid) + aggregate_report_msg_uids.append(message_id) elif parsed_email["report_type"] == "forensic": forensic_reports.append(parsed_email["report"]) - forensic_report_msg_uids.append(msg_uid) + forensic_report_msg_uids.append(message_id) elif parsed_email["report_type"] == "smtp_tls": smtp_tls_reports.append(parsed_email["report"]) - smtp_tls_msg_uids.append(msg_uid) + smtp_tls_msg_uids.append(message_id) except ParserError as error: logger.warning(error.__str__()) if not test: if delete: logger.debug("Deleting message UID {0}".format(msg_uid)) - connection.delete_message(msg_uid) + if isinstance(connection, IMAPConnection): + connection.delete_message(int(message_id)) + else: + connection.delete_message(str(message_id)) else: logger.debug( "Moving message UID {0} to {1}".format( msg_uid, invalid_reports_folder ) ) - connection.move_message(msg_uid, invalid_reports_folder) + if isinstance(connection, IMAPConnection): + connection.move_message(int(message_id), invalid_reports_folder) + else: + connection.move_message(str(message_id), invalid_reports_folder) if not test: if delete: @@ -2181,13 +2265,11 @@ except Exception as e: e = "Error moving message UID {0}: {1}".format(msg_uid, e) logger.error("Mailbox error: {0}".format(e)) - results = OrderedDict( - [ - ("aggregate_reports", aggregate_reports), - ("forensic_reports", forensic_reports), - ("smtp_tls_reports", smtp_tls_reports), - ] - ) + results = { + "aggregate_reports": aggregate_reports, + "forensic_reports": forensic_reports, + "smtp_tls_reports": smtp_tls_reports, + } if current_time: total_messages = len( @@ -2227,21 +2309,21 @@ mailbox_connection: MailboxConnection, callback: Callable, *, - reports_folder: Optional[str] = "INBOX", - archive_folder: Optional[str] = "Archive", - delete: Optional[bool] = False, - test: Optional[bool] = False, - check_timeout: Optional[int] = 30, + reports_folder: str = "INBOX", + archive_folder: str = "Archive", + delete: bool = False, + test: bool = False, + check_timeout: int = 30, ip_db_path: Optional[str] = None, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_path: Optional[str] = None, reverse_dns_map_url: Optional[str] = None, - offline: Optional[bool] = False, + offline: bool = False, nameservers: Optional[list[str]] = None, - dns_timeout: Optional[float] = 6.0, - strip_attachment_payloads: Optional[bool] = False, - batch_size: Optional[int] = None, - normalize_timespan_threshold_hours: Optional[float] = 24, + dns_timeout: float = 6.0, + strip_attachment_payloads: bool = False, + batch_size: int = 10, + normalize_timespan_threshold_hours: float = 24, ): """ Watches the mailbox for new messages and @@ -2271,7 +2353,6 @@ """ def check_callback(connection): - sa = strip_attachment_payloads res = get_dmarc_reports_from_mailbox( connection=connection, reports_folder=reports_folder, @@ -2285,7 +2366,7 @@ offline=offline, nameservers=nameservers, dns_timeout=dns_timeout, - strip_attachment_payloads=sa, + strip_attachment_payloads=strip_attachment_payloads, batch_size=batch_size, create_folders=False, normalize_timespan_threshold_hours=normalize_timespan_threshold_hours, @@ -2296,7 +2377,14 @@ -def append_json(filename, reports): +def append_json( + filename: str, + reports: Union[ + Sequence[AggregateReport], + Sequence[ForensicReport], + Sequence[SMTPTLSReport], + ], +) -> None: with open(filename, "a+", newline="\n", encoding="utf-8") as output: output_json = json.dumps(reports, ensure_ascii=False, indent=2) if output.seek(0, os.SEEK_END) != 0: @@ -2319,7 +2407,7 @@ output.write(output_json) -def append_csv(filename, csv): +def append_csv(filename: str, csv: str) -> None: with open(filename, "a+", newline="\n", encoding="utf-8") as output: if output.seek(0, os.SEEK_END) != 0: # strip the headers from the CSV @@ -2334,21 +2422,21 @@
[docs] def save_output( - results: OrderedDict[str, Any], + results: ParsingResults, *, - output_directory: Optional[str] = "output", - aggregate_json_filename: Optional[str] = "aggregate.json", - forensic_json_filename: Optional[str] = "forensic.json", - smtp_tls_json_filename: Optional[str] = "smtp_tls.json", - aggregate_csv_filename: Optional[str] = "aggregate.csv", - forensic_csv_filename: Optional[str] = "forensic.csv", - smtp_tls_csv_filename: Optional[str] = "smtp_tls.csv", + output_directory: str = "output", + aggregate_json_filename: str = "aggregate.json", + forensic_json_filename: str = "forensic.json", + smtp_tls_json_filename: str = "smtp_tls.json", + aggregate_csv_filename: str = "aggregate.csv", + forensic_csv_filename: str = "forensic.csv", + smtp_tls_csv_filename: str = "smtp_tls.csv", ): """ Save report data in the given directory Args: - results (OrderedDict): Parsing results + results: Parsing results output_directory (str): The path to the directory to save in aggregate_json_filename (str): Filename for the aggregate JSON file forensic_json_filename (str): Filename for the forensic JSON file @@ -2405,7 +2493,11 @@ sample = forensic_report["sample"] message_count = 0 parsed_sample = forensic_report["parsed_sample"] - subject = parsed_sample["filename_safe_subject"] + subject = ( + parsed_sample.get("filename_safe_subject") + or parsed_sample.get("subject") + or "sample" + ) filename = subject while filename in sample_filenames: @@ -2423,12 +2515,12 @@
[docs] -def get_report_zip(results: OrderedDict[str, Any]) -> bytes: +def get_report_zip(results: ParsingResults) -> bytes: """ Creates a zip file of parsed report output Args: - results (OrderedDict): The parsed results + results: The parsed results Returns: bytes: zip file bytes @@ -2449,7 +2541,7 @@ storage = BytesIO() tmp_dir = tempfile.mkdtemp() try: - save_output(results, tmp_dir) + save_output(results, output_directory=tmp_dir) with zipfile.ZipFile(storage, "w", zipfile.ZIP_DEFLATED) as zip_file: for root, dirs, files in os.walk(tmp_dir): for file in files: @@ -2472,27 +2564,27 @@
[docs] def email_results( - results: OrderedDict, - *, + results: ParsingResults, host: str, mail_from: str, - mail_to: str, - mail_cc: list = None, - mail_bcc: list = None, + mail_to: Optional[list[str]], + *, + mail_cc: Optional[list[str]] = None, + mail_bcc: Optional[list[str]] = None, port: int = 0, require_encryption: bool = False, verify: bool = True, - username: str = None, - password: str = None, - subject: str = None, - attachment_filename: str = None, - message: str = None, + username: Optional[str] = None, + password: Optional[str] = None, + subject: Optional[str] = None, + attachment_filename: Optional[str] = None, + message: Optional[str] = None, ): """ Emails parsing results as a zip file Args: - results (OrderedDict): Parsing results + results (dict): Parsing results host (str): Mail server hostname or IP address mail_from: The value of the message from header mail_to (list): A list of addresses to mail to @@ -2507,7 +2599,7 @@ attachment_filename (str): Override the default attachment filename message (str): Override the default plain text body """ - logger.debug("Emailing report to: {0}".format(",".join(mail_to))) + logger.debug("Emailing report") date_string = datetime.now().strftime("%Y-%m-%d") if attachment_filename: if not attachment_filename.lower().endswith(".zip"): diff --git a/_modules/parsedmarc/elastic.html b/_modules/parsedmarc/elastic.html index d210d548..f962d456 100644 --- a/_modules/parsedmarc/elastic.html +++ b/_modules/parsedmarc/elastic.html @@ -5,14 +5,14 @@ - parsedmarc.elastic — parsedmarc 9.0.5 documentation + parsedmarc.elastic — parsedmarc 9.0.6 documentation - + @@ -84,30 +84,28 @@ from __future__ import annotations -from typing import Optional, Union, Any +from typing import Any, Optional, Union -from collections import OrderedDict - -from elasticsearch_dsl.search import Q +from elasticsearch.helpers import reindex from elasticsearch_dsl import ( - connections, - Object, + Boolean, + Date, Document, Index, - Nested, InnerDoc, Integer, - Text, - Boolean, Ip, - Date, + Nested, + Object, Search, + Text, + connections, ) -from elasticsearch.helpers import reindex +from elasticsearch_dsl.search import Q +from parsedmarc import InvalidForensicReport from parsedmarc.log import logger from parsedmarc.utils import human_timestamp_to_datetime -from parsedmarc import InvalidForensicReport
@@ -179,17 +177,17 @@ spf_results = Nested(_SPFResult) def add_policy_override(self, type_: str, comment: str): - self.policy_overrides.append(_PolicyOverride(type=type_, comment=comment)) + self.policy_overrides.append(_PolicyOverride(type=type_, comment=comment)) # pyright: ignore[reportCallIssue] def add_dkim_result(self, domain: str, selector: str, result: _DKIMResult): self.dkim_results.append( _DKIMResult(domain=domain, selector=selector, result=result) - ) + ) # pyright: ignore[reportCallIssue] def add_spf_result(self, domain: str, scope: str, result: _SPFResult): - self.spf_results.append(_SPFResult(domain=domain, scope=scope, result=result)) + self.spf_results.append(_SPFResult(domain=domain, scope=scope, result=result)) # pyright: ignore[reportCallIssue] - def save(self, **kwargs): + def save(self, **kwargs): # pyright: ignore[reportIncompatibleMethodOverride] self.passed_dmarc = False self.passed_dmarc = self.spf_aligned or self.dkim_aligned @@ -223,25 +221,25 @@ attachments = Nested(_EmailAttachmentDoc) def add_to(self, display_name: str, address: str): - self.to.append(_EmailAddressDoc(display_name=display_name, address=address)) + self.to.append(_EmailAddressDoc(display_name=display_name, address=address)) # pyright: ignore[reportCallIssue] def add_reply_to(self, display_name: str, address: str): self.reply_to.append( _EmailAddressDoc(display_name=display_name, address=address) - ) + ) # pyright: ignore[reportCallIssue] def add_cc(self, display_name: str, address: str): - self.cc.append(_EmailAddressDoc(display_name=display_name, address=address)) + self.cc.append(_EmailAddressDoc(display_name=display_name, address=address)) # pyright: ignore[reportCallIssue] def add_bcc(self, display_name: str, address: str): - self.bcc.append(_EmailAddressDoc(display_name=display_name, address=address)) + self.bcc.append(_EmailAddressDoc(display_name=display_name, address=address)) # pyright: ignore[reportCallIssue] def add_attachment(self, filename: str, content_type: str, sha256: str): self.attachments.append( _EmailAttachmentDoc( filename=filename, content_type=content_type, sha256=sha256 ) - ) + ) # pyright: ignore[reportCallIssue] class _ForensicReportDoc(Document): @@ -309,7 +307,7 @@ additional_information=additional_information_uri, failure_reason_code=failure_reason_code, ) - self.failure_details.append(_details) + self.failure_details.append(_details) # pyright: ignore[reportCallIssue] class _SMTPTLSReportDoc(Document): @@ -343,7 +341,7 @@ policy_string=policy_string, mx_host_patterns=mx_host_patterns, failure_details=failure_details, - ) + ) # pyright: ignore[reportCallIssue]
@@ -358,12 +356,12 @@ def set_hosts( hosts: Union[str, list[str]], *, - use_ssl: Optional[bool] = False, + use_ssl: bool = False, ssl_cert_path: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None, api_key: Optional[str] = None, - timeout: Optional[float] = 60.0, + timeout: float = 60.0, ): """ Sets the Elasticsearch hosts to use @@ -465,7 +463,7 @@ } Index(new_index_name).create() Index(new_index_name).put_mapping(doc_type=doc, body=body) - reindex(connections.get_connection(), aggregate_index_name, new_index_name) + reindex(connections.get_connection(), aggregate_index_name, new_index_name) # pyright: ignore[reportArgumentType] Index(aggregate_index_name).delete() for forensic_index in forensic_indexes: @@ -476,18 +474,18 @@
[docs] def save_aggregate_report_to_elasticsearch( - aggregate_report: OrderedDict[str, Any], + aggregate_report: dict[str, Any], index_suffix: Optional[str] = None, index_prefix: Optional[str] = None, monthly_indexes: Optional[bool] = False, - number_of_shards: Optional[int] = 1, - number_of_replicas: Optional[int] = 0, + number_of_shards: int = 1, + number_of_replicas: int = 0, ): """ Saves a parsed DMARC aggregate report to Elasticsearch Args: - aggregate_report (OrderedDict): A parsed forensic report + aggregate_report (dict): A parsed forensic report index_suffix (str): The suffix of the name of the index to save to index_prefix (str): The prefix of the name of the index to save to monthly_indexes (bool): Use monthly indexes instead of daily indexes @@ -511,11 +509,11 @@ else: index_date = begin_date.strftime("%Y-%m-%d") - org_name_query = Q(dict(match_phrase=dict(org_name=org_name))) - report_id_query = Q(dict(match_phrase=dict(report_id=report_id))) - domain_query = Q(dict(match_phrase={"published_policy.domain": domain})) - begin_date_query = Q(dict(match=dict(date_begin=begin_date))) - end_date_query = Q(dict(match=dict(date_end=end_date))) + org_name_query = Q(dict(match_phrase=dict(org_name=org_name))) # type: ignore + report_id_query = Q(dict(match_phrase=dict(report_id=report_id))) # pyright: ignore[reportArgumentType] + domain_query = Q(dict(match_phrase={"published_policy.domain": domain})) # pyright: ignore[reportArgumentType] + begin_date_query = Q(dict(match=dict(date_begin=begin_date))) # pyright: ignore[reportArgumentType] + end_date_query = Q(dict(match=dict(date_end=end_date))) # pyright: ignore[reportArgumentType] if index_suffix is not None: search_index = "dmarc_aggregate_{0}*".format(index_suffix) @@ -527,13 +525,12 @@ query = org_name_query & report_id_query & domain_query query = query & begin_date_query & end_date_query search.query = query + begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ") + end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ") try: existing = search.execute() except Exception as error_: - begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ") - end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ") - raise ElasticsearchError( "Elasticsearch's search for existing report \ error: {}".format(error_.__str__()) @@ -629,7 +626,7 @@ number_of_shards=number_of_shards, number_of_replicas=number_of_replicas ) create_indexes([index], index_settings) - agg_doc.meta.index = index + agg_doc.meta.index = index # pyright: ignore[reportOptionalMemberAccess, reportAttributeAccessIssue] try: agg_doc.save() @@ -641,7 +638,7 @@
[docs] def save_forensic_report_to_elasticsearch( - forensic_report: OrderedDict[str, Any], + forensic_report: dict[str, Any], index_suffix: Optional[Any] = None, index_prefix: Optional[str] = None, monthly_indexes: Optional[bool] = False, @@ -652,7 +649,7 @@ Saves a parsed DMARC forensic report to Elasticsearch Args: - forensic_report (OrderedDict): A parsed forensic report + forensic_report (dict): A parsed forensic report index_suffix (str): The suffix of the name of the index to save to index_prefix (str): The prefix of the name of the index to save to monthly_indexes (bool): Use monthly indexes instead of daily @@ -672,7 +669,7 @@ sample_date = forensic_report["parsed_sample"]["date"] sample_date = human_timestamp_to_datetime(sample_date) original_headers = forensic_report["parsed_sample"]["headers"] - headers = OrderedDict() + headers: dict[str, Any] = {} for original_header in original_headers: headers[original_header.lower()] = original_headers[original_header] @@ -686,7 +683,7 @@ if index_prefix is not None: search_index = "{0}{1}".format(index_prefix, search_index) search = Search(index=search_index) - q = Q(dict(match=dict(arrival_date=arrival_date_epoch_milliseconds))) + q = Q(dict(match=dict(arrival_date=arrival_date_epoch_milliseconds))) # pyright: ignore[reportArgumentType] from_ = None to_ = None @@ -701,7 +698,7 @@ from_ = dict() from_["sample.headers.from"] = headers["from"] - from_query = Q(dict(match_phrase=from_)) + from_query = Q(dict(match_phrase=from_)) # pyright: ignore[reportArgumentType] q = q & from_query if "to" in headers: # We convert the TO header from a string list to a flat string. @@ -713,12 +710,12 @@ to_ = dict() to_["sample.headers.to"] = headers["to"] - to_query = Q(dict(match_phrase=to_)) + to_query = Q(dict(match_phrase=to_)) # pyright: ignore[reportArgumentType] q = q & to_query if "subject" in headers: subject = headers["subject"] subject_query = {"match_phrase": {"sample.headers.subject": subject}} - q = q & Q(subject_query) + q = q & Q(subject_query) # pyright: ignore[reportArgumentType] search.query = q existing = search.execute() @@ -796,7 +793,7 @@ number_of_shards=number_of_shards, number_of_replicas=number_of_replicas ) create_indexes([index], index_settings) - forensic_doc.meta.index = index + forensic_doc.meta.index = index # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess] try: forensic_doc.save() except Exception as e: @@ -811,18 +808,18 @@
[docs] def save_smtp_tls_report_to_elasticsearch( - report: OrderedDict[str, Any], + report: dict[str, Any], index_suffix: Optional[str] = None, index_prefix: Optional[str] = None, - monthly_indexes: Optional[bool] = False, - number_of_shards: Optional[int] = 1, - number_of_replicas: Optional[int] = 0, + monthly_indexes: bool = False, + number_of_shards: int = 1, + number_of_replicas: int = 0, ): """ Saves a parsed SMTP TLS report to Elasticsearch Args: - report (OrderedDict): A parsed SMTP TLS report + report (dict): A parsed SMTP TLS report index_suffix (str): The suffix of the name of the index to save to index_prefix (str): The prefix of the name of the index to save to monthly_indexes (bool): Use monthly indexes instead of daily indexes @@ -846,10 +843,10 @@ report["begin_date"] = begin_date report["end_date"] = end_date - org_name_query = Q(dict(match_phrase=dict(org_name=org_name))) - report_id_query = Q(dict(match_phrase=dict(report_id=report_id))) - begin_date_query = Q(dict(match=dict(date_begin=begin_date))) - end_date_query = Q(dict(match=dict(date_end=end_date))) + org_name_query = Q(dict(match_phrase=dict(org_name=org_name))) # pyright: ignore[reportArgumentType] + report_id_query = Q(dict(match_phrase=dict(report_id=report_id))) # pyright: ignore[reportArgumentType] + begin_date_query = Q(dict(match=dict(date_begin=begin_date))) # pyright: ignore[reportArgumentType] + end_date_query = Q(dict(match=dict(date_end=end_date))) # pyright: ignore[reportArgumentType] if index_suffix is not None: search_index = "smtp_tls_{0}*".format(index_suffix) @@ -950,10 +947,10 @@ additional_information_uri=additional_information_uri, failure_reason_code=failure_reason_code, ) - smtp_tls_doc.policies.append(policy_doc) + smtp_tls_doc.policies.append(policy_doc) # pyright: ignore[reportCallIssue] create_indexes([index], index_settings) - smtp_tls_doc.meta.index = index + smtp_tls_doc.meta.index = index # pyright: ignore[reportOptionalMemberAccess, reportAttributeAccessIssue] try: smtp_tls_doc.save() diff --git a/_modules/parsedmarc/opensearch.html b/_modules/parsedmarc/opensearch.html index 199de42e..69619bf4 100644 --- a/_modules/parsedmarc/opensearch.html +++ b/_modules/parsedmarc/opensearch.html @@ -5,14 +5,14 @@ - parsedmarc.opensearch — parsedmarc 9.0.5 documentation + parsedmarc.opensearch — parsedmarc 9.0.6 documentation - + @@ -84,30 +84,28 @@ from __future__ import annotations -from typing import Optional, Union, Any - -from collections import OrderedDict +from typing import Any, Optional, Union from opensearchpy import ( - Q, - connections, - Object, + Boolean, + Date, Document, Index, - Nested, InnerDoc, Integer, - Text, - Boolean, Ip, - Date, + Nested, + Object, + Q, Search, + Text, + connections, ) from opensearchpy.helpers import reindex +from parsedmarc import InvalidForensicReport from parsedmarc.log import logger from parsedmarc.utils import human_timestamp_to_datetime -from parsedmarc import InvalidForensicReport
@@ -189,7 +187,7 @@ def add_spf_result(self, domain: str, scope: str, result: _SPFResult): self.spf_results.append(_SPFResult(domain=domain, scope=scope, result=result)) - def save(self, **kwargs): + def save(self, **kwargs): # pyright: ignore[reportIncompatibleMethodOverride] self.passed_dmarc = False self.passed_dmarc = self.spf_aligned or self.dkim_aligned @@ -476,18 +474,18 @@
[docs] def save_aggregate_report_to_opensearch( - aggregate_report: OrderedDict[str, Any], + aggregate_report: dict[str, Any], index_suffix: Optional[str] = None, index_prefix: Optional[str] = None, - monthly_indexes: Optional[bool] = False, - number_of_shards: Optional[int] = 1, - number_of_replicas: Optional[int] = 0, + monthly_indexes: bool = False, + number_of_shards: int = 1, + number_of_replicas: int = 0, ): """ Saves a parsed DMARC aggregate report to OpenSearch Args: - aggregate_report (OrderedDict): A parsed forensic report + aggregate_report (dict): A parsed forensic report index_suffix (str): The suffix of the name of the index to save to index_prefix (str): The prefix of the name of the index to save to monthly_indexes (bool): Use monthly indexes instead of daily indexes @@ -527,13 +525,12 @@ query = org_name_query & report_id_query & domain_query query = query & begin_date_query & end_date_query search.query = query + begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ") + end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ") try: existing = search.execute() except Exception as error_: - begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ") - end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ") - raise OpenSearchError( "OpenSearch's search for existing report \ error: {}".format(error_.__str__()) @@ -641,10 +638,10 @@
[docs] def save_forensic_report_to_opensearch( - forensic_report: OrderedDict[str, Any], + forensic_report: dict[str, Any], index_suffix: Optional[str] = None, index_prefix: Optional[str] = None, - monthly_indexes: Optional[bool] = False, + monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0, ): @@ -652,7 +649,7 @@ Saves a parsed DMARC forensic report to OpenSearch Args: - forensic_report (OrderedDict): A parsed forensic report + forensic_report (dict): A parsed forensic report index_suffix (str): The suffix of the name of the index to save to index_prefix (str): The prefix of the name of the index to save to monthly_indexes (bool): Use monthly indexes instead of daily @@ -672,7 +669,7 @@ sample_date = forensic_report["parsed_sample"]["date"] sample_date = human_timestamp_to_datetime(sample_date) original_headers = forensic_report["parsed_sample"]["headers"] - headers = OrderedDict() + headers: dict[str, Any] = {} for original_header in original_headers: headers[original_header.lower()] = original_headers[original_header] @@ -811,18 +808,18 @@
[docs] def save_smtp_tls_report_to_opensearch( - report: OrderedDict[str, Any], + report: dict[str, Any], index_suffix: Optional[str] = None, index_prefix: Optional[str] = None, - monthly_indexes: Optional[bool] = False, - number_of_shards: Optional[int] = 1, - number_of_replicas: Optional[int] = 0, + monthly_indexes: bool = False, + number_of_shards: int = 1, + number_of_replicas: int = 0, ): """ Saves a parsed SMTP TLS report to OpenSearch Args: - report (OrderedDict): A parsed SMTP TLS report + report (dict): A parsed SMTP TLS report index_suffix (str): The suffix of the name of the index to save to index_prefix (str): The prefix of the name of the index to save to monthly_indexes (bool): Use monthly indexes instead of daily indexes diff --git a/_modules/parsedmarc/splunk.html b/_modules/parsedmarc/splunk.html index f57dde23..d10f45b2 100644 --- a/_modules/parsedmarc/splunk.html +++ b/_modules/parsedmarc/splunk.html @@ -5,14 +5,14 @@ - parsedmarc.splunk — parsedmarc 9.0.5 documentation + parsedmarc.splunk — parsedmarc 9.0.6 documentation - + @@ -84,16 +84,13 @@ from __future__ import annotations -from typing import Any, Union - -from collections import OrderedDict - -from urllib.parse import urlparse -import socket import json +import socket +from typing import Any, Union +from urllib.parse import urlparse -import urllib3 import requests +import urllib3 from parsedmarc.constants import USER_AGENT from parsedmarc.log import logger @@ -162,7 +159,7 @@ [docs] def save_aggregate_reports_to_splunk( self, - aggregate_reports: Union[list[OrderedDict[str, Any]], OrderedDict[str, Any]], + aggregate_reports: Union[list[dict[str, Any]], dict[str, Any]], ): """ Saves aggregate DMARC reports to Splunk @@ -231,7 +228,7 @@ [docs] def save_forensic_reports_to_splunk( self, - forensic_reports: Union[list[OrderedDict[str, Any]], OrderedDict[str, Any]], + forensic_reports: Union[list[dict[str, Any]], dict[str, Any]], ): """ Saves forensic DMARC reports to Splunk @@ -270,7 +267,7 @@
[docs] def save_smtp_tls_reports_to_splunk( - self, reports: Union[list[OrderedDict[str, Any]], OrderedDict[str, Any]] + self, reports: Union[list[dict[str, Any]], dict[str, Any]] ): """ Saves aggregate DMARC reports to Splunk diff --git a/_modules/parsedmarc/utils.html b/_modules/parsedmarc/utils.html index 2ead7d7f..015969fe 100644 --- a/_modules/parsedmarc/utils.html +++ b/_modules/parsedmarc/utils.html @@ -5,14 +5,14 @@ - parsedmarc.utils — parsedmarc 9.0.5 documentation + parsedmarc.utils — parsedmarc 9.0.6 documentation - + @@ -86,26 +86,23 @@ from __future__ import annotations -from typing import Optional, Union - -import logging -import os -from datetime import datetime -from datetime import timezone -from datetime import timedelta -from collections import OrderedDict -from expiringdict import ExpiringDict -import tempfile -import subprocess -import shutil -import mailparser -import json -import hashlib import base64 -import mailbox -import re import csv +import hashlib import io +import json +import logging +import mailbox +import os +import re +import shutil +import subprocess +import tempfile +from datetime import datetime, timedelta, timezone +from typing import Optional, TypedDict, Union, cast + +import mailparser +from expiringdict import ExpiringDict try: from importlib.resources import files @@ -114,19 +111,19 @@ from importlib.resources import files -from dateutil.parser import parse as parse_date -import dns.reversename -import dns.resolver import dns.exception +import dns.resolver +import dns.reversename import geoip2.database import geoip2.errors import publicsuffixlist import requests +from dateutil.parser import parse as parse_date -from parsedmarc.log import logger import parsedmarc.resources.dbip import parsedmarc.resources.maps from parsedmarc.constants import USER_AGENT +from parsedmarc.log import logger parenthesis_regex = re.compile(r"\s*\(.*\)\s*") @@ -155,9 +152,32 @@ +
+[docs] +class ReverseDNSService(TypedDict): + name: str + type: Optional[str]
+ + + +ReverseDNSMap = dict[str, ReverseDNSService] + + +
+[docs] +class IPAddressInfo(TypedDict): + ip_address: str + reverse_dns: Optional[str] + country: Optional[str] + base_domain: Optional[str] + name: Optional[str] + type: Optional[str]
+ + +
[docs] -def decode_base64(data) -> bytes: +def decode_base64(data: str) -> bytes: """ Decodes a base64 string, with padding being optional @@ -168,17 +188,17 @@ bytes: The decoded bytes """ - data = bytes(data, encoding="ascii") - missing_padding = len(data) % 4 + data_bytes = bytes(data, encoding="ascii") + missing_padding = len(data_bytes) % 4 if missing_padding != 0: - data += b"=" * (4 - missing_padding) - return base64.b64decode(data)
+ data_bytes += b"=" * (4 - missing_padding) + return base64.b64decode(data_bytes)
[docs] -def get_base_domain(domain: str) -> str: +def get_base_domain(domain: str) -> Optional[str]: """ Gets the base domain name for the given domain @@ -210,8 +230,8 @@ record_type: str, *, cache: Optional[ExpiringDict] = None, - nameservers: list[str] = None, - timeout: int = 2.0, + nameservers: Optional[list[str]] = None, + timeout: float = 2.0, ) -> list[str]: """ Queries DNS @@ -231,9 +251,9 @@ record_type = record_type.upper() cache_key = "{0}_{1}".format(domain, record_type) if cache: - records = cache.get(cache_key, None) - if records: - return records + cached_records = cache.get(cache_key, None) + if isinstance(cached_records, list): + return cast(list[str], cached_records) resolver = dns.resolver.Resolver() timeout = float(timeout) @@ -247,26 +267,12 @@ resolver.nameservers = nameservers resolver.timeout = timeout resolver.lifetime = timeout - if record_type == "TXT": - resource_records = list( - map( - lambda r: r.strings, - resolver.resolve(domain, record_type, lifetime=timeout), - ) - ) - _resource_record = [ - resource_record[0][:0].join(resource_record) - for resource_record in resource_records - if resource_record - ] - records = [r.decode() for r in _resource_record] - else: - records = list( - map( - lambda r: r.to_text().replace('"', "").rstrip("."), - resolver.resolve(domain, record_type, lifetime=timeout), - ) + records = list( + map( + lambda r: r.to_text().replace('"', "").rstrip("."), + resolver.resolve(domain, record_type, lifetime=timeout), ) + ) if cache: cache[cache_key] = records @@ -280,9 +286,9 @@ ip_address, *, cache: Optional[ExpiringDict] = None, - nameservers: list[str] = None, - timeout: int = 2.0, -) -> str: + nameservers: Optional[list[str]] = None, + timeout: float = 2.0, +) -> Optional[str]: """ Resolves an IP address to a hostname using a reverse DNS query @@ -300,7 +306,7 @@ try: address = dns.reversename.from_address(ip_address) hostname = query_dns( - address, "PTR", cache=cache, nameservers=nameservers, timeout=timeout + str(address), "PTR", cache=cache, nameservers=nameservers, timeout=timeout )[0] except dns.exception.DNSException as e: @@ -346,7 +352,7 @@
[docs] def human_timestamp_to_datetime( - human_timestamp: str, *, to_utc: Optional[bool] = False + human_timestamp: str, *, to_utc: bool = False ) -> datetime: """ Converts a human-readable timestamp into a Python ``datetime`` object @@ -380,13 +386,15 @@ float: The converted timestamp """ human_timestamp = human_timestamp.replace("T", " ") - return human_timestamp_to_datetime(human_timestamp).timestamp()
+ return int(human_timestamp_to_datetime(human_timestamp).timestamp())
[docs] -def get_ip_address_country(ip_address: str, *, db_path: Optional[str] = None) -> str: +def get_ip_address_country( + ip_address: str, *, db_path: Optional[str] = None +) -> Optional[str]: """ Returns the ISO code for the country associated with the given IPv4 or IPv6 address @@ -454,12 +462,12 @@ def get_service_from_reverse_dns_base_domain( base_domain, *, - always_use_local_file: Optional[bool] = False, - local_file_path: Optional[bool] = None, - url: Optional[bool] = None, - offline: Optional[bool] = False, - reverse_dns_map: Optional[bool] = None, -) -> str: + always_use_local_file: bool = False, + local_file_path: Optional[str] = None, + url: Optional[str] = None, + offline: bool = False, + reverse_dns_map: Optional[ReverseDNSMap] = None, +) -> ReverseDNSService: """ Returns the service name of a given base domain name from reverse DNS. @@ -476,12 +484,6 @@ the supplied reverse_dns_base_domain and the type will be None """ - def load_csv(_csv_file): - reader = csv.DictReader(_csv_file) - for row in reader: - key = row["base_reverse_dns"].lower().strip() - reverse_dns_map[key] = dict(name=row["name"], type=row["type"]) - base_domain = base_domain.lower().strip() if url is None: url = ( @@ -489,11 +491,24 @@ "/parsedmarc/master/parsedmarc/" "resources/maps/base_reverse_dns_map.csv" ) + reverse_dns_map_value: ReverseDNSMap if reverse_dns_map is None: - reverse_dns_map = dict() + reverse_dns_map_value = {} + else: + reverse_dns_map_value = reverse_dns_map + + def load_csv(_csv_file): + reader = csv.DictReader(_csv_file) + for row in reader: + key = row["base_reverse_dns"].lower().strip() + reverse_dns_map_value[key] = { + "name": row["name"], + "type": row["type"], + } + csv_file = io.StringIO() - if not (offline or always_use_local_file) and len(reverse_dns_map) == 0: + if not (offline or always_use_local_file) and len(reverse_dns_map_value) == 0: try: logger.debug(f"Trying to fetch reverse DNS map from {url}...") headers = {"User-Agent": USER_AGENT} @@ -510,7 +525,7 @@ logging.debug("Response body:") logger.debug(csv_file.read()) - if len(reverse_dns_map) == 0: + if len(reverse_dns_map_value) == 0: logger.info("Loading included reverse DNS map...") path = str( files(parsedmarc.resources.maps).joinpath("base_reverse_dns_map.csv") @@ -519,10 +534,11 @@ path = local_file_path with open(path) as csv_file: load_csv(csv_file) + service: ReverseDNSService try: - service = reverse_dns_map[base_domain] + service = reverse_dns_map_value[base_domain] except KeyError: - service = dict(name=base_domain, type=None) + service = {"name": base_domain, "type": None} return service
@@ -535,14 +551,14 @@ *, ip_db_path: Optional[str] = None, reverse_dns_map_path: Optional[str] = None, - always_use_local_files: Optional[bool] = False, + always_use_local_files: bool = False, reverse_dns_map_url: Optional[str] = None, cache: Optional[ExpiringDict] = None, - reverse_dns_map: Optional[dict] = None, - offline: Optional[bool] = False, + reverse_dns_map: Optional[ReverseDNSMap] = None, + offline: bool = False, nameservers: Optional[list[str]] = None, - timeout: Optional[float] = 2.0, -) -> OrderedDict[str, str]: + timeout: float = 2.0, +) -> IPAddressInfo: """ Returns reverse DNS and country information for the given IP address @@ -560,17 +576,27 @@ timeout (float): Sets the DNS timeout in seconds Returns: - OrderedDict: ``ip_address``, ``reverse_dns``, ``country`` + dict: ``ip_address``, ``reverse_dns``, ``country`` """ ip_address = ip_address.lower() if cache is not None: - info = cache.get(ip_address, None) - if info: + cached_info = cache.get(ip_address, None) + if ( + cached_info + and isinstance(cached_info, dict) + and "ip_address" in cached_info + ): logger.debug(f"IP address {ip_address} was found in cache") - return info - info = OrderedDict() - info["ip_address"] = ip_address + return cast(IPAddressInfo, cached_info) + info: IPAddressInfo = { + "ip_address": ip_address, + "reverse_dns": None, + "country": None, + "base_domain": None, + "name": None, + "type": None, + } if offline: reverse_dns = None else: @@ -580,9 +606,6 @@ country = get_ip_address_country(ip_address, db_path=ip_db_path) info["country"] = country info["reverse_dns"] = reverse_dns - info["base_domain"] = None - info["name"] = None - info["type"] = None if reverse_dns is not None: base_domain = get_base_domain(reverse_dns) if base_domain is not None: @@ -608,7 +631,7 @@ -def parse_email_address(original_address: str) -> OrderedDict[str, str]: +def parse_email_address(original_address: str) -> dict[str, Optional[str]]: if original_address[0] == "": display_name = None else: @@ -621,14 +644,12 @@ local = address_parts[0].lower() domain = address_parts[-1].lower() - return OrderedDict( - [ - ("display_name", display_name), - ("address", address), - ("local", local), - ("domain", domain), - ] - ) + return { + "display_name": display_name, + "address": address, + "local": local, + "domain": domain, + }
@@ -700,7 +721,7 @@
[docs] -def convert_outlook_msg(msg_bytes: bytes) -> str: +def convert_outlook_msg(msg_bytes: bytes) -> bytes: """ Uses the ``msgconvert`` Perl utility to convert an Outlook MS file to standard RFC 822 format @@ -709,7 +730,7 @@ msg_bytes (bytes): the content of the .msg file Returns: - A RFC 822 string + A RFC 822 bytes payload """ if not is_outlook_msg(msg_bytes): raise ValueError("The supplied bytes are not an Outlook MSG file") @@ -740,8 +761,8 @@
[docs] def parse_email( - data: Union[bytes, str], *, strip_attachment_payloads: Optional[bool] = False -): + data: Union[bytes, str], *, strip_attachment_payloads: bool = False +) -> dict: """ A simplified email parser diff --git a/_static/documentation_options.js b/_static/documentation_options.js index 68ae8492..6ad0ab48 100644 --- a/_static/documentation_options.js +++ b/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '9.0.5', + VERSION: '9.0.6', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/api.html b/api.html index 9eafebaa..ea6f956c 100644 --- a/api.html +++ b/api.html @@ -6,14 +6,14 @@ - API reference — parsedmarc 9.0.5 documentation + API reference — parsedmarc 9.0.6 documentation - + @@ -117,6 +117,8 @@
  • parsedmarc.utils
    • DownloadError
    • EmailParserError
    • +
    • IPAddressInfo
    • +
    • ReverseDNSService
    • convert_outlook_msg()
    • decode_base64()
    • get_base_domain()
    • @@ -201,12 +203,12 @@
      -parsedmarc.email_results(results: OrderedDict, *, host: str, mail_from: str, mail_to: str, mail_cc: list = None, mail_bcc: list = None, port: int = 0, require_encryption: bool = False, verify: bool = True, username: str = None, password: str = None, subject: str = None, attachment_filename: str = None, message: str = None)[source]
      +parsedmarc.email_results(results: ParsingResults, host: str, mail_from: str, mail_to: list[str] | None, *, mail_cc: list[str] | None = None, mail_bcc: list[str] | None = None, port: int = 0, require_encryption: bool = False, verify: bool = True, username: str | None = None, password: str | None = None, subject: str | None = None, attachment_filename: str | None = None, message: str | None = None)[source]

      Emails parsing results as a zip file

      Parameters:
        -
      • results (OrderedDict) – Parsing results

      • +
      • results (dict) – Parsing results

      • host (str) – Mail server hostname or IP address

      • mail_from – The value of the message from header

      • mail_to (list) – A list of addresses to mail to

      • @@ -227,7 +229,7 @@
        -parsedmarc.extract_report(content: bytes | str | IO[Any]) str[source]
        +parsedmarc.extract_report(content: bytes | str | BinaryIO) str[source]

        Extracts text from a zip or gzip file, as a base64-encoded string, file-like object, or bytes.

        @@ -254,7 +256,7 @@ file-like object, or bytes.

        -parsedmarc.get_dmarc_reports_from_mailbox(connection: MailboxConnection, *, reports_folder: str | None = 'INBOX', archive_folder: str | None = 'Archive', delete: bool | None = False, test: bool | None = False, ip_db_path: str | None = None, always_use_local_files: bool | None = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool | None = False, nameservers: list[str] | None = None, dns_timeout: float | None = 6.0, strip_attachment_payloads: bool | None = False, results: OrderedDict[str, Any] | None = None, batch_size: int | None = 10, since: datetime | None = None, create_folders: bool | None = True, normalize_timespan_threshold_hours: float | None = 24) OrderedDict[str, OrderedDict[str, Any]][source]
        +parsedmarc.get_dmarc_reports_from_mailbox(connection: MailboxConnection, *, reports_folder: str = 'INBOX', archive_folder: str = 'Archive', delete: bool = False, test: bool = False, ip_db_path: str | None = None, always_use_local_files: bool = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool = False, nameservers: list[str] | None = None, dns_timeout: float = 6.0, strip_attachment_payloads: bool = False, results: ParsingResults | None = None, batch_size: int = 10, since: datetime | date | str | None = None, create_folders: bool = True, normalize_timespan_threshold_hours: float = 24) ParsingResults[source]

        Fetches and parses DMARC reports from a mailbox

        Parameters:
        @@ -287,14 +289,14 @@ forensic report results

        Lists of aggregate_reports, forensic_reports, and smtp_tls_reports

        Return type:
        -

        OrderedDict

        +

        dict

        -parsedmarc.get_dmarc_reports_from_mbox(input_: str, *, nameservers: list[str] | None = None, dns_timeout: float | None = 2.0, strip_attachment_payloads: bool | None = False, ip_db_path: str | None = None, always_use_local_files: bool | None = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool | None = False, normalize_timespan_threshold_hours: float | None = 24.0) OrderedDict[str, OrderedDict[str, Any]][source]
        +parsedmarc.get_dmarc_reports_from_mbox(input_: str, *, nameservers: list[str] | None = None, dns_timeout: float = 2.0, strip_attachment_payloads: bool = False, ip_db_path: str | None = None, always_use_local_files: bool = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool = False, normalize_timespan_threshold_hours: float = 24.0) ParsingResults[source]

        Parses a mailbox in mbox format containing e-mails with attached DMARC reports

        @@ -318,18 +320,18 @@ forensic report results

        Lists of aggregate_reports, forensic_reports, and smtp_tls_reports

        Return type:
        -

        OrderedDict

        +

        dict

        -parsedmarc.get_report_zip(results: OrderedDict[str, Any]) bytes[source]
        +parsedmarc.get_report_zip(results: ParsingResults) bytes[source]

        Creates a zip file of parsed report output

        Parameters:
        -

        results (OrderedDict) – The parsed results

        +

        results – The parsed results

        Returns:

        zip file bytes

        @@ -342,7 +344,7 @@ forensic report results

        -parsedmarc.parse_aggregate_report_file(_input: str | bytes | IO[Any], *, offline: bool | None = False, always_use_local_files: bool | None = None, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, ip_db_path: str | None = None, nameservers: list[str] | None = None, dns_timeout: float | None = 2.0, keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float | None = 24.0) OrderedDict[str, any][source]
        +parsedmarc.parse_aggregate_report_file(_input: str | bytes | BinaryIO, *, offline: bool = False, always_use_local_files: bool = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, ip_db_path: str | None = None, nameservers: list[str] | None = None, dns_timeout: float = 2.0, keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24.0) AggregateReport[source]

        Parses a file at the given path, a file-like object. or bytes as an aggregate DMARC report

        @@ -365,15 +367,15 @@ aggregate DMARC report

        The parsed DMARC aggregate report

        Return type:
        -

        OrderedDict

        +

        dict

        -parsedmarc.parse_aggregate_report_xml(xml: str, *, ip_db_path: str | None = None, always_use_local_files: bool | None = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool | None = False, nameservers: list[str] | None = None, timeout: float | None = 2.0, keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24.0) OrderedDict[str, Any][source]
        -

        Parses a DMARC XML report string and returns a consistent OrderedDict

        +parsedmarc.parse_aggregate_report_xml(xml: str, *, ip_db_path: str | None = None, always_use_local_files: bool = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool = False, nameservers: list[str] | None = None, timeout: float = 2.0, keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24.0) AggregateReport[source] +

        Parses a DMARC XML report string and returns a consistent dict

        Parameters:
          @@ -394,15 +396,15 @@ aggregate DMARC report

          The parsed aggregate DMARC report

          Return type:
          -

          OrderedDict

          +

          dict

        -parsedmarc.parse_forensic_report(feedback_report: str, sample: str, msg_date: datetime, *, always_use_local_files: bool | None = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool | None = False, ip_db_path: str | None = None, nameservers: list[str] | None = None, dns_timeout: float | None = 2.0, strip_attachment_payloads: bool | None = False) OrderedDict[str, Any][source]
        -

        Converts a DMARC forensic report and sample to a OrderedDict

        +parsedmarc.parse_forensic_report(feedback_report: str, sample: str, msg_date: datetime, *, always_use_local_files: bool = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool = False, ip_db_path: str | None = None, nameservers: list[str] | None = None, dns_timeout: float = 2.0, strip_attachment_payloads: bool = False) ForensicReport[source] +

        Converts a DMARC forensic report and sample to a dict

        Parameters:
          @@ -425,14 +427,14 @@ forensic report results

          A parsed report and sample

          Return type:
          -

          OrderedDict

          +

          dict

        -parsedmarc.parse_report_email(input_: bytes | str, *, offline: bool | None = False, ip_db_path: str | None = None, always_use_local_files: bool | None = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, nameservers: list[str] = None, dns_timeout: float | None = 2.0, strip_attachment_payloads: bool | None = False, keep_alive: callable | None = None, normalize_timespan_threshold_hours: float | None = 24.0) OrderedDict[str, Any][source]
        +parsedmarc.parse_report_email(input_: bytes | str, *, offline: bool = False, ip_db_path: str | None = None, always_use_local_files: bool = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, nameservers: list[str] | None = None, dns_timeout: float = 2.0, strip_attachment_payloads: bool = False, keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24.0) AggregateParsedReport | ForensicParsedReport | SMTPTLSParsedReport[source]

        Parses a DMARC report from an email

        Parameters:
        @@ -459,20 +461,20 @@ forensic report results

        Return type:
        -

        OrderedDict

        +

        dict

        -parsedmarc.parse_report_file(input_: bytes | str | IO[Any], *, nameservers: list[str] | None = None, dns_timeout: float | None = 2.0, strip_attachment_payloads: bool | None = False, ip_db_path: str | None = None, always_use_local_files: bool | None = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool | None = False, keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float | None = 24) OrderedDict[str, Any][source]
        +parsedmarc.parse_report_file(input_: bytes | str | BinaryIO, *, nameservers: list[str] | None = None, dns_timeout: float = 2.0, strip_attachment_payloads: bool = False, ip_db_path: str | None = None, always_use_local_files: bool = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool = False, keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24) AggregateParsedReport | ForensicParsedReport | SMTPTLSParsedReport[source]

        Parses a DMARC aggregate or forensic file at the given path, a file-like object. or bytes

        Parameters:
          -
        • input (str | bytes | IO) – A path to a file, a file like object, or bytes

        • +
        • input (str | bytes | BinaryIO) – A path to a file, a file like object, or bytes

        • nameservers (list) – A list of one or more nameservers to use (Cloudflare’s public DNS resolvers by default)

        • dns_timeout (float) – Sets the DNS timeout in seconds

        • @@ -490,20 +492,20 @@ forensic report results

          The parsed DMARC report

          Return type:
          -

          OrderedDict

          +

          dict

        -parsedmarc.parse_smtp_tls_report_json(report: str)[source]
        +parsedmarc.parse_smtp_tls_report_json(report: str | bytes) SMTPTLSReport[source]

        Parses and validates an SMTP TLS report

        -parsedmarc.parsed_aggregate_reports_to_csv(reports: list[OrderedDict[str, Any]]) str[source]
        +parsedmarc.parsed_aggregate_reports_to_csv(reports: AggregateReport | list[AggregateReport]) str[source]

        Converts one or more parsed aggregate reports to flat CSV format, including headers

        @@ -521,7 +523,7 @@ headers

        -parsedmarc.parsed_aggregate_reports_to_csv_rows(reports: list[OrderedDict[str, Any]]) list[dict[str, Any]][source]
        +parsedmarc.parsed_aggregate_reports_to_csv_rows(reports: AggregateReport | list[AggregateReport]) list[dict[str, Any]][source]

        Converts one or more parsed aggregate reports to list of dicts in flat CSV format

        @@ -540,7 +542,7 @@ format

        -parsedmarc.parsed_forensic_reports_to_csv(reports: list[dict[str, Any]]) str[source]
        +parsedmarc.parsed_forensic_reports_to_csv(reports: ForensicReport | list[ForensicReport]) str[source]

        Converts one or more parsed forensic reports to flat CSV format, including headers

        @@ -558,7 +560,7 @@ headers

        -parsedmarc.parsed_forensic_reports_to_csv_rows(reports: list[OrderedDict[str, Any]])[source]
        +parsedmarc.parsed_forensic_reports_to_csv_rows(reports: ForensicReport | list[ForensicReport]) list[dict[str, Any]][source]

        Converts one or more parsed forensic reports to a list of dicts in flat CSV format

        @@ -576,7 +578,7 @@ format

        -parsedmarc.parsed_smtp_tls_reports_to_csv(reports: OrderedDict[str, Any]) str[source]
        +parsedmarc.parsed_smtp_tls_reports_to_csv(reports: SMTPTLSReport | list[SMTPTLSReport]) str[source]

        Converts one or more parsed SMTP TLS reports to flat CSV format, including headers

        @@ -594,19 +596,19 @@ headers

        -parsedmarc.parsed_smtp_tls_reports_to_csv_rows(reports: OrderedDict[str, Any] | List[OrderedDict[str, Any]])[source]
        +parsedmarc.parsed_smtp_tls_reports_to_csv_rows(reports: SMTPTLSReport | list[SMTPTLSReport]) list[dict[str, Any]][source]

        Converts one oor more parsed SMTP TLS reports into a list of single -layer OrderedDict objects suitable for use in a CSV

        +layer dict objects suitable for use in a CSV

        -parsedmarc.save_output(results: OrderedDict[str, Any], *, output_directory: str | None = 'output', aggregate_json_filename: str | None = 'aggregate.json', forensic_json_filename: str | None = 'forensic.json', smtp_tls_json_filename: str | None = 'smtp_tls.json', aggregate_csv_filename: str | None = 'aggregate.csv', forensic_csv_filename: str | None = 'forensic.csv', smtp_tls_csv_filename: str | None = 'smtp_tls.csv')[source]
        +parsedmarc.save_output(results: ParsingResults, *, output_directory: str = 'output', aggregate_json_filename: str = 'aggregate.json', forensic_json_filename: str = 'forensic.json', smtp_tls_json_filename: str = 'smtp_tls.json', aggregate_csv_filename: str = 'aggregate.csv', forensic_csv_filename: str = 'forensic.csv', smtp_tls_csv_filename: str = 'smtp_tls.csv')[source]

        Save report data in the given directory

        Parameters:
          -
        • results (OrderedDict) – Parsing results

        • +
        • results – Parsing results

        • output_directory (str) – The path to the directory to save in

        • aggregate_json_filename (str) – Filename for the aggregate JSON file

        • forensic_json_filename (str) – Filename for the forensic JSON file

        • @@ -621,7 +623,7 @@ layer OrderedDict objects suitable for use in a CSV

          -parsedmarc.watch_inbox(mailbox_connection: MailboxConnection, callback: Callable, *, reports_folder: str | None = 'INBOX', archive_folder: str | None = 'Archive', delete: bool | None = False, test: bool | None = False, check_timeout: int | None = 30, ip_db_path: str | None = None, always_use_local_files: bool | None = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool | None = False, nameservers: list[str] | None = None, dns_timeout: float | None = 6.0, strip_attachment_payloads: bool | None = False, batch_size: int | None = None, normalize_timespan_threshold_hours: float | None = 24)[source]
          +parsedmarc.watch_inbox(mailbox_connection: MailboxConnection, callback: Callable, *, reports_folder: str = 'INBOX', archive_folder: str = 'Archive', delete: bool = False, test: bool = False, check_timeout: int = 30, ip_db_path: str | None = None, always_use_local_files: bool = False, reverse_dns_map_path: str | None = None, reverse_dns_map_url: str | None = None, offline: bool = False, nameservers: list[str] | None = None, dns_timeout: float = 6.0, strip_attachment_payloads: bool = False, batch_size: int = 10, normalize_timespan_threshold_hours: float = 24)[source]
          Watches the mailbox for new messages and

          sends the results to a callback function

          @@ -699,12 +701,12 @@ forensic report samples with None

          -parsedmarc.elastic.save_aggregate_report_to_elasticsearch(aggregate_report: OrderedDict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool | None = False, number_of_shards: int | None = 1, number_of_replicas: int | None = 0)[source]
          +parsedmarc.elastic.save_aggregate_report_to_elasticsearch(aggregate_report: dict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool | None = False, number_of_shards: int = 1, number_of_replicas: int = 0)[source]

          Saves a parsed DMARC aggregate report to Elasticsearch

          Parameters:
            -
          • aggregate_report (OrderedDict) – A parsed forensic report

          • +
          • aggregate_report (dict) – A parsed forensic report

          • index_suffix (str) – The suffix of the name of the index to save to

          • index_prefix (str) – The prefix of the name of the index to save to

          • monthly_indexes (bool) – Use monthly indexes instead of daily indexes

          • @@ -720,12 +722,12 @@ forensic report samples with None

            -parsedmarc.elastic.save_forensic_report_to_elasticsearch(forensic_report: OrderedDict[str, Any], index_suffix: Any | None = None, index_prefix: str | None = None, monthly_indexes: bool | None = False, number_of_shards: int = 1, number_of_replicas: int = 0)[source]
            +parsedmarc.elastic.save_forensic_report_to_elasticsearch(forensic_report: dict[str, Any], index_suffix: Any | None = None, index_prefix: str | None = None, monthly_indexes: bool | None = False, number_of_shards: int = 1, number_of_replicas: int = 0)[source]

            Saves a parsed DMARC forensic report to Elasticsearch

            Parameters:
              -
            • forensic_report (OrderedDict) – A parsed forensic report

            • +
            • forensic_report (dict) – A parsed forensic report

            • index_suffix (str) – The suffix of the name of the index to save to

            • index_prefix (str) – The prefix of the name of the index to save to

            • monthly_indexes (bool) – Use monthly indexes instead of daily @@ -743,12 +745,12 @@ index

            • -parsedmarc.elastic.save_smtp_tls_report_to_elasticsearch(report: OrderedDict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool | None = False, number_of_shards: int | None = 1, number_of_replicas: int | None = 0)[source]
              +parsedmarc.elastic.save_smtp_tls_report_to_elasticsearch(report: dict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0)[source]

              Saves a parsed SMTP TLS report to Elasticsearch

              Parameters:
                -
              • report (OrderedDict) – A parsed SMTP TLS report

              • +
              • report (dict) – A parsed SMTP TLS report

              • index_suffix (str) – The suffix of the name of the index to save to

              • index_prefix (str) – The prefix of the name of the index to save to

              • monthly_indexes (bool) – Use monthly indexes instead of daily indexes

              • @@ -764,7 +766,7 @@ index

                -parsedmarc.elastic.set_hosts(hosts: str | list[str], *, use_ssl: bool | None = False, ssl_cert_path: str | None = None, username: str | None = None, password: str | None = None, api_key: str | None = None, timeout: float | None = 60.0)[source]
                +parsedmarc.elastic.set_hosts(hosts: str | list[str], *, use_ssl: bool = False, ssl_cert_path: str | None = None, username: str | None = None, password: str | None = None, api_key: str | None = None, timeout: float = 60.0)[source]

                Sets the Elasticsearch hosts to use

                Parameters:
                @@ -826,12 +828,12 @@ index

                -parsedmarc.opensearch.save_aggregate_report_to_opensearch(aggregate_report: OrderedDict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool | None = False, number_of_shards: int | None = 1, number_of_replicas: int | None = 0)[source]
                +parsedmarc.opensearch.save_aggregate_report_to_opensearch(aggregate_report: dict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0)[source]

                Saves a parsed DMARC aggregate report to OpenSearch

                Parameters:
                  -
                • aggregate_report (OrderedDict) – A parsed forensic report

                • +
                • aggregate_report (dict) – A parsed forensic report

                • index_suffix (str) – The suffix of the name of the index to save to

                • index_prefix (str) – The prefix of the name of the index to save to

                • monthly_indexes (bool) – Use monthly indexes instead of daily indexes

                • @@ -847,12 +849,12 @@ index

                  -parsedmarc.opensearch.save_forensic_report_to_opensearch(forensic_report: OrderedDict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool | None = False, number_of_shards: int = 1, number_of_replicas: int = 0)[source]
                  +parsedmarc.opensearch.save_forensic_report_to_opensearch(forensic_report: dict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0)[source]

                  Saves a parsed DMARC forensic report to OpenSearch

                  Parameters:
                    -
                  • forensic_report (OrderedDict) – A parsed forensic report

                  • +
                  • forensic_report (dict) – A parsed forensic report

                  • index_suffix (str) – The suffix of the name of the index to save to

                  • index_prefix (str) – The prefix of the name of the index to save to

                  • monthly_indexes (bool) – Use monthly indexes instead of daily @@ -870,12 +872,12 @@ index

                  • -parsedmarc.opensearch.save_smtp_tls_report_to_opensearch(report: OrderedDict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool | None = False, number_of_shards: int | None = 1, number_of_replicas: int | None = 0)[source]
                    +parsedmarc.opensearch.save_smtp_tls_report_to_opensearch(report: dict[str, Any], index_suffix: str | None = None, index_prefix: str | None = None, monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0)[source]

                    Saves a parsed SMTP TLS report to OpenSearch

                    Parameters:
                      -
                    • report (OrderedDict) – A parsed SMTP TLS report

                    • +
                    • report (dict) – A parsed SMTP TLS report

                    • index_suffix (str) – The suffix of the name of the index to save to

                    • index_prefix (str) – The prefix of the name of the index to save to

                    • monthly_indexes (bool) – Use monthly indexes instead of daily indexes

                    • @@ -930,7 +932,7 @@ data before giving up

                    -save_aggregate_reports_to_splunk(aggregate_reports: list[OrderedDict[str, Any]] | OrderedDict[str, Any])[source]
                    +save_aggregate_reports_to_splunk(aggregate_reports: list[dict[str, Any]] | dict[str, Any])[source]

                    Saves aggregate DMARC reports to Splunk

                    Parameters:
                    @@ -942,7 +944,7 @@ to save in Splunk

                    -save_forensic_reports_to_splunk(forensic_reports: list[OrderedDict[str, Any]] | OrderedDict[str, Any])[source]
                    +save_forensic_reports_to_splunk(forensic_reports: list[dict[str, Any]] | dict[str, Any])[source]

                    Saves forensic DMARC reports to Splunk

                    Parameters:
                    @@ -954,7 +956,7 @@ to save in Splunk

                    -save_smtp_tls_reports_to_splunk(reports: list[OrderedDict[str, Any]] | OrderedDict[str, Any])[source]
                    +save_smtp_tls_reports_to_splunk(reports: list[dict[str, Any]] | dict[str, Any])[source]

                    Saves aggregate DMARC reports to Splunk

                    Parameters:
                    @@ -988,9 +990,19 @@ to save in Splunk

                    Raised when an error parsing the email occurs

                    +
                    +
                    +class parsedmarc.utils.IPAddressInfo[source]
                    +
                    + +
                    +
                    +class parsedmarc.utils.ReverseDNSService[source]
                    +
                    +
                    -parsedmarc.utils.convert_outlook_msg(msg_bytes: bytes) str[source]
                    +parsedmarc.utils.convert_outlook_msg(msg_bytes: bytes) bytes[source]

                    Uses the msgconvert Perl utility to convert an Outlook MS file to standard RFC 822 format

                    @@ -998,14 +1010,14 @@ standard RFC 822 format

                    msg_bytes (bytes) – the content of the .msg file

                    Returns:
                    -

                    A RFC 822 string

                    +

                    A RFC 822 bytes payload

                    -parsedmarc.utils.decode_base64(data) bytes[source]
                    +parsedmarc.utils.decode_base64(data: str) bytes[source]

                    Decodes a base64 string, with padding being optional

                    Parameters:
                    @@ -1022,7 +1034,7 @@ standard RFC 822 format

                    -parsedmarc.utils.get_base_domain(domain: str) str[source]
                    +parsedmarc.utils.get_base_domain(domain: str) str | None[source]

                    Gets the base domain name for the given domain

                    Note

                    @@ -1062,7 +1074,7 @@ parsedmarc.resources.maps.psl_overrides.txt

                    -parsedmarc.utils.get_ip_address_country(ip_address: str, *, db_path: str | None = None) str[source]
                    +parsedmarc.utils.get_ip_address_country(ip_address: str, *, db_path: str | None = None) str | None[source]

                    Returns the ISO code for the country associated with the given IPv4 or IPv6 address

                    @@ -1083,7 +1095,7 @@ with the given IPv4 or IPv6 address

                    -parsedmarc.utils.get_ip_address_info(ip_address, *, ip_db_path: str | None = None, reverse_dns_map_path: str | None = None, always_use_local_files: bool | None = False, reverse_dns_map_url: str | None = None, cache: ExpiringDict | None = None, reverse_dns_map: dict | None = None, offline: bool | None = False, nameservers: list[str] | None = None, timeout: float | None = 2.0) OrderedDict[str, str][source]
                    +parsedmarc.utils.get_ip_address_info(ip_address, *, ip_db_path: str | None = None, reverse_dns_map_path: str | None = None, always_use_local_files: bool = False, reverse_dns_map_url: str | None = None, cache: ExpiringDict | None = None, reverse_dns_map: dict[str, ReverseDNSService] | None = None, offline: bool = False, nameservers: list[str] | None = None, timeout: float = 2.0) IPAddressInfo[source]

                    Returns reverse DNS and country information for the given IP address

                    Parameters:
                    @@ -1105,14 +1117,14 @@ with the given IPv4 or IPv6 address

                    ip_address, reverse_dns, country

                    Return type:
                    -

                    OrderedDict

                    +

                    dict

                    -parsedmarc.utils.get_reverse_dns(ip_address, *, cache: ExpiringDict | None = None, nameservers: list[str] = None, timeout: int = 2.0) str[source]
                    +parsedmarc.utils.get_reverse_dns(ip_address, *, cache: ExpiringDict | None = None, nameservers: list[str] | None = None, timeout: float = 2.0) str | None[source]

                    Resolves an IP address to a hostname using a reverse DNS query

                    Parameters:
                    @@ -1135,7 +1147,7 @@ with the given IPv4 or IPv6 address

                    -parsedmarc.utils.get_service_from_reverse_dns_base_domain(base_domain, *, always_use_local_file: bool | None = False, local_file_path: bool | None = None, url: bool | None = None, offline: bool | None = False, reverse_dns_map: bool | None = None) str[source]
                    +parsedmarc.utils.get_service_from_reverse_dns_base_domain(base_domain, *, always_use_local_file: bool = False, local_file_path: str | None = None, url: str | None = None, offline: bool = False, reverse_dns_map: dict[str, ReverseDNSService] | None = None) ReverseDNSService[source]

                    Returns the service name of a given base domain name from reverse DNS.

                    Parameters:
                    @@ -1161,7 +1173,7 @@ the supplied reverse_dns_base_domain and the type will be None

                    -parsedmarc.utils.human_timestamp_to_datetime(human_timestamp: str, *, to_utc: bool | None = False) datetime[source]
                    +parsedmarc.utils.human_timestamp_to_datetime(human_timestamp: str, *, to_utc: bool = False) datetime[source]

                    Converts a human-readable timestamp into a Python datetime object

                    Parameters:
                    @@ -1232,7 +1244,7 @@ the supplied reverse_dns_base_domain and the type will be None

                    -parsedmarc.utils.parse_email(data: bytes | str, *, strip_attachment_payloads: bool | None = False)[source]
                    +parsedmarc.utils.parse_email(data: bytes | str, *, strip_attachment_payloads: bool = False) dict[source]

                    A simplified email parser

                    Parameters:
                    @@ -1252,7 +1264,7 @@ the supplied reverse_dns_base_domain and the type will be None

                    -parsedmarc.utils.query_dns(domain: str, record_type: str, *, cache: ExpiringDict | None = None, nameservers: list[str] = None, timeout: int = 2.0) list[str][source]
                    +parsedmarc.utils.query_dns(domain: str, record_type: str, *, cache: ExpiringDict | None = None, nameservers: list[str] | None = None, timeout: float = 2.0) list[str][source]

                    Queries DNS

                    Parameters:
                    diff --git a/contributing.html b/contributing.html index d437d90d..41aa1042 100644 --- a/contributing.html +++ b/contributing.html @@ -6,14 +6,14 @@ - Contributing to parsedmarc — parsedmarc 9.0.5 documentation + Contributing to parsedmarc — parsedmarc 9.0.6 documentation - + diff --git a/davmail.html b/davmail.html index 7ea9553b..8db1cc6d 100644 --- a/davmail.html +++ b/davmail.html @@ -6,14 +6,14 @@ - Accessing an inbox using OWA/EWS — parsedmarc 9.0.5 documentation + Accessing an inbox using OWA/EWS — parsedmarc 9.0.6 documentation - + diff --git a/dmarc.html b/dmarc.html index 710333ee..40fd0c20 100644 --- a/dmarc.html +++ b/dmarc.html @@ -6,14 +6,14 @@ - Understanding DMARC — parsedmarc 9.0.5 documentation + Understanding DMARC — parsedmarc 9.0.6 documentation - + diff --git a/elasticsearch.html b/elasticsearch.html index 9c2ac22f..46dbf61c 100644 --- a/elasticsearch.html +++ b/elasticsearch.html @@ -6,14 +6,14 @@ - Elasticsearch and Kibana — parsedmarc 9.0.5 documentation + Elasticsearch and Kibana — parsedmarc 9.0.6 documentation - + diff --git a/genindex.html b/genindex.html index e4fb4d9e..930c6bbf 100644 --- a/genindex.html +++ b/genindex.html @@ -5,14 +5,14 @@ - Index — parsedmarc 9.0.5 documentation + Index — parsedmarc 9.0.6 documentation - + @@ -92,6 +92,7 @@ | O | P | Q + | R | S | T | W @@ -203,6 +204,8 @@
                  +

                  R

                  + + +
                  +

                  S

                    diff --git a/index.html b/index.html index 76e05305..873bef37 100644 --- a/index.html +++ b/index.html @@ -6,14 +6,14 @@ - parsedmarc documentation - Open source DMARC report analyzer and visualizer — parsedmarc 9.0.5 documentation + parsedmarc documentation - Open source DMARC report analyzer and visualizer — parsedmarc 9.0.6 documentation - + diff --git a/installation.html b/installation.html index c8af775a..5ee7f843 100644 --- a/installation.html +++ b/installation.html @@ -6,14 +6,14 @@ - Installation — parsedmarc 9.0.5 documentation + Installation — parsedmarc 9.0.6 documentation - + diff --git a/kibana.html b/kibana.html index 86287d2b..aa9ed1dd 100644 --- a/kibana.html +++ b/kibana.html @@ -6,14 +6,14 @@ - Using the Kibana dashboards — parsedmarc 9.0.5 documentation + Using the Kibana dashboards — parsedmarc 9.0.6 documentation - + diff --git a/mailing-lists.html b/mailing-lists.html index 28594033..94d82784 100644 --- a/mailing-lists.html +++ b/mailing-lists.html @@ -6,14 +6,14 @@ - What about mailing lists? — parsedmarc 9.0.5 documentation + What about mailing lists? — parsedmarc 9.0.6 documentation - + diff --git a/objects.inv b/objects.inv index 56c7773bf679b3bc320b964a4e6f381e63a767c6..7c4b47625b17f12b90fa88107186117803a47da3 100644 GIT binary patch delta 1022 zcmV^F*J!gfq82$4v^bWy zp-7da9J{~1LsGWr)Imf!7h4+gz4_vhL$y>JU=7t}-X1)yFQK(u$^6|wav6Plx46HVg#3R;X^@7OlfcE@ z*Y(%WMH+VP9`#adrN=@*P-A&gSbNBPAv92SDCiDYqPqDq{2M@FJ;mU8iOMYyE*b2> zIP|uY|4TZmI?IJgNskB2Z`G3&in4TF^QGIDCUo@yy;?6A!_LdB zJ3xQY?SDl9ddEu`4=t5MtaZy?DD_AhQ;l5v0neaR49JEWc>h-t(zQ$u zMwr5w$z-hC$R%xXb$|-e=p_MEU`?T(ATn8G$14V|57^Sk sb2}p&Pr%s1-nuodAnTi>$gI8x3N!t4))|}{&+&pdRZ(I61l{Dn6A04FBXjK(-~N$dyT_kQu<;g%W;yrG7b?crmic_*QKU(#00YuCs(Q8Ig5 z&WiRlb$+XMR0_VRDgzaD@C{mRoM(VNgVqVH$`1p{W%Q@J#kZSD$j^V;fU<&30Aq?>V)9g!ak4f1kwwIeKqHW!oG?#v#`VC@6qxl zcOApNky!*!Vw;8SRfbJ!3NYK-r%C(IA}E7^{}wOn6VL4Kby$Ch(T_Hd>Ph^k+4Bs^ z1aqE5c$zuSkmQ&XYE*Zw?Zz%i+saNopcb0Y<)porl?ZC}(fU@q@^!Q;dr zaJA;yte3KSHR+|?9)pWe zYW+YOTa8?a5zl{tX%1vVE&TaM7ScB`9!9uE4^%eRZ>x$nm}|PlA7_NBvPncsDCgLD zTEcXvoH<-WA=Fl9;|*W}d>E9?#$wKQfDZxf;G57ldukx>8U*8Zqk_!E9PWT!Rao9J;C>)UL|8#XdZ`7 zM#H=C`t^SCZh5~h;s>A<9y|@;AK3F743y#?5kA@!=hxf>cQz35XCEZc0ABthHdN7~ z0u}BP0WW{)2Bc>=N+bBzKNiEZUQIG%2t$d3U4w@i4e6Mb{@zqt-5Cba?y~P=QM)|+ z1QV>;3ENRA@D=r-pHfErnYuxKv}=$Q55b=l5G8&5LZn>{z2BG`TW>Tq+!j7XI69NU z-wqOjvw_HSUsJSvgXMV~EyL(v1s;(X0@)sB$AMZ%a2LwB*h7$gz2#+pJsQDlq6ju#AE9k9Vr=XNGGo`9)^y>%;ELD9D=iCKL|5@&|z UqO&+Pof8CUs*=L`1sQg)is5DLlK=n! diff --git a/opensearch.html b/opensearch.html index 91f7e7c9..b8336444 100644 --- a/opensearch.html +++ b/opensearch.html @@ -6,14 +6,14 @@ - OpenSearch and Grafana — parsedmarc 9.0.5 documentation + OpenSearch and Grafana — parsedmarc 9.0.6 documentation - + diff --git a/output.html b/output.html index 18177823..59527c17 100644 --- a/output.html +++ b/output.html @@ -6,14 +6,14 @@ - Sample outputs — parsedmarc 9.0.5 documentation + Sample outputs — parsedmarc 9.0.6 documentation - + diff --git a/py-modindex.html b/py-modindex.html index 5d666c0c..11f9d024 100644 --- a/py-modindex.html +++ b/py-modindex.html @@ -5,14 +5,14 @@ - Python Module Index — parsedmarc 9.0.5 documentation + Python Module Index — parsedmarc 9.0.6 documentation - + diff --git a/search.html b/search.html index f84dc209..73e219f7 100644 --- a/search.html +++ b/search.html @@ -5,7 +5,7 @@ - Search — parsedmarc 9.0.5 documentation + Search — parsedmarc 9.0.6 documentation @@ -13,7 +13,7 @@ - + diff --git a/searchindex.js b/searchindex.js index d1b121a2..e157412f 100644 --- a/searchindex.js +++ b/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles":{"API reference":[[0,null]],"Accessing an inbox using OWA/EWS":[[2,null]],"Bug reports":[[1,"bug-reports"]],"CLI help":[[12,"cli-help"]],"CSV aggregate report":[[10,"csv-aggregate-report"]],"CSV forensic report":[[10,"csv-forensic-report"]],"Configuration file":[[12,"configuration-file"]],"Configuring parsedmarc for DavMail":[[2,"configuring-parsedmarc-for-davmail"]],"Contents":[[5,null]],"Contributing to parsedmarc":[[1,null]],"DMARC Alignment Guide":[[3,"dmarc-alignment-guide"]],"DMARC Forensic Samples":[[7,"dmarc-forensic-samples"]],"DMARC Summary":[[7,"dmarc-summary"]],"DMARC guides":[[3,"dmarc-guides"]],"Do":[[3,"do"],[8,"do"]],"Do not":[[3,"do-not"],[8,"do-not"]],"Elasticsearch and Kibana":[[4,null]],"Features":[[5,"features"]],"Indices and tables":[[0,"indices-and-tables"]],"Installation":[[4,"installation"],[6,null],[9,"installation"]],"Installing parsedmarc":[[6,"installing-parsedmarc"]],"JSON SMTP TLS report":[[10,"json-smtp-tls-report"]],"JSON aggregate report":[[10,"json-aggregate-report"]],"JSON forensic report":[[10,"json-forensic-report"]],"LISTSERV":[[3,"listserv"],[8,"listserv"]],"Lookalike domains":[[3,"lookalike-domains"]],"Mailing list best practices":[[3,"mailing-list-best-practices"],[8,"mailing-list-best-practices"]],"Mailman 2":[[3,"mailman-2"],[3,"id1"],[8,"mailman-2"],[8,"id1"]],"Mailman 3":[[3,"mailman-3"],[3,"id2"],[8,"mailman-3"],[8,"id2"]],"Multi-tenant support":[[12,"multi-tenant-support"]],"OpenSearch and Grafana":[[9,null]],"Optional dependencies":[[6,"optional-dependencies"]],"Prerequisites":[[6,"prerequisites"]],"Python Compatibility":[[5,"python-compatibility"]],"Records retention":[[4,"records-retention"],[9,"records-retention"]],"Resources":[[3,"resources"]],"Running DavMail as a systemd service":[[2,"running-davmail-as-a-systemd-service"]],"Running parsedmarc as a systemd service":[[12,"running-parsedmarc-as-a-systemd-service"]],"SPF and DMARC record validation":[[3,"spf-and-dmarc-record-validation"]],"Sample aggregate report output":[[10,"sample-aggregate-report-output"]],"Sample forensic report output":[[10,"sample-forensic-report-output"]],"Sample outputs":[[10,null]],"Splunk":[[11,null]],"Testing multiple report analyzers":[[6,"testing-multiple-report-analyzers"]],"Understanding DMARC":[[3,null]],"Upgrading Kibana index patterns":[[4,"upgrading-kibana-index-patterns"]],"Using Microsoft Exchange":[[6,"using-microsoft-exchange"]],"Using a web proxy":[[6,"using-a-web-proxy"]],"Using parsedmarc":[[12,null]],"Using the Kibana dashboards":[[7,null]],"What about mailing lists?":[[3,"what-about-mailing-lists"],[8,null]],"What if a sender won\u2019t support DKIM/DMARC?":[[3,"what-if-a-sender-wont-support-dkim-dmarc"]],"Workarounds":[[3,"workarounds"],[8,"workarounds"]],"geoipupdate setup":[[6,"geoipupdate-setup"]],"parsedmarc":[[0,"module-parsedmarc"]],"parsedmarc documentation - Open source DMARC report analyzer and visualizer":[[5,null]],"parsedmarc.elastic":[[0,"module-parsedmarc.elastic"]],"parsedmarc.opensearch":[[0,"module-parsedmarc.opensearch"]],"parsedmarc.splunk":[[0,"module-parsedmarc.splunk"]],"parsedmarc.utils":[[0,"module-parsedmarc.utils"]]},"docnames":["api","contributing","davmail","dmarc","elasticsearch","index","installation","kibana","mailing-lists","opensearch","output","splunk","usage"],"envversion":{"sphinx":65,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1},"filenames":["api.md","contributing.md","davmail.md","dmarc.md","elasticsearch.md","index.md","installation.md","kibana.md","mailing-lists.md","opensearch.md","output.md","splunk.md","usage.md"],"indexentries":{"alreadysaved":[[0,"parsedmarc.elastic.AlreadySaved",false],[0,"parsedmarc.opensearch.AlreadySaved",false]],"convert_outlook_msg() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.convert_outlook_msg",false]],"create_indexes() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.create_indexes",false]],"create_indexes() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.create_indexes",false]],"decode_base64() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.decode_base64",false]],"downloaderror":[[0,"parsedmarc.utils.DownloadError",false]],"elasticsearcherror":[[0,"parsedmarc.elastic.ElasticsearchError",false]],"email_results() (in module parsedmarc)":[[0,"parsedmarc.email_results",false]],"emailparsererror":[[0,"parsedmarc.utils.EmailParserError",false]],"extract_report() (in module parsedmarc)":[[0,"parsedmarc.extract_report",false]],"extract_report_from_file_path() (in module parsedmarc)":[[0,"parsedmarc.extract_report_from_file_path",false]],"get_base_domain() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_base_domain",false]],"get_dmarc_reports_from_mailbox() (in module parsedmarc)":[[0,"parsedmarc.get_dmarc_reports_from_mailbox",false]],"get_dmarc_reports_from_mbox() (in module parsedmarc)":[[0,"parsedmarc.get_dmarc_reports_from_mbox",false]],"get_filename_safe_string() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_filename_safe_string",false]],"get_ip_address_country() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_ip_address_country",false]],"get_ip_address_info() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_ip_address_info",false]],"get_report_zip() (in module parsedmarc)":[[0,"parsedmarc.get_report_zip",false]],"get_reverse_dns() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_reverse_dns",false]],"get_service_from_reverse_dns_base_domain() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_service_from_reverse_dns_base_domain",false]],"hecclient (class in parsedmarc.splunk)":[[0,"parsedmarc.splunk.HECClient",false]],"human_timestamp_to_datetime() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.human_timestamp_to_datetime",false]],"human_timestamp_to_unix_timestamp() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.human_timestamp_to_unix_timestamp",false]],"invalidaggregatereport":[[0,"parsedmarc.InvalidAggregateReport",false]],"invaliddmarcreport":[[0,"parsedmarc.InvalidDMARCReport",false]],"invalidforensicreport":[[0,"parsedmarc.InvalidForensicReport",false]],"invalidsmtptlsreport":[[0,"parsedmarc.InvalidSMTPTLSReport",false]],"is_mbox() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.is_mbox",false]],"is_outlook_msg() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.is_outlook_msg",false]],"migrate_indexes() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.migrate_indexes",false]],"migrate_indexes() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.migrate_indexes",false]],"module":[[0,"module-parsedmarc",false],[0,"module-parsedmarc.elastic",false],[0,"module-parsedmarc.opensearch",false],[0,"module-parsedmarc.splunk",false],[0,"module-parsedmarc.utils",false]],"opensearcherror":[[0,"parsedmarc.opensearch.OpenSearchError",false]],"parse_aggregate_report_file() (in module parsedmarc)":[[0,"parsedmarc.parse_aggregate_report_file",false]],"parse_aggregate_report_xml() (in module parsedmarc)":[[0,"parsedmarc.parse_aggregate_report_xml",false]],"parse_email() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.parse_email",false]],"parse_forensic_report() (in module parsedmarc)":[[0,"parsedmarc.parse_forensic_report",false]],"parse_report_email() (in module parsedmarc)":[[0,"parsedmarc.parse_report_email",false]],"parse_report_file() (in module parsedmarc)":[[0,"parsedmarc.parse_report_file",false]],"parse_smtp_tls_report_json() (in module parsedmarc)":[[0,"parsedmarc.parse_smtp_tls_report_json",false]],"parsed_aggregate_reports_to_csv() (in module parsedmarc)":[[0,"parsedmarc.parsed_aggregate_reports_to_csv",false]],"parsed_aggregate_reports_to_csv_rows() (in module parsedmarc)":[[0,"parsedmarc.parsed_aggregate_reports_to_csv_rows",false]],"parsed_forensic_reports_to_csv() (in module parsedmarc)":[[0,"parsedmarc.parsed_forensic_reports_to_csv",false]],"parsed_forensic_reports_to_csv_rows() (in module parsedmarc)":[[0,"parsedmarc.parsed_forensic_reports_to_csv_rows",false]],"parsed_smtp_tls_reports_to_csv() (in module parsedmarc)":[[0,"parsedmarc.parsed_smtp_tls_reports_to_csv",false]],"parsed_smtp_tls_reports_to_csv_rows() (in module parsedmarc)":[[0,"parsedmarc.parsed_smtp_tls_reports_to_csv_rows",false]],"parsedmarc":[[0,"module-parsedmarc",false]],"parsedmarc.elastic":[[0,"module-parsedmarc.elastic",false]],"parsedmarc.opensearch":[[0,"module-parsedmarc.opensearch",false]],"parsedmarc.splunk":[[0,"module-parsedmarc.splunk",false]],"parsedmarc.utils":[[0,"module-parsedmarc.utils",false]],"parsererror":[[0,"parsedmarc.ParserError",false]],"query_dns() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.query_dns",false]],"save_aggregate_report_to_elasticsearch() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.save_aggregate_report_to_elasticsearch",false]],"save_aggregate_report_to_opensearch() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.save_aggregate_report_to_opensearch",false]],"save_aggregate_reports_to_splunk() (parsedmarc.splunk.hecclient method)":[[0,"parsedmarc.splunk.HECClient.save_aggregate_reports_to_splunk",false]],"save_forensic_report_to_elasticsearch() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.save_forensic_report_to_elasticsearch",false]],"save_forensic_report_to_opensearch() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.save_forensic_report_to_opensearch",false]],"save_forensic_reports_to_splunk() (parsedmarc.splunk.hecclient method)":[[0,"parsedmarc.splunk.HECClient.save_forensic_reports_to_splunk",false]],"save_output() (in module parsedmarc)":[[0,"parsedmarc.save_output",false]],"save_smtp_tls_report_to_elasticsearch() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.save_smtp_tls_report_to_elasticsearch",false]],"save_smtp_tls_report_to_opensearch() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.save_smtp_tls_report_to_opensearch",false]],"save_smtp_tls_reports_to_splunk() (parsedmarc.splunk.hecclient method)":[[0,"parsedmarc.splunk.HECClient.save_smtp_tls_reports_to_splunk",false]],"set_hosts() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.set_hosts",false]],"set_hosts() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.set_hosts",false]],"splunkerror":[[0,"parsedmarc.splunk.SplunkError",false]],"timestamp_to_datetime() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.timestamp_to_datetime",false]],"timestamp_to_human() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.timestamp_to_human",false]],"watch_inbox() (in module parsedmarc)":[[0,"parsedmarc.watch_inbox",false]]},"objects":{"":[[0,0,0,"-","parsedmarc"]],"parsedmarc":[[0,1,1,"","InvalidAggregateReport"],[0,1,1,"","InvalidDMARCReport"],[0,1,1,"","InvalidForensicReport"],[0,1,1,"","InvalidSMTPTLSReport"],[0,1,1,"","ParserError"],[0,0,0,"-","elastic"],[0,2,1,"","email_results"],[0,2,1,"","extract_report"],[0,2,1,"","extract_report_from_file_path"],[0,2,1,"","get_dmarc_reports_from_mailbox"],[0,2,1,"","get_dmarc_reports_from_mbox"],[0,2,1,"","get_report_zip"],[0,0,0,"-","opensearch"],[0,2,1,"","parse_aggregate_report_file"],[0,2,1,"","parse_aggregate_report_xml"],[0,2,1,"","parse_forensic_report"],[0,2,1,"","parse_report_email"],[0,2,1,"","parse_report_file"],[0,2,1,"","parse_smtp_tls_report_json"],[0,2,1,"","parsed_aggregate_reports_to_csv"],[0,2,1,"","parsed_aggregate_reports_to_csv_rows"],[0,2,1,"","parsed_forensic_reports_to_csv"],[0,2,1,"","parsed_forensic_reports_to_csv_rows"],[0,2,1,"","parsed_smtp_tls_reports_to_csv"],[0,2,1,"","parsed_smtp_tls_reports_to_csv_rows"],[0,2,1,"","save_output"],[0,0,0,"-","splunk"],[0,0,0,"-","utils"],[0,2,1,"","watch_inbox"]],"parsedmarc.elastic":[[0,1,1,"","AlreadySaved"],[0,1,1,"","ElasticsearchError"],[0,2,1,"","create_indexes"],[0,2,1,"","migrate_indexes"],[0,2,1,"","save_aggregate_report_to_elasticsearch"],[0,2,1,"","save_forensic_report_to_elasticsearch"],[0,2,1,"","save_smtp_tls_report_to_elasticsearch"],[0,2,1,"","set_hosts"]],"parsedmarc.opensearch":[[0,1,1,"","AlreadySaved"],[0,1,1,"","OpenSearchError"],[0,2,1,"","create_indexes"],[0,2,1,"","migrate_indexes"],[0,2,1,"","save_aggregate_report_to_opensearch"],[0,2,1,"","save_forensic_report_to_opensearch"],[0,2,1,"","save_smtp_tls_report_to_opensearch"],[0,2,1,"","set_hosts"]],"parsedmarc.splunk":[[0,3,1,"","HECClient"],[0,1,1,"","SplunkError"]],"parsedmarc.splunk.HECClient":[[0,4,1,"","save_aggregate_reports_to_splunk"],[0,4,1,"","save_forensic_reports_to_splunk"],[0,4,1,"","save_smtp_tls_reports_to_splunk"]],"parsedmarc.utils":[[0,1,1,"","DownloadError"],[0,1,1,"","EmailParserError"],[0,2,1,"","convert_outlook_msg"],[0,2,1,"","decode_base64"],[0,2,1,"","get_base_domain"],[0,2,1,"","get_filename_safe_string"],[0,2,1,"","get_ip_address_country"],[0,2,1,"","get_ip_address_info"],[0,2,1,"","get_reverse_dns"],[0,2,1,"","get_service_from_reverse_dns_base_domain"],[0,2,1,"","human_timestamp_to_datetime"],[0,2,1,"","human_timestamp_to_unix_timestamp"],[0,2,1,"","is_mbox"],[0,2,1,"","is_outlook_msg"],[0,2,1,"","parse_email"],[0,2,1,"","query_dns"],[0,2,1,"","timestamp_to_datetime"],[0,2,1,"","timestamp_to_human"]]},"objnames":{"0":["py","module","Python module"],"1":["py","exception","Python exception"],"2":["py","function","Python function"],"3":["py","class","Python class"],"4":["py","method","Python method"]},"objtypes":{"0":"py:module","1":"py:exception","2":"py:function","3":"py:class","4":"py:method"},"terms":{"":[0,2,3,4,6,8,10,12],"0":[0,2,3,4,5,6,8,9,10,11,12],"00":10,"003":10,"00z":10,"00z_exampl":10,"01":10,"0200":10,"0240":10,"04":10,"08":10,"09":10,"09t00":10,"09t23":10,"1":[0,2,4,5,6,10,12],"10":[0,5,6,10,12],"100":[10,12],"1000":12,"11":[5,6,10],"1143":2,"12":5,"12201":12,"127":[2,4,12],"13":5,"14":5,"150":10,"16":[3,8],"173":10,"176":10,"19":[10,12],"1d":12,"1g":4,"1w":12,"2":[0,4,10,12],"20":10,"2000":12,"201":10,"2010":[6,10],"2012":10,"2013":6,"2016":6,"2017a":[3,8],"2018":10,"2019":6,"2024":10,"2026":5,"2028":5,"2030":5,"2032":5,"2035":5,"208":10,"209":10,"21":6,"212":10,"22":6,"222":10,"23":10,"2369":[3,8],"24":0,"241":10,"25":12,"27":10,"28":10,"2919":[3,8],"2d":12,"2k":12,"3":[5,6,10,11,12],"30":[0,12],"300":2,"30937":10,"30th":6,"3128":6,"365":[2,4],"38":10,"3d":10,"3h":12,"4":[4,6,11],"4096":4,"41":10,"5":[2,4,9],"514":12,"5601":4,"59":10,"59z":10,"5m":[2,12],"6":[0,4,5,6,12],"60":[0,12],"660":4,"7":[4,5,6],"72":10,"7480":10,"8":[2,4,5,6,10,12],"8080":12,"822":0,"85":10,"86399":10,"86400":10,"9":[5,6],"9200":[4,12],"932":12,"9391651994964116463":10,"94":10,"993":12,"A":[0,3,12],"And":0,"As":[4,7],"Be":6,"By":[7,12],"For":[4,12],"If":[0,3,4,6,7,8,12],"In":[2,3,7,8,12],"It":[2,4,7,10,12],"No":[3,8],"Not":5,"On":[3,4,6,7,8],"Or":[4,6],"That":7,"The":[0,3,6,7,11,12],"Then":[2,3,4,6,8,12],"These":7,"To":[2,4,6,7,9,10,12],"With":7,"_cluster":12,"_input":0,"abl":6,"about":[0,5,6],"abov":[2,12],"accept":[3,4,8,12],"access":[0,4,5,6,12],"access_key_id":12,"access_token":0,"accessright":12,"accident":[3,8],"account":[6,7],"acm":10,"acquir":12,"across":7,"action":[3,8],"activ":[4,5,6],"active_primary_shard":12,"active_shard":12,"actual":[3,10],"ad":[3,6,8,12],"add":[2,3,4,6,7,8,12],"addit":[3,8,12],"address":[0,2,3,4,7,8,10,12],"addresse":7,"adkim":10,"admin":[3,8,12],"administr":[3,8],"after":[0,2,4,12],"against":[3,8],"agari":5,"agent":4,"aggreg":[0,5,7,11,12],"aggregate_csv_filenam":[0,12],"aggregate_index":0,"aggregate_json_filenam":[0,12],"aggregate_report":0,"aggregate_top":12,"aggregate_url":12,"align":[5,7,10],"aliv":0,"all":[3,5,7,8,11,12],"allow":[2,3,8,12],"allow_unencrypted_storag":12,"allowremot":2,"alreadysav":0,"also":[2,3,7,8,12],"alter":[3,8],"altern":[5,12],"although":11,"alwai":[0,2,4,12],"always_use_local_fil":[0,12],"an":[0,3,5,7,8,10,12],"analyz":12,"ani":[0,3,7,8,12],"anonym":10,"anoth":[6,12],"answer":[0,12],"apach":5,"api":[2,4,5,12],"api_kei":[0,12],"app":12,"appear":12,"appendix":10,"appid":12,"appli":12,"applic":12,"applicationaccesspolici":12,"approach":12,"approxim":2,"apt":[2,4,6],"ar":[0,2,3,4,5,6,7,8,10,12],"archiv":[0,12],"archive_fold":[0,12],"argument":12,"arriv":12,"arrival_d":10,"arrival_date_utc":10,"artifact":4,"ask":3,"asmx":2,"asn":6,"aspf":10,"assign":4,"assist":5,"associ":0,"attach":[0,3,8,10,12],"attachment_filenam":0,"attribut":6,"august":5,"auth":[2,10,12],"auth_failur":10,"auth_method":12,"auth_result":10,"authent":[0,2,3,4,7,12],"authentication_mechan":10,"authentication_result":10,"auto":2,"avoid":7,"azur":12,"b":[6,10],"b2c":7,"back":12,"base":[0,2,3,4,7,8,10],"base64":0,"base_domain":[0,10],"basic":[2,12],"batch_siz":[0,12],"bcc":[0,10],"bd6e1bb5":10,"becaus":[2,3,7,8,12],"been":[7,12],"befor":[0,12],"begin_d":10,"behind":6,"being":0,"below":[3,8,12],"best":7,"between":[4,7],"beyond":0,"bin":[2,4,6,12],"binari":0,"bind":2,"bindaddress":2,"blank":[3,8],"block":[2,12],"bodi":[0,3,8,10,12],"bool":[0,12],"brand":[5,7],"break":[3,4,8],"browser":4,"bucket":12,"bug":5,"build":6,"built":0,"busi":7,"buster":6,"button":[3,8],"byte":0,"c":[10,12],"ca":4,"cach":[0,12],"call":[7,12],"callabl":0,"callback":0,"came":[3,8],"can":[0,2,3,4,5,6,7,8,12],"cannot":[6,12],"case":[2,3,8],"caus":[3,4,7,8],"cc":[0,10],"center":7,"cento":[4,6],"cert":4,"cert_path":12,"certain":[0,12],"certif":[0,4,12],"cest":10,"chain":0,"chang":[4,7,11,12],"charact":[2,12],"charset":10,"chart":7,"check":[0,2,3,4,6,12],"check_timeout":[0,12],"checkbox":4,"checkdmarc":3,"chines":7,"chmod":[2,4,12],"choos":[3,8],"chown":[2,12],"cisco":12,"citi":6,"class":0,"cli":5,"click":[4,7],"client":[2,3,4,8,12],"client_id":12,"client_secret":12,"clientsecret":12,"clientsotimeout":2,"cloud":12,"cloudflar":[0,12],"cluster":[4,12],"co":4,"code":[0,4,5],"collect":[7,12],"collector":[11,12],"com":[1,2,3,8,9,10,12],"come":7,"comma":[6,12],"command":[2,3,8,12],"comment":12,"commerci":[4,5],"common":[3,4,6,8],"commun":[3,8],"complet":[3,4],"compli":[3,4,6,8,9],"compliant":[3,8],"compon":6,"compress":5,"conf":6,"config":[2,6,12],"config_fil":12,"configur":[3,4,5,6,7,8,9],"conform":4,"connect":[0,2,4,12],"connexion":4,"consid":[5,7],"consist":[0,5,10],"consol":[4,12],"consolid":7,"consum":7,"contact":7,"contain":[0,7,11,12],"content":[0,3,8,10,11],"contrib":6,"contribut":5,"contributor":5,"control":4,"convert":[0,3,8],"convert_outlook_msg":0,"copi":[0,6,11],"core":[3,8],"correct":6,"correctli":[7,12],"could":[3,4,8,12],"count":[2,10],"countri":[0,6,7,10],"crash":[2,4,12],"creat":[0,2,3,4,6,8,12],"create_fold":0,"create_index":0,"creativ":6,"credenti":[6,12],"credentials_fil":12,"cron":6,"crt":4,"csr":4,"csv":[0,5,12],"cumul":6,"current":[2,4,5,12],"custom":[7,12],"d":[0,4,12],"daemon":[2,4,12],"dai":[0,4,9,12],"daili":[0,12],"dashboard":[4,5,9,11],"dat":0,"data":[0,4,5,7,9,11,12],"databas":6,"date":[0,3,8,10],"date_utc":10,"datetim":0,"davmail":5,"db_path":0,"dbip":[0,12],"dce":12,"dcr":12,"dcr_aggregate_stream":12,"dcr_forensic_stream":12,"dcr_immutable_id":12,"dcr_smtp_tls_stream":12,"dd":0,"de":10,"dearmor":4,"deb":4,"debian":[4,5,6],"debug":12,"decemb":6,"decod":0,"decode_base64":0,"default":[0,2,4,5,6,7,12],"defens":5,"delai":[2,10],"deleg":12,"delet":[0,2,4,12],"delivery_result":10,"demystifi":3,"depend":[4,5,12],"deploi":[3,8],"describ":12,"descript":[2,6,12],"destin":0,"detail":[6,7],"dev":[6,12],"devel":6,"develop":5,"devicecod":12,"di":10,"dict":0,"dictionari":0,"differ":[6,7,12],"digest":[3,8],"directori":[0,12],"disabl":[2,12],"disclaim":[3,8],"disk":12,"displai":[3,7,11],"display_nam":10,"disposit":[7,10],"distribut":6,"dkim":[5,7,8,10],"dkim_align":10,"dkim_domain":10,"dkim_result":10,"dkim_selector":10,"dkm":3,"dmarc":[0,4,6,8,9,10,11,12],"dmarc_aggreg":4,"dmarc_align":10,"dmarc_forens":4,"dmarc_moderation_act":[3,8],"dmarc_none_moderation_act":[3,8],"dmarc_quarantine_moderation_act":[3,8],"dmarcian":5,"dmarcresport":12,"dn":[0,3,7,12],"dnf":6,"dns_test_address":12,"dns_timeout":[0,12],"do":[0,2,6,7,12],"doc":9,"doctyp":10,"document":[2,12],"doe":[3,8],"domain":[0,4,7,8,10,12],"domainawar":[1,3,12],"don":3,"down":7,"download":[0,2,4,6,12],"downloaderror":0,"draft":[5,10],"dtd":10,"due":5,"dummi":12,"dure":2,"e":[0,2,3,4,6,8,12],"e7":10,"each":[4,6,9,11,12],"earlier":7,"easi":[4,9],"easier":11,"echo":4,"edit":[2,6,12],"editor":11,"effici":4,"either":[5,12],"elast":[4,5],"elasticsearch":[0,5,12],"elasticsearcherror":0,"elk":12,"els":4,"email":[0,3,5,6,7,8,10,11,12],"email_result":0,"emailparsererror":0,"empti":[3,8],"en":[3,4,8,10],"enabl":[2,4,12],"enableew":2,"enablekeepal":2,"enableproxi":2,"encod":[0,10,12],"encount":0,"encrypt":[4,12],"encryptedsavedobject":4,"encryptionkei":4,"end":[3,4,5],"end_dat":10,"endpoint":12,"endpoint_url":12,"enforc":[3,8],"enrol":4,"ensur":[3,6,8],"entir":[3,7,8],"envelop":3,"envelope_from":10,"envelope_to":10,"environ":6,"eol":5,"error":[0,10,12],"escap":12,"especi":7,"etc":[2,3,4,6,8,12],"even":[2,3,8,12],"event":[2,11,12],"everi":[2,6,12],"ew":5,"ex":12,"exactli":[3,8],"exampl":[3,4,6,8,10,12],"except":[0,12],"exchang":[2,10,12],"exclud":2,"execstart":[2,12],"exist":[0,3,4,8],"exit":12,"expiringdict":0,"explain":[3,8],"explicit":[3,8],"explicitli":6,"export":4,"extract":[0,2],"extract_report":0,"extract_report_from_file_path":0,"ey":[2,12],"f":4,"factor":2,"fail":[0,3,7,8,10,12],"failed_session_count":10,"failur":[5,7,10,12],"failure_detail":10,"fall":12,"fallback":6,"fals":[0,2,6,10,12],"fantast":[3,8],"faster":12,"featur":[4,12],"feedback":0,"feedback_report":0,"feedback_typ":10,"fetch":[0,12],"few":[7,12],"field":4,"file":[0,2,5,6,11],"file_path":[0,12],"filenam":[0,12],"filename_safe_subject":10,"filepath":12,"fill":[4,6],"filter":[3,7,8,11],"financ":12,"find":[3,7,8,12],"fine":[3,8],"first":[3,6,8,12],"first_strip_reply_to":[3,8],"fit":[3,8],"fix":4,"flag":[0,2],"flat":0,"flexibl":11,"flight":12,"float":[0,12],"fo":10,"folder":[0,2,12],"foldersizelimit":2,"follow":[2,4,5],"footer":[3,8],"forens":[0,5,11,12],"forensic_csv_filenam":[0,12],"forensic_index":0,"forensic_json_filenam":[0,12],"forensic_report":0,"forensic_top":12,"forensic_url":12,"format":[0,6,12],"forward":[3,7,8],"found":[0,6,12],"foundat":10,"fqdn":4,"fraud":5,"free":6,"friendli":7,"from":[0,2,3,4,5,6,7,8,10,12],"from_is_list":[3,8],"ftp_proxi":6,"full":12,"fulli":[3,8],"function":0,"further":7,"g":[2,3,4,8,12],"gatewai":2,"gb":4,"gdpr":[4,9],"gelf":12,"gener":[3,4,6,8,10,12],"geoip":6,"geolite2":6,"geoloc":[0,12],"get":[0,2,4,6,12],"get_base_domain":0,"get_dmarc_reports_from_mailbox":0,"get_dmarc_reports_from_mbox":0,"get_filename_safe_str":0,"get_ip_address_countri":0,"get_ip_address_info":0,"get_report_zip":0,"get_reverse_dn":0,"get_service_from_reverse_dns_base_domain":0,"github":[1,6,10,12],"give":[0,4],"given":[0,12],"glass":7,"gmail":[5,7,12],"gmail_api":12,"go":[3,8],"goe":[3,8],"googl":[7,12],"googleapi":12,"got":12,"gov":12,"gpg":4,"grafana":5,"grant":12,"graph":[2,5,7,12],"graph_url":12,"group":[2,7,12],"guid":[4,5],"gzip":[0,5],"h":[0,12],"ha":[4,7,12],"hamburg":4,"hand":[3,8],"handl":[5,12],"has_defect":10,"have":[3,4,6,7,8,11,12],"head":10,"header":[0,3,7,8,10,12],"header_from":10,"headless":2,"health":12,"healthcar":12,"heap":4,"heavi":4,"hec":[0,11,12],"hecclient":0,"hectokengoesher":12,"help":5,"here":[3,8,10,12],"hh":0,"hi":[3,8],"high":7,"higher":[3,8],"highli":12,"hop":10,"host":[0,2,3,4,5,8,12],"hostnam":[0,12],"hour":[0,12],"hover":7,"how":5,"howev":6,"href":10,"html":[3,4,8,10],"http":[0,1,2,3,4,6,8,9,10,11,12],"http_proxi":6,"https_proxi":6,"human":[0,7],"human_timestamp":0,"human_timestamp_to_datetim":0,"human_timestamp_to_unix_timestamp":0,"i":[0,2,3,4,5,6,7,8,10,12],"icon":7,"id":[3,8,10,12],"ideal":[3,8],"ident":[3,8,12],"identifi":10,"idl":[0,2,12],"imap":[0,2,5,12],"imapalwaysapproxmsgs":2,"imapautoexpung":2,"imapcli":5,"imapidledelai":2,"imapport":2,"immedi":2,"immut":12,"impli":12,"import":[4,7],"improv":12,"inbox":[0,3,5,8,12],"inc":10,"includ":[0,3,6,7,8,12],"include_list_post_head":[3,8],"include_rfc2369_head":[3,8],"include_sender_head":[3,8],"include_spam_trash":12,"incom":[7,12],"incorrect":12,"increas":[4,12],"indent":12,"index":[0,5,9,11,12],"index_prefix":[0,12],"index_prefix_domain_map":12,"index_suffix":[0,12],"indic":[3,5],"individu":12,"industri":12,"inform":[0,4,6,7,12],"ingest":12,"ini":[2,12],"initi":0,"input":0,"input_":0,"insid":6,"instal":[2,5,12],"instanc":12,"instead":[0,3,6,8,12],"int":[0,12],"intend":[3,8],"interact":[2,4],"interakt":10,"interfer":[3,8],"intern":6,"interv":12,"interval_begin":10,"interval_end":10,"invalid":0,"invalidaggregatereport":0,"invaliddmarcreport":0,"invalidforensicreport":0,"invalidsmtptlsreport":0,"io":[0,12],"ip":[0,3,4,6,7,12],"ip_address":[0,10],"ip_db_path":[0,6,12],"ipdb":6,"ipv4":0,"ipv6":0,"is_mbox":0,"is_outlook_msg":0,"iso":0,"issu":[1,5],"java":2,"job":[3,6,8],"joe":[3,8],"journalctl":[2,12],"jre":2,"json":[0,5,12],"june":5,"just":7,"jvm":4,"kafka":[5,12],"kb4099855":6,"kb4134118":6,"kb4295699":6,"keep":0,"keep_al":0,"keepal":2,"kei":[0,3,4,6,12],"keyout":4,"keyr":4,"keystor":4,"kibana":[5,11],"kind":12,"know":3,"known":[3,7,8,12],"label":12,"languag":[3,8],"larg":2,"larger":12,"later":[4,6,12],"latest":[2,4,6,9],"layer":0,"layout":11,"leak":7,"least":[4,6,12],"leav":3,"left":7,"legal":[3,8],"legitim":[7,12],"level":[3,4],"libemail":6,"libxml2":6,"libxslt":6,"licens":6,"life":5,"like":[0,3,6,8,12],"limit":[0,2,12],"line":[3,8,12],"link":[3,4,7,8],"linux":[3,6,8],"list":[0,2,4,5,7,12],"listen":[2,12],"lite":6,"ll":[3,8],"load":4,"local":[0,2,4,10,12],"local_file_path":0,"local_reverse_dns_map_path":12,"localhost":12,"locat":[6,7,12],"log":[2,12],"log_analyt":12,"log_fil":12,"logger":12,"login":4,"logstash":4,"long":3,"longer":[3,8],"look":[3,7],"lookup":0,"loopback":2,"lot":7,"lua":10,"m":[0,6,10,12],"m365":12,"maco":6,"magnifi":7,"mai":[5,7,12],"maidir":12,"mail":[0,5,6,10,12],"mail_bcc":0,"mail_cc":0,"mail_from":0,"mail_to":0,"mailbox":[0,7,12],"mailbox_connect":0,"mailboxconnect":0,"maildir":12,"maildir_cr":12,"maildir_path":12,"mailer":10,"mailrelai":10,"mailto":6,"main":4,"maintain":5,"make":[0,3,4,8,9,12],"malici":[7,12],"manag":[4,12],"manual":12,"map":[0,12],"market":7,"match":[0,4,11,12],"max_ag":10,"max_shards_per_nod":12,"maximum":4,"maxmind":[0,6,12],"mbox":[0,12],"mechan":3,"member":[3,8],"mention":7,"menu":[4,7],"messag":[0,2,3,4,6,7,8,10,12],"message_id":10,"meta":10,"method":12,"mfrom":10,"microsoft":[2,5,10,12],"might":[0,3,7,8],"migrate_index":0,"mime":10,"minimum":4,"minut":[0,2,12],"mitig":[3,8],"mkdir":6,"mm":0,"mmdb":[0,12],"mobil":[3,8],"mode":[2,4,10,12],"modern":[2,3,8],"modifi":[3,8,12],"modul":[0,5,12],"mon":10,"monitor":[3,12],"monthli":[0,12],"monthly_index":[0,12],"more":[0,4,6,11,12],"most":[3,4,7,8,12],"mous":7,"move":[0,4,12],"msg":[0,6],"msg_byte":0,"msg_date":0,"msg_footer":[3,8],"msg_header":[3,8],"msgconvert":[0,6],"msgraph":12,"much":12,"multi":[2,5],"multipl":12,"mung":[3,8],"must":[2,3,8,12],"mutual":4,"mv":4,"mx":10,"my":12,"n":[10,12],"n_proc":12,"name":[0,3,4,7,10,11,12],"nameserv":[0,12],"nano":[2,12],"nation":12,"navig":[3,6,8],"ncontent":10,"ndate":10,"ndjson":4,"need":[2,3,4,6,7,8,12],"nelson":[3,8],"net":[2,12],"network":[2,4,12],"new":[0,2,3,6,7,12],"newer":6,"newest":[2,12],"newkei":4,"next":[0,12],"nfrom":10,"nmessag":10,"nmime":10,"node":4,"non":[3,8,12],"none":[0,3,10,12],"noproxyfor":2,"norepli":[3,10],"normal":[0,10,12],"normalize_timespan_threshold_hour":0,"normalized_timespan":10,"nosecureimap":2,"notabl":7,"now":[4,7],"nsubject":10,"nto":10,"null":10,"number":[0,12],"number_of_replica":[0,12],"number_of_shard":[0,12],"nwettbewerb":10,"nx":10,"o":[2,4,12],"oauth2":12,"oauth2_port":12,"object":[0,4],"observ":7,"occur":[0,7],"occurr":11,"oct":10,"offic":2,"office365":2,"offlin":[0,12],"often":7,"ol":[0,6],"old":7,"older":[6,10],"oldest":[2,12],"onc":6,"ondmarc":5,"one":[0,3,5,8,12],"onli":[2,3,6,7,8,12],"onlin":[0,2,12],"oor":0,"open":3,"opendn":12,"opensearch":[5,12],"opensearcherror":0,"openssl":4,"opt":[2,6,12],"option":[0,2,3,4,5,8,11,12],"order":6,"ordereddict":0,"org":[0,6,9,10,12],"org_email":10,"org_extra_contact_info":10,"org_nam":10,"organ":[2,7,12],"organization_nam":10,"origin":[3,8,12],"original_envelope_id":10,"original_mail_from":10,"original_rcpt_to":10,"original_timespan_second":10,"other":[0,3,4,7,8],"our":7,"out":[3,4,7],"outdat":7,"outgo":[3,8,12],"outlook":[0,2,6],"output":[0,5,12],"output_directori":0,"outsid":12,"over":[2,5,7],"overrid":[0,12],"overridden":6,"overwrit":4,"owa":5,"own":[7,11],"p":[3,6,10],"p12":4,"pack":4,"packag":[0,4],"pad":0,"page":[3,4,6,7,8],"paginate_messag":12,"pan":10,"parallel":12,"paramet":0,"parent":7,"pars":[0,3,5,6,10,12],"parse_aggregate_report_fil":0,"parse_aggregate_report_xml":0,"parse_email":0,"parse_forensic_report":0,"parse_report_email":0,"parse_report_fil":0,"parse_smtp_tls_report_json":0,"parsed_aggregate_reports_to_csv":0,"parsed_aggregate_reports_to_csv_row":0,"parsed_forensic_reports_to_csv":0,"parsed_forensic_reports_to_csv_row":0,"parsed_sampl":10,"parsed_smtp_tls_reports_to_csv":0,"parsed_smtp_tls_reports_to_csv_row":0,"parsedmarc":[4,9,10,11],"parser":0,"parsererror":0,"part":[3,4,7,8],"particular":7,"particularli":[5,12],"pass":[3,7,10],"passag":7,"passsword":12,"password":[0,4,6,12],"past":[4,11],"patch":6,"path":[0,4,12],"pattern":[5,7],"payload":[0,12],"pct":10,"per":12,"percentag":7,"perform":[2,12],"period":12,"perl":[0,6],"permiss":[4,12],"persist":12,"peter":10,"pie":7,"pin":5,"pip":6,"place":[4,7,12],"plain":0,"plaintext":[3,8],"platform":[3,8],"pleas":[1,5,12],"plu":7,"polici":[3,8,10,12],"policy_domain":10,"policy_evalu":10,"policy_override_com":10,"policy_override_reason":10,"policy_publish":10,"policy_str":10,"policy_typ":10,"policyscopegroupid":12,"poll":[2,12],"port":[0,2,12],"posit":12,"possibl":12,"post":[3,8,12],"poster":[3,8],"postoriu":[3,8],"powershel":12,"ppa":6,"pre":[6,12],"prefer":[2,6],"prefix":[0,3,8,12],"premad":[5,11],"prerequisit":5,"present":12,"pretti":12,"prettifi":12,"previou":[0,2,4,12],"previous":[4,7],"print":12,"printabl":10,"privaci":[3,6,7,8,12],"process":[0,2,5,6,12],"produc":10,"program":12,"programdata":6,"project":[0,2,3,5,11],"prompt":4,"proofpoint":5,"properti":2,"protect":[2,3,5,8,12],"provid":[4,7,12],"prox":6,"proxi":2,"proxyhost":2,"proxypassword":2,"proxyport":2,"proxyus":2,"pry":[2,12],"psl_overrid":0,"public":[0,3,10,12],"public_suffix_list":0,"publicbaseurl":4,"publicsuffix":0,"publish":3,"put":[4,12],"python":[0,6],"python3":6,"python39":6,"qo":4,"quarantin":[3,8],"queri":[0,12],"query_dn":0,"quickstart":12,"quot":10,"r":[2,6,10,12],"rais":0,"ram":4,"rather":[3,8],"read":[0,12],"readabl":0,"readwrit":12,"realli":3,"reason":[0,2,4,5,12],"receiv":[0,10,12],"receiving_ip":10,"receiving_mx_hostnam":10,"recipi":7,"recogn":7,"recommend":12,"record":[0,5,6,10],"record_typ":0,"refer":[4,5],"regard":12,"regardless":10,"region":12,"region_nam":12,"regist":6,"registr":12,"regul":[4,6,9,12],"regular":[3,8],"reject":[3,8],"relai":[3,8],"relat":[3,12],"releas":[4,6],"reli":7,"reliabl":12,"reload":[2,4,12],"remain":7,"remot":2,"remov":[0,3,4,8,12],"repeat":[3,8],"replac":[0,3,4,8],"repli":[2,3,8],"replica":[0,12],"reply_goes_to_list":[3,8],"reply_to":10,"replyto":[3,8],"report":[0,4,7,11,12],"report_id":10,"report_metadata":10,"report_typ":0,"reported_domain":10,"reports_fold":[0,12],"repositori":[6,11],"req":4,"request":[2,4,12],"requir":[0,2,3,4,6,8,12],"require_encrypt":0,"resid":12,"resolv":[0,12],"resourc":[0,4,5,12],"respons":[0,12],"restart":[2,3,4,8,12],"restartsec":[2,12],"restor":4,"restrict":12,"restrictaccess":12,"result":[0,5,7,10,12],"result_typ":10,"retain":[3,8],"retent":5,"retriev":2,"return":0,"revers":[0,7,12],"reverse_dn":[0,10],"reverse_dns_base_domain":0,"reverse_dns_map":0,"reverse_dns_map_path":0,"reverse_dns_map_url":[0,12],"review":[5,7],"rewrit":[3,8],"rfc":[0,3,8,10],"rfc2369":[3,8],"rfc822":2,"rhel":[4,5,6],"right":[4,7],"rm":4,"ro":0,"rollup":6,"root":[2,12],"rpm":4,"rsa":4,"rua":[5,6],"ruf":[5,6,7,12],"rule":[7,12],"run":[0,4,5,6],"rw":[2,12],"s3":12,"safe":0,"same":[3,4,6,7,11],"sampl":[0,5,12],"sample_headers_onli":10,"save":[0,4,6,12],"save_aggreg":12,"save_aggregate_report_to_elasticsearch":0,"save_aggregate_report_to_opensearch":0,"save_aggregate_reports_to_splunk":0,"save_forens":12,"save_forensic_report_to_elasticsearch":0,"save_forensic_report_to_opensearch":0,"save_forensic_reports_to_splunk":0,"save_output":0,"save_smtp_tl":12,"save_smtp_tls_report_to_elasticsearch":0,"save_smtp_tls_report_to_opensearch":0,"save_smtp_tls_reports_to_splunk":0,"schedul":6,"schema":10,"scope":[10,12],"scrub_nondigest":[3,8],"search":[0,3,8,12],"second":[0,2,12],"secret":12,"secret_access_kei":12,"section":12,"secur":[0,4,12],"see":[2,3,4,5,7,12],"segment":7,"select":6,"selector":10,"self":[4,5],"send":[0,2,3,4,5,7,8,11,12],"sender":[5,7,8],"sending_mta_ip":10,"sensit":12,"sent":[3,8,12],"separ":[3,4,6,7,9,11,12],"server":[0,2,3,4,6,7,10,12],"server_ip":4,"servernameon":10,"servic":[0,3,4,5,7,8],"session":7,"set":[0,2,3,4,6,7,8,9,12],"set_host":0,"setup":[4,9,12],"setuptool":6,"shard":[0,12],"share":[4,12],"sharepoint":10,"should":[3,6,7,8,12],"shouldn":[3,8],"show":[2,7,12],"side":7,"sign":[3,4,6],"signatur":[3,7,8],"silent":12,"similar":7,"simpl":5,"simplifi":0,"sinc":[0,12],"singl":[0,12],"sister":3,"size":[2,4],"skip":12,"skip_certificate_verif":12,"slightli":11,"small":4,"smtp":[0,3,5,7,12],"smtp_tl":[0,12],"smtp_tls_csv_filenam":[0,12],"smtp_tls_json_filenam":[0,12],"smtp_tls_report":0,"smtp_tls_url":12,"so":[3,6,7,8,12],"socket":2,"solut":6,"some":[0,2,3,4,7,8],"someon":4,"sometim":12,"sort":[7,12],"sourc":[0,3,4,6,7,10],"source_base_domain":10,"source_countri":10,"source_ip_address":10,"source_nam":10,"source_reverse_dn":10,"source_typ":10,"sourceforg":2,"sp":[3,10],"spam":12,"special":12,"specif":[3,12],"specifi":[2,3],"spf":[7,10],"spf_align":10,"spf_domain":10,"spf_result":10,"spf_scope":10,"splunk":[5,12],"splunk_hec":12,"splunkerror":0,"splunkhec":12,"spoof":[3,8],"ss":0,"ssl":[0,2,4,12],"ssl_cert_path":0,"st":[10,12],"stabl":4,"stack":[4,12],"standard":[0,5,10],"start":[0,2,4,6,7,9,11,12],"starttl":12,"static":6,"statu":[2,12],"stdout":12,"step":[3,4,8],"still":[3,6,8,10,12],"storag":[0,12],"store":[2,4,9],"str":[0,12],"stream":12,"string":0,"strip":[3,8,12],"strip_attachment_payload":[0,12],"strongli":12,"structur":5,"stsv1":10,"subdomain":[0,3,12],"subject":[0,3,8,10,12],"subject_prefix":[3,8],"subsidiari":7,"successful_session_count":10,"sudo":[2,4,6,12],"suffix":[0,12],"suggest":7,"suitabl":0,"summari":[3,5,8],"suppli":[0,7,12],"support":[2,5,10,11],"sure":[4,6],"sw50zxjha3rpdmugv2v0dgjld2vyymvylcocymvyc2ljahq":10,"switch":7,"syslog":[2,12],"system":[2,3,4,6,8,12],"systemctl":[2,4,12],"systemd":5,"systemdr":6,"t":[5,8,12],"tab":[3,4,8],"tabl":[5,7],"tag":6,"target":[2,12],"task":6,"tby":10,"tcp":12,"tee":4,"tell":[3,6,7,8],"templat":[3,8],"temporari":7,"tenant":5,"tenant_id":12,"term":6,"test":[0,10,12],"text":[0,10],"than":[3,4,8,12],"thank":[5,10],"thei":[3,6,7,8,12],"theirs":3,"them":[0,4,7,12],"therebi":[3,8],"thi":[0,2,3,4,5,6,7,8,10,12],"those":6,"thousand":12,"three":7,"through":3,"time":[0,2,4,6,7,12],"timeout":[0,2,12],"timespan":0,"timespan_requires_norm":10,"timestamp":0,"timestamp_to_datetim":0,"timestamp_to_human":0,"timezon":10,"tl":[0,5,12],"tld":3,"to_domain":10,"to_utc":0,"token":[0,4,12],"token_fil":12,"tool":[6,12],"top":[3,7],"topic":12,"touch":[3,8],"tracker":1,"tradit":[3,8],"trail":12,"transfer":10,"transpar":5,"transport":[4,12],"trash":12,"true":[0,2,4,10,12],"trust":12,"truststor":4,"try":12,"tuesdai":6,"two":6,"txt":0,"type":[0,10,12],"u":[2,6,10,12],"ubuntu":[4,6],"udp":12,"ui":[3,8],"uncondition":[3,8],"under":[4,6,7],"underneath":7,"underscor":12,"understand":[5,7],"unencrypt":12,"unfortun":[3,8],"unit":[0,2,12],"unix":0,"unknown":0,"unsubscrib":[3,8],"until":[0,5,12],"unzip":2,"up":[0,2,4,6,7,9,12],"updat":[0,4,6,12],"upersecur":12,"upgrad":[2,5,6,12],"upload":12,"upper":7,"uri":6,"url":[0,2,12],"us":[0,3,4,5,8,10],"usag":12,"use_ssl":0,"user":[2,3,4,5,6,8,10,12],"user_ag":10,"useradd":[2,6],"usernam":[0,12],"usernamepassword":12,"usesystemproxi":2,"usr":4,"utc":0,"utf":10,"util":5,"v":[6,12],"valid":[0,7,10,12],"valimail":5,"valu":[0,3,4,7,8,12],"var":[3,8],"variou":6,"vendor":3,"venv":[6,12],"verbos":12,"veri":[4,7,12],"verif":[4,12],"verifi":0,"verification_mod":4,"version":[2,4,5,6,9,10,11,12],"vew":2,"via":2,"view":[7,12],"vim":4,"virtualenv":6,"visual":[4,9],"volum":7,"vulner":3,"w":[0,12],"w3c":10,"wa":[3,4,6,8],"wai":[4,7],"wait":[0,12],"want":[2,5,12],"wantedbi":[2,12],"warn":12,"watch":[0,2,4,12],"watch_inbox":0,"watcher":12,"web":[2,4],"webdav":2,"webhook":12,"webmail":[3,7,8],"week":[0,12],"weekli":6,"well":[2,12],"were":[7,12],"wettbewerb":10,"wget":4,"whalensolut":12,"what":5,"when":[0,3,5,7,8,12],"whenev":[0,2,12],"where":[0,2,3,8,12],"wherea":7,"wherev":12,"whether":0,"which":[2,4,5,7,12],"while":[7,12],"who":7,"why":[3,7],"wide":[6,10],"wiki":10,"window":6,"without":[3,4,7,8,12],"won":5,"work":[2,3,5,6,7,8],"workstat":2,"worst":3,"would":[3,5,6,8],"wrap":[3,8],"write":12,"www":[4,6,12],"x":[4,10],"x509":4,"xennn":10,"xml":[0,11],"xml_schema":10,"xms4g":4,"xmx4g":4,"xpack":4,"xxxx":4,"y":[4,6],"yahoo":7,"yaml":12,"ye":[3,8],"year":12,"yet":3,"yml":4,"you":[2,3,4,5,6,7,8,12],"your":[3,4,6,7,8,11,12],"yyyi":0,"zip":[0,2,5,12],"\u00fcbersicht":10},"titles":["API reference","Contributing to parsedmarc","Accessing an inbox using OWA/EWS","Understanding DMARC","Elasticsearch and Kibana","parsedmarc documentation - Open source DMARC report analyzer and visualizer","Installation","Using the Kibana dashboards","What about mailing lists?","OpenSearch and Grafana","Sample outputs","Splunk","Using parsedmarc"],"titleterms":{"2":[3,8],"3":[3,8],"about":[3,8],"access":2,"aggreg":10,"align":3,"an":2,"analyz":[5,6],"api":0,"best":[3,8],"bug":1,"cli":12,"compat":5,"configur":[2,12],"content":5,"contribut":1,"csv":10,"dashboard":7,"davmail":2,"depend":6,"dkim":3,"dmarc":[3,5,7],"do":[3,8],"document":5,"domain":3,"elast":0,"elasticsearch":4,"ew":2,"exchang":6,"featur":5,"file":12,"forens":[7,10],"geoipupd":6,"grafana":9,"guid":3,"help":12,"inbox":2,"index":4,"indic":0,"instal":[4,6,9],"json":10,"kibana":[4,7],"list":[3,8],"listserv":[3,8],"lookalik":3,"mail":[3,8],"mailman":[3,8],"microsoft":6,"multi":12,"multipl":6,"open":5,"opensearch":[0,9],"option":6,"output":10,"owa":2,"parsedmarc":[0,1,2,5,6,12],"pattern":4,"practic":[3,8],"prerequisit":6,"proxi":6,"python":5,"record":[3,4,9],"refer":0,"report":[1,5,6,10],"resourc":3,"retent":[4,9],"run":[2,12],"sampl":[7,10],"sender":3,"servic":[2,12],"setup":6,"smtp":10,"sourc":5,"spf":3,"splunk":[0,11],"summari":7,"support":[3,12],"systemd":[2,12],"t":3,"tabl":0,"tenant":12,"test":6,"tl":10,"understand":3,"upgrad":4,"us":[2,6,7,12],"util":0,"valid":3,"visual":5,"web":6,"what":[3,8],"won":3,"workaround":[3,8]}}) \ No newline at end of file +Search.setIndex({"alltitles":{"API reference":[[0,null]],"Accessing an inbox using OWA/EWS":[[2,null]],"Bug reports":[[1,"bug-reports"]],"CLI help":[[12,"cli-help"]],"CSV aggregate report":[[10,"csv-aggregate-report"]],"CSV forensic report":[[10,"csv-forensic-report"]],"Configuration file":[[12,"configuration-file"]],"Configuring parsedmarc for DavMail":[[2,"configuring-parsedmarc-for-davmail"]],"Contents":[[5,null]],"Contributing to parsedmarc":[[1,null]],"DMARC Alignment Guide":[[3,"dmarc-alignment-guide"]],"DMARC Forensic Samples":[[7,"dmarc-forensic-samples"]],"DMARC Summary":[[7,"dmarc-summary"]],"DMARC guides":[[3,"dmarc-guides"]],"Do":[[3,"do"],[8,"do"]],"Do not":[[3,"do-not"],[8,"do-not"]],"Elasticsearch and Kibana":[[4,null]],"Features":[[5,"features"]],"Indices and tables":[[0,"indices-and-tables"]],"Installation":[[4,"installation"],[6,null],[9,"installation"]],"Installing parsedmarc":[[6,"installing-parsedmarc"]],"JSON SMTP TLS report":[[10,"json-smtp-tls-report"]],"JSON aggregate report":[[10,"json-aggregate-report"]],"JSON forensic report":[[10,"json-forensic-report"]],"LISTSERV":[[3,"listserv"],[8,"listserv"]],"Lookalike domains":[[3,"lookalike-domains"]],"Mailing list best practices":[[3,"mailing-list-best-practices"],[8,"mailing-list-best-practices"]],"Mailman 2":[[3,"mailman-2"],[3,"id1"],[8,"mailman-2"],[8,"id1"]],"Mailman 3":[[3,"mailman-3"],[3,"id2"],[8,"mailman-3"],[8,"id2"]],"Multi-tenant support":[[12,"multi-tenant-support"]],"OpenSearch and Grafana":[[9,null]],"Optional dependencies":[[6,"optional-dependencies"]],"Prerequisites":[[6,"prerequisites"]],"Python Compatibility":[[5,"python-compatibility"]],"Records retention":[[4,"records-retention"],[9,"records-retention"]],"Resources":[[3,"resources"]],"Running DavMail as a systemd service":[[2,"running-davmail-as-a-systemd-service"]],"Running parsedmarc as a systemd service":[[12,"running-parsedmarc-as-a-systemd-service"]],"SPF and DMARC record validation":[[3,"spf-and-dmarc-record-validation"]],"Sample aggregate report output":[[10,"sample-aggregate-report-output"]],"Sample forensic report output":[[10,"sample-forensic-report-output"]],"Sample outputs":[[10,null]],"Splunk":[[11,null]],"Testing multiple report analyzers":[[6,"testing-multiple-report-analyzers"]],"Understanding DMARC":[[3,null]],"Upgrading Kibana index patterns":[[4,"upgrading-kibana-index-patterns"]],"Using Microsoft Exchange":[[6,"using-microsoft-exchange"]],"Using a web proxy":[[6,"using-a-web-proxy"]],"Using parsedmarc":[[12,null]],"Using the Kibana dashboards":[[7,null]],"What about mailing lists?":[[3,"what-about-mailing-lists"],[8,null]],"What if a sender won\u2019t support DKIM/DMARC?":[[3,"what-if-a-sender-wont-support-dkim-dmarc"]],"Workarounds":[[3,"workarounds"],[8,"workarounds"]],"geoipupdate setup":[[6,"geoipupdate-setup"]],"parsedmarc":[[0,"module-parsedmarc"]],"parsedmarc documentation - Open source DMARC report analyzer and visualizer":[[5,null]],"parsedmarc.elastic":[[0,"module-parsedmarc.elastic"]],"parsedmarc.opensearch":[[0,"module-parsedmarc.opensearch"]],"parsedmarc.splunk":[[0,"module-parsedmarc.splunk"]],"parsedmarc.utils":[[0,"module-parsedmarc.utils"]]},"docnames":["api","contributing","davmail","dmarc","elasticsearch","index","installation","kibana","mailing-lists","opensearch","output","splunk","usage"],"envversion":{"sphinx":65,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1},"filenames":["api.md","contributing.md","davmail.md","dmarc.md","elasticsearch.md","index.md","installation.md","kibana.md","mailing-lists.md","opensearch.md","output.md","splunk.md","usage.md"],"indexentries":{"alreadysaved":[[0,"parsedmarc.elastic.AlreadySaved",false],[0,"parsedmarc.opensearch.AlreadySaved",false]],"convert_outlook_msg() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.convert_outlook_msg",false]],"create_indexes() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.create_indexes",false]],"create_indexes() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.create_indexes",false]],"decode_base64() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.decode_base64",false]],"downloaderror":[[0,"parsedmarc.utils.DownloadError",false]],"elasticsearcherror":[[0,"parsedmarc.elastic.ElasticsearchError",false]],"email_results() (in module parsedmarc)":[[0,"parsedmarc.email_results",false]],"emailparsererror":[[0,"parsedmarc.utils.EmailParserError",false]],"extract_report() (in module parsedmarc)":[[0,"parsedmarc.extract_report",false]],"extract_report_from_file_path() (in module parsedmarc)":[[0,"parsedmarc.extract_report_from_file_path",false]],"get_base_domain() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_base_domain",false]],"get_dmarc_reports_from_mailbox() (in module parsedmarc)":[[0,"parsedmarc.get_dmarc_reports_from_mailbox",false]],"get_dmarc_reports_from_mbox() (in module parsedmarc)":[[0,"parsedmarc.get_dmarc_reports_from_mbox",false]],"get_filename_safe_string() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_filename_safe_string",false]],"get_ip_address_country() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_ip_address_country",false]],"get_ip_address_info() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_ip_address_info",false]],"get_report_zip() (in module parsedmarc)":[[0,"parsedmarc.get_report_zip",false]],"get_reverse_dns() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_reverse_dns",false]],"get_service_from_reverse_dns_base_domain() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.get_service_from_reverse_dns_base_domain",false]],"hecclient (class in parsedmarc.splunk)":[[0,"parsedmarc.splunk.HECClient",false]],"human_timestamp_to_datetime() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.human_timestamp_to_datetime",false]],"human_timestamp_to_unix_timestamp() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.human_timestamp_to_unix_timestamp",false]],"invalidaggregatereport":[[0,"parsedmarc.InvalidAggregateReport",false]],"invaliddmarcreport":[[0,"parsedmarc.InvalidDMARCReport",false]],"invalidforensicreport":[[0,"parsedmarc.InvalidForensicReport",false]],"invalidsmtptlsreport":[[0,"parsedmarc.InvalidSMTPTLSReport",false]],"ipaddressinfo (class in parsedmarc.utils)":[[0,"parsedmarc.utils.IPAddressInfo",false]],"is_mbox() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.is_mbox",false]],"is_outlook_msg() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.is_outlook_msg",false]],"migrate_indexes() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.migrate_indexes",false]],"migrate_indexes() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.migrate_indexes",false]],"module":[[0,"module-parsedmarc",false],[0,"module-parsedmarc.elastic",false],[0,"module-parsedmarc.opensearch",false],[0,"module-parsedmarc.splunk",false],[0,"module-parsedmarc.utils",false]],"opensearcherror":[[0,"parsedmarc.opensearch.OpenSearchError",false]],"parse_aggregate_report_file() (in module parsedmarc)":[[0,"parsedmarc.parse_aggregate_report_file",false]],"parse_aggregate_report_xml() (in module parsedmarc)":[[0,"parsedmarc.parse_aggregate_report_xml",false]],"parse_email() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.parse_email",false]],"parse_forensic_report() (in module parsedmarc)":[[0,"parsedmarc.parse_forensic_report",false]],"parse_report_email() (in module parsedmarc)":[[0,"parsedmarc.parse_report_email",false]],"parse_report_file() (in module parsedmarc)":[[0,"parsedmarc.parse_report_file",false]],"parse_smtp_tls_report_json() (in module parsedmarc)":[[0,"parsedmarc.parse_smtp_tls_report_json",false]],"parsed_aggregate_reports_to_csv() (in module parsedmarc)":[[0,"parsedmarc.parsed_aggregate_reports_to_csv",false]],"parsed_aggregate_reports_to_csv_rows() (in module parsedmarc)":[[0,"parsedmarc.parsed_aggregate_reports_to_csv_rows",false]],"parsed_forensic_reports_to_csv() (in module parsedmarc)":[[0,"parsedmarc.parsed_forensic_reports_to_csv",false]],"parsed_forensic_reports_to_csv_rows() (in module parsedmarc)":[[0,"parsedmarc.parsed_forensic_reports_to_csv_rows",false]],"parsed_smtp_tls_reports_to_csv() (in module parsedmarc)":[[0,"parsedmarc.parsed_smtp_tls_reports_to_csv",false]],"parsed_smtp_tls_reports_to_csv_rows() (in module parsedmarc)":[[0,"parsedmarc.parsed_smtp_tls_reports_to_csv_rows",false]],"parsedmarc":[[0,"module-parsedmarc",false]],"parsedmarc.elastic":[[0,"module-parsedmarc.elastic",false]],"parsedmarc.opensearch":[[0,"module-parsedmarc.opensearch",false]],"parsedmarc.splunk":[[0,"module-parsedmarc.splunk",false]],"parsedmarc.utils":[[0,"module-parsedmarc.utils",false]],"parsererror":[[0,"parsedmarc.ParserError",false]],"query_dns() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.query_dns",false]],"reversednsservice (class in parsedmarc.utils)":[[0,"parsedmarc.utils.ReverseDNSService",false]],"save_aggregate_report_to_elasticsearch() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.save_aggregate_report_to_elasticsearch",false]],"save_aggregate_report_to_opensearch() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.save_aggregate_report_to_opensearch",false]],"save_aggregate_reports_to_splunk() (parsedmarc.splunk.hecclient method)":[[0,"parsedmarc.splunk.HECClient.save_aggregate_reports_to_splunk",false]],"save_forensic_report_to_elasticsearch() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.save_forensic_report_to_elasticsearch",false]],"save_forensic_report_to_opensearch() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.save_forensic_report_to_opensearch",false]],"save_forensic_reports_to_splunk() (parsedmarc.splunk.hecclient method)":[[0,"parsedmarc.splunk.HECClient.save_forensic_reports_to_splunk",false]],"save_output() (in module parsedmarc)":[[0,"parsedmarc.save_output",false]],"save_smtp_tls_report_to_elasticsearch() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.save_smtp_tls_report_to_elasticsearch",false]],"save_smtp_tls_report_to_opensearch() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.save_smtp_tls_report_to_opensearch",false]],"save_smtp_tls_reports_to_splunk() (parsedmarc.splunk.hecclient method)":[[0,"parsedmarc.splunk.HECClient.save_smtp_tls_reports_to_splunk",false]],"set_hosts() (in module parsedmarc.elastic)":[[0,"parsedmarc.elastic.set_hosts",false]],"set_hosts() (in module parsedmarc.opensearch)":[[0,"parsedmarc.opensearch.set_hosts",false]],"splunkerror":[[0,"parsedmarc.splunk.SplunkError",false]],"timestamp_to_datetime() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.timestamp_to_datetime",false]],"timestamp_to_human() (in module parsedmarc.utils)":[[0,"parsedmarc.utils.timestamp_to_human",false]],"watch_inbox() (in module parsedmarc)":[[0,"parsedmarc.watch_inbox",false]]},"objects":{"":[[0,0,0,"-","parsedmarc"]],"parsedmarc":[[0,1,1,"","InvalidAggregateReport"],[0,1,1,"","InvalidDMARCReport"],[0,1,1,"","InvalidForensicReport"],[0,1,1,"","InvalidSMTPTLSReport"],[0,1,1,"","ParserError"],[0,0,0,"-","elastic"],[0,2,1,"","email_results"],[0,2,1,"","extract_report"],[0,2,1,"","extract_report_from_file_path"],[0,2,1,"","get_dmarc_reports_from_mailbox"],[0,2,1,"","get_dmarc_reports_from_mbox"],[0,2,1,"","get_report_zip"],[0,0,0,"-","opensearch"],[0,2,1,"","parse_aggregate_report_file"],[0,2,1,"","parse_aggregate_report_xml"],[0,2,1,"","parse_forensic_report"],[0,2,1,"","parse_report_email"],[0,2,1,"","parse_report_file"],[0,2,1,"","parse_smtp_tls_report_json"],[0,2,1,"","parsed_aggregate_reports_to_csv"],[0,2,1,"","parsed_aggregate_reports_to_csv_rows"],[0,2,1,"","parsed_forensic_reports_to_csv"],[0,2,1,"","parsed_forensic_reports_to_csv_rows"],[0,2,1,"","parsed_smtp_tls_reports_to_csv"],[0,2,1,"","parsed_smtp_tls_reports_to_csv_rows"],[0,2,1,"","save_output"],[0,0,0,"-","splunk"],[0,0,0,"-","utils"],[0,2,1,"","watch_inbox"]],"parsedmarc.elastic":[[0,1,1,"","AlreadySaved"],[0,1,1,"","ElasticsearchError"],[0,2,1,"","create_indexes"],[0,2,1,"","migrate_indexes"],[0,2,1,"","save_aggregate_report_to_elasticsearch"],[0,2,1,"","save_forensic_report_to_elasticsearch"],[0,2,1,"","save_smtp_tls_report_to_elasticsearch"],[0,2,1,"","set_hosts"]],"parsedmarc.opensearch":[[0,1,1,"","AlreadySaved"],[0,1,1,"","OpenSearchError"],[0,2,1,"","create_indexes"],[0,2,1,"","migrate_indexes"],[0,2,1,"","save_aggregate_report_to_opensearch"],[0,2,1,"","save_forensic_report_to_opensearch"],[0,2,1,"","save_smtp_tls_report_to_opensearch"],[0,2,1,"","set_hosts"]],"parsedmarc.splunk":[[0,3,1,"","HECClient"],[0,1,1,"","SplunkError"]],"parsedmarc.splunk.HECClient":[[0,4,1,"","save_aggregate_reports_to_splunk"],[0,4,1,"","save_forensic_reports_to_splunk"],[0,4,1,"","save_smtp_tls_reports_to_splunk"]],"parsedmarc.utils":[[0,1,1,"","DownloadError"],[0,1,1,"","EmailParserError"],[0,3,1,"","IPAddressInfo"],[0,3,1,"","ReverseDNSService"],[0,2,1,"","convert_outlook_msg"],[0,2,1,"","decode_base64"],[0,2,1,"","get_base_domain"],[0,2,1,"","get_filename_safe_string"],[0,2,1,"","get_ip_address_country"],[0,2,1,"","get_ip_address_info"],[0,2,1,"","get_reverse_dns"],[0,2,1,"","get_service_from_reverse_dns_base_domain"],[0,2,1,"","human_timestamp_to_datetime"],[0,2,1,"","human_timestamp_to_unix_timestamp"],[0,2,1,"","is_mbox"],[0,2,1,"","is_outlook_msg"],[0,2,1,"","parse_email"],[0,2,1,"","query_dns"],[0,2,1,"","timestamp_to_datetime"],[0,2,1,"","timestamp_to_human"]]},"objnames":{"0":["py","module","Python module"],"1":["py","exception","Python exception"],"2":["py","function","Python function"],"3":["py","class","Python class"],"4":["py","method","Python method"]},"objtypes":{"0":"py:module","1":"py:exception","2":"py:function","3":"py:class","4":"py:method"},"terms":{"":[0,2,3,4,6,8,10,12],"0":[0,2,3,4,5,6,8,9,10,11,12],"00":10,"003":10,"00z":10,"00z_exampl":10,"01":10,"0200":10,"0240":10,"04":10,"08":10,"09":10,"09t00":10,"09t23":10,"1":[0,2,4,5,6,10,12],"10":[0,5,6,10,12],"100":[10,12],"1000":12,"11":[5,6,10],"1143":2,"12":5,"12201":12,"127":[2,4,12],"13":5,"14":5,"150":10,"16":[3,8],"173":10,"176":10,"19":[10,12],"1d":12,"1g":4,"1w":12,"2":[0,4,10,12],"20":10,"2000":12,"201":10,"2010":[6,10],"2012":10,"2013":6,"2016":6,"2017a":[3,8],"2018":10,"2019":6,"2024":10,"2026":5,"2028":5,"2030":5,"2032":5,"2035":5,"208":10,"209":10,"21":6,"212":10,"22":6,"222":10,"23":10,"2369":[3,8],"24":0,"241":10,"25":12,"27":10,"28":10,"2919":[3,8],"2d":12,"2k":12,"3":[5,6,10,11,12],"30":[0,12],"300":2,"30937":10,"30th":6,"3128":6,"365":[2,4],"38":10,"3d":10,"3h":12,"4":[4,6,11],"4096":4,"41":10,"5":[2,4,9],"514":12,"5601":4,"59":10,"59z":10,"5m":[2,12],"6":[0,4,5,6,12],"60":[0,12],"660":4,"7":[4,5,6],"72":10,"7480":10,"8":[2,4,5,6,10,12],"8080":12,"822":0,"85":10,"86399":10,"86400":10,"9":[5,6],"9200":[4,12],"932":12,"9391651994964116463":10,"94":10,"993":12,"A":[0,3,12],"And":0,"As":[4,7],"Be":6,"By":[7,12],"For":[4,12],"If":[0,3,4,6,7,8,12],"In":[2,3,7,8,12],"It":[2,4,7,10,12],"No":[3,8],"Not":5,"On":[3,4,6,7,8],"Or":[4,6],"That":7,"The":[0,3,6,7,11,12],"Then":[2,3,4,6,8,12],"These":7,"To":[2,4,6,7,9,10,12],"With":7,"_cluster":12,"_input":0,"abl":6,"about":[0,5,6],"abov":[2,12],"accept":[3,4,8,12],"access":[0,4,5,6,12],"access_key_id":12,"access_token":0,"accessright":12,"accident":[3,8],"account":[6,7],"acm":10,"acquir":12,"across":7,"action":[3,8],"activ":[4,5,6],"active_primary_shard":12,"active_shard":12,"actual":[3,10],"ad":[3,6,8,12],"add":[2,3,4,6,7,8,12],"addit":[3,8,12],"address":[0,2,3,4,7,8,10,12],"addresse":7,"adkim":10,"admin":[3,8,12],"administr":[3,8],"after":[0,2,4,12],"against":[3,8],"agari":5,"agent":4,"aggreg":[0,5,7,11,12],"aggregate_csv_filenam":[0,12],"aggregate_index":0,"aggregate_json_filenam":[0,12],"aggregate_report":0,"aggregate_top":12,"aggregate_url":12,"aggregateparsedreport":0,"aggregatereport":0,"align":[5,7,10],"aliv":0,"all":[3,5,7,8,11,12],"allow":[2,3,8,12],"allow_unencrypted_storag":12,"allowremot":2,"alreadysav":0,"also":[2,3,7,8,12],"alter":[3,8],"altern":[5,12],"although":11,"alwai":[0,2,4,12],"always_use_local_fil":[0,12],"an":[0,3,5,7,8,10,12],"analyz":12,"ani":[0,3,7,8,12],"anonym":10,"anoth":[6,12],"answer":[0,12],"apach":5,"api":[2,4,5,12],"api_kei":[0,12],"app":12,"appear":12,"appendix":10,"appid":12,"appli":12,"applic":12,"applicationaccesspolici":12,"approach":12,"approxim":2,"apt":[2,4,6],"ar":[0,2,3,4,5,6,7,8,10,12],"archiv":[0,12],"archive_fold":[0,12],"argument":12,"arriv":12,"arrival_d":10,"arrival_date_utc":10,"artifact":4,"ask":3,"asmx":2,"asn":6,"aspf":10,"assign":4,"assist":5,"associ":0,"attach":[0,3,8,10,12],"attachment_filenam":0,"attribut":6,"august":5,"auth":[2,10,12],"auth_failur":10,"auth_method":12,"auth_result":10,"authent":[0,2,3,4,7,12],"authentication_mechan":10,"authentication_result":10,"auto":2,"avoid":7,"azur":12,"b":[6,10],"b2c":7,"back":12,"base":[0,2,3,4,7,8,10],"base64":0,"base_domain":[0,10],"basic":[2,12],"batch_siz":[0,12],"bcc":[0,10],"bd6e1bb5":10,"becaus":[2,3,7,8,12],"been":[7,12],"befor":[0,12],"begin_d":10,"behind":6,"being":0,"below":[3,8,12],"best":7,"between":[4,7],"beyond":0,"bin":[2,4,6,12],"binari":0,"binaryio":0,"bind":2,"bindaddress":2,"blank":[3,8],"block":[2,12],"bodi":[0,3,8,10,12],"bool":[0,12],"brand":[5,7],"break":[3,4,8],"browser":4,"bucket":12,"bug":5,"build":6,"built":0,"busi":7,"buster":6,"button":[3,8],"byte":0,"c":[10,12],"ca":4,"cach":[0,12],"call":[7,12],"callabl":0,"callback":0,"came":[3,8],"can":[0,2,3,4,5,6,7,8,12],"cannot":[6,12],"case":[2,3,8],"caus":[3,4,7,8],"cc":[0,10],"center":7,"cento":[4,6],"cert":4,"cert_path":12,"certain":[0,12],"certif":[0,4,12],"cest":10,"chain":0,"chang":[4,7,11,12],"charact":[2,12],"charset":10,"chart":7,"check":[0,2,3,4,6,12],"check_timeout":[0,12],"checkbox":4,"checkdmarc":3,"chines":7,"chmod":[2,4,12],"choos":[3,8],"chown":[2,12],"cisco":12,"citi":6,"class":0,"cli":5,"click":[4,7],"client":[2,3,4,8,12],"client_id":12,"client_secret":12,"clientsecret":12,"clientsotimeout":2,"cloud":12,"cloudflar":[0,12],"cluster":[4,12],"co":4,"code":[0,4,5],"collect":[7,12],"collector":[11,12],"com":[1,2,3,8,9,10,12],"come":7,"comma":[6,12],"command":[2,3,8,12],"comment":12,"commerci":[4,5],"common":[3,4,6,8],"commun":[3,8],"complet":[3,4],"compli":[3,4,6,8,9],"compliant":[3,8],"compon":6,"compress":5,"conf":6,"config":[2,6,12],"config_fil":12,"configur":[3,4,5,6,7,8,9],"conform":4,"connect":[0,2,4,12],"connexion":4,"consid":[5,7],"consist":[0,5,10],"consol":[4,12],"consolid":7,"consum":7,"contact":7,"contain":[0,7,11,12],"content":[0,3,8,10,11],"contrib":6,"contribut":5,"contributor":5,"control":4,"convert":[0,3,8],"convert_outlook_msg":0,"copi":[0,6,11],"core":[3,8],"correct":6,"correctli":[7,12],"could":[3,4,8,12],"count":[2,10],"countri":[0,6,7,10],"crash":[2,4,12],"creat":[0,2,3,4,6,8,12],"create_fold":0,"create_index":0,"creativ":6,"credenti":[6,12],"credentials_fil":12,"cron":6,"crt":4,"csr":4,"csv":[0,5,12],"cumul":6,"current":[2,4,5,12],"custom":[7,12],"d":[0,4,12],"daemon":[2,4,12],"dai":[0,4,9,12],"daili":[0,12],"dashboard":[4,5,9,11],"dat":0,"data":[0,4,5,7,9,11,12],"databas":6,"date":[0,3,8,10],"date_utc":10,"datetim":0,"davmail":5,"db_path":0,"dbip":[0,12],"dce":12,"dcr":12,"dcr_aggregate_stream":12,"dcr_forensic_stream":12,"dcr_immutable_id":12,"dcr_smtp_tls_stream":12,"dd":0,"de":10,"dearmor":4,"deb":4,"debian":[4,5,6],"debug":12,"decemb":6,"decod":0,"decode_base64":0,"default":[0,2,4,5,6,7,12],"defens":5,"delai":[2,10],"deleg":12,"delet":[0,2,4,12],"delivery_result":10,"demystifi":3,"depend":[4,5,12],"deploi":[3,8],"describ":12,"descript":[2,6,12],"destin":0,"detail":[6,7],"dev":[6,12],"devel":6,"develop":5,"devicecod":12,"di":10,"dict":0,"dictionari":0,"differ":[6,7,12],"digest":[3,8],"directori":[0,12],"disabl":[2,12],"disclaim":[3,8],"disk":12,"displai":[3,7,11],"display_nam":10,"disposit":[7,10],"distribut":6,"dkim":[5,7,8,10],"dkim_align":10,"dkim_domain":10,"dkim_result":10,"dkim_selector":10,"dkm":3,"dmarc":[0,4,6,8,9,10,11,12],"dmarc_aggreg":4,"dmarc_align":10,"dmarc_forens":4,"dmarc_moderation_act":[3,8],"dmarc_none_moderation_act":[3,8],"dmarc_quarantine_moderation_act":[3,8],"dmarcian":5,"dmarcresport":12,"dn":[0,3,7,12],"dnf":6,"dns_test_address":12,"dns_timeout":[0,12],"do":[0,2,6,7,12],"doc":9,"doctyp":10,"document":[2,12],"doe":[3,8],"domain":[0,4,7,8,10,12],"domainawar":[1,3,12],"don":3,"down":7,"download":[0,2,4,6,12],"downloaderror":0,"draft":[5,10],"dtd":10,"due":5,"dummi":12,"dure":2,"e":[0,2,3,4,6,8,12],"e7":10,"each":[4,6,9,11,12],"earlier":7,"easi":[4,9],"easier":11,"echo":4,"edit":[2,6,12],"editor":11,"effici":4,"either":[5,12],"elast":[4,5],"elasticsearch":[0,5,12],"elasticsearcherror":0,"elk":12,"els":4,"email":[0,3,5,6,7,8,10,11,12],"email_result":0,"emailparsererror":0,"empti":[3,8],"en":[3,4,8,10],"enabl":[2,4,12],"enableew":2,"enablekeepal":2,"enableproxi":2,"encod":[0,10,12],"encount":0,"encrypt":[4,12],"encryptedsavedobject":4,"encryptionkei":4,"end":[3,4,5],"end_dat":10,"endpoint":12,"endpoint_url":12,"enforc":[3,8],"enrol":4,"ensur":[3,6,8],"entir":[3,7,8],"envelop":3,"envelope_from":10,"envelope_to":10,"environ":6,"eol":5,"error":[0,10,12],"escap":12,"especi":7,"etc":[2,3,4,6,8,12],"even":[2,3,8,12],"event":[2,11,12],"everi":[2,6,12],"ew":5,"ex":12,"exactli":[3,8],"exampl":[3,4,6,8,10,12],"except":[0,12],"exchang":[2,10,12],"exclud":2,"execstart":[2,12],"exist":[0,3,4,8],"exit":12,"expiringdict":0,"explain":[3,8],"explicit":[3,8],"explicitli":6,"export":4,"extract":[0,2],"extract_report":0,"extract_report_from_file_path":0,"ey":[2,12],"f":4,"factor":2,"fail":[0,3,7,8,10,12],"failed_session_count":10,"failur":[5,7,10,12],"failure_detail":10,"fall":12,"fallback":6,"fals":[0,2,6,10,12],"fantast":[3,8],"faster":12,"featur":[4,12],"feedback":0,"feedback_report":0,"feedback_typ":10,"fetch":[0,12],"few":[7,12],"field":4,"file":[0,2,5,6,11],"file_path":[0,12],"filenam":[0,12],"filename_safe_subject":10,"filepath":12,"fill":[4,6],"filter":[3,7,8,11],"financ":12,"find":[3,7,8,12],"fine":[3,8],"first":[3,6,8,12],"first_strip_reply_to":[3,8],"fit":[3,8],"fix":4,"flag":[0,2],"flat":0,"flexibl":11,"flight":12,"float":[0,12],"fo":10,"folder":[0,2,12],"foldersizelimit":2,"follow":[2,4,5],"footer":[3,8],"forens":[0,5,11,12],"forensic_csv_filenam":[0,12],"forensic_index":0,"forensic_json_filenam":[0,12],"forensic_report":0,"forensic_top":12,"forensic_url":12,"forensicparsedreport":0,"forensicreport":0,"format":[0,6,12],"forward":[3,7,8],"found":[0,6,12],"foundat":10,"fqdn":4,"fraud":5,"free":6,"friendli":7,"from":[0,2,3,4,5,6,7,8,10,12],"from_is_list":[3,8],"ftp_proxi":6,"full":12,"fulli":[3,8],"function":0,"further":7,"g":[2,3,4,8,12],"gatewai":2,"gb":4,"gdpr":[4,9],"gelf":12,"gener":[3,4,6,8,10,12],"geoip":6,"geolite2":6,"geoloc":[0,12],"get":[0,2,4,6,12],"get_base_domain":0,"get_dmarc_reports_from_mailbox":0,"get_dmarc_reports_from_mbox":0,"get_filename_safe_str":0,"get_ip_address_countri":0,"get_ip_address_info":0,"get_report_zip":0,"get_reverse_dn":0,"get_service_from_reverse_dns_base_domain":0,"github":[1,6,10,12],"give":[0,4],"given":[0,12],"glass":7,"gmail":[5,7,12],"gmail_api":12,"go":[3,8],"goe":[3,8],"googl":[7,12],"googleapi":12,"got":12,"gov":12,"gpg":4,"grafana":5,"grant":12,"graph":[2,5,7,12],"graph_url":12,"group":[2,7,12],"guid":[4,5],"gzip":[0,5],"h":[0,12],"ha":[4,7,12],"hamburg":4,"hand":[3,8],"handl":[5,12],"has_defect":10,"have":[3,4,6,7,8,11,12],"head":10,"header":[0,3,7,8,10,12],"header_from":10,"headless":2,"health":12,"healthcar":12,"heap":4,"heavi":4,"hec":[0,11,12],"hecclient":0,"hectokengoesher":12,"help":5,"here":[3,8,10,12],"hh":0,"hi":[3,8],"high":7,"higher":[3,8],"highli":12,"hop":10,"host":[0,2,3,4,5,8,12],"hostnam":[0,12],"hour":[0,12],"hover":7,"how":5,"howev":6,"href":10,"html":[3,4,8,10],"http":[0,1,2,3,4,6,8,9,10,11,12],"http_proxi":6,"https_proxi":6,"human":[0,7],"human_timestamp":0,"human_timestamp_to_datetim":0,"human_timestamp_to_unix_timestamp":0,"i":[0,2,3,4,5,6,7,8,10,12],"icon":7,"id":[3,8,10,12],"ideal":[3,8],"ident":[3,8,12],"identifi":10,"idl":[0,2,12],"imap":[0,2,5,12],"imapalwaysapproxmsgs":2,"imapautoexpung":2,"imapcli":5,"imapidledelai":2,"imapport":2,"immedi":2,"immut":12,"impli":12,"import":[4,7],"improv":12,"inbox":[0,3,5,8,12],"inc":10,"includ":[0,3,6,7,8,12],"include_list_post_head":[3,8],"include_rfc2369_head":[3,8],"include_sender_head":[3,8],"include_spam_trash":12,"incom":[7,12],"incorrect":12,"increas":[4,12],"indent":12,"index":[0,5,9,11,12],"index_prefix":[0,12],"index_prefix_domain_map":12,"index_suffix":[0,12],"indic":[3,5],"individu":12,"industri":12,"inform":[0,4,6,7,12],"ingest":12,"ini":[2,12],"initi":0,"input":0,"input_":0,"insid":6,"instal":[2,5,12],"instanc":12,"instead":[0,3,6,8,12],"int":[0,12],"intend":[3,8],"interact":[2,4],"interakt":10,"interfer":[3,8],"intern":6,"interv":12,"interval_begin":10,"interval_end":10,"invalid":0,"invalidaggregatereport":0,"invaliddmarcreport":0,"invalidforensicreport":0,"invalidsmtptlsreport":0,"io":[0,12],"ip":[0,3,4,6,7,12],"ip_address":[0,10],"ip_db_path":[0,6,12],"ipaddressinfo":0,"ipdb":6,"ipv4":0,"ipv6":0,"is_mbox":0,"is_outlook_msg":0,"iso":0,"issu":[1,5],"java":2,"job":[3,6,8],"joe":[3,8],"journalctl":[2,12],"jre":2,"json":[0,5,12],"june":5,"just":7,"jvm":4,"kafka":[5,12],"kb4099855":6,"kb4134118":6,"kb4295699":6,"keep":0,"keep_al":0,"keepal":2,"kei":[0,3,4,6,12],"keyout":4,"keyr":4,"keystor":4,"kibana":[5,11],"kind":12,"know":3,"known":[3,7,8,12],"label":12,"languag":[3,8],"larg":2,"larger":12,"later":[4,6,12],"latest":[2,4,6,9],"layer":0,"layout":11,"leak":7,"least":[4,6,12],"leav":3,"left":7,"legal":[3,8],"legitim":[7,12],"level":[3,4],"libemail":6,"libxml2":6,"libxslt":6,"licens":6,"life":5,"like":[0,3,6,8,12],"limit":[0,2,12],"line":[3,8,12],"link":[3,4,7,8],"linux":[3,6,8],"list":[0,2,4,5,7,12],"listen":[2,12],"lite":6,"ll":[3,8],"load":4,"local":[0,2,4,10,12],"local_file_path":0,"local_reverse_dns_map_path":12,"localhost":12,"locat":[6,7,12],"log":[2,12],"log_analyt":12,"log_fil":12,"logger":12,"login":4,"logstash":4,"long":3,"longer":[3,8],"look":[3,7],"lookup":0,"loopback":2,"lot":7,"lua":10,"m":[0,6,10,12],"m365":12,"maco":6,"magnifi":7,"mai":[5,7,12],"maidir":12,"mail":[0,5,6,10,12],"mail_bcc":0,"mail_cc":0,"mail_from":0,"mail_to":0,"mailbox":[0,7,12],"mailbox_connect":0,"mailboxconnect":0,"maildir":12,"maildir_cr":12,"maildir_path":12,"mailer":10,"mailrelai":10,"mailto":6,"main":4,"maintain":5,"make":[0,3,4,8,9,12],"malici":[7,12],"manag":[4,12],"manual":12,"map":[0,12],"market":7,"match":[0,4,11,12],"max_ag":10,"max_shards_per_nod":12,"maximum":4,"maxmind":[0,6,12],"mbox":[0,12],"mechan":3,"member":[3,8],"mention":7,"menu":[4,7],"messag":[0,2,3,4,6,7,8,10,12],"message_id":10,"meta":10,"method":12,"mfrom":10,"microsoft":[2,5,10,12],"might":[0,3,7,8],"migrate_index":0,"mime":10,"minimum":4,"minut":[0,2,12],"mitig":[3,8],"mkdir":6,"mm":0,"mmdb":[0,12],"mobil":[3,8],"mode":[2,4,10,12],"modern":[2,3,8],"modifi":[3,8,12],"modul":[0,5,12],"mon":10,"monitor":[3,12],"monthli":[0,12],"monthly_index":[0,12],"more":[0,4,6,11,12],"most":[3,4,7,8,12],"mous":7,"move":[0,4,12],"msg":[0,6],"msg_byte":0,"msg_date":0,"msg_footer":[3,8],"msg_header":[3,8],"msgconvert":[0,6],"msgraph":12,"much":12,"multi":[2,5],"multipl":12,"mung":[3,8],"must":[2,3,8,12],"mutual":4,"mv":4,"mx":10,"my":12,"n":[10,12],"n_proc":12,"name":[0,3,4,7,10,11,12],"nameserv":[0,12],"nano":[2,12],"nation":12,"navig":[3,6,8],"ncontent":10,"ndate":10,"ndjson":4,"need":[2,3,4,6,7,8,12],"nelson":[3,8],"net":[2,12],"network":[2,4,12],"new":[0,2,3,6,7,12],"newer":6,"newest":[2,12],"newkei":4,"next":[0,12],"nfrom":10,"nmessag":10,"nmime":10,"node":4,"non":[3,8,12],"none":[0,3,10,12],"noproxyfor":2,"norepli":[3,10],"normal":[0,10,12],"normalize_timespan_threshold_hour":0,"normalized_timespan":10,"nosecureimap":2,"notabl":7,"now":[4,7],"nsubject":10,"nto":10,"null":10,"number":[0,12],"number_of_replica":[0,12],"number_of_shard":[0,12],"nwettbewerb":10,"nx":10,"o":[2,4,12],"oauth2":12,"oauth2_port":12,"object":[0,4],"observ":7,"occur":[0,7],"occurr":11,"oct":10,"offic":2,"office365":2,"offlin":[0,12],"often":7,"ol":[0,6],"old":7,"older":[6,10],"oldest":[2,12],"onc":6,"ondmarc":5,"one":[0,3,5,8,12],"onli":[2,3,6,7,8,12],"onlin":[0,2,12],"oor":0,"open":3,"opendn":12,"opensearch":[5,12],"opensearcherror":0,"openssl":4,"opt":[2,6,12],"option":[0,2,3,4,5,8,11,12],"order":6,"org":[0,6,9,10,12],"org_email":10,"org_extra_contact_info":10,"org_nam":10,"organ":[2,7,12],"organization_nam":10,"origin":[3,8,12],"original_envelope_id":10,"original_mail_from":10,"original_rcpt_to":10,"original_timespan_second":10,"other":[0,3,4,7,8],"our":7,"out":[3,4,7],"outdat":7,"outgo":[3,8,12],"outlook":[0,2,6],"output":[0,5,12],"output_directori":0,"outsid":12,"over":[2,5,7],"overrid":[0,12],"overridden":6,"overwrit":4,"owa":5,"own":[7,11],"p":[3,6,10],"p12":4,"pack":4,"packag":[0,4],"pad":0,"page":[3,4,6,7,8],"paginate_messag":12,"pan":10,"parallel":12,"paramet":0,"parent":7,"pars":[0,3,5,6,10,12],"parse_aggregate_report_fil":0,"parse_aggregate_report_xml":0,"parse_email":0,"parse_forensic_report":0,"parse_report_email":0,"parse_report_fil":0,"parse_smtp_tls_report_json":0,"parsed_aggregate_reports_to_csv":0,"parsed_aggregate_reports_to_csv_row":0,"parsed_forensic_reports_to_csv":0,"parsed_forensic_reports_to_csv_row":0,"parsed_sampl":10,"parsed_smtp_tls_reports_to_csv":0,"parsed_smtp_tls_reports_to_csv_row":0,"parsedmarc":[4,9,10,11],"parser":0,"parsererror":0,"parsingresult":0,"part":[3,4,7,8],"particular":7,"particularli":[5,12],"pass":[3,7,10],"passag":7,"passsword":12,"password":[0,4,6,12],"past":[4,11],"patch":6,"path":[0,4,12],"pattern":[5,7],"payload":[0,12],"pct":10,"per":12,"percentag":7,"perform":[2,12],"period":12,"perl":[0,6],"permiss":[4,12],"persist":12,"peter":10,"pie":7,"pin":5,"pip":6,"place":[4,7,12],"plain":0,"plaintext":[3,8],"platform":[3,8],"pleas":[1,5,12],"plu":7,"polici":[3,8,10,12],"policy_domain":10,"policy_evalu":10,"policy_override_com":10,"policy_override_reason":10,"policy_publish":10,"policy_str":10,"policy_typ":10,"policyscopegroupid":12,"poll":[2,12],"port":[0,2,12],"posit":12,"possibl":12,"post":[3,8,12],"poster":[3,8],"postoriu":[3,8],"powershel":12,"ppa":6,"pre":[6,12],"prefer":[2,6],"prefix":[0,3,8,12],"premad":[5,11],"prerequisit":5,"present":12,"pretti":12,"prettifi":12,"previou":[0,2,4,12],"previous":[4,7],"print":12,"printabl":10,"privaci":[3,6,7,8,12],"process":[0,2,5,6,12],"produc":10,"program":12,"programdata":6,"project":[0,2,3,5,11],"prompt":4,"proofpoint":5,"properti":2,"protect":[2,3,5,8,12],"provid":[4,7,12],"prox":6,"proxi":2,"proxyhost":2,"proxypassword":2,"proxyport":2,"proxyus":2,"pry":[2,12],"psl_overrid":0,"public":[0,3,10,12],"public_suffix_list":0,"publicbaseurl":4,"publicsuffix":0,"publish":3,"put":[4,12],"python":[0,6],"python3":6,"python39":6,"qo":4,"quarantin":[3,8],"queri":[0,12],"query_dn":0,"quickstart":12,"quot":10,"r":[2,6,10,12],"rais":0,"ram":4,"rather":[3,8],"read":[0,12],"readabl":0,"readwrit":12,"realli":3,"reason":[0,2,4,5,12],"receiv":[0,10,12],"receiving_ip":10,"receiving_mx_hostnam":10,"recipi":7,"recogn":7,"recommend":12,"record":[0,5,6,10],"record_typ":0,"refer":[4,5],"regard":12,"regardless":10,"region":12,"region_nam":12,"regist":6,"registr":12,"regul":[4,6,9,12],"regular":[3,8],"reject":[3,8],"relai":[3,8],"relat":[3,12],"releas":[4,6],"reli":7,"reliabl":12,"reload":[2,4,12],"remain":7,"remot":2,"remov":[0,3,4,8,12],"repeat":[3,8],"replac":[0,3,4,8],"repli":[2,3,8],"replica":[0,12],"reply_goes_to_list":[3,8],"reply_to":10,"replyto":[3,8],"report":[0,4,7,11,12],"report_id":10,"report_metadata":10,"report_typ":0,"reported_domain":10,"reports_fold":[0,12],"repositori":[6,11],"req":4,"request":[2,4,12],"requir":[0,2,3,4,6,8,12],"require_encrypt":0,"resid":12,"resolv":[0,12],"resourc":[0,4,5,12],"respons":[0,12],"restart":[2,3,4,8,12],"restartsec":[2,12],"restor":4,"restrict":12,"restrictaccess":12,"result":[0,5,7,10,12],"result_typ":10,"retain":[3,8],"retent":5,"retriev":2,"return":0,"revers":[0,7,12],"reverse_dn":[0,10],"reverse_dns_base_domain":0,"reverse_dns_map":0,"reverse_dns_map_path":0,"reverse_dns_map_url":[0,12],"reversednsservic":0,"review":[5,7],"rewrit":[3,8],"rfc":[0,3,8,10],"rfc2369":[3,8],"rfc822":2,"rhel":[4,5,6],"right":[4,7],"rm":4,"ro":0,"rollup":6,"root":[2,12],"rpm":4,"rsa":4,"rua":[5,6],"ruf":[5,6,7,12],"rule":[7,12],"run":[0,4,5,6],"rw":[2,12],"s3":12,"safe":0,"same":[3,4,6,7,11],"sampl":[0,5,12],"sample_headers_onli":10,"save":[0,4,6,12],"save_aggreg":12,"save_aggregate_report_to_elasticsearch":0,"save_aggregate_report_to_opensearch":0,"save_aggregate_reports_to_splunk":0,"save_forens":12,"save_forensic_report_to_elasticsearch":0,"save_forensic_report_to_opensearch":0,"save_forensic_reports_to_splunk":0,"save_output":0,"save_smtp_tl":12,"save_smtp_tls_report_to_elasticsearch":0,"save_smtp_tls_report_to_opensearch":0,"save_smtp_tls_reports_to_splunk":0,"schedul":6,"schema":10,"scope":[10,12],"scrub_nondigest":[3,8],"search":[0,3,8,12],"second":[0,2,12],"secret":12,"secret_access_kei":12,"section":12,"secur":[0,4,12],"see":[2,3,4,5,7,12],"segment":7,"select":6,"selector":10,"self":[4,5],"send":[0,2,3,4,5,7,8,11,12],"sender":[5,7,8],"sending_mta_ip":10,"sensit":12,"sent":[3,8,12],"separ":[3,4,6,7,9,11,12],"server":[0,2,3,4,6,7,10,12],"server_ip":4,"servernameon":10,"servic":[0,3,4,5,7,8],"session":7,"set":[0,2,3,4,6,7,8,9,12],"set_host":0,"setup":[4,9,12],"setuptool":6,"shard":[0,12],"share":[4,12],"sharepoint":10,"should":[3,6,7,8,12],"shouldn":[3,8],"show":[2,7,12],"side":7,"sign":[3,4,6],"signatur":[3,7,8],"silent":12,"similar":7,"simpl":5,"simplifi":0,"sinc":[0,12],"singl":[0,12],"sister":3,"size":[2,4],"skip":12,"skip_certificate_verif":12,"slightli":11,"small":4,"smtp":[0,3,5,7,12],"smtp_tl":[0,12],"smtp_tls_csv_filenam":[0,12],"smtp_tls_json_filenam":[0,12],"smtp_tls_report":0,"smtp_tls_url":12,"smtptlsparsedreport":0,"smtptlsreport":0,"so":[3,6,7,8,12],"socket":2,"solut":6,"some":[0,2,3,4,7,8],"someon":4,"sometim":12,"sort":[7,12],"sourc":[0,3,4,6,7,10],"source_base_domain":10,"source_countri":10,"source_ip_address":10,"source_nam":10,"source_reverse_dn":10,"source_typ":10,"sourceforg":2,"sp":[3,10],"spam":12,"special":12,"specif":[3,12],"specifi":[2,3],"spf":[7,10],"spf_align":10,"spf_domain":10,"spf_result":10,"spf_scope":10,"splunk":[5,12],"splunk_hec":12,"splunkerror":0,"splunkhec":12,"spoof":[3,8],"ss":0,"ssl":[0,2,4,12],"ssl_cert_path":0,"st":[10,12],"stabl":4,"stack":[4,12],"standard":[0,5,10],"start":[0,2,4,6,7,9,11,12],"starttl":12,"static":6,"statu":[2,12],"stdout":12,"step":[3,4,8],"still":[3,6,8,10,12],"storag":[0,12],"store":[2,4,9],"str":[0,12],"stream":12,"string":0,"strip":[3,8,12],"strip_attachment_payload":[0,12],"strongli":12,"structur":5,"stsv1":10,"subdomain":[0,3,12],"subject":[0,3,8,10,12],"subject_prefix":[3,8],"subsidiari":7,"successful_session_count":10,"sudo":[2,4,6,12],"suffix":[0,12],"suggest":7,"suitabl":0,"summari":[3,5,8],"suppli":[0,7,12],"support":[2,5,10,11],"sure":[4,6],"sw50zxjha3rpdmugv2v0dgjld2vyymvylcocymvyc2ljahq":10,"switch":7,"syslog":[2,12],"system":[2,3,4,6,8,12],"systemctl":[2,4,12],"systemd":5,"systemdr":6,"t":[5,8,12],"tab":[3,4,8],"tabl":[5,7],"tag":6,"target":[2,12],"task":6,"tby":10,"tcp":12,"tee":4,"tell":[3,6,7,8],"templat":[3,8],"temporari":7,"tenant":5,"tenant_id":12,"term":6,"test":[0,10,12],"text":[0,10],"than":[3,4,8,12],"thank":[5,10],"thei":[3,6,7,8,12],"theirs":3,"them":[0,4,7,12],"therebi":[3,8],"thi":[0,2,3,4,5,6,7,8,10,12],"those":6,"thousand":12,"three":7,"through":3,"time":[0,2,4,6,7,12],"timeout":[0,2,12],"timespan":0,"timespan_requires_norm":10,"timestamp":0,"timestamp_to_datetim":0,"timestamp_to_human":0,"timezon":10,"tl":[0,5,12],"tld":3,"to_domain":10,"to_utc":0,"token":[0,4,12],"token_fil":12,"tool":[6,12],"top":[3,7],"topic":12,"touch":[3,8],"tracker":1,"tradit":[3,8],"trail":12,"transfer":10,"transpar":5,"transport":[4,12],"trash":12,"true":[0,2,4,10,12],"trust":12,"truststor":4,"try":12,"tuesdai":6,"two":6,"txt":0,"type":[0,10,12],"u":[2,6,10,12],"ubuntu":[4,6],"udp":12,"ui":[3,8],"uncondition":[3,8],"under":[4,6,7],"underneath":7,"underscor":12,"understand":[5,7],"unencrypt":12,"unfortun":[3,8],"unit":[0,2,12],"unix":0,"unknown":0,"unsubscrib":[3,8],"until":[0,5,12],"unzip":2,"up":[0,2,4,6,7,9,12],"updat":[0,4,6,12],"upersecur":12,"upgrad":[2,5,6,12],"upload":12,"upper":7,"uri":6,"url":[0,2,12],"us":[0,3,4,5,8,10],"usag":12,"use_ssl":0,"user":[2,3,4,5,6,8,10,12],"user_ag":10,"useradd":[2,6],"usernam":[0,12],"usernamepassword":12,"usesystemproxi":2,"usr":4,"utc":0,"utf":10,"util":5,"v":[6,12],"valid":[0,7,10,12],"valimail":5,"valu":[0,3,4,7,8,12],"var":[3,8],"variou":6,"vendor":3,"venv":[6,12],"verbos":12,"veri":[4,7,12],"verif":[4,12],"verifi":0,"verification_mod":4,"version":[2,4,5,6,9,10,11,12],"vew":2,"via":2,"view":[7,12],"vim":4,"virtualenv":6,"visual":[4,9],"volum":7,"vulner":3,"w":[0,12],"w3c":10,"wa":[3,4,6,8],"wai":[4,7],"wait":[0,12],"want":[2,5,12],"wantedbi":[2,12],"warn":12,"watch":[0,2,4,12],"watch_inbox":0,"watcher":12,"web":[2,4],"webdav":2,"webhook":12,"webmail":[3,7,8],"week":[0,12],"weekli":6,"well":[2,12],"were":[7,12],"wettbewerb":10,"wget":4,"whalensolut":12,"what":5,"when":[0,3,5,7,8,12],"whenev":[0,2,12],"where":[0,2,3,8,12],"wherea":7,"wherev":12,"whether":0,"which":[2,4,5,7,12],"while":[7,12],"who":7,"why":[3,7],"wide":[6,10],"wiki":10,"window":6,"without":[3,4,7,8,12],"won":5,"work":[2,3,5,6,7,8],"workstat":2,"worst":3,"would":[3,5,6,8],"wrap":[3,8],"write":12,"www":[4,6,12],"x":[4,10],"x509":4,"xennn":10,"xml":[0,11],"xml_schema":10,"xms4g":4,"xmx4g":4,"xpack":4,"xxxx":4,"y":[4,6],"yahoo":7,"yaml":12,"ye":[3,8],"year":12,"yet":3,"yml":4,"you":[2,3,4,5,6,7,8,12],"your":[3,4,6,7,8,11,12],"yyyi":0,"zip":[0,2,5,12],"\u00fcbersicht":10},"titles":["API reference","Contributing to parsedmarc","Accessing an inbox using OWA/EWS","Understanding DMARC","Elasticsearch and Kibana","parsedmarc documentation - Open source DMARC report analyzer and visualizer","Installation","Using the Kibana dashboards","What about mailing lists?","OpenSearch and Grafana","Sample outputs","Splunk","Using parsedmarc"],"titleterms":{"2":[3,8],"3":[3,8],"about":[3,8],"access":2,"aggreg":10,"align":3,"an":2,"analyz":[5,6],"api":0,"best":[3,8],"bug":1,"cli":12,"compat":5,"configur":[2,12],"content":5,"contribut":1,"csv":10,"dashboard":7,"davmail":2,"depend":6,"dkim":3,"dmarc":[3,5,7],"do":[3,8],"document":5,"domain":3,"elast":0,"elasticsearch":4,"ew":2,"exchang":6,"featur":5,"file":12,"forens":[7,10],"geoipupd":6,"grafana":9,"guid":3,"help":12,"inbox":2,"index":4,"indic":0,"instal":[4,6,9],"json":10,"kibana":[4,7],"list":[3,8],"listserv":[3,8],"lookalik":3,"mail":[3,8],"mailman":[3,8],"microsoft":6,"multi":12,"multipl":6,"open":5,"opensearch":[0,9],"option":6,"output":10,"owa":2,"parsedmarc":[0,1,2,5,6,12],"pattern":4,"practic":[3,8],"prerequisit":6,"proxi":6,"python":5,"record":[3,4,9],"refer":0,"report":[1,5,6,10],"resourc":3,"retent":[4,9],"run":[2,12],"sampl":[7,10],"sender":3,"servic":[2,12],"setup":6,"smtp":10,"sourc":5,"spf":3,"splunk":[0,11],"summari":7,"support":[3,12],"systemd":[2,12],"t":3,"tabl":0,"tenant":12,"test":6,"tl":10,"understand":3,"upgrad":4,"us":[2,6,7,12],"util":0,"valid":3,"visual":5,"web":6,"what":[3,8],"won":3,"workaround":[3,8]}}) \ No newline at end of file diff --git a/splunk.html b/splunk.html index 096ee861..4f7170c8 100644 --- a/splunk.html +++ b/splunk.html @@ -6,14 +6,14 @@ - Splunk — parsedmarc 9.0.5 documentation + Splunk — parsedmarc 9.0.6 documentation - + diff --git a/usage.html b/usage.html index bd66cde7..7b4f2494 100644 --- a/usage.html +++ b/usage.html @@ -6,14 +6,14 @@ - Using parsedmarc — parsedmarc 9.0.5 documentation + Using parsedmarc — parsedmarc 9.0.6 documentation - +