gr_util.c revision 243328
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 243328 2012-11-20 07:22:07Z 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
67228545Sbapt	if (dir == NULL) {
68228545Sbapt		strcpy(group_dir, _PATH_ETC);
69228545Sbapt	} else {
70228545Sbapt		if (strlen(dir) >= sizeof(group_dir)) {
71228545Sbapt			errno = ENAMETOOLONG;
72228545Sbapt			return (-1);
73228545Sbapt		}
74228545Sbapt		strcpy(group_dir, dir);
75228545Sbapt	}
76228545Sbapt
77228545Sbapt	if (group == NULL) {
78228545Sbapt		if (dir == NULL) {
79228545Sbapt			strcpy(group_file, _PATH_GROUP);
80228545Sbapt		} else if (snprintf(group_file, sizeof(group_file), "%s/group",
81228545Sbapt			group_dir) > (int)sizeof(group_file)) {
82228545Sbapt			errno = ENAMETOOLONG;
83228545Sbapt			return (-1);
84228545Sbapt		}
85228545Sbapt	} else {
86228545Sbapt		if (strlen(group) >= sizeof(group_file)) {
87228545Sbapt			errno = ENAMETOOLONG;
88228545Sbapt			return (-1);
89228545Sbapt		}
90228545Sbapt		strcpy(group_file, group);
91228545Sbapt	}
92242319Sbapt
93228545Sbapt	initialized = 1;
94228545Sbapt	return (0);
95228545Sbapt}
96228545Sbapt
97228545Sbapt/*
98228545Sbapt * Lock the group file
99228545Sbapt */
100228545Sbaptint
101228545Sbaptgr_lock(void)
102228545Sbapt{
103228545Sbapt	if (*group_file == '\0')
104228545Sbapt		return (-1);
105228545Sbapt
106228545Sbapt	for (;;) {
107228545Sbapt		struct stat st;
108228545Sbapt
109228545Sbapt		lockfd = open(group_file, O_RDONLY, 0);
110228545Sbapt		if (lockfd < 0 || fcntl(lockfd, F_SETFD, 1) == -1)
111228545Sbapt			err(1, "%s", group_file);
112228545Sbapt		if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) {
113228545Sbapt			if (errno == EWOULDBLOCK) {
114228545Sbapt				errx(1, "the group file is busy");
115228545Sbapt			} else {
116228545Sbapt				err(1, "could not lock the group file: ");
117228545Sbapt			}
118228545Sbapt		}
119228545Sbapt		if (fstat(lockfd, &st) == -1)
120228545Sbapt			err(1, "fstat() failed: ");
121228545Sbapt		if (st.st_nlink != 0)
122228545Sbapt			break;
123228545Sbapt		close(lockfd);
124228545Sbapt		lockfd = -1;
125228545Sbapt	}
126228545Sbapt	return (lockfd);
127228545Sbapt}
128228545Sbapt
129228545Sbapt/*
130228545Sbapt * Create and open a presmuably safe temp file for editing group data
131228545Sbapt */
132228545Sbaptint
133228545Sbaptgr_tmp(int mfd)
134228545Sbapt{
135228545Sbapt	char buf[8192];
136228545Sbapt	ssize_t nr;
137228545Sbapt	const char *p;
138228545Sbapt	int tfd;
139228545Sbapt
140228545Sbapt	if (*group_file == '\0')
141228545Sbapt		return (-1);
142228545Sbapt	if ((p = strrchr(group_file, '/')))
143228545Sbapt		++p;
144228545Sbapt	else
145228545Sbapt		p = group_file;
146228545Sbapt	if (snprintf(tempname, sizeof(tempname), "%.*sgroup.XXXXXX",
147228545Sbapt		(int)(p - group_file), group_file) >= (int)sizeof(tempname)) {
148228545Sbapt		errno = ENAMETOOLONG;
149228545Sbapt		return (-1);
150228545Sbapt	}
151228545Sbapt	if ((tfd = mkstemp(tempname)) == -1)
152228545Sbapt		return (-1);
153228545Sbapt	if (mfd != -1) {
154228545Sbapt		while ((nr = read(mfd, buf, sizeof(buf))) > 0)
155228545Sbapt			if (write(tfd, buf, (size_t)nr) != nr)
156228545Sbapt				break;
157228545Sbapt		if (nr != 0) {
158228545Sbapt			unlink(tempname);
159228545Sbapt			*tempname = '\0';
160228545Sbapt			close(tfd);
161228545Sbapt			return (-1);
162228545Sbapt		}
163228545Sbapt	}
164228545Sbapt	return (tfd);
165228545Sbapt}
166228545Sbapt
167228545Sbapt/*
168228545Sbapt * Copy the group file from one descriptor to another, replacing, deleting
169228545Sbapt * or adding a single record on the way.
170228545Sbapt */
171228545Sbaptint
172228545Sbaptgr_copy(int ffd, int tfd, const struct group *gr, struct group *old_gr)
173228545Sbapt{
174228545Sbapt	char buf[8192], *end, *line, *p, *q, *r, t;
175228545Sbapt	struct group *fgr;
176228545Sbapt	const struct group *sgr;
177228545Sbapt	size_t len;
178228545Sbapt	int eof, readlen;
179228545Sbapt
180228545Sbapt	sgr = gr;
181228545Sbapt	if (gr == NULL) {
182228545Sbapt		line = NULL;
183228545Sbapt		if (old_gr == NULL)
184228545Sbapt			return (-1);
185228545Sbapt		sgr = old_gr;
186228545Sbapt	} else if ((line = gr_make(gr)) == NULL)
187228545Sbapt		return (-1);
188228545Sbapt
189228545Sbapt	eof = 0;
190228545Sbapt	len = 0;
191228545Sbapt	p = q = end = buf;
192228545Sbapt	for (;;) {
193228545Sbapt		/* find the end of the current line */
194228545Sbapt		for (p = q; q < end && *q != '\0'; ++q)
195228545Sbapt			if (*q == '\n')
196228545Sbapt				break;
197228545Sbapt
198228545Sbapt		/* if we don't have a complete line, fill up the buffer */
199228545Sbapt		if (q >= end) {
200228545Sbapt			if (eof)
201228545Sbapt				break;
202228545Sbapt			if ((size_t)(q - p) >= sizeof(buf)) {
203228545Sbapt				warnx("group line too long");
204228545Sbapt				errno = EINVAL; /* hack */
205228545Sbapt				goto err;
206228545Sbapt			}
207228545Sbapt			if (p < end) {
208228545Sbapt				q = memmove(buf, p, end -p);
209228545Sbapt				end -= p - buf;
210228545Sbapt			} else {
211228545Sbapt				p = q = end = buf;
212228545Sbapt			}
213228545Sbapt			readlen = read(ffd, end, sizeof(buf) - (end -buf));
214228545Sbapt			if (readlen == -1)
215228545Sbapt				goto err;
216228545Sbapt			else
217228545Sbapt				len = (size_t)readlen;
218228545Sbapt			if (len == 0 && p == buf)
219228545Sbapt				break;
220228545Sbapt			end += len;
221228545Sbapt			len = end - buf;
222228545Sbapt			if (len < (ssize_t)sizeof(buf)) {
223228545Sbapt				eof = 1;
224228545Sbapt				if (len > 0 && buf[len -1] != '\n')
225228545Sbapt					++len, *end++ = '\n';
226228545Sbapt			}
227228545Sbapt			continue;
228228545Sbapt		}
229228545Sbapt
230228545Sbapt		/* is it a blank line or a comment? */
231228545Sbapt		for (r = p; r < q && isspace(*r); ++r)
232228545Sbapt			/* nothing */;
233228545Sbapt		if (r == q || *r == '#') {
234228545Sbapt			/* yep */
235228545Sbapt			if (write(tfd, p, q -p + 1) != q - p + 1)
236228545Sbapt				goto err;
237228545Sbapt			++q;
238228545Sbapt			continue;
239228545Sbapt		}
240228545Sbapt
241228545Sbapt		/* is it the one we're looking for? */
242228545Sbapt
243228545Sbapt		t = *q;
244228545Sbapt		*q = '\0';
245228545Sbapt
246228545Sbapt		fgr = gr_scan(r);
247228545Sbapt
248228545Sbapt		/* fgr is either a struct group for the current line,
249228545Sbapt		 * or NULL if the line is malformed.
250228545Sbapt		 */
251228545Sbapt
252228545Sbapt		*q = t;
253228545Sbapt		if (fgr == NULL || fgr->gr_gid != sgr->gr_gid) {
254228545Sbapt			/* nope */
255228545Sbapt			if (fgr != NULL)
256228545Sbapt				free(fgr);
257228545Sbapt			if (write(tfd, p, q - p + 1) != q - p + 1)
258228545Sbapt				goto err;
259228545Sbapt			++q;
260228545Sbapt			continue;
261228545Sbapt		}
262228545Sbapt		if (old_gr && !gr_equal(fgr, old_gr)) {
263228545Sbapt			warnx("entry inconsistent");
264228545Sbapt			free(fgr);
265228545Sbapt			errno = EINVAL; /* hack */
266228545Sbapt			goto err;
267228545Sbapt		}
268228545Sbapt		free(fgr);
269228545Sbapt
270228545Sbapt		/* it is, replace or remove it */
271228545Sbapt		if (line != NULL) {
272228545Sbapt			len = strlen(line);
273228545Sbapt			if (write(tfd, line, len) != (int) len)
274228545Sbapt				goto err;
275228545Sbapt		} else {
276228545Sbapt			/* when removed, avoid the \n */
277228545Sbapt			q++;
278228545Sbapt		}
279228545Sbapt		/* we're done, just copy the rest over */
280228545Sbapt		for (;;) {
281228545Sbapt			if (write(tfd, q, end - q) != end - q)
282228545Sbapt				goto err;
283228545Sbapt			q = buf;
284228545Sbapt			readlen = read(ffd, buf, sizeof(buf));
285228545Sbapt			if (readlen == 0)
286228545Sbapt				break;
287228545Sbapt			else
288228545Sbapt				len = (size_t)readlen;
289228545Sbapt			if (readlen == -1)
290228545Sbapt				goto err;
291228545Sbapt			end = buf + len;
292228545Sbapt		}
293228545Sbapt		goto done;
294228545Sbapt	}
295228545Sbapt
296228545Sbapt	/* if we got here, we didn't find the old entry */
297228545Sbapt	if (line == NULL) {
298228545Sbapt		errno = ENOENT;
299228545Sbapt		goto err;
300228545Sbapt	}
301228545Sbapt	len = strlen(line);
302228545Sbapt	if ((size_t)write(tfd, line, len) != len ||
303228545Sbapt	   write(tfd, "\n", 1) != 1)
304228545Sbapt		goto err;
305228545Sbapt done:
306228545Sbapt	if (line != NULL)
307228545Sbapt		free(line);
308228545Sbapt	return (0);
309228545Sbapt err:
310228545Sbapt	if (line != NULL)
311228545Sbapt		free(line);
312228545Sbapt	return (-1);
313228545Sbapt}
314228545Sbapt
315228545Sbapt/*
316228545Sbapt * Regenerate the group file
317228545Sbapt */
318228545Sbaptint
319228545Sbaptgr_mkdb(void)
320228545Sbapt{
321243328Sbapt	int ret;
322243328Sbapt
323243328Sbapt	ret = rename(tempname, group_file);
324243328Sbapt
325243328Sbapt	if (ret == 0)
326243328Sbapt		chmod(group_file, 0644);
327243328Sbapt
328243328Sbapt	return (ret);
329228545Sbapt}
330228545Sbapt
331228545Sbapt/*
332228545Sbapt * Clean up. Preserver errno for the caller's convenience.
333228545Sbapt */
334228545Sbaptvoid
335228545Sbaptgr_fini(void)
336228545Sbapt{
337228545Sbapt	int serrno;
338228545Sbapt
339228545Sbapt	if (!initialized)
340228545Sbapt		return;
341228545Sbapt	initialized = 0;
342228545Sbapt	serrno = errno;
343228545Sbapt	if (*tempname != '\0') {
344228545Sbapt		unlink(tempname);
345228545Sbapt		*tempname = '\0';
346228545Sbapt	}
347228545Sbapt	if (lockfd != -1)
348228545Sbapt		close(lockfd);
349228545Sbapt	errno = serrno;
350228545Sbapt}
351228545Sbapt
352228545Sbapt/*
353178431Sscf * Compares two struct group's.
354178431Sscf */
355178431Sscfint
356178431Sscfgr_equal(const struct group *gr1, const struct group *gr2)
357178431Sscf{
358185237Sscf	int gr1_ndx;
359185237Sscf	int gr2_ndx;
360184831Sscf	bool found;
361178431Sscf
362178431Sscf	/* Check that the non-member information is the same. */
363185237Sscf	if (gr1->gr_name == NULL || gr2->gr_name == NULL) {
364185237Sscf		if (gr1->gr_name != gr2->gr_name)
365185237Sscf			return (false);
366185237Sscf	} else if (strcmp(gr1->gr_name, gr2->gr_name) != 0)
367185237Sscf		return (false);
368185237Sscf	if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) {
369185237Sscf		if (gr1->gr_passwd != gr2->gr_passwd)
370185237Sscf			return (false);
371185237Sscf	} else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0)
372185237Sscf		return (false);
373185237Sscf	if (gr1->gr_gid != gr2->gr_gid)
374185237Sscf		return (false);
375178431Sscf
376178431Sscf	/* Check all members in both groups. */
377185237Sscf	if (gr1->gr_mem == NULL || gr2->gr_mem == NULL) {
378185237Sscf		if (gr1->gr_mem != gr2->gr_mem)
379185237Sscf			return (false);
380185237Sscf	} else {
381185237Sscf		for (found = false, gr1_ndx = 0; gr1->gr_mem[gr1_ndx] != NULL;
382185237Sscf		    gr1_ndx++) {
383185237Sscf			for (gr2_ndx = 0; gr2->gr_mem[gr2_ndx] != NULL;
384185237Sscf			    gr2_ndx++)
385185237Sscf				if (strcmp(gr1->gr_mem[gr1_ndx],
386185237Sscf				    gr2->gr_mem[gr2_ndx]) == 0) {
387178431Sscf					found = true;
388178431Sscf					break;
389178431Sscf				}
390185237Sscf			if (!found)
391185237Sscf				return (false);
392178431Sscf		}
393178431Sscf
394178431Sscf		/* Check that group2 does not have more members than group1. */
395185237Sscf		if (gr2->gr_mem[gr1_ndx] != NULL)
396185237Sscf			return (false);
397178431Sscf	}
398178431Sscf
399185237Sscf	return (true);
400178431Sscf}
401178431Sscf
402178431Sscf/*
403178431Sscf * Make a group line out of a struct group.
404178431Sscf */
405178431Sscfchar *
406178431Sscfgr_make(const struct group *gr)
407178431Sscf{
408178431Sscf	char *line;
409185237Sscf	size_t line_size;
410178431Sscf	int ndx;
411178431Sscf
412178431Sscf	/* Calculate the length of the group line. */
413185237Sscf	line_size = snprintf(NULL, 0, group_line_format, gr->gr_name,
414178431Sscf	    gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1;
415185237Sscf	if (gr->gr_mem != NULL) {
416185237Sscf		for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++)
417185237Sscf			line_size += strlen(gr->gr_mem[ndx]) + 1;
418185237Sscf		if (ndx > 0)
419185237Sscf			line_size--;
420185237Sscf	}
421178431Sscf
422178431Sscf	/* Create the group line and fill it. */
423185237Sscf	if ((line = malloc(line_size)) == NULL)
424178431Sscf		return (NULL);
425200423Sscf	snprintf(line, line_size, group_line_format, gr->gr_name, gr->gr_passwd,
426200423Sscf	    (uintmax_t)gr->gr_gid);
427185237Sscf	if (gr->gr_mem != NULL)
428185237Sscf		for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) {
429185237Sscf			strcat(line, gr->gr_mem[ndx]);
430185237Sscf			if (gr->gr_mem[ndx + 1] != NULL)
431185237Sscf				strcat(line, ",");
432185237Sscf		}
433178431Sscf
434178431Sscf	return (line);
435178431Sscf}
436178431Sscf
437178431Sscf/*
438178431Sscf * Duplicate a struct group.
439178431Sscf */
440178431Sscfstruct group *
441178431Sscfgr_dup(const struct group *gr)
442178431Sscf{
443185237Sscf	char *dst;
444184831Sscf	size_t len;
445185237Sscf	struct group_storage *gs;
446178431Sscf	int ndx;
447185237Sscf	int num_mem;
448178431Sscf
449185237Sscf	/* Calculate size of the group. */
450185237Sscf	len = sizeof(*gs);
451185237Sscf	if (gr->gr_name != NULL)
452185237Sscf		len += strlen(gr->gr_name) + 1;
453185237Sscf	if (gr->gr_passwd != NULL)
454185237Sscf		len += strlen(gr->gr_passwd) + 1;
455178431Sscf	if (gr->gr_mem != NULL) {
456185237Sscf		for (num_mem = 0; gr->gr_mem[num_mem] != NULL; num_mem++)
457185237Sscf			len += strlen(gr->gr_mem[num_mem]) + 1;
458185237Sscf		len += (num_mem + 1) * sizeof(*gr->gr_mem);
459185237Sscf	} else
460185237Sscf		num_mem = -1;
461178431Sscf
462178431Sscf	/* Create new group and copy old group into it. */
463185237Sscf	if ((gs = calloc(1, len)) == NULL)
464178431Sscf		return (NULL);
465185237Sscf	dst = (char *)&gs->members[num_mem + 1];
466178431Sscf	if (gr->gr_name != NULL) {
467185237Sscf		gs->gr.gr_name = dst;
468185237Sscf		dst = stpcpy(gs->gr.gr_name, gr->gr_name) + 1;
469178431Sscf	}
470178431Sscf	if (gr->gr_passwd != NULL) {
471185237Sscf		gs->gr.gr_passwd = dst;
472185237Sscf		dst = stpcpy(gs->gr.gr_passwd, gr->gr_passwd) + 1;
473178431Sscf	}
474185237Sscf	gs->gr.gr_gid = gr->gr_gid;
475178431Sscf	if (gr->gr_mem != NULL) {
476185237Sscf		gs->gr.gr_mem = gs->members;
477185237Sscf		for (ndx = 0; ndx < num_mem; ndx++) {
478185237Sscf			gs->gr.gr_mem[ndx] = dst;
479185237Sscf			dst = stpcpy(gs->gr.gr_mem[ndx], gr->gr_mem[ndx]) + 1;
480178431Sscf		}
481185237Sscf		gs->gr.gr_mem[ndx] = NULL;
482178431Sscf	}
483178431Sscf
484185237Sscf	return (&gs->gr);
485178431Sscf}
486178431Sscf
487178431Sscf/*
488178431Sscf * Scan a line and place it into a group structure.
489178431Sscf */
490178431Sscfstatic bool
491178431Sscf__gr_scan(char *line, struct group *gr)
492178431Sscf{
493178431Sscf	char *loc;
494178431Sscf	int ndx;
495178431Sscf
496178431Sscf	/* Assign non-member information to structure. */
497178431Sscf	gr->gr_name = line;
498178431Sscf	if ((loc = strchr(line, ':')) == NULL)
499178431Sscf		return (false);
500178431Sscf	*loc = '\0';
501178431Sscf	gr->gr_passwd = loc + 1;
502184831Sscf	if (*gr->gr_passwd == ':')
503184831Sscf		*gr->gr_passwd = '\0';
504178431Sscf	else {
505178431Sscf		if ((loc = strchr(loc + 1, ':')) == NULL)
506178431Sscf			return (false);
507178431Sscf		*loc = '\0';
508178431Sscf	}
509184831Sscf	if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1)
510178431Sscf		return (false);
511178431Sscf
512178431Sscf	/* Assign member information to structure. */
513178431Sscf	if ((loc = strchr(loc + 1, ':')) == NULL)
514178431Sscf		return (false);
515178431Sscf	line = loc + 1;
516178431Sscf	gr->gr_mem = NULL;
517185237Sscf	ndx = 0;
518185237Sscf	do {
519185237Sscf		gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) *
520185237Sscf		    (ndx + 1));
521185237Sscf		if (gr->gr_mem == NULL)
522185237Sscf			return (false);
523185237Sscf
524185237Sscf		/* Skip locations without members (i.e., empty string). */
525178431Sscf		do {
526178431Sscf			gr->gr_mem[ndx] = strsep(&line, ",");
527185237Sscf		} while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0');
528185237Sscf	} while (gr->gr_mem[ndx++] != NULL);
529178431Sscf
530178431Sscf	return (true);
531178431Sscf}
532178431Sscf
533178431Sscf/*
534178431Sscf * Create a struct group from a line.
535178431Sscf */
536178431Sscfstruct group *
537178431Sscfgr_scan(const char *line)
538178431Sscf{
539184831Sscf	struct group gr;
540185237Sscf	char *line_copy;
541185237Sscf	struct group *new_gr;
542178431Sscf
543185237Sscf	if ((line_copy = strdup(line)) == NULL)
544178431Sscf		return (NULL);
545185237Sscf	if (!__gr_scan(line_copy, &gr)) {
546185237Sscf		free(line_copy);
547178431Sscf		return (NULL);
548178431Sscf	}
549185237Sscf	new_gr = gr_dup(&gr);
550185237Sscf	free(line_copy);
551178431Sscf	if (gr.gr_mem != NULL)
552178431Sscf		free(gr.gr_mem);
553178431Sscf
554185237Sscf	return (new_gr);
555178431Sscf}
556