tmda-ofmipd patch for xinetd/tcpserver

Stephen Warren <[email protected]>
Newsgroups gmane.mail.spam.tmda.devel
Message-ID <[email protected]>
All,

Please find attached the patch that adds "one-session" mode to
tmda-ofmipd, updated against latest SVN.

I have performed preliminary testing and it seems to process messages
just fine, at least in a non-qmail-vpopmail environment.

I've also added some example configuration files and wrapper scripts
that I use, in contrib/ofmipd-stunnel-xinetd.

So, unless there are any objections, I'll actually commit this in a few
days.
ofmipd-xinetd.patch (text/x-patch, 16.2 KB)
Index: contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper
===================================================================
--- contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper	(revision 0)
+++ contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper	(revision 0)
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/local/tmda/bin/tmda-ofmipd -f -A "/usr/local/bin/checkpassword-pam -s system-auth -- /bin/true" -1 2>/var/log/tmda-ofmipd-starttls

Property changes on: contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper
___________________________________________________________________
Name: svn:executable
   + *

Index: contrib/ofmipd-stunnel-xinetd/xinetd-wrapper
===================================================================
--- contrib/ofmipd-stunnel-xinetd/xinetd-wrapper	(revision 0)
+++ contrib/ofmipd-stunnel-xinetd/xinetd-wrapper	(revision 0)
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/sbin/stunnel /usr/local/tmda/xinetd/tmda-ofmipd-starttls/stunnel.conf 2>/var/log/tmda-ofmipd-starttls-xinetd

Property changes on: contrib/ofmipd-stunnel-xinetd/xinetd-wrapper
___________________________________________________________________
Name: svn:executable
   + *

Index: contrib/ofmipd-stunnel-xinetd/xinetd.d-tmda-ofmipd-starttls
===================================================================
--- contrib/ofmipd-stunnel-xinetd/xinetd.d-tmda-ofmipd-starttls	(revision 0)
+++ contrib/ofmipd-stunnel-xinetd/xinetd.d-tmda-ofmipd-starttls	(revision 0)
@@ -0,0 +1,10 @@
+# default: on
+# description: TMDA SMTP proxy daemon
+service tmda-ofmipd-starttls
+{
+	socket_type     = stream
+	wait            = no
+	user            = root
+	server          = /usr/local/tmda/xinetd/tmda-ofmipd-starttls/xinetd-wrapper
+	log_on_failure  += USERID
+}
Index: contrib/ofmipd-stunnel-xinetd/stunnel.conf
===================================================================
--- contrib/ofmipd-stunnel-xinetd/stunnel.conf	(revision 0)
+++ contrib/ofmipd-stunnel-xinetd/stunnel.conf	(revision 0)
@@ -0,0 +1,6 @@
+exec = /usr/local/tmda/xinetd/tmda-ofmipd-starttls/tmda-ofmipd-wrapper
+execargs = /usr/local/tmda/xinetd/tmda-ofmipd-starttls/tmda-ofmipd-wrapper
+cert = /etc/ssl/keys/severn.wwwdotorg.org.key-and-crt
+client = no
+foreground = yes
+protocol = smtp
Index: bin/tmda-ofmipd
===================================================================
--- bin/tmda-ofmipd	(revision 2034)
+++ bin/tmda-ofmipd	(working copy)
@@ -176,8 +176,14 @@
         Full pathname of a script which can meter how much mail any user sends.
         The script is passed a login name whenever a user tries to send mail.
         If the script returns a 0, the message is allowed.  For any other
-        value, the message is rejected."""
+        value, the message is rejected.
 
+    -1
+    --one-session
+        Don't bind to a port and accept new connections;
+        Process a single SMTP session on stdin (used both for input & output)
+        This is useful when started from tcpserver or stunnel."""
+
 import getopt
 import os
 import signal
@@ -209,6 +215,7 @@
 fallback = 0
 pure_proxy = False
 foreground = None
