xref: /netbsd-src/usr.bin/newgrp/grutil.c (revision 20837f7b63f9716962b4359c0dce252a434d32b1)
1*20837f7bSgutteridge /*	$NetBSD: grutil.c,v 1.5 2022/10/26 21:18:49 gutteridge Exp $	*/
2542b6466Schristos 
3542b6466Schristos /*-
4542b6466Schristos  * Copyright (c) 2007 The NetBSD Foundation, Inc.
5542b6466Schristos  * All rights reserved.
6542b6466Schristos  *
7542b6466Schristos  * This code is derived from software contributed to The NetBSD Foundation
8542b6466Schristos  * by Brian Ginsbach.
9542b6466Schristos  *
10542b6466Schristos  * Redistribution and use in source and binary forms, with or without
11542b6466Schristos  * modification, are permitted provided that the following conditions
12542b6466Schristos  * are met:
13542b6466Schristos  * 1. Redistributions of source code must retain the above copyright
14542b6466Schristos  *    notice, this list of conditions and the following disclaimer.
15542b6466Schristos  * 2. Redistributions in binary form must reproduce the above copyright
16542b6466Schristos  *    notice, this list of conditions and the following disclaimer in the
17542b6466Schristos  *    documentation and/or other materials provided with the distribution.
18542b6466Schristos  *
19542b6466Schristos  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20542b6466Schristos  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21542b6466Schristos  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22542b6466Schristos  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23542b6466Schristos  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24542b6466Schristos  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25542b6466Schristos  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26542b6466Schristos  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27542b6466Schristos  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28542b6466Schristos  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29542b6466Schristos  * POSSIBILITY OF SUCH DAMAGE.
30542b6466Schristos  */
31542b6466Schristos #include <sys/cdefs.h>
32*20837f7bSgutteridge __RCSID("$NetBSD: grutil.c,v 1.5 2022/10/26 21:18:49 gutteridge Exp $");
33542b6466Schristos 
34542b6466Schristos #include <sys/param.h>
35542b6466Schristos #include <err.h>
36542b6466Schristos #include <errno.h>
37542b6466Schristos #include <grp.h>
38542b6466Schristos #include <pwd.h>
39542b6466Schristos #include <stdio.h>
40542b6466Schristos #include <stdlib.h>
41542b6466Schristos #include <string.h>
42542b6466Schristos #include <unistd.h>
43542b6466Schristos #include <util.h>
44542b6466Schristos 
45542b6466Schristos #ifdef LOGIN_CAP
46542b6466Schristos #include <login_cap.h>
47542b6466Schristos #endif
48542b6466Schristos 
49542b6466Schristos #include "grutil.h"
50542b6466Schristos 
51542b6466Schristos typedef enum {
52542b6466Schristos 	ADDGRP_NOERROR		= 0,	/* must be zero */
53542b6466Schristos 	ADDGRP_EMALLOC		= 1,
54542b6466Schristos 	ADDGRP_EGETGROUPS	= 2,
55542b6466Schristos 	ADDGRP_ESETGROUPS	= 3
56542b6466Schristos } addgrp_ret_t;
57542b6466Schristos 
58542b6466Schristos static void
free_groups(void * groups)59542b6466Schristos free_groups(void *groups)
60542b6466Schristos {
61542b6466Schristos 	int oerrno;
62542b6466Schristos 
63542b6466Schristos 	oerrno = errno;
64542b6466Schristos 	free(groups);
65542b6466Schristos 	errno = oerrno;
66542b6466Schristos }
67542b6466Schristos 
68542b6466Schristos static addgrp_ret_t
alloc_groups(int * ngroups,gid_t ** groups,int * ngroupsmax)69542b6466Schristos alloc_groups(int *ngroups, gid_t **groups, int *ngroupsmax)
70542b6466Schristos {
71542b6466Schristos 	*ngroupsmax = (int)sysconf(_SC_NGROUPS_MAX);
72542b6466Schristos 	if (*ngroupsmax < 0)
73542b6466Schristos 		*ngroupsmax = NGROUPS_MAX;
74542b6466Schristos 
75542b6466Schristos 	*groups = malloc(*ngroupsmax * sizeof(**groups));
76542b6466Schristos 	if (*groups == NULL)
77542b6466Schristos 		return ADDGRP_EMALLOC;
78542b6466Schristos 
79542b6466Schristos 	*ngroups = getgroups(*ngroupsmax, *groups);
80542b6466Schristos 	if (*ngroups == -1) {
81542b6466Schristos 		free_groups(*groups);
82542b6466Schristos 		return ADDGRP_ESETGROUPS;
83542b6466Schristos 	}
84542b6466Schristos 	return ADDGRP_NOERROR;
85542b6466Schristos }
86542b6466Schristos 
87542b6466Schristos static addgrp_ret_t
addgid(gid_t * groups,int ngroups,int ngroupsmax,gid_t gid,int makespace)88542b6466Schristos addgid(gid_t *groups, int ngroups, int ngroupsmax, gid_t gid, int makespace)
89542b6466Schristos {
90542b6466Schristos 	int i;
91542b6466Schristos 
92542b6466Schristos 	/* search for gid in supplemental group list */
93542b6466Schristos 	for (i = 0; i < ngroups && groups[i] != gid; i++)
94542b6466Schristos 		continue;
95542b6466Schristos 
96542b6466Schristos 	/* add the gid to the supplemental group list */
97542b6466Schristos 	if (i == ngroups) {
98542b6466Schristos 		if (ngroups < ngroupsmax)
99542b6466Schristos 			groups[ngroups++] = gid;
100542b6466Schristos 		else {	/*
101542b6466Schristos 			 * setgroups(2) will fail with errno = EINVAL
102542b6466Schristos 			 * if ngroups > nmaxgroups.  If makespace is
103542b6466Schristos 			 * set, replace the last group with the new
104542b6466Schristos 			 * one.  Otherwise, fail the way setgroups(2)
105542b6466Schristos 			 * would if we passed the larger groups array.
106542b6466Schristos 			 */
107542b6466Schristos 			if (makespace) {
108542b6466Schristos 				/*
109542b6466Schristos 				 * Find a slot that doesn't contain
110542b6466Schristos 				 * the primary group.
111542b6466Schristos 				 */
112542b6466Schristos 				struct passwd *pwd;
113542b6466Schristos 				gid_t pgid;
114542b6466Schristos 				pwd = getpwuid(getuid());
115542b6466Schristos 				if (pwd == NULL)
116542b6466Schristos 					goto error;
117542b6466Schristos 				pgid = pwd->pw_gid;
118542b6466Schristos 				for (i = ngroupsmax - 1; i >= 0; i--)
119542b6466Schristos 					if (groups[i] != pgid)
120542b6466Schristos 						break;
121542b6466Schristos 				if (i < 0)
122542b6466Schristos 					goto error;
123542b6466Schristos 				groups[i] = gid;
124542b6466Schristos 			}
125542b6466Schristos 			else {
126542b6466Schristos 		error:
127542b6466Schristos 				errno = EINVAL;
128542b6466Schristos 				return ADDGRP_ESETGROUPS;
129542b6466Schristos 			}
130542b6466Schristos 		}
131542b6466Schristos 		if (setgroups(ngroups, groups) < 0)
132542b6466Schristos 			return ADDGRP_ESETGROUPS;
133542b6466Schristos 	}
134542b6466Schristos 	return ADDGRP_NOERROR;
135542b6466Schristos }
136542b6466Schristos 
137542b6466Schristos static addgrp_ret_t
addgrp(gid_t newgid,int makespace)138542b6466Schristos addgrp(gid_t newgid, int makespace)
139542b6466Schristos {
1404cbd234fSchristos 	int ngroups, ngroupsmax;
1414cbd234fSchristos 	addgrp_ret_t rval;
142542b6466Schristos 	gid_t *groups;
143542b6466Schristos 	gid_t oldgid;
144542b6466Schristos 
145542b6466Schristos 	oldgid = getgid();
146542b6466Schristos 	if (oldgid == newgid) /* nothing to do */
147542b6466Schristos 		return ADDGRP_NOERROR;
148542b6466Schristos 
149542b6466Schristos 	rval = alloc_groups(&ngroups, &groups, &ngroupsmax);
150ce020e48Sshm 	if (rval != ADDGRP_NOERROR)
151542b6466Schristos 		return rval;
152542b6466Schristos 
153542b6466Schristos 	/*
154542b6466Schristos 	 * BSD based systems normally have the egid in the supplemental
155542b6466Schristos 	 * group list.
156542b6466Schristos 	 */
157542b6466Schristos #if (defined(BSD) && BSD >= 199306)
158542b6466Schristos 	/*
159542b6466Schristos 	 * According to POSIX/XPG6:
160*20837f7bSgutteridge 	 * On systems where the egid is normally in the supplemental group list
161542b6466Schristos 	 * (or whenever the old egid actually is in the supplemental group
162542b6466Schristos 	 * list):
163542b6466Schristos 	 *	o If the new egid is in the supplemental group list,
164542b6466Schristos 	 *	  just change the egid.
165542b6466Schristos 	 *	o If the new egid is not in the supplemental group list,
166542b6466Schristos 	 *	  add the new egid to the list if there is room.
167542b6466Schristos 	 */
168542b6466Schristos 
169542b6466Schristos 	rval = addgid(groups, ngroups, ngroupsmax, newgid, makespace);
170542b6466Schristos #else
171542b6466Schristos 	/*
172542b6466Schristos 	 * According to POSIX/XPG6:
173542b6466Schristos 	 * On systems where the egid is not normally in the supplemental group
174542b6466Schristos 	 * list (or whenever the old egid is not in the supplemental group
175542b6466Schristos 	 * list):
176542b6466Schristos 	 *	o If the new egid is in the supplemental group list, delete
177542b6466Schristos 	 *	  it from the list.
178542b6466Schristos 	 *	o If the old egid is not in the supplemental group list,
179542b6466Schristos 	 *	  add the old egid to the list if there is room.
180542b6466Schristos 	 */
181542b6466Schristos 	{
182542b6466Schristos 		int i;
183542b6466Schristos 
184542b6466Schristos 		/* search for new egid in supplemental group list */
185542b6466Schristos 		for (i = 0; i < ngroups && groups[i] != newgid; i++)
186542b6466Schristos 			continue;
187542b6466Schristos 
188542b6466Schristos 		/* remove new egid from supplemental group list */
189542b6466Schristos 		if (i != ngroups)
190542b6466Schristos 			for (--ngroups; i < ngroups; i++)
191542b6466Schristos 				groups[i] = groups[i + 1];
192542b6466Schristos 
193542b6466Schristos 		rval = addgid(groups, ngroups, ngroupsmax, oldgid, makespace);
194542b6466Schristos 	}
195542b6466Schristos #endif
196542b6466Schristos 	free_groups(groups);
197542b6466Schristos 	return rval;
198542b6466Schristos }
199542b6466Schristos 
200542b6466Schristos /*
201542b6466Schristos  * If newgrp fails, it returns (gid_t)-1 and the errno variable is
202542b6466Schristos  * set to:
203542b6466Schristos  *	[EINVAL]	Unknown group.
204542b6466Schristos  *	[EPERM]		Bad password.
205542b6466Schristos  */
206542b6466Schristos static gid_t
newgrp(const char * gname,struct passwd * pwd,uid_t ruid,const char * prompt)207542b6466Schristos newgrp(const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
208542b6466Schristos {
209542b6466Schristos 	struct group *grp;
210542b6466Schristos 	char **ap;
211542b6466Schristos 	char *p;
212542b6466Schristos 	gid_t *groups;
213542b6466Schristos 	int ngroups, ngroupsmax;
214542b6466Schristos 
215542b6466Schristos 	if (gname == NULL)
216542b6466Schristos 		return pwd->pw_gid;
217542b6466Schristos 
218542b6466Schristos 	grp = getgrnam(gname);
219542b6466Schristos 
220542b6466Schristos #ifdef GRUTIL_ACCEPT_GROUP_NUMBERS
221542b6466Schristos 	if (grp == NULL) {
222542b6466Schristos 		gid_t gid;
223542b6466Schristos 		if (*gname != '-') {
224542b6466Schristos 		    gid = (gid_t)strtol(gname, &p, 10);
225542b6466Schristos 		    if (*p == '\0')
226542b6466Schristos 			    grp = getgrgid(gid);
227542b6466Schristos 		}
228542b6466Schristos 	}
229542b6466Schristos #endif
230542b6466Schristos 	if (grp == NULL) {
231542b6466Schristos 		errno = EINVAL;
232542b6466Schristos 		return (gid_t)-1;
233542b6466Schristos 	}
234542b6466Schristos 
235542b6466Schristos 	if (ruid == 0 || pwd->pw_gid == grp->gr_gid)
236542b6466Schristos 		return grp->gr_gid;
237542b6466Schristos 
238ce020e48Sshm 	if (alloc_groups(&ngroups, &groups, &ngroupsmax) == ADDGRP_NOERROR) {
239542b6466Schristos 		int i;
240542b6466Schristos 		for (i = 0; i < ngroups; i++)
241542b6466Schristos 			if (groups[i] == grp->gr_gid) {
242542b6466Schristos 				free_groups(groups);
243542b6466Schristos 				return grp->gr_gid;
244542b6466Schristos 			}
245542b6466Schristos 		free_groups(groups);
246542b6466Schristos 	}
247542b6466Schristos 
248542b6466Schristos 	/*
249542b6466Schristos 	 * Check the group membership list in case the groups[] array
250542b6466Schristos 	 * was maxed out or the user has been added to it since login.
251542b6466Schristos 	 */
252542b6466Schristos 	for (ap = grp->gr_mem; *ap != NULL; ap++)
253542b6466Schristos 		if (strcmp(*ap, pwd->pw_name) == 0)
254542b6466Schristos 			return grp->gr_gid;
255542b6466Schristos 
256542b6466Schristos 	if (*grp->gr_passwd != '\0') {
257542b6466Schristos 		p = getpass(prompt);
258542b6466Schristos 		if (strcmp(grp->gr_passwd, crypt(p, grp->gr_passwd)) == 0) {
259542b6466Schristos 			(void)memset(p, '\0', _PASSWORD_LEN);
260542b6466Schristos 			return grp->gr_gid;
261542b6466Schristos 		}
262542b6466Schristos 		(void)memset(p, '\0', _PASSWORD_LEN);
263542b6466Schristos 	}
264542b6466Schristos 
265542b6466Schristos 	errno = EPERM;
266542b6466Schristos 	return (gid_t)-1;
267542b6466Schristos }
268542b6466Schristos 
269542b6466Schristos #ifdef GRUTIL_SETGROUPS_MAKESPACE
270542b6466Schristos # define ADDGRP_MAKESPACE	1
271542b6466Schristos #else
272542b6466Schristos # define ADDGRP_MAKESPACE	0
273542b6466Schristos #endif
274542b6466Schristos 
275542b6466Schristos #ifdef GRUTIL_ALLOW_GROUP_ERRORS
276542b6466Schristos # define maybe_exit(e)
277542b6466Schristos #else
278542b6466Schristos # define maybe_exit(e)	exit(e);
279542b6466Schristos #endif
280542b6466Schristos 
281542b6466Schristos void
addgroup(login_cap_t * lc,const char * gname,struct passwd * pwd,uid_t ruid,const char * prompt)282542b6466Schristos addgroup(
283542b6466Schristos #ifdef LOGIN_CAP
284542b6466Schristos     login_cap_t *lc,
285542b6466Schristos #endif
286542b6466Schristos     const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
287542b6466Schristos {
288542b6466Schristos 	pwd->pw_gid = newgrp(gname, pwd, ruid, prompt);
289542b6466Schristos 	if (pwd->pw_gid == (gid_t)-1) {
290542b6466Schristos 		switch (errno) {
291542b6466Schristos 		case EINVAL:
292542b6466Schristos 			warnx("Unknown group `%s'", gname);
293542b6466Schristos 			maybe_exit(EXIT_FAILURE);
294542b6466Schristos 			break;
295542b6466Schristos 		case EPERM:	/* password failure */
296542b6466Schristos 			warnx("Sorry");
297542b6466Schristos 			maybe_exit(EXIT_FAILURE);
298542b6466Schristos 			break;
299542b6466Schristos 		default: /* XXX - should never happen */
300542b6466Schristos 			err(EXIT_FAILURE, "unknown error");
301542b6466Schristos 			break;
302542b6466Schristos 		}
303542b6466Schristos 		pwd->pw_gid = getgid();
304542b6466Schristos 	}
305542b6466Schristos 
306542b6466Schristos 	switch (addgrp(pwd->pw_gid, ADDGRP_MAKESPACE)) {
307542b6466Schristos 	case ADDGRP_NOERROR:
308542b6466Schristos 		break;
309542b6466Schristos 	case ADDGRP_EMALLOC:
310542b6466Schristos 		err(EXIT_FAILURE, "malloc");
311542b6466Schristos 		break;
312542b6466Schristos 	case ADDGRP_EGETGROUPS:
313542b6466Schristos 		err(EXIT_FAILURE, "getgroups");
314542b6466Schristos 		break;
315542b6466Schristos 	case ADDGRP_ESETGROUPS:
316542b6466Schristos 		switch(errno) {
317542b6466Schristos 		case EINVAL:
318542b6466Schristos 			warnx("setgroups: ngroups > ngroupsmax");
319542b6466Schristos 			maybe_exit(EXIT_FAILURE);
320542b6466Schristos 			break;
321542b6466Schristos 		case EPERM:
322542b6466Schristos 		case EFAULT:
323542b6466Schristos 		default:
324542b6466Schristos 			warn("setgroups");
325542b6466Schristos 			maybe_exit(EXIT_FAILURE);
326542b6466Schristos 			break;
327542b6466Schristos 		}
328542b6466Schristos 		break;
329542b6466Schristos 	}
330542b6466Schristos 
331542b6466Schristos #ifdef LOGIN_CAP
332542b6466Schristos 	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGID) == -1)
333542b6466Schristos 		err(EXIT_FAILURE, "setting user context");
334542b6466Schristos #else
335542b6466Schristos 	if (setgid(pwd->pw_gid) == -1)
336542b6466Schristos 		err(EXIT_FAILURE, "setgid");
337542b6466Schristos #endif
338542b6466Schristos }
339