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()
signature.asc
Description: OpenPGP digital signature
_________________________________________________ tmda-workers mailing list ([email protected]) http://tmda.net/lists/listinfo/tmda-workers
