Initial submission of working tool
authorPlamen Dimitrov <plamen.dimitrov@intra2net.com>
Wed, 4 Jul 2012 14:08:56 +0000 (16:08 +0200)
committerPlamen Dimitrov <plamen.dimitrov@intra2net.com>
Wed, 4 Jul 2012 14:08:56 +0000 (16:08 +0200)
file_iterator.py [new file with mode: 0644]
mail_iterator.py [new file with mode: 0644]
restore_mail_inject.py [new file with mode: 0644]

diff --git a/file_iterator.py b/file_iterator.py
new file mode 100644 (file)
index 0000000..1e464fb
--- /dev/null
@@ -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 (file)
index 0000000..e8be1e0
--- /dev/null
@@ -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<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
+UIDVAL_RESP = re.compile(r'(?P<name>.*) \(UIDVALIDITY (?P<uidval>.*)\)')
+
+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 (file)
index 0000000..fc41f01
--- /dev/null
@@ -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()