|
|
@@ -21,75 +21,85 @@
|
|
|
"""
|
|
|
PAM authentication.
|
|
|
|
|
|
-Authentication based on the ``pam-python`` module.
|
|
|
+Authentication using the ``pam-python`` module.
|
|
|
|
|
|
+Important: radicale user need access to /etc/shadow by e.g.
|
|
|
+ chgrp radicale /etc/shadow
|
|
|
+ chmod g+r
|
|
|
"""
|
|
|
|
|
|
import grp
|
|
|
-import pam
|
|
|
import pwd
|
|
|
|
|
|
-from .. import config, log
|
|
|
-
|
|
|
-
|
|
|
-GROUP_MEMBERSHIP = config.get("auth", "pam_group_membership")
|
|
|
-
|
|
|
-
|
|
|
-# Compatibility for old versions of python-pam.
|
|
|
-if hasattr(pam, "pam"):
|
|
|
- def pam_authenticate(*args, **kwargs):
|
|
|
- return pam.pam().authenticate(*args, **kwargs)
|
|
|
-else:
|
|
|
- def pam_authenticate(*args, **kwargs):
|
|
|
- return pam.authenticate(*args, **kwargs)
|
|
|
-
|
|
|
-
|
|
|
-def is_authenticated(user, password):
|
|
|
- """Check if ``user``/``password`` couple is valid."""
|
|
|
- if user is None or password is None:
|
|
|
- return False
|
|
|
+from radicale import auth
|
|
|
+from radicale.log import logger
|
|
|
+
|
|
|
+
|
|
|
+class Auth(auth.BaseAuth):
|
|
|
+ def __init__(self, configuration) -> None:
|
|
|
+ super().__init__(configuration)
|
|
|
+ try:
|
|
|
+ import pam
|
|
|
+ self.pam = pam
|
|
|
+ except ImportError as e:
|
|
|
+ raise RuntimeError("PAM authentication requires the Python pam module") from e
|
|
|
+ self._service = configuration.get("auth", "pam_service")
|
|
|
+ logger.info("auth.pam_service: %s" % self._service)
|
|
|
+ self._group_membership = configuration.get("auth", "pam_group_membership")
|
|
|
+ if (self._group_membership):
|
|
|
+ logger.info("auth.pam_group_membership: %s" % self._group_membership)
|
|
|
+ else:
|
|
|
+ logger.info("auth.pam_group_membership: (empty, nothing to check / INSECURE)")
|
|
|
+
|
|
|
+ def pam_authenticate(self, *args, **kwargs):
|
|
|
+ return self.pam.authenticate(*args, **kwargs)
|
|
|
+
|
|
|
+ def _login(self, login: str, password: str) -> str:
|
|
|
+ """Check if ``user``/``password`` couple is valid."""
|
|
|
+ if login is None or password is None:
|
|
|
+ return ""
|
|
|
+
|
|
|
+ # Check whether the user exists in the PAM system
|
|
|
+ try:
|
|
|
+ pwd.getpwnam(login).pw_uid
|
|
|
+ except KeyError:
|
|
|
+ logger.debug("PAM user not found: %r" % login)
|
|
|
+ return ""
|
|
|
+ else:
|
|
|
+ logger.debug("PAM user found: %r" % login)
|
|
|
|
|
|
- # Check whether the user exists in the PAM system
|
|
|
- try:
|
|
|
- pwd.getpwnam(user).pw_uid
|
|
|
- except KeyError:
|
|
|
- log.LOGGER.debug("User %s not found" % user)
|
|
|
- return False
|
|
|
- else:
|
|
|
- log.LOGGER.debug("User %s found" % user)
|
|
|
+ # Check whether the user has a primary group (mandatory)
|
|
|
+ try:
|
|
|
+ # Get user primary group
|
|
|
+ primary_group = grp.getgrgid(pwd.getpwnam(login).pw_gid).gr_name
|
|
|
+ logger.debug("PAM user %r has primary group: %r" % (login, primary_group))
|
|
|
+ except KeyError:
|
|
|
+ logger.debug("PAM user has no primary group: %r" % login)
|
|
|
+ return ""
|
|
|
|
|
|
- # Check whether the group exists
|
|
|
- try:
|
|
|
# Obtain supplementary groups
|
|
|
- members = grp.getgrnam(GROUP_MEMBERSHIP).gr_mem
|
|
|
- except KeyError:
|
|
|
- log.LOGGER.debug(
|
|
|
- "The PAM membership required group (%s) doesn't exist" %
|
|
|
- GROUP_MEMBERSHIP)
|
|
|
- return False
|
|
|
+ members = []
|
|
|
+ if (self._group_membership):
|
|
|
+ try:
|
|
|
+ members = grp.getgrnam(self._group_membership).gr_mem
|
|
|
+ except KeyError:
|
|
|
+ logger.debug(
|
|
|
+ "PAM membership required group doesn't exist: %r" %
|
|
|
+ self._group_membership)
|
|
|
+ return ""
|
|
|
+
|
|
|
+ # Check whether the user belongs to the required group
|
|
|
+ # (primary or supplementary)
|
|
|
+ if (self._group_membership):
|
|
|
+ if (primary_group != self._group_membership) and (login not in members):
|
|
|
+ logger.warning("PAM user %r belongs not to the required group: %r" % (login, self._group_membership))
|
|
|
+ return ""
|
|
|
+ else:
|
|
|
+ logger.debug("PAM user %r belongs to the required group: %r" % (login, self._group_membership))
|
|
|
|
|
|
- # Check whether the user exists
|
|
|
- try:
|
|
|
- # Get user primary group
|
|
|
- primary_group = grp.getgrgid(pwd.getpwnam(user).pw_gid).gr_name
|
|
|
- except KeyError:
|
|
|
- log.LOGGER.debug("The PAM user (%s) doesn't exist" % user)
|
|
|
- return False
|
|
|
-
|
|
|
- # Check whether the user belongs to the required group
|
|
|
- # (primary or supplementary)
|
|
|
- if primary_group == GROUP_MEMBERSHIP or user in members:
|
|
|
- log.LOGGER.debug(
|
|
|
- "The PAM user belongs to the required group (%s)" %
|
|
|
- GROUP_MEMBERSHIP)
|
|
|
# Check the password
|
|
|
- if pam_authenticate(user, password, service='radicale'):
|
|
|
- return True
|
|
|
+ if self.pam_authenticate(login, password, service=self._service):
|
|
|
+ return login
|
|
|
else:
|
|
|
- log.LOGGER.debug("Wrong PAM password")
|
|
|
- else:
|
|
|
- log.LOGGER.debug(
|
|
|
- "The PAM user doesn't belong to the required group (%s)" %
|
|
|
- GROUP_MEMBERSHIP)
|
|
|
-
|
|
|
- return False
|
|
|
+ logger.debug("PAM authentication not successful for user: %r (service %r)" % (login, self._service))
|
|
|
+ return ""
|