Adds authentication using PLAIN SASL mechanism.
This is a plain-text authentication mechanism which can be used
instead of IMAP4.login() when UTF-8 support is required.
.. method:: IMAP4.login_cram_md5(user, password)
Force use of ``CRAM-MD5`` authentication when identifying the client to protect
- the password. Will only work if the server ``CAPABILITY`` response includes the
- phrase ``AUTH=CRAM-MD5``.
+ the password. It will only work if the server ``CAPABILITY`` response includes
+ the phrase ``AUTH=CRAM-MD5``.
.. versionchanged:: 3.15
An :exc:`IMAP4.error` is raised if MD5 support is not available.
+.. method:: IMAP4.login_plain(user, password)
+
+ Authenticate using the ``PLAIN`` SASL mechanism (:rfc:`4616`).
+
+ This is a plaintext authentication mechanism that can be used instead
+ of :meth:`login` when UTF-8 support is required (see :rfc:`6855`).
+ Since the credentials are only base64-encoded, not encrypted, this
+ method should only be used over a TLS-protected connection, such as
+ :class:`IMAP4_SSL` or after :meth:`starttls`.
+
+ It will only work if the server supports the ``PLAIN`` mechanism,
+ which it need not advertise as ``AUTH=PLAIN`` in its ``CAPABILITY``
+ response.
+
+ .. versionadded:: next
+
+
.. method:: IMAP4.logout()
Shutdown connection to server. Returns server ``BYE`` response.
a wrapper for the ``MOVE`` command (:rfc:`6851`).
(Contributed by Serhiy Storchaka in :gh:`77508`.)
+* Add the :meth:`~imaplib.IMAP4.login_plain` method, which authenticates
+ using the ``PLAIN`` SASL mechanism (:rfc:`4616`) and supports non-ASCII
+ user names and passwords.
+ (Contributed by Przemysław Buczkowski and Serhiy Storchaka in :gh:`89869`.)
+
logging
-------
return typ, dat
+ def login_plain(self, user, password):
+ """Authenticate using the PLAIN SASL mechanism (RFC 4616).
+
+ This is a plaintext authentication mechanism that can be used
+ instead of login() when UTF-8 support is required. Since the
+ credentials are only base64-encoded, not encrypted, it should
+ only be used over a TLS-protected connection.
+
+ 'user' and 'password' can be strings (encoded to UTF-8) or
+ bytes-like objects.
+ """
+ if isinstance(user, str):
+ user = user.encode('utf-8')
+ if isinstance(password, str):
+ password = password.encode('utf-8')
+ if b'\0' in user or b'\0' in password:
+ raise ValueError("NUL is not allowed in user name or password")
+ # An empty authorization identity (RFC 4616) makes the server
+ # derive it from the authentication identity; a non-empty one
+ # requests proxy authorization and is often rejected.
+ response = b'\0' + bytes(user) + b'\0' + bytes(password)
+ return self.authenticate("PLAIN", lambda _: response)
+
+
def login_cram_md5(self, user, password):
""" Force use of CRAM-MD5 authentication.
self._send_tagged(tag, 'NO', 'No access')
+class AuthHandler_PLAIN(SimpleIMAPHandler):
+ capabilities = 'LOGINDISABLED AUTH=PLAIN'
+ def cmd_AUTHENTICATE(self, tag, args):
+ self.server.auth_args = args
+ self._send_textline('+')
+ self.server.response = yield
+ self._send_tagged(tag, 'OK', 'Logged in.')
+
+
class NewIMAPTestsMixin:
client = None
with self.assertRaisesRegex(imaplib.IMAP4.error, msg):
client.login_cram_md5("tim", b"tanstaaftanstaaf")
+ def test_login_plain_ascii(self):
+ client, server = self._setup(AuthHandler_PLAIN)
+ self.assertIn('AUTH=PLAIN', client.capabilities)
+ ret, _ = client.login_plain("prem", "pass")
+ self.assertEqual(ret, "OK")
+ self.assertEqual(server.auth_args, ['PLAIN'])
+ self.assertEqual(server.response, b'AHByZW0AcGFzcw==\r\n')
+
+ def test_login_plain_utf8(self):
+ client, server = self._setup(AuthHandler_PLAIN)
+ self.assertIn('AUTH=PLAIN', client.capabilities)
+ ret, _ = client.login_plain("pręm", "żółć")
+ self.assertEqual(ret, "OK")
+ self.assertEqual(server.response, b'AHByxJltAMW8w7PFgsSH\r\n')
+
+ def test_login_plain_bytes(self):
+ # bytes are accepted and sent verbatim (not re-encoded).
+ client, server = self._setup(AuthHandler_PLAIN)
+ ret, _ = client.login_plain(b'pr\xc4\x99m', b'\xc5\xbc\xc3\xb3\xc5\x82\xc4\x87')
+ self.assertEqual(ret, "OK")
+ self.assertEqual(server.response, b'AHByxJltAMW8w7PFgsSH\r\n')
+
+ def test_login_plain_nul(self):
+ client, _ = self._setup(AuthHandler_PLAIN)
+ with self.assertRaises(ValueError):
+ client.login_plain('user\0', 'pass')
+ with self.assertRaises(ValueError):
+ client.login_plain('user', b'pa\0ss')
+
def test_aborted_authentication(self):
class MyServer(SimpleIMAPHandler):
def cmd_AUTHENTICATE(self, tag, args):
Brandt Bucher
Curtis Bucher
Colm Buckley
+Przemysław Buczkowski
Erik de Bueger
Jan-Hein Bührman
Marc Bürg
--- /dev/null
+Add :meth:`imaplib.IMAP4.login_plain`, which authenticates using the
+``PLAIN`` SASL mechanism (:rfc:`4616`). Unlike :meth:`~imaplib.IMAP4.login`,
+it supports non-ASCII user names and passwords.