+one_session = False
 remoteauth = { 'proto': None,
                'host':  'localhost',
                'port':  None,
@@ -269,23 +276,25 @@
 
 try:
     opts, args = getopt.getopt(sys.argv[1:],
-                               'p:u:a:R:A:Fc:C:dVhfbPS:v:t:', ['proxyport=',
-                                                               'username=',
-                                                               'authfile=',
-                                                               'remoteauth=',
-                                                               'authprog=',
-                                                               'fallback',
-                                                               'configdir=',
-                                                               'connections=',
-                                                               'debug',
-                                                               'version',
-                                                               'help',
-                                                               'foreground',
-                                                               'background',
-                                                               'pure-proxy',
-                                                               'vhome-script=',
-                                                               'vdomains-path=',
-                                                               'throttle-script='])
+                               'p:u:a:R:A:Fc:C:dVhfbPS:v:t:1',
+                               ['proxyport=',
+                                'username=',
+                                'authfile=',
+                                'remoteauth=',
+                                'authprog=',
+                                'fallback',
+                                'configdir=',
+                                'connections=',
+                                'debug',
+                                'version',
+                                'help',
+                                'foreground',
+                                'background',
+                                'pure-proxy',
+                                'vhome-script=',
+                                'vdomains-path=',
+                                'throttle-script=',
+                                'one-session'])
 except getopt.error, msg:
     usage(1, msg)
 
@@ -356,6 +365,8 @@
         vdomainspath = arg
     elif opt in ('-t', '--throttle-script'):
         throttlescript = arg
+    elif opt in ('-1', '--one-session'):
+        one_session = True
 
 if vhomescript and configdir:
     msg = "WARNING: --vhome-script and --config-dir are incompatible." + \
@@ -553,7 +564,7 @@
     DATA = 1
     AUTH = 2
     
-    def __init__(self, server, conn, addr):
+    def __init__(self, server, conn):
         asynchat.async_chat.__init__(self, conn)
         # SMTP AUTH
         self.__smtpauth = 0
@@ -571,7 +582,6 @@
                                                       int(time.time()), FQDN)
         self.__server = server
         self.__conn = conn
-        self.__addr = addr
         self.__line = []
         self.__state = self.COMMAND
         #self.__greeting = 0
@@ -579,11 +589,42 @@
         self.__rcpttos = []
         self.__data = ''
         self.__fqdn = FQDN
-        self.__peer = conn.getpeername()
-        self.__peerip = self.__peer[0]
-        self.__peername = socket.getfqdn(self.__peerip)
-        self.__sockip = conn.getsockname()[0]
-        print >> DEBUGSTREAM, 'Peer:', repr(self.__peer)
+
+        # If we're running under tcpserver, then it sets up a bunch of
+        # environment variables that give socket address information.
+        # We always use this, rather than e.g. calling getsockname on
+        # conn, because the tcpserver socket might not be passed directly
+        # to tmda-ofmipd. For example, stunnel might terminate the socket,
+        # decrypt the data and send it here over a pipe...
+        # Note: Whilst tcpserver does provide these variables, the
+        # xinetd/stunnel combination does not...
+        if one_session and os.environ.has_key('TCPREMOTEIP'):
+            self.__peerip = os.environ['TCPREMOTEIP']
+            self.__peername = os.environ.get('TCPREMOTEHOST', None)
+            if not self.__peername:
+                self.__peername = socket.getfqdn(self.__peerip)
+            self.__peerport = os.environ['TCPREMOTEPORT']
+            self.__peer = (self.__peerip, self.__peerport)
+
+            self._localip = os.environ['TCPLOCALIP']
+            self._localname = os.environ.get('TCPLOCALHOST', None)
+            if not self._localname:
+                self._localname = socket.getfqdn(self._localip)
+            self._localport = os.environ['TCPLOCALPORT']
+            self._local = (self._localip, self._localport)
+        else:
+            self.__peer = conn.getpeername()
+            self.__peerip = self.__peer[0]
+            self.__peername = socket.getfqdn(self.__peerip)
+            self.__peerport = self.__peer[1]
+            self._local = conn.getsockname()
+            self._localip = self._local[0]
+            self._localname = socket.getfqdn(self._localip)
+            self._localport = self._local[1]
+
+        print >> DEBUGSTREAM, 'Incoming connection from:', repr(self.__peer)
+        print >> DEBUGSTREAM, 'Incoming connection to:', repr(self._local)
+
         self.push('220 %s ESMTP tmda-ofmipd' % (self.__fqdn))
         self.set_terminator('\r\n')
 
