def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR num_recipients = 0 # count number of email addresses in To and Cc headers for header_keyword in ["To", "Cc"]: if header_keyword in email: for header in email.get_all(header_keyword): email_addresses = extract_email_addresses(str(header)) if email_addresses: num_recipients += len(email_addresses) if num_recipients > config.recipient_limit: return ReturnCode.LIMIT_EXCEEDED return ReturnCode.LIMIT_OK
def main(args): try: email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR for part in email.walk(): if part.get_content_type() == "text/html": charset_mime = part.get_content_charset() if charset_mime: content = part.get_payload(decode=True).decode(charset_mime, errors="ignore") match = re.search(r"<meta [^>]*charset=\"?([^;\"> ]+)", content, flags=re.IGNORECASE) if match is not None: charset_meta = match.group(1).lower() if charset_mime != charset_meta: content = re.sub( r"(<meta [^>]*charset=\"?)({})".format( charset_meta), r"\1{}".format(charset_mime), content, flags=re.IGNORECASE) if HEADER_CTE in part: del part[HEADER_CTE] part.set_payload(content, charset=charset_mime) try: with open(args.input, "wb") as f: f.write(email.as_bytes()) except Exception: write_log(args.log, "Error writing '{}'".format(args.input)) return ReturnCode.ERROR return ReturnCode.CHARSET_CHANGED return ReturnCode.NOT_MODIFIED
def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR email = email.as_bytes() for list_string in config.search_strings: for string in list_string: if email.find(string.encode(CHARSET_UTF8)) == -1: break else: return ReturnCode.STRING_FOUND return ReturnCode.NOT_FOUND
def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if "Sensitivity" in email and str(email.get("Sensitivity")) == "private": if len(email.as_bytes()) > config.max_size_kb * 1024: write_log(args.log, "Mail exceeds max size") return ReturnCode.INVALID for part in email.walk(): if part.is_attachment(): write_log(args.log, "Mail has attachment") return ReturnCode.INVALID return ReturnCode.PRIVATE return ReturnCode.NOT_PRIVATE
def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR try: check_function = get_expression_list(config.name_expression_list).pop() except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR try: exec(check_function, globals()) return check_email(email, args.log) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR
def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if "Subject" not in email: return ReturnCode.ENCRYPTION_SKIPPED header_subject = str(email.get("Subject")) keyword_escaped = re.escape(config.keyword_encryption) if not re.match(r"^{}".format(keyword_escaped), header_subject, re.IGNORECASE): return ReturnCode.ENCRYPTION_SKIPPED if "From" not in email: write_log(args.log, "Header from does not exist") return ReturnCode.ERROR header_from = str(email.get("From")) if not header_from: write_log(args.log, "Header from is empty") return ReturnCode.ERROR address_sender = extract_email_address(header_from) if not address_sender: write_log(args.log, "Cannot find sender address") return ReturnCode.ERROR address_recipient = dict() # collect email addresses in To and Cc headers for header_keyword in ["To", "Cc"]: address_recipient[header_keyword] = set() if header_keyword in email: for header in email.get_all(header_keyword): email_addresses = extract_email_addresses(str(header)) if email_addresses: address_recipient[header_keyword] |= email_addresses if not address_recipient["To"]: write_log(args.log, "Cannot find recipient address") return ReturnCode.ERROR # remove encryption keyword from subject header email = re.sub(r"(\n|^)Subject: *{} *".format(keyword_escaped), r"\1Subject: ", email.as_string(), count=1, flags=re.IGNORECASE) header_subject = re.sub(r"^{} *".format(keyword_escaped), "", header_subject, flags=re.IGNORECASE) password_characters = string.ascii_letters + string.digits if config.password_punctuation: password_characters += string.punctuation password = "".join( random.choice(password_characters) for i in range(config.password_length)) try: zip_archive = zip_encrypt({("email.eml", email)}, password) except Exception: write_log(args.log, "Error zip-encrypting email") return ReturnCode.ERROR # send email with encrypted original mail attached to recipients email_message = EmailMessage() email_message["Subject"] = header_subject email_message["From"] = address_sender email_message["To"] = ", ".join(address_recipient["To"]) if address_recipient["Cc"]: email_message["Cc"] = ", ".join(address_recipient["Cc"]) email_message.set_content(MESSAGE_RECIPIENT.format(address_sender)) email_message.add_attachment(zip_archive, maintype="application", subtype="zip", filename="email.zip") try: with smtplib.SMTP("localhost", port=PORT_SMTP) as s: s.send_message(email_message) except Exception: write_log(args.log, "Cannot send recipient email") return ReturnCode.ERROR # send email with password to sender email_message = EmailMessage() email_message["Subject"] = "Re: {}".format(header_subject) email_message["From"] = address_sender email_message["To"] = address_sender email_message.set_content(MESSAGE_SENDER.format(password)) try: with smtplib.SMTP("localhost", port=PORT_SMTP) as s: s.send_message(email_message) except Exception: write_log(args.log, "Cannot send sender email") return ReturnCode.MAIL_ENCRYPTED
def main(args): HEADER_CTE = "Content-Transfer-Encoding" try: config = get_config(args.config, CONFIG_PARAMETERS) email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR sys.setrecursionlimit(RECURSION_LIMIT) if config.text_tag or (args.remove and config.address_tag and config.clean_text): text_part = None for part in email.walk(): if part.get_content_type( ) == "text/plain" and not part.is_attachment(): text_part = part text_charset = python_charset(text_part.get_content_charset()) if text_charset is None: text_charset = CHARSET_UTF8 try: text_content = text_part.get_payload(decode=True).decode( text_charset, errors="ignore").replace("\r", "") except Exception: write_log( args.log, "Cannot decode text part with charset '{}'".format( text_charset)) return ReturnCode.INVALID_ENCODING break if config.html_tag or (args.remove and config.address_tag and config.clean_html): html_part = None for part in email.walk(): if part.get_content_type( ) == "text/html" and not part.is_attachment(): html_part = part html_charset = python_charset(html_part.get_content_charset()) if html_charset is None: html_charset = CHARSET_UTF8 try: html_content = html_part.get_payload(decode=True).decode( html_charset, errors="ignore") except Exception: write_log( args.log, "Cannot decode html part with charset '{}'".format( html_charset)) return ReturnCode.INVALID_ENCODING break if config.calendar_tag: calendar_part = None for part in email.walk(): if part.get_content_type( ) == "text/calendar" and not part.is_attachment(): calendar_part = part calendar_charset = python_charset( calendar_part.get_content_charset()) if calendar_charset is None: calendar_charset = CHARSET_UTF8 try: calendar_content = calendar_part.get_payload( decode=True).decode(calendar_charset, errors="ignore") except Exception: write_log( args.log, "Cannot decode calendar part with charset '{}'".format( calendar_charset)) return ReturnCode.INVALID_ENCODING break email_modified = False if args.remove: if config.address_tag: # remove address tag string_tag = "{} ".format(config.address_tag) pattern_tag = re.compile(r'^"?{} '.format( re.escape(config.address_tag))) pattern_quote = re.compile(r'^".*"$') for header_keyword in ["To", "Cc"]: if header_keyword in email: header = "".join([ part if isinstance(part, str) else part.decode(python_charset(encoding), errors="ignore") if encoding else part.decode(CHARSET_UTF8, errors="ignore") for (part, encoding) in decode_header(", ".join([ str(header) for header in email.get_all(header_keyword) ]).replace("\n", "")) ]) list_address = extract_addresses(header) if list_address: header = "" header_modified = False for (prefix, address, suffix) in list_address: if prefix.startswith(";") or prefix.startswith( ","): prefix = prefix[1:] if prefix.endswith("<") and suffix.startswith(">"): prefix = prefix[:-1] suffix = suffix[1:] prefix = prefix.strip() suffix = suffix.strip() if re.search(pattern_tag, prefix): if prefix.startswith('"'): prefix = '"' + prefix[ len(config.address_tag) + 2:] else: prefix = prefix[len(config.address_tag) + 1:] header_modified = True if not string_ascii(prefix): if re.search(pattern_quote, prefix): prefix = prefix[1:-1] prefix = make_header([ (prefix.encode(CHARSET_UTF8), CHARSET_UTF8) ]).encode() if prefix: header += prefix + " " header += "<" + address + ">" if suffix: header += " " + suffix header += ", " if header_modified: del email[header_keyword] email[header_keyword] = header[:-2] email_modified = True if config.subject_tag and "Subject" in email: # remove subject tag header = "".join([ part if isinstance(part, str) else part.decode(python_charset(encoding), errors="ignore") if encoding else part.decode(CHARSET_UTF8, errors="ignore") for (part, encoding) in decode_header( str(email.get("Subject")).strip().replace("\n", "")) ]) match = re.search(r"{} ".format(re.escape(config.subject_tag)), header) if match is not None: del email["Subject"] email["Subject"] = header[:match.start()] + header[match.end( ):] email_modified = True if (config.text_tag or (config.address_tag and config.clean_text)) and text_part is not None: # remove text body tag body_modified = False if config.text_tag: split_tag = config.text_tag.split("\n") while not split_tag[-1]: del split_tag[-1] pattern_tag = re.compile("\\n".join( [r"(>+ )*" + re.escape(item) for item in split_tag]) + r"\n") match = re.search(pattern_tag, text_content) if match is not None: text_content = re.sub(pattern_tag, "", text_content) body_modified = True if config.address_tag and config.clean_text and string_tag in text_content: text_content = text_content.replace(string_tag, "") body_modified = True if body_modified: if HEADER_CTE in text_part: del text_part[HEADER_CTE] text_part.set_payload(text_content, charset=text_charset) email_modified = True if (config.html_tag or (config.address_tag and config.clean_html)) and html_part is not None: # remove html body tag body_modified = False soup = bs4.BeautifulSoup(html_content, features="html5lib") list_tag = soup.find_all("div", id=re.compile(r".*{}.*".format( re.escape(config.html_tag_id)))) if list_tag: for tag in list_tag: tag.decompose() try: html_content = soup.encode(html_charset).decode( html_charset) except Exception: write_log(args.log, "Error converting soup to string") return ReturnCode.ERROR body_modified = True if config.address_tag and config.clean_html and string_tag in html_content: html_content = html_content.replace(string_tag, "") body_modified = True if body_modified: if HEADER_CTE in html_part: del html_part[HEADER_CTE] html_part.set_payload(html_content, charset=html_charset) email_modified = True if config.calendar_tag and calendar_part is not None: # remove calendar tag match = re.search(r"\nORGANIZER;.*CN=([^:;\r\n]+)", calendar_content) if match is not None: organizer = match.group(1) organizer_start = match.start(1) organizer_end = match.end(1) match = re.search( r"^{} ".format(re.escape(config.calendar_tag)), organizer) if match is not None: if HEADER_CTE in calendar_part: del calendar_part[HEADER_CTE] if calendar_charset != CHARSET_UTF8 and not string_ascii( config.calendar_tag): calendar_charset = CHARSET_UTF8 calendar_part.set_payload( calendar_content[:organizer_start] + organizer[match.end():] + calendar_content[organizer_end:], charset=calendar_charset) email_modified = True else: if config.address_tag and "From" in email: # add address tag pattern_tag = re.compile(r'^"?{} '.format( re.escape(config.address_tag))) pattern_quote = re.compile(r'^".*"$') list_address = extract_addresses("".join([ part if isinstance(part, str) else part.decode(python_charset(encoding), errors="ignore") if encoding else part.decode(CHARSET_UTF8, errors="ignore") for (part, encoding) in decode_header( str(email.get("From")).replace("\n", "")) ])) if list_address: (prefix, address, suffix) = list_address[0] if prefix.endswith("<") and suffix.startswith(">"): prefix = prefix[:-1] suffix = suffix[1:] prefix = prefix.strip() suffix = suffix.strip() if not re.search(pattern_tag, prefix): prefix_new = "" for (index, char) in enumerate(prefix): if char == '"': if end_escape(prefix[:index]): prefix_new += char else: prefix_new += char if prefix_new: prefix = '"{} {}"'.format(config.address_tag, prefix_new) else: prefix = '"{} {}"'.format(config.address_tag, address) if not string_ascii(prefix): if re.search(pattern_quote, prefix): prefix = prefix[1:-1] prefix = make_header([(prefix.encode(CHARSET_UTF8), CHARSET_UTF8)]).encode() del email["From"] email["From"] = prefix + " <" + address + "> " + suffix email_modified = True if config.name_domain_list: # add address tag to external addresses in To/Cc header try: set_address = get_address_list(config.name_domain_list) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR pattern_domain = re.compile(r"^\S+@(\S+)") set_domain = { match.group(1).lower() for match in [ re.search(pattern_domain, address) for address in set_address ] if match is not None } for header_keyword in ["To", "Cc"]: if header_keyword in email: header = "".join([ part if isinstance(part, str) else part.decode(python_charset(encoding), errors="ignore") if encoding else part.decode(CHARSET_UTF8, errors="ignore") for (part, encoding) in decode_header(", ".join([ str(header) for header in email.get_all(header_keyword) ]).replace("\n", "")) ]) list_address = extract_addresses(header) if list_address: header = "" header_modified = False for (prefix, address, suffix) in list_address: if prefix.startswith(";") or prefix.startswith( ","): prefix = prefix[1:] if prefix.endswith("<") and suffix.startswith( ">"): prefix = prefix[:-1] suffix = suffix[1:] prefix = prefix.strip() suffix = suffix.strip() match = re.search(pattern_domain, address) if match and match.group(1).lower( ) not in set_domain and not re.search( pattern_tag, prefix): prefix_new = "" for (index, char) in enumerate(prefix): if char == '"': if end_escape(prefix[:index]): prefix_new += char else: prefix_new += char if prefix_new: prefix = '"{} {}"'.format( config.address_tag, prefix_new) else: prefix = '"{} {}"'.format( config.address_tag, address) header_modified = True if not string_ascii(prefix): if re.search(pattern_quote, prefix): prefix = prefix[1:-1] prefix = make_header([ (prefix.encode(CHARSET_UTF8), CHARSET_UTF8) ]).encode() if prefix: header += prefix + " " header += "<" + address + ">" if suffix: header += " " + suffix header += ", " if header_modified: del email[header_keyword] email[header_keyword] = header[:-2] email_modified = True if config.subject_tag and "Subject" in email: # add subject tag header = "".join([ part if isinstance(part, str) else part.decode(python_charset(encoding), errors="ignore") if encoding else part.decode(CHARSET_UTF8, errors="ignore") for (part, encoding) in decode_header( str(email.get("Subject")).strip().replace("\n", "")) ]) if not re.search(r"^{} ".format(re.escape(config.subject_tag)), header): header = "{} {}".format(config.subject_tag, header) del email["Subject"] email["Subject"] = header email_modified = True if config.text_tag and text_part is not None: # add text body tag if config.text_top: text_content = config.text_tag + text_content else: text_content += config.text_tag if text_charset != CHARSET_UTF8 and not string_ascii( config.text_tag): text_charset = CHARSET_UTF8 if HEADER_CTE in text_part: del text_part[HEADER_CTE] text_part.set_payload(text_content, charset=text_charset) email_modified = True if config.html_tag and html_part is not None: # add html body tag if config.html_top: match = re.search(r"<body[^>]*>", html_content) if match is not None: index = match.end() else: match = re.search(r"<html[^>]*>", html_content) if match is not None: index = match.end() else: index = 0 else: index = html_content.find("</body>") if index < 0: index = html_content.find("</html>") if index < 0: index = len(html_content) - 1 if HEADER_CTE in html_part: del html_part[HEADER_CTE] if html_charset != CHARSET_UTF8 and not string_ascii( config.html_tag): html_charset = CHARSET_UTF8 html_part.set_payload(html_content[:index] + '<div id="{}">{}</div>'.format( config.html_tag_id, config.html_tag) + html_content[index:], charset=html_charset) email_modified = True if config.calendar_tag and calendar_part is not None: # add calendar tag match = re.search(r"\nORGANIZER;.*CN=([^:;\r\n]+)", calendar_content) if match is not None: organizer = match.group(1) if not re.search( r"^{} ".format(re.escape(config.calendar_tag)), organizer): if HEADER_CTE in calendar_part: del calendar_part[HEADER_CTE] if calendar_charset != CHARSET_UTF8 and not string_ascii( config.calendar_tag): calendar_charset = CHARSET_UTF8 calendar_part.set_payload( calendar_content[:match.start(1)] + config.calendar_tag + " " + organizer + calendar_content[match.end(1):], charset=calendar_charset) email_modified = True if email_modified: try: with open(args.input, "wb") as f: f.write(email.as_bytes()) except Exception: write_log(args.log, "Error writing '{}'".format(args.input)) return ReturnCode.ERROR return ReturnCode.TAG_ADDED return ReturnCode.NOT_MODIFIED
def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR try: set_password = get_expression_list(config.name_expression_list) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if not set_password: write_log(args.log, "Password list is empty") return ReturnCode.ERROR try: pdf_file = fitz.open(args.input) except Exception: write_log(args.log, "Cannot open PDF file '{}'".format(args.input)) return ReturnCode.ERROR for password in set_password: if pdf_file.authenticate(password) != 0: break else: write_log(args.log, "Decryption failed") return ReturnCode.PROBLEM list_scan = list() if config.scan_sophos: list_scan.append(scan_sophos) if config.scan_kaspersky: list_scan.append(scan_kaspersky) if config.scan_avira: list_scan.append(scan_avira) if list_scan or config.remove_encryption: virus_found = None with NamedTemporaryFile(dir="/tmp") as path_tmpfile: path_file = path_tmpfile.name pdf_file.save(path_file) pdf_file.close() for scan in list_scan: try: virus_found = scan(path_file) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if virus_found is not None: break if virus_found is not None: write_log(args.log, "Virus '{}'".format(virus_found)) return ReturnCode.PROBLEM if config.remove_encryption: copyfile(path_file, args.input) return ReturnCode.DECRYPTED return ReturnCode.SUCCESS
def main(args): PATTERN_URL = re.compile(r"(https?://|www\.|ftp\.)\S+", re.IGNORECASE) PATTERN_DOMAIN = re.compile(r"^(https?://)?([^/]+)\S*$", re.IGNORECASE) try: config = get_config(args.config, CONFIG_PARAMETERS) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR try: image = Image.open(args.input) except Exception: write_log(args.log, "Cannot read image '{}'".format(args.input)) return ReturnCode.ERROR list_qr = decode(image) for qr_code in list_qr: try: text = qr_code.data.decode(CHARSET_UTF8) except Exception: text = None if text is not None and re.search(PATTERN_URL, text) is not None: try: set_url = get_url_list(config.name_url_blacklist) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR set_blacklist = { re.compile(url2regex(url), re.IGNORECASE) for url in set_url } try: set_url = get_url_list(config.name_url_whitelist) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR set_whitelist = { re.compile(url2regex(url), re.IGNORECASE) for url in set_url } for match in re.finditer(PATTERN_URL, text): url = match.group() for pattern in set_whitelist: if re.search(pattern, url) is not None: break else: for pattern in set_blacklist: if re.search(pattern, url) is not None: write_log( args.log, "'{}' listed on '{}'".format( url, config.name_url_blacklist)) return ReturnCode.MALICIOUS domain = re.search(PATTERN_DOMAIN, url).group(2).lower() while True: blacklist = domain_blacklisted(domain) if blacklist is not None: write_log( args.log, "'{}' listed on '{}'".format( domain, blacklist)) return ReturnCode.MALICIOUS index = domain.find(".") if index < 0: break domain = domain[index + 1:] return ReturnCode.OK
def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR try: set_password = get_expression_list(config.name_expression_list) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if not set_password: write_log(args.log, "Password list is empty") return ReturnCode.ERROR list_scan = list() if config.scan_sophos: list_scan.append(scan_sophos) if config.scan_kaspersky: list_scan.append(scan_kaspersky) if config.scan_avira: list_scan.append(scan_avira) virus_found = None if config.remove_encryption: buffer = BytesIO() zf_decrypted = pyzipper.AESZipFile(buffer, "w", compression=pyzipper.ZIP_LZMA) with TemporaryDirectory(dir="/tmp") as path_tmpdir: path_tmpdir = Path(path_tmpdir) if config.scan_avira: path_tmpdir.chmod(0o755) with pyzipper.AESZipFile(args.input, "r", compression=pyzipper.ZIP_LZMA, encryption=pyzipper.WZ_AES) as zf: for password in set_password: try: zf.pwd = password.encode(CHARSET_UTF8) for file_name in zf.namelist(): path_file = path_tmpdir.joinpath(file_name) data = zf.read(file_name) try: with open(path_file, "wb") as f: f.write(data) except Exception: write_log(args.log, "Cannot extract file '{}'".format(file_name)) return ReturnCode.ERROR path_file = str(path_file) for scan in list_scan: try: virus_found = scan(path_file) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if virus_found is not None: break if virus_found is not None: write_log(args.log, "Virus '{}'".format(virus_found)) return ReturnCode.PROBLEM if config.remove_encryption: zf_decrypted.writestr(file_name, data) break except RuntimeError: pass else: write_log(args.log, "Decryption failed") return ReturnCode.PROBLEM if config.remove_encryption: zf_decrypted.close() with open(args.input, "wb") as f: f.write(buffer.getvalue()) return ReturnCode.DECRYPTED return ReturnCode.SUCCESS
def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR try: set_expression = get_expression_list(config.name_expression_list) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if not set_expression: write_log(args.log, "Expression list is empty") return ReturnCode.ERROR part_text = None part_html = None for part in email.walk(): if part_text is None and part.get_content_type() == "text/plain": part_text = part elif part_html is None and part.get_content_type() == "text/html": part_html = part if part_text is not None and part_html is not None: break list_pattern = list() for expression in set_expression: list_pattern.append(re.compile(expression, re.IGNORECASE)) expression_found = False if part_text is not None: content_text = part_text.get_payload(decode=True).decode( "utf-8", errors="ignore") for pattern in list_pattern: match = re.search(pattern, content_text) if match: expression_found = True break if part_html is not None: content_html = part_html.get_payload(decode=True).decode( "utf-8", errors="ignore") if not expression_found: text_html = html2text(content_html) for pattern in list_pattern: match = re.search(pattern, text_html) if match: expression_found = True break if expression_found: if part_text is not None: set_url = { url for (_, url, _, _) in re.findall(PATTERN_URL, content_text) } for url in set_url: content_text = content_text.replace(url, config.url_replacement) part_text.set_payload(content_text) if part_html is not None: soup = BeautifulSoup(content_html, features="html5lib") for a in soup.findAll("a", href=True): a["href"] = config.url_replacement content_html = str(soup) part_html.set_payload(content_html) try: with open(args.input, "wb") as f: f.write(email.as_bytes()) except Exception: write_log(args.log, "Error writing '{}'".format(args.input)) return ReturnCode.ERROR return ReturnCode.URL_REPLACED return ReturnCode.NOT_MODIFIED
def main(args): try: xml_report = read_file(args.input, ignore_errors=True) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR try: syslog.openlog("dmarc_report", facility=syslog.LOG_MAIL) except Exception: write_log(args.log, "Cannot connect to syslog") return ReturnCode.ERROR try: tree = ElementTree.fromstring(xml_report) except Exception: write_log(args.log, "Cannot parse xml") return ReturnCode.ERROR name_org = None id_report = None date_begin = None date_end = None domain = None for child in tree: if child.tag == "report_metadata": for grandchild in child: if grandchild.tag == "org_name": name_org = grandchild.text elif grandchild.tag == "report_id": id_report = grandchild.text elif grandchild.tag == "date_range": for grandgrandchild in grandchild: if grandgrandchild.tag == "begin": date_begin = grandgrandchild.text elif grandgrandchild.tag == "end": date_end = grandgrandchild.text elif child.tag == "policy_published": for grandchild in child: if grandchild.tag == "domain": domain = grandchild.text elif child.tag == "record": for grandchild in child: if grandchild.tag == "row": ip_source = None count = None disposition = None dkim = None spf = None for grandgrandchild in grandchild: if grandgrandchild.tag == "source_ip": ip_source = grandgrandchild.text elif grandgrandchild.tag == "count": count = grandgrandchild.text elif grandgrandchild.tag == "policy_evaluated": for grandgrandgrandchild in grandgrandchild: if grandgrandgrandchild.tag == "disposition": disposition = grandgrandgrandchild.text elif grandgrandgrandchild.tag == "dkim": dkim = grandgrandgrandchild.text elif grandgrandgrandchild.tag == "spf": spf = grandgrandgrandchild.text syslog.syslog( TEMPLATE_SYSLOG.substitute(name_org=name_org, id_report=id_report, date_begin=date_begin, date_end=date_end, domain=domain, ip_source=ip_source, count=count, disposition=disposition, dkim=dkim, spf=spf)) break return ReturnCode.SUCCESS
def main(args): try: config = get_config(args.config, CONFIG_PARAMETERS) email = read_email(args.input) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if "Received" not in email: write_log(args.log, "Header received does not exist") return ReturnCode.ERROR header_received = str(email.get("Received")) if not header_received: write_log(args.log, "Header received is empty") return ReturnCode.ERROR sender_ip = re.search(r"\[([0-9.]+)\]", header_received) if not sender_ip: write_log(args.log, "Cannot find sender IP") return ReturnCode.ERROR sender_ip = sender_ip.group(1) try: ipv4_address = IPv4Address(sender_ip) except Exception: write_log(args.log, "Invalid sender IP") return ReturnCode.ERROR for network in config.internal_networks: try: ipv4_network = IPv4Network(network) except Exception: write_log(args.log, "Invalid CIDR '{}'".format(network)) return ReturnCode.ERROR if ipv4_address in ipv4_network: break else: return ReturnCode.SENDER_EXTERNAL if "From" not in email: write_log(args.log, "Header does not exist") return ReturnCode.ERROR header_from = str(email.get("From")) if not header_from: write_log(args.log, "Header from is empty") return ReturnCode.ERROR sender_address = extract_email_address(header_from) if not sender_address: write_log(args.log, "Cannot find sender address") return ReturnCode.ERROR sender_address = sender_address.group(1) index_at = sender_address.find("@") if index_at == -1: write_log(args.log, "Invalid sender address") return ReturnCode.ERROR sender_domain = sender_address[index_at + 1:] if not sender_domain: write_log(args.log, "Empty sender domain") return ReturnCode.ERROR try: set_address = get_address_list(config.name_address_list) except Exception as ex: write_log(args.log, ex) return ReturnCode.ERROR if not set_address: write_log(args.log, "Address list is empty") return ReturnCode.ERROR set_domain = { email_address[email_address.find("@") + 1:] for email_address in set_address } if sender_domain not in set_domain: return ReturnCode.SENDER_EXTERNAL return ReturnCode.SENDER_INTERNAL