Clear inbox acls and other fixes based on autotest validation
[imap-restore-mail] / src / mail_iterator.py
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