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.
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()

Attachment: signature.asc
Description: OpenPGP digital signature

_________________________________________________
tmda-workers mailing list ([email protected])
http://tmda.net/lists/listinfo/tmda-workers

Reply via email to