This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: smtplib's SMTP.connect() should store the server name in ._host for .starttls()
Type: behavior Stage: patch review
Components: email, Library (Lib) Versions: Python 3.7, Python 3.6, Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: r.david.murray Nosy List: SilentGhost, barry, christian.heimes, gigaplastik, labrat, r.david.murray
Priority: normal Keywords: patch

Created on 2015-12-12 22:59 by labrat, last changed 2022-04-11 14:58 by admin.

Files
File name Uploaded Description Edit
0001-smtplib-Set-SMTP._host-in-.connect.patch labrat, 2015-12-12 22:59 Patch shifting the _host save from SMTP.__init__ to SMTP.connect
issue25852.diff SilentGhost, 2015-12-13 00:18 review
issue25852_v2.patch gigaplastik, 2016-03-03 08:50 Alternative patch review
issue25852_v3_with_tests.patch gigaplastik, 2016-09-09 09:51 Alternative updated patch with unit test review
Messages (5)
msg256299 - (view) Author: W. Trevor King (labrat) * Date: 2015-12-12 22:59
With the current tip, starttls uses ._host when calling wrap_socket [1], but ._host is only setup in SMTP.__init__ [2].  Before #22921 [3] starttls would ignore ._host when SNI wasn't available locally.  But as far as I can tell, starttls has never used _host when connection happens via an explicit connect() call.  This leads to errors like [4]:

  >>> smtp = smtplib.SMTP()
  >>> smtp.connect(host="smtp.gmail.com", port=587)
  >>> smtp.ehlo()
  >>> smtp.starttls()
  File "smtp_test.py", line 10, in <module>
    smtp.starttls()
  File "/usr/lib/python3.4/smtplib.py", line 676, in starttls
    server_hostname=server_hostname)
  File "/usr/lib/python3.4/ssl.py", line 344, in wrap_socket
    _context=self)
  File "/usr/lib/python3.4/ssl.py", line 540, in __init__
    self.do_handshake()
  File "/usr/lib/python3.4/ssl.py", line 767, in do_handshake
    self._sslobj.do_handshake()
  ssl.SSLError: [SSL: TLSV1_ALERT_DECODE_ERROR] tlsv1 alert decode error (_ssl.c:598)

I think a better approach would be to move the ._host set into .connect (patch attached).  It would still happen in SMTP(host=…) because [5], but would also allow starttls when users use SMTP() and then call connect(host=…) explicitly.

I've formatted the patch with Git, but its simple enough that it should be easy to apply in Mercurial.  Still, let me know if I can make applying it easier by rerolling the patch.

[1]: https://hg.python.org/cpython/file/323c10701e5d/Lib/smtplib.py#l766
[2]: https://hg.python.org/cpython/file/323c10701e5d/Lib/smtplib.py#l244
[3]: http://bugs.python.org/issue22921
[4]: http://stackoverflow.com/questions/23616803/smtplib-smtp-starttls-fails-with-tlsv1-alert-decode-error
[5]: https://hg.python.org/cpython/file/323c10701e5d/Lib/smtplib.py#l251
msg256305 - (view) Author: SilentGhost (SilentGhost) * (Python triager) Date: 2015-12-13 00:18
Here is a usable patch, there doesn't seem to be a test for starttls, but it's still would be a good idea to add a test for this issue.
msg261151 - (view) Author: gigaplastik (gigaplastik) * Date: 2016-03-03 08:50
Found the same issue independently, but I believe my version of the patch is a little more thoughtful. Since the host is allowed to be supplied in 'hostname:port' format the assignment to ._host should be made _after_ checking (and probably parsing) this format.

The reason for this is that ._host is passed to ssl.SSLContext.wrap_socket method where it is used for SNI, defined in [1]. According to this RFC, "[c]urrently, the only server names supported are DNS hostnames; ... Literal IPv4 and IPv6 addresses are not permitted in [HostName]."

Checking if hostname passed is really a DNS name and not an IP address is up to ssl library, but here, in .connect method, at least the port number should be stripped off.

[1] https://tools.ietf.org/html/rfc4366.html
msg275054 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2016-09-08 15:41
Please provide a more recent patch with a unit test.
msg275297 - (view) Author: gigaplastik (gigaplastik) * Date: 2016-09-09 09:51
Here is the updated (3.6.0a4+) patch for the issue with the unit test included.

My unit test only checks for this issue particular artifacts, it does not test that .starttls actually works. This is because the current test suite for smtplib (Lib/test/test_smtplib.py) does not implement SSL/TLS functionality neither in its "debugging server", nor in its "simulated server" and implementing it on my own is beyond my expertise. Nevertheless, the provided unit test does guarantee .starttls wouldn't break because of the empty ._host attribute.
The overall Python test suite also runs successfully with these patches applied.
History
Date User Action Args
2022-04-11 14:58:24adminsetgithub: 70039
2017-09-07 02:07:21christian.heimessetcomponents: - SSL
2017-09-07 02:07:12christian.heimessetassignee: christian.heimes -> r.david.murray
stage: needs patch -> patch review
versions: + Python 2.7, - Python 3.5
2016-09-15 07:49:17christian.heimessetassignee: christian.heimes
components: + SSL
2016-09-09 09:51:43gigaplastiksetfiles: + issue25852_v3_with_tests.patch

messages: + msg275297
2016-09-08 15:41:22christian.heimessetversions: + Python 3.7, - Python 3.4
nosy: + christian.heimes

messages: + msg275054

stage: patch review -> needs patch
2016-03-03 08:50:54gigaplastiksetfiles: + issue25852_v2.patch
nosy: + gigaplastik
messages: + msg261151

2015-12-13 00:18:56SilentGhostsetfiles: + issue25852.diff
versions: + Python 3.6, - Python 3.3
nosy: + SilentGhost

messages: + msg256305
2015-12-13 00:10:07r.david.murraysetnosy: + barry, r.david.murray

components: + email
stage: patch review
2015-12-12 22:59:22labratcreate