changeset 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 (2011-01-18)
parents 11f71187d5e4
children 0ecd9c33fa3a
files frontends/src/bridge/DBus.py src/bridge/DBus.py src/plugins/plugin_misc_imap.py src/plugins/plugin_misc_smtp.py src/sat.tac
diffstat 5 files changed, 215 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/bridge/DBus.py	Tue Jan 18 01:07:12 2011 +0100
+++ b/frontends/src/bridge/DBus.py	Tue Jan 18 03:59:59 2011 +0100
@@ -80,8 +80,8 @@
     def getWaitingSub(self, profile_key='@DEFAULT@'):
         return self.db_comm_iface.getWaitingSub(profile_key)
 
-    def sendMessage(self, to, message, type='chat', profile_key='@DEFAULT@'):
-        return self.db_comm_iface.sendMessage(to, message, type, profile_key)
+    def sendMessage(self, to, message, subject='', type='chat', profile_key='@DEFAULT@'):
+        return self.db_comm_iface.sendMessage(to, message, subject, type, profile_key)
     
     def setPresence(self, to="", show="", priority=0, statuses={}, profile_key='@DEFAULT@'):
         return self.db_comm_iface.setPresence(to, show, priority, statuses, profile_key)
--- a/src/bridge/DBus.py	Tue Jan 18 01:07:12 2011 +0100
+++ b/src/bridge/DBus.py	Tue Jan 18 03:59:59 2011 +0100
@@ -182,11 +182,11 @@
         return self.cb["getWaitingSub"](profile_key)
 
     @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='ssss', out_signature='')
-    def sendMessage(self, to, message, type='chat', profile_key='@DEFAULT@'):
+                         in_signature='sssss', out_signature='')
+    def sendMessage(self, to, message, subject="", type='chat', profile_key='@DEFAULT@'):
         debug("sendMessage...")
         print "sendtype=", type  #gof
-        self.cb["sendMessage"](to, message, type, profile_key)
+        self.cb["sendMessage"](to, message, subject, type, profile_key)
 
     @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
                          in_signature='ssia{ss}s', out_signature='')
--- a/src/plugins/plugin_misc_imap.py	Tue Jan 18 01:07:12 2011 +0100
+++ b/src/plugins/plugin_misc_imap.py	Tue Jan 18 03:59:59 2011 +0100
@@ -26,6 +26,7 @@
 from twisted.cred import portal,checkers,credentials
 from twisted.cred import error as cred_error
 from twisted.mail import imap4
