diff --git a/_modules/index.html b/_modules/index.html index 3d20908..a59832e 100644 --- a/_modules/index.html +++ b/_modules/index.html @@ -1,23 +1,20 @@ + + - + - Overview: module code — parsedmarc 8.15.0 documentation - - + Overview: module code — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
diff --git a/_modules/parsedmarc.html b/_modules/parsedmarc.html index 9fe2d2b..d45bc84 100644 --- a/_modules/parsedmarc.html +++ b/_modules/parsedmarc.html @@ -1,23 +1,20 @@ + + - + - parsedmarc — parsedmarc 8.15.0 documentation - - + parsedmarc — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -121,7 +115,7 @@ from parsedmarc.utils import parse_email from parsedmarc.utils import timestamp_to_human, human_timestamp_to_datetime -__version__ = "8.15.0" +__version__ = "8.15.1" logger.debug("parsedmarc v{0}".format(__version__)) @@ -130,8 +124,8 @@ xml_schema_regex = re.compile(r"</??xs:schema.*>", re.MULTILINE) text_report_regex = re.compile(r"\s*([a-zA-Z\s]+):\s(.+)", re.MULTILINE) -MAGIC_ZIP = b"\x50\x4B\x03\x04" -MAGIC_GZIP = b"\x1F\x8B" +MAGIC_ZIP = b"\x50\x4b\x03\x04" +MAGIC_GZIP = b"\x1f\x8b" MAGIC_XML = b"\x3c\x3f\x78\x6d\x6c\x20" MAGIC_JSON = b"\7b" @@ -139,32 +133,51 @@ REVERSE_DNS_MAP = dict() -
[docs]class ParserError(RuntimeError): +
+[docs] +class ParserError(RuntimeError): """Raised whenever the parser fails for some reason"""
-
[docs]class InvalidDMARCReport(ParserError): + +
+[docs] +class InvalidDMARCReport(ParserError): """Raised when an invalid DMARC report is encountered"""
-
[docs]class InvalidSMTPTLSReport(ParserError): + +
+[docs] +class InvalidSMTPTLSReport(ParserError): """Raised when an invalid SMTP TLS report is encountered"""
-
[docs]class InvalidAggregateReport(InvalidDMARCReport): + +
+[docs] +class InvalidAggregateReport(InvalidDMARCReport): """Raised when an invalid DMARC aggregate report is encountered"""
-
[docs]class InvalidForensicReport(InvalidDMARCReport): + +
+[docs] +class InvalidForensicReport(InvalidDMARCReport): """Raised when an invalid DMARC forensic report is encountered"""
-def _parse_report_record(record, ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, - nameservers=None, dns_timeout=2.0): + +def _parse_report_record( + record, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + nameservers=None, + dns_timeout=2.0, +): """ Converts a record from a DMARC aggregate report into a more consistent format @@ -197,15 +210,19 @@ reverse_dns_map=REVERSE_DNS_MAP, offline=offline, nameservers=nameservers, - timeout=dns_timeout) + timeout=dns_timeout, + ) 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 = OrderedDict( + [ + ("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": @@ -215,10 +232,14 @@ if "spf" in policy_evaluated: new_policy_evaluated["spf"] = policy_evaluated["spf"] reasons = [] - spf_aligned = policy_evaluated["spf"] is not None and policy_evaluated[ - "spf"].lower() == "pass" - dkim_aligned = policy_evaluated["dkim"] is not None and policy_evaluated[ - "dkim"].lower() == "pass" + spf_aligned = ( + policy_evaluated["spf"] is not None + and policy_evaluated["spf"].lower() == "pass" + ) + dkim_aligned = ( + policy_evaluated["dkim"] is not None + and policy_evaluated["dkim"].lower() == "pass" + ) dmarc_aligned = spf_aligned or dkim_aligned new_record["alignment"] = dict() new_record["alignment"]["spf"] = spf_aligned @@ -242,7 +263,7 @@ if type(new_record["identifiers"]["header_from"]) is str: lowered_from = new_record["identifiers"]["header_from"].lower() else: - lowered_from = '' + lowered_from = "" new_record["identifiers"]["header_from"] = lowered_from if record["auth_results"] is not None: auth_results = record["auth_results"].copy() @@ -318,29 +339,30 @@ ) if "sending-mta-ip" in failure_details: - new_failure_details["sending_mta_ip"] = failure_details[ - "sending-mta-ip"] + new_failure_details["sending_mta_ip"] = failure_details["sending-mta-ip"] if "receiving-ip" in failure_details: - new_failure_details["receiving_ip"] = failure_details[ - "receiving-ip"] + new_failure_details["receiving_ip"] = failure_details["receiving-ip"] if "receiving-mx-hostname" in failure_details: new_failure_details["receiving_mx_hostname"] = failure_details[ - "receiving-mx-hostname"] + "receiving-mx-hostname" + ] if "receiving-mx-helo" in failure_details: new_failure_details["receiving_mx_helo"] = failure_details[ - "receiving-mx-helo"] + "receiving-mx-helo" + ] if "additional-info-uri" in failure_details: new_failure_details["additional_info_uri"] = failure_details[ - "additional-info-uri"] + "additional-info-uri" + ] if "failure-reason-code" in failure_details: new_failure_details["failure_reason_code"] = failure_details[ - "failure-reason-code"] + "failure-reason-code" + ] return new_failure_details except KeyError as e: - raise InvalidSMTPTLSReport(f"Missing required failure details field:" - f" {e}") + raise InvalidSMTPTLSReport(f"Missing required failure details field:" f" {e}") except Exception as e: raise InvalidSMTPTLSReport(str(e)) @@ -352,29 +374,26 @@ policy_type = policy["policy"]["policy-type"] failure_details = [] if policy_type not in policy_types: - raise InvalidSMTPTLSReport(f"Invalid policy type " - f"{policy_type}") - new_policy = OrderedDict(policy_domain=policy_domain, - policy_type=policy_type) + raise InvalidSMTPTLSReport(f"Invalid policy type " f"{policy_type}") + new_policy = OrderedDict(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: - new_policy["policy_strings"] = policy["policy"][ - "policy-string"] + new_policy["policy_strings"] = policy["policy"]["policy-string"] if "mx-host-pattern" in policy["policy"]: if isinstance(policy["policy"]["mx-host-pattern"], list): if len(policy["policy"]["mx-host-pattern"]) > 0: - new_policy["mx_host_patterns"] = policy["policy"][ - "mx-host-pattern"] + new_policy["mx_host_patterns"] = policy["policy"]["mx-host-pattern"] new_policy["successful_session_count"] = policy["summary"][ - "total-successful-session-count"] + "total-successful-session-count" + ] new_policy["failed_session_count"] = policy["summary"][ - "total-failure-session-count"] + "total-failure-session-count" + ] if "failure-details" in policy: for details in policy["failure-details"]: - failure_details.append(_parse_smtp_tls_failure_details( - details)) + failure_details.append(_parse_smtp_tls_failure_details(details)) new_policy["failure_details"] = failure_details return new_policy @@ -385,11 +404,17 @@ raise InvalidSMTPTLSReport(str(e)) -
[docs]def parse_smtp_tls_report_json(report): +
+[docs] +def parse_smtp_tls_report_json(report): """Parses and validates an SMTP TLS report""" - required_fields = ["organization-name", "date-range", - "contact-info", "report-id", - "policies"] + required_fields = [ + "organization-name", + "date-range", + "contact-info", + "report-id", + "policies", + ] try: policies = [] @@ -399,8 +424,9 @@ raise Exception(f"Missing required field: {required_field}]") if not isinstance(report["policies"], list): policies_type = type(report["policies"]) - raise InvalidSMTPTLSReport(f"policies must be a list, " - f"not {policies_type}") + raise InvalidSMTPTLSReport( + f"policies must be a list, " f"not {policies_type}" + ) for policy in report["policies"]: policies.append(_parse_smtp_tls_report_policy(policy)) @@ -410,7 +436,7 @@ end_date=report["date-range"]["end-datetime"], contact_info=report["contact-info"], report_id=report["report-id"], - policies=policies + policies=policies, ) return new_report @@ -421,7 +447,10 @@ raise InvalidSMTPTLSReport(str(e))
-
[docs]def parsed_smtp_tls_reports_to_csv_rows(reports): + +
+[docs] +def parsed_smtp_tls_reports_to_csv_rows(reports): """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: @@ -433,18 +462,18 @@ organization_name=report["organization_name"], begin_date=report["begin_date"], end_date=report["end_date"], - report_id=report["report_id"] + report_id=report["report_id"], ) record = common_fields.copy() for policy in report["policies"]: if "policy_strings" in policy: record["policy_strings"] = "|".join(policy["policy_strings"]) if "mx_host_patterns" in policy: - record["mx_host_patterns"] = "|".join( - policy["mx_host_patterns"]) + record["mx_host_patterns"] = "|".join(policy["mx_host_patterns"]) successful_record = record.copy() successful_record["successful_session_count"] = policy[ - "successful_session_count"] + "successful_session_count" + ] rows.append(successful_record) if "failure_details" in policy: for failure_details in policy["failure_details"]: @@ -456,7 +485,10 @@ return rows
-
[docs]def parsed_smtp_tls_reports_to_csv(reports): + +
+[docs] +def parsed_smtp_tls_reports_to_csv(reports): """ Converts one or more parsed SMTP TLS reports to flat CSV format, including headers @@ -468,12 +500,25 @@ str: Parsed aggregate report data in flat CSV format, including headers """ - fields = ["organization_name", "begin_date", "end_date", "report_id", - "result_type", "successful_session_count", - "failed_session_count", "policy_domain", "policy_type", - "policy_strings", "mx_host_patterns", "sending_mta_ip", - "receiving_ip", "receiving_mx_hostname", "receiving_mx_helo", - "additional_info_uri", "failure_reason_code"] + fields = [ + "organization_name", + "begin_date", + "end_date", + "report_id", + "result_type", + "successful_session_count", + "failed_session_count", + "policy_domain", + "policy_type", + "policy_strings", + "mx_host_patterns", + "sending_mta_ip", + "receiving_ip", + "receiving_mx_hostname", + "receiving_mx_helo", + "additional_info_uri", + "failure_reason_code", + ] csv_file_object = StringIO(newline="\n") writer = DictWriter(csv_file_object, fields) @@ -488,16 +533,20 @@ return csv_file_object.getvalue()
-
[docs]def parse_aggregate_report_xml( - xml, - ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, - nameservers=None, - timeout=2.0, - keep_alive=None): + +
+[docs] +def parse_aggregate_report_xml( + xml, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + nameservers=None, + timeout=2.0, + keep_alive=None, +): """Parses a DMARC XML report string and returns a consistent OrderedDict Args: @@ -518,26 +567,27 @@ errors = [] # Parse XML and recover from errors if isinstance(xml, bytes): - xml = xml.decode(errors='ignore') + xml = xml.decode(errors="ignore") try: xmltodict.parse(xml)["feedback"] except Exception as e: errors.append("Invalid XML: {0}".format(e.__str__())) try: tree = etree.parse( - BytesIO(xml.encode('utf-8')), - etree.XMLParser(recover=True, resolve_entities=False)) + BytesIO(xml.encode("utf-8")), + etree.XMLParser(recover=True, resolve_entities=False), + ) s = etree.tostring(tree) - xml = '' if s is None else s.decode('utf-8') + xml = "" if s is None else s.decode("utf-8") except Exception: - xml = u'<a/>' + xml = "<a/>" try: # Replace XML header (sometimes they are invalid) - xml = xml_header_regex.sub("<?xml version=\"1.0\"?>", xml) + xml = xml_header_regex.sub('<?xml version="1.0"?>', xml) # Remove invalid schema tags - xml = xml_schema_regex.sub('', xml) + xml = xml_schema_regex.sub("", xml) report = xmltodict.parse(xml)["feedback"] report_metadata = report["report_metadata"] @@ -548,20 +598,21 @@ new_report_metadata = OrderedDict() if report_metadata["org_name"] is None: if report_metadata["email"] is not None: - report_metadata["org_name"] = report_metadata[ - "email"].split("@")[-1] + report_metadata["org_name"] = report_metadata["email"].split("@")[-1] org_name = report_metadata["org_name"] if org_name is not None and " " not in org_name: new_org_name = get_base_domain(org_name) if new_org_name is not None: org_name = new_org_name if not org_name: - logger.debug("Could not parse org_name from XML.\r\n{0}".format( - report.__str__() - )) - raise KeyError("Organization name is missing. \ + logger.debug( + "Could not parse org_name from XML.\r\n{0}".format(report.__str__()) + ) + raise KeyError( + "Organization name is missing. \ This field is a requirement for \ - saving the report") + saving the report" + ) new_report_metadata["org_name"] = org_name new_report_metadata["org_email"] = report_metadata["email"] extra = None @@ -570,11 +621,10 @@ new_report_metadata["org_extra_contact_info"] = extra new_report_metadata["report_id"] = report_metadata["report_id"] report_id = new_report_metadata["report_id"] - report_id = report_id.replace("<", - "").replace(">", "").split("@")[0] + report_id = report_id.replace("<", "").replace(">", "").split("@")[0] new_report_metadata["report_id"] = report_id date_range = report["report_metadata"]["date_range"] - if int(date_range["end"]) - int(date_range["begin"]) > 2*86400: + if int(date_range["end"]) - int(date_range["begin"]) > 2 * 86400: _error = "Time span > 24 hours - RFC 7489 section 7.2" errors.append(_error) date_range["begin"] = timestamp_to_human(date_range["begin"]) @@ -590,6 +640,8 @@ new_report["report_metadata"] = new_report_metadata records = [] policy_published = report["policy_published"] + if type(policy_published) is list: + policy_published = policy_published[0] new_policy_published = OrderedDict() new_policy_published["domain"] = policy_published["domain"] adkim = "r" @@ -606,17 +658,17 @@ sp = new_policy_published["p"] if "sp" in policy_published: if policy_published["sp"] is not None: - sp = report["policy_published"]["sp"] + sp = policy_published["sp"] new_policy_published["sp"] = sp pct = "100" if "pct" in policy_published: if policy_published["pct"] is not None: - pct = report["policy_published"]["pct"] + pct = policy_published["pct"] new_policy_published["pct"] = pct fo = "0" if "fo" in policy_published: if policy_published["fo"] is not None: - fo = report["policy_published"]["fo"] + fo = policy_published["fo"] new_policy_published["fo"] = fo new_report["policy_published"] = new_policy_published @@ -625,8 +677,7 @@ if keep_alive is not None and i > 0 and i % 20 == 0: logger.debug("Sending keepalive cmd") keep_alive() - logger.debug("Processed {0}/{1}".format( - i, len(report["record"]))) + logger.debug("Processed {0}/{1}".format(i, len(report["record"]))) try: report_record = _parse_report_record( report["record"][i], @@ -636,7 +687,8 @@ reverse_dns_map_path=reverse_dns_map_path, reverse_dns_map_url=reverse_dns_map_url, nameservers=nameservers, - dns_timeout=timeout) + dns_timeout=timeout, + ) records.append(report_record) except Exception as e: logger.warning("Could not parse record: {0}".format(e)) @@ -650,7 +702,8 @@ reverse_dns_map_url=reverse_dns_map_url, offline=offline, nameservers=nameservers, - dns_timeout=timeout) + dns_timeout=timeout, + ) records.append(report_record) new_report["records"] = records @@ -658,21 +711,21 @@ return new_report except expat.ExpatError as error: - raise InvalidAggregateReport( - "Invalid XML: {0}".format(error.__str__())) + raise InvalidAggregateReport("Invalid XML: {0}".format(error.__str__())) except KeyError as error: - raise InvalidAggregateReport( - "Missing field: {0}".format(error.__str__())) + raise InvalidAggregateReport("Missing field: {0}".format(error.__str__())) except AttributeError: raise InvalidAggregateReport("Report missing required section") except Exception as error: - raise InvalidAggregateReport( - "Unexpected error: {0}".format(error.__str__()))
+ raise InvalidAggregateReport("Unexpected error: {0}".format(error.__str__()))
-
[docs]def extract_report(content): + +
+[docs] +def extract_report(content): """ Extracts text from a zip or gzip file, as a base64-encoded string, file-like object, or bytes. @@ -703,14 +756,13 @@ file_object.seek(0) if header.startswith(MAGIC_ZIP): _zip = zipfile.ZipFile(file_object) - report = _zip.open(_zip.namelist()[0]).read().decode( - errors='ignore') + report = _zip.open(_zip.namelist()[0]).read().decode(errors="ignore") elif header.startswith(MAGIC_GZIP): - report = zlib.decompress( - file_object.read(), - zlib.MAX_WBITS | 16).decode(errors='ignore') + report = zlib.decompress(file_object.read(), zlib.MAX_WBITS | 16).decode( + errors="ignore" + ) elif header.startswith(MAGIC_XML) or header.startswith(MAGIC_JSON): - report = file_object.read().decode(errors='ignore') + report = file_object.read().decode(errors="ignore") else: file_object.close() raise ParserError("Not a valid zip, gzip, json, or xml file") @@ -722,13 +774,15 @@ raise ParserError("File objects must be opened in binary (rb) mode") except Exception as error: file_object.close() - raise ParserError( - "Invalid archive file: {0}".format(error.__str__())) + raise ParserError("Invalid archive file: {0}".format(error.__str__())) return report
-
[docs]def extract_report_from_file_path(file_path): + +
+[docs] +def extract_report_from_file_path(file_path): """Extracts report from a file at the given file_path""" try: with open(file_path, "rb") as report_file: @@ -737,16 +791,20 @@ raise ParserError("File was not found")
-
[docs]def parse_aggregate_report_file( - _input, - offline=False, - always_use_local_files=None, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - ip_db_path=None, - nameservers=None, - dns_timeout=2.0, - keep_alive=None): + +
+[docs] +def parse_aggregate_report_file( + _input, + offline=False, + always_use_local_files=None, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + ip_db_path=None, + nameservers=None, + dns_timeout=2.0, + keep_alive=None, +): """Parses a file at the given path, a file-like object. or bytes as an aggregate DMARC report @@ -780,10 +838,14 @@ offline=offline, nameservers=nameservers, timeout=dns_timeout, - keep_alive=keep_alive)
+ keep_alive=keep_alive, + )
-
[docs]def parsed_aggregate_reports_to_csv_rows(reports): + +
+[docs] +def parsed_aggregate_reports_to_csv_rows(reports): """ Converts one or more parsed aggregate reports to list of dicts in flat CSV format @@ -821,12 +883,23 @@ pct = report["policy_published"]["pct"] fo = report["policy_published"]["fo"] - report_dict = dict(xml_schema=xml_schema, org_name=org_name, - org_email=org_email, - org_extra_contact_info=org_extra_contact, - report_id=report_id, begin_date=begin_date, - end_date=end_date, errors=errors, domain=domain, - adkim=adkim, aspf=aspf, p=p, sp=sp, pct=pct, fo=fo) + report_dict = dict( + xml_schema=xml_schema, + org_name=org_name, + org_email=org_email, + org_extra_contact_info=org_extra_contact, + report_id=report_id, + begin_date=begin_date, + end_date=end_date, + errors=errors, + domain=domain, + adkim=adkim, + aspf=aspf, + p=p, + sp=sp, + pct=pct, + fo=fo, + ) for record in report["records"]: row = report_dict.copy() @@ -841,18 +914,20 @@ row["dkim_aligned"] = record["alignment"]["dkim"] row["dmarc_aligned"] = record["alignment"]["dmarc"] row["disposition"] = record["policy_evaluated"]["disposition"] - policy_override_reasons = list(map( - lambda r_: r_["type"] or "none", - record["policy_evaluated"] - ["policy_override_reasons"])) - policy_override_comments = list(map( - lambda r_: r_["comment"] or "none", - record["policy_evaluated"] - ["policy_override_reasons"])) - row["policy_override_reasons"] = ",".join( - policy_override_reasons) - row["policy_override_comments"] = "|".join( - policy_override_comments) + policy_override_reasons = list( + map( + lambda r_: r_["type"] or "none", + record["policy_evaluated"]["policy_override_reasons"], + ) + ) + policy_override_comments = list( + map( + lambda r_: r_["comment"] or "none", + record["policy_evaluated"]["policy_override_reasons"], + ) + ) + row["policy_override_reasons"] = ",".join(policy_override_reasons) + row["policy_override_comments"] = "|".join(policy_override_comments) row["envelope_from"] = record["identifiers"]["envelope_from"] row["header_from"] = record["identifiers"]["header_from"] envelope_to = record["identifiers"]["envelope_to"] @@ -883,12 +958,15 @@ for r in rows: for k, v in r.items(): if type(v) not in [str, int, bool]: - r[k] = '' + r[k] = "" return rows
-
[docs]def parsed_aggregate_reports_to_csv(reports): + +
+[docs] +def parsed_aggregate_reports_to_csv(reports): """ Converts one or more parsed aggregate reports to flat CSV format, including headers @@ -900,16 +978,45 @@ str: Parsed aggregate report data in flat CSV format, including headers """ - fields = ["xml_schema", "org_name", "org_email", - "org_extra_contact_info", "report_id", "begin_date", "end_date", - "errors", "domain", "adkim", "aspf", "p", "sp", "pct", "fo", - "source_ip_address", "source_country", "source_reverse_dns", - "source_base_domain", "source_name", "source_type", "count", - "spf_aligned", "dkim_aligned", "dmarc_aligned", "disposition", - "policy_override_reasons", "policy_override_comments", - "envelope_from", "header_from", - "envelope_to", "dkim_domains", "dkim_selectors", "dkim_results", - "spf_domains", "spf_scopes", "spf_results"] + fields = [ + "xml_schema", + "org_name", + "org_email", + "org_extra_contact_info", + "report_id", + "begin_date", + "end_date", + "errors", + "domain", + "adkim", + "aspf", + "p", + "sp", + "pct", + "fo", + "source_ip_address", + "source_country", + "source_reverse_dns", + "source_base_domain", + "source_name", + "source_type", + "count", + "spf_aligned", + "dkim_aligned", + "dmarc_aligned", + "disposition", + "policy_override_reasons", + "policy_override_comments", + "envelope_from", + "header_from", + "envelope_to", + "dkim_domains", + "dkim_selectors", + "dkim_results", + "spf_domains", + "spf_scopes", + "spf_results", + ] csv_file_object = StringIO(newline="\n") writer = DictWriter(csv_file_object, fields) @@ -924,17 +1031,22 @@ return csv_file_object.getvalue()
-
[docs]def parse_forensic_report(feedback_report, - sample, - msg_date, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, - ip_db_path=None, - nameservers=None, - dns_timeout=2.0, - strip_attachment_payloads=False): + +
+[docs] +def parse_forensic_report( + feedback_report, + sample, + msg_date, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + ip_db_path=None, + nameservers=None, + dns_timeout=2.0, + strip_attachment_payloads=False, +): """ Converts a DMARC forensic report and sample to a ``OrderedDict`` @@ -967,8 +1079,7 @@ if "arrival_date" not in parsed_report: if msg_date is None: - raise InvalidForensicReport( - "Forensic sample is not a valid email") + raise InvalidForensicReport("Forensic sample is not a valid email") parsed_report["arrival_date"] = msg_date.isoformat() if "version" not in parsed_report: @@ -988,11 +1099,12 @@ parsed_report["delivery_result"] = "other" arrival_utc = human_timestamp_to_datetime( - parsed_report["arrival_date"], to_utc=True) + parsed_report["arrival_date"], to_utc=True + ) arrival_utc = arrival_utc.strftime("%Y-%m-%d %H:%M:%S") parsed_report["arrival_date_utc"] = arrival_utc - ip_address = re.split(r'\s', parsed_report["source_ip"]).pop(0) + ip_address = re.split(r"\s", parsed_report["source_ip"]).pop(0) parsed_report_source = get_ip_address_info( ip_address, cache=IP_ADDRESS_CACHE, @@ -1003,7 +1115,8 @@ reverse_dns_map=REVERSE_DNS_MAP, offline=offline, nameservers=nameservers, - timeout=dns_timeout) + timeout=dns_timeout, + ) parsed_report["source"] = parsed_report_source del parsed_report["source_ip"] @@ -1023,15 +1136,19 @@ auth_failure = parsed_report["auth_failure"].split(",") parsed_report["auth_failure"] = auth_failure - optional_fields = ["original_envelope_id", "dkim_domain", - "original_mail_from", "original_rcpt_to"] + optional_fields = [ + "original_envelope_id", + "dkim_domain", + "original_mail_from", + "original_rcpt_to", + ] for optional_field in optional_fields: if optional_field not in parsed_report: parsed_report[optional_field] = None parsed_sample = parse_email( - sample, - strip_attachment_payloads=strip_attachment_payloads) + sample, strip_attachment_payloads=strip_attachment_payloads + ) if "reported_domain" not in parsed_report: parsed_report["reported_domain"] = parsed_sample["from"]["domain"] @@ -1051,15 +1168,16 @@ return parsed_report except KeyError as error: - raise InvalidForensicReport("Missing value: {0}".format( - error.__str__())) + raise InvalidForensicReport("Missing value: {0}".format(error.__str__())) except Exception as error: - raise InvalidForensicReport( - "Unexpected error: {0}".format(error.__str__()))
+ raise InvalidForensicReport("Unexpected error: {0}".format(error.__str__()))
-
[docs]def parsed_forensic_reports_to_csv_rows(reports): + +
+[docs] +def parsed_forensic_reports_to_csv_rows(reports): """ Converts one or more parsed forensic reports to a list of dicts in flat CSV format @@ -1087,8 +1205,7 @@ row["subject"] = report["parsed_sample"]["subject"] row["auth_failure"] = ",".join(report["auth_failure"]) authentication_mechanisms = report["authentication_mechanisms"] - row["authentication_mechanisms"] = ",".join( - authentication_mechanisms) + row["authentication_mechanisms"] = ",".join(authentication_mechanisms) del row["sample"] del row["parsed_sample"] rows.append(row) @@ -1096,7 +1213,10 @@ return rows
-
[docs]def parsed_forensic_reports_to_csv(reports): + +
+[docs] +def parsed_forensic_reports_to_csv(reports): """ Converts one or more parsed forensic reports to flat CSV format, including headers @@ -1107,14 +1227,31 @@ Returns: str: Parsed forensic report data in flat CSV format, including headers """ - fields = ["feedback_type", "user_agent", "version", "original_envelope_id", - "original_mail_from", "original_rcpt_to", "arrival_date", - "arrival_date_utc", "subject", "message_id", - "authentication_results", "dkim_domain", "source_ip_address", - "source_country", "source_reverse_dns", - "source_base_domain", "source_name", "source_type", - "delivery_result", "auth_failure", "reported_domain", - "authentication_mechanisms", "sample_headers_only"] + fields = [ + "feedback_type", + "user_agent", + "version", + "original_envelope_id", + "original_mail_from", + "original_rcpt_to", + "arrival_date", + "arrival_date_utc", + "subject", + "message_id", + "authentication_results", + "dkim_domain", + "source_ip_address", + "source_country", + "source_reverse_dns", + "source_base_domain", + "source_name", + "source_type", + "delivery_result", + "auth_failure", + "reported_domain", + "authentication_mechanisms", + "sample_headers_only", + ] csv_file = StringIO() csv_writer = DictWriter(csv_file, fieldnames=fields) @@ -1131,16 +1268,21 @@ return csv_file.getvalue()
-
[docs]def parse_report_email( - input_, - offline=False, - ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - nameservers=None, dns_timeout=2.0, - strip_attachment_payloads=False, - keep_alive=None): + +
+[docs] +def parse_report_email( + input_, + offline=False, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + nameservers=None, + dns_timeout=2.0, + strip_attachment_payloads=False, + keep_alive=None, +): """ Parses a DMARC report from an email @@ -1173,8 +1315,7 @@ msg_headers = json.loads(msg.headers_json) date = email.utils.format_datetime(datetime.utcnow()) if "Date" in msg_headers: - date = human_timestamp_to_datetime( - msg_headers["Date"]) + date = human_timestamp_to_datetime(msg_headers["Date"]) msg = email.message_from_string(input_) except Exception as e: @@ -1184,8 +1325,7 @@ smtp_tls_report = None sample = None if "From" in msg_headers: - logger.info("Parsing mail from {0} on {1}".format(msg_headers["From"], - date)) + logger.info("Parsing mail from {0} on {1}".format(msg_headers["From"], date)) if "Subject" in msg_headers: subject = msg_headers["Subject"] for part in msg.walk(): @@ -1200,8 +1340,7 @@ feedback_report = payload else: feedback_report = b64decode(payload).__str__() - feedback_report = feedback_report.lstrip( - "b'").rstrip("'") + feedback_report = feedback_report.lstrip("b'").rstrip("'") feedback_report = feedback_report.replace("\\r", "") feedback_report = feedback_report.replace("\\n", "\n") except (ValueError, TypeError, binascii.Error): @@ -1215,13 +1354,15 @@ if "{" not in payload: payload = str(b64decode(payload)) smtp_tls_report = parse_smtp_tls_report_json(payload) - return OrderedDict([("report_type", "smtp_tls"), - ("report", smtp_tls_report)]) + return OrderedDict( + [("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 OrderedDict( + [("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: @@ -1233,13 +1374,13 @@ field_name = match[0].lower().replace(" ", "-") fields[field_name] = match[1].strip() - feedback_report = "Arrival-Date: {}\n" \ - "Source-IP: {}" \ - "".format(fields["received-date"], - fields["sender-ip-address"]) + feedback_report = "Arrival-Date: {}\n" "Source-IP: {}" "".format( + fields["received-date"], fields["sender-ip-address"] + ) except Exception as e: - error = 'Unable to parse message with ' \ - 'subject "{0}": {1}'.format(subject, e) + error = "Unable to parse message with " 'subject "{0}": {1}'.format( + subject, e + ) raise InvalidDMARCReport(error) sample = parts[1].lstrip() @@ -1247,14 +1388,14 @@ else: try: payload = b64decode(payload) - if payload.startswith(MAGIC_ZIP) or \ - payload.startswith(MAGIC_GZIP): + if payload.startswith(MAGIC_ZIP) or payload.startswith(MAGIC_GZIP): payload = extract_report(payload) ns = nameservers if payload.startswith("{"): smtp_tls_report = parse_smtp_tls_report_json(payload) - result = OrderedDict([("report_type", "smtp_tls"), - ("report", smtp_tls_report)]) + result = OrderedDict( + [("report_type", "smtp_tls"), ("report", smtp_tls_report)] + ) return result aggregate_report = parse_aggregate_report_xml( payload, @@ -1265,23 +1406,28 @@ offline=offline, nameservers=ns, timeout=dns_timeout, - keep_alive=keep_alive) - result = OrderedDict([("report_type", "aggregate"), - ("report", aggregate_report)]) + keep_alive=keep_alive, + ) + result = OrderedDict( + [("report_type", "aggregate"), ("report", aggregate_report)] + ) return result except (TypeError, ValueError, binascii.Error): pass except InvalidAggregateReport as e: - error = 'Message with subject "{0}" ' \ - 'is not a valid ' \ - 'aggregate DMARC report: {1}'.format(subject, e) + error = ( + 'Message with subject "{0}" ' + "is not a valid " + "aggregate DMARC report: {1}".format(subject, e) + ) raise ParserError(error) except Exception as e: - error = 'Unable to parse message with ' \ - 'subject "{0}": {1}'.format(subject, e) + error = "Unable to parse message with " 'subject "{0}": {1}'.format( + subject, e + ) raise ParserError(error) if feedback_report and sample: @@ -1297,31 +1443,41 @@ reverse_dns_map_url=reverse_dns_map_url, nameservers=nameservers, dns_timeout=dns_timeout, - strip_attachment_payloads=strip_attachment_payloads) + strip_attachment_payloads=strip_attachment_payloads, + ) except InvalidForensicReport as e: - error = 'Message with subject "{0}" ' \ - 'is not a valid ' \ - 'forensic DMARC report: {1}'.format(subject, e) + error = ( + 'Message with subject "{0}" ' + "is not a valid " + "forensic DMARC report: {1}".format(subject, e) + ) raise InvalidForensicReport(error) except Exception as e: raise InvalidForensicReport(e.__str__()) - result = OrderedDict([("report_type", "forensic"), - ("report", forensic_report)]) + result = OrderedDict([("report_type", "forensic"), ("report", forensic_report)]) return result if result is None: - error = 'Message with subject "{0}" is ' \ - 'not a valid report'.format(subject) + error = 'Message with subject "{0}" is ' "not a valid report".format(subject) raise InvalidDMARCReport(error)
-
[docs]def parse_report_file(input_, nameservers=None, dns_timeout=2.0, - strip_attachment_payloads=False, ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, keep_alive=None): + +
+[docs] +def parse_report_file( + input_, + nameservers=None, + dns_timeout=2.0, + strip_attachment_payloads=False, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + keep_alive=None, +): """Parses a DMARC aggregate or forensic file at the given path, a file-like object. or bytes @@ -1362,14 +1518,13 @@ offline=offline, nameservers=nameservers, dns_timeout=dns_timeout, - keep_alive=keep_alive) - results = OrderedDict([("report_type", "aggregate"), - ("report", report)]) + keep_alive=keep_alive, + ) + results = OrderedDict([("report_type", "aggregate"), ("report", report)]) except InvalidAggregateReport: try: report = parse_smtp_tls_report_json(content) - results = OrderedDict([("report_type", "smtp_tls"), - ("report", report)]) + results = OrderedDict([("report_type", "smtp_tls"), ("report", report)]) except InvalidSMTPTLSReport: try: sa = strip_attachment_payloads @@ -1383,19 +1538,27 @@ nameservers=nameservers, dns_timeout=dns_timeout, strip_attachment_payloads=sa, - keep_alive=keep_alive) + keep_alive=keep_alive, + ) except InvalidDMARCReport: raise ParserError("Not a valid report") return results
-
[docs]def get_dmarc_reports_from_mbox(input_, nameservers=None, dns_timeout=2.0, - strip_attachment_payloads=False, - ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False): + +
+[docs] +def get_dmarc_reports_from_mbox( + input_, + nameservers=None, + dns_timeout=2.0, + strip_attachment_payloads=False, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, +): """Parses a mailbox in mbox format containing e-mails with attached DMARC reports @@ -1423,13 +1586,10 @@ mbox = mailbox.mbox(input_) message_keys = mbox.keys() total_messages = len(message_keys) - logger.debug("Found {0} messages in {1}".format(total_messages, - input_)) + logger.debug("Found {0} messages in {1}".format(total_messages, input_)) for i in range(len(message_keys)): message_key = message_keys[i] - logger.info("Processing message {0} of {1}".format( - i+1, total_messages - )) + logger.info("Processing message {0} of {1}".format(i + 1, total_messages)) msg_content = mbox.get_string(message_key) try: sa = strip_attachment_payloads @@ -1442,7 +1602,8 @@ offline=offline, nameservers=nameservers, dns_timeout=dns_timeout, - strip_attachment_payloads=sa) + strip_attachment_payloads=sa, + ) if parsed_email["report_type"] == "aggregate": aggregate_reports.append(parsed_email["report"]) elif parsed_email["report_type"] == "forensic": @@ -1453,27 +1614,36 @@ 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 OrderedDict( + [ + ("aggregate_reports", aggregate_reports), + ("forensic_reports", forensic_reports), + ("smtp_tls_reports", smtp_tls_reports), + ] + )
-
[docs]def get_dmarc_reports_from_mailbox(connection: MailboxConnection, - reports_folder="INBOX", - archive_folder="Archive", - delete=False, - test=False, - ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, - nameservers=None, - dns_timeout=6.0, - strip_attachment_payloads=False, - results=None, - batch_size=10, - create_folders=True): + +
+[docs] +def get_dmarc_reports_from_mailbox( + connection: MailboxConnection, + reports_folder="INBOX", + archive_folder="Archive", + delete=False, + test=False, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + nameservers=None, + dns_timeout=6.0, + strip_attachment_payloads=False, + results=None, + batch_size=10, + create_folders=True, +): """ Fetches and parses DMARC reports from a mailbox @@ -1513,15 +1683,10 @@ aggregate_report_msg_uids = [] forensic_report_msg_uids = [] smtp_tls_msg_uids = [] - folder_separator = connection.get_folder_separator() - aggregate_reports_folder = "{0}{1}Aggregate".format(archive_folder, - folder_separator) - forensic_reports_folder = "{0}{1}Forensic".format(archive_folder, - folder_separator) - smtp_tls_reports_folder = "{0}{1}SMTP-TLS".format(archive_folder, - folder_separator) - invalid_reports_folder = "{0}{1}Invalid".format(archive_folder, - folder_separator) + aggregate_reports_folder = "{0}/Aggregate".format(archive_folder) + forensic_reports_folder = "{0}/Forensic".format(archive_folder) + smtp_tls_reports_folder = "{0}/SMTP-TLS".format(archive_folder) + invalid_reports_folder = "{0}/Invalid".format(archive_folder) if results: aggregate_reports = results["aggregate_reports"].copy() @@ -1537,8 +1702,7 @@ messages = connection.fetch_messages(reports_folder, batch_size=batch_size) total_messages = len(messages) - logger.debug("Found {0} messages in {1}".format(len(messages), - reports_folder)) + logger.debug("Found {0} messages in {1}".format(len(messages), reports_folder)) if batch_size: message_limit = min(total_messages, batch_size) @@ -1549,9 +1713,11 @@ for i in range(message_limit): msg_uid = messages[i] - logger.debug("Processing message {0} of {1}: UID {2}".format( - i+1, message_limit, msg_uid - )) + logger.debug( + "Processing message {0} of {1}: UID {2}".format( + i + 1, message_limit, msg_uid + ) + ) msg_content = connection.fetch_message(msg_uid) try: sa = strip_attachment_payloads @@ -1565,7 +1731,8 @@ reverse_dns_map_url=reverse_dns_map_url, offline=offline, strip_attachment_payloads=sa, - keep_alive=connection.keepalive) + keep_alive=connection.keepalive, + ) if parsed_email["report_type"] == "aggregate": aggregate_reports.append(parsed_email["report"]) aggregate_report_msg_uids.append(msg_uid) @@ -1579,27 +1746,30 @@ logger.warning(error.__str__()) if not test: if delete: - logger.debug( - "Deleting message UID {0}".format(msg_uid)) + logger.debug("Deleting message UID {0}".format(msg_uid)) connection.delete_message(msg_uid) else: logger.debug( "Moving message UID {0} to {1}".format( - msg_uid, invalid_reports_folder)) + msg_uid, invalid_reports_folder + ) + ) connection.move_message(msg_uid, invalid_reports_folder) if not test: if delete: - processed_messages = aggregate_report_msg_uids + \ - forensic_report_msg_uids + \ - smtp_tls_msg_uids + processed_messages = ( + aggregate_report_msg_uids + forensic_report_msg_uids + smtp_tls_msg_uids + ) number_of_processed_msgs = len(processed_messages) for i in range(number_of_processed_msgs): msg_uid = processed_messages[i] logger.debug( "Deleting message {0} of {1}: UID {2}".format( - i + 1, number_of_processed_msgs, msg_uid)) + i + 1, number_of_processed_msgs, msg_uid + ) + ) try: connection.delete_message(msg_uid) @@ -1612,17 +1782,19 @@ log_message = "Moving aggregate report messages from" logger.debug( "{0} {1} to {2}".format( - log_message, reports_folder, - aggregate_reports_folder)) + log_message, reports_folder, aggregate_reports_folder + ) + ) number_of_agg_report_msgs = len(aggregate_report_msg_uids) for i in range(number_of_agg_report_msgs): msg_uid = aggregate_report_msg_uids[i] logger.debug( "Moving message {0} of {1}: UID {2}".format( - i+1, number_of_agg_report_msgs, msg_uid)) + i + 1, number_of_agg_report_msgs, msg_uid + ) + ) try: - connection.move_message(msg_uid, - aggregate_reports_folder) + connection.move_message(msg_uid, aggregate_reports_folder) except Exception as e: message = "Error moving message UID" e = "{0} {1}: {2}".format(message, msg_uid, e) @@ -1630,46 +1802,52 @@ if len(forensic_report_msg_uids) > 0: message = "Moving forensic report messages from" logger.debug( - "{0} {1} to {2}".format(message, - reports_folder, - forensic_reports_folder)) + "{0} {1} to {2}".format( + message, reports_folder, forensic_reports_folder + ) + ) number_of_forensic_msgs = len(forensic_report_msg_uids) for i in range(number_of_forensic_msgs): msg_uid = forensic_report_msg_uids[i] message = "Moving message" - logger.debug("{0} {1} of {2}: UID {3}".format( - message, - i + 1, number_of_forensic_msgs, msg_uid)) + logger.debug( + "{0} {1} of {2}: UID {3}".format( + message, i + 1, number_of_forensic_msgs, msg_uid + ) + ) try: - connection.move_message(msg_uid, - forensic_reports_folder) + connection.move_message(msg_uid, forensic_reports_folder) except Exception as e: - e = "Error moving message UID {0}: {1}".format( - msg_uid, e) + e = "Error moving message UID {0}: {1}".format(msg_uid, e) logger.error("Mailbox error: {0}".format(e)) if len(smtp_tls_msg_uids) > 0: message = "Moving SMTP TLS report messages from" logger.debug( - "{0} {1} to {2}".format(message, - reports_folder, - smtp_tls_reports_folder)) + "{0} {1} to {2}".format( + message, reports_folder, smtp_tls_reports_folder + ) + ) number_of_smtp_tls_uids = len(smtp_tls_msg_uids) for i in range(number_of_smtp_tls_uids): msg_uid = smtp_tls_msg_uids[i] message = "Moving message" - logger.debug("{0} {1} of {2}: UID {3}".format( - message, - i + 1, number_of_smtp_tls_uids, msg_uid)) + logger.debug( + "{0} {1} of {2}: UID {3}".format( + message, i + 1, number_of_smtp_tls_uids, msg_uid + ) + ) try: - connection.move_message(msg_uid, - smtp_tls_reports_folder) + connection.move_message(msg_uid, smtp_tls_reports_folder) except Exception as e: - e = "Error moving message UID {0}: {1}".format( - msg_uid, 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 = OrderedDict( + [ + ("aggregate_reports", aggregate_reports), + ("forensic_reports", forensic_reports), + ("smtp_tls_reports", smtp_tls_reports), + ] + ) total_messages = len(connection.fetch_messages(reports_folder)) @@ -1689,23 +1867,33 @@ always_use_local_files=always_use_local_files, reverse_dns_map_path=reverse_dns_map_path, reverse_dns_map_url=reverse_dns_map_url, - offline=offline + offline=offline, ) return results
-
[docs]def watch_inbox(mailbox_connection: MailboxConnection, - callback: Callable, - reports_folder="INBOX", - archive_folder="Archive", delete=False, test=False, - check_timeout=30, ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, nameservers=None, - dns_timeout=6.0, strip_attachment_payloads=False, - batch_size=None): + +
+[docs] +def watch_inbox( + mailbox_connection: MailboxConnection, + callback: Callable, + reports_folder="INBOX", + archive_folder="Archive", + delete=False, + test=False, + check_timeout=30, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + nameservers=None, + dns_timeout=6.0, + strip_attachment_payloads=False, + batch_size=None, +): """ Watches the mailbox for new messages and sends the results to a callback function @@ -1749,11 +1937,12 @@ dns_timeout=dns_timeout, strip_attachment_payloads=sa, batch_size=batch_size, - create_folders=False) + create_folders=False, + ) callback(res) - mailbox_connection.watch(check_callback=check_callback, - check_timeout=check_timeout)
+ mailbox_connection.watch(check_callback=check_callback, check_timeout=check_timeout)
+ def append_json(filename, reports): @@ -1791,13 +1980,18 @@ output.write(csv) -
[docs]def save_output(results, output_directory="output", - aggregate_json_filename="aggregate.json", - forensic_json_filename="forensic.json", - smtp_tls_json_filename="smtp_tls.json", - aggregate_csv_filename="aggregate.csv", - forensic_csv_filename="forensic.csv", - smtp_tls_csv_filename="smtp_tls.csv"): +
+[docs] +def save_output( + results, + output_directory="output", + aggregate_json_filename="aggregate.json", + forensic_json_filename="forensic.json", + smtp_tls_json_filename="smtp_tls.json", + aggregate_csv_filename="aggregate.csv", + forensic_csv_filename="forensic.csv", + smtp_tls_csv_filename="smtp_tls.csv", +): """ Save report data in the given directory @@ -1823,23 +2017,32 @@ else: os.makedirs(output_directory) - append_json(os.path.join(output_directory, aggregate_json_filename), - aggregate_reports) + append_json( + os.path.join(output_directory, aggregate_json_filename), aggregate_reports + ) - append_csv(os.path.join(output_directory, aggregate_csv_filename), - parsed_aggregate_reports_to_csv(aggregate_reports)) + append_csv( + os.path.join(output_directory, aggregate_csv_filename), + parsed_aggregate_reports_to_csv(aggregate_reports), + ) - append_json(os.path.join(output_directory, forensic_json_filename), - forensic_reports) + append_json( + os.path.join(output_directory, forensic_json_filename), forensic_reports + ) - append_csv(os.path.join(output_directory, forensic_csv_filename), - parsed_forensic_reports_to_csv(forensic_reports)) + append_csv( + os.path.join(output_directory, forensic_csv_filename), + parsed_forensic_reports_to_csv(forensic_reports), + ) - append_json(os.path.join(output_directory, smtp_tls_json_filename), - smtp_tls_reports) + append_json( + os.path.join(output_directory, smtp_tls_json_filename), smtp_tls_reports + ) - append_csv(os.path.join(output_directory, smtp_tls_csv_filename), - parsed_smtp_tls_reports_to_csv(smtp_tls_reports)) + append_csv( + os.path.join(output_directory, smtp_tls_csv_filename), + parsed_smtp_tls_reports_to_csv(smtp_tls_reports), + ) samples_directory = os.path.join(output_directory, "samples") if not os.path.exists(samples_directory): @@ -1865,7 +2068,10 @@ sample_file.write(sample)
-
[docs]def get_report_zip(results): + +
+[docs] +def get_report_zip(results): """ Creates a zip file of parsed report output @@ -1875,6 +2081,7 @@ Returns: bytes: zip file bytes """ + def add_subdir(root_path, subdir): subdir_path = os.path.join(root_path, subdir) for subdir_root, subdir_dirs, subdir_files in os.walk(subdir_path): @@ -1891,13 +2098,12 @@ tmp_dir = tempfile.mkdtemp() try: save_output(results, tmp_dir) - with zipfile.ZipFile(storage, 'w', zipfile.ZIP_DEFLATED) as zip_file: + with zipfile.ZipFile(storage, "w", zipfile.ZIP_DEFLATED) as zip_file: for root, dirs, files in os.walk(tmp_dir): for file in files: file_path = os.path.join(root, file) if os.path.isfile(file_path): - arcname = os.path.join(os.path.relpath(root, tmp_dir), - file) + arcname = os.path.join(os.path.relpath(root, tmp_dir), file) zip_file.write(file_path, arcname) for directory in dirs: dir_path = os.path.join(root, directory) @@ -1910,11 +2116,25 @@ return storage.getvalue()
-
[docs]def email_results(results, host, mail_from, mail_to, - mail_cc=None, mail_bcc=None, port=0, - require_encryption=False, verify=True, - username=None, password=None, subject=None, - attachment_filename=None, message=None): + +
+[docs] +def email_results( + results, + host, + mail_from, + mail_to, + mail_cc=None, + mail_bcc=None, + port=0, + require_encryption=False, + verify=True, + username=None, + password=None, + subject=None, + attachment_filename=None, + message=None, +): """ Emails parsing results as a zip file @@ -1952,11 +2172,22 @@ zip_bytes = get_report_zip(results) attachments = [(filename, zip_bytes)] - send_email(host, mail_from, mail_to, message_cc=mail_cc, - message_bcc=mail_bcc, port=port, - require_encryption=require_encryption, verify=verify, - username=username, password=password, subject=subject, - attachments=attachments, plain_message=message)
+ send_email( + host, + mail_from, + mail_to, + message_cc=mail_cc, + message_bcc=mail_bcc, + port=port, + require_encryption=require_encryption, + verify=verify, + username=username, + password=password, + subject=subject, + attachments=attachments, + plain_message=message, + )
+
diff --git a/_modules/parsedmarc/elastic.html b/_modules/parsedmarc/elastic.html index 6cba634..b64a5ee 100644 --- a/_modules/parsedmarc/elastic.html +++ b/_modules/parsedmarc/elastic.html @@ -1,23 +1,20 @@ + + - + - parsedmarc.elastic — parsedmarc 8.15.0 documentation - - + parsedmarc.elastic — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -91,8 +85,20 @@ from collections import OrderedDict from elasticsearch_dsl.search import Q -from elasticsearch_dsl import connections, Object, Document, Index, Nested, \ - InnerDoc, Integer, Text, Boolean, Ip, Date, Search +from elasticsearch_dsl import ( + connections, + Object, + Document, + Index, + Nested, + InnerDoc, + Integer, + Text, + Boolean, + Ip, + Date, + Search, +) from elasticsearch.helpers import reindex from parsedmarc.log import logger @@ -100,10 +106,13 @@ from parsedmarc import InvalidForensicReport -
[docs]class ElasticsearchError(Exception): +
+[docs] +class ElasticsearchError(Exception): """Raised when an Elasticsearch error occurs"""
+ class _PolicyOverride(InnerDoc): type = Text() comment = Text() @@ -164,24 +173,21 @@ spf_results = Nested(_SPFResult) def add_policy_override(self, type_, comment): - self.policy_overrides.append(_PolicyOverride(type=type_, - comment=comment)) + self.policy_overrides.append(_PolicyOverride(type=type_, comment=comment)) def add_dkim_result(self, domain, selector, result): - self.dkim_results.append(_DKIMResult(domain=domain, - selector=selector, - result=result)) + self.dkim_results.append( + _DKIMResult(domain=domain, selector=selector, result=result) + ) def add_spf_result(self, domain, scope, result): - self.spf_results.append(_SPFResult(domain=domain, - scope=scope, - result=result)) + self.spf_results.append(_SPFResult(domain=domain, scope=scope, result=result)) - def save(self, ** kwargs): + def save(self, **kwargs): self.passed_dmarc = False self.passed_dmarc = self.spf_aligned or self.dkim_aligned - return super().save(** kwargs) + return super().save(**kwargs) class _EmailAddressDoc(InnerDoc): @@ -211,24 +217,25 @@ attachments = Nested(_EmailAttachmentDoc) def add_to(self, display_name, address): - self.to.append(_EmailAddressDoc(display_name=display_name, - address=address)) + self.to.append(_EmailAddressDoc(display_name=display_name, address=address)) def add_reply_to(self, display_name, address): - self.reply_to.append(_EmailAddressDoc(display_name=display_name, - address=address)) + self.reply_to.append( + _EmailAddressDoc(display_name=display_name, address=address) + ) def add_cc(self, display_name, address): - self.cc.append(_EmailAddressDoc(display_name=display_name, - address=address)) + self.cc.append(_EmailAddressDoc(display_name=display_name, address=address)) def add_bcc(self, display_name, address): - self.bcc.append(_EmailAddressDoc(display_name=display_name, - address=address)) + self.bcc.append(_EmailAddressDoc(display_name=display_name, address=address)) def add_attachment(self, filename, content_type, sha256): - self.attachments.append(_EmailAttachmentDoc(filename=filename, - content_type=content_type, sha256=sha256)) + self.attachments.append( + _EmailAttachmentDoc( + filename=filename, content_type=content_type, sha256=sha256 + ) + ) class _ForensicReportDoc(Document): @@ -273,14 +280,18 @@ failed_session_count = Integer() failure_details = Nested(_SMTPTLSFailureDetailsDoc) - def add_failure_details(self, result_type, ip_address, - receiving_ip, - receiving_mx_helo, - failed_session_count, - sending_mta_ip=None, - receiving_mx_hostname=None, - additional_information_uri=None, - failure_reason_code=None): + def add_failure_details( + self, + result_type, + ip_address, + receiving_ip, + receiving_mx_helo, + failed_session_count, + sending_mta_ip=None, + receiving_mx_hostname=None, + additional_information_uri=None, + failure_reason_code=None, + ): _details = _SMTPTLSFailureDetailsDoc( result_type=result_type, ip_address=ip_address, @@ -290,13 +301,12 @@ receiving_ip=receiving_ip, failed_session_count=failed_session_count, additional_information=additional_information_uri, - failure_reason_code=failure_reason_code + failure_reason_code=failure_reason_code, ) self.failure_details.append(_details) class _SMTPTLSReportDoc(Document): - class Index: name = "smtp_tls" @@ -308,27 +318,45 @@ report_id = Text() policies = Nested(_SMTPTLSPolicyDoc) - def add_policy(self, policy_type, policy_domain, - successful_session_count, - failed_session_count, - policy_string=None, - mx_host_patterns=None, - failure_details=None): - self.policies.append(policy_type=policy_type, - policy_domain=policy_domain, - successful_session_count=successful_session_count, - failed_session_count=failed_session_count, - policy_string=policy_string, - mx_host_patterns=mx_host_patterns, - failure_details=failure_details) + def add_policy( + self, + policy_type, + policy_domain, + successful_session_count, + failed_session_count, + policy_string=None, + mx_host_patterns=None, + failure_details=None, + ): + self.policies.append( + policy_type=policy_type, + policy_domain=policy_domain, + successful_session_count=successful_session_count, + failed_session_count=failed_session_count, + policy_string=policy_string, + mx_host_patterns=mx_host_patterns, + failure_details=failure_details, + ) -
[docs]class AlreadySaved(ValueError): +
+[docs] +class AlreadySaved(ValueError): """Raised when a report to be saved matches an existing report"""
-
[docs]def set_hosts(hosts, use_ssl=False, ssl_cert_path=None, - username=None, password=None, apiKey=None, timeout=60.0): + +
+[docs] +def set_hosts( + hosts, + use_ssl=False, + ssl_cert_path=None, + username=None, + password=None, + apiKey=None, + timeout=60.0, +): """ Sets the Elasticsearch hosts to use @@ -343,25 +371,25 @@ """ if not isinstance(hosts, list): hosts = [hosts] - conn_params = { - "hosts": hosts, - "timeout": timeout - } + conn_params = {"hosts": hosts, "timeout": timeout} if use_ssl: - conn_params['use_ssl'] = True + conn_params["use_ssl"] = True if ssl_cert_path: - conn_params['verify_certs'] = True - conn_params['ca_certs'] = ssl_cert_path + conn_params["verify_certs"] = True + conn_params["ca_certs"] = ssl_cert_path else: - conn_params['verify_certs'] = False + conn_params["verify_certs"] = False if username: - conn_params['http_auth'] = (username+":"+password) + conn_params["http_auth"] = username + ":" + password if apiKey: - conn_params['api_key'] = apiKey + conn_params["api_key"] = apiKey connections.create_connection(**conn_params)
-
[docs]def create_indexes(names, settings=None): + +
+[docs] +def create_indexes(names, settings=None): """ Create Elasticsearch indexes @@ -376,17 +404,18 @@ if not index.exists(): logger.debug("Creating Elasticsearch index: {0}".format(name)) if settings is None: - index.settings(number_of_shards=1, - number_of_replicas=0) + index.settings(number_of_shards=1, number_of_replicas=0) else: index.settings(**settings) index.create() except Exception as e: - raise ElasticsearchError( - "Elasticsearch error: {0}".format(e.__str__()))
+ raise ElasticsearchError("Elasticsearch error: {0}".format(e.__str__()))
-
[docs]def migrate_indexes(aggregate_indexes=None, forensic_indexes=None): + +
+[docs] +def migrate_indexes(aggregate_indexes=None, forensic_indexes=None): """ Updates index mappings @@ -415,33 +444,34 @@ fo_type = fo_mapping["type"] if fo_type == "long": new_index_name = "{0}-v{1}".format(aggregate_index_name, version) - body = {"properties": {"published_policy.fo": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 + body = { + "properties": { + "published_policy.fo": { + "type": "text", + "fields": {"keyword": {"type": "keyword", "ignore_above": 256}}, } } } - } - } 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) Index(aggregate_index_name).delete() for forensic_index in forensic_indexes: pass
-
[docs]def save_aggregate_report_to_elasticsearch(aggregate_report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_aggregate_report_to_elasticsearch( + aggregate_report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ Saves a parsed DMARC aggregate report to Elasticsearch @@ -462,10 +492,8 @@ org_name = metadata["org_name"] report_id = metadata["report_id"] domain = aggregate_report["policy_published"]["domain"] - begin_date = human_timestamp_to_datetime(metadata["begin_date"], - to_utc=True) - end_date = human_timestamp_to_datetime(metadata["end_date"], - to_utc=True) + begin_date = human_timestamp_to_datetime(metadata["begin_date"], to_utc=True) + end_date = human_timestamp_to_datetime(metadata["end_date"], to_utc=True) begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ") end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ") if monthly_indexes: @@ -474,8 +502,7 @@ index_date = begin_date.strftime("%Y-%m-%d") aggregate_report["begin_date"] = begin_date aggregate_report["end_date"] = end_date - date_range = [aggregate_report["begin_date"], - aggregate_report["end_date"]] + date_range = [aggregate_report["begin_date"], aggregate_report["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))) @@ -497,18 +524,20 @@ try: existing = search.execute() except Exception as error_: - raise ElasticsearchError("Elasticsearch's search for existing report \ - error: {}".format(error_.__str__())) + raise ElasticsearchError( + "Elasticsearch's search for existing report \ + error: {}".format(error_.__str__()) + ) if len(existing) > 0: - raise AlreadySaved("An aggregate report ID {0} from {1} about {2} " - "with a date range of {3} UTC to {4} UTC already " - "exists in " - "Elasticsearch".format(report_id, - org_name, - domain, - begin_date_human, - end_date_human)) + raise AlreadySaved( + "An aggregate report ID {0} from {1} about {2} " + "with a date range of {3} UTC to {4} UTC already " + "exists in " + "Elasticsearch".format( + report_id, org_name, domain, begin_date_human, end_date_human + ) + ) published_policy = _PublishedPolicy( domain=aggregate_report["policy_published"]["domain"], adkim=aggregate_report["policy_published"]["adkim"], @@ -516,7 +545,7 @@ p=aggregate_report["policy_published"]["p"], sp=aggregate_report["policy_published"]["sp"], pct=aggregate_report["policy_published"]["pct"], - fo=aggregate_report["policy_published"]["fo"] + fo=aggregate_report["policy_published"]["fo"], ) for record in aggregate_report["records"]: @@ -539,28 +568,33 @@ source_name=record["source"]["name"], message_count=record["count"], disposition=record["policy_evaluated"]["disposition"], - dkim_aligned=record["policy_evaluated"]["dkim"] is not None and - record["policy_evaluated"]["dkim"].lower() == "pass", - spf_aligned=record["policy_evaluated"]["spf"] is not None and - record["policy_evaluated"]["spf"].lower() == "pass", + dkim_aligned=record["policy_evaluated"]["dkim"] is not None + and record["policy_evaluated"]["dkim"].lower() == "pass", + spf_aligned=record["policy_evaluated"]["spf"] is not None + and record["policy_evaluated"]["spf"].lower() == "pass", header_from=record["identifiers"]["header_from"], envelope_from=record["identifiers"]["envelope_from"], - envelope_to=record["identifiers"]["envelope_to"] + envelope_to=record["identifiers"]["envelope_to"], ) for override in record["policy_evaluated"]["policy_override_reasons"]: - agg_doc.add_policy_override(type_=override["type"], - comment=override["comment"]) + agg_doc.add_policy_override( + type_=override["type"], comment=override["comment"] + ) for dkim_result in record["auth_results"]["dkim"]: - agg_doc.add_dkim_result(domain=dkim_result["domain"], - selector=dkim_result["selector"], - result=dkim_result["result"]) + agg_doc.add_dkim_result( + domain=dkim_result["domain"], + selector=dkim_result["selector"], + result=dkim_result["result"], + ) for spf_result in record["auth_results"]["spf"]: - agg_doc.add_spf_result(domain=spf_result["domain"], - scope=spf_result["scope"], - result=spf_result["result"]) + agg_doc.add_spf_result( + domain=spf_result["domain"], + scope=spf_result["scope"], + result=spf_result["result"], + ) index = "dmarc_aggregate" if index_suffix: @@ -569,41 +603,46 @@ index = "{0}{1}".format(index_prefix, index) index = "{0}-{1}".format(index, index_date) - index_settings = dict(number_of_shards=number_of_shards, - number_of_replicas=number_of_replicas) + index_settings = dict( + number_of_shards=number_of_shards, number_of_replicas=number_of_replicas + ) create_indexes([index], index_settings) agg_doc.meta.index = index try: agg_doc.save() except Exception as e: - raise ElasticsearchError( - "Elasticsearch error: {0}".format(e.__str__()))
+ raise ElasticsearchError("Elasticsearch error: {0}".format(e.__str__()))
-
[docs]def save_forensic_report_to_elasticsearch(forensic_report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_forensic_report_to_elasticsearch( + forensic_report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ - Saves a parsed DMARC forensic report to Elasticsearch + Saves a parsed DMARC forensic report to Elasticsearch - Args: - forensic_report (OrderedDict): 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 - number_of_shards (int): The number of shards to use in the index - number_of_replicas (int): The number of replicas to use in the - index + Args: + forensic_report (OrderedDict): 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 + number_of_shards (int): The number of shards to use in the index + number_of_replicas (int): The number of replicas to use in the + index - Raises: - AlreadySaved + Raises: + AlreadySaved - """ + """ logger.info("Saving forensic report to Elasticsearch") forensic_report = forensic_report.copy() sample_date = None @@ -648,14 +687,12 @@ existing = search.execute() if len(existing) > 0: - raise AlreadySaved("A forensic sample to {0} from {1} " - "with a subject of {2} and arrival date of {3} " - "already exists in " - "Elasticsearch".format(to_, - from_, - subject, - arrival_date_human - )) + raise AlreadySaved( + "A forensic sample to {0} from {1} " + "with a subject of {2} and arrival date of {3} " + "already exists in " + "Elasticsearch".format(to_, from_, subject, arrival_date_human) + ) parsed_sample = forensic_report["parsed_sample"] sample = _ForensicSampleDoc( @@ -665,25 +702,25 @@ date=sample_date, subject=forensic_report["parsed_sample"]["subject"], filename_safe_subject=parsed_sample["filename_safe_subject"], - body=forensic_report["parsed_sample"]["body"] + body=forensic_report["parsed_sample"]["body"], ) for address in forensic_report["parsed_sample"]["to"]: - sample.add_to(display_name=address["display_name"], - address=address["address"]) + sample.add_to(display_name=address["display_name"], address=address["address"]) for address in forensic_report["parsed_sample"]["reply_to"]: - sample.add_reply_to(display_name=address["display_name"], - address=address["address"]) + sample.add_reply_to( + display_name=address["display_name"], address=address["address"] + ) for address in forensic_report["parsed_sample"]["cc"]: - sample.add_cc(display_name=address["display_name"], - address=address["address"]) + sample.add_cc(display_name=address["display_name"], address=address["address"]) for address in forensic_report["parsed_sample"]["bcc"]: - sample.add_bcc(display_name=address["display_name"], - address=address["address"]) + sample.add_bcc(display_name=address["display_name"], address=address["address"]) for attachment in forensic_report["parsed_sample"]["attachments"]: - sample.add_attachment(filename=attachment["filename"], - content_type=attachment["mail_content_type"], - sha256=attachment["sha256"]) + sample.add_attachment( + filename=attachment["filename"], + content_type=attachment["mail_content_type"], + sha256=attachment["sha256"], + ) try: forensic_doc = _ForensicReportDoc( feedback_type=forensic_report["feedback_type"], @@ -699,12 +736,11 @@ source_country=forensic_report["source"]["country"], source_reverse_dns=forensic_report["source"]["reverse_dns"], source_base_domain=forensic_report["source"]["base_domain"], - authentication_mechanisms=forensic_report[ - "authentication_mechanisms"], + authentication_mechanisms=forensic_report["authentication_mechanisms"], auth_failure=forensic_report["auth_failure"], dkim_domain=forensic_report["dkim_domain"], original_rcpt_to=forensic_report["original_rcpt_to"], - sample=sample + sample=sample, ) index = "dmarc_forensic" @@ -717,26 +753,32 @@ else: index_date = arrival_date.strftime("%Y-%m-%d") index = "{0}-{1}".format(index, index_date) - index_settings = dict(number_of_shards=number_of_shards, - number_of_replicas=number_of_replicas) + index_settings = dict( + number_of_shards=number_of_shards, number_of_replicas=number_of_replicas + ) create_indexes([index], index_settings) forensic_doc.meta.index = index try: forensic_doc.save() except Exception as e: - raise ElasticsearchError( - "Elasticsearch error: {0}".format(e.__str__())) + raise ElasticsearchError("Elasticsearch error: {0}".format(e.__str__())) except KeyError as e: raise InvalidForensicReport( - "Forensic report missing required field: {0}".format(e.__str__()))
+ "Forensic report missing required field: {0}".format(e.__str__()) + )
-
[docs]def save_smtp_tls_report_to_elasticsearch(report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_smtp_tls_report_to_elasticsearch( + report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ Saves a parsed SMTP TLS report to Elasticsearch @@ -754,10 +796,8 @@ logger.info("Saving smtp tls report to Elasticsearch") org_name = report["organization_name"] report_id = report["report_id"] - begin_date = human_timestamp_to_datetime(report["begin_date"], - to_utc=True) - end_date = human_timestamp_to_datetime(report["end_date"], - to_utc=True) + begin_date = human_timestamp_to_datetime(report["begin_date"], to_utc=True) + end_date = human_timestamp_to_datetime(report["end_date"], to_utc=True) begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ") end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ") if monthly_indexes: @@ -786,15 +826,19 @@ try: existing = search.execute() except Exception as error_: - raise ElasticsearchError("Elasticsearch's search for existing report \ - error: {}".format(error_.__str__())) + raise ElasticsearchError( + "Elasticsearch's search for existing report \ + error: {}".format(error_.__str__()) + ) if len(existing) > 0: - raise AlreadySaved(f"An SMTP TLS report ID {report_id} from " - f" {org_name} with a date range of " - f"{begin_date_human} UTC to " - f"{end_date_human} UTC already " - "exists in Elasticsearch") + raise AlreadySaved( + f"An SMTP TLS report ID {report_id} from " + f" {org_name} with a date range of " + f"{begin_date_human} UTC to " + f"{end_date_human} UTC already " + "exists in Elasticsearch" + ) index = "smtp_tls" if index_suffix: @@ -802,8 +846,9 @@ if index_prefix: index = "{0}{1}".format(index_prefix, index) index = "{0}-{1}".format(index, index_date) - index_settings = dict(number_of_shards=number_of_shards, - number_of_replicas=number_of_replicas) + index_settings = dict( + number_of_shards=number_of_shards, number_of_replicas=number_of_replicas + ) smtp_tls_doc = _SMTPTLSReportDoc( org_name=report["organization_name"], @@ -811,10 +856,10 @@ date_begin=report["begin_date"], date_end=report["end_date"], contact_info=report["contact_info"], - report_id=report["report_id"] + report_id=report["report_id"], ) - for policy in report['policies']: + for policy in report["policies"]: policy_strings = None mx_host_patterns = None if "policy_strings" in policy: @@ -827,7 +872,7 @@ succesful_session_count=policy["successful_session_count"], failed_session_count=policy["failed_session_count"], policy_string=policy_strings, - mx_host_patterns=mx_host_patterns + mx_host_patterns=mx_host_patterns, ) if "failure_details" in policy: for failure_detail in policy["failure_details"]: @@ -840,11 +885,11 @@ sending_mta_ip = None if "receiving_mx_hostname" in failure_detail: - receiving_mx_hostname = failure_detail[ - "receiving_mx_hostname"] + receiving_mx_hostname = failure_detail["receiving_mx_hostname"] if "additional_information_uri" in failure_detail: additional_information_uri = failure_detail[ - "additional_information_uri"] + "additional_information_uri" + ] if "failure_reason_code" in failure_detail: failure_reason_code = failure_detail["failure_reason_code"] if "ip_address" in failure_detail: @@ -860,12 +905,11 @@ ip_address=ip_address, receiving_ip=receiving_ip, receiving_mx_helo=receiving_mx_helo, - failed_session_count=failure_detail[ - "failed_session_count"], + failed_session_count=failure_detail["failed_session_count"], sending_mta_ip=sending_mta_ip, receiving_mx_hostname=receiving_mx_hostname, additional_information_uri=additional_information_uri, - failure_reason_code=failure_reason_code + failure_reason_code=failure_reason_code, ) smtp_tls_doc.policies.append(policy_doc) @@ -875,8 +919,8 @@ try: smtp_tls_doc.save() except Exception as e: - raise ElasticsearchError( - "Elasticsearch error: {0}".format(e.__str__()))
+ raise ElasticsearchError("Elasticsearch error: {0}".format(e.__str__()))
+
diff --git a/_modules/parsedmarc/opensearch.html b/_modules/parsedmarc/opensearch.html index 1a98ebe..694d160 100644 --- a/_modules/parsedmarc/opensearch.html +++ b/_modules/parsedmarc/opensearch.html @@ -1,23 +1,20 @@ + + - + - parsedmarc.opensearch — parsedmarc 8.15.0 documentation - - + parsedmarc.opensearch — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -90,8 +84,21 @@ from collections import OrderedDict -from opensearchpy import Q, connections, Object, Document, Index, Nested, \ - InnerDoc, Integer, Text, Boolean, Ip, Date, Search +from opensearchpy import ( + Q, + connections, + Object, + Document, + Index, + Nested, + InnerDoc, + Integer, + Text, + Boolean, + Ip, + Date, + Search, +) from opensearchpy.helpers import reindex from parsedmarc.log import logger @@ -99,10 +106,13 @@ from parsedmarc import InvalidForensicReport -
[docs]class OpenSearchError(Exception): +
+[docs] +class OpenSearchError(Exception): """Raised when an OpenSearch error occurs"""
+ class _PolicyOverride(InnerDoc): type = Text() comment = Text() @@ -163,24 +173,21 @@ spf_results = Nested(_SPFResult) def add_policy_override(self, type_, comment): - self.policy_overrides.append(_PolicyOverride(type=type_, - comment=comment)) + self.policy_overrides.append(_PolicyOverride(type=type_, comment=comment)) def add_dkim_result(self, domain, selector, result): - self.dkim_results.append(_DKIMResult(domain=domain, - selector=selector, - result=result)) + self.dkim_results.append( + _DKIMResult(domain=domain, selector=selector, result=result) + ) def add_spf_result(self, domain, scope, result): - self.spf_results.append(_SPFResult(domain=domain, - scope=scope, - result=result)) + self.spf_results.append(_SPFResult(domain=domain, scope=scope, result=result)) - def save(self, ** kwargs): + def save(self, **kwargs): self.passed_dmarc = False self.passed_dmarc = self.spf_aligned or self.dkim_aligned - return super().save(** kwargs) + return super().save(**kwargs) class _EmailAddressDoc(InnerDoc): @@ -210,24 +217,25 @@ attachments = Nested(_EmailAttachmentDoc) def add_to(self, display_name, address): - self.to.append(_EmailAddressDoc(display_name=display_name, - address=address)) + self.to.append(_EmailAddressDoc(display_name=display_name, address=address)) def add_reply_to(self, display_name, address): - self.reply_to.append(_EmailAddressDoc(display_name=display_name, - address=address)) + self.reply_to.append( + _EmailAddressDoc(display_name=display_name, address=address) + ) def add_cc(self, display_name, address): - self.cc.append(_EmailAddressDoc(display_name=display_name, - address=address)) + self.cc.append(_EmailAddressDoc(display_name=display_name, address=address)) def add_bcc(self, display_name, address): - self.bcc.append(_EmailAddressDoc(display_name=display_name, - address=address)) + self.bcc.append(_EmailAddressDoc(display_name=display_name, address=address)) def add_attachment(self, filename, content_type, sha256): - self.attachments.append(_EmailAttachmentDoc(filename=filename, - content_type=content_type, sha256=sha256)) + self.attachments.append( + _EmailAttachmentDoc( + filename=filename, content_type=content_type, sha256=sha256 + ) + ) class _ForensicReportDoc(Document): @@ -272,13 +280,17 @@ failed_session_count = Integer() failure_details = Nested(_SMTPTLSFailureDetailsDoc) - def add_failure_details(self, result_type, ip_address, - receiving_ip, - receiving_mx_helo, - failed_session_count, - receiving_mx_hostname=None, - additional_information_uri=None, - failure_reason_code=None): + def add_failure_details( + self, + result_type, + ip_address, + receiving_ip, + receiving_mx_helo, + failed_session_count, + receiving_mx_hostname=None, + additional_information_uri=None, + failure_reason_code=None, + ): self.failure_details.append( result_type=result_type, ip_address=ip_address, @@ -287,12 +299,11 @@ receiving_ip=receiving_ip, failed_session_count=failed_session_count, additional_information=additional_information_uri, - failure_reason_code=failure_reason_code + failure_reason_code=failure_reason_code, ) class _SMTPTLSFailureReportDoc(Document): - class Index: name = "smtp_tls" @@ -304,27 +315,45 @@ report_id = Text() policies = Nested(_SMTPTLSPolicyDoc) - def add_policy(self, policy_type, policy_domain, - successful_session_count, - failed_session_count, - policy_string=None, - mx_host_patterns=None, - failure_details=None): - self.policies.append(policy_type=policy_type, - policy_domain=policy_domain, - successful_session_count=successful_session_count, - failed_session_count=failed_session_count, - policy_string=policy_string, - mx_host_patterns=mx_host_patterns, - failure_details=failure_details) + def add_policy( + self, + policy_type, + policy_domain, + successful_session_count, + failed_session_count, + policy_string=None, + mx_host_patterns=None, + failure_details=None, + ): + self.policies.append( + policy_type=policy_type, + policy_domain=policy_domain, + successful_session_count=successful_session_count, + failed_session_count=failed_session_count, + policy_string=policy_string, + mx_host_patterns=mx_host_patterns, + failure_details=failure_details, + ) -
[docs]class AlreadySaved(ValueError): +
+[docs] +class AlreadySaved(ValueError): """Raised when a report to be saved matches an existing report"""
-
[docs]def set_hosts(hosts, use_ssl=False, ssl_cert_path=None, - username=None, password=None, apiKey=None, timeout=60.0): + +
+[docs] +def set_hosts( + hosts, + use_ssl=False, + ssl_cert_path=None, + username=None, + password=None, + apiKey=None, + timeout=60.0, +): """ Sets the OpenSearch hosts to use @@ -339,25 +368,25 @@ """ if not isinstance(hosts, list): hosts = [hosts] - conn_params = { - "hosts": hosts, - "timeout": timeout - } + conn_params = {"hosts": hosts, "timeout": timeout} if use_ssl: - conn_params['use_ssl'] = True + conn_params["use_ssl"] = True if ssl_cert_path: - conn_params['verify_certs'] = True - conn_params['ca_certs'] = ssl_cert_path + conn_params["verify_certs"] = True + conn_params["ca_certs"] = ssl_cert_path else: - conn_params['verify_certs'] = False + conn_params["verify_certs"] = False if username: - conn_params['http_auth'] = (username+":"+password) + conn_params["http_auth"] = username + ":" + password if apiKey: - conn_params['api_key'] = apiKey + conn_params["api_key"] = apiKey connections.create_connection(**conn_params)
-
[docs]def create_indexes(names, settings=None): + +
+[docs] +def create_indexes(names, settings=None): """ Create OpenSearch indexes @@ -372,17 +401,18 @@ if not index.exists(): logger.debug("Creating OpenSearch index: {0}".format(name)) if settings is None: - index.settings(number_of_shards=1, - number_of_replicas=0) + index.settings(number_of_shards=1, number_of_replicas=0) else: index.settings(**settings) index.create() except Exception as e: - raise OpenSearchError( - "OpenSearch error: {0}".format(e.__str__()))
+ raise OpenSearchError("OpenSearch error: {0}".format(e.__str__()))
-
[docs]def migrate_indexes(aggregate_indexes=None, forensic_indexes=None): + +
+[docs] +def migrate_indexes(aggregate_indexes=None, forensic_indexes=None): """ Updates index mappings @@ -411,33 +441,34 @@ fo_type = fo_mapping["type"] if fo_type == "long": new_index_name = "{0}-v{1}".format(aggregate_index_name, version) - body = {"properties": {"published_policy.fo": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 + body = { + "properties": { + "published_policy.fo": { + "type": "text", + "fields": {"keyword": {"type": "keyword", "ignore_above": 256}}, } } } - } - } 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) Index(aggregate_index_name).delete() for forensic_index in forensic_indexes: pass
-
[docs]def save_aggregate_report_to_opensearch(aggregate_report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_aggregate_report_to_opensearch( + aggregate_report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ Saves a parsed DMARC aggregate report to OpenSearch @@ -458,10 +489,8 @@ org_name = metadata["org_name"] report_id = metadata["report_id"] domain = aggregate_report["policy_published"]["domain"] - begin_date = human_timestamp_to_datetime(metadata["begin_date"], - to_utc=True) - end_date = human_timestamp_to_datetime(metadata["end_date"], - to_utc=True) + begin_date = human_timestamp_to_datetime(metadata["begin_date"], to_utc=True) + end_date = human_timestamp_to_datetime(metadata["end_date"], to_utc=True) begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ") end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ") if monthly_indexes: @@ -470,8 +499,7 @@ index_date = begin_date.strftime("%Y-%m-%d") aggregate_report["begin_date"] = begin_date aggregate_report["end_date"] = end_date - date_range = [aggregate_report["begin_date"], - aggregate_report["end_date"]] + date_range = [aggregate_report["begin_date"], aggregate_report["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))) @@ -493,18 +521,20 @@ try: existing = search.execute() except Exception as error_: - raise OpenSearchError("OpenSearch's search for existing report \ - error: {}".format(error_.__str__())) + raise OpenSearchError( + "OpenSearch's search for existing report \ + error: {}".format(error_.__str__()) + ) if len(existing) > 0: - raise AlreadySaved("An aggregate report ID {0} from {1} about {2} " - "with a date range of {3} UTC to {4} UTC already " - "exists in " - "OpenSearch".format(report_id, - org_name, - domain, - begin_date_human, - end_date_human)) + raise AlreadySaved( + "An aggregate report ID {0} from {1} about {2} " + "with a date range of {3} UTC to {4} UTC already " + "exists in " + "OpenSearch".format( + report_id, org_name, domain, begin_date_human, end_date_human + ) + ) published_policy = _PublishedPolicy( domain=aggregate_report["policy_published"]["domain"], adkim=aggregate_report["policy_published"]["adkim"], @@ -512,7 +542,7 @@ p=aggregate_report["policy_published"]["p"], sp=aggregate_report["policy_published"]["sp"], pct=aggregate_report["policy_published"]["pct"], - fo=aggregate_report["policy_published"]["fo"] + fo=aggregate_report["policy_published"]["fo"], ) for record in aggregate_report["records"]: @@ -535,28 +565,33 @@ source_name=record["source"]["name"], message_count=record["count"], disposition=record["policy_evaluated"]["disposition"], - dkim_aligned=record["policy_evaluated"]["dkim"] is not None and - record["policy_evaluated"]["dkim"].lower() == "pass", - spf_aligned=record["policy_evaluated"]["spf"] is not None and - record["policy_evaluated"]["spf"].lower() == "pass", + dkim_aligned=record["policy_evaluated"]["dkim"] is not None + and record["policy_evaluated"]["dkim"].lower() == "pass", + spf_aligned=record["policy_evaluated"]["spf"] is not None + and record["policy_evaluated"]["spf"].lower() == "pass", header_from=record["identifiers"]["header_from"], envelope_from=record["identifiers"]["envelope_from"], - envelope_to=record["identifiers"]["envelope_to"] + envelope_to=record["identifiers"]["envelope_to"], ) for override in record["policy_evaluated"]["policy_override_reasons"]: - agg_doc.add_policy_override(type_=override["type"], - comment=override["comment"]) + agg_doc.add_policy_override( + type_=override["type"], comment=override["comment"] + ) for dkim_result in record["auth_results"]["dkim"]: - agg_doc.add_dkim_result(domain=dkim_result["domain"], - selector=dkim_result["selector"], - result=dkim_result["result"]) + agg_doc.add_dkim_result( + domain=dkim_result["domain"], + selector=dkim_result["selector"], + result=dkim_result["result"], + ) for spf_result in record["auth_results"]["spf"]: - agg_doc.add_spf_result(domain=spf_result["domain"], - scope=spf_result["scope"], - result=spf_result["result"]) + agg_doc.add_spf_result( + domain=spf_result["domain"], + scope=spf_result["scope"], + result=spf_result["result"], + ) index = "dmarc_aggregate" if index_suffix: @@ -564,41 +599,46 @@ if index_prefix: index = "{0}{1}".format(index_prefix, index) index = "{0}-{1}".format(index, index_date) - index_settings = dict(number_of_shards=number_of_shards, - number_of_replicas=number_of_replicas) + index_settings = dict( + number_of_shards=number_of_shards, number_of_replicas=number_of_replicas + ) create_indexes([index], index_settings) agg_doc.meta.index = index try: agg_doc.save() except Exception as e: - raise OpenSearchError( - "OpenSearch error: {0}".format(e.__str__()))
+ raise OpenSearchError("OpenSearch error: {0}".format(e.__str__()))
-
[docs]def save_forensic_report_to_opensearch(forensic_report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_forensic_report_to_opensearch( + forensic_report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ - Saves a parsed DMARC forensic report to OpenSearch + Saves a parsed DMARC forensic report to OpenSearch - Args: - forensic_report (OrderedDict): 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 - number_of_shards (int): The number of shards to use in the index - number_of_replicas (int): The number of replicas to use in the - index + Args: + forensic_report (OrderedDict): 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 + number_of_shards (int): The number of shards to use in the index + number_of_replicas (int): The number of replicas to use in the + index - Raises: - AlreadySaved + Raises: + AlreadySaved - """ + """ logger.info("Saving forensic report to OpenSearch") forensic_report = forensic_report.copy() sample_date = None @@ -643,12 +683,12 @@ existing = search.execute() if len(existing) > 0: - raise AlreadySaved("A forensic sample to {0} from {1} " - "with a subject of {2} and arrival date of {3} " - "already exists in " - "OpenSearch".format( - to_, from_, subject, arrival_date_human - )) + raise AlreadySaved( + "A forensic sample to {0} from {1} " + "with a subject of {2} and arrival date of {3} " + "already exists in " + "OpenSearch".format(to_, from_, subject, arrival_date_human) + ) parsed_sample = forensic_report["parsed_sample"] sample = _ForensicSampleDoc( @@ -658,25 +698,25 @@ date=sample_date, subject=forensic_report["parsed_sample"]["subject"], filename_safe_subject=parsed_sample["filename_safe_subject"], - body=forensic_report["parsed_sample"]["body"] + body=forensic_report["parsed_sample"]["body"], ) for address in forensic_report["parsed_sample"]["to"]: - sample.add_to(display_name=address["display_name"], - address=address["address"]) + sample.add_to(display_name=address["display_name"], address=address["address"]) for address in forensic_report["parsed_sample"]["reply_to"]: - sample.add_reply_to(display_name=address["display_name"], - address=address["address"]) + sample.add_reply_to( + display_name=address["display_name"], address=address["address"] + ) for address in forensic_report["parsed_sample"]["cc"]: - sample.add_cc(display_name=address["display_name"], - address=address["address"]) + sample.add_cc(display_name=address["display_name"], address=address["address"]) for address in forensic_report["parsed_sample"]["bcc"]: - sample.add_bcc(display_name=address["display_name"], - address=address["address"]) + sample.add_bcc(display_name=address["display_name"], address=address["address"]) for attachment in forensic_report["parsed_sample"]["attachments"]: - sample.add_attachment(filename=attachment["filename"], - content_type=attachment["mail_content_type"], - sha256=attachment["sha256"]) + sample.add_attachment( + filename=attachment["filename"], + content_type=attachment["mail_content_type"], + sha256=attachment["sha256"], + ) try: forensic_doc = _ForensicReportDoc( feedback_type=forensic_report["feedback_type"], @@ -692,12 +732,11 @@ source_country=forensic_report["source"]["country"], source_reverse_dns=forensic_report["source"]["reverse_dns"], source_base_domain=forensic_report["source"]["base_domain"], - authentication_mechanisms=forensic_report[ - "authentication_mechanisms"], + authentication_mechanisms=forensic_report["authentication_mechanisms"], auth_failure=forensic_report["auth_failure"], dkim_domain=forensic_report["dkim_domain"], original_rcpt_to=forensic_report["original_rcpt_to"], - sample=sample + sample=sample, ) index = "dmarc_forensic" @@ -710,26 +749,32 @@ else: index_date = arrival_date.strftime("%Y-%m-%d") index = "{0}-{1}".format(index, index_date) - index_settings = dict(number_of_shards=number_of_shards, - number_of_replicas=number_of_replicas) + index_settings = dict( + number_of_shards=number_of_shards, number_of_replicas=number_of_replicas + ) create_indexes([index], index_settings) forensic_doc.meta.index = index try: forensic_doc.save() except Exception as e: - raise OpenSearchError( - "OpenSearch error: {0}".format(e.__str__())) + raise OpenSearchError("OpenSearch error: {0}".format(e.__str__())) except KeyError as e: raise InvalidForensicReport( - "Forensic report missing required field: {0}".format(e.__str__()))
+ "Forensic report missing required field: {0}".format(e.__str__()) + )
-
[docs]def save_smtp_tls_report_to_opensearch(report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_smtp_tls_report_to_opensearch( + report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ Saves a parsed SMTP TLS report to OpenSearch @@ -747,10 +792,8 @@ logger.info("Saving aggregate report to OpenSearch") org_name = report["org_name"] report_id = report["report_id"] - begin_date = human_timestamp_to_datetime(report["begin_date"], - to_utc=True) - end_date = human_timestamp_to_datetime(report["end_date"], - to_utc=True) + begin_date = human_timestamp_to_datetime(report["begin_date"], to_utc=True) + end_date = human_timestamp_to_datetime(report["end_date"], to_utc=True) begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ") end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ") if monthly_indexes: @@ -779,15 +822,19 @@ try: existing = search.execute() except Exception as error_: - raise OpenSearchError("OpenSearch's search for existing report \ - error: {}".format(error_.__str__())) + raise OpenSearchError( + "OpenSearch's search for existing report \ + error: {}".format(error_.__str__()) + ) if len(existing) > 0: - raise AlreadySaved(f"An SMTP TLS report ID {report_id} from " - f" {org_name} with a date range of " - f"{begin_date_human} UTC to " - f"{end_date_human} UTC already " - "exists in OpenSearch") + raise AlreadySaved( + f"An SMTP TLS report ID {report_id} from " + f" {org_name} with a date range of " + f"{begin_date_human} UTC to " + f"{end_date_human} UTC already " + "exists in OpenSearch" + ) index = "smtp_tls" if index_suffix: @@ -795,8 +842,9 @@ if index_prefix: index = "{0}{1}".format(index_prefix, index) index = "{0}-{1}".format(index, index_date) - index_settings = dict(number_of_shards=number_of_shards, - number_of_replicas=number_of_replicas) + index_settings = dict( + number_of_shards=number_of_shards, number_of_replicas=number_of_replicas + ) smtp_tls_doc = _SMTPTLSFailureReportDoc( organization_name=report["organization_name"], @@ -804,10 +852,10 @@ date_begin=report["date_begin"], date_end=report["date_end"], contact_info=report["contact_info"], - report_id=report["report_id"] + report_id=report["report_id"], ) - for policy in report['policies']: + for policy in report["policies"]: policy_strings = None mx_host_patterns = None if "policy_strings" in policy: @@ -818,7 +866,7 @@ policy_domain=policy["policy_domain"], policy_type=policy["policy_type"], policy_string=policy_strings, - mx_host_patterns=mx_host_patterns + mx_host_patterns=mx_host_patterns, ) if "failure_details" in policy: failure_details = policy["failure_details"] @@ -826,11 +874,11 @@ additional_information_uri = None failure_reason_code = None if "receiving_mx_hostname" in failure_details: - receiving_mx_hostname = failure_details[ - "receiving_mx_hostname"] + receiving_mx_hostname = failure_details["receiving_mx_hostname"] if "additional_information_uri" in failure_details: additional_information_uri = failure_details[ - "additional_information_uri"] + "additional_information_uri" + ] if "failure_reason_code" in failure_details: failure_reason_code = failure_details["failure_reason_code"] policy_doc.add_failure_details( @@ -841,7 +889,7 @@ failed_session_count=failure_details["failed_session_count"], receiving_mx_hostname=receiving_mx_hostname, additional_information_uri=additional_information_uri, - failure_reason_code=failure_reason_code + failure_reason_code=failure_reason_code, ) smtp_tls_doc.policies.append(policy_doc) @@ -851,8 +899,8 @@ try: smtp_tls_doc.save() except Exception as e: - raise OpenSearchError( - "OpenSearch error: {0}".format(e.__str__()))
+ raise OpenSearchError("OpenSearch error: {0}".format(e.__str__()))
+
diff --git a/_modules/parsedmarc/splunk.html b/_modules/parsedmarc/splunk.html index 0cb9182..999aa3c 100644 --- a/_modules/parsedmarc/splunk.html +++ b/_modules/parsedmarc/splunk.html @@ -1,23 +1,20 @@ + + - + - parsedmarc.splunk — parsedmarc 8.15.0 documentation - - + parsedmarc.splunk — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -100,18 +94,24 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) -
[docs]class SplunkError(RuntimeError): +
+[docs] +class SplunkError(RuntimeError): """Raised when a Splunk API error occurs"""
-
[docs]class HECClient(object): + +
+[docs] +class HECClient(object): """A client for a Splunk HTTP Events Collector (HEC)""" # http://docs.splunk.com/Documentation/Splunk/latest/Data/AboutHEC # http://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTinput#services.2Fcollector - def __init__(self, url, access_token, index, - source="parsedmarc", verify=True, timeout=60): + def __init__( + self, url, access_token, index, source="parsedmarc", verify=True, timeout=60 + ): """ Initializes the HECClient @@ -125,8 +125,9 @@ data before giving up """ url = urlparse(url) - self.url = "{0}://{1}/services/collector/event/1.0".format(url.scheme, - url.netloc) + self.url = "{0}://{1}/services/collector/event/1.0".format( + url.scheme, url.netloc + ) self.access_token = access_token.lstrip("Splunk ") self.index = index self.host = socket.getfqdn() @@ -134,15 +135,16 @@ self.session = requests.Session() self.timeout = timeout self.session.verify = verify - self._common_data = dict(host=self.host, source=self.source, - index=self.index) + self._common_data = dict(host=self.host, source=self.source, index=self.index) self.session.headers = { "User-Agent": "parsedmarc/{0}".format(__version__), - "Authorization": "Splunk {0}".format(self.access_token) + "Authorization": "Splunk {0}".format(self.access_token), } -
[docs] def save_aggregate_reports_to_splunk(self, aggregate_reports): +
+[docs] + def save_aggregate_reports_to_splunk(self, aggregate_reports): """ Saves aggregate DMARC reports to Splunk @@ -166,36 +168,26 @@ for metadata in report["report_metadata"]: new_report[metadata] = report["report_metadata"][metadata] new_report["published_policy"] = report["policy_published"] - new_report["source_ip_address"] = record["source"][ - "ip_address"] + new_report["source_ip_address"] = record["source"]["ip_address"] new_report["source_country"] = record["source"]["country"] - new_report["source_reverse_dns"] = record["source"][ - "reverse_dns"] - new_report["source_base_domain"] = record["source"][ - "base_domain"] + new_report["source_reverse_dns"] = record["source"]["reverse_dns"] + new_report["source_base_domain"] = record["source"]["base_domain"] new_report["source_type"] = record["source"]["type"] new_report["source_name"] = record["source"]["name"] new_report["message_count"] = record["count"] - new_report["disposition"] = record["policy_evaluated"][ - "disposition" - ] + new_report["disposition"] = record["policy_evaluated"]["disposition"] new_report["spf_aligned"] = record["alignment"]["spf"] new_report["dkim_aligned"] = record["alignment"]["dkim"] new_report["passed_dmarc"] = record["alignment"]["dmarc"] - new_report["header_from"] = record["identifiers"][ - "header_from"] - new_report["envelope_from"] = record["identifiers"][ - "envelope_from"] + new_report["header_from"] = record["identifiers"]["header_from"] + new_report["envelope_from"] = record["identifiers"]["envelope_from"] if "dkim" in record["auth_results"]: - new_report["dkim_results"] = record["auth_results"][ - "dkim"] + new_report["dkim_results"] = record["auth_results"]["dkim"] if "spf" in record["auth_results"]: - new_report["spf_results"] = record["auth_results"][ - "spf"] + new_report["spf_results"] = record["auth_results"]["spf"] data["sourcetype"] = "dmarc:aggregate" - timestamp = human_timestamp_to_unix_timestamp( - new_report["begin_date"]) + timestamp = human_timestamp_to_unix_timestamp(new_report["begin_date"]) data["time"] = timestamp data["event"] = new_report.copy() json_str += "{0}\n".format(json.dumps(data)) @@ -203,15 +195,17 @@ if not self.session.verify: logger.debug("Skipping certificate verification for Splunk HEC") try: - response = self.session.post(self.url, data=json_str, - timeout=self.timeout) + response = self.session.post(self.url, data=json_str, timeout=self.timeout) response = response.json() except Exception as e: raise SplunkError(e.__str__()) if response["code"] != 0: raise SplunkError(response["text"])
-
[docs] def save_forensic_reports_to_splunk(self, forensic_reports): + +
+[docs] + def save_forensic_reports_to_splunk(self, forensic_reports): """ Saves forensic DMARC reports to Splunk @@ -230,8 +224,7 @@ for report in forensic_reports: data = self._common_data.copy() data["sourcetype"] = "dmarc:forensic" - timestamp = human_timestamp_to_unix_timestamp( - report["arrival_date_utc"]) + timestamp = human_timestamp_to_unix_timestamp(report["arrival_date_utc"]) data["time"] = timestamp data["event"] = report.copy() json_str += "{0}\n".format(json.dumps(data)) @@ -239,15 +232,17 @@ if not self.session.verify: logger.debug("Skipping certificate verification for Splunk HEC") try: - response = self.session.post(self.url, data=json_str, - timeout=self.timeout) + response = self.session.post(self.url, data=json_str, timeout=self.timeout) response = response.json() except Exception as e: raise SplunkError(e.__str__()) if response["code"] != 0: raise SplunkError(response["text"])
-
[docs] def save_smtp_tls_reports_to_splunk(self, reports): + +
+[docs] + def save_smtp_tls_reports_to_splunk(self, reports): """ Saves aggregate DMARC reports to Splunk @@ -267,8 +262,7 @@ json_str = "" for report in reports: data["sourcetype"] = "smtp:tls" - timestamp = human_timestamp_to_unix_timestamp( - report["begin_date"]) + timestamp = human_timestamp_to_unix_timestamp(report["begin_date"]) data["time"] = timestamp data["event"] = report.copy() json_str += "{0}\n".format(json.dumps(data)) @@ -276,13 +270,14 @@ if not self.session.verify: logger.debug("Skipping certificate verification for Splunk HEC") try: - response = self.session.post(self.url, data=json_str, - timeout=self.timeout) + response = self.session.post(self.url, data=json_str, timeout=self.timeout) response = response.json() except Exception as e: raise SplunkError(e.__str__()) if response["code"] != 0: - raise SplunkError(response["text"])
+ raise SplunkError(response["text"])
+
+
diff --git a/_modules/parsedmarc/utils.html b/_modules/parsedmarc/utils.html index 91259fd..f8504ac 100644 --- a/_modules/parsedmarc/utils.html +++ b/_modules/parsedmarc/utils.html @@ -1,23 +1,20 @@ + + - + - parsedmarc.utils — parsedmarc 8.15.0 documentation - - + parsedmarc.utils — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -126,22 +120,30 @@ import parsedmarc.resources.maps -parenthesis_regex = re.compile(r'\s*\(.*\)\s*') +parenthesis_regex = re.compile(r"\s*\(.*\)\s*") null_file = open(os.devnull, "w") mailparser_logger = logging.getLogger("mailparser") mailparser_logger.setLevel(logging.CRITICAL) -
[docs]class EmailParserError(RuntimeError): +
+[docs] +class EmailParserError(RuntimeError): """Raised when an error parsing the email occurs"""
-
[docs]class DownloadError(RuntimeError): + +
+[docs] +class DownloadError(RuntimeError): """Raised when an error occurs when downloading a file"""
-
[docs]def decode_base64(data): + +
+[docs] +def decode_base64(data): """ Decodes a base64 string, with padding being optional @@ -155,11 +157,14 @@ data = bytes(data, encoding="ascii") missing_padding = len(data) % 4 if missing_padding != 0: - data += b'=' * (4 - missing_padding) + data += b"=" * (4 - missing_padding) return base64.b64decode(data)
-
[docs]def get_base_domain(domain): + +
+[docs] +def get_base_domain(domain): """ Gets the base domain name for the given domain @@ -178,7 +183,10 @@ return psl.privatesuffix(domain)
-
[docs]def query_dns(domain, record_type, cache=None, nameservers=None, timeout=2.0): + +
+[docs] +def query_dns(domain, record_type, cache=None, nameservers=None, timeout=2.0): """ Queries DNS @@ -204,31 +212,45 @@ resolver = dns.resolver.Resolver() timeout = float(timeout) if nameservers is None: - nameservers = ["1.1.1.1", "1.0.0.1", - "2606:4700:4700::1111", "2606:4700:4700::1001", - ] + nameservers = [ + "1.1.1.1", + "1.0.0.1", + "2606:4700:4700::1111", + "2606:4700:4700::1001", + ] 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_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] + 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 return records
-
[docs]def get_reverse_dns(ip_address, cache=None, nameservers=None, timeout=2.0): + +
+[docs] +def get_reverse_dns(ip_address, cache=None, nameservers=None, timeout=2.0): """ Resolves an IP address to a hostname using a reverse DNS query @@ -245,9 +267,9 @@ hostname = None try: address = dns.reversename.from_address(ip_address) - hostname = query_dns(address, "PTR", cache=cache, - nameservers=nameservers, - timeout=timeout)[0] + hostname = query_dns( + address, "PTR", cache=cache, nameservers=nameservers, timeout=timeout + )[0] except dns.exception.DNSException as e: logger.warning(f"get_reverse_dns({ip_address}) exception: {e}") @@ -256,7 +278,10 @@ return hostname
-
[docs]def timestamp_to_datetime(timestamp): + +
+[docs] +def timestamp_to_datetime(timestamp): """ Converts a UNIX/DMARC timestamp to a Python ``datetime`` object @@ -269,7 +294,10 @@ return datetime.fromtimestamp(int(timestamp))
-
[docs]def timestamp_to_human(timestamp): + +
+[docs] +def timestamp_to_human(timestamp): """ Converts a UNIX/DMARC timestamp to a human-readable string @@ -282,7 +310,10 @@ return timestamp_to_datetime(timestamp).strftime("%Y-%m-%d %H:%M:%S")
-
[docs]def human_timestamp_to_datetime(human_timestamp, to_utc=False): + +
+[docs] +def human_timestamp_to_datetime(human_timestamp, to_utc=False): """ Converts a human-readable timestamp into a Python ``datetime`` object @@ -301,7 +332,10 @@ return dt.astimezone(timezone.utc) if to_utc else dt
-
[docs]def human_timestamp_to_unix_timestamp(human_timestamp): + +
+[docs] +def human_timestamp_to_unix_timestamp(human_timestamp): """ Converts a human-readable timestamp into a UNIX timestamp @@ -315,7 +349,10 @@ return human_timestamp_to_datetime(human_timestamp).timestamp()
-
[docs]def get_ip_address_country(ip_address, db_path=None): + +
+[docs] +def get_ip_address_country(ip_address, db_path=None): """ Returns the ISO code for the country associated with the given IPv4 or IPv6 address @@ -344,9 +381,11 @@ if db_path is not None: if os.path.isfile(db_path) is False: db_path = None - logger.warning(f"No file exists at {db_path}. Falling back to an " - "included copy of the IPDB IP to Country " - "Lite database.") + logger.warning( + f"No file exists at {db_path}. Falling back to an " + "included copy of the IPDB IP to Country " + "Lite database." + ) if db_path is None: for system_path in db_paths: @@ -355,12 +394,12 @@ break if db_path is None: - with pkg_resources.path(parsedmarc.resources.dbip, - "dbip-country-lite.mmdb") as path: + with pkg_resources.path( + parsedmarc.resources.dbip, "dbip-country-lite.mmdb" + ) as path: db_path = path - db_age = datetime.now() - datetime.fromtimestamp( - os.stat(db_path).st_mtime) + db_age = datetime.now() - datetime.fromtimestamp(os.stat(db_path).st_mtime) if db_age > timedelta(days=30): logger.warning("IP database is more than a month old") @@ -376,12 +415,17 @@ return country
-
[docs]def get_service_from_reverse_dns_base_domain(base_domain, - always_use_local_file=False, - local_file_path=None, - url=None, - offline=False, - reverse_dns_map=None): + +
+[docs] +def get_service_from_reverse_dns_base_domain( + base_domain, + always_use_local_file=False, + local_file_path=None, + url=None, + offline=False, + reverse_dns_map=None, +): """ Returns the service name of a given base domain name from reverse DNS. @@ -397,28 +441,27 @@ If the service is unknown, the name will be 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"]) + reverse_dns_map[key] = dict(name=row["name"], type=row["type"]) base_domain = base_domain.lower().strip() if url is None: - url = ("https://raw.githubusercontent.com/domainaware" - "/parsedmarc/master/parsedmarc/" - "resources/maps/base_reverse_dns_map.csv") + url = ( + "https://raw.githubusercontent.com/domainaware" + "/parsedmarc/master/parsedmarc/" + "resources/maps/base_reverse_dns_map.csv" + ) if reverse_dns_map is None: reverse_dns_map = dict() 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) == 0: try: - logger.debug(f"Trying to fetch " - f"reverse DNS map from {url}...") + logger.debug(f"Trying to fetch " f"reverse DNS map from {url}...") csv_file.write(requests.get(url).text) csv_file.seek(0) load_csv(csv_file) @@ -426,8 +469,9 @@ logger.warning(f"Failed to fetch reverse DNS map: {e}") if len(reverse_dns_map) == 0: logger.info("Loading included reverse DNS map...") - with pkg_resources.path(parsedmarc.resources.maps, - "base_reverse_dns_map.csv") as path: + with pkg_resources.path( + parsedmarc.resources.maps, "base_reverse_dns_map.csv" + ) as path: if local_file_path is not None: path = local_file_path with open(path) as csv_file: @@ -440,15 +484,21 @@ return service
-
[docs]def get_ip_address_info(ip_address, - ip_db_path=None, - reverse_dns_map_path=None, - always_use_local_files=False, - reverse_dns_map_url=None, - cache=None, - reverse_dns_map=None, - offline=False, - nameservers=None, timeout=2.0): + +
+[docs] +def get_ip_address_info( + ip_address, + ip_db_path=None, + reverse_dns_map_path=None, + always_use_local_files=False, + reverse_dns_map_url=None, + cache=None, + reverse_dns_map=None, + offline=False, + nameservers=None, + timeout=2.0, +): """ Returns reverse DNS and country information for the given IP address @@ -480,9 +530,9 @@ if offline: reverse_dns = None else: - reverse_dns = get_reverse_dns(ip_address, - nameservers=nameservers, - timeout=timeout) + reverse_dns = get_reverse_dns( + ip_address, nameservers=nameservers, timeout=timeout + ) country = get_ip_address_country(ip_address, db_path=ip_db_path) info["country"] = country info["reverse_dns"] = reverse_dns @@ -498,7 +548,8 @@ local_file_path=reverse_dns_map_path, url=reverse_dns_map_url, always_use_local_file=always_use_local_files, - reverse_dns_map=reverse_dns_map) + reverse_dns_map=reverse_dns_map, + ) info["base_domain"] = base_domain info["type"] = service["type"] info["name"] = service["name"] @@ -512,6 +563,7 @@ return info
+ def parse_email_address(original_address): if original_address[0] == "": display_name = None @@ -525,13 +577,19 @@ local = address_parts[0].lower() domain = address_parts[-1].lower() - return OrderedDict([("display_name", display_name), - ("address", address), - ("local", local), - ("domain", domain)]) + return OrderedDict( + [ + ("display_name", display_name), + ("address", address), + ("local", local), + ("domain", domain), + ] + ) -
[docs]def get_filename_safe_string(string): +
+[docs] +def get_filename_safe_string(string): """ Converts a string to a string that is safe for a filename @@ -541,8 +599,7 @@ Returns: str: A string safe for a filename """ - invalid_filename_chars = ['\\', '/', ':', '"', '*', '?', '|', '\n', - '\r'] + invalid_filename_chars = ["\\", "/", ":", '"', "*", "?", "|", "\n", "\r"] if string is None: string = "None" for char in invalid_filename_chars: @@ -554,7 +611,10 @@ return string
-
[docs]def is_mbox(path): + +
+[docs] +def is_mbox(path): """ Checks if the given content is an MBOX mailbox file @@ -575,7 +635,10 @@ return _is_mbox
-
[docs]def is_outlook_msg(content): + +
+[docs] +def is_outlook_msg(content): """ Checks if the given content is an Outlook msg OLE/MSG file @@ -586,10 +649,14 @@ bool: A flag that indicates if the file is an Outlook MSG file """ return isinstance(content, bytes) and content.startswith( - b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1")
+ b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" + )
-
[docs]def convert_outlook_msg(msg_bytes): + +
+[docs] +def convert_outlook_msg(msg_bytes): """ Uses the ``msgconvert`` Perl utility to convert an Outlook MS file to standard RFC 822 format @@ -608,14 +675,16 @@ with open("sample.msg", "wb") as msg_file: msg_file.write(msg_bytes) try: - subprocess.check_call(["msgconvert", "sample.msg"], - stdout=null_file, stderr=null_file) + subprocess.check_call( + ["msgconvert", "sample.msg"], stdout=null_file, stderr=null_file + ) eml_path = "sample.eml" with open(eml_path, "rb") as eml_file: rfc822 = eml_file.read() except FileNotFoundError: raise EmailParserError( - "Failed to convert Outlook MSG: msgconvert utility not found") + "Failed to convert Outlook MSG: msgconvert utility not found" + ) finally: os.chdir(orig_dir) shutil.rmtree(tmp_dir) @@ -623,7 +692,10 @@ return rfc822
-
[docs]def parse_email(data, strip_attachment_payloads=False): + +
+[docs] +def parse_email(data, strip_attachment_payloads=False): """ A simplified email parser @@ -650,8 +722,7 @@ if received["date_utc"] is None: del received["date_utc"] else: - received["date_utc"] = received["date_utc"].replace("T", - " ") + received["date_utc"] = received["date_utc"].replace("T", " ") if "from" not in parsed_email: if "From" in parsed_email["headers"]: @@ -667,33 +738,36 @@ else: parsed_email["date"] = None if "reply_to" in parsed_email: - parsed_email["reply_to"] = list(map(lambda x: parse_email_address(x), - parsed_email["reply_to"])) + parsed_email["reply_to"] = list( + map(lambda x: parse_email_address(x), parsed_email["reply_to"]) + ) else: parsed_email["reply_to"] = [] if "to" in parsed_email: - parsed_email["to"] = list(map(lambda x: parse_email_address(x), - parsed_email["to"])) + parsed_email["to"] = list( + map(lambda x: parse_email_address(x), parsed_email["to"]) + ) else: parsed_email["to"] = [] if "cc" in parsed_email: - parsed_email["cc"] = list(map(lambda x: parse_email_address(x), - parsed_email["cc"])) + parsed_email["cc"] = list( + map(lambda x: parse_email_address(x), parsed_email["cc"]) + ) else: parsed_email["cc"] = [] if "bcc" in parsed_email: - parsed_email["bcc"] = list(map(lambda x: parse_email_address(x), - parsed_email["bcc"])) + parsed_email["bcc"] = list( + map(lambda x: parse_email_address(x), parsed_email["bcc"]) + ) else: parsed_email["bcc"] = [] if "delivered_to" in parsed_email: parsed_email["delivered_to"] = list( - map(lambda x: parse_email_address(x), - parsed_email["delivered_to"]) + map(lambda x: parse_email_address(x), parsed_email["delivered_to"]) ) if "attachments" not in parsed_email: @@ -710,9 +784,7 @@ payload = str.encode(payload) attachment["sha256"] = hashlib.sha256(payload).hexdigest() except Exception as e: - logger.debug("Unable to decode attachment: {0}".format( - e.__str__() - )) + logger.debug("Unable to decode attachment: {0}".format(e.__str__())) if strip_attachment_payloads: for attachment in parsed_email["attachments"]: if "payload" in attachment: @@ -722,12 +794,14 @@ parsed_email["subject"] = None parsed_email["filename_safe_subject"] = get_filename_safe_string( - parsed_email["subject"]) + parsed_email["subject"] + ) if "body" not in parsed_email: parsed_email["body"] = None return parsed_email
+
diff --git a/_sources/usage.md.txt b/_sources/usage.md.txt index e59f2c3..3bd0487 100644 --- a/_sources/usage.md.txt +++ b/_sources/usage.md.txt @@ -103,6 +103,12 @@ port = 514 host = logger port = 12201 mode = tcp + +[webhook] +aggregate_url = https://aggregate_url.example.com +forensic_url = https://forensic_url.example.com +smtp_tls_url = https://smtp_tls_url.example.com +timeout = 60 ``` The full set of configuration options are: @@ -130,6 +136,8 @@ The full set of configuration options are: - `reverse_dns_map_url` - Overrides the default download URL for the reverse DNS map - `nameservers` - str: A comma separated list of DNS resolvers (Default: `[Cloudflare's public resolvers]`) + - `dns_test_address` - str: a dummy address used for DNS pre-flight checks + (Default: 1.1.1.1) - `dns_timeout` - float: DNS timeout period - `debug` - bool: Print debugging messages - `silent` - bool: Only print errors (Default: `True`) @@ -355,6 +363,16 @@ The full set of configuration options are: - `port` - int: The port to use - `mode` - str: The GELF transport type to use. Valid modes: `tcp`, `udp`, `tls` +- `maildir` + - `reports_folder` - str: Full path for mailbox maidir location (Default: `INBOX`) + - `maildir_create` - bool: Create maildir if not present (Default: False) + +- `webhook` - Post the individual reports to a webhook url with the report as the JSON body + - `aggregate_url` - str: URL of the webhook which should receive the aggregate reports + - `forensic_url` - str: URL of the webhook which should receive the forensic reports + - `smtp_tls_url` - str: URL of the webhook which should receive the smtp_tls reports + - `timeout` - int: Interval in which the webhook call should timeout + :::{warning} It is **strongly recommended** to **not** use the `nameservers` setting. By default, `parsedmarc` uses diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js index 8549469..8141580 100644 --- a/_static/_sphinx_javascript_frameworks_compat.js +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -1,20 +1,9 @@ -/* - * _sphinx_javascript_frameworks_compat.js - * ~~~~~~~~~~ - * - * Compatability shim for jQuery and underscores.js. - * - * WILL BE REMOVED IN Sphinx 6.0 - * xref RemovedInSphinx60Warning +/* Compatability shim for jQuery and underscores.js. * + * Copyright Sphinx contributors + * Released under the two clause BSD licence */ -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - - /** * small helper function to urldecode strings * diff --git a/_static/basic.css b/_static/basic.css index eeb0519..7ebbd6d 100644 --- a/_static/basic.css +++ b/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { @@ -236,17 +225,11 @@ div.body p, div.body dd, div.body li, div.body blockquote { a.headerlink { visibility: hidden; } -a.brackets:before, -span.brackets > a:before{ - content: "["; -} -a.brackets:after, -span.brackets > a:after { - content: "]"; +a:visited { + color: #551A8B; } - h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, @@ -334,11 +317,17 @@ aside.sidebar { p.sidebar-title { font-weight: bold; } + +nav.contents, +aside.topic, div.admonition, div.topic, blockquote { clear: left; } /* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, div.topic { border: 1px solid #ccc; padding: 7px; @@ -377,6 +366,8 @@ div.body p.centered { div.sidebar > :last-child, aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, div.topic > :last-child, div.admonition > :last-child { margin-bottom: 0; @@ -384,6 +375,8 @@ div.admonition > :last-child { div.sidebar::after, aside.sidebar::after, +nav.contents::after, +aside.topic::after, div.topic::after, div.admonition::after, blockquote::after { @@ -608,19 +601,27 @@ ol.simple p, ul.simple p { margin-bottom: 0; } -dl.footnote > dt, -dl.citation > dt { - float: left; - margin-right: 0.5em; -} -dl.footnote > dd, -dl.citation > dd { +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { margin-bottom: 0em; } - -dl.footnote > dd:after, -dl.citation > dd:after { +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { content: ""; clear: both; } @@ -636,10 +637,6 @@ dl.field-list > dt { padding-left: 0.5em; padding-right: 5px; } -dl.field-list > dt:after { - content: ":"; -} - dl.field-list > dd { padding-left: 0.5em; @@ -666,6 +663,16 @@ dd { margin-left: 30px; } +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; @@ -734,6 +741,14 @@ abbr, acronym { cursor: help; } +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + /* -- code displays --------------------------------------------------------- */ pre { diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css index c718cee..88ba55b 100644 --- a/_static/css/badge_only.css +++ b/_static/css/badge_only.css @@ -1 +1 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/_static/css/theme.css b/_static/css/theme.css index 19a446a..0f14f10 100644 --- a/_static/css/theme.css +++ b/_static/css/theme.css @@ -1,4 +1,4 @@ html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js index 527b876..0398ebb 100644 --- a/_static/doctools.js +++ b/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/_static/documentation_options.js b/_static/documentation_options.js index 58bbb4c..3954403 100644 --- a/_static/documentation_options.js +++ b/_static/documentation_options.js @@ -1,6 +1,5 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '8.15.0', +const DOCUMENTATION_OPTIONS = { + VERSION: '8.15.1', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/_static/js/versions.js b/_static/js/versions.js new file mode 100644 index 0000000..818bc99 --- /dev/null +++ b/_static/js/versions.js @@ -0,0 +1,224 @@ +const themeFlyoutDisplay = "hidden"; +const themeVersionSelector = "True"; +const themeLanguageSelector = "True"; + +if (themeFlyoutDisplay === "attached") { + function renderLanguages(config) { + if (!config.projects.translations.length) { + return ""; + } + + const languagesHTML = ` +
+
Languages
+ ${config.projects.translations + .map( + (translation) => ` +
+ ${translation.language.code} +
+ `, + ) + .join("\n")} +
+ `; + return languagesHTML; + } + + function renderVersions(config) { + if (!config.versions.active.length) { + return ""; + } + const versionsHTML = ` +
+
Versions
+ ${config.versions.active + .map( + (version) => ` +
+ ${version.slug} +
+ `, + ) + .join("\n")} +
+ `; + return versionsHTML; + } + + function renderDownloads(config) { + if (!Object.keys(config.versions.current.downloads).length) { + return ""; + } + const downloadsNameDisplay = { + pdf: "PDF", + epub: "Epub", + htmlzip: "HTML", + }; + + const downloadsHTML = ` +
+
Downloads
+ ${Object.entries(config.versions.current.downloads) + .map( + ([name, url]) => ` +
+ ${downloadsNameDisplay[name]} +
+ `, + ) + .join("\n")} +
+ `; + return downloadsHTML; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const flyout = ` +
+ + Read the Docs + v: ${config.versions.current.slug} + + +
+
+ ${renderLanguages(config)} + ${renderVersions(config)} + ${renderDownloads(config)} +
+
On Read the Docs
+
+ Project Home +
+
+ Builds +
+
+ Downloads +
+
+
+
Search
+
+ + +
+ +
+
+ + Hosted by Read the Docs + +
+
+ `; + + // Inject the generated flyout into the body HTML element. + document.body.insertAdjacentHTML("beforeend", flyout); + + // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. + document + .querySelector("#flyout-search-form") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); + }) +} + +if (themeLanguageSelector || themeVersionSelector) { + function onSelectorSwitch(event) { + const option = event.target.selectedIndex; + const item = event.target.options[option]; + window.location.href = item.dataset.url; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const versionSwitch = document.querySelector( + "div.switch-menus > div.version-switch", + ); + if (themeVersionSelector) { + let versions = config.versions.active; + if (config.versions.current.hidden || config.versions.current.type === "external") { + versions.unshift(config.versions.current); + } + const versionSelect = ` + + `; + + versionSwitch.innerHTML = versionSelect; + versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + + const languageSwitch = document.querySelector( + "div.switch-menus > div.language-switch", + ); + + if (themeLanguageSelector) { + if (config.projects.translations.length) { + // Add the current language to the options on the selector + let languages = config.projects.translations.concat( + config.projects.current, + ); + languages = languages.sort((a, b) => + a.language.name.localeCompare(b.language.name), + ); + + const languageSelect = ` + + `; + + languageSwitch.innerHTML = languageSelect; + languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + else { + languageSwitch.remove(); + } + } + }); +} + +document.addEventListener("readthedocs-addons-data-ready", function (event) { + // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. + document + .querySelector("[role='search'] input") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); +}); \ No newline at end of file diff --git a/_static/language_data.js b/_static/language_data.js index 2e22b06..c7fe6c6 100644 --- a/_static/language_data.js +++ b/_static/language_data.js @@ -1,19 +1,12 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; -/* Non-minified version is copied as a separate JS file, is available */ +/* Non-minified version is copied as a separate JS file, if available */ /** * Porter Stemmer diff --git a/_static/searchtools.js b/_static/searchtools.js index e89e34d..2c774d1 100644 --- a/_static/searchtools.js +++ b/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -57,16 +58,20 @@ const _removeChildren = (element) => { const _escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -const _displayItem = (item, searchTerms) => { +const _displayItem = (item, searchTerms, highlightTerms) => { const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -75,28 +80,35 @@ const _displayItem = (item, searchTerms) => { if (dirname.match(/\/index\/$/)) dirname = dirname.substring(0, dirname.length - 6); else if (dirname === "index/") dirname = ""; - requestUrl = docUrlRoot + dirname; + requestUrl = contentRoot + dirname; linkUrl = requestUrl; } else { // normal html builders - requestUrl = docUrlRoot + docName + docFileSuffix; + requestUrl = contentRoot + docName + docFileSuffix; linkUrl = docName + docLinkSuffix; } let linkEl = listItem.appendChild(document.createElement("a")); linkEl.href = linkUrl + anchor; linkEl.dataset.score = score; linkEl.innerHTML = title; - if (descr) + if (descr) { listItem.appendChild(document.createElement("span")).innerHTML = " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } else if (showSearchSummary) fetch(requestUrl) .then((responseData) => responseData.text()) .then((data) => { if (data) listItem.appendChild( - Search.makeSearchSummary(data, searchTerms) + Search.makeSearchSummary(data, searchTerms, anchor) ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); }); Search.output.appendChild(listItem); }; @@ -108,27 +120,46 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - `Search finished, found ${resultCount} page(s) matching the search query.` - ); + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, + ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( results, resultCount, - searchTerms + searchTerms, + highlightTerms, ) => { // results left, load the summary and display it // this is intended to be dynamic (don't sub resultsCount) if (results.length) { - _displayItem(results.pop(), searchTerms); + _displayItem(results.pop(), searchTerms, highlightTerms); setTimeout( - () => _displayNextItem(results, resultCount, searchTerms), + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), 5 ); } // search finished, update title and status message else _finishSearch(resultCount); }; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; /** * Default splitQuery function. Can be overridden in ``sphinx.search`` with a @@ -152,13 +183,26 @@ const Search = { _queued_query: null, _pulse_status: -1, - htmlToText: (htmlString) => { + htmlToText: (htmlString, anchor) => { const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent !== undefined) return docContent.textContent; + if (docContent) return docContent.textContent; + console.warn( - "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." ); return ""; }, @@ -211,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -231,16 +276,7 @@ const Search = { else Search.deferQuery(query); }, - /** - * execute search (requires search index to be loaded) - */ - query: (query) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - + _parseQuery: (query) => { // stem the search terms and add them to the correct list const stemmer = new Stemmer(); const searchTerms = new Set(); @@ -276,22 +312,40 @@ const Search = { // console.info("required: ", [...searchTerms]); // console.info("excluded: ", [...excludedTerms]); - // array of [docname, title, anchor, descr, score, filename] - let results = []; + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. + const normalResults = []; + const nonMainIndexResults = []; + _removeChildren(document.getElementById("search-progress")); - const queryLower = query.toLowerCase(); + const queryLower = query.toLowerCase().trim(); for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { for (const [file, id] of foundTitles) { - let score = Math.round(100 * queryLower.length / title.length) - results.push([ + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ docNames[file], titles[file] !== title ? `${titles[file]} > ${title}` : title, id !== null ? "#" + id : "", null, - score, + score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -300,46 +354,48 @@ const Search = { // search for explicit entries in index directives for (const [entry, foundEntries] of Object.entries(indexEntries)) { if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id] of foundEntries) { - let score = Math.round(100 * queryLower.length / entry.length) - results.push([ + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ docNames[file], titles[file], id ? "#" + id : "", null, score, filenames[file], - ]); + SearchResultKind.index, + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } } } } // lookup as object objectTerms.forEach((term) => - results.push(...Search.performObjectSearch(term, objectTerms)) + normalResults.push(...Search.performObjectSearch(term, objectTerms)) ); // lookup as search terms in fulltext - results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); // let the scorer override scores with a custom scoring function - if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } - // now sort the results by score (in opposite order of appearance, since the - // display function below uses pop() to retrieve items) and then - // alphabetically - results.sort((a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; - }); + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; // remove duplicate search results // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept @@ -353,14 +409,19 @@ const Search = { return acc; }, []); - results = results.reverse(); + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); // for debugging //Search.lastresults = results.slice(); // a copy // console.info("search results:", Search.lastresults); // print the results - _displayNextItem(results, results.length, searchTerms); + _displayNextItem(results, results.length, searchTerms, highlightTerms); }, /** @@ -424,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -458,14 +520,18 @@ const Search = { // add support for partial matches if (word.length > 2) { const escapedWord = _escapeRegExp(word); - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord) && !terms[word]) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord) && !titleTerms[word]) - arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); - }); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } } // no match but word was a required one @@ -488,9 +554,8 @@ const Search = { // create the mapping files.forEach((file) => { - if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) - fileMap.get(file).push(word); - else fileMap.set(file, [word]); + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); }); }); @@ -531,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; @@ -541,8 +607,8 @@ const Search = { * search summary for a given text. keywords is a list * of stemmed words. */ - makeSearchSummary: (htmlText, keywords) => { - const text = Search.htmlToText(htmlText); + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); if (text === "") return null; const textLower = text.toLowerCase(); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js index aae669d..8a96c69 100644 --- a/_static/sphinx_highlight.js +++ b/_static/sphinx_highlight.js @@ -29,14 +29,19 @@ const _highlight = (node, addItems, text, className) => { } span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); parent.insertBefore( span, parent.insertBefore( - document.createTextNode(val.substr(pos + text.length)), + rest, node.nextSibling ) ); node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); if (isInSVG) { const rect = document.createElementNS( @@ -140,5 +145,10 @@ const SphinxHighlight = { }, }; -_ready(SphinxHighlight.highlightSearchWords); -_ready(SphinxHighlight.initEscapeListener); +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/api.html b/api.html index 2ac493c..59df3a4 100644 --- a/api.html +++ b/api.html @@ -1,24 +1,21 @@ + + - + - + - API reference — parsedmarc 8.15.0 documentation - - + API reference — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -36,9 +33,6 @@ parsedmarc -
- 8.15.0 -
@@ -171,46 +165,46 @@
-

API reference

+

API reference

-

parsedmarc

+

parsedmarc

A Python package for parsing DMARC reports

-exception parsedmarc.InvalidAggregateReport[source]
+exception parsedmarc.InvalidAggregateReport[source]

Raised when an invalid DMARC aggregate report is encountered

-exception parsedmarc.InvalidDMARCReport[source]
+exception parsedmarc.InvalidDMARCReport[source]

Raised when an invalid DMARC report is encountered

-exception parsedmarc.InvalidForensicReport[source]
+exception parsedmarc.InvalidForensicReport[source]

Raised when an invalid DMARC forensic report is encountered

-exception parsedmarc.InvalidSMTPTLSReport[source]
+exception parsedmarc.InvalidSMTPTLSReport[source]

Raised when an invalid SMTP TLS report is encountered

-exception parsedmarc.ParserError[source]
+exception parsedmarc.ParserError[source]

Raised whenever the parser fails for some reason

-parsedmarc.email_results(results, host, mail_from, mail_to, mail_cc=None, mail_bcc=None, port=0, require_encryption=False, verify=True, username=None, password=None, subject=None, attachment_filename=None, message=None)[source]
+parsedmarc.email_results(results, host, mail_from, mail_to, mail_cc=None, mail_bcc=None, port=0, require_encryption=False, verify=True, username=None, password=None, subject=None, attachment_filename=None, message=None)[source]

Emails parsing results as a zip file

-
Parameters
+
Parameters:
  • results (OrderedDict) – Parsing results

  • host – Mail server hostname or IP address

  • @@ -233,20 +227,20 @@
    -parsedmarc.extract_report(content)[source]
    +parsedmarc.extract_report(content)[source]

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

    -
    Parameters
    +
    Parameters:
    • content – report file as a base64-encoded string, file-like object or

    • -
    • bytes.

    • +
    • bytes.

    -
    Returns
    +
    Returns:

    The extracted text

    -
    Return type
    +
    Return type:

    str

    @@ -254,16 +248,16 @@ file-like object, or bytes.

    -parsedmarc.extract_report_from_file_path(file_path)[source]
    +parsedmarc.extract_report_from_file_path(file_path)[source]

    Extracts report from a file at the given file_path

    -parsedmarc.get_dmarc_reports_from_mailbox(connection: MailboxConnection, reports_folder='INBOX', archive_folder='Archive', delete=False, test=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, nameservers=None, dns_timeout=6.0, strip_attachment_payloads=False, results=None, batch_size=10, create_folders=True)[source]
    +parsedmarc.get_dmarc_reports_from_mailbox(connection: MailboxConnection, reports_folder='INBOX', archive_folder='Archive', delete=False, test=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, nameservers=None, dns_timeout=6.0, strip_attachment_payloads=False, results=None, batch_size=10, create_folders=True)[source]

    Fetches and parses DMARC reports from a mailbox

    -
    Parameters
    +
    Parameters:
    • connection – A Mailbox connection object

    • reports_folder – The folder where reports can be found

    • @@ -286,10 +280,10 @@ forensic report results

      (not used in watch)

    -
    Returns
    +
    Returns:

    Lists of aggregate_reports and forensic_reports

    -
    Return type
    +
    Return type:

    OrderedDict

    @@ -297,11 +291,11 @@ forensic report results

    -parsedmarc.get_dmarc_reports_from_mbox(input_, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False)[source]
    +parsedmarc.get_dmarc_reports_from_mbox(input_, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False)[source]

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

    -
    Parameters
    +
    Parameters:
    • input – A path to a mbox file

    • nameservers (list) – A list of one or more nameservers to use @@ -316,10 +310,10 @@ forensic report results

    • offline (bool) – Do not make online queries for geolocation or DNS

    -
    Returns
    +
    Returns:

    Lists of aggregate_reports and forensic_reports

    -
    Return type
    +
    Return type:

    OrderedDict

    @@ -327,16 +321,16 @@ forensic report results

    -parsedmarc.get_report_zip(results)[source]
    +parsedmarc.get_report_zip(results)[source]

    Creates a zip file of parsed report output

    -
    Parameters
    +
    Parameters:

    results (OrderedDict) – The parsed results

    -
    Returns
    +
    Returns:

    zip file bytes

    -
    Return type
    +
    Return type:

    bytes

    @@ -344,11 +338,11 @@ forensic report results

    -parsedmarc.parse_aggregate_report_file(_input, offline=False, always_use_local_files=None, reverse_dns_map_path=None, reverse_dns_map_url=None, ip_db_path=None, nameservers=None, dns_timeout=2.0, keep_alive=None)[source]
    +parsedmarc.parse_aggregate_report_file(_input, offline=False, always_use_local_files=None, reverse_dns_map_path=None, reverse_dns_map_url=None, ip_db_path=None, nameservers=None, dns_timeout=2.0, keep_alive=None)[source]

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

    -
    Parameters
    +
    Parameters:
    • _input – A path to a file, a file like object, or bytes

    • offline (bool) – Do not query online for geolocation or DNS

    • @@ -362,10 +356,10 @@ aggregate DMARC report

    • keep_alive (callable) – Keep alive function

    -
    Returns
    +
    Returns:

    The parsed DMARC aggregate report

    -
    Return type
    +
    Return type:

    OrderedDict

    @@ -373,10 +367,10 @@ aggregate DMARC report

    -parsedmarc.parse_aggregate_report_xml(xml, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, nameservers=None, timeout=2.0, keep_alive=None)[source]
    +parsedmarc.parse_aggregate_report_xml(xml, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, nameservers=None, timeout=2.0, keep_alive=None)[source]

    Parses a DMARC XML report string and returns a consistent OrderedDict

    -
    Parameters
    +
    Parameters:
    • xml (str) – A string of DMARC aggregate report XML

    • ip_db_path (str) – Path to a MMDB file from MaxMind or DBIP

    • @@ -390,10 +384,10 @@ aggregate DMARC report

    • keep_alive (callable) – Keep alive function

    -
    Returns
    +
    Returns:

    The parsed aggregate DMARC report

    -
    Return type
    +
    Return type:

    OrderedDict

    @@ -401,10 +395,10 @@ aggregate DMARC report

    -parsedmarc.parse_forensic_report(feedback_report, sample, msg_date, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, ip_db_path=None, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False)[source]
    +parsedmarc.parse_forensic_report(feedback_report, sample, msg_date, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, ip_db_path=None, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False)[source]

    Converts a DMARC forensic report and sample to a OrderedDict

    -
    Parameters
    +
    Parameters:
    • feedback_report (str) – A message’s feedback report as a string

    • sample (str) – The RFC 822 headers or RFC 822 message sample

    • @@ -421,10 +415,10 @@ aggregate DMARC report

      forensic report results

    -
    Returns
    +
    Returns:

    A parsed report and sample

    -
    Return type
    +
    Return type:

    OrderedDict

    @@ -432,10 +426,10 @@ forensic report results

    -parsedmarc.parse_report_email(input_, offline=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, keep_alive=None)[source]
    +parsedmarc.parse_report_email(input_, offline=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, keep_alive=None)[source]

    Parses a DMARC report from an email

    -
    Parameters
    +
    Parameters:
    • input – An emailed DMARC report in RFC 822 format, as bytes or a string

    • ip_db_path (str) – Path to a MMDB file from MaxMind or DBIP

    • @@ -450,14 +444,14 @@ forensic report results

    • keep_alive (callable) – keep alive function

    -
    Returns
    +
    Returns:

    • report_type: aggregate or forensic

    • report: The parsed report

    -
    Return type
    +
    Return type:

    OrderedDict

    @@ -465,11 +459,11 @@ forensic report results

    -parsedmarc.parse_report_file(input_, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, keep_alive=None)[source]
    +parsedmarc.parse_report_file(input_, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, keep_alive=None)[source]

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

    -
    Parameters
    +
    Parameters:
    • input – A path to a file, a file like object, or bytes

    • nameservers (list) – A list of one or more nameservers to use @@ -485,10 +479,10 @@ forensic report results

    • keep_alive (callable) – Keep alive function

    -
    Returns
    +
    Returns:

    The parsed DMARC report

    -
    Return type
    +
    Return type:

    OrderedDict

    @@ -496,23 +490,23 @@ forensic report results

    -parsedmarc.parse_smtp_tls_report_json(report)[source]
    +parsedmarc.parse_smtp_tls_report_json(report)[source]

    Parses and validates an SMTP TLS report

    -parsedmarc.parsed_aggregate_reports_to_csv(reports)[source]
    +parsedmarc.parsed_aggregate_reports_to_csv(reports)[source]

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

    -
    Parameters
    +
    Parameters:

    reports – A parsed aggregate report or list of parsed aggregate reports

    -
    Returns
    +
    Returns:

    Parsed aggregate report data in flat CSV format, including headers

    -
    Return type
    +
    Return type:

    str

    @@ -520,18 +514,18 @@ headers

    -parsedmarc.parsed_aggregate_reports_to_csv_rows(reports)[source]
    +parsedmarc.parsed_aggregate_reports_to_csv_rows(reports)[source]

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

    -
    Parameters
    +
    Parameters:

    reports – A parsed aggregate report or list of parsed aggregate reports

    -
    Returns
    +
    Returns:

    Parsed aggregate report data as a list of dicts in flat CSV format

    -
    Return type
    +
    Return type:

    list

    @@ -539,17 +533,17 @@ format

    -parsedmarc.parsed_forensic_reports_to_csv(reports)[source]
    +parsedmarc.parsed_forensic_reports_to_csv(reports)[source]

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

    -
    Parameters
    +
    Parameters:

    reports – A parsed forensic report or list of parsed forensic reports

    -
    Returns
    +
    Returns:

    Parsed forensic report data in flat CSV format, including headers

    -
    Return type
    +
    Return type:

    str

    @@ -557,17 +551,17 @@ headers

    -parsedmarc.parsed_forensic_reports_to_csv_rows(reports)[source]
    +parsedmarc.parsed_forensic_reports_to_csv_rows(reports)[source]

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

    -
    Parameters
    +
    Parameters:

    reports – A parsed forensic report or list of parsed forensic reports

    -
    Returns
    +
    Returns:

    Parsed forensic report data as a list of dicts in flat CSV format

    -
    Return type
    +
    Return type:

    list

    @@ -575,17 +569,17 @@ format

    -parsedmarc.parsed_smtp_tls_reports_to_csv(reports)[source]
    +parsedmarc.parsed_smtp_tls_reports_to_csv(reports)[source]

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

    -
    Parameters
    +
    Parameters:

    reports – A parsed aggregate report or list of parsed aggregate reports

    -
    Returns
    +
    Returns:

    Parsed aggregate report data in flat CSV format, including headers

    -
    Return type
    +
    Return type:

    str

    @@ -593,17 +587,17 @@ headers

    -parsedmarc.parsed_smtp_tls_reports_to_csv_rows(reports)[source]
    +parsedmarc.parsed_smtp_tls_reports_to_csv_rows(reports)[source]

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

    -parsedmarc.save_output(results, output_directory='output', aggregate_json_filename='aggregate.json', forensic_json_filename='forensic.json', smtp_tls_json_filename='smtp_tls.json', aggregate_csv_filename='aggregate.csv', forensic_csv_filename='forensic.csv', smtp_tls_csv_filename='smtp_tls.csv')[source]
    +parsedmarc.save_output(results, output_directory='output', aggregate_json_filename='aggregate.json', forensic_json_filename='forensic.json', smtp_tls_json_filename='smtp_tls.json', aggregate_csv_filename='aggregate.csv', forensic_csv_filename='forensic.csv', smtp_tls_csv_filename='smtp_tls.csv')[source]

    Save report data in the given directory

    -
    Parameters
    +
    Parameters:
    • results (OrderedDict) – Parsing results

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

    • @@ -620,13 +614,13 @@ layer OrderedDict objects suitable for use in a CSV

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

      sends the results to a callback function

      -
      Parameters
      +
      Parameters:
      • mailbox_connection – The mailbox connection object

      • callback – The callback function to receive the parsing results

      • @@ -654,25 +648,25 @@ forensic report samples with None

-

parsedmarc.elastic

+

parsedmarc.elastic

-exception parsedmarc.elastic.AlreadySaved[source]
+exception parsedmarc.elastic.AlreadySaved[source]

Raised when a report to be saved matches an existing report

-exception parsedmarc.elastic.ElasticsearchError[source]
+exception parsedmarc.elastic.ElasticsearchError[source]

Raised when an Elasticsearch error occurs

-parsedmarc.elastic.create_indexes(names, settings=None)[source]
+parsedmarc.elastic.create_indexes(names, settings=None)[source]

Create Elasticsearch indexes

-
Parameters
+
Parameters:
  • names (list) – A list of index names

  • settings (dict) – Index settings

  • @@ -683,10 +677,10 @@ forensic report samples with None

    -parsedmarc.elastic.migrate_indexes(aggregate_indexes=None, forensic_indexes=None)[source]
    +parsedmarc.elastic.migrate_indexes(aggregate_indexes=None, forensic_indexes=None)[source]

    Updates index mappings

    -
    Parameters
    +
    Parameters:
    • aggregate_indexes (list) – A list of aggregate index names

    • forensic_indexes (list) – A list of forensic index names

    • @@ -697,10 +691,10 @@ forensic report samples with None

      -parsedmarc.elastic.save_aggregate_report_to_elasticsearch(aggregate_report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]
      +parsedmarc.elastic.save_aggregate_report_to_elasticsearch(aggregate_report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]

      Saves a parsed DMARC aggregate report to Elasticsearch

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

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

      • @@ -710,7 +704,7 @@ forensic report samples with None

      • number_of_replicas (int) – The number of replicas to use in the index

      -
      Raises
      +
      Raises:

      AlreadySaved

      @@ -718,10 +712,10 @@ forensic report samples with None

      -parsedmarc.elastic.save_forensic_report_to_elasticsearch(forensic_report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]
      +parsedmarc.elastic.save_forensic_report_to_elasticsearch(forensic_report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]

      Saves a parsed DMARC forensic report to Elasticsearch

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

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

      • @@ -733,7 +727,7 @@ indexes

        index

      -
      Raises
      +
      Raises:

      AlreadySaved

      @@ -741,10 +735,10 @@ index

      -parsedmarc.elastic.save_smtp_tls_report_to_elasticsearch(report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]
      +parsedmarc.elastic.save_smtp_tls_report_to_elasticsearch(report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]

      Saves a parsed SMTP TLS report to Elasticsearch

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

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

      • @@ -754,7 +748,7 @@ index

      • number_of_replicas (int) – The number of replicas to use in the index

      -
      Raises
      +
      Raises:

      AlreadySaved

      @@ -762,10 +756,10 @@ index

      -parsedmarc.elastic.set_hosts(hosts, use_ssl=False, ssl_cert_path=None, username=None, password=None, apiKey=None, timeout=60.0)[source]
      +parsedmarc.elastic.set_hosts(hosts, use_ssl=False, ssl_cert_path=None, username=None, password=None, apiKey=None, timeout=60.0)[source]

      Sets the Elasticsearch hosts to use

      -
      Parameters
      +
      Parameters:
      • hosts (str) – A single hostname or URL, or list of hostnames or URLs

      • use_ssl (bool) – Use a HTTPS connection to the server

      • @@ -781,25 +775,25 @@ index

-

parsedmarc.opensearch

+

parsedmarc.opensearch

-exception parsedmarc.opensearch.AlreadySaved[source]
+exception parsedmarc.opensearch.AlreadySaved[source]

Raised when a report to be saved matches an existing report

-exception parsedmarc.opensearch.OpenSearchError[source]
+exception parsedmarc.opensearch.OpenSearchError[source]

Raised when an OpenSearch error occurs

-parsedmarc.opensearch.create_indexes(names, settings=None)[source]
+parsedmarc.opensearch.create_indexes(names, settings=None)[source]

Create OpenSearch indexes

-
Parameters
+
Parameters:
  • names (list) – A list of index names

  • settings (dict) – Index settings

  • @@ -810,10 +804,10 @@ index

    -parsedmarc.opensearch.migrate_indexes(aggregate_indexes=None, forensic_indexes=None)[source]
    +parsedmarc.opensearch.migrate_indexes(aggregate_indexes=None, forensic_indexes=None)[source]

    Updates index mappings

    -
    Parameters
    +
    Parameters:
    • aggregate_indexes (list) – A list of aggregate index names

    • forensic_indexes (list) – A list of forensic index names

    • @@ -824,10 +818,10 @@ index

      -parsedmarc.opensearch.save_aggregate_report_to_opensearch(aggregate_report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]
      +parsedmarc.opensearch.save_aggregate_report_to_opensearch(aggregate_report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]

      Saves a parsed DMARC aggregate report to OpenSearch

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

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

      • @@ -837,7 +831,7 @@ index

      • number_of_replicas (int) – The number of replicas to use in the index

      -
      Raises
      +
      Raises:

      AlreadySaved

      @@ -845,10 +839,10 @@ index

      -parsedmarc.opensearch.save_forensic_report_to_opensearch(forensic_report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]
      +parsedmarc.opensearch.save_forensic_report_to_opensearch(forensic_report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]

      Saves a parsed DMARC forensic report to OpenSearch

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

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

      • @@ -860,7 +854,7 @@ indexes

        index

      -
      Raises
      +
      Raises:

      AlreadySaved

      @@ -868,10 +862,10 @@ index

      -parsedmarc.opensearch.save_smtp_tls_report_to_opensearch(report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]
      +parsedmarc.opensearch.save_smtp_tls_report_to_opensearch(report, index_suffix=None, index_prefix=None, monthly_indexes=False, number_of_shards=1, number_of_replicas=0)[source]

      Saves a parsed SMTP TLS report to OpenSearch

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

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

      • @@ -881,7 +875,7 @@ index

      • number_of_replicas (int) – The number of replicas to use in the index

      -
      Raises
      +
      Raises:

      AlreadySaved

      @@ -889,10 +883,10 @@ index

      -parsedmarc.opensearch.set_hosts(hosts, use_ssl=False, ssl_cert_path=None, username=None, password=None, apiKey=None, timeout=60.0)[source]
      +parsedmarc.opensearch.set_hosts(hosts, use_ssl=False, ssl_cert_path=None, username=None, password=None, apiKey=None, timeout=60.0)[source]

      Sets the OpenSearch hosts to use

      -
      Parameters
      +
      Parameters:
      • hosts (str|list) – A hostname or URL, or list of hostnames or URLs

      • use_ssl (bool) – Use an HTTPS connection to the server

      • @@ -908,13 +902,13 @@ index

-

parsedmarc.splunk

+

parsedmarc.splunk

-class parsedmarc.splunk.HECClient(url, access_token, index, source='parsedmarc', verify=True, timeout=60)[source]
+class parsedmarc.splunk.HECClient(url, access_token, index, source='parsedmarc', verify=True, timeout=60)[source]

Initializes the HECClient

-
Parameters
+
Parameters:
  • url (str) – The URL of the HEC

  • access_token (str) – The HEC access token

  • @@ -928,10 +922,10 @@ data before giving up

-save_aggregate_reports_to_splunk(aggregate_reports)[source]
+save_aggregate_reports_to_splunk(aggregate_reports)[source]

Saves aggregate DMARC reports to Splunk

-
Parameters
+
Parameters:

aggregate_reports – A list of aggregate report dictionaries to save in Splunk

@@ -940,10 +934,10 @@ to save in Splunk

-save_forensic_reports_to_splunk(forensic_reports)[source]
+save_forensic_reports_to_splunk(forensic_reports)[source]

Saves forensic DMARC reports to Splunk

-
Parameters
+
Parameters:

forensic_reports (list) – A list of forensic report dictionaries to save in Splunk

@@ -952,10 +946,10 @@ to save in Splunk

-save_smtp_tls_reports_to_splunk(reports)[source]
+save_smtp_tls_reports_to_splunk(reports)[source]

Saves aggregate DMARC reports to Splunk

-
Parameters
+
Parameters:

reports – A list of SMTP TLS report dictionaries to save in Splunk

@@ -966,36 +960,36 @@ to save in Splunk

-exception parsedmarc.splunk.SplunkError[source]
+exception parsedmarc.splunk.SplunkError[source]

Raised when a Splunk API error occurs

-

parsedmarc.utils

+

parsedmarc.utils

Utility functions that might be useful for other projects

-exception parsedmarc.utils.DownloadError[source]
+exception parsedmarc.utils.DownloadError[source]

Raised when an error occurs when downloading a file

-exception parsedmarc.utils.EmailParserError[source]
+exception parsedmarc.utils.EmailParserError[source]

Raised when an error parsing the email occurs

-parsedmarc.utils.convert_outlook_msg(msg_bytes)[source]
+parsedmarc.utils.convert_outlook_msg(msg_bytes)[source]

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

-
Parameters
+
Parameters:

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

-
Returns
+
Returns:

A RFC 822 string

@@ -1003,16 +997,16 @@ standard RFC 822 format

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

Decodes a base64 string, with padding being optional

-
Parameters
+
Parameters:

data – A base64 encoded string

-
Returns
+
Returns:

The decoded bytes

-
Return type
+
Return type:

bytes

@@ -1020,7 +1014,7 @@ standard RFC 822 format

-parsedmarc.utils.get_base_domain(domain)[source]
+parsedmarc.utils.get_base_domain(domain)[source]

Gets the base domain name for the given domain

Note

@@ -1028,13 +1022,13 @@ standard RFC 822 format

https://publicsuffix.org/list/public_suffix_list.dat.

-
Parameters
+
Parameters:

domain (str) – A domain or subdomain

-
Returns
+
Returns:

The base domain of the given domain

-
Return type
+
Return type:

str

@@ -1042,16 +1036,16 @@ standard RFC 822 format

-parsedmarc.utils.get_filename_safe_string(string)[source]
+parsedmarc.utils.get_filename_safe_string(string)[source]

Converts a string to a string that is safe for a filename

-
Parameters
+
Parameters:

string (str) – A string to make safe for a filename

-
Returns
+
Returns:

A string safe for a filename

-
Return type
+
Return type:

str

@@ -1059,20 +1053,20 @@ standard RFC 822 format

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

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

-
Parameters
+
Parameters:
  • ip_address (str) – The IP address to query for

  • db_path (str) – Path to a MMDB file from MaxMind or DBIP

-
Returns
+
Returns:

And ISO country code associated with the given IP address

-
Return type
+
Return type:

str

@@ -1080,10 +1074,10 @@ with the given IPv4 or IPv6 address

-parsedmarc.utils.get_ip_address_info(ip_address, ip_db_path=None, reverse_dns_map_path=None, always_use_local_files=False, reverse_dns_map_url=None, cache=None, reverse_dns_map=None, offline=False, nameservers=None, timeout=2.0)[source]
+parsedmarc.utils.get_ip_address_info(ip_address, ip_db_path=None, reverse_dns_map_path=None, always_use_local_files=False, reverse_dns_map_url=None, cache=None, reverse_dns_map=None, offline=False, nameservers=None, timeout=2.0)[source]

Returns reverse DNS and country information for the given IP address

-
Parameters
+
Parameters:
  • ip_address (str) – The IP address to check

  • ip_db_path (str) – path to a MMDB file from MaxMind or DBIP

  • @@ -1098,10 +1092,10 @@ with the given IPv4 or IPv6 address

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

-
Returns
+
Returns:

ip_address, reverse_dns

-
Return type
+
Return type:

OrderedDict

@@ -1109,10 +1103,10 @@ with the given IPv4 or IPv6 address

-parsedmarc.utils.get_reverse_dns(ip_address, cache=None, nameservers=None, timeout=2.0)[source]
+parsedmarc.utils.get_reverse_dns(ip_address, cache=None, nameservers=None, timeout=2.0)[source]

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

-
Parameters
+
Parameters:
  • ip_address (str) – The IP address to resolve

  • cache (ExpiringDict) – Cache storage

  • @@ -1121,10 +1115,10 @@ with the given IPv4 or IPv6 address

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

-
Returns
+
Returns:

The reverse DNS hostname (if any)

-
Return type
+
Return type:

str

@@ -1132,10 +1126,10 @@ with the given IPv4 or IPv6 address

-parsedmarc.utils.get_service_from_reverse_dns_base_domain(base_domain, always_use_local_file=False, local_file_path=None, url=None, offline=False, reverse_dns_map=None)[source]
+parsedmarc.utils.get_service_from_reverse_dns_base_domain(base_domain, always_use_local_file=False, local_file_path=None, url=None, offline=False, reverse_dns_map=None)[source]

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

-
Parameters
+
Parameters:
  • base_domain (str) – The base domain of the reverse DNS lookup

  • always_use_local_file (bool) – Always use a local map file

  • @@ -1145,12 +1139,12 @@ with the given IPv4 or IPv6 address

  • reverse_dns_map (dict) – A reverse DNS map

-
Returns
+
Returns:

A dictionary containing name and type. If the service is unknown, the name will be the supplied reverse_dns_base_domain and the type will be None

-
Return type
+
Return type:

dict

@@ -1158,19 +1152,19 @@ the supplied reverse_dns_base_domain and the type will be None

-parsedmarc.utils.human_timestamp_to_datetime(human_timestamp, to_utc=False)[source]
+parsedmarc.utils.human_timestamp_to_datetime(human_timestamp, to_utc=False)[source]

Converts a human-readable timestamp into a Python datetime object

-
Parameters
+
Parameters:
  • human_timestamp (str) – A timestamp string

  • to_utc (bool) – Convert the timestamp to UTC

-
Returns
+
Returns:

The converted timestamp

-
Return type
+
Return type:

datetime

@@ -1178,16 +1172,16 @@ the supplied reverse_dns_base_domain and the type will be None

-parsedmarc.utils.human_timestamp_to_unix_timestamp(human_timestamp)[source]
+parsedmarc.utils.human_timestamp_to_unix_timestamp(human_timestamp)[source]

Converts a human-readable timestamp into a UNIX timestamp

-
Parameters
+
Parameters:

human_timestamp (str) – A timestamp in YYYY-MM-DD HH:MM:SS` format

