Mercurial > libervia-backend
comparison sat/plugins/plugin_misc_smtp.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | src/plugins/plugin_misc_smtp.py@33c8c4973743 |
children | 56f94936df1e |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SàT plugin for managing smtp server | |
5 # Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero 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 Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from sat.core.i18n import _ | |
21 from sat.core.constants import Const as C | |
22 from sat.core.log import getLogger | |
23 log = getLogger(__name__) | |
24 from twisted.internet import defer | |
25 from twisted.cred import portal, checkers, credentials | |
26 from twisted.cred import error as cred_error | |
27 from twisted.mail import smtp | |
28 from twisted.python import failure | |
29 from email.parser import Parser | |
30 from email.utils import parseaddr | |
31 from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials | |
32 from twisted.internet import reactor | |
33 import sys | |
34 | |
35 from zope.interface import implements | |
36 | |
37 PLUGIN_INFO = { | |
38 C.PI_NAME: "SMTP server Plugin", | |
39 C.PI_IMPORT_NAME: "SMTP", | |
40 C.PI_TYPE: "Misc", | |
41 C.PI_PROTOCOLS: [], | |
42 C.PI_DEPENDENCIES: ["Maildir"], | |
43 C.PI_MAIN: "SMTP_server", | |
44 C.PI_HANDLER: "no", | |
45 C.PI_DESCRIPTION: _("""Create a SMTP server that you can use to send your "normal" type messages""") | |
46 } | |
47 | |
48 | |
49 class SMTP_server(object): | |
50 | |
51 params = """ | |
52 <params> | |
53 <general> | |
54 <category name="Mail Server"> | |
55 <param name="SMTP Port" value="10125" type="int" constraint="1;65535" /> | |
56 </category> | |
57 </general> | |
58 </params> | |
59 """ | |
60 | |
61 def __init__(self, host): | |
62 log.info(_("Plugin SMTP Server initialization")) | |
63 self.host = host | |
64 | |
65 #parameters | |
66 host.memory.updateParams(self.params) | |
67 | |
68 port = int(self.host.memory.getParamA("SMTP Port", "Mail Server")) | |
69 log.info(_("Launching SMTP server on port %d") % port) | |
70 | |
71 self.server_factory = SmtpServerFactory(self.host) | |
72 reactor.listenTCP(port, self.server_factory) | |
73 | |
74 | |
75 class SatSmtpMessage(object): | |
76 implements(smtp.IMessage) | |
77 | |
78 def __init__(self, host, profile): | |
79 self.host = host | |
80 self.profile = profile | |
81 self.message = [] | |
82 | |
83 def lineReceived(self, line): | |
84 """handle another line""" | |
85 self.message.append(line) | |
86 | |
87 def eomReceived(self): | |
88 """handle end of message""" | |
89 mail = Parser().parsestr("\n".join(self.message)) | |
90 try: | |
91 self.host._sendMessage(parseaddr(mail['to'].decode('utf-8', 'replace'))[1], mail.get_payload().decode('utf-8', 'replace'), # TODO: manage other charsets | |
92 subject=mail['subject'].decode('utf-8', 'replace'), mess_type='normal', profile_key=self.profile) | |
93 except: | |
94 exc_type, exc_value, exc_traceback = sys.exc_info() | |
95 log.error(_(u"Can't send message: %s") % exc_value) # The email is invalid or incorreclty parsed | |
96 return defer.fail() | |
97 self.message = None | |
98 return defer.succeed(None) | |
99 | |
100 def connectionLost(self): | |
101 """handle message truncated""" | |
102 raise smtp.SMTPError | |
103 | |
104 | |
105 class SatSmtpDelivery(object): | |
106 implements(smtp.IMessageDelivery) | |
107 | |
108 def __init__(self, host, profile): | |
109 self.host = host | |
110 self.profile = profile | |
111 | |
112 def receivedHeader(self, helo, origin, recipients): | |
113 """ | |
114 Generate the Received header for a message | |
115 @param helo: The argument to the HELO command and the client's IP | |
116 address. | |
117 @param origin: The address the message is from | |
118 @param recipients: A list of the addresses for which this message | |
119 is bound. | |
120 @return: The full \"Received\" header string. | |
121 """ | |
122 return "Received:" | |
123 | |
124 def validateTo(self, user): | |
125 """ | |
126 Validate the address for which the message is destined. | |
127 @param user: The address to validate. | |
128 @return: A Deferred which becomes, or a callable which | |
129 takes no arguments and returns an object implementing IMessage. | |
130 This will be called and the returned object used to deliver the | |
131 message when it arrives. | |
132 """ | |
133 return lambda: SatSmtpMessage(self.host, self.profile) | |
134 | |
135 def validateFrom(self, helo, origin): | |
136 """ | |
137 Validate the address from which the message originates. | |
138 @param helo: The argument to the HELO command and the client's IP | |
139 address. | |
140 @param origin: The address the message is from | |
141 @return: origin or a Deferred whose callback will be | |
142 passed origin. | |
143 """ | |
144 return origin | |
145 | |
146 | |
147 class SmtpRealm(object): | |
148 implements(portal.IRealm) | |
149 | |
150 def __init__(self, host): | |
151 self.host = host | |
152 | |
153 def requestAvatar(self, avatarID, mind, *interfaces): | |
154 log.debug('requestAvatar') | |
155 profile = avatarID.decode('utf-8') | |
156 if smtp.IMessageDelivery not in interfaces: | |
157 raise NotImplementedError | |
158 return smtp.IMessageDelivery, SatSmtpDelivery(self.host, profile), lambda: None | |
159 | |
160 | |
161 class SatProfileCredentialChecker(object): | |
162 """ | |
163 This credential checker check against SàT's profile and associated jabber's password | |
164 Check if the profile exists, and if the password is OK | |
165 Return the profile as avatarId | |
166 """ | |
167 implements(checkers.ICredentialsChecker) | |
168 credentialInterfaces = (credentials.IUsernamePassword, | |
169 credentials.IUsernameHashedPassword) | |
170 | |
171 def __init__(self, host): | |
172 self.host = host | |
173 | |
174 def _cbPasswordMatch(self, matched, profile): | |
175 if matched: | |
176 return profile.encode('utf-8') | |
177 else: | |
178 return failure.Failure(cred_error.UnauthorizedLogin()) | |
179 | |
180 def requestAvatarId(self, credentials): | |
181 profiles = self.host.memory.getProfilesList() | |
182 if not credentials.username in profiles: | |
183 return defer.fail(cred_error.UnauthorizedLogin()) | |
184 d = self.host.memory.asyncGetParamA("Password", "Connection", profile_key=credentials.username) | |
185 d.addCallback(credentials.checkPassword) | |
186 d.addCallback(self._cbPasswordMatch, credentials.username) | |
187 return d | |
188 | |
189 | |
190 class SmtpServerFactory(smtp.SMTPFactory): | |
191 | |
192 def __init__(self, host): | |
193 self.protocol = smtp.ESMTP | |
194 self.host = host | |
195 _portal = portal.Portal(SmtpRealm(self.host)) | |
196 _portal.registerChecker(SatProfileCredentialChecker(self.host)) | |
197 smtp.SMTPFactory.__init__(self, _portal) | |
198 | |
199 def startedConnecting(self, connector): | |
200 log.debug(_("SMTP server connection started")) | |
201 smtp.SMTPFactory.startedConnecting(self, connector) | |
202 | |
203 def clientConnectionLost(self, connector, reason): | |
204 log.debug(_(u"SMTP server connection lost (reason: %s)"), reason) | |
205 smtp.SMTPFactory.clientConnectionLost(self, connector, reason) | |
206 | |
207 def buildProtocol(self, addr): | |
208 p = smtp.SMTPFactory.buildProtocol(self, addr) | |
209 # add the challengers from imap4, more secure and complicated challengers are available | |
210 p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials} | |
211 return p |