Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 66 additions & 18 deletions Lib/test/test_smtplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import threading

import unittest
import unittest.mock as mock
from test import support, mock_socket
from test.support import hashlib_helper
from test.support import socket_helper
Expand Down Expand Up @@ -350,7 +351,7 @@ def testVRFY(self):
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
expected = (252, b'Cannot VRFY user, but will accept message ' + \
b'and attempt delivery')
b'and attempt delivery')
self.assertEqual(smtp.vrfy('[email protected]'), expected)
self.assertEqual(smtp.verify('[email protected]'), expected)
smtp.quit()
Expand All @@ -371,7 +372,7 @@ def testHELP(self):
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
self.assertEqual(smtp.help(), b'Supported commands: EHLO HELO MAIL ' + \
b'RCPT DATA RSET NOOP QUIT VRFY')
b'RCPT DATA RSET NOOP QUIT VRFY')
smtp.quit()

def testSend(self):
Expand Down Expand Up @@ -527,7 +528,7 @@ def testSendMessageWithAddresses(self):
smtp.quit()
# make sure the Bcc header is still in the message.
self.assertEqual(m['Bcc'], 'John Root <root@localhost>, "Dinsdale" '
'<[email protected]>')
'<[email protected]>')

self.client_evt.set()
self.serv_evt.wait()
Expand Down Expand Up @@ -766,7 +767,7 @@ def tearDown(self):

def testFailingHELO(self):
self.assertRaises(smtplib.SMTPConnectError, smtplib.SMTP,
HOST, self.port, 'localhost', 3)
HOST, self.port, 'localhost', 3)


class TooLongLineTests(unittest.TestCase):
Expand Down Expand Up @@ -804,14 +805,14 @@ def testLineTooLong(self):
sim_users = {'[email protected]':'John A',
'[email protected]':'Sally B',
'[email protected]':'Ruth C',
}
}

sim_auth = ('[email protected]', 'somepassword')
sim_cram_md5_challenge = ('PENCeUxFREJoU0NnbmhNWitOMjNGNn'
'dAZWx3b29kLmlubm9zb2Z0LmNvbT4=')
sim_lists = {'list-1':['[email protected]','[email protected]'],
'list-2':['[email protected]',],
}
}

# Simulated SMTP channel & server
class ResponseException(Exception): pass
Expand All @@ -830,6 +831,7 @@ class SimSMTPChannel(smtpd.SMTPChannel):
def __init__(self, extra_features, *args, **kw):
self._extrafeatures = ''.join(
[ "250-{0}\r\n".format(x) for x in extra_features ])
self.all_received_lines = []
super(SimSMTPChannel, self).__init__(*args, **kw)

# AUTH related stuff. It would be nice if support for this were in smtpd.
Expand All @@ -844,6 +846,7 @@ def found_terminator(self):
self.smtp_state = self.COMMAND
self.push('%s %s' % (e.smtp_code, e.smtp_error))
return
self.all_received_lines.append(self.received_lines)
super().found_terminator()


Expand Down Expand Up @@ -924,11 +927,14 @@ def _auth_cram_md5(self, arg=None):
except ValueError as e:
self.push('535 Splitting response {!r} into user and password '
'failed: {}'.format(logpass, e))
return False
valid_hashed_pass = hmac.HMAC(
sim_auth[1].encode('ascii'),
self._decode_base64(sim_cram_md5_challenge).encode('ascii'),
'md5').hexdigest()
return
pwd = sim_auth[1].encode('ascii')
msg = self._decode_base64(sim_cram_md5_challenge).encode('ascii')
try:
valid_hashed_pass = hmac.HMAC(pwd, msg, 'md5').hexdigest()
except ValueError:
self.push('504 CRAM-MD5 is not supported')
return
self._authenticated(user, hashed_pass == valid_hashed_pass)
# end AUTH related stuff.

Expand Down Expand Up @@ -1170,8 +1176,7 @@ def auth_buggy(challenge=None):
finally:
smtp.close()

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def testAUTH_CRAM_MD5(self):
self.serv.add_feature("AUTH CRAM-MD5")
Expand All @@ -1181,8 +1186,40 @@ def testAUTH_CRAM_MD5(self):
self.assertEqual(resp, (235, b'Authentication Succeeded'))
smtp.close()

