git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[git-multimail] smtplib, check certificate

From
SPSimon P <simon.git@le-huit.fr>
Date
Apr 21, 2016, 21:44 UTC
Message-ID
<571949D2.10507@le-huit.fr>
Hi,

It seems that smtplib doesn't check if a certificate is valid (signed by a trusted CA).

For my personal usage, I patched the starttls code in git-multimail: only for starttls with smtplib.

This patch is inspired from
https://github.com/graingert/secure-smtplib/blob/master/src/secure_smtplib/__init__.py

It could be easy to add support cert check in for smtps (see secure_smtplib).

This patch was tested only on git-multimail (v1.2)
It introduces two new options:
  - multimailhook.smtpcheckcert (default false)
  - multimailhook.smtpcacerts (default
                               /etc/ssl/certs/ca-certificates.crt)

Best regards, Simon P.

diff --git a/git-multimail/git_multimail.py b/git-multimail/git_multimail.py
index fae5c91..b49ed9d 100755
--- a/git-multimail/git_multimail.py
+++ b/git-multimail/git_multimail.py
@@ -57,6 +57,7 @@ import subprocess
 import shlex
 import optparse
 import smtplib
+import ssl
 import time
 import cgi
 
@@ -1945,6 +1946,7 @@ class SMTPMailer(Mailer):
                  smtpservertimeout=10.0, smtpserverdebuglevel=0,
                  smtpencryption='none',
                  smtpuser='', smtppass='',
+                 smtpcacerts='/etc/ssl/certs/ca-certificates.crt',smtpcheckcert=False
                  ):
         if not envelopesender:
             sys.stderr.write(
@@ -1974,13 +1976,43 @@ class SMTPMailer(Mailer):
             if self.security == 'none':
                 self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
             elif self.security == 'ssl':
+                if smtpcheckcert:
+                    msg = "Checking certificate is not supported for ssl, prefer starttls"
+                    raise smtplib.SMTPException(msg)
                 self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout)
             elif self.security == 'tls':
                 if ':' not in self.smtpserver:
                     self.smtpserver += ':587'  # default port for TLS
                 self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
-                self.smtp.ehlo()
-                self.smtp.starttls()
+                if smtpcheckcert:
+                    # inspired form:
+                    #   https://github.com/graingert/secure-smtplib/blob/master/src/secure_smtplib/__init__.py
+                    # but add the path to trusted ca, and force ceritficate verification.
+                    self.smtp.ehlo_or_helo_if_needed()
+                    if not self.smtp.has_extn("starttls"):
+                        msg = "STARTTLS extension not supported by server"
+                        raise smtplib.SMTPException(msg)
+                    (resp, reply) = self.smtp.docmd("STARTTLS")
+                    if resp == 220:
+                        self.smtp.sock = ssl.wrap_socket(
+                            self.smtp.sock,
+                            ca_certs=smtpcacerts,
+                            cert_reqs=ssl.CERT_REQUIRED
+                        )
+                        if not hasattr(self.smtp.sock, "read"):
+                            # using httplib.FakeSocket with Python 2.5.x or earlier
+                            self.smtp.sock.read = self.smtp.sock.recv
+                        self.smtp.file = smtplib.SSLFakeFile(self.smtp.sock)
+                        self.smtp.helo_resp = None
+                        self.smtp.ehlo_resp = None
+                        self.smtp.esmtp_features = {}
+                        self.smtp.does_esmtp = 0
+                    else:
+                        msg = "Wrong answer to the STARTTLS command"
+                        raise smtplib.SMTPException(msg)
+                else:
+                    self.smtp.ehlo()
+                    self.smtp.starttls()
                 self.smtp.ehlo()
             else:
                 sys.stdout.write('*** Error: Control reached an invalid option. ***')
@@ -3500,6 +3532,8 @@ def choose_mailer(config, environment):
         smtpencryption = config.get('smtpencryption', default='none')
         smtpuser = config.get('smtpuser', default='')
         smtppass = config.get('smtppass', default='')
+        smtpcacerts = config.get('smtpcacerts', default='/etc/ssl/certs/ca-certificates.crt')
+        smtpcheckcert = config.get_bool('smtpcheckcert', default='false')
         mailer = SMTPMailer(
             envelopesender=(environment.get_sender() or environment.get_fromaddr()),
             smtpserver=smtpserver, smtpservertimeout=smtpservertimeout,
@@ -3507,6 +3541,8 @@ def choose_mailer(config, environment):
             smtpencryption=smtpencryption,
             smtpuser=smtpuser,
             smtppass=smtppass,
+            smtpcacerts=smtpcacerts,
+            smtpcheckcert=smtpcheckcert
             )
     elif mailer == 'sendmail':
         command = config.get('sendmailcommand')
Next: Matthieu Moy
Message 1 of 5 in “[git-multimail] smtplib, check certificate”
  1. Simon PApr 21, 2016
  2. Matthieu MoyApr 22, 2016
  3. Michael HaggertyApr 22, 2016
  4. Simon PontiéApr 24, 2016
  5. Simon PontiéApr 24, 2016

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.