diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:22:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:22:51 +0000 |
commit | 9ada0093e92388590c7368600ca4e9e3e376f0d0 (patch) | |
tree | a56fe41110023676d7082028cbaa47ca4b6e6164 /modules/pam_keyinit/pam_keyinit.c | |
parent | Initial commit. (diff) | |
download | pam-upstream/1.5.2.tar.xz pam-upstream/1.5.2.zip |
Adding upstream version 1.5.2.upstream/1.5.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | modules/pam_keyinit/pam_keyinit.c | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/modules/pam_keyinit/pam_keyinit.c b/modules/pam_keyinit/pam_keyinit.c new file mode 100644 index 0000000..92e4953 --- /dev/null +++ b/modules/pam_keyinit/pam_keyinit.c @@ -0,0 +1,298 @@ +/* + * pam_keyinit: Initialise the session keyring on login through a PAM module + * + * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program 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 + * 2 of the License, or (at your option) any later version. + */ + +#include "config.h" +#include <stdarg.h> +#include <string.h> +#include <syslog.h> +#include <pwd.h> +#include <unistd.h> +#include <errno.h> +#include <security/pam_modules.h> +#include <security/pam_modutil.h> +#include <security/pam_ext.h> +#include <sys/syscall.h> + +#define KEY_SPEC_SESSION_KEYRING -3 /* ID for session keyring */ +#define KEY_SPEC_USER_KEYRING -4 /* ID for UID-specific keyring */ +#define KEY_SPEC_USER_SESSION_KEYRING -5 /* - key ID for UID-session keyring */ + +#define KEYCTL_GET_KEYRING_ID 0 /* ask for a keyring's ID */ +#define KEYCTL_JOIN_SESSION_KEYRING 1 /* start named session keyring */ +#define KEYCTL_REVOKE 3 /* revoke a key */ +#define KEYCTL_LINK 8 /* link a key into a keyring */ + +static int my_session_keyring = 0; +static int session_counter = 0; +static int do_revoke = 0; +static uid_t revoke_as_uid; +static gid_t revoke_as_gid; +static int xdebug = 0; + +static void debug(pam_handle_t *pamh, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +static void debug(pam_handle_t *pamh, const char *fmt, ...) +{ + va_list va; + + if (xdebug) { + va_start(va, fmt); + pam_vsyslog(pamh, LOG_DEBUG, fmt, va); + va_end(va); + } +} + +static void error(pam_handle_t *pamh, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +static void error(pam_handle_t *pamh, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + pam_vsyslog(pamh, LOG_ERR, fmt, va); + va_end(va); +} + +/* + * initialise the session keyring for this process + */ +static int init_keyrings(pam_handle_t *pamh, int force, int error_ret) +{ + int session, usession, ret; + + if (!force) { + /* get the IDs of the session keyring and the user session + * keyring */ + session = syscall(__NR_keyctl, + KEYCTL_GET_KEYRING_ID, + KEY_SPEC_SESSION_KEYRING, + 0); + debug(pamh, "GET SESSION = %d", session); + if (session < 0) { + /* don't worry about keyrings if facility not + * installed */ + if (errno == ENOSYS) + return PAM_SUCCESS; + return error_ret; + } + + usession = syscall(__NR_keyctl, + KEYCTL_GET_KEYRING_ID, + KEY_SPEC_USER_SESSION_KEYRING, + 0); + debug(pamh, "GET SESSION = %d", usession); + if (usession < 0) + return error_ret; + + /* if the user session keyring is our keyring, then we don't + * need to do anything if we're not forcing */ + if (session != usession) + return PAM_SUCCESS; + } + + /* create a session keyring, discarding the old one */ + ret = syscall(__NR_keyctl, + KEYCTL_JOIN_SESSION_KEYRING, + NULL); + debug(pamh, "JOIN = %d", ret); + if (ret < 0) + return error_ret; + + my_session_keyring = ret; + + /* make a link from the session keyring to the user keyring */ + ret = syscall(__NR_keyctl, + KEYCTL_LINK, + KEY_SPEC_USER_KEYRING, + KEY_SPEC_SESSION_KEYRING); + + return ret < 0 ? error_ret : PAM_SUCCESS; +} + +/* + * revoke the session keyring for this process + */ +static int kill_keyrings(pam_handle_t *pamh, int error_ret) +{ + uid_t old_uid; + gid_t old_gid; + int ret = PAM_SUCCESS; + + /* revoke the session keyring we created earlier */ + if (my_session_keyring > 0) { + debug(pamh, "REVOKE %d", my_session_keyring); + + old_uid = geteuid(); + old_gid = getegid(); + debug(pamh, "UID:%d [%d] GID:%d [%d]", + revoke_as_uid, old_uid, revoke_as_gid, old_gid); + + /* switch to the real UID and GID so that we have permission to + * revoke the key */ + if (revoke_as_gid != old_gid && setregid(-1, revoke_as_gid) < 0) { + error(pamh, "Unable to change GID to %d temporarily\n", revoke_as_gid); + return error_ret; + } + + if (revoke_as_uid != old_uid && setresuid(-1, revoke_as_uid, old_uid) < 0) { + error(pamh, "Unable to change UID to %d temporarily\n", revoke_as_uid); + if (getegid() != old_gid && setregid(-1, old_gid) < 0) + error(pamh, "Unable to change GID back to %d\n", old_gid); + return error_ret; + } + + if (syscall(__NR_keyctl, KEYCTL_REVOKE, my_session_keyring) < 0) { + ret = error_ret; + } + + /* return to the original UID and GID (probably root) */ + if (revoke_as_uid != old_uid && setreuid(-1, old_uid) < 0) { + error(pamh, "Unable to change UID back to %d\n", old_uid); + ret = error_ret; + } + + if (revoke_as_gid != old_gid && setregid(-1, old_gid) < 0) { + error(pamh, "Unable to change GID back to %d\n", old_gid); + ret = error_ret; + } + + my_session_keyring = 0; + } + return ret; +} + +static int do_keyinit(pam_handle_t *pamh, int argc, const char **argv, int error_ret) +{ + struct passwd *pw; + const char *username; + int ret, loop, force = 0; + uid_t old_uid, uid; + gid_t old_gid, gid; + + for (loop = 0; loop < argc; loop++) { + if (strcmp(argv[loop], "force") == 0) + force = 1; + else if (strcmp(argv[loop], "debug") == 0) + xdebug = 1; + else if (strcmp(argv[loop], "revoke") == 0) + do_revoke = 1; + } + + /* don't do anything if already created a keyring (will be called + * multiple times if mentioned more than once in a pam script) + */ + if (my_session_keyring > 0) + return PAM_SUCCESS; + + /* look up the target UID */ + ret = pam_get_user(pamh, &username, "key user"); + if (ret != PAM_SUCCESS) + return ret; + + pw = pam_modutil_getpwnam(pamh, username); + if (!pw) { + pam_syslog(pamh, LOG_NOTICE, "Unable to look up user \"%s\"\n", + username); + return PAM_USER_UNKNOWN; + } + + revoke_as_uid = uid = pw->pw_uid; + old_uid = getuid(); + revoke_as_gid = gid = pw->pw_gid; + old_gid = getgid(); + debug(pamh, "UID:%d [%d] GID:%d [%d]", uid, old_uid, gid, old_gid); + + /* switch to the real UID and GID so that the keyring ends up owned by + * the right user */ + if (gid != old_gid && setregid(gid, -1) < 0) { + error(pamh, "Unable to change GID to %d temporarily\n", gid); + return error_ret; + } + + if (uid != old_uid && setreuid(uid, -1) < 0) { + error(pamh, "Unable to change UID to %d temporarily\n", uid); + if (setregid(old_gid, -1) < 0) + error(pamh, "Unable to change GID back to %d\n", old_gid); + return error_ret; + } + + ret = init_keyrings(pamh, force, error_ret); + + /* return to the original UID and GID (probably root) */ + if (uid != old_uid && setreuid(old_uid, -1) < 0) { + error(pamh, "Unable to change UID back to %d\n", old_uid); + ret = error_ret; + } + + if (gid != old_gid && setregid(old_gid, -1) < 0) { + error(pamh, "Unable to change GID back to %d\n", old_gid); + ret = error_ret; + } + + return ret; +} + +/* + * Dummy + */ +int pam_sm_authenticate(pam_handle_t *pamh UNUSED, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) +{ + return PAM_IGNORE; +} + +/* + * since setcred and open_session are called in different orders, a + * session ring is invoked by the first of these functions called. + */ +int pam_sm_setcred(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + if (flags & PAM_ESTABLISH_CRED) { + debug(pamh, "ESTABLISH_CRED"); + return do_keyinit(pamh, argc, argv, PAM_CRED_ERR); + } + if (flags & PAM_DELETE_CRED && my_session_keyring > 0 && do_revoke) { + debug(pamh, "DELETE_CRED"); + return kill_keyrings(pamh, PAM_CRED_ERR); + } + return PAM_IGNORE; +} + +int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED, + int argc, const char **argv) +{ + session_counter++; + + debug(pamh, "OPEN %d", session_counter); + + return do_keyinit(pamh, argc, argv, PAM_SESSION_ERR); +} + +/* + * close a PAM session by revoking the session keyring if requested + */ +int pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED, + int argc UNUSED, const char **argv UNUSED) +{ + debug(pamh, "CLOSE %d,%d,%d", + session_counter, my_session_keyring, do_revoke); + + session_counter--; + + if (session_counter <= 0 && my_session_keyring > 0 && do_revoke) + kill_keyrings(pamh, PAM_SESSION_ERR); + + return PAM_SUCCESS; +} |