e46c31226d9810f82329c407b52c59304062832f
[imap-restore-mail] / src / mail_iterator.py
1 '''
2 restore-mail-inject.py - Tool to inject mails via IMAP
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>.*) (?P<acls>.*)')
26
27 class MailIterator:
28     """This class communicates with the e-mail server."""
29
30     # class attributes
31     # IMAP4_SSL for connection with an IMAP server
32     mail_con = None
33     # list of tuples (uidvalidity, mailboxname) for the retrieved mailboxes
34     mailboxes = None
35     # logged in status
36     logged_in = None
37
38     def __init__(self, server, username, password):
39         """Creates a connection and a user session."""
40
41         # connect to server
42         try:
43             self.mail_con = imaplib.IMAP4(server)
44
45             # socket functionality (currently unavailable)
46             #imap_socket = socket.socket(socket.AF_UNIX)
47             #imap_socket.connect("/var/imap/socket/imap")
48             #self.mail_con.sock = imap_socket
49             #self.mail_con.file = self.mail_con.sock.makefile('rb')
50
51             logging.info("Connected to %s", server)
52         except (socket.error, self.mail_con.error) as ex:
53             logging.error("Could not connect to host: %s", ex)
54             sys.exit()
55
56         # log in
57         try:
58
59             # proxy authentication
60             #self.mail_con.login("cyrus", "geheim")
61             #self.mail_con.proxyauth(username)
62
63             self.mail_con.login(username, password)
64             self.logged_in = True
65             logging.info("Logged in as %s.", username)
66         except self.mail_con.error as ex:
67             self.logged_in = False
68             logging.error("Could not log in as user %s: %s", username, ex)
69             sys.exit()
70
71         # list mailboxes
72         try:
73             _result, mailboxes = self.mail_con.list()
74         except self.mail_con.error as ex:
75             logging.warning("Could not retrieve mailboxes for user %s: %s", username, ex)
76         self.mailboxes = []
77         for mailbox in mailboxes:
78             mailbox = MAILBOX_RESP.match(mailbox.decode('iso-8859-1')).groups()
79             self.mailboxes.append(mailbox)
80         self.mailboxes = sorted(self.mailboxes, key=lambda box: box[2], reverse=True)
81
82         return
83
84     def __del__(self):
85         """Closes the connection and the user session."""
86
87         if self.logged_in:
88             self.mail_con.logout()
89
90     def clear_inbox_acls(self, user):
91         """Resets the inbox acls for a given user."""
92
93         try:
94             _result, inbox_acls = self.mail_con.getacl("INBOX")
95         except self.mail_con.error as ex:
96             logging.warning("Could not get the acls of INBOX: %s", ex)
97             return
98         inbox_acls = ACLS_RESP.findall(inbox_acls[0][6:])
99         logging.debug("Retrieved acls from INBOX are %s", inbox_acls)
100         for acl_ref in inbox_acls:
101             if acl_ref[0] != user:
102                 try:
103                     self.mail_con.deleteacl("INBOX", acl_ref[0])
104                     logging.debug("Reset acls on INBOX for user %s", acl_ref[0].decode('iso-8859-1'))
105                 except self.mail_con.error as ex:
106                     logging.warning("Could not reset acls on INBOX for user %s: %s", acl_ref[0], ex)
107
108         return
109
110     def add_acls(self, mailbox, mailbox_list, original_user, target_user):
111         """Add acls to mailbox."""
112
113         # change encoding to internal cyrus format and make folder absolute
114         mailbox = mailbox.replace("INBOX/", "user/" + original_user + "/")
115         mailbox = mailbox.replace(".", "^")
116         mailbox = mailbox.replace("/", ".")
117
118         # find folder to set all acls
119         try:
120             mbox_acls = mailbox_list[mailbox]
121         except KeyError:
122             # no rights for the mailbox were found
123             logging.warning("Could not find the acls for mailbox %s for user %s.", mailbox, original_user)
124             return
125         for acl_user in mbox_acls:
126             # (in case target user != original user):
127             # - don't overwrite acls eventually set for the current targetuser
128             # - don't set the default owner acls for the new folder
129             if acl_user != target_user and acl_user != original_user:
130                 try:
131                     self.mail_con.setacl(mailbox, acl_user, mbox_acls[acl_user])
132                     logging.debug("Set acls %s for user %s on mailbox %s", mbox_acls[acl_user], acl_user, mailbox)
133                 except self.mail_con.error as ex:
134                     logging.warning("Could not set acls %s for user %s on mailbox %s: %s", mbox_acls[acl_user], acl_user, mailbox, ex)    
135
136         return
137
138     def delete_mailboxes(self, deleted_mailbox):
139         """Delete specified mailbox or empty inbox."""
140
141         for mailbox in self.mailboxes:
142             pattern = '^\"?' + deleted_mailbox
143             # if INBOX it cannot be deleted so add delimiter
144             if (deleted_mailbox == "INBOX"):
145                 pattern += mailbox[1]
146             if re.compile(pattern).match(mailbox[2]):
147                 result, data = self.mail_con.delete(mailbox[2])
148                 if result == "OK":
149                     logging.debug("Deleted mailbox %s", mailbox[2])
150                 else:
151                     logging.warning("Could not delete mailbox %s: %s", mailbox[2], data[0]) 
152         
153         return
154
155     def create_mailbox(self, mailbox):
156
157         """Create new mailbox to inject messages."""
158         if mailbox != "INBOX":
159             result, data = self.mail_con.create(mailbox)
160             if result == "OK":
161                 logging.debug("Creating mailbox %s", mailbox)
162             else:
163                 logging.warning("Could not create mailbox %s: %s", mailbox, data[0])
164
165         return
166
167     def inject_message(self, message, mailbox, internal_date):
168
169         """Inject a message into a mailbox."""
170         result, data = self.mail_con.append(mailbox, "\\Seen", internal_date, message.encode())
171         if result == "OK":
172             logging.debug("Appending message to mailbox %s", mailbox)
173         else:
174             logging.warning("Could not append the e-mail %s: %s", message, data[0])
175
176         return