gr_util.c revision 242319
1178431Sscf/*-
2178431Sscf * Copyright (c) 2008 Sean C. Farley <scf@FreeBSD.org>
3178431Sscf * All rights reserved.
4178431Sscf *
5178431Sscf * Redistribution and use in source and binary forms, with or without
6178431Sscf * modification, are permitted provided that the following conditions
7178431Sscf * are met:
8178431Sscf * 1. Redistributions of source code must retain the above copyright
9178431Sscf *    notice, this list of conditions and the following disclaimer,
10178431Sscf *    without modification, immediately at the beginning of the file.
11178431Sscf * 2. Redistributions in binary form must reproduce the above copyright
12178431Sscf *    notice, this list of conditions and the following disclaimer in the
13178431Sscf *    documentation and/or other materials provided with the distribution.
14178431Sscf *
15178431Sscf * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16178431Sscf * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17178431Sscf * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18178431Sscf * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19178431Sscf * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20178431Sscf * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21178431Sscf * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22178431Sscf * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23178431Sscf * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24178431Sscf * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25178431Sscf */
26178431Sscf
27178431Sscf#include <sys/cdefs.h>
28178431Sscf__FBSDID("$FreeBSD: head/lib/libutil/gr_util.c 242319 2012-10-29 17:19:43Z bapt $");
29178431Sscf
30178431Sscf#include <sys/param.h>
31228545Sbapt#include <sys/errno.h>
32228545Sbapt#include <sys/stat.h>
33184831Sscf
34228545Sbapt#include <ctype.h>
35228545Sbapt#include <err.h>
36228545Sbapt#include <fcntl.h>
37178431Sscf#include <grp.h>
38178431Sscf#include <inttypes.h>
39184831Sscf#include <libutil.h>
40228545Sbapt#include <paths.h>
41178431Sscf#include <stdbool.h>
42178431Sscf#include <stdio.h>
43178431Sscf#include <stdlib.h>
44178431Sscf#include <string.h>
45228545Sbapt#include <unistd.h>
46178431Sscf
47185237Sscfstruct group_storage {
48185237Sscf	struct group	 gr;
49185237Sscf	char		*members[];
50185237Sscf};
51178431Sscf
52228545Sbaptstatic int lockfd = -1;
53228545Sbaptstatic char group_dir[PATH_MAX];
54228545Sbaptstatic char group_file[PATH_MAX];
55228545Sbaptstatic char tempname[PATH_MAX];
56228545Sbaptstatic int initialized;
57228545Sbapt
58185237Sscfstatic const char group_line_format[] = "%s:%s:%ju:";
59185237Sscf
60178431Sscf/*
61228545Sbapt * Initialize statics
62228545Sbapt */
63228545Sbaptint
64228545Sbaptgr_init(const char *dir, const char *group)
65228545Sbapt{
66242319Sbapt	struct stat st;
67242319Sbapt
68228545Sbapt	if (dir == NULL) {
69228545Sbapt		strcpy(group_dir, _PATH_ETC);
70228545Sbapt	} else {
71228545Sbapt		if (strlen(dir) >= sizeof(group_dir)) {
72228545Sbapt			errno = ENAMETOOLONG;
73228545Sbapt			return (-1);
74228545Sbapt		}
75228545Sbapt		strcpy(group_dir, dir);
76228545Sbapt	}
77228545Sbapt
78228545Sbapt	if (group == NULL) {
79228545Sbapt		if (dir == NULL) {
80228545Sbapt			strcpy(group_file, _PATH_GROUP);
81228545Sbapt		} else if (snprintf(group_file, sizeof(group_file), "%s/group",
82228545Sbapt			group_dir) > (int)sizeof(group_file)) {
83228545Sbapt			errno = ENAMETOOLONG;
84228545Sbapt			return (-1);
85228545Sbapt		}
86228545Sbapt	} else {
87228545Sbapt		if (strlen(group) >= sizeof(group_file)) {
88228545Sbapt			errno = ENAMETOOLONG;
89228545Sbapt			return (-1);
90228545Sbapt		}
91228545Sbapt		strcpy(group_file, group);
92228545Sbapt	}
93242319Sbapt
94242319Sbapt	if (stat(group_file, &st) == -1)
95242319Sbapt		return (-1);
96242319Sbapt
97242319Sbapt	if (S_ISDIR(st.st_mode)) {
98242319Sbapt		errno = EISDIR;
99242319Sbapt		return (-1);
100242319Sbapt	}
101242319Sbapt
102228545Sbapt	initialized = 1;
103228545Sbapt	return (0);
104228545Sbapt}
105228545Sbapt
106228545Sbapt/*
107228545Sbapt * Lock the group file
108228545Sbapt */
109228545Sbaptint
110228545Sbaptgr_lock(void)
111228545Sbapt{
112228545Sbapt	if (*group_file == '\0')
113228545Sbapt		return (-1);
114228545Sbapt
115228545Sbapt	for (;;) {
116228545Sbapt		struct stat st;
117228545Sbapt
118228545Sbapt		lockfd = open(group_file, O_RDONLY, 0);
119228545Sbapt		if (lockfd < 0 || fcntl(lockfd, F_SETFD, 1) == -1)
120228545Sbapt			err(1, "%s", group_file);
121228545Sbapt		if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) {
122228545Sbapt			if (errno == EWOULDBLOCK) {
123228545Sbapt				errx(1, "the group file is busy");
124228545Sbapt			} else {
125228545Sbapt				err(1, "could not lock the group file: ");
126228545Sbapt			}
127228545Sbapt		}
128228545Sbapt		if (fstat(lockfd, &st) == -1)
129228545Sbapt			err(1, "fstat() failed: ");
130228545Sbapt		if (st.st_nlink != 0)
131228545Sbapt			break;
132228545Sbapt		close(lockfd);
133228545Sbapt		lockfd = -1;
134228545Sbapt	}
135228545Sbapt	return (lockfd);
136228545Sbapt}
137228545Sbapt
138228545Sbapt/*
139228545Sbapt * Create and open a presmuably safe temp file for editing group data
140228545Sbapt */
141228545Sbaptint
142228545Sbaptgr_tmp(int mfd)
143228545Sbapt{
144228545Sbapt	char buf[8192];
145228545Sbapt	ssize_t nr;
146228545Sbapt	const char *p;
147228545Sbapt	int tfd;
148228545Sbapt
149228545Sbapt	if (*group_file == '\0')
150228545Sbapt		return (-1);
151228545Sbapt	if ((p = strrchr(group_file, '/')))
152228545Sbapt		++p;
153228545Sbapt	else
154228545Sbapt		p = group_file;
155228545Sbapt	if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX",
156228545Sbapt		(int)(p - group_file), group_file) >= (int)sizeof(tempname)) {
157228545Sbapt		errno = ENAMETOOLONG;
158228545Sbapt		return (-1);
159228545Sbapt	}
160228545Sbapt	if ((tfd = mkstemp(tempname)) == -1)
161228545Sbapt		return (-1);
162228545Sbapt	if (mfd != -1) {
163228545Sbapt		while ((nr = read(mfd, buf, sizeof(buf))) > 0)
164228545Sbapt			if (write(tfd, buf, (size_t)nr) != nr)
165228545Sbapt				break;
166228545Sbapt		if (nr != 0) {
167228545Sbapt			unlink(tempname);
168228545Sbapt			*tempname = '\0';
169228545Sbapt			close(tfd);
170228545Sbapt			return (-1);
171228545Sbapt		}
172228545Sbapt	}
173228545Sbapt	return (tfd);
174228545Sbapt}
175228545Sbapt
176228545Sbapt/*
177228545Sbapt * Copy the group file from one descriptor to another, replacing, deleting
178228545Sbapt * or adding a single record on the way.
179228545Sbapt */
180228545Sbaptint
181228545Sbaptgr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr)
182228545Sbapt{
183228545Sbapt	char buf[8192], *end, *line, *p, *q, *r, t;
184228545Sbapt	struct group *fgr;
185228545Sbapt	const struct group *sgr;
186228545Sbapt	size_t len;
187228545Sbapt	int eof, readlen;
188228545Sbapt
189228545Sbapt	sgr = gr;
190228545Sbapt	if (gr == NULL) {
191228545Sbapt		line = NULL;
192228545Sbapt		if (old_gr == NULL)
193228545Sbapt			return (-1);
194228545Sbapt		sgr = old_gr;
195228545Sbapt	} else if ((line = gr_make(gr)) == NULL)
196228545Sbapt		return (-1);
197228545Sbapt
198228545Sbapt	eof = 0;
199228545Sbapt	len = 0;
200228545Sbapt	p = q = end = buf;
201228545Sbapt	for (;;) {
202228545Sbapt		/* find the end of the current line */
203228545Sbapt		for (p = q; q < end && *q != '\0'; ++q)
204228545Sbapt			if (*q == '\n')
205228545Sbapt				break;
206228545Sbapt
207228545Sbapt		/* if we don't have a complete line, fill up the buffer */
208228545Sbapt		if (q >= end) {
209228545Sbapt			if (eof)
210228545Sbapt				break;
211228545Sbapt			if ((size_t)(q - p) >= sizeof(buf)) {
212228545Sbapt				warnx("group line too long");
213228545Sbapt				errno = EINVAL; /* hack */
214228545Sbapt				goto err;
215228545Sbapt			}
216228545Sbapt			if (p < end) {
217228545Sbapt				q = memmove(buf, p, end -p);
218228545Sbapt				end -= p - buf;
219228545Sbapt			} else {
220228545Sbapt				p = q = end = buf;
221228545Sbapt			}
222228545Sbapt			readlen = read(ffd, end, sizeof(buf) - (end -buf));
223228545Sbapt			if (readlen == -1)
224228545Sbapt				goto err;
225228545Sbapt			else
226228545Sbapt				len = (size_t)readlen;
227228545Sbapt			if (len == 0 && p == buf)
228228545Sbapt				break;
229228545Sbapt			end += len;
230228545Sbapt			len = end - buf;
231228545Sbapt			if (len < (ssize_t)sizeof(buf)) {
232228545Sbapt				eof = 1;
233228545Sbapt				if (len > 0 && buf[len -1] != '\n')
234228545Sbapt					++len, *end++ = '\n';
235228545Sbapt			}
236228545Sbapt			continue;
237228545Sbapt		}
238228545Sbapt
239228545Sbapt		/* is it a blank line or a comment? */
240228545Sbapt		for (r = p; r < q && isspace(*r); ++r)
241228545Sbapt			/* nothing */;
242228545Sbapt		if (r == q || *r == '#') {
243228545Sbapt			/* yep */
244228545Sbapt			if (write(tfd, p, q -p + 1) != q - p + 1)
245228545Sbapt				goto err;
246228545Sbapt			++q;
247228545Sbapt			continue;
248228545Sbapt		}
249228545Sbapt
250228545Sbapt		/* is it the one we're looking for? */
251228545Sbapt
252228545Sbapt		t = *q;
253228545Sbapt		*q = '\0';
254228545Sbapt
255228545Sbapt		fgr = gr_scan(r);
256228545Sbapt
257228545Sbapt		/* fgr is either a struct group for the current line,
258228545Sbapt		 * or NULL if the line is malformed.
259228545Sbapt		 */
260228545Sbapt
261228545Sbapt		*q = t;
262228545Sbapt		if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) {
263228545Sbapt			/* nope */
264228545Sbapt			if (fgr != NULL)
265228545Sbapt				free(fgr);
266228545Sbapt			if (write(tfd, p, q - p + 1) != q - p + 1)
267228545Sbapt				goto err;
268228545Sbapt			++q;
269228545Sbapt			continue;
270228545Sbapt		}
271228545Sbapt		if (old_gr && !gr_equal(fgr, old_gr)) {
272228545Sbapt			warnx("entry inconsistent");
273228545Sbapt			free(fgr);
274228545Sbapt			errno = EINVAL; /* hack */
275228545Sbapt			goto err;
276228545Sbapt		}
277228545Sbapt		free(fgr);
278228545Sbapt
279228545Sbapt		/* it is, replace or remove it */
280228545Sbapt		if (line != NULL) {
281228545Sbapt			len = strlen(line);
282228545Sbapt			if (write(tfd, line, len) != (int) len)
283228545Sbapt				goto err;
284228545Sbapt		} else {
285228545Sbapt			/* when removed, avoid the \n */
286228545Sbapt			q++;
287228545Sbapt		}
288228545Sbapt		/* we're done, just copy the rest over */
289228545Sbapt		for (;;) {
290228545Sbapt			if (write(tfd, q, end - q) != end - q)
291228545Sbapt				goto err;
292228545Sbapt			q = buf;
293228545Sbapt			readlen = read(ffd, buf, sizeof(buf));
294228545Sbapt			if (readlen == 0)
295228545Sbapt				break;
296228545Sbapt			else
297228545Sbapt				len = (size_t)readlen;
298228545Sbapt			if (readlen == -1)
299228545Sbapt				goto err;
300228545Sbapt			end = buf + len;
301228545Sbapt		}
302228545Sbapt		goto done;
303228545Sbapt	}
304228545Sbapt
305228545Sbapt	/* if we got here, we didn't find the old entry */
306228545Sbapt	if (line == NULL) {
307228545Sbapt		errno = ENOENT;
308228545Sbapt		goto err;
309228545Sbapt	}
310228545Sbapt	len = strlen(line);
311228545Sbapt	if ((size_t)write(tfd, line, len) != len ||
312228545Sbapt	   write(tfd, "\n", 1) != 1)
313228545Sbapt		goto err;
314228545Sbapt done:
315228545Sbapt	if (line != NULL)
316228545Sbapt		free(line);
317228545Sbapt	return (0);
318228545Sbapt err:
319228545Sbapt	if (line != NULL)
320228545Sbapt		free(line);
321228545Sbapt	return (-1);
322228545Sbapt}
323228545Sbapt
324228545Sbapt/*
325228545Sbapt * Regenerate the group file
326228545Sbapt */
327228545Sbaptint
328228545Sbaptgr_mkdb(void)
329228545Sbapt{
330228545Sbapt	return (rename(tempname, group_file));
331228545Sbapt}
332228545Sbapt
333228545Sbapt/*
334228545Sbapt * Clean up. Preserver errno for the caller's convenience.
335228545Sbapt */
336228545Sbaptvoid
337228545Sbaptgr_fini(void)
338228545Sbapt{
339228545Sbapt	int serrno;
340228545Sbapt
341228545Sbapt	if (!initialized)
342228545Sbapt		return;
343228545Sbapt	initialized = 0;
344228545Sbapt	serrno = errno;
345228545Sbapt	if (*tempname != '\0') {
346228545Sbapt		unlink(tempname);
347228545Sbapt		*tempname = '\0';
348228545Sbapt	}
349228545Sbapt	if (lockfd != -1)
350228545Sbapt		close(lockfd);
351228545Sbapt	errno = serrno;
352228545Sbapt}
353228545Sbapt
354228545Sbapt/*
355178431Sscf * Compares two struct group's.
356178431Sscf */
357178431Sscfint
358178431Sscfgr_equal(const struct group *gr1, const struct group *gr2)
359178431Sscf{
360185237Sscf	int gr1_ndx;
361185237Sscf	int gr2_ndx;
362184831Sscf	bool found;
363178431Sscf
364178431Sscf	/* Check that the non-member information is the same. */
365185237Sscf	if (gr1->gr_name == NULL || gr2->gr_name == NULL) {
366185237Sscf		if (gr1->gr_name != gr2->gr_name)
367185237Sscf			return (false);
368185237Sscf	} else if (strcmp(gr1->gr_name, gr2->gr_name) != 0)
369185237Sscf		return (false);
370185237Sscf	if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) {
371185237Sscf		if (gr1->gr_passwd != gr2->gr_passwd)
372185237Sscf			return (false);
373185237Sscf	} else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0)
374185237Sscf		return (false);
375185237Sscf	if (gr1->gr_gid != gr2->gr_gid)
376185237Sscf		return (false);
377178431Sscf
378178431Sscf	/* Check all members in both groups. */
379185237Sscf	if (gr1->gr_mem == NULL || gr2->gr_mem == NULL) {
380185237Sscf		if (gr1->gr_mem != gr2->gr_mem)
381185237Sscf			return (false);
382185237Sscf	} else {
383185237Sscf		for (found = false, gr1_ndx = 0; gr1->gr_mem[gr1_ndx] != NULL;
384185237Sscf		    gr1_ndx++) {
385185237Sscf			for (gr2_ndx = 0; gr2->gr_mem[gr2_ndx] != NULL;
386185237Sscf			    gr2_ndx++)
387185237Sscf				if (strcmp(gr1->gr_mem[gr1_ndx],
388185237Sscf				    gr2->gr_mem[gr2_ndx]) == 0) {
389178431Sscf					found = true;
390178431Sscf					break;
391178431Sscf				}
392185237Sscf			if (!found)
393185237Sscf				return (false);
394178431Sscf		}
395178431Sscf
396178431Sscf		/* Check that group2 does not have more members than group1. */
397185237Sscf		if (gr2->gr_mem[gr1_ndx] != NULL)
398185237Sscf			return (false);
399178431Sscf	}
400178431Sscf
401185237Sscf	return (true);
402178431Sscf}
403178431Sscf
404178431Sscf/*
405178431Sscf * Make a group line out of a struct group.
406178431Sscf */
407178431Sscfchar *
408178431Sscfgr_make(const struct group *gr)
409178431Sscf{
410178431Sscf	char *line;
411185237Sscf	size_t line_size;
412178431Sscf	int ndx;
413178431Sscf
414178431Sscf	/* Calculate the length of the group line. */
415185237Sscf	line_size = snprintf(NULL, 0, group_line_format, gr->gr_name,
416178431Sscf	    gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1;
417185237Sscf	if (gr->gr_mem != NULL) {
418185237Sscf		for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++)
419185237Sscf			line_size += strlen(gr->gr_mem[ndx]) + 1;
420185237Sscf		if (ndx > 0)
421185237Sscf			line_size--;
422185237Sscf	}
423178431Sscf
424178431Sscf	/* Create the group line and fill it. */
425185237Sscf	if ((line = malloc(line_size)) == NULL)
426178431Sscf		return (NULL);
427200423Sscf	snprintf(line, line_size, group_line_format, gr->gr_name, gr->gr_passwd,
428200423Sscf	    (uintmax_t)gr->gr_gid);
429185237Sscf	if (gr->gr_mem != NULL)
430185237Sscf		for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) {
431185237Sscf			strcat(line, gr->gr_mem[ndx]);
432185237Sscf			if (gr->gr_mem[ndx + 1] != NULL)
433185237Sscf				strcat(line, ",");
434185237Sscf		}
435178431Sscf
436178431Sscf	return (line);
437178431Sscf}
438178431Sscf
439178431Sscf/*
440178431Sscf * Duplicate a struct group.
441178431Sscf */
442178431Sscfstruct group *
443178431Sscfgr_dup(const struct group *gr)
444178431Sscf{
445185237Sscf	char *dst;
446184831Sscf	size_t len;
447185237Sscf	struct group_storage *gs;
448178431Sscf	int ndx;
449185237Sscf	int num_mem;
450178431Sscf
451185237Sscf	/* Calculate size of the group. */
452185237Sscf	len = sizeof(*gs);
453185237Sscf	if (gr->gr_name != NULL)
454185237Sscf		len += strlen(gr->gr_name) + 1;
455185237Sscf	if (gr->gr_passwd != NULL)
456185237Sscf		len += strlen(gr->gr_passwd) + 1;
457178431Sscf	if (gr->gr_mem != NULL) {
458185237Sscf		for (num_mem = 0; gr->gr_mem[num_mem] != NULL; num_mem++)
459185237Sscf			len += strlen(gr->gr_mem[num_mem]) + 1;
460185237Sscf		len += (num_mem + 1) * sizeof(*gr->gr_mem);
461185237Sscf	} else
462185237Sscf		num_mem = -1;
463178431Sscf
464178431Sscf	/* Create new group and copy old group into it. */
465185237Sscf	if ((gs = calloc(1, len)) == NULL)
466178431Sscf		return (NULL);
467185237Sscf	dst = (char *)&gs->members[num_mem + 1];
468178431Sscf	if (gr->gr_name != NULL) {
469185237Sscf		gs->gr.gr_name = dst;
470185237Sscf		dst = stpcpy(gs->gr.gr_name, gr->gr_name) + 1;
471178431Sscf	}
472178431Sscf	if (gr->gr_passwd != NULL) {
473185237Sscf		gs->gr.gr_passwd = dst;
474185237Sscf		dst = stpcpy(gs->gr.gr_passwd, gr->gr_passwd) + 1;
475178431Sscf	}
476185237Sscf	gs->gr.gr_gid = gr->gr_gid;
477178431Sscf	if (gr->gr_mem != NULL) {
478185237Sscf		gs->gr.gr_mem = gs->members;
479185237Sscf		for (ndx = 0; ndx < num_mem; ndx++) {
480185237Sscf			gs->gr.gr_mem[ndx] = dst;
481185237Sscf			dst = stpcpy(gs->gr.gr_mem[ndx], gr->gr_mem[ndx]) + 1;
482178431Sscf		}
483185237Sscf		gs->gr.gr_mem[ndx] = NULL;
484178431Sscf	}
485178431Sscf
486185237Sscf	return (&gs->gr);
487178431Sscf}
488178431Sscf
489178431Sscf/*
490178431Sscf * Scan a line and place it into a group structure.
491178431Sscf */
492178431Sscfstatic bool
493178431Sscf__gr_scan(char *line, struct group *gr)
494178431Sscf{
495178431Sscf	char *loc;
496178431Sscf	int ndx;
497178431Sscf
498178431Sscf	/* Assign non-member information to structure. */
499178431Sscf	gr->gr_name = line;
500178431Sscf	if ((loc = strchr(line, ':')) == NULL)
501178431Sscf		return (false);
502178431Sscf	*loc = '\0';
503178431Sscf	gr->gr_passwd = loc + 1;
504184831Sscf	if (*gr->gr_passwd == ':')
505184831Sscf		*gr->gr_passwd = '\0';
506178431Sscf	else {
507178431Sscf		if ((loc = strchr(loc + 1, ':')) == NULL)
508178431Sscf			return (false);
509178431Sscf		*loc = '\0';
510178431Sscf	}
511184831Sscf	if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1)
512178431Sscf		return (false);
513178431Sscf
514178431Sscf	/* Assign member information to structure. */
515178431Sscf	if ((loc = strchr(loc + 1, ':')) == NULL)
516178431Sscf		return (false);
517178431Sscf	line = loc + 1;
518178431Sscf	gr->gr_mem = NULL;
519185237Sscf	ndx = 0;
520185237Sscf	do {
521185237Sscf		gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) *
522185237Sscf		    (ndx + 1));
523185237Sscf		if (gr->gr_mem == NULL)
524185237Sscf			return (false);
525185237Sscf
526185237Sscf		/* Skip locations without members (i.e., empty string). */
527178431Sscf		do {
528178431Sscf			gr->gr_mem[ndx] = strsep(&line, ",");
529185237Sscf		} while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0');
530185237Sscf	} while (gr->gr_mem[ndx++] != NULL);
531178431Sscf
532178431Sscf	return (true);
533178431Sscf}
534178431Sscf
535178431Sscf/*
536178431Sscf * Create a struct group from a line.
537178431Sscf */
538178431Sscfstruct group *
539178431Sscfgr_scan(const char *line)
540178431Sscf{
541184831Sscf	struct group gr;
542185237Sscf	char *line_copy;
543185237Sscf	struct group *new_gr;
544178431Sscf
545185237Sscf	if ((line_copy = strdup(line)) == NULL)
546178431Sscf		return (NULL);
547185237Sscf	if (!__gr_scan(line_copy, &gr)) {
548185237Sscf		free(line_copy);
549178431Sscf		return (NULL);
550178431Sscf	}
551185237Sscf	new_gr = gr_dup(&gr);
552185237Sscf	free(line_copy);
553178431Sscf	if (gr.gr_mem != NULL)
554178431Sscf		free(gr.gr_mem);
555178431Sscf
556185237Sscf	return (new_gr);
557178431Sscf}
558