From b88b0efa43d0822ee59019598871846aad32b15b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net>
Date: Tue, 2 Sep 2014 08:37:34 +0200
Subject: [PATCH] Replace pygnutls with stdlib's ssl module. (Closes #7791)

---
 setup.py            |   2 +-
 whisperBack/mail.py | 155 +++++++++++++++++-----------------------------------
 2 files changed, 50 insertions(+), 107 deletions(-)

diff --git a/setup.py b/setup.py
index 8b35dfb..0b889ba 100755
--- a/setup.py
+++ b/setup.py
@@ -71,7 +71,7 @@ setup(name='whisperback',
                 ('share/doc/whisperback', ['doc/config.py.sample', 
                                            'README']),
                 ('share/man/man1', ['doc/whisperback.1'])],
-    requires=['gtk', 'GunPGInterface', 'gnutls'],
+    requires=['gtk', 'GunPGInterface'],
     cmdclass = { "build" : build_extra.build_extra,
         "build_gtkbuilderi18n" : build_gtkbuilderi18n,
         "build_man" : build_man,
diff --git a/whisperBack/mail.py b/whisperBack/mail.py
index 4edc24a..23c159c 100644
--- a/whisperBack/mail.py
+++ b/whisperBack/mail.py
@@ -25,13 +25,53 @@
 
 """
 
+from email.mime.text import MIMEText
 import smtplib
 import socket
-from email.mime.text import MIMEText
-import gnutls.errors
+import ssl
+import struct
 import time
 
-def create_message (from_address, to_address, subject, message):
+
+class SMTP(smtplib.SMTP):
+    """
+    Wrapper around smtplib.SMTP, exploited in order to trust a single
+    certificate.
+
+    XXX. This class might be removed once SSLContexts will be available in
+    smtplib (which right now happens only for python >= 2.7.8).
+    For more informations, see:
+    <https://docs.python.org/2/library/smtplib.html#smtplib.SMTP.starttls>
+    """
+    def starttls(self, cafile):
+        """Puts the connection to the SMTP server into TLS mode.
+
+        If the server supports TLS, this will encrypt the rest of the SMTP
+        session.
+
+        """
+        (resp, reply) = self.docmd("STARTTLS")
+        if resp == 220:
+            tv = struct.pack('ii', int(6), int(0))
+            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, tv)
+
+            self._sock = self.sock
+            self.sock = ssl.wrap_socket(self._sock,
+                                        ca_certs=cafile,
+                                        ca_reqs=ssl.CERT_REQUIRED)
+            self.file = smtplib.SSLFakeFile(self.sock)
+            # RFC 3207:
+            # The client MUST discard any knowledge obtained from
+            # the server, such as the list of SMTP service extensions,
+            # which was not obtained from the TLS negotiation itself.
+            self.helo_resp = None
+            self.ehlo_resp = None
+            self.esmtp_features = {}
+            self.does_esmtp = 0
+        return (resp, reply)
+
+
+def create_message(from_address, to_address, subject, message):
     """Create a plaintext mail
 
     This is from an example from doc.python.org
@@ -52,7 +92,7 @@ def create_message (from_address, to_address, subject, message):
     return msg.as_string()
 
 #pylint: disable=R0913
-def send_message_tls (from_address, to_address, message, host="localhost",
+def send_message_tls(from_address, to_address, message, host="localhost",
                     port=25, tls_cafile=None):
     """Sends a mail
 
@@ -63,116 +103,19 @@ def send_message_tls (from_address, to_address, message, host="localhost",
     @param message The content of the mail
     @param host The host of the smtp server to connect to
     @param port The port of the smtp server to connect to
-    @param tls_ca Certificate authority file passed to the socket module’s ssl() function.
+    @param tls_ca Certificate authority file.
     """
     # Send the message via our own SMTP server, but don't include the
     # envelope header.
     # We set a long timeout because Tor is slow
-    smtp = smtplib.SMTP(timeout=120)
+    smtp = SMTP(timeout=120)
     smtp.connect(host, port)
-    (resp, reply) = smtp.starttls(cafile = tls_cafile)
+    (resp, reply) = smtp.starttls(cafile=tls_cafile)
     # Default python let you continue in cleartext if starttls
     # fails, while you expect to have an encrypted connexion
     if resp != 220:
-        raise TLSError("%s answered %i, %s when trying to start TLS"
-                       % (host, resp, reply))
+        raise ssl.SSLError("%s answered %i, %s when trying to start TLS"
+                           % (host, resp, reply))
     else:
         smtp.sendmail(from_address, [to_address], message)
         smtp.quit()
-
-class TLSError(Exception):
-    """Exception raised if problem happens in STARTTLS step"""
-    pass
-
-# This is a monkey patch to make the starttls function of libsmtp use
-# starttls, as the buildin doesn't really check certificates and doesn't
-# have a timeout parameter
-#pylint: disable=W0613
-def starttls(self, keyfile = None, certfile = None, cafile=None):
-    """Puts the connection to the SMTP server into TLS mode.
-
-    If the server supports TLS, this will encrypt the rest of the SMTP
-    session.
-
-    """
-    (resp, reply) = self.docmd("STARTTLS")
-    if resp == 220:
-
-        from gnutls.crypto import X509Certificate
-        from gnutls.connection import ClientSession, X509Credentials
-        # Don't worry, this is a monkey patch
-        #pylint: disable=W0621
-        import struct
-
-        tv = struct.pack('ii', int(6), int(0))
-        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, tv)
-
-        if cafile:
-            ca = X509Certificate(open(cafile).read())
-        # XXX: use CRL
-        #crl = X509CRL(open(certs_path + '/crl.pem').read())
-        #cred = X509Credentials(trusted=[ca], crl_list=[crl])
-        cred = X509Credentials(trusted=[ca])
-        session = ClientSession(self.sock, cred)
-
-        while True:
-            try:
-                session.handshake()
-                session.verify_peer()
-                break
-            except gnutls.errors.OperationWouldBlock:
-                time.sleep(0.1)
-
-        def tls_quit():
-            """Terminate the SMTP session."""
-            self.docmd("quit")
-            while True:
-                try:
-                    self.sock.bye()
-                    break
-                except gnutls.errors.OperationWouldBlock:
-                    time.sleep(0.1)
-            self.close()
-
-        self.quit = tls_quit
-
-        self.sock = session
-        self.file = SSLFakeFile(session)
-
-        # RFC 3207:
-        # The client MUST discard any knowledge obtained from
-        # the server, such as the list of SMTP service extensions,
-        # which was not obtained from the TLS negotiation itself.
-        self.helo_resp = None
-        self.ehlo_resp = None
-        self.esmtp_features = {}
-        self.does_esmtp = 0
-    return (resp, reply)
-
-class SSLFakeFile:
-    """A fake file like object that really wraps a SSLObject.
-
-    It only supports what is needed in smtplib.
-    """
-    def __init__(self, sslobj):
-        self.sslobj = sslobj
-
-    #pylint: disable=C0111
-    def readline(self):
-        string = ""
-        char = None
-        while char != "\n":
-            while True:
-                try:          
-                    char = self.sslobj.recv(1)
-                    string += char
-                    break
-                except gnutls.errors.OperationWouldBlock:
-                    time.sleep(0.1)
-        return string
-
-    #pylint: disable=C0111
-    def close(self):
-        pass
-
-smtplib.SMTP.starttls = starttls
-- 
2.1.0

