12ed42d8110304c2cf50fd87cb1737cc477e4b32
[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 SERVER = "localhost"
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, 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] != 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, mailbox_list, original_user, target_user):
113         """Add acls to mailbox."""
114
115         # change encoding to internal cyrus format and make folder absolute
116         mailbox = mailbox.replace("INBOX/", "user/" + original_user + "/")
117         mailbox = mailbox.replace(".", "^")
118         mailbox = mailbox.replace("/", ".")
119
120         # find folder to set all acls
121         try:
122             mbox_acls = mailbox_list[mailbox]
123         except KeyError:
124             # no rights for the mailbox were found
125             logging.warning("Could not find the acls for mailbox %s for user %s.", mailbox, original_user)
126             return
127         for acl_user in mbox_acls:
128             # (in case target user != original user):
129             # - don't overwrite acls eventually set for the current targetuser
130             # - don't set the default owner acls for the new folder
131             if acl_user != target_user and acl_user != original_user:
132                 try:
133                     self.mail_con.setacl(mailbox, acl_user, mbox_acls[acl_user])
134                     logging.debug("Set acls %s for user %s on mailbox %s", mbox_acls[acl_user], acl_user, mailbox)
135                 except self.mail_con.error as ex:
136                     logging.warning("Could not set acls %s for user %s on mailbox %s: %s", mbox_acls[acl_user], acl_user, mailbox, ex)    
137
138         return
139
140     def delete_mailboxes(self, deleted_mailbox):
141         """Delete specified mailbox or empty inbox."""
142
143         for mailbox in self.mailboxes:
144             pattern = '^\"?' + deleted_mailbox
145             # if INBOX it cannot be deleted so add delimiter
146             if (deleted_mailbox == "INBOX"):
147                 pattern += mailbox[1]
148             if re.compile(pattern).match(mailbox[2]):
149                 result, data = self.mail_con.delete(mailbox[2])
150                 if result == "OK":
151                     logging.debug("Deleted mailbox %s", mailbox[2])
152                 else:
153                     logging.warning("Could not delete mailbox %s: %s", mailbox[2], data[0]) 
154         
155         return
156
157     def create_mailbox(self, mailbox):
158
159         """Create new mailbox to inject messages."""
160         if mailbox != "INBOX":
161             result, data = self.mail_con.create(mailbox)
162             if result == "OK":
163                 logging.debug("Creating mailbox %s", mailbox)
164             else:
165                 logging.warning("Could not create mailbox %s: %s", mailbox, data[0])
166
167         return
168
169     def inject_message(self, message, mailbox, internal_date):
170
171         """Inject a message into a mailbox."""
172         result, data = self.mail_con.append(mailbox, "\\Seen", internal_date, message.encode())
173         if result == "OK":
174             logging.debug("Appending message to mailbox %s", mailbox)
175         else:
176             logging.warning("Could not append the e-mail %s: %s", message, data[0])
177
178         return