Mercurial > libervia-backend
annotate src/plugins/plugin_misc_imap.py @ 254:9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 17 Jan 2011 04:23:31 +0100 |
parents | f45ffbf211e9 |
children | 55b750017b71 |
rev | line source |
---|---|
253 | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | |
4 """ | |
5 SAT plugin for managing imap server | |
6 Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org) | |
7 | |
8 This program is free software: you can redistribute it and/or modify | |
9 it under the terms of the GNU General Public License as published by | |
10 the Free Software Foundation, either version 3 of the License, or | |
11 (at your option) any later version. | |
12 | |
13 This program is distributed in the hope that it will be useful, | |
14 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 GNU General Public License for more details. | |
17 | |
18 You should have received a copy of the GNU General Public License | |
19 along with this program. If not, see <http://www.gnu.org/licenses/>. | |
20 """ | |
21 | |
22 from logging import debug, info, error | |
23 import warnings | |
24 from twisted.internet import protocol | |
25 from twisted.words.protocols.jabber import error as jab_error | |
26 from twisted.cred import portal,checkers | |
27 from twisted.mail import imap4 | |
28 from email.parser import Parser | |
29 import email.message | |
30 import os,os.path | |
31 from cStringIO import StringIO | |
32 from twisted.internet import reactor | |
33 import pdb | |
34 | |
35 | |
36 from zope.interface import implements | |
37 | |
38 | |
39 PLUGIN_INFO = { | |
40 "name": "IMAP server Plugin", | |
41 "import_name": "IMAP", | |
42 "type": "Misc", | |
43 "protocols": [], | |
44 "dependencies": ["Maildir"], | |
45 "main": "IMAP_server", | |
46 "handler": "no", | |
47 "description": _("""Create an Imap server that you can use to read your "normal" type messages""") | |
48 } | |
49 | |
50 class IMAP_server(): | |
51 | |
52 params = """ | |
53 <params> | |
54 <general> | |
55 <category name="IMAP Server"> | |
56 <param name="Port" value="10143" type="string" /> | |
57 </category> | |
58 </general> | |
59 </params> | |
60 """ | |
61 | |
62 def __init__(self, host): | |
63 info(_("Plugin Imap Server initialization")) | |
64 self.host = host | |
65 | |
66 #parameters | |
67 host.memory.importParams(self.params) | |
68 | |
69 port = int(self.host.memory.getParamA("Port", "IMAP Server")) | |
70 info(_("Launching IMAP server on port %d"), port) | |
71 | |
72 self.server_factory = ImapServerFactory(self.host) | |
73 reactor.listenTCP(port, self.server_factory) | |
74 | |
75 class Message(): | |
76 implements(imap4.IMessage) | |
77 | |
78 def __init__(self, uid, mess_fp): | |
79 debug('Message Init') | |
80 self.uid=uid | |
81 self.mess_fp=mess_fp | |
82 self.message=Parser().parse(mess_fp) | |
83 | |
84 def getUID(self): | |
85 """Retrieve the unique identifier associated with this message. | |
86 """ | |
87 debug('getUID (message)') | |
254
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
88 debug ('===>%i', self.uid) |
253 | 89 return self.uid |
90 | |
91 def getFlags(self): | |
92 """Retrieve the flags associated with this message. | |
93 @return: The flags, represented as strings. | |
94 """ | |
95 debug('getFlags') | |
96 return [] | |
97 | |
98 def getInternalDate(self): | |
99 """Retrieve the date internally associated with this message. | |
100 @return: An RFC822-formatted date string. | |
101 """ | |
102 debug('getInternalDate') | |
103 return self.message['Date'] | |
104 | |
105 | |
106 def getHeaders(self, negate, *names): | |
107 """Retrieve a group of message headers. | |
108 @param names: The names of the headers to retrieve or omit. | |
109 @param negate: If True, indicates that the headers listed in names | |
110 should be omitted from the return value, rather than included. | |
111 @return: A mapping of header field names to header field values | |
112 """ | |
113 debug('getHeaders %s - %s' % (negate, names)) | |
114 final_dict={} | |
115 to_check=[name.lower() for name in names] | |
116 for header in self.message.keys(): | |
117 if (negate and not header.lower() in to_check) or \ | |
118 (not negate and header.lower() in to_check): | |
119 final_dict[header]=self.message[header] | |
120 return final_dict | |
121 | |
122 def getBodyFile(self): | |
123 """Retrieve a file object containing only the body of this message. | |
124 """ | |
125 debug('getBodyFile') | |
126 return StringIO(self.message.get_payload()) | |
127 | |
128 def getSize(self): | |
129 """Retrieve the total size, in octets, of this message. | |
130 """ | |
131 debug('getSize') | |
132 self.mess_fp.seek(0,os.SEEK_END) | |
133 return self.mess_fp.tell() | |
134 | |
135 | |
136 def isMultipart(self): | |
137 """Indicate whether this message has subparts. | |
138 """ | |
139 debug('isMultipart') | |
140 return False | |
141 | |
142 def getSubPart(self,part): | |
143 """Retrieve a MIME sub-message | |
144 @param part: The number of the part to retrieve, indexed from 0. | |
145 @return: The specified sub-part. | |
146 """ | |
147 debug('getSubPart') | |
148 return TypeError | |
149 | |
150 | |
151 class SatMailbox: | |
152 implements(imap4.IMailbox) | |
153 | |
154 def __init__(self,host,name): | |
155 self.host = host | |
156 self.listeners=set() | |
157 debug ('Mailbox init (%s)', name) | |
158 if name!="INBOX": | |
159 raise imap4.MailboxException("Only INBOX is managed for the moment") | |
160 self.name=name | |
161 self.mailbox=self.host.plugins["Maildir"].accessMessageBox("INBOX",self.newMessage) | |
162 | |
163 def newMessage(self): | |
164 """Called when a new message is in the mailbox""" | |
165 debug ("newMessage signal received") | |
166 nb_messages=self.getMessageCount() | |
167 for listener in self.listeners: | |
168 listener.newMessages(nb_messages,None) | |
169 | |
170 def getUIDValidity(self): | |
171 """Return the unique validity identifier for this mailbox. | |
172 """ | |
173 debug ('getUIDValidity') | |
174 return 0 | |
175 | |
176 def getUIDNext(self): | |
177 """Return the likely UID for the next message added to this mailbox. | |
178 """ | |
179 debug ('getUIDNext') | |
254
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
180 return self.mailbox.getNextUid() |
253 | 181 |
182 def getUID(self,message): | |
183 """Return the UID of a message in the mailbox | |
184 @param message: The message sequence number | |
185 @return: The UID of the message. | |
186 """ | |
187 debug ('getUID') | |
254
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
188 return self.mailbox.getUid(message) |
253 | 189 |
190 def getMessageCount(self): | |
191 """Return the number of messages in this mailbox. | |
192 """ | |
193 debug('getMessageCount') | |
194 ret = self.mailbox.getMessageCount() | |
195 debug("count = %i" % ret) | |
196 return ret | |
197 | |
198 def getRecentCount(self): | |
199 """Return the number of messages with the 'Recent' flag. | |
200 """ | |
201 debug('getRecentCount') | |
202 return 0 | |
203 | |
204 def getUnseenCount(self): | |
205 """Return the number of messages with the 'Unseen' flag. | |
206 """ | |
207 debug('getUnseenCount') | |
208 return 1 | |
209 | |
210 def isWriteable(self): | |
211 """Get the read/write status of the mailbox. | |
212 @return: A true value if write permission is allowed, a false value otherwise. | |
213 """ | |
214 debug('isWriteable') | |
215 return True | |
216 | |
217 def destroy(self): | |
218 """Called before this mailbox is deleted, permanently. | |
219 """ | |
220 debug('destroy') | |
221 | |
222 | |
223 def requestStatus(self, names): | |
224 """Return status information about this mailbox. | |
225 @param names: The status names to return information regarding. | |
226 The possible values for each name are: MESSAGES, RECENT, UIDNEXT, | |
227 UIDVALIDITY, UNSEEN. | |
228 @return: A dictionary containing status information about the | |
229 requested names is returned. If the process of looking this | |
230 information up would be costly, a deferred whose callback will | |
231 eventually be passed this dictionary is returned instead. | |
232 """ | |
233 debug('requestStatus') | |
234 return imap4.statusRequestHelper(self, names) | |
235 | |
236 def addListener(self, listener): | |
237 """Add a mailbox change listener | |
238 | |
239 @type listener: Any object which implements C{IMailboxListener} | |
240 @param listener: An object to add to the set of those which will | |
241 be notified when the contents of this mailbox change. | |
242 """ | |
243 debug('addListener %s' % listener) | |
244 self.listeners.add(listener) | |
245 | |
246 def removeListener(self, listener): | |
247 """Remove a mailbox change listener | |
248 | |
249 @type listener: Any object previously added to and not removed from | |
250 this mailbox as a listener. | |
251 @param listener: The object to remove from the set of listeners. | |
252 | |
253 @raise ValueError: Raised when the given object is not a listener for | |
254 this mailbox. | |
255 """ | |
256 debug('removeListener') | |
257 if listener in self.listeners: | |
258 self.listeners.remove(listener) | |
259 else: | |
260 raise imap4.MailboxException('Trying to remove an unknown listener') | |
261 | |
262 def addMessage(self, message, flags = (), date = None): | |
263 """Add the given message to this mailbox. | |
264 @param message: The RFC822 formatted message | |
265 @param flags: The flags to associate with this message | |
266 @param date: If specified, the date to associate with this | |
267 @return: A deferred whose callback is invoked with the message | |
268 id if the message is added successfully and whose errback is | |
269 invoked otherwise. | |
270 """ | |
271 debug('addMessage') | |
272 raise NotImplementedError | |
273 | |
274 def expunge(self): | |
275 """Remove all messages flagged \\Deleted. | |
276 @return: The list of message sequence numbers which were deleted, | |
277 or a Deferred whose callback will be invoked with such a list. | |
278 """ | |
279 debug('expunge') | |
280 raise NotImplementedError | |
281 | |
282 def fetch(self, messages, uid): | |
283 """Retrieve one or more messages. | |
284 @param messages: The identifiers of messages to retrieve information | |
285 about | |
286 @param uid: If true, the IDs specified in the query are UIDs; | |
287 """ | |
288 debug('fetch (%s, %s)'%(messages,uid)) | |
254
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
289 if uid: |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
290 messages.last = self.mailbox.getMaxUid() |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
291 messages.getnext = self.mailbox.getNextExistingUid |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
292 for mess_uid in messages: |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
293 if mess_uid == None: |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
294 debug ('stopping iteration') |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
295 raise StopIteration |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
296 try: |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
297 debug ('yielding (%s,%s)' % (mess_uid,Message(mess_uid,self.mailbox.getMessageUid(mess_uid)))) |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
298 yield (mess_uid,Message(mess_uid,self.mailbox.getMessageUid(mess_uid))) |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
299 except IndexError: |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
300 continue |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
301 else: |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
302 messages.last = self.getMessageCount() |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
303 for mess_idx in messages: |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
304 if mess_idx>self.getMessageCount(): |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
305 raise StopIteration |
9fc32d1d9046
Plugin IMAP, plugin MAILDIR: added IMAP's UID management, mailbox data persistence
Goffi <goffi@goffi.org>
parents:
253
diff
changeset
|
306 yield (mess_idx,Message(mess_idx,self.mailbox.getMessage(mess_idx-1))) |
253 | 307 |
308 def store(self, messages, flags, mode, uid): | |
309 """Set the flags of one or more messages. | |
310 @param messages: The identifiers of the messages to set the flags of. | |
311 @param flags: The flags to set, unset, or add. | |
312 @param mode: If mode is -1, these flags should be removed from the | |
313 specified messages. If mode is 1, these flags should be added to | |
314 the specified messages. If mode is 0, all existing flags should be | |
315 cleared and these flags should be added. | |
316 @param uid: If true, the IDs specified in the query are UIDs; | |
317 otherwise they are message sequence IDs. | |
318 @return: A dict mapping message sequence numbers to sequences of str | |
319 representing the flags set on the message after this operation has | |
320 been performed, or a Deferred whose callback will be invoked with | |
321 such a dict. | |
322 """ | |
323 debug('store') | |
324 raise NotImplementedError | |
325 | |
326 def getFlags(self): | |
327 """Return the flags defined in this mailbox | |
328 Flags with the \\ prefix are reserved for use as system flags. | |
329 @return: A list of the flags that can be set on messages in this mailbox. | |
330 """ | |
331 debug('getFlags') | |
332 #return ['\Seen','\Answered','\Flagged','\Deleted','\Draft', '\Recent'] | |
333 return [] | |
334 | |
335 def getHierarchicalDelimiter(self): | |
336 """Get the character which delimits namespaces for in this mailbox. | |
337 """ | |
338 debug('getHierarchicalDelimiter') | |
339 return '.' | |
340 | |
341 | |
342 | |
343 class ImapAccount(imap4.MemoryAccount): | |
344 #implements(imap4.IAccount) | |
345 # Actually implement the interface here | |
346 | |
347 def __init__(self, host, name): | |
348 debug("ImapAccount init") | |
349 self.host=host | |
350 imap4.MemoryAccount.__init__(self,name) | |
351 self.addMailbox("Inbox") #We only manage Inbox for the moment | |
352 debug ('INBOX added') | |
353 | |
354 def _emptyMailbox(self, name, id): | |
355 return SatMailbox(self.host,name) | |
356 | |
357 | |
358 class ImapRealm: | |
359 implements(portal.IRealm) | |
360 | |
361 def __init__(self,host): | |
362 self.host = host | |
363 | |
364 def requestAvatar(self, avatarID, mind, *interfaces): | |
365 debug('requestAvatar') | |
366 if imap4.IAccount not in interfaces: | |
367 raise NotImplementedError | |
368 return imap4.IAccount, ImapAccount(self.host,avatarID), lambda:None | |
369 | |
370 class ImapServerFactory(protocol.ServerFactory): | |
371 protocol = imap4.IMAP4Server | |
372 | |
373 def __init__(self, host): | |
374 self.host=host | |
375 | |
376 def startedConnecting(self, connector): | |
377 debug (_("IMAP server connection started")) | |
378 | |
379 def clientConnectionLost(self, connector, reason): | |
380 debug (_("IMAP server connection lost (reason: %s)"), reason) | |
381 | |
382 def buildProtocol(self, addr): | |
383 debug ("Building protocole") | |
384 prot = protocol.ServerFactory.buildProtocol(self, addr) | |
385 prot.portal = portal.Portal(ImapRealm(self.host)) | |
386 prot.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(goffi="toto")) | |
387 return prot |