Mercurial > libervia-backend
comparison src/plugins/plugin_misc_imap.py @ 253:f45ffbf211e9
MAILDIR + IMAP plugins: first draft
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 17 Jan 2011 00:15:50 +0100 |
parents | |
children | 9fc32d1d9046 |
comparison
equal
deleted
inserted
replaced
252:c09aa319712e | 253:f45ffbf211e9 |
---|---|
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)') | |
88 return self.uid | |
89 | |
90 def getFlags(self): | |
91 """Retrieve the flags associated with this message. | |
92 @return: The flags, represented as strings. | |
93 """ | |
94 debug('getFlags') | |
95 return [] | |
96 | |
97 def getInternalDate(self): | |
98 """Retrieve the date internally associated with this message. | |
99 @return: An RFC822-formatted date string. | |
100 """ | |
101 debug('getInternalDate') | |
102 return self.message['Date'] | |
103 | |
104 | |
105 def getHeaders(self, negate, *names): | |
106 """Retrieve a group of message headers. | |
107 @param names: The names of the headers to retrieve or omit. | |
108 @param negate: If True, indicates that the headers listed in names | |
109 should be omitted from the return value, rather than included. | |
110 @return: A mapping of header field names to header field values | |
111 """ | |
112 debug('getHeaders %s - %s' % (negate, names)) | |
113 final_dict={} | |
114 to_check=[name.lower() for name in names] | |
115 for header in self.message.keys(): | |
116 if (negate and not header.lower() in to_check) or \ | |
117 (not negate and header.lower() in to_check): | |
118 final_dict[header]=self.message[header] | |
119 return final_dict | |
120 | |
121 def getBodyFile(self): | |
122 """Retrieve a file object containing only the body of this message. | |
123 """ | |
124 debug('getBodyFile') | |
125 return StringIO(self.message.get_payload()) | |
126 | |
127 def getSize(self): | |
128 """Retrieve the total size, in octets, of this message. | |
129 """ | |
130 debug('getSize') | |
131 self.mess_fp.seek(0,os.SEEK_END) | |
132 return self.mess_fp.tell() | |
133 | |
134 | |
135 def isMultipart(self): | |
136 """Indicate whether this message has subparts. | |
137 """ | |
138 debug('isMultipart') | |
139 return False | |
140 | |
141 def getSubPart(self,part): | |
142 """Retrieve a MIME sub-message | |
143 @param part: The number of the part to retrieve, indexed from 0. | |
144 @return: The specified sub-part. | |
145 """ | |
146 debug('getSubPart') | |
147 return TypeError | |
148 | |
149 | |
150 class SatMailbox: | |
151 implements(imap4.IMailbox) | |
152 | |
153 def __init__(self,host,name): | |
154 self.host = host | |
155 self.listeners=set() | |
156 debug ('Mailbox init (%s)', name) | |
157 if name!="INBOX": | |
158 raise imap4.MailboxException("Only INBOX is managed for the moment") | |
159 self.name=name | |
160 self.mailbox=self.host.plugins["Maildir"].accessMessageBox("INBOX",self.newMessage) | |
161 | |
162 def newMessage(self): | |
163 """Called when a new message is in the mailbox""" | |
164 debug ("newMessage signal received") | |
165 nb_messages=self.getMessageCount() | |
166 for listener in self.listeners: | |
167 listener.newMessages(nb_messages,None) | |
168 | |
169 def getUIDValidity(self): | |
170 """Return the unique validity identifier for this mailbox. | |
171 """ | |
172 debug ('getUIDValidity') | |
173 return 0 | |
174 | |
175 def getUIDNext(self): | |
176 """Return the likely UID for the next message added to this mailbox. | |
177 """ | |
178 debug ('getUIDNext') | |
179 return self.getMessageCount()+1 | |
180 | |
181 def getUID(self,message): | |
182 """Return the UID of a message in the mailbox | |
183 @param message: The message sequence number | |
184 @return: The UID of the message. | |
185 """ | |
186 debug ('getUID') | |
187 return self.mailbox.getId(message) | |
188 | |
189 def getMessageCount(self): | |
190 """Return the number of messages in this mailbox. | |
191 """ | |
192 debug('getMessageCount') | |
193 ret = self.mailbox.getMessageCount() | |
194 debug("count = %i" % ret) | |
195 return ret | |
196 | |
197 def getRecentCount(self): | |
198 """Return the number of messages with the 'Recent' flag. | |
199 """ | |
200 debug('getRecentCount') | |
201 return 0 | |
202 | |
203 def getUnseenCount(self): | |
204 """Return the number of messages with the 'Unseen' flag. | |
205 """ | |
206 debug('getUnseenCount') | |
207 return 1 | |
208 | |
209 def isWriteable(self): | |
210 """Get the read/write status of the mailbox. | |
211 @return: A true value if write permission is allowed, a false value otherwise. | |
212 """ | |
213 debug('isWriteable') | |
214 return True | |
215 | |
216 def destroy(self): | |
217 """Called before this mailbox is deleted, permanently. | |
218 """ | |
219 debug('destroy') | |
220 | |
221 | |
222 def requestStatus(self, names): | |
223 """Return status information about this mailbox. | |
224 @param names: The status names to return information regarding. | |
225 The possible values for each name are: MESSAGES, RECENT, UIDNEXT, | |
226 UIDVALIDITY, UNSEEN. | |
227 @return: A dictionary containing status information about the | |
228 requested names is returned. If the process of looking this | |
229 information up would be costly, a deferred whose callback will | |
230 eventually be passed this dictionary is returned instead. | |
231 """ | |
232 debug('requestStatus') | |
233 return imap4.statusRequestHelper(self, names) | |
234 | |
235 def addListener(self, listener): | |
236 """Add a mailbox change listener | |
237 | |
238 @type listener: Any object which implements C{IMailboxListener} | |
239 @param listener: An object to add to the set of those which will | |
240 be notified when the contents of this mailbox change. | |
241 """ | |
242 debug('addListener %s' % listener) | |
243 self.listeners.add(listener) | |
244 | |
245 def removeListener(self, listener): | |
246 """Remove a mailbox change listener | |
247 | |
248 @type listener: Any object previously added to and not removed from | |
249 this mailbox as a listener. | |
250 @param listener: The object to remove from the set of listeners. | |
251 | |
252 @raise ValueError: Raised when the given object is not a listener for | |
253 this mailbox. | |
254 """ | |
255 debug('removeListener') | |
256 if listener in self.listeners: | |
257 self.listeners.remove(listener) | |
258 else: | |
259 raise imap4.MailboxException('Trying to remove an unknown listener') | |
260 | |
261 def addMessage(self, message, flags = (), date = None): | |
262 """Add the given message to this mailbox. | |
263 @param message: The RFC822 formatted message | |
264 @param flags: The flags to associate with this message | |
265 @param date: If specified, the date to associate with this | |
266 @return: A deferred whose callback is invoked with the message | |
267 id if the message is added successfully and whose errback is | |
268 invoked otherwise. | |
269 """ | |
270 debug('addMessage') | |
271 raise NotImplementedError | |
272 | |
273 def expunge(self): | |
274 """Remove all messages flagged \\Deleted. | |
275 @return: The list of message sequence numbers which were deleted, | |
276 or a Deferred whose callback will be invoked with such a list. | |
277 """ | |
278 debug('expunge') | |
279 raise NotImplementedError | |
280 | |
281 def fetch(self, messages, uid): | |
282 """Retrieve one or more messages. | |
283 @param messages: The identifiers of messages to retrieve information | |
284 about | |
285 @param uid: If true, the IDs specified in the query are UIDs; | |
286 """ | |
287 debug('fetch (%s, %s)'%(messages,uid)) | |
288 messages.last = self.getMessageCount() | |
289 for mes_idx in messages: | |
290 if mes_idx>self.getMessageCount(): | |
291 continue | |
292 yield (mes_idx,Message(mes_idx,self.mailbox.getMessage(mes_idx-1))) | |
293 | |
294 def store(self, messages, flags, mode, uid): | |
295 """Set the flags of one or more messages. | |
296 @param messages: The identifiers of the messages to set the flags of. | |
297 @param flags: The flags to set, unset, or add. | |
298 @param mode: If mode is -1, these flags should be removed from the | |
299 specified messages. If mode is 1, these flags should be added to | |
300 the specified messages. If mode is 0, all existing flags should be | |
301 cleared and these flags should be added. | |
302 @param uid: If true, the IDs specified in the query are UIDs; | |
303 otherwise they are message sequence IDs. | |
304 @return: A dict mapping message sequence numbers to sequences of str | |
305 representing the flags set on the message after this operation has | |
306 been performed, or a Deferred whose callback will be invoked with | |
307 such a dict. | |
308 """ | |
309 debug('store') | |
310 raise NotImplementedError | |
311 | |
312 def getFlags(self): | |
313 """Return the flags defined in this mailbox | |
314 Flags with the \\ prefix are reserved for use as system flags. | |
315 @return: A list of the flags that can be set on messages in this mailbox. | |
316 """ | |
317 debug('getFlags') | |
318 #return ['\Seen','\Answered','\Flagged','\Deleted','\Draft', '\Recent'] | |
319 return [] | |
320 | |
321 def getHierarchicalDelimiter(self): | |
322 """Get the character which delimits namespaces for in this mailbox. | |
323 """ | |
324 debug('getHierarchicalDelimiter') | |
325 return '.' | |
326 | |
327 | |
328 | |
329 class ImapAccount(imap4.MemoryAccount): | |
330 #implements(imap4.IAccount) | |
331 # Actually implement the interface here | |
332 | |
333 def __init__(self, host, name): | |
334 debug("ImapAccount init") | |
335 self.host=host | |
336 imap4.MemoryAccount.__init__(self,name) | |
337 self.addMailbox("Inbox") #We only manage Inbox for the moment | |
338 debug ('INBOX added') | |
339 | |
340 def _emptyMailbox(self, name, id): | |
341 return SatMailbox(self.host,name) | |
342 | |
343 | |
344 class ImapRealm: | |
345 implements(portal.IRealm) | |
346 | |
347 def __init__(self,host): | |
348 self.host = host | |
349 | |
350 def requestAvatar(self, avatarID, mind, *interfaces): | |
351 debug('requestAvatar') | |
352 if imap4.IAccount not in interfaces: | |
353 raise NotImplementedError | |
354 return imap4.IAccount, ImapAccount(self.host,avatarID), lambda:None | |
355 | |
356 class ImapServerFactory(protocol.ServerFactory): | |
357 protocol = imap4.IMAP4Server | |
358 | |
359 def __init__(self, host): | |
360 self.host=host | |
361 | |
362 def startedConnecting(self, connector): | |
363 debug (_("IMAP server connection started")) | |
364 | |
365 def clientConnectionLost(self, connector, reason): | |
366 debug (_("IMAP server connection lost (reason: %s)"), reason) | |
367 | |
368 def buildProtocol(self, addr): | |
369 debug ("Building protocole") | |
370 prot = protocol.ServerFactory.buildProtocol(self, addr) | |
371 prot.portal = portal.Portal(ImapRealm(self.host)) | |
372 prot.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(goffi="toto")) | |
373 return prot |