SmtpXmpp Transport

I wrote Smtp-Xmpp Transporter by python.
Anybody need this?

This implementation has a security issue.
Only AUTH PLAIN is supported.

set your jabber server information
130: self.__xmpp_domain = 'xmppserver'
131: self.__xmpp_server = '192.168.1.1'
132: self.__xmpp_port = 5222

----------

# coding: UTF-8
import smtpd
import base64
import xmpp

""" by t.yanagiwara 2010 Feb 21
"""

__all__ = ['SmtpXmppGw', 'SMTPXMPPGateway']
__version__ = 'smtp-xmppGW 0.1';

class ESMTPServer(smtpd.SMTPServer):
    def handle_accept(self):
        """Override
        """
        conn, addr = self.accept()
        print >> smtpd.DEBUGSTREAM, 'Incoming connection from %s' % repr(addr)
        channel = ESMTPChannel(self, conn, addr)
       
    def process_auth(self, username, password):
        raise NotImplementedError

    def disconnect(self):
        pass

class ESMTPChannel(smtpd.SMTPChannel):
    AUTH = 2

    def __init__(self, server, conn, addr):
        smtpd.asynchat.async_chat.__init__(self, conn)
        self.__server = server
        self._SMTPChannel__server = server
        self._SMTPChannel__conn = conn
        self._SMTPChannel__addr = addr
        self._SMTPChannel__line = []
        self._SMTPChannel__state = self.COMMAND
        self._SMTPChannel__greeting = 0
        self._SMTPChannel__mailfrom = None
        self._SMTPChannel__rcpttos = []
        self._SMTPChannel__data = ''
        self._SMTPChannel__fqdn = smtpd.socket.getfqdn()
        self._SMTPChannel__peer = conn.getpeername()
        self.__username = None
        self.__password = None
        self.__auth = 0
        print >> smtpd.DEBUGSTREAM, 'Peer:', repr(self._SMTPChannel__peer)
        self.push('220 %s ESMTP %s' % (self._SMTPChannel__fqdn, __version__))
        self.set_terminator('\r\n')

    def found_terminator(self):
        if self._SMTPChannel__state != self.AUTH:
            return smtpd.SMTPChannel.found_terminator(self)
        line = smtpd.EMPTYSTRING.join(self._SMTPChannel__line)
        print >> smtpd.DEBUGSTREAM, 'Data:', repr(line)
        self._SMTPChannel__line = []
        self.__auth_PLAIN(line)

    def smtp_EHLO(self, arg):
        if not arg:
            self.push('501 Syntax: EHLO hostname')
            return
        if self._SMTPChannel__greeting:
            self.push('503 Duplicate HELO/EHLO')
        else:
            self._SMTPChannel__greeting = arg
            self.push('250-AUTH PLAIN')
            self.push('250 AUTH=PLAIN')

    def smtp_MAIL(self, arg):
        if not self.__username:
            self.push('503 Error: need AUTH command')
            return
        smtpd.SMTPChannel.smtp_MAIL(self, arg)

    def smtp_RSET(self, arg):
        if arg:
            self.push('501 Syntax: RSET')
            return
        self.__username = None
        self.__password = None
        self.__server.disconnect()
        smtpd.SMTPChannel.smtp_RSET(self, arg)

    def smtp_QUIT(self, arg):
        self.__server.disconnect()
        smtpd.SMTPChannel.smtp_QUIT(self, arg)

    def __auth_PLAIN(self, arg):
        self._SMTPChannel__state = self.COMMAND
        try:
            decstring = base64.standard_b64decode(arg.strip())
        except:
            self.push('535 Error: authentication failed: bad protocol / cancel')
            return
       
        args = decstring.split('\0')
        if len(args) != 3:
            self.push('535 Error: authentication failed: bad protocol / cancel')
            return
        self.__username = args[1]
        self.__password = args[2]

        #print (' %s %s ' % ( self.__username, self.__password ))
        status = self.__server.process_auth(self.__username, self.__password)
        if not status:
            self.push('235 Authentication successful')
        else:
            self.push(status)
            self.__username = None
            self.__password = None

    def smtp_AUTH(self, arg):
        if not arg:
            self.push('501 Syntax: AUTH PLAIN xxxx')
            return
        else:
            args = arg.split()
            if args[0].upper() != 'PLAIN':
                self.push('535 Error: authentication failed: no mechanism available')
                return
           
            if len(args) == 1:
                self._SMTPChannel__state = self.AUTH
                self.push('334 ')
                return
            return self.__auth_PLAIN(args[1])

class XMPPServer(ESMTPServer):
    def process_auth(self, username, password):
        self.__xmpp_domain = 'xmppserver'
        self.__xmpp_server = '192.168.1.1'
        self.__xmpp_port = 5222
       
        jid=xmpp.protocol.JID(username)
        if self.__xmpp_domain:
            cl=xmpp.Client(self.__xmpp_domain, debug=[])
            con=cl.connect(server=(self.__xmpp_server, self.__xmpp_port))
        else:
            cl=xmpp.Client(jid.getDomain(), debug=[])
            con=cl.connect()
        if not con:
            cl.disconnect()
            return '535 Error: authentication failed / internal server error'
        print 'connected with',con
       
        if jid.getNode():
            auth=cl.auth(jid.getNode(), password, resource="Gateway") #jid.getResource())
        else:
            auth=cl.auth(username, password, resource="Gateway") #jid.getResource())
        print auth
        if not auth:
            cl.disconnect()
            return '535 Error: authentication failed'
        self.__client = cl

    def process_message(self, peer, mailfrom, rcpttos, data):
        if not self.__client:
            return '550 Error: internal server error'
        for dest in rcpttos:
            id=self.__client.send(xmpp.protocol.Message(dest, data))

    def disconnect(self):
        if self.__client:
            self.__client.disconnect()
        self.__client = None

if __name__ == '__main__':
    myserver = XMPPServer(('0.0.0.0', 25), None)
    try:
        smtpd.asyncore.loop()
    except KeyboardInterrupt:
        pass
    except:
        raise

Using pymilter with sendmail

Using pymilter with sendmail or postfix will solve your security problem, and make the gateway much more robust. Also consider a more selective gateway. E.g., when the mail server is shared with other users it should redirect message to you only. And in my case, only messages to me from certain senders (that insist on using email for conversations that should be on IM).

Syndicate content