From 67e2ec0236698cccdb86f3d7adb90049d5bb7f1b Mon Sep 17 00:00:00 2001 From: Plamen Dimitrov Date: Wed, 4 Jul 2012 16:08:56 +0200 Subject: [PATCH] Initial submission of working tool --- file_iterator.py | 105 +++++++++++++++++++++++++++++++++++++++++++ mail_iterator.py | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ restore_mail_inject.py | 75 +++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+), 0 deletions(-) create mode 100644 file_iterator.py create mode 100644 mail_iterator.py create mode 100644 restore_mail_inject.py diff --git a/file_iterator.py b/file_iterator.py new file mode 100644 index 0000000..1e464fb --- /dev/null +++ b/file_iterator.py @@ -0,0 +1,105 @@ +''' +restore-mail-inject.py - Tool to inject mails via IMAP + +Copyright (c) 2012 Intra2net AG +''' + +import os +import re + +MAIL_FILENAME = re.compile("^[0-9]+\.$") + +class FileIterator: + """This class iterates through the e-mail files.""" + + # class attributes + # mailbox database? + mboxdb = None + # mailboxes created during file traversal + created_mailboxes = None + # mailboxes to update during file traversal + acl_mailboxes = None + + def __init__(self, mboxlist = ""): + """Creates a connection and a user session.""" + self.mboxdb = [] + if(mboxlist != ""): + try: + acl_file = open(mboxlist, 'r') + self.mboxdb = self._mbox_read(acl_file) + acl_file.close() + except IOError: + print("Could not open mboxlist file %s." % mboxlist) + mboxlist = "" + return + + def __del__(self): + """Closes the connection and the user session.""" + return + + def _mbox_read(self, acl_file): + "Reads something." + # + return + + def _message_read(self, filename): + """Retrieves a message from the message file.""" + try: + with open(filename, "r") as msgfile: + message = msgfile.read() + except: + print("Could not open the e-mail file %s.", filename) + return message + + def load_mails(self, filepath, mailpath): + """Loads all e-mails from file hierarchy. + This recursive generator always returns a tuple of + the next found (e-mail, mailbox to store, internaldate).""" + # reset created mailboxes and mailboxes for acl update + self.created_mailboxes = [] + self.acl_mailboxes = [] + #print("Entered directory %s -> %s." % (filepath, mailpath)) + try: + filepath = os.path.abspath(filepath) + os.chdir(filepath) + except OSError: + print("Can't open the directory %s." % filepath) + return + # mark mailboxes that should be created + self.created_mailboxes.append(mailpath) + subpaths = os.listdir(filepath) + for subpath in subpaths: + #print("Now checking subpath %s in %s" % (subpath, filepath)) + if(subpath == "." or subpath == ".."): + continue + new_filepath = filepath + "/" + subpath + if (os.path.isfile(new_filepath)): + if(os.path.getsize(new_filepath) == 0): + print("Skipping empty file %s." % subpath) + else: + if(MAIL_FILENAME.match(subpath)): + print("Injecting file %s." % subpath) + try: + message = self._message_read(new_filepath) + # suggest file modification date for internaldate + yield (message, mailpath, os.path.getmtime(new_filepath)) + except: + print("Could not retrieve mail from file: %s", new_filepath) + else: + if(os.path.isdir(new_filepath)): + # cyrus ^ vs . storage replacement + subpath = subpath.replace("^", ".") + new_mailpath = mailpath + "/" + subpath + #print("Inserting mails from directory %s into mailbox %s." % (new_filepath, new_mailpath)) + # load_mails($mboxdbref, $origuser, $targetuser) + rcrs_generator = self.load_mails(new_filepath, new_mailpath) + # you enter the generator in the for loop + for rcr in rcrs_generator: + yield rcr + #print("Done with directory %s and mailbox %s." % (new_filepath, new_mailpath)) + # mark mailboxes that need acl update + self.acl_mailboxes.append(mailpath) + return + # + # + # \ No newline at end of file diff --git a/mail_iterator.py b/mail_iterator.py new file mode 100644 index 0000000..e8be1e0 --- /dev/null +++ b/mail_iterator.py @@ -0,0 +1,115 @@ +''' +restore-mail-inject.py - Tool to inject mails via IMAP + +Copyright (c) 2012 Intra2net AG +''' + +import imaplib +import re + +MAILBOX_RESP = re.compile(r'\((?P.*?)\) "(?P.*)" (?P.*)') +UIDVAL_RESP = re.compile(r'(?P.*) \(UIDVALIDITY (?P.*)\)') + +class MailIterator: + """This class communicates with the e-mail server.""" + + # class attributes + # IMAP4_SSL for connection with an IMAP server + mail_con = None + # list of tuples (uidvalidity, mailboxname) for the retrieved mailboxes + mailboxes = None + # logged in status + logged_in = None + + def __init__(self, server, username, password): + """Creates a connection and a user session.""" + # authentication + try: + self.mail_con = imaplib.IMAP4_SSL(server) + self.mail_con.login(username, password) + print("Logged in as %s." % username) + except: + self.logged_in = False + print("Could not log in as user " + username + ".") + raise UserWarning + self.logged_in = True + + # prepare mailboxes + _result, mailboxes = self.mail_con.list() + self.mailboxes = [] + for mailbox in mailboxes: + mailbox = MAILBOX_RESP.match(mailbox.decode('iso-8859-1')).groups() + self.mailboxes.append(mailbox) + self.mailboxes = sorted(self.mailboxes, key=lambda box: box[2], reverse=True) + return + + def __del__(self): + """Closes the connection and the user session.""" + if(self.logged_in): + try: + self.mail_con.close() + self.mail_con.logout() + except: + pass + + def clear_inbox_acls(self, user): + """Resets the inbox acls for each user.""" + try: + hashref = self.mail_con.getacl("INBOX") + except: + print("Could not get the acls of INBOX.") + print(hashref) + for acluser in hashref: + if(acluser != user): + try: + #print(self.mail_con.setacl("INBOX", acluser, "")) + print("Reset acls on INBOX for user %s" % acluser) + except: + print("Could not reset acls on INBOX for user %s" % acluser) + return + + def add_acls(self, mailbox, config): + """Add acls to mailbox.""" + + # change encoding to internal cyrus format + mailbox = mailbox.replace(".", "^") + mailbox = mailbox.replace("/", ".") + + return + + def delete_mailboxes(self, deleted_mailbox): + """Delete specified mailbox or empty inbox.""" + for mailbox in self.mailboxes: + pattern = '^\"?' + deleted_mailbox + # if INBOX it cannot be deleted so add delimiter + if (deleted_mailbox == "INBOX"): + pattern += mailbox[1] + if(re.compile(pattern).match(mailbox[2])): + result, data = self.mail_con.delete(mailbox[2]) + if(result == "OK"): + print("Deleted mailbox %s" % mailbox[2]) + else: + print("Could not delete folder %s: %s" % (mailbox[2], data[0])) + return + + def create_mailbox(self, mailbox): + """Create new mailbox to inject messages.""" + if(mailbox != "INBOX"): + result, data = self.mail_con.create(mailbox) + if(result == "OK"): + print("Creating mailbox %s" % mailbox) + else: + print("Could not create mailbox %s: %s" % (mailbox, data[0])) + return + + def inject_message(self, message, mailbox, internal_date): + """Inject a message into a mailbox.""" + + #$imap->append_file($folder, $full_path, undef, "\\Seen", true) + try: + result, data = self.mail_con.append(mailbox, "\\Seen", + internal_date, message) + print("Appending message to mailbox %s" % mailbox) + except: + raise UserWarning("Could not append the e-mail." % message) + return \ No newline at end of file diff --git a/restore_mail_inject.py b/restore_mail_inject.py new file mode 100644 index 0000000..fc41f01 --- /dev/null +++ b/restore_mail_inject.py @@ -0,0 +1,75 @@ +''' +restore-mail-inject.py - Tool to inject mails via IMAP + +Copyright (c) 2012 Intra2net AG +''' + +import argparse +from mail_iterator import MailIterator +from file_iterator import FileIterator + +def main(): + """Main function.""" + + # prepare configuration + args = configure_args() + print("The module restore_mail_inject.py started with user %s, folder %s and source %s." % + (args.user, args.folder, args.srcdir)) + + # connect + try: + session = MailIterator("gwt-intranator.m.i2n", "mit.punkt", "mit.punkt") + #session = MailIterator("/var/imap/socket/imap", "cyrus", "geheim") + storage = FileIterator(args.mboxlist) + except UserWarning: + print("Couldn't connect to IMAP server.") + return + + # delete olf IMAP folders if no append requested + if(not args.append): + session.delete_mailboxes(args.folder) + if(args.folder == "INBOX"): + session.clear_inbox_acls(args.user) + + # inject emails + path_generator = storage.load_mails(args.srcdir, args.folder) + for message, mailbox, date_modified in path_generator: + for new_mailbox in storage.created_mailboxes: + session.create_mailbox(new_mailbox) + for acl_mailbox in storage.acl_mailboxes: + session.add_acls(acl_mailbox, args) + session.inject_message(message, mailbox, date_modified) + # add acls after all subfolders or their acls will be derived from parent folder + + print("Finished injecting mails.") + + return + +def configure_args(): + """Configure arguments and return them.""" + # parse arguments + parser = argparse.ArgumentParser(description="Tool to inject mails via IMAP.") + parser.add_argument('-u', '--username', dest='user', action='store', + required=True, help='user to store mails to') + parser.add_argument('-f', '--foldername', dest='folder', action='store', + default="INBOX", help='folder to store mails to - if not specified we overwrite INBOX') + parser.add_argument('-s', '--sourcedir', dest='srcdir', action='store', + required=True, help='folder to read mail from') + parser.add_argument('-m', '--mboxlist', dest='mboxlist', action='store', + default="", help='mboxlist file (flat file format) to read the ACLs from') + parser.add_argument('-o', '--ouser', dest='ouser', action='store', + default="", help='name of the original user (=username if not specified)') + parser.add_argument('-a', '--append', dest='append', action='store_true', + default=False, help="append mails, don't delete anything") + args = parser.parse_args() + + if (args.folder != "INBOX"): + args.folder = "INBOX/" + args.folder + if (args.ouser == ""): + args.ouser = args.user + + return args + + +if(__name__ == "__main__"): + main() -- 1.7.1