-
Returns
+
Returns:

The converted timestamp

-
Return type
+
Return type:

float

@@ -1195,16 +1189,16 @@ the supplied reverse_dns_base_domain and the type will be None

-parsedmarc.utils.is_mbox(path)[source]
+parsedmarc.utils.is_mbox(path)[source]

Checks if the given content is an MBOX mailbox file

-
Parameters
+
Parameters:

path – Content to check

-
Returns
+
Returns:

A flag that indicates if the file is an MBOX mailbox file

-
Return type
+
Return type:

bool

@@ -1212,16 +1206,16 @@ the supplied reverse_dns_base_domain and the type will be None

-parsedmarc.utils.is_outlook_msg(content)[source]
+parsedmarc.utils.is_outlook_msg(content)[source]

Checks if the given content is an Outlook msg OLE/MSG file

-
Parameters
+
Parameters:

content – Content to check

-
Returns
+
Returns:

A flag that indicates if the file is an Outlook MSG file

-
Return type
+
Return type:

bool

@@ -1229,19 +1223,19 @@ the supplied reverse_dns_base_domain and the type will be None

-parsedmarc.utils.parse_email(data, strip_attachment_payloads=False)[source]
+parsedmarc.utils.parse_email(data, strip_attachment_payloads=False)[source]

