pam: merge+adjust module from v1

This commit is contained in:
Peter Bieringer 2025-02-22 17:50:07 +01:00
parent c8f650bc2c
commit 855e3743ca

View file

@ -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 ""