(root)/
Linux-PAM-1.5.3/
libpam/
pam_modutil_priv.c
       1  /*
       2   * $Id$
       3   *
       4   * This file provides two functions:
       5   * pam_modutil_drop_priv:
       6   *   temporarily lower process fs privileges by switching to another uid/gid,
       7   * pam_modutil_regain_priv:
       8   *   regain process fs privileges lowered by pam_modutil_drop_priv().
       9   */
      10  
      11  #include "pam_modutil_private.h"
      12  #include <security/pam_ext.h>
      13  #include <unistd.h>
      14  #include <syslog.h>
      15  #include <pwd.h>
      16  #include <grp.h>
      17  #include <sys/fsuid.h>
      18  
      19  /*
      20   * Two setfsuid() calls in a row are necessary to check
      21   * whether setfsuid() succeeded or not.
      22   */
      23  static int change_uid(uid_t uid, uid_t *save)
      24  {
      25  	uid_t tmp = setfsuid(uid);
      26  	if (save)
      27  		*save = tmp;
      28  	return (uid_t) setfsuid(uid) == uid ? 0 : -1;
      29  }
      30  static int change_gid(gid_t gid, gid_t *save)
      31  {
      32  	gid_t tmp = setfsgid(gid);
      33  	if (save)
      34  		*save = tmp;
      35  	return (gid_t) setfsgid(gid) == gid ? 0 : -1;
      36  }
      37  
      38  static int cleanup(struct pam_modutil_privs *p)
      39  {
      40  	if (p->allocated) {
      41  		p->allocated = 0;
      42  		free(p->grplist);
      43  	}
      44  	p->grplist = NULL;
      45  	p->number_of_groups = 0;
      46  	return -1;
      47  }
      48  
      49  #define PRIV_MAGIC			0x1004000a
      50  #define PRIV_MAGIC_DONOTHING		0xdead000a
      51  
      52  int pam_modutil_drop_priv(pam_handle_t *pamh,
      53  			  struct pam_modutil_privs *p,
      54  			  const struct passwd *pw)
      55  {
      56  	int res;
      57  
      58  	if (p->is_dropped) {
      59  		pam_syslog(pamh, LOG_CRIT,
      60  			   "pam_modutil_drop_priv: called with dropped privileges");
      61  		return -1;
      62  	}
      63  
      64  	/*
      65  	 * If not root, we can do nothing.
      66  	 * If switching to root, we have nothing to do.
      67  	 * That is, in both cases, we do not care.
      68  	 */
      69  	if (geteuid() != 0 || pw->pw_uid == 0) {
      70  		p->is_dropped = PRIV_MAGIC_DONOTHING;
      71  		return 0;
      72  	}
      73  
      74  	if (!p->grplist || p->number_of_groups <= 0) {
      75  		pam_syslog(pamh, LOG_CRIT,
      76  			   "pam_modutil_drop_priv: called without room for supplementary groups");
      77  		return -1;
      78  	}
      79  	res = getgroups(0, NULL);
      80  	if (res < 0) {
      81  		pam_syslog(pamh, LOG_ERR,
      82  			   "pam_modutil_drop_priv: getgroups failed: %m");
      83  		return -1;
      84  	}
      85  
      86  	p->allocated = 0;
      87  	if (res > p->number_of_groups) {
      88  		p->grplist = calloc(res, sizeof(gid_t));
      89  		if (!p->grplist) {
      90  			pam_syslog(pamh, LOG_CRIT, "out of memory");
      91  			return cleanup(p);
      92  		}
      93  		p->allocated = 1;
      94  		p->number_of_groups = res;
      95  	}
      96  
      97  	res = getgroups(p->number_of_groups, p->grplist);
      98  	if (res < 0) {
      99  		pam_syslog(pamh, LOG_ERR,
     100  			   "pam_modutil_drop_priv: getgroups failed: %m");
     101  		return cleanup(p);
     102  	}
     103  
     104  	p->number_of_groups = res;
     105  
     106  	/*
     107  	 * We should care to leave process credentials in consistent state.
     108  	 * That is, e.g. if change_gid() succeeded but change_uid() failed,
     109  	 * we should try to restore old gid.
     110  	 *
     111  	 * We try to add the supplementary groups on a best-effort
     112  	 * basis.  If it fails, it's not fatal: we fall back to using an
     113  	 * empty list.
     114  	 */
     115  	if (initgroups(pw->pw_name, pw->pw_gid)) {
     116  		pam_syslog(pamh, LOG_WARNING,
     117  			   "pam_modutil_drop_priv: initgroups failed: %m");
     118  
     119  		if (setgroups(0, NULL)) {
     120  			pam_syslog(pamh, LOG_ERR,
     121  				   "pam_modutil_drop_priv: setgroups failed: %m");
     122  			return cleanup(p);
     123  		}
     124  	}
     125  	if (change_gid(pw->pw_gid, &p->old_gid)) {
     126  		pam_syslog(pamh, LOG_ERR,
     127  			   "pam_modutil_drop_priv: change_gid failed: %m");
     128  		(void) setgroups(p->number_of_groups, p->grplist);
     129  		return cleanup(p);
     130  	}
     131  	if (change_uid(pw->pw_uid, &p->old_uid)) {
     132  		pam_syslog(pamh, LOG_ERR,
     133  			   "pam_modutil_drop_priv: change_uid failed: %m");
     134  		(void) change_gid(p->old_gid, NULL);
     135  		(void) setgroups(p->number_of_groups, p->grplist);
     136  		return cleanup(p);
     137  	}
     138  
     139  	p->is_dropped = PRIV_MAGIC;
     140  	return 0;
     141  }
     142  
     143  int pam_modutil_regain_priv(pam_handle_t *pamh,
     144  			  struct pam_modutil_privs *p)
     145  {
     146  	switch (p->is_dropped) {
     147  		case PRIV_MAGIC_DONOTHING:
     148  			p->is_dropped = 0;
     149  			return 0;
     150  
     151  		case PRIV_MAGIC:
     152  			break;
     153  
     154  		default:
     155  			pam_syslog(pamh, LOG_CRIT,
     156  				   "pam_modutil_regain_priv: called with invalid state");
     157  			return -1;
     158  		}
     159  
     160  	if (change_uid(p->old_uid, NULL)) {
     161  		pam_syslog(pamh, LOG_ERR,
     162  			   "pam_modutil_regain_priv: change_uid failed: %m");
     163  		return cleanup(p);
     164  	}
     165  	if (change_gid(p->old_gid, NULL)) {
     166  		pam_syslog(pamh, LOG_ERR,
     167  			   "pam_modutil_regain_priv: change_gid failed: %m");
     168  		return cleanup(p);
     169  	}
     170  	if (setgroups(p->number_of_groups, p->grplist)) {
     171  		pam_syslog(pamh, LOG_ERR,
     172  			   "pam_modutil_regain_priv: setgroups failed: %m");
     173  		return cleanup(p);
     174  	}
     175  
     176  	p->is_dropped = 0;
     177  	cleanup(p);
     178  	return 0;
     179  }