SF.net SVN: tmda: [2037] trunk/tmda
[email protected] Wed, 27 Sep 2006 19:05:05 -0700
| Newsgroups | gmane.mail.spam.tmda.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 2037
http://svn.sourceforge.net/tmda/?rev=2037&view=rev
Author: srwarren
Date: 2006-09-27 19:05:01 -0700 (Wed, 27 Sep 2006)
Log Message:
-----------
Implement 'one-session' mode for tmda-ofmipd.
This allows using xinetd or tcpserver, possibly in conjunction with stunnel, to spawn tmda-ofmipd.
You'd do this to:
* Re-use your existing xinetd/tcpserver configuration/management, e.g. hosts.allow, etc.
* It allows tmda-ofmipd to know the real IP of the client, for better Received: line logging.
See contrib/ofmipd-stunnel-xinetd for some configuration examples.
Modified Paths:
--------------
trunk/tmda/bin/tmda-ofmipd
Added Paths:
-----------
trunk/tmda/contrib/ofmipd-stunnel-xinetd/
trunk/tmda/contrib/ofmipd-stunnel-xinetd/README.txt
trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel-wrapper
trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel.conf
trunk/tmda/contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper
trunk/tmda/contrib/ofmipd-stunnel-xinetd/xinetd.d-tmda-ofmipd-starttls
Modified: trunk/tmda/bin/tmda-ofmipd
===================================================================
--- trunk/tmda/bin/tmda-ofmipd 2006-09-28 01:02:44 UTC (rev 2036)
+++ trunk/tmda/bin/tmda-ofmipd 2006-09-28 02:05:01 UTC (rev 2037)
@@ -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,48 @@
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:
+ # xinetd (or stunnel?) does at least provide REMOTE_HOST.
+ if one_session and os.environ.has_key('REMOTE_HOST'):
+ self.__peerip = os.environ['REMOTE_HOST']
+ self.__peerport = ''
+ self.__peer = (self.__peerip, self.__peerport)
+ else:
+ self.__peer = conn.getpeername()
+ self.__peerip = self.__peer[0]
+ self.__peerport = self.__peer[1]
+ self.__peername = socket.getfqdn(self.__peerip)
+ 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 +653,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 +683,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 +995,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 +1025,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 +1041,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 +1127,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 +1236,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 +1275,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 +1308,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()
Added: trunk/tmda/contrib/ofmipd-stunnel-xinetd/README.txt
===================================================================
--- trunk/tmda/contrib/ofmipd-stunnel-xinetd/README.txt (rev 0)
+++ trunk/tmda/contrib/ofmipd-stunnel-xinetd/README.txt 2006-09-28 02:05:01 UTC (rev 2037)
@@ -0,0 +1,35 @@
+The files in this directory show how to run tmda-ofmipd from xinet.d, using
+stunnel to provide TLS encryption.
+
+The same general principles (i.e. tmda-ofmipd "one-session" mode) should allow
+running tmda-ofmipd under DJB's tcpserver. A previous version of this patch
+was developed for this situation, so it should work fine.
+
+The files are:
+
+xinetd.d-tmda-ofmipd-starttls
+
+ Put this in /etc/xinet.d/tmda-ofmipd-starttls. This file configures xinetd
+ to listen for connections, and run the stunnel wrapper script for each
+ connection. You will need an entry such as the following in /etc/services:
+
+ tmda-ofmipd-starttls 8026/tcp
+
+stunnel-wrapper
+
+ xinetd runs this script for each connection. This script invokes stunnel
+ using stunnel.conf
+
+stunnel.conf
+
+ This configuration file tells stunnel what to do with the connection;
+ specifically, setup an SMTP session, allow STARTTLS, and then invoke
+ tmda-ofmipd in one-session mode
+
+tmda-ofmipd-wrapper
+
+ Once stunnel has established the TLS-encrypted SMTP session, it runs this
+ script to actually communicate with the SMTP client. This script invokes
+ tmda-ofmipd in "one-session" mode, and appropriate authentication etc.
+ options.
+
Added: trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel-wrapper
===================================================================
--- trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel-wrapper (rev 0)
+++ trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel-wrapper 2006-09-28 02:05:01 UTC (rev 2037)
@@ -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-stunnel-wrapper
Property changes on: trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel-wrapper
___________________________________________________________________
Name: svn:executable
+ *
Added: trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel.conf
===================================================================
--- trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel.conf (rev 0)
+++ trunk/tmda/contrib/ofmipd-stunnel-xinetd/stunnel.conf 2006-09-28 02:05:01 UTC (rev 2037)
@@ -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
Added: trunk/tmda/contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper
===================================================================
--- trunk/tmda/contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper (rev 0)
+++ trunk/tmda/contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper 2006-09-28 02:05:01 UTC (rev 2037)
@@ -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: trunk/tmda/contrib/ofmipd-stunnel-xinetd/tmda-ofmipd-wrapper
___________________________________________________________________
Name: svn:executable
+ *
Added: trunk/tmda/contrib/ofmipd-stunnel-xinetd/xinetd.d-tmda-ofmipd-starttls
===================================================================
--- trunk/tmda/contrib/ofmipd-stunnel-xinetd/xinetd.d-tmda-ofmipd-starttls (rev 0)
+++ trunk/tmda/contrib/ofmipd-stunnel-xinetd/xinetd.d-tmda-ofmipd-starttls 2006-09-28 02:05:01 UTC (rev 2037)
@@ -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/stunnel-wrapper
+ log_on_failure += USERID
+}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.