A simplified email parser

-
Parameters
+
Parameters:
  • data – The RFC 822 message string, or MSG binary

  • strip_attachment_payloads (bool) – Remove attachment payloads

-
Returns
+
Returns:

Parsed email data

-
Return type
+
Return type:

dict

@@ -1249,10 +1243,10 @@ the supplied reverse_dns_base_domain and the type will be None

-parsedmarc.utils.query_dns(domain, record_type, cache=None, nameservers=None, timeout=2.0)[source]
+parsedmarc.utils.query_dns(domain, record_type, cache=None, nameservers=None, timeout=2.0)[source]

Queries DNS

-
Parameters
+
Parameters:
  • domain (str) – The domain or subdomain to query about

  • record_type (str) – The record type to query for

  • @@ -1262,10 +1256,10 @@ the supplied reverse_dns_base_domain and the type will be None

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

-
Returns
+
Returns:

A list of answers

-
Return type
+
Return type:

list

@@ -1273,16 +1267,16 @@ the supplied reverse_dns_base_domain and the type will be None

-parsedmarc.utils.timestamp_to_datetime(timestamp)[source]
+parsedmarc.utils.timestamp_to_datetime(timestamp)[source]

Converts a UNIX/DMARC timestamp to a Python datetime object

-
Parameters
+
Parameters:

