| 1 | ''' |
| 2 | The module contains the MailIterator class. |
| 3 | |
| 4 | Copyright (c) 2012 Intra2net AG |
| 5 | Author: Plamen Dimitrov and Thomas Jarosch |
| 6 | |
| 7 | This program is free software: you can redistribute it and/or modify |
| 8 | it under the terms of the GNU General Public License as published by |
| 9 | the Free Software Foundation, either version 3 of the License, or |
| 10 | (at your option) any later version. |
| 11 | |
| 12 | This program is distributed in the hope that it will be useful, |
| 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | GNU General Public License for more details. |
| 16 | ''' |
| 17 | |
| 18 | import sys |
| 19 | import socket, imaplib |
| 20 | import re |
| 21 | import logging |
| 22 | |
| 23 | MAILBOX_RESP = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)') |
| 24 | UIDVAL_RESP = re.compile(r'(?P<name>.*) \(UIDVALIDITY (?P<uidval>.*)\)') |
| 25 | ACLS_RESP = re.compile(b'(?P<user>\S+) (?P<acls>\S+)') |
| 26 | |
| 27 | #imaplib.Debug = 4 |
| 28 | |
| 29 | class MailIterator: |
| 30 | """This class communicates with the e-mail server.""" |
| 31 | |
| 32 | # class attributes |
| 33 | # IMAP4_SSL for connection with an IMAP server |
| 34 | mail_con = None |
| 35 | # list of tuples (uidvalidity, mailboxname) for the retrieved mailboxes |
| 36 | mailboxes = None |
| 37 | # logged in status |
| 38 | logged_in = None |
| 39 | |
| 40 | def __init__(self, server, username, password): |
| 41 | """Creates a connection and a user session.""" |
| 42 | |
| 43 | # connect to server |
| 44 | try: |
| 45 | self.mail_con = imaplib.IMAP4(server) |
| 46 | |
| 47 | # socket functionality (currently unavailable) |
| 48 | #imap_socket = socket.socket(socket.AF_UNIX) |
| 49 | #imap_socket.connect("/var/imap/socket/imap") |
| 50 | #self.mail_con.sock = imap_socket |
| 51 | #self.mail_con.file = self.mail_con.sock.makefile('rb') |
| 52 | |
| 53 | logging.info("Connected to %s", server) |
| 54 | except (socket.error, self.mail_con.error) as ex: |
| 55 | logging.error("Could not connect to host: %s", ex) |
| 56 | sys.exit() |
| 57 | |
| 58 | # log in |
| 59 | try: |
| 60 | |
| 61 | # proxy authentication |
| 62 | #self.mail_con.login("cyrus", "geheim") |
| 63 | #self.mail_con.proxyauth(username) |
| 64 | |
| 65 | self.mail_con.login(username, password) |
| 66 | self.logged_in = True |
| 67 | logging.info("Logged in as %s.", username) |
| 68 | except self.mail_con.error as ex: |
| 69 | self.logged_in = False |
| 70 | logging.error("Could not log in as user %s: %s", username, ex) |
| 71 | sys.exit() |
| 72 | |
| 73 | # list mailboxes |
| 74 | try: |
| 75 | _result, mailboxes = self.mail_con.list() |
| 76 | except self.mail_con.error as ex: |
| 77 | logging.warning("Could not retrieve mailboxes for user %s: %s", username, ex) |
| 78 | self.mailboxes = [] |
| 79 | for mailbox in mailboxes: |
| 80 | mailbox = MAILBOX_RESP.match(mailbox.decode('iso-8859-1')).groups() |
| 81 | self.mailboxes.append(mailbox) |
| 82 | self.mailboxes = sorted(self.mailboxes, key=lambda box: box[2], reverse=True) |
| 83 | |
| 84 | return |
| 85 | |
| 86 | def __del__(self): |
| 87 | """Closes the connection and the user session.""" |
| 88 | |
| 89 | if self.logged_in: |
| 90 | self.mail_con.logout() |
| 91 | |
| 92 | def clear_inbox_acls(self, user): |
| 93 | """Resets the inbox acls for a given user.""" |
| 94 | |
| 95 | try: |
| 96 | _result, inbox_acls = self.mail_con.getacl("INBOX") |
| 97 | except self.mail_con.error as ex: |
| 98 | logging.warning("Could not get the acls of INBOX: %s", ex) |
| 99 | return |
| 100 | inbox_acls = ACLS_RESP.findall(inbox_acls[0][6:]) |
| 101 | logging.debug("Retrieved acls from INBOX are %s", inbox_acls) |
| 102 | for acl_ref in inbox_acls: |
| 103 | if acl_ref[0].decode('iso-8859-1') != user: |
| 104 | try: |
| 105 | self.mail_con.deleteacl("INBOX", acl_ref[0]) |
| 106 | logging.debug("Reset acls on INBOX for user %s", acl_ref[0].decode('iso-8859-1')) |
| 107 | except self.mail_con.error as ex: |
| 108 | logging.warning("Could not reset acls on INBOX for user %s: %s", acl_ref[0], ex) |
| 109 | |
| 110 | return |
| 111 | |
| 112 | def add_acls(self, mailbox, mb_acls, original_user, target_user): |
| 113 | """Add acls to mailbox.""" |
| 114 | |
| 115 | mailbox = mailbox.replace("user/" + original_user + "/", "INBOX/") |
| 116 | for acl_user in mb_acls: |
| 117 | # (in case target user != original user): |
| 118 | # - don't overwrite acls eventually set for the current targetuser |
| 119 | # - don't set the default owner acls for the new folder |
| 120 | if acl_user != target_user and acl_user != original_user: |
| 121 | try: |
| 122 | self.mail_con.setacl(mailbox, acl_user, mb_acls[acl_user]) |
| 123 | logging.debug("Set acls %s for user %s on mailbox %s", mb_acls[acl_user], acl_user, mailbox) |
| 124 | except self.mail_con.error as ex: |
| 125 | logging.warning("Could not set acls %s for user %s on mailbox %s: %s", mb_acls[acl_user], acl_user, mailbox, ex) |
| 126 | |
| 127 | return |
| 128 | |
| 129 | def delete_mailboxes(self, deleted_mailbox): |
| 130 | """Delete specified mailbox or empty inbox.""" |
| 131 | |
| 132 | for mailbox in self.mailboxes: |
| 133 | pattern = '^\"?' + deleted_mailbox |
| 134 | # if INBOX it cannot be deleted so add delimiter |
| 135 | if deleted_mailbox == "INBOX": |
| 136 | pattern += mailbox[1] |
| 137 | if re.compile(pattern).match(mailbox[2]): |
| 138 | result, data = self.mail_con.delete(mailbox[2]) |
| 139 | logging.debug("Deleted mailbox %s", mailbox[2]) |
| 140 | if result != "OK": |
| 141 | logging.warning("Could not delete mailbox %s: %s", mailbox[2], data[0]) |
| 142 | |
| 143 | return |
| 144 | |
| 145 | def create_mailbox(self, mailbox): |
| 146 | """Create new mailbox to inject messages.""" |
| 147 | |
| 148 | if mailbox != "INBOX": |
| 149 | result, data = self.mail_con.create(mailbox) |
| 150 | logging.debug("Creating mailbox %s", mailbox) |
| 151 | if result != "OK": |
| 152 | logging.warning("Could not create mailbox %s: %s", mailbox, data[0].decode('iso-8859-1')) |
| 153 | |
| 154 | return |
| 155 | |
| 156 | def inject_message(self, message, mailbox, internal_date): |
| 157 | """Inject a message into a mailbox.""" |
| 158 | |
| 159 | result, data = self.mail_con.append(mailbox, "\\Seen", internal_date, message.encode()) |
| 160 | logging.debug("Appending message to mailbox %s", mailbox) |
| 161 | if result != "OK": |
| 162 | logging.warning("Could not append an e-mail to the mailbox %s: %s.", mailbox, data[0].decode('iso-8859-1')) |
| 163 | #logging.debug("Email content:\n%s", message) |
| 164 | |
| 165 | return |