Home | History | Annotate | Line # | Download | only in newgrp
      1 /*	$NetBSD: grutil.c,v 1.5 2022/10/26 21:18:49 gutteridge Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2007 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Brian Ginsbach.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 #include <sys/cdefs.h>
     32 __RCSID("$NetBSD: grutil.c,v 1.5 2022/10/26 21:18:49 gutteridge Exp $");
     33 
     34 #include <sys/param.h>
     35 #include <err.h>
     36 #include <errno.h>
     37 #include <grp.h>
     38 #include <pwd.h>
     39 #include <stdio.h>
     40 #include <stdlib.h>
     41 #include <string.h>
     42 #include <unistd.h>
     43 #include <util.h>
     44 
     45 #ifdef LOGIN_CAP
     46 #include <login_cap.h>
     47 #endif
     48 
     49 #include "grutil.h"
     50 
     51 typedef enum {
     52 	ADDGRP_NOERROR		= 0,	/* must be zero */
     53 	ADDGRP_EMALLOC		= 1,
     54 	ADDGRP_EGETGROUPS	= 2,
     55 	ADDGRP_ESETGROUPS	= 3
     56 } addgrp_ret_t;
     57 
     58 static void
     59 free_groups(void *groups)
     60 {
     61 	int oerrno;
     62 
     63 	oerrno = errno;
     64 	free(groups);
     65 	errno = oerrno;
     66 }
     67 
     68 static addgrp_ret_t
     69 alloc_groups(int *ngroups, gid_t **groups, int *ngroupsmax)
     70 {
     71 	*ngroupsmax = (int)sysconf(_SC_NGROUPS_MAX);
     72 	if (*ngroupsmax < 0)
     73 		*ngroupsmax = NGROUPS_MAX;
     74 
     75 	*groups = malloc(*ngroupsmax * sizeof(**groups));
     76 	if (*groups == NULL)
     77 		return ADDGRP_EMALLOC;
     78 
     79 	*ngroups = getgroups(*ngroupsmax, *groups);
     80 	if (*ngroups == -1) {
     81 		free_groups(*groups);
     82 		return ADDGRP_ESETGROUPS;
     83 	}
     84 	return ADDGRP_NOERROR;
     85 }
     86 
     87 static addgrp_ret_t
     88 addgid(gid_t *groups, int ngroups, int ngroupsmax, gid_t gid, int makespace)
     89 {
     90 	int i;
     91 
     92 	/* search for gid in supplemental group list */
     93 	for (i = 0; i < ngroups && groups[i] != gid; i++)
     94 		continue;
     95 
     96 	/* add the gid to the supplemental group list */
     97 	if (i == ngroups) {
     98 		if (ngroups < ngroupsmax)
     99 			groups[ngroups++] = gid;
    100 		else {	/*
    101 			 * setgroups(2) will fail with errno = EINVAL
    102 			 * if ngroups > nmaxgroups.  If makespace is
    103 			 * set, replace the last group with the new
    104 			 * one.  Otherwise, fail the way setgroups(2)
    105 			 * would if we passed the larger groups array.
    106 			 */
    107 			if (makespace) {
    108 				/*
    109 				 * Find a slot that doesn't contain
    110 				 * the primary group.
    111 				 */
    112 				struct passwd *pwd;
    113 				gid_t pgid;
    114 				pwd = getpwuid(getuid());
    115 				if (pwd == NULL)
    116 					goto error;
    117 				pgid = pwd->pw_gid;
    118 				for (i = ngroupsmax - 1; i >= 0; i--)
    119 					if (groups[i] != pgid)
    120 						break;
    121 				if (i < 0)
    122 					goto error;
    123 				groups[i] = gid;
    124 			}
    125 			else {
    126 		error:
    127 				errno = EINVAL;
    128 				return ADDGRP_ESETGROUPS;
    129 			}
    130 		}
    131 		if (setgroups(ngroups, groups) < 0)
    132 			return ADDGRP_ESETGROUPS;
    133 	}
    134 	return ADDGRP_NOERROR;
    135 }
    136 
    137 static addgrp_ret_t
    138 addgrp(gid_t newgid, int makespace)
    139 {
    140 	int ngroups, ngroupsmax;
    141 	addgrp_ret_t rval;
    142 	gid_t *groups;
    143 	gid_t oldgid;
    144 
    145 	oldgid = getgid();
    146 	if (oldgid == newgid) /* nothing to do */
    147 		return ADDGRP_NOERROR;
    148 
    149 	rval = alloc_groups(&ngroups, &groups, &ngroupsmax);
    150 	if (rval != ADDGRP_NOERROR)
    151 		return rval;
    152 
    153 	/*
    154 	 * BSD based systems normally have the egid in the supplemental
    155 	 * group list.
    156 	 */
    157 #if (defined(BSD) && BSD >= 199306)
    158 	/*
    159 	 * According to POSIX/XPG6:
    160 	 * On systems where the egid is normally in the supplemental group list
    161 	 * (or whenever the old egid actually is in the supplemental group
    162 	 * list):
    163 	 *	o If the new egid is in the supplemental group list,
    164 	 *	  just change the egid.
    165 	 *	o If the new egid is not in the supplemental group list,
    166 	 *	  add the new egid to the list if there is room.
    167 	 */
    168 
    169 	rval = addgid(groups, ngroups, ngroupsmax, newgid, makespace);
    170 #else
    171 	/*
    172 	 * According to POSIX/XPG6:
    173 	 * On systems where the egid is not normally in the supplemental group
    174 	 * list (or whenever the old egid is not in the supplemental group
    175 	 * list):
    176 	 *	o If the new egid is in the supplemental group list, delete
    177 	 *	  it from the list.
    178 	 *	o If the old egid is not in the supplemental group list,
    179 	 *	  add the old egid to the list if there is room.
    180 	 */
    181 	{
    182 		int i;
    183 
    184 		/* search for new egid in supplemental group list */
    185 		for (i = 0; i < ngroups && groups[i] != newgid; i++)
    186 			continue;
    187 
    188 		/* remove new egid from supplemental group list */
    189 		if (i != ngroups)
    190 			for (--ngroups; i < ngroups; i++)
    191 				groups[i] = groups[i + 1];
    192 
    193 		rval = addgid(groups, ngroups, ngroupsmax, oldgid, makespace);
    194 	}
    195 #endif
    196 	free_groups(groups);
    197 	return rval;
    198 }
    199 
    200 /*
    201  * If newgrp fails, it returns (gid_t)-1 and the errno variable is
    202  * set to:
    203  *	[EINVAL]	Unknown group.
    204  *	[EPERM]		Bad password.
    205  */
    206 static gid_t
    207 newgrp(const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
    208 {
    209 	struct group *grp;
    210 	char **ap;
    211 	char *p;
    212 	gid_t *groups;
    213 	int ngroups, ngroupsmax;
    214 
    215 	if (gname == NULL)
    216 		return pwd->pw_gid;
    217 
    218 	grp = getgrnam(gname);
    219 
    220 #ifdef GRUTIL_ACCEPT_GROUP_NUMBERS
    221 	if (grp == NULL) {
    222 		gid_t gid;
    223 		if (*gname != '-') {
    224 		    gid = (gid_t)strtol(gname, &p, 10);
    225 		    if (*p == '\0')
    226 			    grp = getgrgid(gid);
    227 		}
    228 	}
    229 #endif
    230 	if (grp == NULL) {
    231 		errno = EINVAL;
    232 		return (gid_t)-1;
    233 	}
    234 
    235 	if (ruid == 0 || pwd->pw_gid == grp->gr_gid)
    236 		return grp->gr_gid;
    237 
    238 	if (alloc_groups(&ngroups, &groups, &ngroupsmax) == ADDGRP_NOERROR) {
    239 		int i;
    240 		for (i = 0; i < ngroups; i++)
    241 			if (groups[i] == grp->gr_gid) {
    242 				free_groups(groups);
    243 				return grp->gr_gid;
    244 			}
    245 		free_groups(groups);
    246 	}
    247 
    248 	/*
    249 	 * Check the group membership list in case the groups[] array
    250 	 * was maxed out or the user has been added to it since login.
    251 	 */
    252 	for (ap = grp->gr_mem; *ap != NULL; ap++)
    253 		if (strcmp(*ap, pwd->pw_name) == 0)
    254 			return grp->gr_gid;
    255 
    256 	if (*grp->gr_passwd != '\0') {
    257 		p = getpass(prompt);
    258 		if (strcmp(grp->gr_passwd, crypt(p, grp->gr_passwd)) == 0) {
    259 			(void)memset(p, '\0', _PASSWORD_LEN);
    260 			return grp->gr_gid;
    261 		}
    262 		(void)memset(p, '\0', _PASSWORD_LEN);
    263 	}
    264 
    265 	errno = EPERM;
    266 	return (gid_t)-1;
    267 }
    268 
    269 #ifdef GRUTIL_SETGROUPS_MAKESPACE
    270 # define ADDGRP_MAKESPACE	1
    271 #else
    272 # define ADDGRP_MAKESPACE	0
    273 #endif
    274 
    275 #ifdef GRUTIL_ALLOW_GROUP_ERRORS
    276 # define maybe_exit(e)
    277 #else
    278 # define maybe_exit(e)	exit(e);
    279 #endif
    280 
    281 void
    282 addgroup(
    283 #ifdef LOGIN_CAP
    284     login_cap_t *lc,
    285 #endif
    286     const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
    287 {
    288 	pwd->pw_gid = newgrp(gname, pwd, ruid, prompt);
    289 	if (pwd->pw_gid == (gid_t)-1) {
    290 		switch (errno) {
    291 		case EINVAL:
    292 			warnx("Unknown group `%s'", gname);
    293 			maybe_exit(EXIT_FAILURE);
    294 			break;
    295 		case EPERM:	/* password failure */
    296 			warnx("Sorry");
    297 			maybe_exit(EXIT_FAILURE);
    298 			break;
    299 		default: /* XXX - should never happen */
    300 			err(EXIT_FAILURE, "unknown error");
    301 			break;
    302 		}
    303 		pwd->pw_gid = getgid();
    304 	}
    305 
    306 	switch (addgrp(pwd->pw_gid, ADDGRP_MAKESPACE)) {
    307 	case ADDGRP_NOERROR:
    308 		break;
    309 	case ADDGRP_EMALLOC:
    310 		err(EXIT_FAILURE, "malloc");
    311 		break;
    312 	case ADDGRP_EGETGROUPS:
    313 		err(EXIT_FAILURE, "getgroups");
    314 		break;
    315 	case ADDGRP_ESETGROUPS:
    316 		switch(errno) {
    317 		case EINVAL:
    318 			warnx("setgroups: ngroups > ngroupsmax");
    319 			maybe_exit(EXIT_FAILURE);
    320 			break;
    321 		case EPERM:
    322 		case EFAULT:
    323 		default:
    324 			warn("setgroups");
    325 			maybe_exit(EXIT_FAILURE);
    326 			break;
    327 		}
    328 		break;
    329 	}
    330 
    331 #ifdef LOGIN_CAP
    332 	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGID) == -1)
    333 		err(EXIT_FAILURE, "setting user context");
    334 #else
    335 	if (setgid(pwd->pw_gid) == -1)
    336 		err(EXIT_FAILURE, "setgid");
    337 #endif
    338 }
    339