-----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

Reply via email to