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