xref: /netbsd-src/usr.bin/newgrp/grutil.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: grutil.c,v 1.2 2008/04/28 20:24:14 martin 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.2 2008/04/28 20:24:14 martin 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, rval;
141 	gid_t *groups;
142 	gid_t oldgid;
143 
144 	oldgid = getgid();
145 	if (oldgid == newgid) /* nothing to do */
146 		return ADDGRP_NOERROR;
147 
148 	rval = alloc_groups(&ngroups, &groups, &ngroupsmax);
149 	if (rval != 0)
150 		return rval;
151 
152 	/*
153 	 * BSD based systems normally have the egid in the supplemental
154 	 * group list.
155 	 */
156 #if (defined(BSD) && BSD >= 199306)
157 	/*
158 	 * According to POSIX/XPG6:
159 	 * On system where the egid is normally in the supplemental group list
160 	 * (or whenever the old egid actually is in the supplemental group
161 	 * list):
162 	 *	o If the new egid is in the supplemental group list,
163 	 *	  just change the egid.
164 	 *	o If the new egid is not in the supplemental group list,
165 	 *	  add the new egid to the list if there is room.
166 	 */
167 
168 	rval = addgid(groups, ngroups, ngroupsmax, newgid, makespace);
169 #else
170 	/*
171 	 * According to POSIX/XPG6:
172 	 * On systems where the egid is not normally in the supplemental group
173 	 * list (or whenever the old egid is not in the supplemental group
174 	 * list):
175 	 *	o If the new egid is in the supplemental group list, delete
176 	 *	  it from the list.
177 	 *	o If the old egid is not in the supplemental group list,
178 	 *	  add the old egid to the list if there is room.
179 	 */
180 	{
181 		int i;
182 
183 		/* search for new egid in supplemental group list */
184 		for (i = 0; i < ngroups && groups[i] != newgid; i++)
185 			continue;
186 
187 		/* remove new egid from supplemental group list */
188 		if (i != ngroups)
189 			for (--ngroups; i < ngroups; i++)
190 				groups[i] = groups[i + 1];
191 
192 		rval = addgid(groups, ngroups, ngroupsmax, oldgid, makespace);
193 	}
194 #endif
195 	free_groups(groups);
196 	return rval;
197 }
198 
199 /*
200  * If newgrp fails, it returns (gid_t)-1 and the errno variable is
201  * set to:
202  *	[EINVAL]	Unknown group.
203  *	[EPERM]		Bad password.
204  */
205 static gid_t
206 newgrp(const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
207 {
208 	struct group *grp;
209 	char **ap;
210 	char *p;
211 	gid_t *groups;
212 	int ngroups, ngroupsmax;
213 
214 	if (gname == NULL)
215 		return pwd->pw_gid;
216 
217 	grp = getgrnam(gname);
218 
219 #ifdef GRUTIL_ACCEPT_GROUP_NUMBERS
220 	if (grp == NULL) {
221 		gid_t gid;
222 		if (*gname != '-') {
223 		    gid = (gid_t)strtol(gname, &p, 10);
224 		    if (*p == '\0')
225 			    grp = getgrgid(gid);
226 		}
227 	}
228 #endif
229 	if (grp == NULL) {
230 		errno = EINVAL;
231 		return (gid_t)-1;
232 	}
233 
234 	if (ruid == 0 || pwd->pw_gid == grp->gr_gid)
235 		return grp->gr_gid;
236 
237 	if (alloc_groups(&ngroups, &groups, &ngroupsmax) == 0) {
238 		int i;
239 		for (i = 0; i < ngroups; i++)
240 			if (groups[i] == grp->gr_gid) {
241 				free_groups(groups);
242 				return grp->gr_gid;
243 			}
244 		free_groups(groups);
245 	}
246 
247 	/*
248 	 * Check the group membership list in case the groups[] array
249 	 * was maxed out or the user has been added to it since login.
250 	 */
251 	for (ap = grp->gr_mem; *ap != NULL; ap++)
252 		if (strcmp(*ap, pwd->pw_name) == 0)
253 			return grp->gr_gid;
254 
255 	if (*grp->gr_passwd != '\0') {
256 		p = getpass(prompt);
257 		if (strcmp(grp->gr_passwd, crypt(p, grp->gr_passwd)) == 0) {
258 			(void)memset(p, '\0', _PASSWORD_LEN);
259 			return grp->gr_gid;
260 		}
261 		(void)memset(p, '\0', _PASSWORD_LEN);
262 	}
263 
264 	errno = EPERM;
265 	return (gid_t)-1;
266 }
267 
268 #ifdef GRUTIL_SETGROUPS_MAKESPACE
269 # define ADDGRP_MAKESPACE	1
270 #else
271 # define ADDGRP_MAKESPACE	0
272 #endif
273 
274 #ifdef GRUTIL_ALLOW_GROUP_ERRORS
275 # define maybe_exit(e)
276 #else
277 # define maybe_exit(e)	exit(e);
278 #endif
279 
280 void
281 addgroup(
282 #ifdef LOGIN_CAP
283     login_cap_t *lc,
284 #endif
285     const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
286 {
287 	pwd->pw_gid = newgrp(gname, pwd, ruid, prompt);
288 	if (pwd->pw_gid == (gid_t)-1) {
289 		switch (errno) {
290 		case EINVAL:
291 			warnx("Unknown group `%s'", gname);
292 			maybe_exit(EXIT_FAILURE);
293 			break;
294 		case EPERM:	/* password failure */
295 			warnx("Sorry");
296 			maybe_exit(EXIT_FAILURE);
297 			break;
298 		default: /* XXX - should never happen */
299 			err(EXIT_FAILURE, "unknown error");
300 			break;
301 		}
302 		pwd->pw_gid = getgid();
303 	}
304 
305 	switch (addgrp(pwd->pw_gid, ADDGRP_MAKESPACE)) {
306 	case ADDGRP_NOERROR:
307 		break;
308 	case ADDGRP_EMALLOC:
309 		err(EXIT_FAILURE, "malloc");
310 		break;
311 	case ADDGRP_EGETGROUPS:
312 		err(EXIT_FAILURE, "getgroups");
313 		break;
314 	case ADDGRP_ESETGROUPS:
315 		switch(errno) {
316 		case EINVAL:
317 			warnx("setgroups: ngroups > ngroupsmax");
318 			maybe_exit(EXIT_FAILURE);
319 			break;
320 		case EPERM:
321 		case EFAULT:
322 		default:
323 			warn("setgroups");
324 			maybe_exit(EXIT_FAILURE);
325 			break;
326 		}
327 		break;
328 	}
329 
330 #ifdef LOGIN_CAP
331 	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGID) == -1)
332 		err(EXIT_FAILURE, "setting user context");
333 #else
334 	if (setgid(pwd->pw_gid) == -1)
335 		err(EXIT_FAILURE, "setgid");
336 #endif
337 }
338