Mercurial > libervia-backend
comparison src/plugins/plugin_misc_smtp.py @ 260:c8406fe5e81e
Added SMTP server plugin, for sending messages from classic MUA \o/
- added subject managing in sendMessage
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 18 Jan 2011 03:59:59 +0100 |
parents | |
children | 0ecd9c33fa3a |
comparison
equal
deleted
inserted
replaced
259:11f71187d5e4 | 260:c8406fe5e81e |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 """ | |
5 SàT plugin for managing smtp 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,defer | |
25 from twisted.words.protocols.jabber import error as jab_error | |
26 from twisted.cred import portal,checkers,credentials | |
27 from twisted.cred import error as cred_error | |
28 from twisted.mail import smtp | |
29 from twisted.python import failure | |
30 from email.parser import Parser | |
31 from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials | |
32 import os,os.path | |
33 from twisted.internet import reactor | |
34 import pdb | |
35 | |
36 | |
37 from zope.interface import implements | |
38 | |
39 | |
40 PLUGIN_INFO = { | |
41 "name": "SMTP server Plugin", | |
42 "import_name": "SMTP", | |
43 "type": "Misc", | |
44 "protocols": [], | |
45 "dependencies": ["Maildir"], | |
46 "main": "SMTP_server", | |
47 "handler": "no", | |
48 "description": _("""Create a SMTP server that you can use to send your "normal" type messages""") | |
49 } | |
50 | |
51 class SMTP_server(): | |
52 | |
53 params = """ | |
54 <params> | |
55 <general> | |
56 <category name="SMTP Server"> | |
57 <param name="Port" value="10125" type="string" /> | |
58 </category> | |
59 </general> | |
60 </params> | |
61 """ | |
62 | |
63 def __init__(self, host): | |
64 info(_("Plugin SMTP Server initialization")) | |
65 self.host = host | |
66 | |
67 #parameters | |
68 host.memory.importParams(self.params) | |
69 | |
70 port = int(self.host.memory.getParamA("Port", "SMTP Server")) | |
71 info(_("Launching SMTP server on port %d"), port) | |
72 | |
73 self.server_factory = SmtpServerFactory(self.host) | |
74 reactor.listenTCP(port, self.server_factory) | |
75 | |
76 class SatSmtpMessage: | |
77 implements(smtp.IMessage) | |
78 | |
79 def __init__(self, host, profile): | |
80 self.host=host | |
81 self.profile=profile | |
82 self.message=[] | |
83 | |
84 def lineReceived(self, line): | |
85 """handle another line""" | |
86 self.message.append(line) | |
87 | |
88 def eomReceived(self): | |
89 """handle end of message""" | |
90 mail = Parser().parsestr("\n".join(self.message)) | |
91 self.host.sendMessage(mail['to'].decode('utf-8'), mail.get_payload().decode('utf-8'), | |
92 subject=mail['subject'].decode('utf-8'), type='normal', profile_key=self.profile) | |
93 self.message=None | |
94 return defer.succeed(None) | |
95 | |
96 def connectionLost(self): | |
97 """handle message truncated""" | |
98 raise smtp.SMTPError | |
99 | |
100 class SatSmtpDelivery: | |
101 implements(smtp.IMessageDelivery) | |
102 | |
103 def __init__(self, host, profile): | |
104 self.host=host | |
105 self.profile=profile | |
106 | |
107 def receivedHeader(self, helo, origin, recipients): | |
108 """ | |
109 Generate the Received header for a message | |
110 @param helo: The argument to the HELO command and the client's IP | |
111 address. | |
112 @param origin: The address the message is from | |
113 @param recipients: A list of the addresses for which this message | |
114 is bound. | |
115 @return: The full \"Received\" header string. | |
116 """ | |
117 return "Received:" | |
118 | |
119 def validateTo(self, user): | |
120 """ | |
121 Validate the address for which the message is destined. | |
122 @param user: The address to validate. | |
123 @return: A Deferred which becomes, or a callable which | |
124 takes no arguments and returns an object implementing IMessage. | |
125 This will be called and the returned object used to deliver the | |
126 message when it arrives. | |
127 """ | |
128 return lambda: SatSmtpMessage(self.host, self.profile) | |
129 | |
130 def validateFrom(self, helo, origin): | |
131 """ | |
132 Validate the address from which the message originates. | |
133 @param helo: The argument to the HELO command and the client's IP | |
134 address. | |
135 @param origin: The address the message is from | |
136 @return: origin or a Deferred whose callback will be | |
137 passed origin. | |
138 """ | |
139 return origin | |
140 | |
141 class SmtpRealm: | |
142 implements(portal.IRealm) | |
143 | |
144 def __init__(self,host): | |
145 self.host = host | |
146 | |
147 def requestAvatar(self, avatarID, mind, *interfaces): | |
148 debug('requestAvatar') | |
149 profile=avatarID.decode('utf-8') | |
150 if smtp.IMessageDelivery not in interfaces: | |
151 raise NotImplementedError | |
152 return smtp.IMessageDelivery, SatSmtpDelivery(self.host,profile), lambda:None | |
153 | |
154 class SatProfileCredentialChecker: | |
155 """ | |
156 This credential checker check against SàT's profile and associated jabber's password | |
157 Check if the profile exists, and if the password is OK | |
158 Return the profile as avatarId | |
159 """ | |
160 implements(checkers.ICredentialsChecker) | |
161 credentialInterfaces = (credentials.IUsernamePassword, | |
162 credentials.IUsernameHashedPassword) | |
163 | |
164 | |
165 def __init__(self, host): | |
166 self.host = host | |
167 | |
168 def _cbPasswordMatch(self, matched, profile): | |
169 if matched: | |
170 return profile.encode('utf-8') | |
171 else: | |
172 return failure.Failure(cred_error.UnauthorizedLogin()) | |
173 | |
174 def requestAvatarId(self, credentials): | |
175 profiles = self.host.memory.getProfilesList() | |
176 if not credentials.username in profiles: | |
177 return defer.fail(cred_error.UnauthorizedLogin()) | |
178 password = self.host.memory.getParamA("Password", "Connection", profile_key=credentials.username) | |
179 return defer.maybeDeferred( | |
180 credentials.checkPassword, | |
181 password).addCallback( | |
182 self._cbPasswordMatch, credentials.username) | |
183 | |
184 class SmtpServerFactory(smtp.SMTPFactory): | |
185 | |
186 def __init__(self, host): | |
187 self.protocol = smtp.ESMTP | |
188 self.host=host | |
189 _portal = portal.Portal(SmtpRealm(self.host)) | |
190 _portal.registerChecker(SatProfileCredentialChecker(self.host)) | |
191 smtp.SMTPFactory.__init__(self, _portal) | |
192 | |
193 def startedConnecting(self, connector): | |
194 debug (_("SMTP server connection started")) | |
195 smtp.SMTPFactory.startedConnecting(self, connector) | |
196 | |
197 def clientConnectionLost(self, connector, reason): | |
198 debug (_("SMTP server connection lost (reason: %s)"), reason) | |
199 smtp.SMTPFactory.clientConnectionLost(self, connector, reason) | |
200 | |
201 def buildProtocol(self, addr): | |
202 p = smtp.SMTPFactory.buildProtocol(self, addr) | |
203 # add the challengers from imap4, more secure and complicated challengers are available | |
204 p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials} | |
205 return p | |
206 |