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