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