From 7b7d20b1a4aaa5ece70c05e5f42f730a9cb58f0a Mon Sep 17 00:00:00 2001 From: Paolo Schiro Date: Fri, 6 Sep 2024 21:44:57 +0200 Subject: [PATCH] Added input report maildir connector, issue #82 (#555) Co-authored-by: Paolo Schiro --- docs/source/usage.md | 4 +++ parsedmarc/cli.py | 23 ++++++++++++++- parsedmarc/mail/maildir.py | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 parsedmarc/mail/maildir.py diff --git a/docs/source/usage.md b/docs/source/usage.md index e59f2c31..c815c158 100644 --- a/docs/source/usage.md +++ b/docs/source/usage.md @@ -355,6 +355,10 @@ 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) + :::{warning} It is **strongly recommended** to **not** use the `nameservers` setting. By default, `parsedmarc` uses diff --git a/parsedmarc/cli.py b/parsedmarc/cli.py index 406ce732..efa32480 100644 --- a/parsedmarc/cli.py +++ b/parsedmarc/cli.py @@ -20,7 +20,8 @@ parse_report_file, get_dmarc_reports_from_mbox, elastic, opensearch, \ kafkaclient, splunk, save_output, email_results, ParserError, \ __version__, InvalidDMARCReport, s3, syslog, loganalytics, gelf -from parsedmarc.mail import IMAPConnection, MSGraphConnection, GmailConnection +from parsedmarc.mail import IMAPConnection, MSGraphConnection, GmailConnection, \ + MaildirConnection from parsedmarc.mail.graph import AuthMethod from parsedmarc.log import logger @@ -504,6 +505,8 @@ def process_reports(reports_): gmail_api_paginate_messages=True, gmail_api_scopes=[], gmail_api_oauth2_port=8080, + maildir_path = None, + maildir_create = False, log_file=args.log_file, n_procs=1, ip_db_path=None, @@ -1008,6 +1011,13 @@ def process_reports(reports_): opts.gmail_api_oauth2_port = \ gmail_api_config.get("oauth2_port", 8080) + if "maildir" in config.sections(): + maildir_api_config = config["maildir"] + opts.maildir_path = \ + maildir_api_config.get("maildir_path") + opts.maildir_create = \ + maildir_api_config.get("maildir_create") + if "log_analytics" in config.sections(): log_analytics_config = config["log_analytics"] opts.la_client_id = \ @@ -1072,6 +1082,7 @@ def process_reports(reports_): if opts.imap_host is None \ and opts.graph_client_id is None \ and opts.gmail_api_credentials_file is None \ + and opts.maildir_path is None \ and len(opts.file_path) == 0: logger.error("You must supply input files or a mailbox connection") exit(1) @@ -1380,6 +1391,16 @@ def process_reports(reports_): logger.exception("Gmail API Error") exit(1) + if opts.maildir_path: + try: + mailbox_connection = MaildirConnection( + maildir_path=opts.maildir_path, + maildir_create=opts.maildir_create, + ) + except Exception: + logger.exception("Maildir Error") + exit(1) + if mailbox_connection: try: reports = get_dmarc_reports_from_mailbox( diff --git a/parsedmarc/mail/maildir.py b/parsedmarc/mail/maildir.py new file mode 100644 index 00000000..f8c70c28 --- /dev/null +++ b/parsedmarc/mail/maildir.py @@ -0,0 +1,60 @@ +from time import sleep + +#from imapclient.exceptions import IMAPClientError +#from mailsuite.imap import IMAPClient +from socket import timeout + +from parsedmarc.log import logger +from parsedmarc.mail.mailbox_connection import MailboxConnection +#import email +import mailbox +import os + +class MaildirConnection(MailboxConnection): + def __init__(self, + maildir_path=None, + maildir_create=False, + ): + self._maildir_path = maildir_path + self._maildir_create = maildir_create + maildir_owner=os.stat(maildir_path).st_uid + if os.getuid() != maildir_owner: + if os.getuid() == 0: + logger.warning("Switching uid to {} to access Maildir".format(maildir_owner)) + os.setuid(maildir_owner) + else: + raise Exception('runtime uid {} differ from maildir {} owner {}'.format(os.getuid().maildir_path,maildir_owner)) + self._client = mailbox.Maildir(maildir_path, create=maildir_create) + self._subfolder_client={} + + def create_folder(self, folder_name: str): + self._subfolder_client[folder_name]=self._client.add_folder(folder_name) + self._client.add_folder(folder_name) + + def fetch_messages(self, reports_folder: str, **kwargs): + return self._client.keys() + + def fetch_message(self, message_id): + return self._client.get(message_id).as_string() + + def delete_message(self, message_id: str): + self._client.remove(message_id) + + def move_message(self, message_id: str, folder_name: str): + message_data=self._client.get(message_id) + if folder_name not in self._subfolder_client.keys(): + self._subfolder_client = mailbox.Maildir(os.join(maildir_path,folder_name), create=maildir_create) + self._subfolder_client[folder_name].add(message_data) + self._client.remove(message_id) + + def keepalive(self): + return + + def watch(self, check_callback, check_timeout): + while True: + try: + check_callback(self) + except Exception as e: + logger.warning("Maildir init error. {0}".format(e)) + sleep(check_timeout) + \ No newline at end of file