imap.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. # RadicaleIMAP IMAP authentication plugin for Radicale.
  2. # Copyright © 2017, 2020 Unrud <unrud@outlook.com>
  3. # Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. import imaplib
  18. import ssl
  19. import sys
  20. from typing import Union
  21. from radicale import auth
  22. from radicale.log import logger
  23. class Auth(auth.BaseAuth):
  24. """Authenticate user with IMAP."""
  25. def __init__(self, configuration) -> None:
  26. super().__init__(configuration)
  27. self._host, self._port = self.configuration.get("auth", "imap_host")
  28. logger.info("auth imap host: %r", self._host)
  29. self._security = self.configuration.get("auth", "imap_security")
  30. if self._security == "none":
  31. logger.warning("auth imap security: %s (INSECURE, credentials are transmitted in clear text)", self._security)
  32. else:
  33. logger.info("auth imap security: %s", self._security)
  34. if self._security == "tls":
  35. if self._port is None:
  36. self._port = 993
  37. logger.info("auth imap port (autoselected): %d", self._port)
  38. else:
  39. logger.info("auth imap port: %d", self._port)
  40. else:
  41. if self._port is None:
  42. self._port = 143
  43. logger.info("auth imap port (autoselected): %d", self._port)
  44. else:
  45. logger.info("auth imap port: %d", self._port)
  46. def _login(self, login, password) -> str:
  47. try:
  48. if sys.version_info < (3, 10):
  49. connection: Union[imaplib.IMAP4, imaplib.IMAP4_SSL]
  50. else:
  51. connection: imaplib.IMAP4 | imaplib.IMAP4_SSL
  52. if self._security == "tls":
  53. connection = imaplib.IMAP4_SSL(
  54. host=self._host, port=self._port,
  55. ssl_context=ssl.create_default_context())
  56. else:
  57. connection = imaplib.IMAP4(host=self._host, port=self._port)
  58. if self._security == "starttls":
  59. connection.starttls(ssl.create_default_context())
  60. try:
  61. connection.authenticate(
  62. "PLAIN",
  63. lambda _: "{0}\x00{0}\x00{1}".format(login, password).encode(),
  64. )
  65. except imaplib.IMAP4.error as e:
  66. logger.warning("IMAP authentication failed for user %r: %s", login, e, exc_info=False)
  67. return ""
  68. connection.logout()
  69. return login
  70. except (OSError, imaplib.IMAP4.error) as e:
  71. logger.error("Failed to communicate with IMAP server %r: %s" % ("[%s]:%d" % (self._host, self._port), e))
  72. return ""