Repository: ckan/ckan

Issue:
mailer python 3.8.5 issue.
**CKAN version**
2.9.0
**Describe the bug**
This bug appears when trying to use mailer with tls protocol turned on in python 3.8.5.  All I'm trying to use the mailer package to email something in the background worker.  It seems to be an issue related to smtplib build into python where the connect does not set the object variable _host in teh SMTP class.  We need to do this right at the instantiation in order to set this variable in order for the starttls function to work.  This doesn't seem to be an issue with Python 2.7 but is an issue in Python 3.8.5


**Steps to reproduce**
Steps to reproduce the behavior:
my module has similar to following

import ckan.lib.mailer as mailer
    try:
        # send email
        email = {'recipient_name': recipient['display_name'],
                 'recipient_email': recipient['email'],
                 'subject': email_dict['subject'],
                 'body': email_dict['body'],
                 #  'headers': {'header1': 'value1'}
                 }
        mailer.mail_recipient(**email)
    
    except mailer.MailerException as e:
        raise

I recommend we simply move line 68 to line 84 on file ckan/lib/mailer.py, but pass the first argument as the host name right there.  Also to catch SMTPConnectError as well.


**Expected behavior**
A clear and concise description of what you expected to happen.
I expected to successfully start tls and run the code above.

**Additional details**
Traceback (most recent call last):
  File "/Users/shasan/PycharmProjects/ckan/ckan/lib/mailer.py", line 97, in _mail_recipient
    smtp_connection.starttls()
  File "/Users/shasan/.pyenv/versions/3.8.5/lib/python3.8/smtplib.py", line 774, in starttls
    self.sock = context.wrap_socket(self.sock,
  File "/Users/shasan/.pyenv/versions/3.8.5/lib/python3.8/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/Users/shasan/.pyenv/versions/3.8.5/lib/python3.8/ssl.py", line 1031, in _create
    self._sslobj = self._context._wrap_socket(
ValueError: server_hostname cannot be an empty string or start with a leading dot.



Discussion:
Also the line "msg['To'] = Header(recipient, 'utf-8') causes issues as the to address is urlencoded in python 3.8.5.  I used the line right above as the value instead as a fix.

Resolving patch:
diff --git a/ckan/lib/mailer.py b/ckan/lib/mailer.py
--- a/ckan/lib/mailer.py
+++ b/ckan/lib/mailer.py
@@ -56,15 +56,13 @@ def _mail_recipient(recipient_name, recipient_email,
     subject = Header(subject.encode('utf-8'), 'utf-8')
     msg['Subject'] = subject
     msg['From'] = _("%s <%s>") % (sender_name, mail_from)
-    recipient = u"%s <%s>" % (recipient_name, recipient_email)
-    msg['To'] = Header(recipient, 'utf-8')
+    msg['To'] = u"%s <%s>" % (recipient_name, recipient_email)
     msg['Date'] = utils.formatdate(time())
     msg['X-Mailer'] = "CKAN %s" % ckan.__version__
     if reply_to and reply_to != '':
         msg['Reply-to'] = reply_to
 
     # Send the email using Python's smtplib.
-    smtp_connection = smtplib.SMTP()
     if 'smtp.test_server' in config:
         # If 'smtp.test_server' is configured we assume we're running tests,
         # and don't use the smtp.server, starttls, user, password etc. options.
@@ -80,11 +78,12 @@ def _mail_recipient(recipient_name, recipient_email,
         smtp_password = config.get('smtp.password')
 
     try:
-        smtp_connection.connect(smtp_server)
-    except socket.error as e:
+        smtp_connection = smtplib.SMTP(smtp_server)
+    except (socket.error, smtplib.SMTPConnectError) as e:
         log.exception(e)
         raise MailerException('SMTP server could not be connected to: "%s" %s'
                               % (smtp_server, e))
+
     try:
         # Identify ourselves and prompt the server for supported features.
         smtp_connection.ehlo()


Test patch: