xref: /freebsd/usr.sbin/pw/pw_group.c (revision 48aee7f33a5dce843b51b7907af59573f8d3a7c4)
1 /*-
2  * Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
3  * 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 as
10  *    the first lines of this file unmodified.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by David L. Nugent.
17  * 4. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  *	$Id$
33  */
34 
35 #include <unistd.h>
36 #include <ctype.h>
37 #include <termios.h>
38 
39 #include "pw.h"
40 #include "bitmap.h"
41 
42 
43 static int      print_group(struct group * grp, int pretty);
44 static gid_t    gr_gidpolicy(struct userconf * cnf, struct cargs * args);
45 
46 int
47 pw_group(struct userconf * cnf, int mode, struct cargs * args)
48 {
49 	struct carg    *a_name = getarg(args, 'n');
50 	struct carg    *a_gid = getarg(args, 'g');
51 	struct carg    *arg;
52 	struct group   *grp = NULL;
53 	char           *members[_UC_MAXGROUPS];
54 
55 	static struct group fakegroup =
56 	{
57 		"nogroup",
58 		"*",
59 		-1,
60 		NULL
61 	};
62 
63 	/*
64 	 * With M_NEXT, we only need to return the
65 	 * next gid to stdout
66 	 */
67 	if (mode == M_NEXT)
68 	{
69 		gid_t next = gr_gidpolicy(cnf, args);
70 		if (getarg(args, 'q'))
71 			return next;
72 		printf("%ld\n", (long)next);
73 		return EXIT_SUCCESS;
74 	}
75 
76 	if (mode == M_PRINT && getarg(args, 'a')) {
77 		int             pretty = getarg(args, 'P') != NULL;
78 
79 		setgrent();
80 		while ((grp = getgrent()) != NULL)
81 			print_group(grp, pretty);
82 		endgrent();
83 		return EXIT_SUCCESS;
84 	}
85 	if (a_gid == NULL) {
86 		if (a_name == NULL)
87 			cmderr(EX_DATAERR, "group name or id required\n");
88 
89 		if (mode != M_ADD && grp == NULL && isdigit(*a_name->val)) {
90 			(a_gid = a_name)->ch = 'g';
91 			a_name = NULL;
92 		}
93 	}
94 	grp = (a_name != NULL) ? getgrnam(a_name->val) : getgrgid((gid_t) atoi(a_gid->val));
95 
96 	if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) {
97 		if (a_name == NULL && grp == NULL)	/* Try harder */
98 			grp = getgrgid(atoi(a_gid->val));
99 
100 		if (grp == NULL) {
101 			if (mode == M_PRINT && getarg(args, 'F')) {
102 				fakegroup.gr_name = a_name ? a_name->val : "nogroup";
103 				fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1;
104 				return print_group(&fakegroup, getarg(args, 'P') != NULL);
105 			}
106 			cmderr(EX_DATAERR, "unknown group `%s'\n", a_name ? a_name->val : a_gid->val);
107 		}
108 		if (a_name == NULL)	/* Needed later */
109 			a_name = addarg(args, 'n', grp->gr_name);
110 
111 		/*
112 		 * Handle deletions now
113 		 */
114 		if (mode == M_DELETE) {
115 			gid_t           gid = grp->gr_gid;
116 
117 			if (delgrent(grp) == -1)
118 				cmderr(EX_IOERR, "Error updating group file: %s\n", strerror(errno));
119 			pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid);
120 			return EXIT_SUCCESS;
121 		} else if (mode == M_PRINT)
122 			return print_group(grp, getarg(args, 'P') != NULL);
123 
124 		if (a_gid)
125 			grp->gr_gid = (gid_t) atoi(a_gid->val);
126 
127 		if ((arg = getarg(args, 'l')) != NULL)
128 			grp->gr_name = arg->val;
129 	} else {
130 		if (a_name == NULL)	/* Required */
131 			cmderr(EX_DATAERR, "group name required\n");
132 		else if (grp != NULL)	/* Exists */
133 			cmderr(EX_DATAERR, "group name `%s' already exists\n", a_name->val);
134 
135 		memset(members, 0, sizeof members);
136 		grp = &fakegroup;
137 		grp->gr_name = a_name->val;
138 		grp->gr_passwd = "*";
139 		grp->gr_gid = gr_gidpolicy(cnf, args);
140 		grp->gr_mem = members;
141 	}
142 
143 	/*
144 	 * This allows us to set a group password Group passwords is an
145 	 * antique idea, rarely used and insecure (no secure database) Should
146 	 * be discouraged, but it is apparently still supported by some
147 	 * software.
148 	 */
149 
150 	if ((arg = getarg(args, 'h')) != NULL) {
151 		if (strcmp(arg->val, "-") == 0)
152 			grp->gr_passwd = "*";	/* No access */
153 		else {
154 			int             fd = atoi(arg->val);
155 			int             b;
156 			int             istty = isatty(fd);
157 			struct termios  t;
158 			char           *p, line[256];
159 
160 			if (istty) {
161 				if (tcgetattr(fd, &t) == -1)
162 					istty = 0;
163 				else {
164 					struct termios  n = t;
165 
166 					/* Disable echo */
167 					n.c_lflag &= ~(ECHO);
168 					tcsetattr(fd, TCSANOW, &n);
169 					printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name);
170 					fflush(stdout);
171 				}
172 			}
173 			b = read(fd, line, sizeof(line) - 1);
174 			if (istty) {	/* Restore state */
175 				tcsetattr(fd, TCSANOW, &t);
176 				fputc('\n', stdout);
177 				fflush(stdout);
178 			}
179 			if (b < 0) {
180 				perror("-h file descriptor");
181 				return EX_OSERR;
182 			}
183 			line[b] = '\0';
184 			if ((p = strpbrk(line, " \t\r\n")) != NULL)
185 				*p = '\0';
186 			if (!*line)
187 				cmderr(EX_DATAERR, "empty password read on file descriptor %d\n", fd);
188 			grp->gr_passwd = pw_pwcrypt(line);
189 		}
190 	}
191 
192 	if (((arg = getarg(args, 'M')) != NULL || (arg = getarg(args, 'm')) != NULL) && arg->val) {
193 		int	i = 0;
194 		char   *p;
195 		struct passwd	*pwd;
196 
197 		if (arg->ch == 'm') {
198 			while (i < _UC_MAXGROUPS && grp->gr_mem[i] != NULL) {
199 				members[i] = grp->gr_mem[i];
200 				i++;
201 			}
202 		}
203 		for (p = strtok(arg->val, ", \t"); i < _UC_MAXGROUPS && p != NULL; p = strtok(NULL, ", \t")) {
204 			int     j;
205 			if ((pwd = getpwnam(p)) == NULL) {
206 				if (!isdigit(*p) || (pwd = getpwuid((uid_t) atoi(p))) == NULL)
207 					cmderr(EX_NOUSER, "user `%s' does not exist\n", p);
208 			}
209 			/*
210 			 * Check for duplicates
211 			 */
212 			for (j = 0; j < i && strcmp(members[j], pwd->pw_name)!=0; j++)
213 				;
214 			if (j == i)
215 				members[i++] = newstr(pwd->pw_name);
216 		}
217 		while (i < _UC_MAXGROUPS)
218 			members[i++] = NULL;
219 		grp->gr_mem = members;
220 	}
221 
222 	if (getarg(args, 'N') != NULL)
223 		return print_group(grp, getarg(args, 'P') != NULL);
224 
225 	if ((mode == M_ADD && !addgrent(grp)) || (mode == M_UPDATE && !chggrent(a_name->val, grp))) {
226 		perror("group update");
227 		return EX_IOERR;
228 	}
229 	/* grp may have been invalidated */
230 	if ((grp = getgrnam(a_name->val)) == NULL)
231 		cmderr(EX_SOFTWARE, "group disappeared during update\n");
232 
233 	pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid);
234 
235 	return EXIT_SUCCESS;
236 }
237 
238 
239 static          gid_t
240 gr_gidpolicy(struct userconf * cnf, struct cargs * args)
241 {
242 	struct group   *grp;
243 	gid_t           gid = (gid_t) - 1;
244 	struct carg    *a_gid = getarg(args, 'g');
245 
246 	/*
247 	 * Check the given gid, if any
248 	 */
249 	if (a_gid != NULL) {
250 		gid = (gid_t) atol(a_gid->val);
251 
252 		if ((grp = getgrgid(gid)) != NULL && getarg(args, 'o') == NULL)
253 			cmderr(EX_DATAERR, "gid `%ld' has already been allocated\n", (long) grp->gr_gid);
254 	} else {
255 		struct bitmap   bm;
256 
257 		/*
258 		 * We need to allocate the next available gid under one of
259 		 * two policies a) Grab the first unused gid b) Grab the
260 		 * highest possible unused gid
261 		 */
262 		if (cnf->min_gid >= cnf->max_gid) {	/* Sanity claus^H^H^H^Hheck */
263 			cnf->min_gid = 1000;
264 			cnf->max_gid = 32000;
265 		}
266 		bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
267 
268 		/*
269 		 * Now, let's fill the bitmap from the password file
270 		 */
271 		setgrent();
272 		while ((grp = getgrent()) != NULL)
273 			if (grp->gr_gid >= (int) cnf->min_gid && grp->gr_gid <= (int) cnf->max_gid)
274 				bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
275 		endgrent();
276 
277 		/*
278 		 * Then apply the policy, with fallback to reuse if necessary
279 		 */
280 		if (cnf->reuse_gids)
281 			gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
282 		else {
283 			gid = (gid_t) (bm_lastset(&bm) + 1);
284 			if (!bm_isset(&bm, gid))
285 				gid += cnf->min_gid;
286 			else
287 				gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
288 		}
289 
290 		/*
291 		 * Another sanity check
292 		 */
293 		if (gid < cnf->min_gid || gid > cnf->max_gid)
294 			cmderr(EX_SOFTWARE, "unable to allocate a new gid - range fully used\n");
295 		bm_dealloc(&bm);
296 	}
297 	return gid;
298 }
299 
300 
301 static int
302 print_group(struct group * grp, int pretty)
303 {
304 	if (!pretty) {
305 		char            buf[_UC_MAXLINE];
306 
307 		fmtgrent(buf, grp);
308 		fputs(buf, stdout);
309 	} else {
310 		int             i;
311 
312 		printf("Group Name : %-10s   #%lu\n"
313 		       "   Members : ",
314 		       grp->gr_name, (long) grp->gr_gid);
315 		for (i = 0; i < _UC_MAXGROUPS && grp->gr_mem[i]; i++)
316 			printf("%s%s", i ? "," : "", grp->gr_mem[i]);
317 		fputs("\n\n", stdout);
318 	}
319 	return EXIT_SUCCESS;
320 }
321