# GET/SETANNOTATION contributed by Tomas Lindroos <skitta@abo.fi> June 2005.
# IDLE contributed by Forest <forestix@nom.one> August 2024.
-__version__ = "2.59"
+__version__ = "2.60"
import binascii, errno, random, re, socket, subprocess, sys, time, calendar
from datetime import datetime, timezone, timedelta
def _CRAM_MD5_AUTH(self, challenge):
""" Authobject to use with CRAM-MD5 authentication. """
import hmac
- pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
- else self.password)
- return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
+
+ if isinstance(self.password, str):
+ password = self.password.encode('utf-8')
+ else:
+ password = self.password
+
+ try:
+ authcode = hmac.HMAC(password, challenge, 'md5')
+ except ValueError: # HMAC-MD5 is not available
+ raise self.error("CRAM-MD5 authentication is not supported")
+ return f"{self.user} {authcode.hexdigest()}"
def logout(self):
self._send_tagged(tag, 'BAD', 'Expected DONE')
-class NewIMAPTestsMixin():
+class AuthHandler_CRAM_MD5(SimpleIMAPHandler):
+ capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
+ def cmd_AUTHENTICATE(self, tag, args):
+ self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
+ 'VzdG9uLm1jaS5uZXQ=')
+ r = yield
+ if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
+ b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
+ self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
+ else:
+ self._send_tagged(tag, 'NO', 'No access')
+
+
+class NewIMAPTestsMixin:
client = None
def _setup(self, imap_handler, connect=True):
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5_bytes(self):
- class AuthHandler(SimpleIMAPHandler):
- capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
- def cmd_AUTHENTICATE(self, tag, args):
- self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
- 'VzdG9uLm1jaS5uZXQ=')
- r = yield
- if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
- b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
- self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
- else:
- self._send_tagged(tag, 'NO', 'No access')
- client, _ = self._setup(AuthHandler)
- self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
+ client, _ = self._setup(AuthHandler_CRAM_MD5)
+ self.assertIn('AUTH=CRAM-MD5', client.capabilities)
ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf")
self.assertEqual(ret, "OK")
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5_plain_text(self):
- class AuthHandler(SimpleIMAPHandler):
- capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
- def cmd_AUTHENTICATE(self, tag, args):
- self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
- 'VzdG9uLm1jaS5uZXQ=')
- r = yield
- if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
- b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
- self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
- else:
- self._send_tagged(tag, 'NO', 'No access')
- client, _ = self._setup(AuthHandler)
- self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
+ client, _ = self._setup(AuthHandler_CRAM_MD5)
+ self.assertIn('AUTH=CRAM-MD5', client.capabilities)
ret, _ = client.login_cram_md5("tim", "tanstaaftanstaaf")
self.assertEqual(ret, "OK")
+ def test_login_cram_md5_blocked(self):
+ def side_effect(*a, **kw):
+ raise ValueError
+
+ client, _ = self._setup(AuthHandler_CRAM_MD5)
+ self.assertIn('AUTH=CRAM-MD5', client.capabilities)
+ msg = re.escape("CRAM-MD5 authentication is not supported")
+ with (
+ mock.patch("hmac.HMAC", side_effect=side_effect),
+ self.assertRaisesRegex(imaplib.IMAP4.error, msg)
+ ):
+ client.login_cram_md5("tim", b"tanstaaftanstaaf")
+
def test_aborted_authentication(self):
class MyServer(SimpleIMAPHandler):
def cmd_AUTHENTICATE(self, tag, args):