diff --git a/radicale/auth/pam.py b/radicale/auth/pam.py index 3194cb59..84cacb82 100644 --- a/radicale/auth/pam.py +++ b/radicale/auth/pam.py @@ -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 +from radicale import auth +from radicale.log import logger -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 - - # 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 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 - - # 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 +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: - log.LOGGER.debug("Wrong PAM password") - else: - log.LOGGER.debug( - "The PAM user doesn't belong to the required group (%s)" % - GROUP_MEMBERSHIP) + logger.info("auth.pam_group_membership: (empty, nothing to check / INSECURE)") - return False + 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 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 "" + + # Obtain supplementary groups + 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 the password + if self.pam_authenticate(login, password, service=self._service): + return login + else: + logger.debug("PAM authentication not successful for user: %r (service %r)" % (login, self._service)) + return ""