@@ -606,11 +647,10 @@
             return 501
         self.__auth_username = username.lower()
         self.__auth_password = password
-        localip = self.__conn.getsockname()[0]
-        os.environ['TCPLOCALIP'] = localip
+        os.environ['TCPLOCALIP'] = self._localip
         if remoteauth['enable']:
             # Try first with the remote auth
-            if run_remoteauth(username, password, localip):
+            if run_remoteauth(username, password, self._localip):
                 return 1
         if authprog:
             # Then with the authprog
@@ -637,11 +677,10 @@
             return 0
         self.__auth_username = username.lower()
         self.__auth_password = password
-        localip = self.__conn.getsockname()[0]
-        os.environ['TCPLOCALIP'] = localip
+        os.environ['TCPLOCALIP'] = self._localip
         if remoteauth['enable']:
             # Try first with the remote auth
-            if run_remoteauth(username, password, localip):
+            if run_remoteauth(username, password, self._localip):
                 return 1
         if authprog:
             # Then with the authprog
@@ -950,37 +989,10 @@
         self.auth_challenge()
 
 
-class SMTPServer(asyncore.dispatcher):
-    """The base class for the backend.  Raises NotImplementedError if
-    you try to use it."""
-    def __init__(self, localaddr, remoteaddr):
-        self._localaddr = localaddr
-        self._remoteaddr = remoteaddr
-        asyncore.dispatcher.__init__(self)
-        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
-        # try to re-use a server port if possible
-        self.set_reuse_addr()
-        self.bind(localaddr)
-        self.listen(5)
-        print >> DEBUGSTREAM, \
-              'tmda-ofmipd started at %s\n\tListening on %s' % \
-              (Util.make_date(), proxyport)
+class MessageProcessor:
+    """Base 'pure' SMTP message processing class.
+    Raises NotImplementedError if you try to use it."""
 
-    def readable(self):
-        if len(asyncore.socket_map) > int(connections):
-            # too many simultaneous connections
-            return 0
-        else:
-            return 1
-        
-    def handle_accept(self):
-        conn, addr = self.accept()
-        print >> DEBUGSTREAM, 'Incoming connection from %s' % repr(addr)
-        locaddr = conn.getsockname()
-        self._localip = locaddr[0]
-        print >> DEBUGSTREAM, 'Incoming connection to %s' % repr(locaddr)
-        channel = SMTPChannel(self, conn, addr)
-
     # API for "doing something useful with the message"
     def process_message(self, peer, mailfrom, rcpttos, data):
         """Override this abstract method to handle messages from the client.
@@ -1007,7 +1019,7 @@
         raise NotImplementedError
 
 
-class DebuggingServer(SMTPServer):
+class DebuggingMessageProcessor(MessageProcessor):
     """Simply prints each message it receives on stdout."""
     # Do something with the gathered message
     def process_message(self, peer, mailfrom, rcpttos, data):
@@ -1023,9 +1035,45 @@
         print '------------ END MESSAGE ------------'
 
 
-class PureProxy(SMTPServer):
+class SMTPServer(asyncore.dispatcher, MessageProcessor):
+    """Run an SMTP server daemon - accept new socket connections and
+    process SMTP sessions on each conneciton."""
+    def __init__(self, localaddr, remoteaddr):
+        self._localaddr = localaddr
+        self._remoteaddr = remoteaddr
+        asyncore.dispatcher.__init__(self)
+        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+        # try to re-use a server port if possible
+        self.set_reuse_addr()
+        self.bind(localaddr)
+        self.listen(5)
+        print >> DEBUGSTREAM, \
+              'tmda-ofmipd started at %s\n\tListening on %s' % \
+              (Util.make_date(), proxyport)
+
+    def readable(self):
+        if len(asyncore.socket_map) > int(connections):
+            # too many simultaneous connections
+            return 0
+        else:
+            return 1
+        
+    def handle_accept(self):
+        conn = self.accept()[0]
+        self._channel = SMTPChannel(self, conn)
+
+
+class SMTPProcessor(asyncore.dispatcher, MessageProcessor):
+    """Run a single SMTP session, on the stdin file descriptor."""
+    def __init__(self):
+        asyncore.dispatcher.__init__(self)
+        conn = socket.fromfd(0, socket.AF_INET, socket.SOCK_STREAM)
+        self._channel = SMTPChannel(self, conn)
+
+
+class PureProxy(MessageProcessor):
     """Proxies all messages to a real smtpd which does final delivery.
