--- tmda-ofmipd.orig	2003-11-13 11:28:08.000000000 -0800
+++ tmda-ofmipd	2004-01-09 10:37:10.000000000 -0800
@@ -164,7 +164,12 @@
         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 (e.g. when started from tcpserver)."""
 
 import getopt
 import os
@@ -196,6 +201,7 @@
 authprog = None
 fallback = 0
 foreground = None
+one_session = 0
 remoteauth = { 'proto': None,
                'host':  'localhost',
                'port':  None,
@@ -261,22 +267,23 @@
 
 try:
     opts, args = getopt.getopt(sys.argv[1:],
-                      'p:u:a:R:A:Fc:C:dVhfbS:v:t:', ['proxyport=',
-                                                     'username=',
-                                                     'authfile=',
-                                                     'remoteauth=',
-                                                     'authprog=',
-                                                     'fallback',
-                                                     'configdir=',
-                                                     'connections=',
-                                                     'debug',
-                                                     'version',
-                                                     'help',
-                                                     'foreground',
-                                                     'background',
-                                                     'vhome-script=',
-                                                     'vdomains-path=',
-                                                     'throttle-script='])
+                      'p:u:a:R:A:Fc:C:dVhfbS:v:t:1', ['proxyport=',
+                                                      'username=',
+                                                      'authfile=',
+                                                      'remoteauth=',
+                                                      'authprog=',
+                                                      'fallback',
+                                                      'configdir=',
+                                                      'connections=',
+                                                      'debug',
+                                                      'version',
+                                                      'help',
+                                                      'foreground',
+                                                      'background',
+                                                      'vhome-script=',
+                                                      'vdomains-path=',
+                                                      'throttle-script=',
+                                                      'one-session'])
 except getopt.error, msg:
     usage(1, msg)
 
@@ -345,6 +352,8 @@
         vdomainspath = arg
     elif opt in ('-t', '--throttle-script'):
         throttlescript = arg
+    elif opt in ('-1', '--one-session'):
+        one_session = 1
 
 if vhomescript and configdir:
     msg = "WARNING: --vhome-script and --config-dir are incompatible." + \
@@ -590,8 +599,9 @@
     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
         self.__auth_resp1 = None
@@ -608,7 +618,6 @@
                                                       int(time.time()), FQDN)
         self.__server = server
         self.__conn = conn
-        self.__addr = addr
         self.__line = []
         self.__state = self.COMMAND
         #self.__greeting = 0
@@ -616,11 +625,41 @@
         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 regular fd...
+        if os.environ.has_key('TCPLOCALIP'):
+            self.__peerip = os.environ['TCPREMOTEIP']
+            if os.environ.has_key('TCPREMOTEHOST'):
+                self.__peername = os.environ['TCPREMOTEHOST']
+            else:
+                self.__peername = socket.getfqdn(self.__peerip)
+            self.__peerport = os.environ['TCPREMOTEPORT']
+            self.__peer = (self.__peerip, self.__peerport)
+            self._localip = os.environ['TCPLOCALIP']
+            if os.environ.has_key('TCPLOCALHOST'):
+                self._localname = os.environ['TCPLOCALHOST']
+            else:
+                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 %s' % repr(self.__peer)
+        print >> DEBUGSTREAM, 'Incoming connection to %s' % repr(self._local)
+
         self.push('220 %s ESMTP tmda-ofmipd' % (self.__fqdn))
         self.set_terminator('\r\n')
 
@@ -643,11 +682,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
@@ -674,11 +712,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
@@ -984,36 +1021,9 @@
         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.unixdate(), 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, 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)
+class MessageProcessor:
+    """Base 'pure' SMTP message processing class.
+    Raises NotImplementedError if you try to use it."""
 
     # API for "doing something useful with the message"
     def process_message(self, peer, mailfrom, rcpttos, data):
@@ -1041,8 +1051,9 @@
         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):
         inheaders = 1
@@ -1057,9 +1068,47 @@
         print '------------ END MESSAGE ------------'
 
 
-class PureProxy(SMTPServer):
-    """Proxies all messages to a real smtpd which does final
-    delivery."""
+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.unixdate(), 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."""
+
+    def __init__(self):
+        asyncore.dispatcher.__init__(self)
+        conn = socket.fromfd(0, socket.AF_INET, socket.SOCK_STREAM)
+        self._channel = SMTPChannel(self, conn)
+
+
+class PureProxy:
+    """Proxies all messages to a real smtpd which does final delivery."""
+
     def process_message(self, peer, mailfrom, rcpttos, data):
         lines = data.split('\n')
         # Look for the last header
@@ -1104,10 +1153,11 @@
     (VPopMail or VMailMgr) environment.  It needs to behave differently from
     the standard TMDA proxy in that authenticated users are not system
     (/etc/passwd) users."""
+
     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
@@ -1160,9 +1210,18 @@
             os._exit(0)
 
 
+class VDomainProxyServer(VDomainProxy, SMTPServer):
+    pass
+
+
+class VDomainProxyProcessor(VDomainProxy, SMTPProcessor):
+    pass
+
+
 class TMDAProxy(PureProxy):
     """Using this server for outgoing smtpd, the authenticated user
     will have his mail tagged using his TMDA config file."""
+
     def process_message(self, peer, mailfrom, rcpttos, data, auth_username):
         if configdir is None:
             # ~user/.tmda/
@@ -1202,6 +1261,14 @@
             Util.pipecmd(inject_cmd, data)
             
 
+class TMDAProxyServer(TMDAProxy, SMTPServer):
+    pass
+
+
+class TMDAProxyProcessor(TMDAProxy, SMTPProcessor):
+    pass
+
+
 def main():
     # check permissions of authfile if using only remote
     # authentication.
@@ -1213,11 +1280,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
@@ -1240,7 +1313,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()
