xref: /dragonfly/usr.sbin/pw/pw_group.c (revision c89a6c1b)
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  * $FreeBSD: src/usr.sbin/pw/pw_group.c,v 1.12.2.1 2000/06/28 19:19:04 ache Exp $
27  * $DragonFly: src/usr.sbin/pw/pw_group.c,v 1.3 2004/09/25 20:38:21 dillon Exp $
28  */
29 
30 #include <ctype.h>
31 #include <err.h>
32 #include <termios.h>
33 #include <unistd.h>
34 
35 #include "pw.h"
36 #include "bitmap.h"
37 
38 
39 static int      print_group(struct group * grp, int pretty);
40 static gid_t    gr_gidpolicy(struct userconf * cnf, struct cargs * args);
41 
42 static
43 int
44 alldigits(const char *str)
45 {
46 	while (*str) {
47 		if (*str < '0' || *str > '9')
48 			return(0);
49 		++str;
50 	}
51 	return(1);
52 }
53 
54 int
55 pw_group(struct userconf * cnf, int mode, struct cargs * args)
56 {
57 	int		rc;
58 	struct carg    *a_name = getarg(args, 'n');
59 	struct carg    *a_gid = getarg(args, 'g');
60 	struct carg    *arg;
61 	struct group   *grp = NULL;
62 	int	        grmembers = 0;
63 	char           **members = NULL;
64 
65 	static struct group fakegroup =
66 	{
67 		"nogroup",
68 		"*",
69 		-1,
70 		NULL
71 	};
72 
73 	if (mode == M_LOCK || mode == M_UNLOCK)
74 		errx(EX_USAGE, "'lock' command is not available for groups");
75 
76 	/*
77 	 * With M_NEXT, we only need to return the
78 	 * next gid to stdout
79 	 */
80 	if (mode == M_NEXT) {
81 		gid_t next = gr_gidpolicy(cnf, args);
82 		if (getarg(args, 'q'))
83 			return next;
84 		printf("%ld\n", (long)next);
85 		return EXIT_SUCCESS;
86 	}
87 
88 	if (mode == M_PRINT && getarg(args, 'a')) {
89 		int             pretty = getarg(args, 'P') != NULL;
90 
91 		SETGRENT();
92 		while ((grp = GETGRENT()) != NULL)
93 			print_group(grp, pretty);
94 		ENDGRENT();
95 		return EXIT_SUCCESS;
96 	}
97 	if (a_gid == NULL) {
98 		if (a_name == NULL)
99 			errx(EX_DATAERR, "group name or id required");
100 
101 		if (mode != M_ADD && grp == NULL && alldigits(a_name->val)) {
102 			(a_gid = a_name)->ch = 'g';
103 			a_name = NULL;
104 		}
105 	}
106 	grp = (a_name != NULL) ? GETGRNAM(a_name->val) : GETGRGID((gid_t) atoi(a_gid->val));
107 
108 	if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) {
109 		if (a_name == NULL && grp == NULL)	/* Try harder */
110 			grp = GETGRGID(atoi(a_gid->val));
111 
112 		if (grp == NULL) {
113 			if (mode == M_PRINT && getarg(args, 'F')) {
114 				char	*fmems[1];
115 				fmems[0] = NULL;
116 				fakegroup.gr_name = a_name ? a_name->val : "nogroup";
117 				fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1;
118 				fakegroup.gr_mem = fmems;
119 				return print_group(&fakegroup, getarg(args, 'P') != NULL);
120 			}
121 			errx(EX_DATAERR, "unknown group `%s'", a_name ? a_name->val : a_gid->val);
122 		}
123 		if (a_name == NULL)	/* Needed later */
124 			a_name = addarg(args, 'n', grp->gr_name);
125 
126 		/*
127 		 * Handle deletions now
128 		 */
129 		if (mode == M_DELETE) {
130 			gid_t           gid = grp->gr_gid;
131 
132 			rc = delgrent(grp);
133 			if (rc == -1)
134 				err(EX_IOERR, "group '%s' not available (NIS?)", grp->gr_name);
135 			else if (rc != 0) {
136 				warn("group update");
137 				return EX_IOERR;
138 			}
139 			pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid);
140 			return EXIT_SUCCESS;
141 		} else if (mode == M_PRINT)
142 			return print_group(grp, getarg(args, 'P') != NULL);
143 
144 		if (a_gid)
145 			grp->gr_gid = (gid_t) atoi(a_gid->val);
146 
147 		if ((arg = getarg(args, 'l')) != NULL)
148 			grp->gr_name = pw_checkname((u_char *)arg->val, 0);
149 	} else {
150 		if (a_name == NULL)	/* Required */
151 			errx(EX_DATAERR, "group name required");
152 		else if (grp != NULL)	/* Exists */
153 			errx(EX_DATAERR, "group name `%s' already exists", a_name->val);
154 
155 		extendarray(&members, &grmembers, 200);
156 		members[0] = NULL;
157 		grp = &fakegroup;
158 		grp->gr_name = pw_checkname((u_char *)a_name->val, 0);
159 		grp->gr_passwd = "*";
160 		grp->gr_gid = gr_gidpolicy(cnf, args);
161 		grp->gr_mem = members;
162 	}
163 
164 	/*
165 	 * This allows us to set a group password Group passwords is an
166 	 * antique idea, rarely used and insecure (no secure database) Should
167 	 * be discouraged, but it is apparently still supported by some
168 	 * software.
169 	 */
170 
171 	if ((arg = getarg(args, 'h')) != NULL) {
172 		if (strcmp(arg->val, "-") == 0)
173 			grp->gr_passwd = "*";	/* No access */
174 		else {
175 			int             fd = atoi(arg->val);
176 			int             b;
177 			int             istty = isatty(fd);
178 			struct termios  t;
179 			char           *p, line[256];
180 
181 			if (istty) {
182 				if (tcgetattr(fd, &t) == -1)
183 					istty = 0;
184 				else {
185 					struct termios  n = t;
186 
187 					/* Disable echo */
188 					n.c_lflag &= ~(ECHO);
189 					tcsetattr(fd, TCSANOW, &n);
190 					printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name);
191 					fflush(stdout);
192 				}
193 			}
194 			b = read(fd, line, sizeof(line) - 1);
195 			if (istty) {	/* Restore state */
196 				tcsetattr(fd, TCSANOW, &t);
197 				fputc('\n', stdout);
198 				fflush(stdout);
199 			}
200 			if (b < 0) {
201 				warn("-h file descriptor");
202 				return EX_OSERR;
203 			}
204 			line[b] = '\0';
205 			if ((p = strpbrk(line, " \t\r\n")) != NULL)
206 				*p = '\0';
207 			if (!*line)
208 				errx(EX_DATAERR, "empty password read on file descriptor %d", fd);
209 			grp->gr_passwd = pw_pwcrypt(line);
210 		}
211 	}
212 
213 	if (((arg = getarg(args, 'M')) != NULL || (arg = getarg(args, 'm')) != NULL) && arg->val) {
214 		int	i = 0;
215 		char   *p;
216 		struct passwd	*pwd;
217 
218 		/* Make sure this is not stay NULL with -M "" */
219 		extendarray(&members, &grmembers, 200);
220 		if (arg->ch == 'm') {
221 			int	k = 0;
222 
223 			while (grp->gr_mem[k] != NULL) {
224 				if (extendarray(&members, &grmembers, i + 2) != -1) {
225 					members[i++] = grp->gr_mem[k];
226 				}
227 				k++;
228 			}
229 		}
230 		for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
231 			int     j;
232 			if ((pwd = GETPWNAM(p)) == NULL) {
233 				if (!isdigit((unsigned char)*p) || (pwd = getpwuid((uid_t) atoi(p))) == NULL)
234 					errx(EX_NOUSER, "user `%s' does not exist", p);
235 			}
236 			/*
237 			 * Check for duplicates
238 			 */
239 			for (j = 0; j < i && strcmp(members[j], pwd->pw_name)!=0; j++)
240 				;
241 			if (j == i && extendarray(&members, &grmembers, i + 2) != -1)
242 				members[i++] = newstr(pwd->pw_name);
243 		}
244 		while (i < grmembers)
245 			members[i++] = NULL;
246 		grp->gr_mem = members;
247 	}
248 
249 	if (getarg(args, 'N') != NULL)
250 		return print_group(grp, getarg(args, 'P') != NULL);
251 
252 	if (mode == M_ADD && (rc = addgrent(grp)) != 0) {
253 		if (rc == -1)
254 			warnx("group '%s' already exists", grp->gr_name);
255 		else
256 			warn("group update");
257 		return EX_IOERR;
258 	} else if (mode == M_UPDATE && (rc = chggrent(a_name->val, grp)) != 0) {
259 		if (rc == -1)
260 			warnx("group '%s' not available (NIS?)", grp->gr_name);
261 		else
262 			warn("group update");
263 		return EX_IOERR;
264 	}
265 	/* grp may have been invalidated */
266 	if ((grp = GETGRNAM(a_name->val)) == NULL)
267 		errx(EX_SOFTWARE, "group disappeared during update");
268 
269 	pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid);
270 
271 	if (members)
272 		free(members);
273 
274 	return EXIT_SUCCESS;
275 }
276 
277 
278 static          gid_t
279 gr_gidpolicy(struct userconf * cnf, struct cargs * args)
280 {
281 	struct group   *grp;
282 	gid_t           gid = (gid_t) - 1;
283 	struct carg    *a_gid = getarg(args, 'g');
284 
285 	/*
286 	 * Check the given gid, if any
287 	 */
288 	if (a_gid != NULL) {
289 		gid = (gid_t) atol(a_gid->val);
290 
291 		if ((grp = GETGRGID(gid)) != NULL && getarg(args, 'o') == NULL)
292 			errx(EX_DATAERR, "gid `%ld' has already been allocated", (long) grp->gr_gid);
293 	} else {
294 		struct bitmap   bm;
295 
296 		/*
297 		 * We need to allocate the next available gid under one of
298 		 * two policies a) Grab the first unused gid b) Grab the
299 		 * highest possible unused gid
300 		 */
301 		if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
302 			cnf->min_gid = 1000;
303 			cnf->max_gid = 32000;
304 		}
305 		bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
306 
307 		/*
308 		 * Now, let's fill the bitmap from the password file
309 		 */
310 		SETGRENT();
311 		while ((grp = GETGRENT()) != NULL)
312 			if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
313                             (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
314 				bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
315 		ENDGRENT();
316 
317 		/*
318 		 * Then apply the policy, with fallback to reuse if necessary
319 		 */
320 		if (cnf->reuse_gids)
321 			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
322 		else {
323 			gid = (gid_t) (bm_lastset(&bm) + 1);
324 			if (!bm_isset(&bm, gid))
325 				gid += cnf->min_gid;
326 			else
327 				gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
328 		}
329 
330 		/*
331 		 * Another sanity check
332 		 */
333 		if (gid < cnf->min_gid || gid > cnf->max_gid)
334 			errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used");
335 		bm_dealloc(&bm);
336 	}
337 	return gid;
338 }
339 
340 
341 static int
342 print_group(struct group * grp, int pretty)
343 {
344 	if (!pretty) {
345 		int		buflen = 0;
346 		char           *buf = NULL;
347 
348 		fmtgrent(&buf, &buflen, grp);
349 		fputs(buf, stdout);
350 		free(buf);
351 	} else {
352 		int             i;
353 
354 		printf("Group Name: %-15s   #%lu\n"
355 		       "   Members: ",
356 		       grp->gr_name, (long) grp->gr_gid);
357 		for (i = 0; grp->gr_mem[i]; i++)
358 			printf("%s%s", i ? "," : "", grp->gr_mem[i]);
359 		fputs("\n\n", stdout);
360 	}
361 	return EXIT_SUCCESS;
362 }
363