xref: /dragonfly/lib/libutil/gr_util.c (revision 8accc937)
1 /*-
2  * Copyright (c) 2008 Sean C. Farley <scf@FreeBSD.org>
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,
10  *    without modification, immediately at the beginning of the file.
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  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <sys/param.h>
28 #include <sys/errno.h>
29 #include <sys/stat.h>
30 
31 #include <ctype.h>
32 #include <err.h>
33 #include <fcntl.h>
34 #include <grp.h>
35 #include <inttypes.h>
36 #include <libutil.h>
37 #include <paths.h>
38 #include <stdbool.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 struct group_storage {
45 	struct group	 gr;
46 	char		*members[];
47 };
48 
49 static int lockfd = -1;
50 static char group_dir[PATH_MAX];
51 static char group_file[PATH_MAX];
52 static char tempname[PATH_MAX];
53 static int initialized;
54 
55 static const char group_line_format[] = "%s:%s:%ju:";
56 
57 /*
58  * Initialize statics
59  */
60 int
61 gr_init(const char *dir, const char *group)
62 {
63 
64 	if (dir == NULL) {
65 		strcpy(group_dir, _PATH_ETC);
66 	} else {
67 		if (strlen(dir) >= sizeof(group_dir)) {
68 			errno = ENAMETOOLONG;
69 			return (-1);
70 		}
71 		strcpy(group_dir, dir);
72 	}
73 
74 	if (group == NULL) {
75 		if (dir == NULL) {
76 			strcpy(group_file, _PATH_GROUP);
77 		} else if (snprintf(group_file, sizeof(group_file), "%s/group",
78 			group_dir) > (int)sizeof(group_file)) {
79 			errno = ENAMETOOLONG;
80 			return (-1);
81 		}
82 	} else {
83 		if (strlen(group) >= sizeof(group_file)) {
84 			errno = ENAMETOOLONG;
85 			return (-1);
86 		}
87 		strcpy(group_file, group);
88 	}
89 
90 	initialized = 1;
91 	return (0);
92 }
93 
94 /*
95  * Lock the group file
96  */
97 int
98 gr_lock(void)
99 {
100 	if (*group_file == '\0')
101 		return (-1);
102 
103 	for (;;) {
104 		struct stat st;
105 
106 		lockfd = open(group_file, O_RDONLY, 0);
107 		if (lockfd < 0 || fcntl(lockfd, F_SETFD, 1) == -1)
108 			err(1, "%s", group_file);
109 		if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) {
110 			if (errno == EWOULDBLOCK) {
111 				errx(1, "the group file is busy");
112 			} else {
113 				err(1, "could not lock the group file: ");
114 			}
115 		}
116 		if (fstat(lockfd, &st) == -1)
117 			err(1, "fstat() failed: ");
118 		if (st.st_nlink != 0)
119 			break;
120 		close(lockfd);
121 		lockfd = -1;
122 	}
123 	return (lockfd);
124 }
125 
126 /*
127  * Create and open a presmuably safe temp file for editing group data
128  */
129 int
130 gr_tmp(int mfd)
131 {
132 	char buf[8192];
133 	ssize_t nr;
134 	const char *p;
135 	int tfd;
136 
137 	if (*group_file == '\0')
138 		return (-1);
139 	if ((p = strrchr(group_file, '/')))
140 		++p;
141 	else
142 		p = group_file;
143 	if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX",
144 		(int)(p - group_file), group_file) >= (int)sizeof(tempname)) {
145 		errno = ENAMETOOLONG;
146 		return (-1);
147 	}
148 	if ((tfd = mkstemp(tempname)) == -1)
149 		return (-1);
150 	if (mfd != -1) {
151 		while ((nr = read(mfd, buf, sizeof(buf))) > 0)
152 			if (write(tfd, buf, (size_t)nr) != nr)
153 				break;
154 		if (nr != 0) {
155 			unlink(tempname);
156 			*tempname = '\0';
157 			close(tfd);
158 			return (-1);
159 		}
160 	}
161 	return (tfd);
162 }
163 
164 /*
165  * Copy the group file from one descriptor to another, replacing, deleting
166  * or adding a single record on the way.
167  */
168 int
169 gr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr)
170 {
171 	char buf[8192], *end, *line, *p, *q, *r, t;
172 	struct group *fgr;
173 	const struct group *sgr;
174 	size_t len;
175 	int eof, readlen;
176 
177 	sgr = gr;
178 	if (gr == NULL) {
179 		line = NULL;
180 		if (old_gr == NULL)
181 			return (-1);
182 		sgr = old_gr;
183 	} else if ((line = gr_make(gr)) == NULL)
184 		return (-1);
185 
186 	eof = 0;
187 	len = 0;
188 	p = q = end = buf;
189 	for (;;) {
190 		/* find the end of the current line */
191 		for (p = q; q < end && *q != '\0'; ++q)
192 			if (*q == '\n')
193 				break;
194 
195 		/* if we don't have a complete line, fill up the buffer */
196 		if (q >= end) {
197 			if (eof)
198 				break;
199 			if ((size_t)(q - p) >= sizeof(buf)) {
200 				warnx("group line too long");
201 				errno = EINVAL; /* hack */
202 				goto err;
203 			}
204 			if (p < end) {
205 				q = memmove(buf, p, end -p);
206 				end -= p - buf;
207 			} else {
208 				p = q = end = buf;
209 			}
210 			readlen = read(ffd, end, sizeof(buf) - (end -buf));
211 			if (readlen == -1)
212 				goto err;
213 			else
214 				len = (size_t)readlen;
215 			if (len == 0 && p == buf)
216 				break;
217 			end += len;
218 			len = end - buf;
219 			if (len < (ssize_t)sizeof(buf)) {
220 				eof = 1;
221 				if (len > 0 && buf[len -1] != '\n')
222 					++len, *end++ = '\n';
223 			}
224 			continue;
225 		}
226 
227 		/* is it a blank line or a comment? */
228 		for (r = p; r < q && isspace(*r); ++r)
229 			/* nothing */;
230 		if (r == q || *r == '#') {
231 			/* yep */
232 			if (write(tfd, p, q -p + 1) != q - p + 1)
233 				goto err;
234 			++q;
235 			continue;
236 		}
237 
238 		/* is it the one we're looking for? */
239 
240 		t = *q;
241 		*q = '\0';
242 
243 		fgr = gr_scan(r);
244 
245 		/* fgr is either a struct group for the current line,
246 		 * or NULL if the line is malformed.
247 		 */
248 
249 		*q = t;
250 		if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) {
251 			/* nope */
252 			if (fgr != NULL)
253 				free(fgr);
254 			if (write(tfd, p, q - p + 1) != q - p + 1)
255 				goto err;
256 			++q;
257 			continue;
258 		}
259 		if (old_gr && !gr_equal(fgr, old_gr)) {
260 			warnx("entry inconsistent");
261 			free(fgr);
262 			errno = EINVAL; /* hack */
263 			goto err;
264 		}
265 		free(fgr);
266 
267 		/* it is, replace or remove it */
268 		if (line != NULL) {
269 			len = strlen(line);
270 			if (write(tfd, line, len) != (int) len)
271 				goto err;
272 		} else {
273 			/* when removed, avoid the \n */
274 			q++;
275 		}
276 		/* we're done, just copy the rest over */
277 		for (;;) {
278 			if (write(tfd, q, end - q) != end - q)
279 				goto err;
280 			q = buf;
281 			readlen = read(ffd, buf, sizeof(buf));
282 			if (readlen == 0)
283 				break;
284 			else
285 				len = (size_t)readlen;
286 			if (readlen == -1)
287 				goto err;
288 			end = buf + len;
289 		}
290 		goto done;
291 	}
292 
293 	/* if we got here, we didn't find the old entry */
294 	if (line == NULL) {
295 		errno = ENOENT;
296 		goto err;
297 	}
298 	len = strlen(line);
299 	if ((size_t)write(tfd, line, len) != len ||
300 	   write(tfd, "\n", 1) != 1)
301 		goto err;
302  done:
303 	if (line != NULL)
304 		free(line);
305 	return (0);
306  err:
307 	if (line != NULL)
308 		free(line);
309 	return (-1);
310 }
311 
312 /*
313  * Regenerate the group file
314  */
315 int
316 gr_mkdb(void)
317 {
318 	return (rename(tempname, group_file));
319 }
320 
321 /*
322  * Clean up. Preserver errno for the caller's convenience.
323  */
324 void
325 gr_fini(void)
326 {
327 	int serrno;
328 
329 	if (!initialized)
330 		return;
331 	initialized = 0;
332 	serrno = errno;
333 	if (*tempname != '\0') {
334 		unlink(tempname);
335 		*tempname = '\0';
336 	}
337 	if (lockfd != -1)
338 		close(lockfd);
339 	errno = serrno;
340 }
341 
342 /*
343  * Compares two struct group's.
344  */
345 int
346 gr_equal(const struct group *gr1, const struct group *gr2)
347 {
348 	int gr1_ndx;
349 	int gr2_ndx;
350 	bool found;
351 
352 	/* Check that the non-member information is the same. */
353 	if (gr1->gr_name == NULL || gr2->gr_name == NULL) {
354 		if (gr1->gr_name != gr2->gr_name)
355 			return (false);
356 	} else if (strcmp(gr1->gr_name, gr2->gr_name) != 0)
357 		return (false);
358 	if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) {
359 		if (gr1->gr_passwd != gr2->gr_passwd)
360 			return (false);
361 	} else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0)
362 		return (false);
363 	if (gr1->gr_gid != gr2->gr_gid)
364 		return (false);
365 
366 	/* Check all members in both groups. */
367 	if (gr1->gr_mem == NULL || gr2->gr_mem == NULL) {
368 		if (gr1->gr_mem != gr2->gr_mem)
369 			return (false);
370 	} else {
371 		for (found = false, gr1_ndx = 0; gr1->gr_mem[gr1_ndx] != NULL;
372 		    gr1_ndx++) {
373 			for (gr2_ndx = 0; gr2->gr_mem[gr2_ndx] != NULL;
374 			    gr2_ndx++)
375 				if (strcmp(gr1->gr_mem[gr1_ndx],
376 				    gr2->gr_mem[gr2_ndx]) == 0) {
377 					found = true;
378 					break;
379 				}
380 			if (!found)
381 				return (false);
382 		}
383 
384 		/* Check that group2 does not have more members than group1. */
385 		if (gr2->gr_mem[gr1_ndx] != NULL)
386 			return (false);
387 	}
388 
389 	return (true);
390 }
391 
392 /*
393  * Make a group line out of a struct group.
394  */
395 char *
396 gr_make(const struct group *gr)
397 {
398 	char *line;
399 	size_t line_size;
400 	int ndx;
401 
402 	/* Calculate the length of the group line. */
403 	line_size = snprintf(NULL, 0, group_line_format, gr->gr_name,
404 	    gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1;
405 	if (gr->gr_mem != NULL) {
406 		for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++)
407 			line_size += strlen(gr->gr_mem[ndx]) + 1;
408 		if (ndx > 0)
409 			line_size--;
410 	}
411 
412 	/* Create the group line and fill it. */
413 	if ((line = malloc(line_size)) == NULL)
414 		return (NULL);
415 	snprintf(line, line_size, group_line_format, gr->gr_name, gr->gr_passwd,
416 	    (uintmax_t)gr->gr_gid);
417 	if (gr->gr_mem != NULL)
418 		for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) {
419 			strcat(line, gr->gr_mem[ndx]);
420 			if (gr->gr_mem[ndx + 1] != NULL)
421 				strcat(line, ",");
422 		}
423 
424 	return (line);
425 }
426 
427 /*
428  * Duplicate a struct group.
429  */
430 struct group *
431 gr_dup(const struct group *gr)
432 {
433 	char *dst;
434 	size_t len;
435 	struct group_storage *gs;
436 	int ndx;
437 	int num_mem;
438 
439 	/* Calculate size of the group. */
440 	len = sizeof(*gs);
441 	if (gr->gr_name != NULL)
442 		len += strlen(gr->gr_name) + 1;
443 	if (gr->gr_passwd != NULL)
444 		len += strlen(gr->gr_passwd) + 1;
445 	if (gr->gr_mem != NULL) {
446 		for (num_mem = 0; gr->gr_mem[num_mem] != NULL; num_mem++)
447 			len += strlen(gr->gr_mem[num_mem]) + 1;
448 		len += (num_mem + 1) * sizeof(*gr->gr_mem);
449 	} else
450 		num_mem = -1;
451 
452 	/* Create new group and copy old group into it. */
453 	if ((gs = calloc(1, len)) == NULL)
454 		return (NULL);
455 	dst = (char *)&gs->members[num_mem + 1];
456 	if (gr->gr_name != NULL) {
457 		gs->gr.gr_name = dst;
458 		dst = stpcpy(gs->gr.gr_name, gr->gr_name) + 1;
459 	}
460 	if (gr->gr_passwd != NULL) {
461 		gs->gr.gr_passwd = dst;
462 		dst = stpcpy(gs->gr.gr_passwd, gr->gr_passwd) + 1;
463 	}
464 	gs->gr.gr_gid = gr->gr_gid;
465 	if (gr->gr_mem != NULL) {
466 		gs->gr.gr_mem = gs->members;
467 		for (ndx = 0; ndx < num_mem; ndx++) {
468 			gs->gr.gr_mem[ndx] = dst;
469 			dst = stpcpy(gs->gr.gr_mem[ndx], gr->gr_mem[ndx]) + 1;
470 		}
471 		gs->gr.gr_mem[ndx] = NULL;
472 	}
473 
474 	return (&gs->gr);
475 }
476 
477 /*
478  * Scan a line and place it into a group structure.
479  */
480 static bool
481 __gr_scan(char *line, struct group *gr)
482 {
483 	char *loc;
484 	int ndx;
485 
486 	/* Assign non-member information to structure. */
487 	gr->gr_name = line;
488 	if ((loc = strchr(line, ':')) == NULL)
489 		return (false);
490 	*loc = '\0';
491 	gr->gr_passwd = loc + 1;
492 	if (*gr->gr_passwd == ':')
493 		*gr->gr_passwd = '\0';
494 	else {
495 		if ((loc = strchr(loc + 1, ':')) == NULL)
496 			return (false);
497 		*loc = '\0';
498 	}
499 	if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1)
500 		return (false);
501 
502 	/* Assign member information to structure. */
503 	if ((loc = strchr(loc + 1, ':')) == NULL)
504 		return (false);
505 	line = loc + 1;
506 	gr->gr_mem = NULL;
507 	ndx = 0;
508 	do {
509 		gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) *
510 		    (ndx + 1));
511 		if (gr->gr_mem == NULL)
512 			return (false);
513 
514 		/* Skip locations without members (i.e., empty string). */
515 		do {
516 			gr->gr_mem[ndx] = strsep(&line, ",");
517 		} while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0');
518 	} while (gr->gr_mem[ndx++] != NULL);
519 
520 	return (true);
521 }
522 
523 /*
524  * Create a struct group from a line.
525  */
526 struct group *
527 gr_scan(const char *line)
528 {
529 	struct group gr;
530 	char *line_copy;
531 	struct group *new_gr;
532 
533 	if ((line_copy = strdup(line)) == NULL)
534 		return (NULL);
535 	if (!__gr_scan(line_copy, &gr)) {
536 		free(line_copy);
537 		return (NULL);
538 	}
539 	new_gr = gr_dup(&gr);
540 	free(line_copy);
541 	if (gr.gr_mem != NULL)
542 		free(gr.gr_mem);
543 
544 	return (new_gr);
545 }
546