[PATCH 2 of 4 RFC] smtp: add the class to verify the certificate of the SMTP server for SMTPS

FUJIWARA Katsunori foozy at lares.dti.ne.jp
Mon Mar 25 12:32:32 CDT 2013


# HG changeset patch
# User FUJIWARA Katsunori <foozy at lares.dti.ne.jp>
# Date 1364232463 -32400
# Node ID 746995f6d779c452d940081053cc9e827b7cddaa
# Parent  a3e837b701257097d9b4f0fb3f4fe36176deeb49
smtp: add the class to verify the certificate of the SMTP server for SMTPS

Original "smtplib.SMTP_SSL" has no route to pass "ca_certs" and
"cert_reqs" arguments to underlying SSL socket creation. This causes
that "getpeercert()" on SSL socket returns empty dict, so the peer
certificate for SMTPS can't be verified.

This patch introduces the "SMTPS" class derived from "smtplib.SMTP" to
pass "ca_certs" and "cert_reqs" arguments to underlying SSL socket
creation.

"SMTPS" class is derived directly from "smtplib.SMTP", because amount
of "smtplib.SMTP_SSL" definition derived from "smtplib.SMTP" is as
same as one needed to override it.

This patch defines "SMTPS" class, only when "smtplib.SMTP" class has
"_get_socket()" method, because this makes using SSL socket instead of
normal socket easy.

"smtplib.SMTP" class of Python 2.5.x or earlier doesn't have this
method. Omitting SMTPS support for them is reasonable, because
"smtplib.SMTP_SSL" is already unavailable for them before this patch.

Almost all code of "SMTPS" class is imported from "smtplib.SMTP_SSL"
of Python 2.7.3, but it differs from original code in point below:

  - "ssl.wrap_socket()" is replaced by "sslutil.ssl_wrap_socket()" for
    compatibility between Python versions

diff --git a/mercurial/mail.py b/mercurial/mail.py
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -57,6 +57,31 @@
             self.does_esmtp = 0
         return (resp, reply)
 
+if util.safehasattr(smtplib.SMTP, '_get_socket'):
+    class SMTPS(smtplib.SMTP):
+        '''Derived class to verify the peer certificate for SMTPS.
+
+        This class allows to pass any keyword arguments to SSL socket creation.
+        '''
+        def __init__(self, sslkwargs, keyfile=None, certfile=None, **kwargs):
+            self.keyfile = keyfile
+            self.certfile = certfile
+            smtplib.SMTP.__init__(self, **kwargs)
+            self.default_port = smtplib.SMTP_SSL_PORT
+            self._sslkwargs = sslkwargs
+
+        def _get_socket(self, host, port, timeout):
+            if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
+            new_socket = socket.create_connection((host, port), timeout)
+            new_socket = sslutil.ssl_wrap_socket(new_socket,
+                                                 self.keyfile, self.certfile,
+                                                 **self._sslkwargs)
+            self.file = smtplib.SSLFakeFile(new_socket)
+            return new_socket
+else:
+    def SMTPS(sslkwargs, keyfile=None, certfile=None, **kwargs):
+        raise util.Abort(_('SMTPS requires Python 2.6 or later'))
+
 def _smtp(ui):
     '''build an smtp connection and return a function to send mail'''
     local_hostname = ui.config('smtp', 'local_hostname')


More information about the Mercurial-devel mailing list