+from twisted.python import failure
 from email.parser import Parser
 import email.message
 import os,os.path
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/plugin_misc_smtp.py	Tue Jan 18 03:59:59 2011 +0100
@@ -0,0 +1,206 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+SàT plugin for managing smtp server
+Copyright (C) 2011  Jérôme Poisson (goffi@goffi.org)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from logging import debug, info, error
+import warnings
+from twisted.internet import protocol,defer
+from twisted.words.protocols.jabber import error as jab_error
+from twisted.cred import portal,checkers,credentials
+from twisted.cred import error as cred_error
+from twisted.mail import smtp
+from twisted.python import failure
+from email.parser import Parser
+from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials
+import os,os.path
+from twisted.internet import reactor
+import pdb
+
+
+from zope.interface import implements
+
+
+PLUGIN_INFO = {
+"name": "SMTP server Plugin",
+"import_name": "SMTP",
+"type": "Misc",
+"protocols": [],
+"dependencies": ["Maildir"],
+"main": "SMTP_server",
+"handler": "no",
+"description": _("""Create a SMTP server that you can use to send your "normal" type messages""")
+}
+
+class SMTP_server():
+    
+    params = """
+    <params>
+    <general>
+    <category name="SMTP Server">
+        <param name="Port" value="10125" type="string" />
+    </category>
+    </general>
+    </params>
+    """
+
+    def __init__(self, host):
+        info(_("Plugin SMTP Server initialization"))
+        self.host = host
+        
+        #parameters
+        host.memory.importParams(self.params)
+
+        port = int(self.host.memory.getParamA("Port", "SMTP Server"))
+        info(_("Launching SMTP server on port %d"), port)
+        
+        self.server_factory = SmtpServerFactory(self.host)
+        reactor.listenTCP(port, self.server_factory)
+
+class SatSmtpMessage:
+    implements(smtp.IMessage)
+
+    def __init__(self, host, profile):
+        self.host=host
+        self.profile=profile
+        self.message=[]
+    
+    def lineReceived(self, line):
+        """handle another line"""
+        self.message.append(line)
+
+    def eomReceived(self):
+        """handle end of message"""
+        mail = Parser().parsestr("\n".join(self.message))
+        self.host.sendMessage(mail['to'].decode('utf-8'), mail.get_payload().decode('utf-8'), 
+                              subject=mail['subject'].decode('utf-8'), type='normal', profile_key=self.profile)
+        self.message=None
+        return defer.succeed(None)
+
+    def connectionLost(self):
+        """handle message truncated"""
+        raise smtp.SMTPError
+
+class SatSmtpDelivery:
+    implements(smtp.IMessageDelivery)
+
+    def __init__(self, host, profile):
+        self.host=host
+        self.profile=profile
+
+    def receivedHeader(self, helo, origin, recipients):
+        """
+        Generate the Received header for a message
+        @param helo: The argument to the HELO command and the client's IP
+        address.
+        @param origin: The address the message is from
+        @param recipients: A list of the addresses for which this message
+        is bound.
+        @return: The full \"Received\" header string.
+        """
+        return "Received:"
+
+    def validateTo(self, user):
+        """
+        Validate the address for which the message is destined.
+        @param user: The address to validate.
+        @return: A Deferred which becomes, or a callable which
+        takes no arguments and returns an object implementing IMessage.
+        This will be called and the returned object used to deliver the
+        message when it arrives.
+        """
+        return lambda: SatSmtpMessage(self.host, self.profile)
+
+    def validateFrom(self, helo, origin):
+        """
+        Validate the address from which the message originates.
+        @param helo: The argument to the HELO command and the client's IP
+        address.
+        @param origin: The address the message is from
+        @return: origin or a Deferred whose callback will be
+        passed origin.
+        """
+        return origin
+
+class SmtpRealm:
+    implements(portal.IRealm)
+
+    def __init__(self,host):
+        self.host = host
+
+    def requestAvatar(self, avatarID, mind, *interfaces):
+        debug('requestAvatar')
+        profile=avatarID.decode('utf-8')
+        if smtp.IMessageDelivery not in interfaces:
+            raise NotImplementedError
+        return smtp.IMessageDelivery, SatSmtpDelivery(self.host,profile), lambda:None
+
+class SatProfileCredentialChecker:
+    """
+    This credential checker check against SàT's profile and associated jabber's password
+    Check if the profile exists, and if the password is OK
+    Return the profile as avatarId
+    """
+    implements(checkers.ICredentialsChecker)
+    credentialInterfaces = (credentials.IUsernamePassword,
+                            credentials.IUsernameHashedPassword)
+
+    
+    def __init__(self, host):
+        self.host = host
+
+    def _cbPasswordMatch(self, matched, profile):
+        if matched:
+            return profile.encode('utf-8')
+        else:
+            return failure.Failure(cred_error.UnauthorizedLogin())
+
+    def requestAvatarId(self, credentials):
+        profiles = self.host.memory.getProfilesList()
+        if not credentials.username in profiles:
+            return defer.fail(cred_error.UnauthorizedLogin())
+        password = self.host.memory.getParamA("Password", "Connection", profile_key=credentials.username)
+        return defer.maybeDeferred(
+            credentials.checkPassword,
+            password).addCallback(
+            self._cbPasswordMatch, credentials.username)
+
+class SmtpServerFactory(smtp.SMTPFactory):
+
+    def __init__(self, host):
+        self.protocol = smtp.ESMTP
+        self.host=host
+        _portal = portal.Portal(SmtpRealm(self.host))
+        _portal.registerChecker(SatProfileCredentialChecker(self.host))
+        smtp.SMTPFactory.__init__(self, _portal)
+
+    def startedConnecting(self, connector):
+        debug (_("SMTP server connection started"))
+        smtp.SMTPFactory.startedConnecting(self, connector)
+
+    def clientConnectionLost(self, connector, reason):
+        debug (_("SMTP server connection lost (reason: %s)"), reason)
+        smtp.SMTPFactory.clientConnectionLost(self, connector, reason)
+    
+    def buildProtocol(self, addr):
+        p = smtp.SMTPFactory.buildProtocol(self, addr)
+        # add the challengers from imap4, more secure and complicated challengers are available
+        p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials}
+        return p
+
--- a/src/sat.tac	Tue Jan 18 01:07:12 2011 +0100
+++ b/src/sat.tac	Tue Jan 18 03:59:59 2011 +0100
@@ -591,7 +591,7 @@
 
     ## jabber methods ##
     
-    def sendMessage(self,to,msg,type='chat', profile_key='@DEFAULT@'):
+    def sendMessage(self, to, msg, subject=None, type='chat', profile_key='@DEFAULT@'):
         #FIXME: check validity of recipient
         profile = self.memory.getProfileName(profile_key)
         assert(profile)
@@ -601,6 +601,8 @@
         message["to"] = jid.JID(to).full()
         message["from"] = current_jid.full()
         message["type"] = type
+        if subject:
+            message.addElement("subject", "jabber:client", subject)
         message.addElement("body", "jabber:client", msg)
         self.profiles[profile].xmlstream.send(message)
         self.memory.addToHistory(current_jid, current_jid, jid.JID(to), message["type"], unicode(msg))