timestamp (int) – The timestamp

-
Returns
+
Returns:

The converted timestamp as a Python datetime object

-
Return type
+
Return type:

datetime

@@ -1290,16 +1284,16 @@ the supplied reverse_dns_base_domain and the type will be None

-parsedmarc.utils.timestamp_to_human(timestamp)[source]
+parsedmarc.utils.timestamp_to_human(timestamp)[source]

Converts a UNIX/DMARC timestamp to a human-readable string

-
Parameters
+
Parameters:

timestamp – The timestamp

-
Returns
+
Returns:

The converted timestamp in YYYY-MM-DD HH:MM:SS format

-
Return type
+
Return type:

str

@@ -1307,7 +1301,7 @@ the supplied reverse_dns_base_domain and the type will be None

-

Indices and tables

+

Indices and tables

  • Index

  • Module Index

  • diff --git a/contributing.html b/contributing.html index 6df2565..692a5d4 100644 --- a/contributing.html +++ b/contributing.html @@ -1,24 +1,21 @@ + + - + - + - Contributing to parsedmarc — parsedmarc 8.15.0 documentation - - + Contributing to parsedmarc — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -91,9 +85,9 @@
    -

    Contributing to parsedmarc

    +

    Contributing to parsedmarc

    -

    Bug reports

    +

    Bug reports

    Please report bugs on the GitHub issue tracker

    https://github.com/domainaware/parsedmarc/issues

    diff --git a/davmail.html b/davmail.html index 1ea3935..bb9c3cb 100644 --- a/davmail.html +++ b/davmail.html @@ -1,24 +1,21 @@ + + - + - + - Accessing an inbox using OWA/EWS — parsedmarc 8.15.0 documentation - - + Accessing an inbox using OWA/EWS — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -92,7 +86,7 @@
    -

    Accessing an inbox using OWA/EWS

    +

    Accessing an inbox using OWA/EWS

    Note

    Starting in 8.0.0, parsedmarc supports accessing Microsoft/Office 365 @@ -177,7 +171,7 @@ as a local EWS/OWA IMAP gateway. It can even work where

    -

    Running DavMail as a systemd service

    +

    Running DavMail as a systemd service

    Use systemd to run davmail as a service.

    Create a system user

    sudo useradd davmail -r -s /bin/false
    @@ -244,7 +238,7 @@ well as the current process (newest to oldest), run:

    -

    Configuring parsedmarc for DavMail

    +

    Configuring parsedmarc for DavMail

    Because you are interacting with DavMail server over the loopback (i.e. 127.0.0.1), add the following options to parsedmarc.ini config file:

    diff --git a/dmarc.html b/dmarc.html index 3a76485..29c5559 100644 --- a/dmarc.html +++ b/dmarc.html @@ -1,24 +1,21 @@ + + - + - + - Understanding DMARC — parsedmarc 8.15.0 documentation - - + Understanding DMARC — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -110,39 +104,34 @@
    -

    Understanding DMARC

    +

    Understanding DMARC

    -

    Resources

    +

    Resources

    -

    DMARC guides

    +

    DMARC guides

    -

    SPF and DMARC record validation

    +

    SPF and DMARC record validation

    If you are looking for SPF and DMARC record validation and parsing, check out the sister project, checkdmarc.

    -

    Lookalike domains

    +

    Lookalike domains

    DMARC protects against domain spoofing, not lookalike domains. for open source lookalike domain monitoring, check out DomainAware.

    -

    DMARC Alignment Guide

    +

    DMARC Alignment Guide

    DMARC ensures that SPF and DKM authentication mechanisms actually authenticate against the same domain that the end user sees.

    A message passes a DMARC check by passing DKIM or SPF, as long as the related indicators are also in alignment.

    ----- @@ -180,7 +169,7 @@ header

    DKIM

    -

    What if a sender won’t support DKIM/DMARC?

    +

    What if a sender won’t support DKIM/DMARC?

    1. Some vendors don’t know about DMARC yet; ask about SPF and DKIM/email authentication.

    2. @@ -200,21 +189,21 @@ spoofing of your TLD and/or any subdomain.

    -

    What about mailing lists?

    +

    What about mailing lists?

    When you deploy DMARC on your domain, you might find that messages relayed by mailing lists are failing DMARC, most likely because the mailing list is spoofing your from address, and modifying the subject, footer, or other part of the message, thereby breaking the DKIM signature.

    -

    Mailing list best practices

    +

    Mailing list best practices

    Ideally, a mailing list should forward messages without altering the headers or body content at all. Joe Nelson does a fantastic job of explaining exactly what mailing lists should and shouldn’t do to be fully DMARC compliant. Rather than repeat his fine work, here’s a summary:

    -

    Do

    +

    Do

    • Retain headers from the original message

    • Add RFC 2369 List-Unsubscribe headers to outgoing messages, instead of @@ -234,7 +223,7 @@ adding unsubscribe links to the body

    • these headers.

    -

    Do not

    +

    Do not

    • Remove or modify any existing headers from the original message, including From, Date, Subject, etc.

    • @@ -251,13 +240,9 @@ to the mailing list post address, and not their email address.

      Configuration steps for common mailing list platforms are listed below.

    -

    Mailman 2

    +

    Mailman 2

    Navigate to General Settings, and configure the settings below

    ---- @@ -287,10 +272,6 @@ to the mailing list post address, and not their email address.

    Setting

    Value

    Navigate to Non-digest options, and configure the settings below

    ---- @@ -308,10 +289,6 @@ to the mailing list post address, and not their email address.

    Setting

    Value

    Navigate to Privacy Options> Sending Filters, and configure the settings below

    ---- @@ -329,16 +306,12 @@ to the mailing list post address, and not their email address.

    Setting

    Value

    -

    Mailman 3

    +

    Mailman 3

    Navigate to Settings> List Identity

    Make Subject prefix blank.

    Navigate to Settings> Alter Messages

    Configure the settings below

    ---- @@ -366,10 +339,6 @@ to the mailing list post address, and not their email address.

    Navigate to Settings> DMARC Mitigation

    Configure the settings below

    Setting

    Value

    ---- @@ -393,13 +362,13 @@ command line instead, for example:

    Then restart mailman core.

    -

    LISTSERV

    +

    LISTSERV

    LISTSERV 16.0-2017a and higher will rewrite the From header for domains that enforce with a DMARC quarantine or reject policy.

    Some additional steps are needed for Linux hosts.

    -

    Workarounds

    +

    Workarounds

    If a mailing list must go against best practices and modify the message (e.g. to add a required legal footer), the mailing list administrator must configure the list to replace the From address of the @@ -407,13 +376,9 @@ message (also known as munging) with the address of the mailing list, so they no longer spoof email addresses with domains protected by DMARC.

    Configuration steps for common mailing list platforms are listed below.

    -
    Mailman 2
    +
    Mailman 2

    Navigate to Privacy Options> Sending Filters, and configure the settings below

    Setting

    Value

    ---- @@ -442,13 +407,9 @@ the original sender.

    -
    Mailman 3
    +
    Mailman 3

    In the DMARC Mitigations tab of the Settings page, configure the settings below

    Setting

    Value

    ---- diff --git a/elasticsearch.html b/elasticsearch.html index bcbfc5f..e3fdaec 100644 --- a/elasticsearch.html +++ b/elasticsearch.html @@ -1,24 +1,21 @@ + + - + - + - Elasticsearch and Kibana — parsedmarc 8.15.0 documentation - - + Elasticsearch and Kibana — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -93,14 +87,14 @@
    -

    Elasticsearch and Kibana

    +

    Elasticsearch and Kibana

    To set up visual dashboards of DMARC data, install Elasticsearch and Kibana.

    Note

    Elasticsearch and Kibana 6 or later are required

    -

    Installation

    +

    Installation

    On Debian/Ubuntu based systems, run:

    sudo apt-get install -y apt-transport-https
     wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
    @@ -243,11 +237,13 @@ page of Kibana. (Hamburger menu -> “Management” -> “Stack Management
     visualizations, which could be used to restore them if you or someone else
     breaks them, as there are no permissions/access controls in Kibana without
     the commercial X-Pack.

    -A screenshot of setting the Saved Objects Stack management UI in Kibana -A screenshot of the overwrite conformation prompt +A screenshot of setting the Saved Objects Stack management UI in Kibana + +A screenshot of the overwrite conformation prompt +
    -

    Upgrading Kibana index patterns

    +

    Upgrading Kibana index patterns

    parsedmarc 5.0.0 makes some changes to the way data is indexed in Elasticsearch. if you are upgrading from a previous release of parsedmarc, you need to complete the following steps to replace the @@ -266,7 +262,7 @@ Saved Objects page

    -

    Records retention

    +

    Records retention

    Starting in version 5.0.0, parsedmarc stores data in a separate index for each day to make it easy to comply with records retention regulations such as GDPR. For more information, diff --git a/genindex.html b/genindex.html index 712a0e5..5fe4aed 100644 --- a/genindex.html +++ b/genindex.html @@ -1,23 +1,20 @@ + + - + - Index — parsedmarc 8.15.0 documentation - - + Index — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -

    - 8.15.0 -
    diff --git a/index.html b/index.html index e16a079..17b97f9 100644 --- a/index.html +++ b/index.html @@ -1,24 +1,21 @@ + + - + - + - parsedmarc documentation - Open source DMARC report analyzer and visualizer — parsedmarc 8.15.0 documentation - - + parsedmarc documentation - Open source DMARC report analyzer and visualizer — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -36,9 +33,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -87,7 +81,7 @@
    -

    parsedmarc documentation - Open source DMARC report analyzer and visualizer

    +

    parsedmarc documentation - Open source DMARC report analyzer and visualizer

    BuildStatus CodeCoverage PyPIPackage @@ -100,14 +94,15 @@ Please consider reviewing the open contributors!

    -A screenshot of DMARC summary charts in Kibana +A screenshot of DMARC summary charts in Kibana +

    parsedmarc is a Python module and CLI utility for parsing DMARC reports. When used with Elasticsearch and Kibana (or Splunk), or with OpenSearch and Grafana, it works as a self-hosted open source alternative to commercial DMARC report processing services such as Agari Brand Protection, Dmarcian, OnDMARC, ProofPoint Email Fraud Defense, and Valimail.

    -

    Features

    +

    Features

    • Parses draft and 1.0 standard aggregate/rua reports

    • Parses forensic/failure/ruf reports

    • diff --git a/installation.html b/installation.html index 1582924..4df49f0 100644 --- a/installation.html +++ b/installation.html @@ -1,24 +1,21 @@ + + - + - + - Installation — parsedmarc 8.15.0 documentation - - + Installation — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
      - 8.15.0 -
      @@ -99,18 +93,18 @@
      -

      Installation

      +

      Installation

      -

      Prerequisites

      +

      Prerequisites

      parsedmarc works with Python 3 only.

      -

      Testing multiple report analyzers

      +

      Testing multiple report analyzers

      If you would like to test parsedmarc and another report processing solution at the same time, you can have up to two mailto URIs in each of the rua and ruf tags in your DMARC record, separated by commas.

      -

      Using a web proxy

      +

      Using a web proxy

      If your system is behind a web proxy, you need to configure your system to use that proxy. To do this, edit /etc/environment and add your proxy details there, for example:

      @@ -128,7 +122,7 @@ proxy details there, for example:

      This will set the proxy up for use system-wide, including for parsedmarc.

      -

      Using Microsoft Exchange

      +

      Using Microsoft Exchange

      If your mail server is Microsoft Exchange, ensure that it is patched to at least:

        @@ -138,7 +132,7 @@ least:

      -

      geoipupdate setup

      +

      geoipupdate setup

      Note

      Starting in parsedmarc 7.1.0, a static copy of the @@ -210,7 +204,7 @@ job or scheduled task.

      -

      Installing parsedmarc

      +

      Installing parsedmarc

      On Debian or Ubuntu systems, run:

      sudo apt-get install -y python3-pip python3-virtualenv python3-dev libxml2-dev libxslt-dev
       
      @@ -245,7 +239,7 @@ explicitly tell vir
      -

      Optional dependencies

      +

      Optional dependencies

      If you would like to be able to parse emails saved from Microsoft Outlook (i.e. OLE .msg files), install msgconvert:

      On Debian or Ubuntu systems, run:

      diff --git a/kibana.html b/kibana.html index 1cf8d57..d3069d2 100644 --- a/kibana.html +++ b/kibana.html @@ -1,24 +1,21 @@ + + - + - + - Using the Kibana dashboards — parsedmarc 8.15.0 documentation - - + Using the Kibana dashboards — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
      - 8.15.0 -
      @@ -92,7 +86,7 @@
      -

      Using the Kibana dashboards

      +

      Using the Kibana dashboards

      The Kibana DMARC dashboards are a human-friendly way to understand the results from incoming DMARC reports.

      @@ -101,7 +95,7 @@ results from incoming DMARC reports.

      click on the Dashboard link on the left side menu of Kibana.

      -

      DMARC Summary

      +

      DMARC Summary

      As the name suggests, this dashboard is the best place to start reviewing your aggregate DMARC data.

      Across the top of the dashboard, three pie charts display the percentage of @@ -158,7 +152,7 @@ the DMARC Summary dashboard. To view failures only, use the pie chart.

      filters by clicking on Add Filter at the upper right of the page.

      -

      DMARC Forensic Samples

      +

      DMARC Forensic Samples

      The DMARC Forensic Samples dashboard contains information on DMARC forensic reports (also known as failure reports or ruf reports). These reports contain samples of emails that have failed to pass DMARC.

      diff --git a/mailing-lists.html b/mailing-lists.html index 25e659e..565bc95 100644 --- a/mailing-lists.html +++ b/mailing-lists.html @@ -1,24 +1,21 @@ + + - + - + - What about mailing lists? — parsedmarc 8.15.0 documentation - - + What about mailing lists? — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -35,9 +32,6 @@ parsedmarc -
      - 8.15.0 -
      @@ -86,21 +80,21 @@
      -

      What about mailing lists?

      +

      What about mailing lists?

      When you deploy DMARC on your domain, you might find that messages relayed by mailing lists are failing DMARC, most likely because the mailing list is spoofing your from address, and modifying the subject, footer, or other part of the message, thereby breaking the DKIM signature.

      -

      Mailing list best practices

      +

      Mailing list best practices

      Ideally, a mailing list should forward messages without altering the headers or body content at all. Joe Nelson does a fantastic job of explaining exactly what mailing lists should and shouldn’t do to be fully DMARC compliant. Rather than repeat his fine work, here’s a summary:

      -

      Do

      +

      Do

      • Retain headers from the original message

      • Add RFC 2369 List-Unsubscribe headers to outgoing messages, instead of @@ -120,7 +114,7 @@ adding unsubscribe links to the body

      • these headers.

      -

      Do not

      +

      Do not

      • Remove or modify any existing headers from the original message, including From, Date, Subject, etc.

      • @@ -137,13 +131,9 @@ to the mailing list post address, and not their email address.

        Configuration steps for common mailing list platforms are listed below.

      -

      Mailman 2

      +

      Mailman 2

      Navigate to General Settings, and configure the settings below

    Setting

    Value

    ---- @@ -173,10 +163,6 @@ to the mailing list post address, and not their email address.

    Setting

    Value

    Navigate to Non-digest options, and configure the settings below

    ---- @@ -194,10 +180,6 @@ to the mailing list post address, and not their email address.

    Setting

    Value

    Navigate to Privacy Options> Sending Filters, and configure the settings below

    ---- @@ -215,16 +197,12 @@ to the mailing list post address, and not their email address.

    Setting

    Value

    -

    Mailman 3

    +

    Mailman 3

    Navigate to Settings> List Identity

    Make Subject prefix blank.

    Navigate to Settings> Alter Messages

    Configure the settings below

    ---- @@ -252,10 +230,6 @@ to the mailing list post address, and not their email address.

    Navigate to Settings> DMARC Mitigation

    Configure the settings below

    Setting

    Value

    ---- @@ -279,13 +253,13 @@ command line instead, for example:

    Then restart mailman core.

    -

    LISTSERV

    +

    LISTSERV

    LISTSERV 16.0-2017a and higher will rewrite the From header for domains that enforce with a DMARC quarantine or reject policy.

    Some additional steps are needed for Linux hosts.

    -

    Workarounds

    +

    Workarounds

    If a mailing list must go against best practices and modify the message (e.g. to add a required legal footer), the mailing list administrator must configure the list to replace the From address of the @@ -293,13 +267,9 @@ message (also known as munging) with the address of the mailing list, so they no longer spoof email addresses with domains protected by DMARC.

    Configuration steps for common mailing list platforms are listed below.

    -

    Mailman 2

    +

    Mailman 2

    Navigate to Privacy Options> Sending Filters, and configure the settings below

    Setting

    Value

    ---- @@ -328,13 +298,9 @@ the original sender.

    -

    Mailman 3

    +

    Mailman 3

    In the DMARC Mitigations tab of the Settings page, configure the settings below

    Setting

    Value

    ---- diff --git a/objects.inv b/objects.inv index 06b9d23..ce07f1e 100644 Binary files a/objects.inv and b/objects.inv differ diff --git a/opensearch.html b/opensearch.html index 093387a..65eeed2 100644 --- a/opensearch.html +++ b/opensearch.html @@ -1,24 +1,21 @@ + + - + - + - OpenSearch and Grafana — parsedmarc 8.15.0 documentation - - + OpenSearch and Grafana — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -92,15 +86,15 @@
    -

    OpenSearch and Grafana

    +

    OpenSearch and Grafana

    To set up visual dashboards of DMARC data, install OpenSearch and Grafana.

    -

    Installation

    +

    Installation

    OpenSearch: https://opensearch.org/docs/latest/install-and-configure/install-opensearch/index/ Grafana: https://grafana.com/docs/grafana/latest/setup-grafana/installation/

    -

    Records retention

    +

    Records retention

    Starting in version 5.0.0, parsedmarc stores data in a separate index for each day to make it easy to comply with records retention regulations such as GDPR.

    diff --git a/output.html b/output.html index 0eec805..0a504a7 100644 --- a/output.html +++ b/output.html @@ -1,24 +1,21 @@ + + - + - + - Sample outputs — parsedmarc 8.15.0 documentation - - + Sample outputs — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -101,9 +95,9 @@
    -

    Sample outputs

    +

    Sample outputs

    -

    Sample aggregate report output

    +

    Sample aggregate report output

    Here are the results from parsing the example report from the dmarc.org wiki. It’s actually an older draft of the 1.0 report schema standardized in @@ -112,7 +106,7 @@ This draft schema is still in wide use.

    parsedmarc produces consistent, normalized output, regardless of the report schema.

    -

    JSON aggregate report

    +

    JSON aggregate report

    {
       "xml_schema": "draft",
       "report_metadata": {
    @@ -181,7 +175,7 @@ of the report schema.

    -

    CSV aggregate report

    +

    CSV aggregate report

    xml_schema,org_name,org_email,org_extra_contact_info,report_id,begin_date,end_date,errors,domain,adkim,aspf,p,sp,pct,fo,source_ip_address,source_country,source_reverse_dns,source_base_domain,count,spf_aligned,dkim_aligned,dmarc_aligned,disposition,policy_override_reasons,policy_override_comments,envelope_from,header_from,envelope_to,dkim_domains,dkim_selectors,dkim_results,spf_domains,spf_scopes,spf_results
     draft,acme.com,noreply-dmarc-support@acme.com,http://acme.com/dmarc/support,9391651994964116463,2012-04-27 20:00:00,2012-04-28 19:59:59,,example.com,r,r,none,none,100,0,72.150.241.94,US,adsl-72-150-241-94.shv.bellsouth.net,bellsouth.net,2,True,False,True,none,,,example.com,example.com,,example.com,none,fail,example.com,mfrom,pass
     
    @@ -189,11 +183,11 @@ draft,acme.com,noreply-dmarc-support@acme.com,http://acme.com/dmarc/support,9391
    -

    Sample forensic report output

    +

    Sample forensic report output

    Thanks to GitHub user xennn for the anonymized forensic report email sample.

    -

    JSON forensic report

    +

    JSON forensic report

    {
          "feedback_type": "auth-failure",
          "user_agent": "Lua/1.0",
    @@ -282,14 +276,14 @@ draft,acme.com,noreply-dmarc-support@acme.com,http://acme.com/dmarc/support,9391
     
    -

    CSV forensic report

    +

    CSV forensic report

    feedback_type,user_agent,version,original_envelope_id,original_mail_from,original_rcpt_to,arrival_date,arrival_date_utc,subject,message_id,authentication_results,dkim_domain,source_ip_address,source_country,source_reverse_dns,source_base_domain,delivery_result,auth_failure,reported_domain,authentication_mechanisms,sample_headers_only
     auth-failure,Lua/1.0,1.0,,sharepoint@domain.de,peter.pan@domain.de,"Mon, 01 Oct 2018 11:20:27 +0200",2018-10-01 09:20:27,Subject,<38.E7.30937.BD6E1BB5@ mailrelay.de>,"dmarc=fail (p=none, dis=none) header.from=domain.de",,10.10.10.10,,,,policy,dmarc,domain.de,,False
     
    -

    JSON SMTP TLS report

    +

    JSON SMTP TLS report

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

    Splunk

    +

    Splunk

    Starting in version 4.3.0 parsedmarc supports sending aggregate and/or forensic DMARC data to a Splunk HTTP Event collector (HEC).

    The project repository contains XML files for premade Splunk diff --git a/usage.html b/usage.html index d9837c4..801dc99 100644 --- a/usage.html +++ b/usage.html @@ -1,24 +1,21 @@ + + - + - + - Using parsedmarc — parsedmarc 8.15.0 documentation - - + Using parsedmarc — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -

    - 8.15.0 -
    @@ -93,9 +87,9 @@
    -

    Using parsedmarc

    +

    Using parsedmarc

    -

    CLI help

    +

    CLI help

    usage: parsedmarc [-h] [-c CONFIG_FILE] [--strip-attachment-payloads] [-o OUTPUT]
                        [--aggregate-json-filename AGGREGATE_JSON_FILENAME]
                        [--forensic-json-filename FORENSIC_JSON_FILENAME]
    @@ -147,7 +141,7 @@ configuration file, described below.

    -

    Configuration file

    +

    Configuration file

    parsedmarc can be configured by supplying the path to an INI file

    parsedmarc -c /etc/parsedmarc.ini
     
    @@ -193,6 +187,12 @@ configuration file, described below.

    host = logger port = 12201 mode = tcp + +[webhook] +aggregate_url = https://aggregate_url.example.com +forensic_url = https://forensic_url.example.com +smtp_tls_url = https://smtp_tls_url.example.com +timeout = 60

    The full set of configuration options are:

    @@ -221,6 +221,8 @@ or DNS

  • reverse_dns_map_url - Overrides the default download URL for the reverse DNS map

  • nameservers - str: A comma separated list of DNS resolvers (Default: [Cloudflare's public resolvers])

  • +
  • dns_test_address - str: a dummy address used for DNS pre-flight checks +(Default: 1.1.1.1)

  • dns_timeout - float: DNS timeout period

  • debug - bool: Print debugging messages

  • silent - bool: Only print errors (Default: True)

  • @@ -489,6 +491,20 @@ When False

    mode - str: The GELF transport type to use. Valid modes: tcp, udp, tls

    +
  • maildir

    +
      +
    • reports_folder - str: Full path for mailbox maidir location (Default: INBOX)

    • +
    • maildir_create - bool: Create maildir if not present (Default: False)

    • +
    +
  • +
  • webhook - Post the individual reports to a webhook url with the report as the JSON body

    +
      +
    • aggregate_url - str: URL of the webhook which should receive the aggregate reports

    • +
    • forensic_url - str: URL of the webhook which should receive the forensic reports

    • +
    • smtp_tls_url - str: URL of the webhook which should receive the smtp_tls reports

    • +
    • timeout - int: Interval in which the webhook call should timeout

    • +
    +
  • Warning

    @@ -551,7 +567,7 @@ Check current usage (from Management -> Dev Tools -> Console):

    -

    Running parsedmarc as a systemd service

    +

    Running parsedmarc as a systemd service

    Use systemd to run parsedmarc as a service and process reports as they arrive.

    Protect the parsedmarc configuration file from prying eyes

    Setting

    Value