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