gr_util.c revision 244779
1130803Smarcel/*-
2130803Smarcel * Copyright (c) 2008 Sean C. Farley <scf@FreeBSD.org>
3130803Smarcel * All rights reserved.
4130803Smarcel *
5130803Smarcel * Redistribution and use in source and binary forms, with or without
6130803Smarcel * modification, are permitted provided that the following conditions
7130803Smarcel * are met:
8130803Smarcel * 1. Redistributions of source code must retain the above copyright
9130803Smarcel *    notice, this list of conditions and the following disclaimer,
10130803Smarcel *    without modification, immediately at the beginning of the file.
11130803Smarcel * 2. Redistributions in binary form must reproduce the above copyright
12130803Smarcel *    notice, this list of conditions and the following disclaimer in the
13130803Smarcel *    documentation and/or other materials provided with the distribution.
14130803Smarcel *
15130803Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16130803Smarcel * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17130803Smarcel * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18130803Smarcel * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19130803Smarcel * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20130803Smarcel * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21130803Smarcel * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22130803Smarcel * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23130803Smarcel * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24130803Smarcel * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25130803Smarcel */
26130803Smarcel
27130803Smarcel#include <sys/cdefs.h>
28130803Smarcel__FBSDID("$FreeBSD: head/lib/libutil/gr_util.c 244779 2012-12-28 20:30:04Z bapt $");
29130803Smarcel
30130803Smarcel#include <sys/param.h>
31130803Smarcel#include <sys/errno.h>
32130803Smarcel#include <sys/stat.h>
33130803Smarcel
34130803Smarcel#include <ctype.h>
35130803Smarcel#include <err.h>
36130803Smarcel#include <fcntl.h>
37130803Smarcel#include <grp.h>
38130803Smarcel#include <inttypes.h>
39130803Smarcel#include <libutil.h>
40130803Smarcel#include <paths.h>
41130803Smarcel#include <stdbool.h>
42130803Smarcel#include <stdio.h>
43130803Smarcel#include <stdlib.h>
44130803Smarcel#include <string.h>
45130803Smarcel#include <unistd.h>
46130803Smarcel
47130803Smarcelstatic int lockfd = -1;
48130803Smarcelstatic char group_dir[PATH_MAX];
49130803Smarcelstatic char group_file[PATH_MAX];
50130803Smarcelstatic char tempname[PATH_MAX];
51130803Smarcelstatic int initialized;
52130803Smarcel
53130803Smarcelstatic const char group_line_format[] = "%s:%s:%ju:";
54130803Smarcel
55130803Smarcel/*
56130803Smarcel * Initialize statics
57130803Smarcel */
58130803Smarcelint
59130803Smarcelgr_init(const char *dir, const char *group)
60130803Smarcel{
61130803Smarcel
62130803Smarcel	if (dir == NULL) {
63130803Smarcel		strcpy(group_dir, _PATH_ETC);
64130803Smarcel	} else {
65130803Smarcel		if (strlen(dir) >= sizeof(group_dir)) {
66130803Smarcel			errno = ENAMETOOLONG;
67130803Smarcel			return (-1);
68130803Smarcel		}
69130803Smarcel		strcpy(group_dir, dir);
70130803Smarcel	}
71130803Smarcel
72130803Smarcel	if (group == NULL) {
73130803Smarcel		if (dir == NULL) {
74130803Smarcel			strcpy(group_file, _PATH_GROUP);
75130803Smarcel		} else if (snprintf(group_file, sizeof(group_file), "%s/group",
76130803Smarcel			group_dir) > (int)sizeof(group_file)) {
77130803Smarcel			errno = ENAMETOOLONG;
78130803Smarcel			return (-1);
79130803Smarcel		}
80130803Smarcel	} else {
81130803Smarcel		if (strlen(group) >= sizeof(group_file)) {
82130803Smarcel			errno = ENAMETOOLONG;
83130803Smarcel			return (-1);
84130803Smarcel		}
85130803Smarcel		strcpy(group_file, group);
86130803Smarcel	}
87130803Smarcel
88130803Smarcel	initialized = 1;
89130803Smarcel	return (0);
90130803Smarcel}
91130803Smarcel
92130803Smarcel/*
93130803Smarcel * Lock the group file
94130803Smarcel */
95130803Smarcelint
96130803Smarcelgr_lock(void)
97130803Smarcel{
98130803Smarcel	if (*group_file == '\0')
99130803Smarcel		return (-1);
100130803Smarcel
101130803Smarcel	for (;;) {
102130803Smarcel		struct stat st;
103130803Smarcel
104130803Smarcel		lockfd = flopen(group_file, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0);
105130803Smarcel		if (lockfd == -1) {
106130803Smarcel			if (errno == EWOULDBLOCK) {
107130803Smarcel				errx(1, "the group file is busy");
108130803Smarcel			} else {
109130803Smarcel				err(1, "could not lock the group file: ");
110130803Smarcel			}
111130803Smarcel		}
112130803Smarcel		if (fstat(lockfd, &st) == -1)
113130803Smarcel			err(1, "fstat() failed: ");
114130803Smarcel		if (st.st_nlink != 0)
115130803Smarcel			break;
116130803Smarcel		close(lockfd);
117130803Smarcel		lockfd = -1;
118130803Smarcel	}
119130803Smarcel	return (lockfd);
120130803Smarcel}
121130803Smarcel
122130803Smarcel/*
123130803Smarcel * Create and open a presmuably safe temp file for editing group data
124130803Smarcel */
125130803Smarcelint
126130803Smarcelgr_tmp(int mfd)
127130803Smarcel{
128130803Smarcel	char buf[8192];
129130803Smarcel	ssize_t nr;
130130803Smarcel	const char *p;
131130803Smarcel	int tfd;
132130803Smarcel
133130803Smarcel	if (*group_file == '\0')
134130803Smarcel		return (-1);
135130803Smarcel	if ((p = strrchr(group_file, '/')))
136130803Smarcel		++p;
137130803Smarcel	else
138130803Smarcel		p = group_file;
139130803Smarcel	if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX",
140130803Smarcel		(int)(p - group_file), group_file) >= (int)sizeof(tempname)) {
141130803Smarcel		errno = ENAMETOOLONG;
142130803Smarcel		return (-1);
143130803Smarcel	}
144130803Smarcel	if ((tfd = mkstemp(tempname)) == -1)
145130803Smarcel		return (-1);
146130803Smarcel	if (mfd != -1) {
147130803Smarcel		while ((nr = read(mfd, buf, sizeof(buf))) > 0)
148130803Smarcel			if (write(tfd, buf, (size_t)nr) != nr)
149130803Smarcel				break;
150130803Smarcel		if (nr != 0) {
151130803Smarcel			unlink(tempname);
152130803Smarcel			*tempname = '\0';
153130803Smarcel			close(tfd);
154130803Smarcel			return (-1);
155130803Smarcel		}
156130803Smarcel	}
157130803Smarcel	return (tfd);
158130803Smarcel}
159130803Smarcel
160130803Smarcel/*
161130803Smarcel * Copy the group file from one descriptor to another, replacing, deleting
162130803Smarcel * or adding a single record on the way.
163130803Smarcel */
164130803Smarcelint
165130803Smarcelgr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr)
166130803Smarcel{
167130803Smarcel	char buf[8192], *end, *line, *p, *q, *r, t;
168130803Smarcel	struct group *fgr;
169130803Smarcel	const struct group *sgr;
170130803Smarcel	size_t len;
171130803Smarcel	int eof, readlen;
172130803Smarcel
173130803Smarcel	sgr = gr;
174130803Smarcel	if (gr == NULL) {
175130803Smarcel		line = NULL;
176130803Smarcel		if (old_gr == NULL)
177130803Smarcel			return (-1);
178130803Smarcel		sgr = old_gr;
179130803Smarcel	} else if ((line = gr_make(gr)) == NULL)
180130803Smarcel		return (-1);
181130803Smarcel
182130803Smarcel	eof = 0;
183130803Smarcel	len = 0;
184130803Smarcel	p = q = end = buf;
185130803Smarcel	for (;;) {
186130803Smarcel		/* find the end of the current line */
187130803Smarcel		for (p = q; q < end && *q != '\0'; ++q)
188130803Smarcel			if (*q == '\n')
189130803Smarcel				break;
190130803Smarcel
191130803Smarcel		/* if we don't have a complete line, fill up the buffer */
192130803Smarcel		if (q >= end) {
193130803Smarcel			if (eof)
194130803Smarcel				break;
195130803Smarcel			if ((size_t)(q - p) >= sizeof(buf)) {
196130803Smarcel				warnx("group line too long");
197130803Smarcel				errno = EINVAL; /* hack */
198130803Smarcel				goto err;
199130803Smarcel			}
200130803Smarcel			if (p < end) {
201130803Smarcel				q = memmove(buf, p, end -p);
202130803Smarcel				end -= p - buf;
203130803Smarcel			} else {
204130803Smarcel				p = q = end = buf;
205130803Smarcel			}
206130803Smarcel			readlen = read(ffd, end, sizeof(buf) - (end -buf));
207130803Smarcel			if (readlen == -1)
208130803Smarcel				goto err;
209130803Smarcel			else
210130803Smarcel				len = (size_t)readlen;
211130803Smarcel			if (len == 0 && p == buf)
212130803Smarcel				break;
213130803Smarcel			end += len;
214130803Smarcel			len = end - buf;
215130803Smarcel			if (len < (ssize_t)sizeof(buf)) {
216130803Smarcel				eof = 1;
217130803Smarcel				if (len > 0 && buf[len -1] != '\n')
218130803Smarcel					++len, *end++ = '\n';
219130803Smarcel			}
220130803Smarcel			continue;
221130803Smarcel		}
222130803Smarcel
223130803Smarcel		/* is it a blank line or a comment? */
224130803Smarcel		for (r = p; r < q && isspace(*r); ++r)
225130803Smarcel			/* nothing */;
226130803Smarcel		if (r == q || *r == '#') {
227130803Smarcel			/* yep */
228130803Smarcel			if (write(tfd, p, q -p + 1) != q - p + 1)
229130803Smarcel				goto err;
230130803Smarcel			++q;
231130803Smarcel			continue;
232130803Smarcel		}
233130803Smarcel
234130803Smarcel		/* is it the one we're looking for? */
235130803Smarcel
236130803Smarcel		t = *q;
237130803Smarcel		*q = '\0';
238130803Smarcel
239130803Smarcel		fgr = gr_scan(r);
240130803Smarcel
241130803Smarcel		/* fgr is either a struct group for the current line,
242130803Smarcel		 * or NULL if the line is malformed.
243130803Smarcel		 */
244130803Smarcel
245130803Smarcel		*q = t;
246130803Smarcel		if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) {
247130803Smarcel			/* nope */
248130803Smarcel			if (fgr != NULL)
249130803Smarcel				free(fgr);
250130803Smarcel			if (write(tfd, p, q - p + 1) != q - p + 1)
251130803Smarcel				goto err;
252130803Smarcel			++q;
253130803Smarcel			continue;
254130803Smarcel		}
255130803Smarcel		if (old_gr && !gr_equal(fgr, old_gr)) {
256130803Smarcel			warnx("entry inconsistent");
257130803Smarcel			free(fgr);
258130803Smarcel			errno = EINVAL; /* hack */
259130803Smarcel			goto err;
260130803Smarcel		}
261130803Smarcel		free(fgr);
262130803Smarcel
263130803Smarcel		/* it is, replace or remove it */
264130803Smarcel		if (line != NULL) {
265130803Smarcel			len = strlen(line);
266130803Smarcel			if (write(tfd, line, len) != (int) len)
267130803Smarcel				goto err;
268130803Smarcel		} else {
269130803Smarcel			/* when removed, avoid the \n */
270130803Smarcel			q++;
271130803Smarcel		}
272130803Smarcel		/* we're done, just copy the rest over */
273130803Smarcel		for (;;) {
274130803Smarcel			if (write(tfd, q, end - q) != end - q)
275130803Smarcel				goto err;
276130803Smarcel			q = buf;
277130803Smarcel			readlen = read(ffd, buf, sizeof(buf));
278130803Smarcel			if (readlen == 0)
279130803Smarcel				break;
280130803Smarcel			else
281130803Smarcel				len = (size_t)readlen;
282130803Smarcel			if (readlen == -1)
283130803Smarcel				goto err;
284130803Smarcel			end = buf + len;
285130803Smarcel		}
286130803Smarcel		goto done;
287130803Smarcel	}
288130803Smarcel
289130803Smarcel	/* if we got here, we didn't find the old entry */
290130803Smarcel	if (line == NULL) {
291130803Smarcel		errno = ENOENT;
292130803Smarcel		goto err;
293130803Smarcel	}
294130803Smarcel	len = strlen(line);
295130803Smarcel	if ((size_t)write(tfd, line, len) != len ||
296130803Smarcel	   write(tfd, "\n", 1) != 1)
297130803Smarcel		goto err;
298130803Smarcel done:
299130803Smarcel	if (line != NULL)
300130803Smarcel		free(line);
301130803Smarcel	return (0);
302130803Smarcel err:
303130803Smarcel	if (line != NULL)
304130803Smarcel		free(line);
305130803Smarcel	return (-1);
306130803Smarcel}
307130803Smarcel
308130803Smarcel/*
309130803Smarcel * Regenerate the group file
310130803Smarcel */
311130803Smarcelint
312130803Smarcelgr_mkdb(void)
313130803Smarcel{
314130803Smarcel	if (chmod(tempname, 0644) != 0)
315130803Smarcel		return (-1);
316130803Smarcel
317130803Smarcel	return (rename(tempname, group_file));
318130803Smarcel}
319130803Smarcel
320130803Smarcel/*
321130803Smarcel * Clean up. Preserver errno for the caller's convenience.
322130803Smarcel */
323130803Smarcelvoid
324130803Smarcelgr_fini(void)
325130803Smarcel{
326130803Smarcel	int serrno;
327130803Smarcel
328130803Smarcel	if (!initialized)
329130803Smarcel		return;
330130803Smarcel	initialized = 0;
331130803Smarcel	serrno = errno;
332130803Smarcel	if (*tempname != '\0') {
333130803Smarcel		unlink(tempname);
334130803Smarcel		*tempname = '\0';
335130803Smarcel	}
336130803Smarcel	if (lockfd != -1)
337130803Smarcel		close(lockfd);
338130803Smarcel	errno = serrno;
339130803Smarcel}
340130803Smarcel
341130803Smarcel/*
342130803Smarcel * Compares two struct group's.
343 */
344int
345gr_equal(const struct group *gr1, const struct group *gr2)
346{
347	int gr1_ndx;
348	int gr2_ndx;
349	bool found;
350
351	/* Check that the non-member information is the same. */
352	if (gr1->gr_name == NULL || gr2->gr_name == NULL) {
353		if (gr1->gr_name != gr2->gr_name)
354			return (false);
355	} else if (strcmp(gr1->gr_name, gr2->gr_name) != 0)
356		return (false);
357	if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) {
358		if (gr1->gr_passwd != gr2->gr_passwd)
359			return (false);
360	} else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0)
361		return (false);
362	if (gr1->gr_gid != gr2->gr_gid)
363		return (false);
364
365	/* Check all members in both groups. */
366	if (gr1->gr_mem == NULL || gr2->gr_mem == NULL) {
367		if (gr1->gr_mem != gr2->gr_mem)
368			return (false);
369	} else {
370		for (found = false, gr1_ndx = 0; gr1->gr_mem[gr1_ndx] != NULL;
371		    gr1_ndx++) {
372			for (gr2_ndx = 0; gr2->gr_mem[gr2_ndx] != NULL;
373			    gr2_ndx++)
374				if (strcmp(gr1->gr_mem[gr1_ndx],
375				    gr2->gr_mem[gr2_ndx]) == 0) {
376					found = true;
377					break;
378				}
379			if (!found)
380				return (false);
381		}
382
383		/* Check that group2 does not have more members than group1. */
384		if (gr2->gr_mem[gr1_ndx] != NULL)
385			return (false);
386	}
387
388	return (true);
389}
390
391/*
392 * Make a group line out of a struct group.
393 */
394char *
395gr_make(const struct group *gr)
396{
397	char *line;
398	size_t line_size;
399	int ndx;
400
401	/* Calculate the length of the group line. */
402	line_size = snprintf(NULL, 0, group_line_format, gr->gr_name,
403	    gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1;
404	if (gr->gr_mem != NULL) {
405		for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++)
406			line_size += strlen(gr->gr_mem[ndx]) + 1;
407		if (ndx > 0)
408			line_size--;
409	}
410
411	/* Create the group line and fill it. */
412	if ((line = malloc(line_size)) == NULL)
413		return (NULL);
414	snprintf(line, line_size, group_line_format, gr->gr_name, gr->gr_passwd,
415	    (uintmax_t)gr->gr_gid);
416	if (gr->gr_mem != NULL)
417		for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) {
418			strcat(line, gr->gr_mem[ndx]);
419			if (gr->gr_mem[ndx + 1] != NULL)
420				strcat(line, ",");
421		}
422
423	return (line);
424}
425
426/*
427 * Duplicate a struct group.
428 */
429struct group *
430gr_dup(const struct group *gr)
431{
432	struct group *newgr;
433	char *dst;
434	size_t len;
435	int ndx;
436	int num_mem;
437
438	/* Calculate size of the group. */
439	len = sizeof(*newgr);
440	if (gr->gr_name != NULL)
441		len += strlen(gr->gr_name) + 1;
442	if (gr->gr_passwd != NULL)
443		len += strlen(gr->gr_passwd) + 1;
444	if (gr->gr_mem != NULL) {
445		for (num_mem = 0; gr->gr_mem[num_mem] != NULL; num_mem++)
446			len += strlen(gr->gr_mem[num_mem]) + 1;
447		len += (num_mem + 1) * sizeof(*gr->gr_mem);
448	} else
449		num_mem = -1;
450	/* Create new group and copy old group into it. */
451	if ((newgr = malloc(len)) == NULL)
452		return (NULL);
453	/* point new gr_mem to end of struct + 1 */
454	if (gr->gr_mem != NULL)
455		newgr->gr_mem = (char **)(newgr + 1);
456	else
457		newgr->gr_mem = NULL;
458	/* point dst after the end of all the gr_mem pointers in newgr */
459	dst = (char *)newgr + sizeof(struct group) +
460	    (num_mem + 1) * sizeof(*gr->gr_mem);
461	if (gr->gr_name != NULL) {
462		newgr->gr_name = dst;
463		dst = stpcpy(dst, gr->gr_name) + 1;
464	} else {
465		newgr->gr_name = NULL;
466	}
467	if (gr->gr_passwd != NULL) {
468		newgr->gr_passwd = dst;
469		dst = stpcpy(dst, gr->gr_passwd) + 1;
470	} else {
471		newgr->gr_passwd = NULL;
472	}
473	newgr->gr_gid = gr->gr_gid;
474	if (gr->gr_mem != NULL) {
475		for (ndx = 0; ndx < num_mem; ndx++) {
476			newgr->gr_mem[ndx] = dst;
477			dst = stpcpy(dst, gr->gr_mem[ndx]) + 1;
478		}
479		newgr->gr_mem[ndx] = NULL;
480	}
481	return (newgr);
482}
483
484/*
485 * Add a new member name to a struct group.
486 */
487struct group *
488gr_add(struct group *gr, char *newmember)
489{
490	size_t mlen;
491	int num_mem=0;
492	char **members;
493	struct group *newgr;
494
495	if (newmember == NULL)
496		return(gr_dup(gr));
497
498	if (gr->gr_mem != NULL) {
499		for (num_mem = 0; gr->gr_mem[num_mem] != NULL; num_mem++) {
500			if (strcmp(gr->gr_mem[num_mem], newmember) == 0) {
501				errno = EEXIST;
502				return (NULL);
503			}
504		}
505	}
506	/* Allocate enough for current pointers + 1 more and NULL marker */
507	mlen = (num_mem + 2) * sizeof(*gr->gr_mem);
508	if ((members = malloc(mlen)) == NULL)
509		return (NULL);
510	memcpy(members, gr->gr_mem, num_mem * sizeof(*gr->gr_mem));
511	members[num_mem++] = newmember;
512	members[num_mem] = NULL;
513	gr->gr_mem = members;
514	newgr = gr_dup(gr);
515	free(members);
516	return (newgr);
517}
518
519/*
520 * Scan a line and place it into a group structure.
521 */
522static bool
523__gr_scan(char *line, struct group *gr)
524{
525	char *loc;
526	int ndx;
527
528	/* Assign non-member information to structure. */
529	gr->gr_name = line;
530	if ((loc = strchr(line, ':')) == NULL)
531		return (false);
532	*loc = '\0';
533	gr->gr_passwd = loc + 1;
534	if (*gr->gr_passwd == ':')
535		*gr->gr_passwd = '\0';
536	else {
537		if ((loc = strchr(loc + 1, ':')) == NULL)
538			return (false);
539		*loc = '\0';
540	}
541	if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1)
542		return (false);
543
544	/* Assign member information to structure. */
545	if ((loc = strchr(loc + 1, ':')) == NULL)
546		return (false);
547	line = loc + 1;
548	gr->gr_mem = NULL;
549	ndx = 0;
550	do {
551		gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) *
552		    (ndx + 1));
553		if (gr->gr_mem == NULL)
554			return (false);
555
556		/* Skip locations without members (i.e., empty string). */
557		do {
558			gr->gr_mem[ndx] = strsep(&line, ",");
559		} while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0');
560	} while (gr->gr_mem[ndx++] != NULL);
561
562	return (true);
563}
564
565/*
566 * Create a struct group from a line.
567 */
568struct group *
569gr_scan(const char *line)
570{
571	struct group gr;
572	char *line_copy;
573	struct group *new_gr;
574
575	if ((line_copy = strdup(line)) == NULL)
576		return (NULL);
577	if (!__gr_scan(line_copy, &gr)) {
578		free(line_copy);
579		return (NULL);
580	}
581	new_gr = gr_dup(&gr);
582	free(line_copy);
583	if (gr.gr_mem != NULL)
584		free(gr.gr_mem);
585
586	return (new_gr);
587}
588