Clear inbox acls and other fixes based on autotest validation
[imap-restore-mail] / src / mail_iterator.py
... / ...
CommitLineData
1'''
2The module contains the MailIterator class.
3
4Copyright (c) 2012 Intra2net AG
5Author: Plamen Dimitrov and Thomas Jarosch
6
7This program is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16'''
17
18import sys
19import socket, imaplib
20import re
21import logging
22
23MAILBOX_RESP = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
24UIDVAL_RESP = re.compile(r'(?P<name>.*) \(UIDVALIDITY (?P<uidval>.*)\)')
25ACLS_RESP = re.compile(b'(?P<user>\S+) (?P<acls>\S+)')
26
27#imaplib.Debug = 4
28
29class 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