(root)/
Linux-PAM-1.5.3/
modules/
pam_keyinit/
pam_keyinit.c
       1  /*
       2   * pam_keyinit: Initialise the session keyring on login through a PAM module
       3   *
       4   * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved.
       5   * Written by David Howells (dhowells@redhat.com)
       6   *
       7   * This program is free software; you can redistribute it and/or
       8   * modify it under the terms of the GNU General Public License
       9   * as published by the Free Software Foundation; either version
      10   * 2 of the License, or (at your option) any later version.
      11   */
      12  
      13  #include "config.h"
      14  #include <stdarg.h>
      15  #include <string.h>
      16  #include <syslog.h>
      17  #include <pwd.h>
      18  #include <unistd.h>
      19  #include <errno.h>
      20  #include <security/pam_modules.h>
      21  #include <security/pam_modutil.h>
      22  #include <security/pam_ext.h>
      23  #include <sys/syscall.h>
      24  #include <stdatomic.h>
      25  
      26  #define KEY_SPEC_SESSION_KEYRING	-3 /* ID for session keyring */
      27  #define KEY_SPEC_USER_KEYRING		-4 /* ID for UID-specific keyring */
      28  #define KEY_SPEC_USER_SESSION_KEYRING	-5 /* - key ID for UID-session keyring */
      29  
      30  #define KEYCTL_GET_KEYRING_ID		0 /* ask for a keyring's ID */
      31  #define KEYCTL_JOIN_SESSION_KEYRING	1 /* start named session keyring */
      32  #define KEYCTL_REVOKE			3 /* revoke a key */
      33  #define KEYCTL_LINK			8 /* link a key into a keyring */
      34  
      35  static _Thread_local int my_session_keyring = 0;
      36  static _Atomic int session_counter = 0;
      37  static _Thread_local int do_revoke = 0;
      38  static _Thread_local uid_t revoke_as_uid;
      39  static _Thread_local gid_t revoke_as_gid;
      40  static _Thread_local int xdebug = 0;
      41  
      42  static void debug(pam_handle_t *pamh, const char *fmt, ...)
      43  	__attribute__((format(printf, 2, 3)));
      44  
      45  static void debug(pam_handle_t *pamh, const char *fmt, ...)
      46  {
      47  	va_list va;
      48  
      49  	if (xdebug) {
      50  		va_start(va, fmt);
      51  		pam_vsyslog(pamh, LOG_DEBUG, fmt, va);
      52  		va_end(va);
      53  	}
      54  }
      55  
      56  static void error(pam_handle_t *pamh, const char *fmt, ...)
      57  	__attribute__((format(printf, 2, 3)));
      58  
      59  static void error(pam_handle_t *pamh, const char *fmt, ...)
      60  {
      61  	va_list va;
      62  
      63  	va_start(va, fmt);
      64  	pam_vsyslog(pamh, LOG_ERR, fmt, va);
      65  	va_end(va);
      66  }
      67  
      68  static int pam_setreuid(uid_t ruid, uid_t euid)
      69  {
      70  #if defined(SYS_setreuid32)
      71      return syscall(SYS_setreuid32, ruid, euid);
      72  #else
      73      return syscall(SYS_setreuid, ruid, euid);
      74  #endif
      75  }
      76  
      77  static int pam_setregid(gid_t rgid, gid_t egid)
      78  {
      79  #if defined(SYS_setregid32)
      80      return syscall(SYS_setregid32, rgid, egid);
      81  #else
      82      return syscall(SYS_setregid, rgid, egid);
      83  #endif
      84  }
      85  
      86  static int pam_setresuid(uid_t ruid, uid_t euid, uid_t suid)
      87  {
      88  #if defined(SYS_setresuid32)
      89      return syscall(SYS_setresuid32, ruid, euid, suid);
      90  #else
      91      return syscall(SYS_setresuid, ruid, euid, suid);
      92  #endif
      93  }
      94  
      95  /*
      96   * initialise the session keyring for this process
      97   */
      98  static int init_keyrings(pam_handle_t *pamh, int force, int error_ret)
      99  {
     100  	int session, usession, ret;
     101  
     102  	if (!force) {
     103  		/* get the IDs of the session keyring and the user session
     104  		 * keyring */
     105  		session = syscall(__NR_keyctl,
     106  				  KEYCTL_GET_KEYRING_ID,
     107  				  KEY_SPEC_SESSION_KEYRING,
     108  				  0);
     109  		debug(pamh, "GET SESSION = %d", session);
     110  		if (session < 0) {
     111  			/* don't worry about keyrings if facility not
     112  			 * installed */
     113  			if (errno == ENOSYS)
     114  				return PAM_SUCCESS;
     115  			return error_ret;
     116  		}
     117  
     118  		usession = syscall(__NR_keyctl,
     119  				   KEYCTL_GET_KEYRING_ID,
     120  				   KEY_SPEC_USER_SESSION_KEYRING,
     121  				   0);
     122  		debug(pamh, "GET SESSION = %d", usession);
     123  		if (usession < 0)
     124  			return error_ret;
     125  
     126  		/* if the user session keyring is our keyring, then we don't
     127  		 * need to do anything if we're not forcing */
     128  		if (session != usession)
     129  			return PAM_SUCCESS;
     130  	}
     131  
     132  	/* create a session keyring, discarding the old one */
     133  	ret = syscall(__NR_keyctl,
     134  		      KEYCTL_JOIN_SESSION_KEYRING,
     135  		      NULL);
     136  	debug(pamh, "JOIN = %d", ret);
     137  	if (ret < 0)
     138  		return error_ret;
     139  
     140  	my_session_keyring = ret;
     141  
     142  	/* make a link from the session keyring to the user keyring */
     143  	ret = syscall(__NR_keyctl,
     144  		      KEYCTL_LINK,
     145  		      KEY_SPEC_USER_KEYRING,
     146  		      KEY_SPEC_SESSION_KEYRING);
     147  
     148  	return ret < 0 ? error_ret : PAM_SUCCESS;
     149  }
     150  
     151  /*
     152   * revoke the session keyring for this process
     153   */
     154  static int kill_keyrings(pam_handle_t *pamh, int error_ret)
     155  {
     156  	uid_t old_uid;
     157  	gid_t old_gid;
     158  	int ret = PAM_SUCCESS;
     159  
     160  	/* revoke the session keyring we created earlier */
     161  	if (my_session_keyring > 0) {
     162  		debug(pamh, "REVOKE %d", my_session_keyring);
     163  
     164  		old_uid = geteuid();
     165  		old_gid = getegid();
     166  		debug(pamh, "UID:%d [%d]  GID:%d [%d]",
     167  		      revoke_as_uid, old_uid, revoke_as_gid, old_gid);
     168  
     169  		/* switch to the real UID and GID so that we have permission to
     170  		 * revoke the key */
     171  		if (revoke_as_gid != old_gid && pam_setregid(-1, revoke_as_gid) < 0) {
     172  			error(pamh, "Unable to change GID to %d temporarily\n", revoke_as_gid);
     173  			return error_ret;
     174  		}
     175  
     176  		if (revoke_as_uid != old_uid && pam_setresuid(-1, revoke_as_uid, old_uid) < 0) {
     177  			error(pamh, "Unable to change UID to %d temporarily\n", revoke_as_uid);
     178  			if (getegid() != old_gid && pam_setregid(-1, old_gid) < 0)
     179  				error(pamh, "Unable to change GID back to %d\n", old_gid);
     180  			return error_ret;
     181  		}
     182  
     183  		if (syscall(__NR_keyctl, KEYCTL_REVOKE, my_session_keyring) < 0) {
     184  			ret = error_ret;
     185  		}
     186  
     187  		/* return to the original UID and GID (probably root) */
     188  		if (revoke_as_uid != old_uid && pam_setreuid(-1, old_uid) < 0) {
     189  			error(pamh, "Unable to change UID back to %d\n", old_uid);
     190  			ret = error_ret;
     191  		}
     192  
     193  		if (revoke_as_gid != old_gid && pam_setregid(-1, old_gid) < 0) {
     194  			error(pamh, "Unable to change GID back to %d\n", old_gid);
     195  			ret = error_ret;
     196  		}
     197  
     198  		my_session_keyring = 0;
     199  	}
     200  	return ret;
     201  }
     202  
     203  static int do_keyinit(pam_handle_t *pamh, int argc, const char **argv, int error_ret)
     204  {
     205  	struct passwd *pw;
     206  	const char *username;
     207  	int ret, loop, force = 0;
     208  	uid_t old_uid, uid;
     209  	gid_t old_gid, gid;
     210  
     211  	for (loop = 0; loop < argc; loop++) {
     212  		if (strcmp(argv[loop], "force") == 0)
     213  			force = 1;
     214  		else if (strcmp(argv[loop], "debug") == 0)
     215  			xdebug = 1;
     216  		else if (strcmp(argv[loop], "revoke") == 0)
     217  			do_revoke = 1;
     218  	}
     219  
     220  	/* don't do anything if already created a keyring (will be called
     221  	 * multiple times if mentioned more than once in a pam script)
     222  	 */
     223  	if (my_session_keyring > 0)
     224  		return PAM_SUCCESS;
     225  
     226  	/* look up the target UID */
     227  	ret = pam_get_user(pamh, &username, "key user");
     228  	if (ret != PAM_SUCCESS)
     229  		return ret;
     230  
     231  	pw = pam_modutil_getpwnam(pamh, username);
     232  	if (!pw) {
     233  		pam_syslog(pamh, LOG_NOTICE, "Unable to look up user \"%s\"\n",
     234  			   username);
     235  		return PAM_USER_UNKNOWN;
     236  	}
     237  
     238  	revoke_as_uid = uid = pw->pw_uid;
     239  	old_uid = getuid();
     240  	revoke_as_gid = gid = pw->pw_gid;
     241  	old_gid = getgid();
     242  	debug(pamh, "UID:%d [%d]  GID:%d [%d]", uid, old_uid, gid, old_gid);
     243  
     244  	/* switch to the real UID and GID so that the keyring ends up owned by
     245  	 * the right user */
     246  	if (gid != old_gid && pam_setregid(gid, -1) < 0) {
     247  		error(pamh, "Unable to change GID to %d temporarily\n", gid);
     248  		return error_ret;
     249  	}
     250  
     251  	if (uid != old_uid && pam_setreuid(uid, -1) < 0) {
     252  		error(pamh, "Unable to change UID to %d temporarily\n", uid);
     253  		if (pam_setregid(old_gid, -1) < 0)
     254  			error(pamh, "Unable to change GID back to %d\n", old_gid);
     255  		return error_ret;
     256  	}
     257  
     258  	ret = init_keyrings(pamh, force, error_ret);
     259  
     260  	/* return to the original UID and GID (probably root) */
     261  	if (uid != old_uid && pam_setreuid(old_uid, -1) < 0) {
     262  		error(pamh, "Unable to change UID back to %d\n", old_uid);
     263  		ret = error_ret;
     264  	}
     265  
     266  	if (gid != old_gid && pam_setregid(old_gid, -1) < 0) {
     267  		error(pamh, "Unable to change GID back to %d\n", old_gid);
     268  		ret = error_ret;
     269  	}
     270  
     271  	return ret;
     272  }
     273  
     274  /*
     275   * Dummy
     276   */
     277  int pam_sm_authenticate(pam_handle_t *pamh UNUSED, int flags UNUSED,
     278                     int argc UNUSED, const char **argv UNUSED)
     279  {
     280    return PAM_IGNORE;
     281  }
     282  
     283  /*
     284   * since setcred and open_session are called in different orders, a
     285   * session ring is invoked by the first of these functions called.
     286   */
     287  int pam_sm_setcred(pam_handle_t *pamh, int flags,
     288                     int argc, const char **argv)
     289  {
     290  	if (flags & PAM_ESTABLISH_CRED) {
     291  		debug(pamh, "ESTABLISH_CRED");
     292  		return do_keyinit(pamh, argc, argv, PAM_CRED_ERR);
     293  	}
     294  	if (flags & PAM_DELETE_CRED && my_session_keyring > 0 && do_revoke) {
     295  		debug(pamh, "DELETE_CRED");
     296  		return kill_keyrings(pamh, PAM_CRED_ERR);
     297  	}
     298  	return PAM_IGNORE;
     299  }
     300  
     301  int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
     302  			int argc, const char **argv)
     303  {
     304  	session_counter++;
     305  
     306  	debug(pamh, "OPEN %d", session_counter);
     307  
     308  	return do_keyinit(pamh, argc, argv, PAM_SESSION_ERR);
     309  }
     310  
     311  /*
     312   * close a PAM session by revoking the session keyring if requested
     313   */
     314  int pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
     315  			 int argc UNUSED, const char **argv UNUSED)
     316  {
     317  	debug(pamh, "CLOSE %d,%d,%d",
     318  	      session_counter, my_session_keyring, do_revoke);
     319  
     320  	session_counter--;
     321  
     322  	if (session_counter <= 0 && my_session_keyring > 0 && do_revoke)
     323  		kill_keyrings(pamh, PAM_SESSION_ERR);
     324  
     325  	return PAM_SUCCESS;
     326  }