/* pam_keyinit.c: 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 #include #include #include #include #include #include #include #include #include #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; static int session_counter; static int do_revoke; static int revoke_as_uid; static int 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 int error(pam_handle_t *pamh, const char *fmt, ...) __attribute__((format(printf, 2, 3))); static int 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); return PAM_SESSION_ERR; } /* * initialise the session keyring for this process */ static int init_keyrings(pam_handle_t *pamh, int force) { 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 PAM_SESSION_ERR; } usession = syscall(__NR_keyctl, KEYCTL_GET_KEYRING_ID, KEY_SPEC_USER_SESSION_KEYRING, 0); debug(pamh, "GET SESSION = %d", usession); if (usession < 0) return PAM_SESSION_ERR; /* 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 PAM_SESSION_ERR; 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 ? PAM_SESSION_ERR : PAM_SUCCESS; } /* * revoke the session keyring for this process */ static void kill_keyrings(pam_handle_t *pamh) { int old_uid, old_gid; /* 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); 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); syscall(__NR_keyctl, KEYCTL_REVOKE, my_session_keyring); /* return to the orignal 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); if (revoke_as_gid != old_gid && setregid(-1, old_gid) < 0) error(pamh, "Unable to change GID back to %d\n", old_gid); my_session_keyring = 0; } } /* * open a PAM session by making sure there's a session keyring */ int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv) { struct passwd *pw; const char *username; int ret, old_uid, uid, old_gid, gid, loop, force = 0; 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) */ session_counter++; debug(pamh, "OPEN %d", session_counter); 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) { error(pamh, "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 PAM_SESSION_ERR; } 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 PAM_SESSION_ERR; } ret = init_keyrings(pamh, force); /* return to the orignal UID and GID (probably root) */ if (uid != old_uid && setreuid(old_uid, -1) < 0) ret = error(pamh, "Unable to change UID back to %d\n", old_uid); if (gid != old_gid && setregid(old_gid, -1) < 0) ret = error(pamh, "Unable to change GID back to %d\n", old_gid); return ret; } /* * 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); return PAM_SUCCESS; }