xref: /freebsd/usr.sbin/pw/pw_group.c (revision 3823d5e198425b4f5e5a80267d195769d1063773)
1 /*-
2  * Copyright (C) 1996
3  *	David L. Nugent.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #ifndef lint
28 static const char rcsid[] =
29   "$FreeBSD$";
30 #endif /* not lint */
31 
32 #include <ctype.h>
33 #include <err.h>
34 #include <termios.h>
35 #include <stdbool.h>
36 #include <unistd.h>
37 #include <grp.h>
38 #include <libutil.h>
39 
40 #include "pw.h"
41 #include "bitmap.h"
42 
43 
44 static struct passwd *lookup_pwent(const char *user);
45 static void	delete_members(char ***members, int *grmembers, int *i,
46     struct carg *arg, struct group *grp);
47 static int      print_group(struct group * grp, int pretty);
48 static gid_t    gr_gidpolicy(struct userconf * cnf, struct cargs * args);
49 
50 int
51 pw_group(struct userconf * cnf, int mode, struct cargs * args)
52 {
53 	int		rc;
54 	struct carg    *a_newname = getarg(args, 'l');
55 	struct carg    *a_name = getarg(args, 'n');
56 	struct carg    *a_gid = getarg(args, 'g');
57 	struct carg    *arg;
58 	struct group   *grp = NULL;
59 	int	        grmembers = 0;
60 	char           **members = NULL;
61 
62 	static struct group fakegroup =
63 	{
64 		"nogroup",
65 		"*",
66 		-1,
67 		NULL
68 	};
69 
70 	if (mode == M_LOCK || mode == M_UNLOCK)
71 		errx(EX_USAGE, "'lock' command is not available for groups");
72 
73 	/*
74 	 * With M_NEXT, we only need to return the
75 	 * next gid to stdout
76 	 */
77 	if (mode == M_NEXT) {
78 		gid_t next = gr_gidpolicy(cnf, args);
79 		if (getarg(args, 'q'))
80 			return next;
81 		printf("%ld\n", (long)next);
82 		return EXIT_SUCCESS;
83 	}
84 
85 	if (mode == M_PRINT && getarg(args, 'a')) {
86 		int             pretty = getarg(args, 'P') != NULL;
87 
88 		SETGRENT();
89 		while ((grp = GETGRENT()) != NULL)
90 			print_group(grp, pretty);
91 		ENDGRENT();
92 		return EXIT_SUCCESS;
93 	}
94 	if (a_gid == NULL) {
95 		if (a_name == NULL)
96 			errx(EX_DATAERR, "group name or id required");
97 
98 		if (mode != M_ADD && grp == NULL && isdigit((unsigned char)*a_name->val)) {
99 			(a_gid = a_name)->ch = 'g';
100 			a_name = NULL;
101 		}
102 	}
103 	grp = (a_name != NULL) ? GETGRNAM(a_name->val) : GETGRGID((gid_t) atoi(a_gid->val));
104 
105 	if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) {
106 		if (a_name == NULL && grp == NULL)	/* Try harder */
107 			grp = GETGRGID(atoi(a_gid->val));
108 
109 		if (grp == NULL) {
110 			if (mode == M_PRINT && getarg(args, 'F')) {
111 				char	*fmems[1];
112 				fmems[0] = NULL;
113 				fakegroup.gr_name = a_name ? a_name->val : "nogroup";
114 				fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1;
115 				fakegroup.gr_mem = fmems;
116 				return print_group(&fakegroup, getarg(args, 'P') != NULL);
117 			}
118 			errx(EX_DATAERR, "unknown group `%s'", a_name ? a_name->val : a_gid->val);
119 		}
120 		if (a_name == NULL)	/* Needed later */
121 			a_name = addarg(args, 'n', grp->gr_name);
122 
123 		/*
124 		 * Handle deletions now
125 		 */
126 		if (mode == M_DELETE) {
127 			gid_t           gid = grp->gr_gid;
128 
129 			rc = delgrent(grp);
130 			if (rc == -1)
131 				err(EX_IOERR, "group '%s' not available (NIS?)", grp->gr_name);
132 			else if (rc != 0) {
133 				warn("group update");
134 				return EX_IOERR;
135 			}
136 			pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid);
137 			return EXIT_SUCCESS;
138 		} else if (mode == M_PRINT)
139 			return print_group(grp, getarg(args, 'P') != NULL);
140 
141 		if (a_gid)
142 			grp->gr_gid = (gid_t) atoi(a_gid->val);
143 
144 		if (a_newname != NULL)
145 			grp->gr_name = pw_checkname((u_char *)a_newname->val, 0);
146 	} else {
147 		if (a_name == NULL)	/* Required */
148 			errx(EX_DATAERR, "group name required");
149 		else if (grp != NULL)	/* Exists */
150 			errx(EX_DATAERR, "group name `%s' already exists", a_name->val);
151 
152 		extendarray(&members, &grmembers, 200);
153 		members[0] = NULL;
154 		grp = &fakegroup;
155 		grp->gr_name = pw_checkname((u_char *)a_name->val, 0);
156 		grp->gr_passwd = "*";
157 		grp->gr_gid = gr_gidpolicy(cnf, args);
158 		grp->gr_mem = members;
159 	}
160 
161 	/*
162 	 * This allows us to set a group password Group passwords is an
163 	 * antique idea, rarely used and insecure (no secure database) Should
164 	 * be discouraged, but it is apparently still supported by some
165 	 * software.
166 	 */
167 
168 	if ((arg = getarg(args, 'h')) != NULL ||
169 	    (arg = getarg(args, 'H')) != NULL) {
170 		if (strcmp(arg->val, "-") == 0)
171 			grp->gr_passwd = "*";	/* No access */
172 		else {
173 			int             fd = atoi(arg->val);
174 			int		precrypt = (arg->ch == 'H');
175 			int             b;
176 			int             istty = isatty(fd);
177 			struct termios  t;
178 			char           *p, line[256];
179 
180 			if (istty) {
181 				if (tcgetattr(fd, &t) == -1)
182 					istty = 0;
183 				else {
184 					struct termios  n = t;
185 
186 					/* Disable echo */
187 					n.c_lflag &= ~(ECHO);
188 					tcsetattr(fd, TCSANOW, &n);
189 					printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name);
190 					fflush(stdout);
191 				}
192 			}
193 			b = read(fd, line, sizeof(line) - 1);
194 			if (istty) {	/* Restore state */
195 				tcsetattr(fd, TCSANOW, &t);
196 				fputc('\n', stdout);
197 				fflush(stdout);
198 			}
199 			if (b < 0) {
200 				warn("-h file descriptor");
201 				return EX_OSERR;
202 			}
203 			line[b] = '\0';
204 			if ((p = strpbrk(line, " \t\r\n")) != NULL)
205 				*p = '\0';
206 			if (!*line)
207 				errx(EX_DATAERR, "empty password read on file descriptor %d", fd);
208 			if (precrypt) {
209 				if (strchr(line, ':') != NULL)
210 					return EX_DATAERR;
211 				grp->gr_passwd = line;
212 			} else
213 				grp->gr_passwd = pw_pwcrypt(line);
214 		}
215 	}
216 
217 	if (((arg = getarg(args, 'M')) != NULL ||
218 	    (arg = getarg(args, 'd')) != NULL ||
219 	    (arg = getarg(args, 'm')) != NULL) && arg->val) {
220 		int	i = 0;
221 		char   *p;
222 		struct passwd	*pwd;
223 
224 		/* Make sure this is not stay NULL with -M "" */
225 		extendarray(&members, &grmembers, 200);
226 		if (arg->ch == 'd')
227 			delete_members(&members, &grmembers, &i, arg, grp);
228 		else if (arg->ch == 'm') {
229 			int	k = 0;
230 
231 			if (grp->gr_mem != NULL) {
232 				while (grp->gr_mem[k] != NULL) {
233 					if (extendarray(&members, &grmembers, i + 2) != -1)
234 						members[i++] = grp->gr_mem[k];
235 					k++;
236 				}
237 			}
238 		}
239 
240 		if (arg->ch != 'd')
241 			for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
242 				int	j;
243 
244 				/*
245 				 * Check for duplicates
246 				 */
247 				pwd = lookup_pwent(p);
248 				for (j = 0; j < i && strcmp(members[j], pwd->pw_name) != 0; j++)
249 					;
250 				if (j == i && extendarray(&members, &grmembers, i + 2) != -1)
251 					members[i++] = newstr(pwd->pw_name);
252 			}
253 		while (i < grmembers)
254 			members[i++] = NULL;
255 		grp->gr_mem = members;
256 	}
257 
258 	if (getarg(args, 'N') != NULL)
259 		return print_group(grp, getarg(args, 'P') != NULL);
260 
261 	if (mode == M_ADD && (rc = addgrent(grp)) != 0) {
262 		if (rc == -1)
263 			warnx("group '%s' already exists", grp->gr_name);
264 		else
265 			warn("group update");
266 		return EX_IOERR;
267 	} else if (mode == M_UPDATE && (rc = chggrent(a_name->val, grp)) != 0) {
268 		if (rc == -1)
269 			warnx("group '%s' not available (NIS?)", grp->gr_name);
270 		else
271 			warn("group update");
272 		return EX_IOERR;
273 	}
274 
275 	arg = a_newname != NULL ? a_newname : a_name;
276 	/* grp may have been invalidated */
277 	if ((grp = GETGRNAM(arg->val)) == NULL)
278 		errx(EX_SOFTWARE, "group disappeared during update");
279 
280 	pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid);
281 
282 	free(members);
283 
284 	return EXIT_SUCCESS;
285 }
286 
287 
288 /*
289  * Lookup a passwd entry using a name or UID.
290  */
291 static struct passwd *
292 lookup_pwent(const char *user)
293 {
294 	struct passwd *pwd;
295 
296 	if ((pwd = GETPWNAM(user)) == NULL &&
297 	    (!isdigit((unsigned char)*user) ||
298 	    (pwd = getpwuid((uid_t) atoi(user))) == NULL))
299 		errx(EX_NOUSER, "user `%s' does not exist", user);
300 
301 	return (pwd);
302 }
303 
304 
305 /*
306  * Delete requested members from a group.
307  */
308 static void
309 delete_members(char ***members, int *grmembers, int *i, struct carg *arg,
310     struct group *grp)
311 {
312 	bool matchFound;
313 	char *user;
314 	char *valueCopy;
315 	char *valuePtr;
316 	int k;
317 	struct passwd *pwd;
318 
319 	if (grp->gr_mem == NULL)
320 		return;
321 
322 	k = 0;
323 	while (grp->gr_mem[k] != NULL) {
324 		matchFound = false;
325 		if ((valueCopy = strdup(arg->val)) == NULL)
326 			errx(EX_UNAVAILABLE, "out of memory");
327 		valuePtr = valueCopy;
328 		while ((user = strsep(&valuePtr, ", \t")) != NULL) {
329 			pwd = lookup_pwent(user);
330 			if (strcmp(grp->gr_mem[k], pwd->pw_name) == 0) {
331 				matchFound = true;
332 				break;
333 			}
334 		}
335 		free(valueCopy);
336 
337 		if (!matchFound &&
338 		    extendarray(members, grmembers, *i + 2) != -1)
339 			(*members)[(*i)++] = grp->gr_mem[k];
340 
341 		k++;
342 	}
343 
344 	return;
345 }
346 
347 
348 static          gid_t
349 gr_gidpolicy(struct userconf * cnf, struct cargs * args)
350 {
351 	struct group   *grp;
352 	gid_t           gid = (gid_t) - 1;
353 	struct carg    *a_gid = getarg(args, 'g');
354 
355 	/*
356 	 * Check the given gid, if any
357 	 */
358 	if (a_gid != NULL) {
359 		gid = (gid_t) atol(a_gid->val);
360 
361 		if ((grp = GETGRGID(gid)) != NULL && getarg(args, 'o') == NULL)
362 			errx(EX_DATAERR, "gid `%ld' has already been allocated", (long) grp->gr_gid);
363 	} else {
364 		struct bitmap   bm;
365 
366 		/*
367 		 * We need to allocate the next available gid under one of
368 		 * two policies a) Grab the first unused gid b) Grab the
369 		 * highest possible unused gid
370 		 */
371 		if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
372 			cnf->min_gid = 1000;
373 			cnf->max_gid = 32000;
374 		}
375 		bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
376 
377 		/*
378 		 * Now, let's fill the bitmap from the password file
379 		 */
380 		SETGRENT();
381 		while ((grp = GETGRENT()) != NULL)
382 			if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
383                             (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
384 				bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
385 		ENDGRENT();
386 
387 		/*
388 		 * Then apply the policy, with fallback to reuse if necessary
389 		 */
390 		if (cnf->reuse_gids)
391 			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
392 		else {
393 			gid = (gid_t) (bm_lastset(&bm) + 1);
394 			if (!bm_isset(&bm, gid))
395 				gid += cnf->min_gid;
396 			else
397 				gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
398 		}
399 
400 		/*
401 		 * Another sanity check
402 		 */
403 		if (gid < cnf->min_gid || gid > cnf->max_gid)
404 			errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used");
405 		bm_dealloc(&bm);
406 	}
407 	return gid;
408 }
409 
410 
411 static int
412 print_group(struct group * grp, int pretty)
413 {
414 	if (!pretty) {
415 		char           *buf = NULL;
416 
417 		buf = gr_make(grp);
418 		printf("%s\n", buf);
419 		free(buf);
420 	} else {
421 		int             i;
422 
423 		printf("Group Name: %-15s   #%lu\n"
424 		       "   Members: ",
425 		       grp->gr_name, (long) grp->gr_gid);
426 		if (grp->gr_mem != NULL) {
427 			for (i = 0; grp->gr_mem[i]; i++)
428 				printf("%s%s", i ? "," : "", grp->gr_mem[i]);
429 		}
430 		fputs("\n\n", stdout);
431 	}
432 	return EXIT_SUCCESS;
433 }
434