diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index b9c4c572..8718c08f 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -829,6 +829,10 @@ Available backends: `oauth2` : Use an OAuth2 server to authenticate users. +`pam` +: Use local PAM to authenticate users. + + Default: `none` ##### cache_logins diff --git a/radicale/auth/pam.py b/radicale/auth/pam.py new file mode 100644 index 00000000..25e66b19 --- /dev/null +++ b/radicale/auth/pam.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Radicale Server - Calendar Server +# Copyright © 2011 Henry-Nicolas Tourneur +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Radicale. If not, see . + +""" +PAM authentication. + +Authentication based on the ``pam-python`` module. + +""" + +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 + + # 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 + 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