SF.net SVN: tmda: [2172] trunk/tmda/bin/tmda-ofmipd
[email protected] Mon, 12 Mar 2007 16:15:35 -0700
| Newsgroups | gmane.mail.spam.tmda.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 2172
http://svn.sourceforge.net/tmda/?rev=2172&view=rev
Author: srwarren
Date: 2007-03-12 16:15:35 -0700 (Mon, 12 Mar 2007)
Log Message:
-----------
tmda-ofmipd: SSL fixes for BSD-based OSs.
* Only send SMTP server signon message after the SSL handshake has completed,
to avoid interleaving the handshake text with SSL packets before SSL is
active.
* Create/use SMTPSessionSignon class, which helps us control when tlslite
calls methods there (via sibling class mechanism) v.s. in the main
SMTPSession.
* Comment the above in detail!
* Various other/releated minor cleanups, code dup removal, etc.
Modified Paths:
--------------
trunk/tmda/bin/tmda-ofmipd
Modified: trunk/tmda/bin/tmda-ofmipd
===================================================================
--- trunk/tmda/bin/tmda-ofmipd 2007-03-07 07:01:47 UTC (rev 2171)
+++ trunk/tmda/bin/tmda-ofmipd 2007-03-12 23:15:35 UTC (rev 2172)
@@ -666,27 +666,85 @@
# Classes
-class SMTPSession(asynchat.async_chat):
+# Integration helper class for tlslite.
+#
+# tlslite's SSL handshake function is not synchronous; it may return prior
+# to handshake completion. In this case, we cannot perform application level
+# reads/writes to the socket, because doing so would corrupt the handshake
+# process.
+#
+# However, the very first thing an SMTP server wants to after a new connection
+# is established is to send the server signon greeting.
+#
+# So, we must wait for the handshake to complete. tlslite will notify us when
+# this occurs by calling the handle_connect function. However, note that
+# this function is called on the "sibling class" part of the object, not the
+# SMTPSession object. This forces us to sub-class async_chat so that we can
+# hook this call into the sibling class.
+#
+# tlslite calls the sibling class directly because asyncore/asynchat are
+# designed to call handle_connect on "self". Typically, the child class
+# (SMTPSession in our case) will implement this function. When tlslite is in
+# use, handle_connect is implemented by the tlslite mixin, which forwards the
+# evenr to the tlslite internals. To avoid infinite recursion, tlslite must
+# call handle_connect directly on the sibling class, not "self".
+#
+# Also note that when initializing an asyncore/async_chat sub-class with an
+# existing connected socket, like tmda-ofmipd does, asyncore/async_chat don't
+# call handle_connect. For this reason, we force this call ourselves at the
+# end of SMTPSession.__init__ (when not in SSL mode) in order to re-use the
+# same mechanism/function to send the server signon.
+
+class SMTPSessionSignon(asynchat.async_chat):
+ def __init__(self, conn):
+ asynchat.async_chat.__init__(self, conn)
+
+ def handle_connect(self):
+ self.init_dynamic_state() # calls sub-class
+ self.push('220 %s ESMTP tmda-ofmipd' % FQDN)
+ self.set_terminator('\r\n')
+
+class SMTPSession(SMTPSessionSignon):
COMMAND = 0
DATA = 1
AUTH = 2
ac_in_buffer_size = 16384
-
+
def __init__(self, conn, process_msg_func):
+ # Base class __init__ calls
+
if opts.ssl or opts.tls:
- TMDATLSAsyncDispatcherMixIn.__init__(self, conn, asynchat.async_chat)
+ TMDATLSAsyncDispatcherMixIn.__init__(self, conn, SMTPSessionSignon)
self.tlsConnection.ignoreAbruptClose = True
+ SMTPSessionSignon.__init__(self, conn)
+ # Save our own __init__ parameters
+
+ self.__conn = conn
+ self.__process_msg_func = process_msg_func
+
+ # Initialize object state
+
+ self.init_static_state()
+
+ # Debug tracing
+
+ print >> DEBUGSTREAM, 'Incoming connection from:', repr(self.__peer)
+ print >> DEBUGSTREAM, 'Incoming connection to:', repr(self._local)
+
+ # Start SSL session, or perform plain-text signon
+
if opts.ssl:
- self.tlsMixinSetActive()
- self.setServerHandshakeOp(certChain=opts.ssl_cert_value,
- privateKey=opts.ssl_key_value)
+ self.do_ssl_handshake()
+ else:
+ self.handle_connect()
- asynchat.async_chat.__init__(self, conn)
+ def init_static_state(self):
+ """Initialize 'static state' - that state which is associated
+ solely with the object, or the physical network connection.
+ In particular, this state is not flushed by STARTTLS."""
- self.__process_msg_func = process_msg_func
-
# 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
@@ -716,11 +774,11 @@
self.__peerport = ''
self.__peer = (self.__peerip, self.__peerport)
else:
- self.__peer = conn.getpeername()
+ self.__peer = self.__conn.getpeername()
self.__peerip = self.__peer[0]
self.__peerport = self.__peer[1]
self.__peername = socket.getfqdn(self.__peerip)
- self._local = conn.getsockname()
+ self._local = self.__conn.getsockname()
self._localip = self._local[0]
self._localname = socket.getfqdn(self._localip)
self._localport = self._local[1]
@@ -729,17 +787,13 @@
# VPopMail's reverse IP domain mapping.
os.environ['TCPLOCALIP'] = self._localip
- print >> DEBUGSTREAM, 'Incoming connection from:', repr(self.__peer)
- print >> DEBUGSTREAM, 'Incoming connection to:', repr(self._local)
-
# SSL/TLS/STARTTLS
self.__can_starttls = opts.tls
- self.__conn = conn
- self.reinit()
- self.signon()
+ def init_dynamic_state(self):
+ """Initialize 'dynamic state' - that state which must be flushed
+ when a STARTLS command is issued, according to the RFC."""
- def reinit(self):
# SMTP AUTH
self.__smtpauth = 0
self.__auth_resp1 = None
@@ -759,11 +813,12 @@
self.__mailfrom = None
self.__rcpttos = []
self.__data = ''
- self.__fqdn = FQDN
- def signon(self):
- self.push('220 %s ESMTP tmda-ofmipd' % (self.__fqdn))
- self.set_terminator('\r\n')
+ def do_ssl_handshake(self):
+ self.__can_starttls = False
+ self.tlsMixinSetActive()
+ self.setServerHandshakeOp(certChain=opts.ssl_cert_value,
+ privateKey=opts.ssl_key_value)
# Overrides base class for convenience
def push(self, msg):
@@ -1034,7 +1089,7 @@
return
responses = []
- responses.append('%s' % self.__fqdn)
+ responses.append('%s' % FQDN)
if not self.__can_starttls or opts.tls == 'optional':
responses.append('AUTH %s' %
(' '.join(map(lambda s: s.upper(), self.__sasl_types))))
@@ -1057,7 +1112,7 @@
rh.append('(using SMTP over TLS)')
if opts.tls and not self.__can_starttls:
rh.append('(using STARTTLS)')
- rh.append('by %s (tmda-ofmipd) with ESMTP;' % (self.__fqdn))
+ rh.append('by %s (tmda-ofmipd) with ESMTP;' % (FQDN))
rh.append(Util.make_date())
os.environ['TMDA_OFMIPD_RECEIVED'] = ' '.join(rh)
@@ -1156,19 +1211,9 @@
self.push('501 Syntax error (no parameters allowed)')
return
self.push('220 Ready to start TLS')
+ self.do_ssl_handshake()
- self.__can_starttls = False
- self.tlsMixinSetActive()
- self.setServerHandshakeOp(certChain=opts.ssl_cert_value,
- privateKey=opts.ssl_key_value)
-
- self.reinit()
-
- def handle_connect(self):
- print ">>> handle_connect called!"
-
-
if opts.ssl or opts.tls:
SMTPSession.__bases__ = (TMDATLSAsyncDispatcherMixIn,) + SMTPSession.__bases__
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.