--- /dev/null
+'''
+imap-mark-seen.py - Tool to mark all e-mails as seen
+
+Copyright (c) 2012 Intra2net AG
+Author: Plamen Dimitrov and Thomas Jarosch
+'''
+import logging
+import argparse, getpass
+from mail_iterator import MailIterator
+from warnings_handler import WarningsHandler
+
+# logging settings
+LOG_FILENAME = "imap_mark_seen.log"
+LOG_FILE_LEVEL = logging.DEBUG
+LOG_SHELL_LEVEL = logging.INFO
+LOG_UNCLEAN_EXIT_LEVEL = logging.WARNING
+
+def main():
+ """Main function."""
+
+ # prepare configuration
+ args = configure_args()
+ warnings_handler = prepare_logger()
+ logging.info("Marking messages as seen from %s of %s", args.folder, args.user)
+ psw = getpass.getpass()
+
+ # prepare simple mail iterator and iterate throug mailboxes
+ session = MailIterator(args.server, args.user, psw)
+ for mailbox in session:
+ if args.folder != "all folders" and ("INBOX/" + args.folder) not in mailbox[2]:
+ continue
+ try:
+ mail_ids = session.fetch_messages()
+ except UserWarning as ex:
+ logging.error(ex)
+ continue
+ for mid in mail_ids:
+ logging.debug("Setting message %s from mailbox %s as seen",
+ mid.decode('iso-8859-1'), mailbox[2])
+ try:
+ session.update_message(mid.decode('iso-8859-1'), mailbox[2])
+ except UserWarning as ex:
+ logging.error(ex)
+ continue
+
+ logging.info("Finished marking messages as seen. Exiting with code %s.", warnings_handler.detected_problems)
+ return int(warnings_handler.detected_problems > 0)
+
+def configure_args():
+ """Configure arguments and return them."""
+
+ # parse arguments
+ parser = argparse.ArgumentParser(description="Tool to mark messages as seen.")
+ parser.add_argument('-u', '--user', dest='user', action='store',
+ required=True, help='mark all messages as seen for a single user')
+ parser.add_argument('-f', '--folder', dest='folder', action='store',
+ default="all folders", help='only mark given folder as seen')
+ parser.add_argument('-s', '--server', dest='server', action='store',
+ default="localhost", help='imap server name with default localhost')
+ args = parser.parse_args()
+
+ return args
+
+def prepare_logger():
+ """Sets up the logging functionality"""
+
+ # reset the log
+ with open(LOG_FILENAME, 'w'):
+ pass
+
+ # add basic configuration
+ logging.basicConfig(filename=LOG_FILENAME,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=LOG_FILE_LEVEL)
+
+ # add a handler for a console output
+ default_logger = logging.getLogger('')
+ console = logging.StreamHandler()
+ console.setLevel(LOG_SHELL_LEVEL)
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ console.setFormatter(formatter)
+ default_logger.addHandler(console)
+
+ # add a handler for warnings counting
+ warnings_handler = WarningsHandler()
+ warnings_handler.setLevel(LOG_UNCLEAN_EXIT_LEVEL)
+ default_logger.addHandler(warnings_handler)
+
+ return warnings_handler
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+"""Collection of functions for mass IMAP server manipulation"""
+import imaplib
+import logging
+import re
+
+SERVER = 'intranator.m.i2n'
+USERNAME = 'FIXME'
+PASSWORD = 'FIXME'
+ANNOTATION_SHARED_SEEN = '"/vendor/cmu/cyrus-imapd/sharedseen"'
+
+MAILBOX_RESP = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
+
+# imaplib.Debug = 5
+
+class ImapAction:
+ """Class for mass IMAP manipulation"""
+
+ def __init__(self, server, username, password):
+ self.mail_con = None
+ self.mailboxes = None
+
+ # connect to server
+ try:
+ self.mail_con = imaplib.IMAP4(server)
+ except Exception as ex:
+ raise UserWarning("Could not connect to host %s: %s" % (server, ex))
+
+ # log in
+ try:
+ self.mail_con.login(username, password)
+ logging.info("Logged in as %s.", username)
+ except:
+ self.logged_in = False
+ raise UserWarning("Could not log in as user " + username)
+ self.logged_in = True
+
+ # list mailboxes
+ try:
+ _result, self.mailboxes = self.mail_con.list()
+ except (self.mail_con.error):
+ raise UserWarning("Could not retrieve mailboxes for user " + username)
+
+ def mass_set_group_rights(self, identifier, rights):
+ """Mass set ACL rights on mailboxes"""
+ for raw_mailbox in self.mailboxes:
+ mailbox = MAILBOX_RESP.match(raw_mailbox.decode('iso-8859-1')).groups()[2]
+ if not mailbox.startswith('\"INBOX'):
+ print('Skipping mailbox %s' % mailbox)
+ continue
+ print('Modifying ACL for mailbox %s' % mailbox)
+
+ self.mail_con.setacl(mailbox, identifier, rights)
+
+ def mass_set_shared_seen(self):
+ """Enable shared seen state on all mailboxes"""
+ for raw_mailbox in self.mailboxes:
+ mailbox = MAILBOX_RESP.match(raw_mailbox.decode('iso-8859-1')).groups()[2]
+ if not mailbox.startswith('\"INBOX'):
+ print('Skipping mailbox %s' % mailbox)
+ continue
+ print('Setting sharedseen annotation on mailbox %s' % mailbox)
+
+ self.mail_con.setannotation(mailbox, ANNOTATION_SHARED_SEEN, '("value.shared" "true")')
+
+ def list_sharedseen_state(self):
+ """List shared seen state on all mailboxes"""
+ for raw_mailbox in self.mailboxes:
+ mailbox = MAILBOX_RESP.match(raw_mailbox.decode('iso-8859-1')).groups()[2]
+ if not mailbox.startswith('\"INBOX'):
+ print('Skipping mailbox %s' % mailbox)
+ continue
+
+ raw_annotations = self.mail_con.getannotation(mailbox, ANNOTATION_SHARED_SEEN, '"value.shared"')
+
+ # Hack to parse annotation result
+ sharedseen_enabled = False
+ if str(raw_annotations[1]).find('("value.shared" "true")') != -1:
+ sharedseen_enabled = True
+
+ print('sharedseen state on mailbox %s: %s' % (mailbox, sharedseen_enabled))
+
+def main():
+ """Main function"""
+ imap = ImapAction(SERVER, USERNAME, PASSWORD)
+
+ imap.mass_set_group_rights("group:infokonto", "lrswipcd")
+ imap.mass_set_shared_seen()
+
+ imap.list_sharedseen_state()
+
+if(__name__ == "__main__"):
+ main()
--- /dev/null
+'''
+mail_iterator.py - The module contains the MailIterator class.
+
+Copyright (c) 2012 Intra2net AG
+Author: Plamen Dimitrov and Thomas Jarosch
+'''
+
+import sys
+import imaplib, socket
+import re
+import logging
+
+MAILBOX_RESP = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
+
+#imaplib.Debug = 4
+
+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."""
+
+ # connect to server
+ try:
+ self.mail_con = imaplib.IMAP4(server)
+ logging.info("Connected to %s", server)
+ except socket.error as ex:
+ logging.error("Could not connect to host: %s", ex)
+ sys.exit()
+
+ # log in
+ try:
+ self.mail_con.login(username, password)
+ self.logged_in = True
+ logging.info("Logged in as %s.", username)
+ except self.mail_con.error as ex:
+ self.logged_in = False
+ logging.error("Could not log in as user %s: %s", username, ex)
+ sys.exit()
+
+ # list mailboxes
+ try:
+ _result, mailboxes = self.mail_con.list()
+ except self.mail_con.error as ex:
+ logging.warning("Could not retrieve mailboxes for user %s: %s", username, ex)
+ 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):
+ self.mail_con.close()
+ self.mail_con.logout()
+
+ def __iter__(self):
+ """Iterates through all mailboxes, returns (children,delimiter,name)."""
+
+ for mailbox in self.mailboxes:
+ logging.debug("Checking mailbox %s", mailbox[2])
+ # select mailbox if writable
+ try:
+ self.mail_con.select(mailbox[2])
+ logging.info("Processing mailbox %s", mailbox[2])
+ except self.mail_con.readonly:
+ logging.warning("Mailbox %s is not writable and therefore skipped.", mailbox[2])
+ continue
+ yield mailbox
+
+ def fetch_messages(self):
+ """Fetches the messages from the current mailbox, return list of uids."""
+
+ try:
+ # Work around unsolicited server responses in imaplib by clearing them
+ self.mail_con.response('SEARCH')
+ _result, data = self.mail_con.uid('search', None, "ALL")
+ except (self.mail_con.error):
+ raise UserWarning("Could not fetch messages.")
+ mailid_list = data[0].split()
+ return mailid_list
+
+ def update_message(self, mid, mailbox):
+ """Sets the \\Seen flag for a message."""
+
+ try:
+ # Work around unsolicited server responses in imaplib by clearing them
+ self.mail_con.response('STORE')
+ result, data = self.mail_con.uid('STORE', mid, '+FLAGS', "(\Seen)")
+ logging.debug("New flags for message %s are %s", mid, data)
+ except (self.mail_con.error):
+ raise UserWarning("Could not set the flags for the e-mail " + mid.decode('iso-8859-1') + ".")
+ self.mail_con.expunge()
--- /dev/null
+'''
+restore-mail-inject.py - Tool to inject mails via IMAP
+
+Copyright (c) 2012 Intra2net AG
+Author: Plamen Dimitrov and Thomas Jarosch
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+'''
+import logging
+
+class WarningsHandler(logging.Handler):
+ """This class iterates through the e-mail files."""
+
+ # class attributes
+ detected_problems = None
+
+ def __init__(self):
+ """Initialize a handler to count number of warnings."""
+
+ logging.Handler.__init__(self)
+ self.detected_problems = 0
+
+ def emit(self, record):
+ """Increase number of warnings found"""
+
+ self.detected_problems += 1
+
\ No newline at end of file