-    Not used currently."""
+    Used solely as a base class."""
     def process_message(self, peer, mailfrom, rcpttos, data):
         lines = data.split('\n')
         # Look for the last header
@@ -1073,7 +1121,7 @@
     def process_message(self, peer, mailfrom, rcpttos, data, auth_username):
         # Set the TCPLOCALIP environment variable to support VPopMail's reverse
         # IP domain mapping.
-        os.environ['TCPLOCALIP'] = self._localip
+        os.environ['TCPLOCALIP'] = self._channel._localip
         # Set up partial tmda-inject command line.
         execdir = os.path.dirname(os.path.abspath(program))
         inject_cmd = [os.path.join(execdir, 'tmda-inject')] + rcpttos
@@ -1182,6 +1230,34 @@
             Util.pipecmd(inject_cmd, data)
             
 
+class VDomainProxyServer(VDomainProxy, SMTPServer):
+    """A proxy server class that binds to the server port, accepts new
+    connections, and processes them as necessary for a qmail virtual user
+    environment. All implementation is inherited from superclasses."""
+    pass
+
+
+class VDomainProxyProcessor(VDomainProxy, SMTPProcessor):
+    """A proxy server class that handles an SMTP session on a previously
+    created socket, and performs processing as necessary for a qmail virtual
+    user environment. All implementation is inherited from superclasses."""
+    pass
+
+
+class TMDAProxyServer(TMDAProxy, SMTPServer):
+    """A proxy server class that binds to the server port, accepts new
+    connections, and processes them as necessary for a system user
+    environment. All implementation is inherited from superclasses."""
+    pass
+
+
+class TMDAProxyProcessor(TMDAProxy, SMTPProcessor):
+    """A proxy server class that handles an SMTP session on a previously
+    created socket, and performs processing as necessary for a system
+    user environment. All implementation is inherited from superclasses."""
+    pass
+
+
 def main():
     # check permissions of authfile if using only remote
     # authentication.
@@ -1193,11 +1269,17 @@
     # try binding to the specified host:port
     host, port = proxyport.split(':', 1)
     if vhomescript:
-        proxy = VDomainProxy((host, int(port)),
-                             ('localhost', 25))
+        if one_session:
+            proxy = VDomainProxyProcessor()
+        else:
+            proxy = VDomainProxyServer((host, int(port)),
+                                       ('localhost', 25))
     else:
-        proxy = TMDAProxy((host, int(port)),
-                          ('localhost', 25))
+        if one_session:
+            proxy = TMDAProxyProcessor()
+        else:
+            proxy = TMDAProxyServer((host, int(port)),
+                                    ('localhost', 25))
     if running_as_root:
         pw_uid = Util.getuid(username)
         # check ownership of authfile if using only remote
@@ -1220,7 +1302,7 @@
     #    print "The default (background) behavior",
     #    print "could be changed in a future version."
     # Try to fork to go to daemon unless foreground mode
-    if not foreground:
+    if not (foreground or one_session):
         signal.signal(signal.SIGHUP, signal.SIG_IGN) # ignore SIGHUP
         if os.fork() != 0:
             sys.exit()
signature.asc (application/pgp-signature, 251 B)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.5 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org

iD8DBQFFGfdghk3bo0lNTrURAl4EAJ93vMZO4gZxEGwrDV+vWlBGL5+gegCgv+Uu
vIVS+I7UmJ5nAk7/NpqO/M4=
=gtaL
-----END PGP SIGNATURE-----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.