-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Jason R. Mastaler wrote:
> Stephen Warren <[EMAIL PROTECTED]> writes:
>
>> Please find attached a patch to make SSL mode work on *BSD (inc. MacOS).
>>
>> I've very briefly tested this in SSL, TLS, and plain-text modes under
>> NetBSD 3.1. I haven't confirmed it still works under Linux, but I'm
>> pretty sure it will; I'll test it more extensively soon.
>
> This causes a bizarre traceback on Python 2.5, Mac, Thunderbird:
>
> ...<type 'exceptions.RuntimeError'>:maximum recursion depth exceeded ...
OK. I reproduced this. This was because I did this:
baseclass1.__init__()
baseclass1.function()
baseclass2.__init__()
and baseclass1.function() ended up calling a function in baseclass2
before baseclass2's init had run, hence the baseclass2 part of the
object was missing a ton of initialization, so some attributes didn't
exist, which caused nasty infinite recursion in __getattr__.
I've fixed this, and cleaned up a couple other things. The new patch is
attached. I also tested SSL and TLS under all 3 Python versions on
Linux, at least with "openssl s_client".
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
iD8DBQFF8luphk3bo0lNTrURAn+KAJ0cpl6A9yW98v5I8xjDZ4vm6ekmhQCgrzYE
rdiFzcBJ78VVDDYe62ObfcE=
=zCcM
-----END PGP SIGNATURE-----
Index: bin/tmda-ofmipd
===================================================================
--- bin/tmda-ofmipd (revision 2171)
+++ bin/tmda-ofmipd (working copy)
@@ -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__
_________________________________________________
tmda-workers mailing list ([email protected])
http://tmda.net/lists/listinfo/tmda-workers