# TODO: RUSTPYTHON
@unittest.expectedFailure
@mock.patch("hmac.HMAC")
@mock.patch("smtplib._have_cram_md5_support", False)
def testAUTH_CRAM_MD5_blocked(self, hmac_constructor):
# CRAM-MD5 is the only "known" method by the server,
# but it is not supported by the client. In particular,
# no challenge will ever be sent.
self.serv.add_feature("AUTH CRAM-MD5")
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
msg = re.escape("No suitable authentication method found.")
with self.assertRaisesRegex(smtplib.SMTPException, msg):
smtp.login(sim_auth[0], sim_auth[1])
hmac_constructor.assert_not_called() # call has been bypassed

@mock.patch("smtplib._have_cram_md5_support", False)
def testAUTH_CRAM_MD5_blocked_and_fallback(self):
# Test that PLAIN is tried after CRAM-MD5 failed
self.serv.add_feature("AUTH CRAM-MD5 PLAIN")
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
with (
mock.patch.object(smtp, "auth_cram_md5") as smtp_auth_cram_md5,
mock.patch.object(
smtp, "auth_plain", wraps=smtp.auth_plain
) as smtp_auth_plain
):
resp = smtp.login(sim_auth[0], sim_auth[1])
smtp_auth_plain.assert_called_once()
smtp_auth_cram_md5.assert_not_called() # no call to HMAC constructor
self.assertEqual(resp, (235, b'Authentication Succeeded'))

@unittest.expectedFailure # TODO: RUSTPYTHON
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def testAUTH_multiple(self):
# Test that multiple authentication methods are tried.
Expand All @@ -1193,8 +1230,7 @@ def testAUTH_multiple(self):
self.assertEqual(resp, (235, b'Authentication Succeeded'))
smtp.close()

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_auth_function(self):
supported = {'PLAIN', 'LOGIN'}
try:
Expand Down Expand Up @@ -1354,6 +1390,18 @@ def test_name_field_not_included_in_envelop_addresses(self):
self.assertEqual(self.serv._addresses['from'], '[email protected]')
self.assertEqual(self.serv._addresses['tos'], ['[email protected]'])

def test_lowercase_mail_from_rcpt_to(self):
m = 'A test message'
smtp = smtplib.SMTP(
HOST, self.port, local_hostname='localhost',
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)

smtp.sendmail('John', 'Sally', m)

self.assertIn(['mail from:<John> size=14'], self.serv._SMTPchannel.all_received_lines)
self.assertIn(['rcpt to:<Sally>'], self.serv._SMTPchannel.all_received_lines)


class SimSMTPUTF8Server(SimSMTPServer):

Expand All @@ -1372,7 +1420,7 @@ def handle_accepted(self, conn, addr):
)

def process_message(self, peer, mailfrom, rcpttos, data, mail_options=None,
rcpt_options=None):
rcpt_options=None):
self.last_peer = peer
self.last_mailfrom = mailfrom
self.last_rcpttos = rcpttos
Expand Down
9 changes: 6 additions & 3 deletions Lib/test/test_smtpnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
from test import support
from test.support import import_helper
from test.support import socket_helper
import os
import smtplib
import socket

ssl = import_helper.import_module("ssl")

support.requires("network")

SMTP_TEST_SERVER = os.getenv('CPYTHON_TEST_SMTP_SERVER', 'smtp.gmail.com')

def check_ssl_verifiy(host, port):
context = ssl.create_default_context()
with socket.create_connection((host, port)) as sock:
Expand All @@ -22,7 +25,7 @@ def check_ssl_verifiy(host, port):


class SmtpTest(unittest.TestCase):
testServer = 'smtp.gmail.com'
testServer = SMTP_TEST_SERVER
remotePort = 587

def test_connect_starttls(self):
Expand All @@ -44,7 +47,7 @@ def test_connect_starttls(self):


class SmtpSSLTest(unittest.TestCase):
testServer = 'smtp.gmail.com'
testServer = SMTP_TEST_SERVER
remotePort = 465

def test_connect(self):
Expand Down Expand Up @@ -87,4 +90,4 @@ def test_connect_using_sslcontext_verified(self):


if __name__ == "__main__":
unittest.main()
unittest.main()
Loading