Skip to content
Projeler
Gruplar
Parçacıklar
Yardım
Yükleniyor...
Oturum aç / Kaydol
Gezinmeyi değiştir
C
cpython
Proje
Proje
Ayrıntılar
Etkinlik
Cycle Analytics
Depo (repository)
Depo (repository)
Dosyalar
Kayıtlar (commit)
Dallar (branch)
Etiketler
Katkıda bulunanlar
Grafik
Karşılaştır
Grafikler
Konular (issue)
0
Konular (issue)
0
Liste
Pano
Etiketler
Kilometre Taşları
Birleştirme (merge) Talepleri
0
Birleştirme (merge) Talepleri
0
CI / CD
CI / CD
İş akışları (pipeline)
İşler
Zamanlamalar
Grafikler
Paketler
Paketler
Wiki
Wiki
Parçacıklar
Parçacıklar
Üyeler
Üyeler
Collapse sidebar
Close sidebar
Etkinlik
Grafik
Grafikler
Yeni bir konu (issue) oluştur
İşler
Kayıtlar (commit)
Konu (issue) Panoları
Kenar çubuğunu aç
Batuhan Osman TASKAYA
cpython
Commits
c5ea754e
Kaydet (Commit)
c5ea754e
authored
Tem 09, 2015
tarafından
Barry Warsaw
Dosyalara gözat
Seçenekler
Dosyalara Gözat
İndir
Eposta Yamaları
Sade Fark
- Issue #15014: SMTP.auth() and SMTP.login() now support RFC 4954's optional
initial-response argument to the SMTP AUTH command.
üst
b85b4275
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
153 additions
and
39 deletions
+153
-39
smtplib.rst
Doc/library/smtplib.rst
+31
-14
smtplib.py
Lib/smtplib.py
+36
-16
test_smtplib.py
Lib/test/test_smtplib.py
+83
-9
NEWS
Misc/NEWS
+3
-0
No files found.
Doc/library/smtplib.rst
Dosyayı görüntüle @
c5ea754e
...
@@ -288,7 +288,7 @@ An :class:`SMTP` instance has the following methods:
...
@@ -288,7 +288,7 @@ An :class:`SMTP` instance has the following methods:
Many sites disable SMTP ``VRFY`` in order to foil spammers.
Many sites disable SMTP ``VRFY`` in order to foil spammers.
.. method:: SMTP.login(user, password)
.. method:: SMTP.login(user, password
, *, initial_response_ok=True
)
Log in on an SMTP server that requires authentication. The arguments are the
Log in on an SMTP server that requires authentication. The arguments are the
username and the password to authenticate with. If there has been no previous
username and the password to authenticate with. If there has been no previous
...
@@ -309,14 +309,21 @@ An :class:`SMTP` instance has the following methods:
...
@@ -309,14 +309,21 @@ An :class:`SMTP` instance has the following methods:
No suitable authentication method was found.
No suitable authentication method was found.
Each of the authentication methods supported by :mod:`smtplib` are tried in
Each of the authentication methods supported by :mod:`smtplib` are tried in
turn if they are advertised as supported by the server (see :meth:`auth`
turn if they are advertised as supported by the server. See :meth:`auth`
for a list of supported authentication methods).
for a list of supported authentication methods. *initial_response_ok* is
passed through to :meth:`auth`.
Optional keyword argument *initial_response_ok* specifies whether, for
authentication methods that support it, an "initial response" as specified
in :rfc:`4954` can be sent along with the ``AUTH`` command, rather than
requiring a challenge/response.
.. versionchanged:: 3.5
.. versionchanged:: 3.5
:exc:`SMTPNotSupportedError` may be raised.
:exc:`SMTPNotSupportedError` may be raised, and the
*initial_response_ok* parameter was added.
.. method:: SMTP.auth(mechanism, authobject)
.. method:: SMTP.auth(mechanism, authobject
, *, initial_response_ok=True
)
Issue an ``SMTP`` ``AUTH`` command for the specified authentication
Issue an ``SMTP`` ``AUTH`` command for the specified authentication
*mechanism*, and handle the challenge response via *authobject*.
*mechanism*, and handle the challenge response via *authobject*.
...
@@ -325,13 +332,23 @@ An :class:`SMTP` instance has the following methods:
...
@@ -325,13 +332,23 @@ An :class:`SMTP` instance has the following methods:
be used as argument to the ``AUTH`` command; the valid values are
be used as argument to the ``AUTH`` command; the valid values are
those listed in the ``auth`` element of :attr:`esmtp_features`.
those listed in the ``auth`` element of :attr:`esmtp_features`.
*authobject* must be a callable object taking a single argument:
*authobject* must be a callable object taking an optional single argument:
data = authobject(challenge=None)
data = authobject(challenge)
If optional keyword argument *initial_response_ok* is true,
``authobject()`` will be called first with no argument. It can return the
:rfc:`4954` "initial response" bytes which will be encoded and sent with
the ``AUTH`` command as below. If the ``authobject()`` does not support an
initial response (e.g. because it requires a challenge), it should return
None when called with ``challenge=None``. If *initial_response_ok* is
false, then ``authobject()`` will not be called first with None.
It will be called to process the server's challenge response; the
If the initial response check returns None, or if *initial_response_ok* is
*challenge* argument it is passed will be a ``bytes``. It should return
false, ``authobject()`` will be called to process the server's challenge
``bytes`` *data* that will be base64 encoded and sent to the server.
response; the *challenge* argument it is passed will be a ``bytes``. It
should return ``bytes`` *data* that will be base64 encoded and sent to the
server.
The ``SMTP`` class provides ``authobjects`` for the ``CRAM-MD5``, ``PLAIN``,
The ``SMTP`` class provides ``authobjects`` for the ``CRAM-MD5``, ``PLAIN``,
and ``LOGIN`` mechanisms; they are named ``SMTP.auth_cram_md5``,
and ``LOGIN`` mechanisms; they are named ``SMTP.auth_cram_md5``,
...
@@ -340,10 +357,10 @@ An :class:`SMTP` instance has the following methods:
...
@@ -340,10 +357,10 @@ An :class:`SMTP` instance has the following methods:
set to appropriate values.
set to appropriate values.
User code does not normally need to call ``auth`` directly, but can instead
User code does not normally need to call ``auth`` directly, but can instead
call the :meth:`login` method, which will try each of the above mechanisms
in
call the :meth:`login` method, which will try each of the above mechanisms
turn, in the order listed. ``auth`` is exposed to facilitate the
in
turn, in the order listed. ``auth`` is exposed to facilitate the
implementation of authentication methods not (or not yet) supported
directly
implementation of authentication methods not (or not yet) supported
by :mod:`smtplib`.
directly
by :mod:`smtplib`.
.. versionadded:: 3.5
.. versionadded:: 3.5
...
...
Lib/smtplib.py
Dosyayı görüntüle @
c5ea754e
...
@@ -601,7 +601,7 @@ class SMTP:
...
@@ -601,7 +601,7 @@ class SMTP:
if
not
(
200
<=
code
<=
299
):
if
not
(
200
<=
code
<=
299
):
raise
SMTPHeloError
(
code
,
resp
)
raise
SMTPHeloError
(
code
,
resp
)
def
auth
(
self
,
mechanism
,
authobject
):
def
auth
(
self
,
mechanism
,
authobject
,
*
,
initial_response_ok
=
True
):
"""Authentication command - requires response processing.
"""Authentication command - requires response processing.
'mechanism' specifies which authentication mechanism is to
'mechanism' specifies which authentication mechanism is to
...
@@ -615,32 +615,46 @@ class SMTP:
...
@@ -615,32 +615,46 @@ class SMTP:
It will be called to process the server's challenge response; the
It will be called to process the server's challenge response; the
challenge argument it is passed will be a bytes. It should return
challenge argument it is passed will be a bytes. It should return
bytes data that will be base64 encoded and sent to the server.
bytes data that will be base64 encoded and sent to the server.
"""
Keyword arguments:
- initial_response_ok: Allow sending the RFC 4954 initial-response
to the AUTH command, if the authentication methods supports it.
"""
# RFC 4954 allows auth methods to provide an initial response. Not all
# methods support it. By definition, if they return something other
# than None when challenge is None, then they do. See issue #15014.
mechanism
=
mechanism
.
upper
()
mechanism
=
mechanism
.
upper
()
(
code
,
resp
)
=
self
.
docmd
(
"AUTH"
,
mechanism
)
initial_response
=
(
authobject
()
if
initial_response_ok
else
None
)
# Server replies with 334 (challenge) or 535 (not supported)
if
initial_response
is
not
None
:
if
code
==
334
:
response
=
encode_base64
(
initial_response
.
encode
(
'ascii'
),
eol
=
''
)
challenge
=
base64
.
decodebytes
(
resp
)
(
code
,
resp
)
=
self
.
docmd
(
"AUTH"
,
mechanism
+
" "
+
response
)
response
=
encode_base64
(
else
:
authobject
(
challenge
)
.
encode
(
'ascii'
),
eol
=
''
)
(
code
,
resp
)
=
self
.
docmd
(
"AUTH"
,
mechanism
)
(
code
,
resp
)
=
self
.
docmd
(
response
)
# Server replies with 334 (challenge) or 535 (not supported)
if
code
in
(
235
,
503
):
if
code
==
334
:
return
(
code
,
resp
)
challenge
=
base64
.
decodebytes
(
resp
)
response
=
encode_base64
(
authobject
(
challenge
)
.
encode
(
'ascii'
),
eol
=
''
)
(
code
,
resp
)
=
self
.
docmd
(
response
)
if
code
in
(
235
,
503
):
return
(
code
,
resp
)
raise
SMTPAuthenticationError
(
code
,
resp
)
raise
SMTPAuthenticationError
(
code
,
resp
)
def
auth_cram_md5
(
self
,
challenge
):
def
auth_cram_md5
(
self
,
challenge
=
None
):
""" Authobject to use with CRAM-MD5 authentication. Requires self.user
""" Authobject to use with CRAM-MD5 authentication. Requires self.user
and self.password to be set."""
and self.password to be set."""
# CRAM-MD5 does not support initial-response.
if
challenge
is
None
:
return
None
return
self
.
user
+
" "
+
hmac
.
HMAC
(
return
self
.
user
+
" "
+
hmac
.
HMAC
(
self
.
password
.
encode
(
'ascii'
),
challenge
,
'md5'
)
.
hexdigest
()
self
.
password
.
encode
(
'ascii'
),
challenge
,
'md5'
)
.
hexdigest
()
def
auth_plain
(
self
,
challenge
):
def
auth_plain
(
self
,
challenge
=
None
):
""" Authobject to use with PLAIN authentication. Requires self.user and
""" Authobject to use with PLAIN authentication. Requires self.user and
self.password to be set."""
self.password to be set."""
return
"
\0
%
s
\0
%
s"
%
(
self
.
user
,
self
.
password
)
return
"
\0
%
s
\0
%
s"
%
(
self
.
user
,
self
.
password
)
def
auth_login
(
self
,
challenge
):
def
auth_login
(
self
,
challenge
=
None
):
""" Authobject to use with LOGIN authentication. Requires self.user and
""" Authobject to use with LOGIN authentication. Requires self.user and
self.password to be set."""
self.password to be set."""
(
code
,
resp
)
=
self
.
docmd
(
(
code
,
resp
)
=
self
.
docmd
(
...
@@ -649,13 +663,17 @@ class SMTP:
...
@@ -649,13 +663,17 @@ class SMTP:
return
self
.
password
return
self
.
password
raise
SMTPAuthenticationError
(
code
,
resp
)
raise
SMTPAuthenticationError
(
code
,
resp
)
def
login
(
self
,
user
,
password
):
def
login
(
self
,
user
,
password
,
*
,
initial_response_ok
=
True
):
"""Log in on an SMTP server that requires authentication.
"""Log in on an SMTP server that requires authentication.
The arguments are:
The arguments are:
- user: The user name to authenticate with.
- user: The user name to authenticate with.
- password: The password for the authentication.
- password: The password for the authentication.
Keyword arguments:
- initial_response_ok: Allow sending the RFC 4954 initial-response
to the AUTH command, if the authentication methods supports it.
If there has been no previous EHLO or HELO command this session, this
If there has been no previous EHLO or HELO command this session, this
method tries ESMTP EHLO first.
method tries ESMTP EHLO first.
...
@@ -698,7 +716,9 @@ class SMTP:
...
@@ -698,7 +716,9 @@ class SMTP:
for
authmethod
in
authlist
:
for
authmethod
in
authlist
:
method_name
=
'auth_'
+
authmethod
.
lower
()
.
replace
(
'-'
,
'_'
)
method_name
=
'auth_'
+
authmethod
.
lower
()
.
replace
(
'-'
,
'_'
)
try
:
try
:
(
code
,
resp
)
=
self
.
auth
(
authmethod
,
getattr
(
self
,
method_name
))
(
code
,
resp
)
=
self
.
auth
(
authmethod
,
getattr
(
self
,
method_name
),
initial_response_ok
=
initial_response_ok
)
# 235 == 'Authentication successful'
# 235 == 'Authentication successful'
# 503 == 'Error: already authenticated'
# 503 == 'Error: already authenticated'
if
code
in
(
235
,
503
):
if
code
in
(
235
,
503
):
...
...
Lib/test/test_smtplib.py
Dosyayı görüntüle @
c5ea754e
import
asyncore
import
asyncore
import
email.mime.text
import
email.mime.text
from
email.message
import
EmailMessage
from
email.message
import
EmailMessage
from
email.base64mime
import
body_encode
as
encode_base64
import
email.utils
import
email.utils
import
socket
import
socket
import
smtpd
import
smtpd
...
@@ -814,11 +815,11 @@ class SMTPSimTests(unittest.TestCase):
...
@@ -814,11 +815,11 @@ class SMTPSimTests(unittest.TestCase):
def
testVRFY
(
self
):
def
testVRFY
(
self
):
smtp
=
smtplib
.
SMTP
(
HOST
,
self
.
port
,
local_hostname
=
'localhost'
,
timeout
=
15
)
smtp
=
smtplib
.
SMTP
(
HOST
,
self
.
port
,
local_hostname
=
'localhost'
,
timeout
=
15
)
for
email
,
name
in
sim_users
.
items
():
for
addr_spec
,
name
in
sim_users
.
items
():
expected_known
=
(
250
,
bytes
(
'
%
s
%
s'
%
expected_known
=
(
250
,
bytes
(
'
%
s
%
s'
%
(
name
,
smtplib
.
quoteaddr
(
email
)),
(
name
,
smtplib
.
quoteaddr
(
addr_spec
)),
"ascii"
))
"ascii"
))
self
.
assertEqual
(
smtp
.
vrfy
(
email
),
expected_known
)
self
.
assertEqual
(
smtp
.
vrfy
(
addr_spec
),
expected_known
)
u
=
'nobody@nowhere.com'
u
=
'nobody@nowhere.com'
expected_unknown
=
(
550
,
(
'No such user:
%
s'
%
u
)
.
encode
(
'ascii'
))
expected_unknown
=
(
550
,
(
'No such user:
%
s'
%
u
)
.
encode
(
'ascii'
))
...
@@ -851,7 +852,7 @@ class SMTPSimTests(unittest.TestCase):
...
@@ -851,7 +852,7 @@ class SMTPSimTests(unittest.TestCase):
def
testAUTH_PLAIN
(
self
):
def
testAUTH_PLAIN
(
self
):
self
.
serv
.
add_feature
(
"AUTH PLAIN"
)
self
.
serv
.
add_feature
(
"AUTH PLAIN"
)
smtp
=
smtplib
.
SMTP
(
HOST
,
self
.
port
,
local_hostname
=
'localhost'
,
timeout
=
15
)
smtp
=
smtplib
.
SMTP
(
HOST
,
self
.
port
,
local_hostname
=
'localhost'
,
timeout
=
15
)
try
:
smtp
.
login
(
sim_auth
[
0
],
sim_auth
[
1
])
try
:
smtp
.
login
(
sim_auth
[
0
],
sim_auth
[
1
]
,
initial_response_ok
=
False
)
except
smtplib
.
SMTPAuthenticationError
as
err
:
except
smtplib
.
SMTPAuthenticationError
as
err
:
self
.
assertIn
(
sim_auth_plain
,
str
(
err
))
self
.
assertIn
(
sim_auth_plain
,
str
(
err
))
smtp
.
close
()
smtp
.
close
()
...
@@ -892,7 +893,7 @@ class SMTPSimTests(unittest.TestCase):
...
@@ -892,7 +893,7 @@ class SMTPSimTests(unittest.TestCase):
'LOGIN'
:
smtp
.
auth_login
,
'LOGIN'
:
smtp
.
auth_login
,
}
}
for
mechanism
,
method
in
supported
.
items
():
for
mechanism
,
method
in
supported
.
items
():
try
:
smtp
.
auth
(
mechanism
,
method
)
try
:
smtp
.
auth
(
mechanism
,
method
,
initial_response_ok
=
False
)
except
smtplib
.
SMTPAuthenticationError
as
err
:
except
smtplib
.
SMTPAuthenticationError
as
err
:
self
.
assertIn
(
sim_auth_credentials
[
mechanism
.
lower
()]
.
upper
(),
self
.
assertIn
(
sim_auth_credentials
[
mechanism
.
lower
()]
.
upper
(),
str
(
err
))
str
(
err
))
...
@@ -1142,12 +1143,85 @@ class SMTPUTF8SimTests(unittest.TestCase):
...
@@ -1142,12 +1143,85 @@ class SMTPUTF8SimTests(unittest.TestCase):
smtp
.
send_message
(
msg
))
smtp
.
send_message
(
msg
))
EXPECTED_RESPONSE
=
encode_base64
(
b
'
\0
psu
\0
doesnotexist'
,
eol
=
''
)
class
SimSMTPAUTHInitialResponseChannel
(
SimSMTPChannel
):
def
smtp_AUTH
(
self
,
arg
):
# RFC 4954's AUTH command allows for an optional initial-response.
# Not all AUTH methods support this; some require a challenge. AUTH
# PLAIN does those, so test that here. See issue #15014.
args
=
arg
.
split
()
if
args
[
0
]
.
lower
()
==
'plain'
:
if
len
(
args
)
==
2
:
# AUTH PLAIN <initial-response> with the response base 64
# encoded. Hard code the expected response for the test.
if
args
[
1
]
==
EXPECTED_RESPONSE
:
self
.
push
(
'235 Ok'
)
return
self
.
push
(
'571 Bad authentication'
)
class
SimSMTPAUTHInitialResponseServer
(
SimSMTPServer
):
channel_class
=
SimSMTPAUTHInitialResponseChannel
@unittest.skipUnless
(
threading
,
'Threading required for this test.'
)
class
SMTPAUTHInitialResponseSimTests
(
unittest
.
TestCase
):
def
setUp
(
self
):
self
.
real_getfqdn
=
socket
.
getfqdn
socket
.
getfqdn
=
mock_socket
.
getfqdn
self
.
serv_evt
=
threading
.
Event
()
self
.
client_evt
=
threading
.
Event
()
# Pick a random unused port by passing 0 for the port number
self
.
serv
=
SimSMTPAUTHInitialResponseServer
(
(
HOST
,
0
),
(
'nowhere'
,
-
1
),
decode_data
=
True
)
# Keep a note of what port was assigned
self
.
port
=
self
.
serv
.
socket
.
getsockname
()[
1
]
serv_args
=
(
self
.
serv
,
self
.
serv_evt
,
self
.
client_evt
)
self
.
thread
=
threading
.
Thread
(
target
=
debugging_server
,
args
=
serv_args
)
self
.
thread
.
start
()
# wait until server thread has assigned a port number
self
.
serv_evt
.
wait
()
self
.
serv_evt
.
clear
()
def
tearDown
(
self
):
socket
.
getfqdn
=
self
.
real_getfqdn
# indicate that the client is finished
self
.
client_evt
.
set
()
# wait for the server thread to terminate
self
.
serv_evt
.
wait
()
self
.
thread
.
join
()
def
testAUTH_PLAIN_initial_response_login
(
self
):
self
.
serv
.
add_feature
(
'AUTH PLAIN'
)
smtp
=
smtplib
.
SMTP
(
HOST
,
self
.
port
,
local_hostname
=
'localhost'
,
timeout
=
15
)
smtp
.
login
(
'psu'
,
'doesnotexist'
)
smtp
.
close
()
def
testAUTH_PLAIN_initial_response_auth
(
self
):
self
.
serv
.
add_feature
(
'AUTH PLAIN'
)
smtp
=
smtplib
.
SMTP
(
HOST
,
self
.
port
,
local_hostname
=
'localhost'
,
timeout
=
15
)
smtp
.
user
=
'psu'
smtp
.
password
=
'doesnotexist'
code
,
response
=
smtp
.
auth
(
'plain'
,
smtp
.
auth_plain
)
smtp
.
close
()
self
.
assertEqual
(
code
,
235
)
@support.reap_threads
@support.reap_threads
def
test_main
(
verbose
=
None
):
def
test_main
(
verbose
=
None
):
support
.
run_unittest
(
GeneralTests
,
DebuggingServerTests
,
support
.
run_unittest
(
NonConnectingTests
,
BadHELOServerTests
,
BadHELOServerTests
,
SMTPSimTests
,
DebuggingServerTests
,
TooLongLineTests
)
GeneralTests
,
NonConnectingTests
,
SMTPAUTHInitialResponseSimTests
,
SMTPSimTests
,
TooLongLineTests
,
)
if
__name__
==
'__main__'
:
if
__name__
==
'__main__'
:
test_main
()
test_main
()
Misc/NEWS
Dosyayı görüntüle @
c5ea754e
...
@@ -22,6 +22,9 @@ Library
...
@@ -22,6 +22,9 @@ Library
- Issue #24259: tarfile now raises a ReadError if an archive is truncated
- Issue #24259: tarfile now raises a ReadError if an archive is truncated
inside a data segment.
inside a data segment.
- Issue #15014: SMTP.auth() and SMTP.login() now support RFC 4954'
s
optional
initial
-
response
argument
to
the
SMTP
AUTH
command
.
What
's New in Python 3.5.0 beta 3?
What
's New in Python 3.5.0 beta 3?
==================================
==================================
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment