1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27/*	  All Rights Reserved	*/
28
29#include <sys/param.h>
30#include <sys/types.h>
31#include <unistd.h>
32#include <stdlib.h>
33#include <stdio.h>
34#include <string.h>
35#include <ctype.h>
36#include <pwd.h>
37#include <errno.h>
38#include <locale.h>
39#include <limits.h>
40
41#define	BADLINE "Too many/few fields"
42#define	TOOLONG "Line too long"
43#define	NONAME	"No group name"
44#define	BADNAME "Bad character(s) in group name"
45#define	BADGID  "Invalid GID"
46#define	NULLNAME "Null login name"
47#define	NOTFOUND "Logname not found in password file"
48#define	DUPNAME "Duplicate logname entry"
49#define	DUPNAME2 "Duplicate logname entry (gid first occurs in passwd entry)"
50#define	NOMEM	"Out of memory"
51#define	NGROUPS	"Maximum groups exceeded for logname "
52#define	BLANKLINE "Blank line detected. Please remove line"
53#define	LONGNAME  "Group name too long"
54
55int eflag, badchar, baddigit, badlognam, colons, len;
56static int longnam = 0;
57int code;
58
59#define	MYBUFSIZE (LINE_MAX)	/* max line length including newline and null */
60#define	NUM_COLONS	3
61
62char *buf;
63char *nptr;
64char *cptr;
65FILE *fptr;
66gid_t gid;
67void error(char *msg);
68
69struct group {
70	struct group *nxt;
71	int cnt;
72	gid_t grp;
73};
74
75struct node {
76	struct node *next;
77	int ngroups;
78	struct group *groups;
79	char user[1];
80};
81
82void *
83emalloc(size_t size)
84{
85	void *vp;
86	vp = malloc(size);
87	if (vp == NULL) {
88		fprintf(stderr, "%s\n", gettext(NOMEM));
89		exit(1);
90	}
91	return (vp);
92}
93
94int
95main(int argc, char *argv[])
96{
97	struct passwd *pwp;
98	struct node *root = NULL;
99	struct node *t;
100	struct group *gp;
101	int ngroups_max;
102	int ngroups = 0;
103	int listlen;
104	int i;
105	int lineno = 0;
106	char *buf_off, *tmpbuf;
107	int delim[NUM_COLONS + 1], buf_len, bufsize;
108
109	(void) setlocale(LC_ALL, "");
110
111#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
112#define	TEXT_DOMAIN "SYS_TEST"
113#endif
114	(void) textdomain(TEXT_DOMAIN);
115
116	code = 0;
117	ngroups_max = sysconf(_SC_NGROUPS_MAX);
118
119	if (argc == 1)
120		argv[1] = "/etc/group";
121	else if (argc != 2) {
122		fprintf(stderr, gettext("usage: %s filename\n"), *argv);
123		exit(1);
124	}
125
126	if ((fptr = fopen(argv[1], "r")) == NULL) {
127		fprintf(stderr, gettext("cannot open file %s: %s\n"), argv[1],
128		    strerror(errno));
129		exit(1);
130	}
131
132#ifdef ORIG_SVR4
133	while ((pwp = getpwent()) != NULL) {
134		t = (struct node *)emalloc(sizeof (*t) + strlen(pwp->pw_name));
135		t->next = root;
136		root = t;
137		strcpy(t->user, pwp->pw_name);
138		t->ngroups = 1;
139		if (!ngroups_max)
140			t->groups = NULL;
141		else {
142			t->groups = (struct group *)
143			    emalloc(sizeof (struct group));
144			t->groups->grp = pwp->pw_gid;
145			t->groups->cnt = 1;
146			t->groups->nxt = NULL;
147		}
148	}
149#endif
150
151	bufsize = MYBUFSIZE;
152	if ((buf = malloc(bufsize)) == NULL) {
153		(void) fprintf(stderr, gettext(NOMEM));
154		exit(1);
155	}
156	while (!feof(fptr) && !ferror(fptr)) {
157		buf_len = 0;
158		buf_off = buf;
159		while (fgets(buf_off, (bufsize - buf_len), fptr) != NULL) {
160			buf_len += strlen(buf_off);
161			if (buf[buf_len - 1] == '\n' || feof(fptr))
162				break;
163			tmpbuf = realloc(buf, (bufsize + MYBUFSIZE));
164			if (tmpbuf == NULL) {
165				(void) fprintf(stderr, gettext(NOMEM));
166				exit(1);
167			}
168			bufsize += MYBUFSIZE;
169			buf = tmpbuf;
170			buf_off = buf + buf_len;
171		}
172		if (buf_len == 0)
173			continue;
174
175		/* Report error to be consistent with libc */
176		if ((buf_len + 1) > LINE_MAX)
177			error(TOOLONG);
178
179		lineno++;
180		if (buf[0] == '\n')    /* blank lines are ignored */
181		{
182			code = 1;		/* exit with error code = 1 */
183			eflag = 0;	/* force print of "blank" line */
184			fprintf(stderr, "\n%s %d\n", gettext(BLANKLINE),
185			    lineno);
186			continue;
187		}
188
189		if (buf[buf_len - 1] == '\n') {
190			if ((tmpbuf = strdup(buf)) == NULL) {
191				(void) fprintf(stderr, gettext(NOMEM));
192				exit(1);
193			}
194			tmpbuf[buf_len - 1] = ',';
195		} else {
196			if ((tmpbuf = malloc(buf_len + 2)) == NULL) {
197				(void) fprintf(stderr, gettext(NOMEM));
198				exit(1);
199			}
200			(void) strcpy(tmpbuf, buf);
201			tmpbuf[buf_len++] = ',';
202			tmpbuf[buf_len] = '\0';
203		}
204
205		colons = 0;
206		eflag = 0;
207		badchar = 0;
208		baddigit = 0;
209		badlognam = 0;
210		gid = 0;
211
212		ngroups++;	/* Increment number of groups found */
213		/* Check that entry is not a nameservice redirection */
214
215		if (buf[0] == '+' || buf[0] == '-')  {
216			/*
217			 * Should set flag here to allow special case checking
218			 * in the rest of the code,
219			 * but for now, we'll just ignore this entry.
220			 */
221			free(tmpbuf);
222			continue;
223		}
224
225		/*	Check number of fields	*/
226
227		for (i = 0; buf[i] != '\0'; i++) {
228			if (buf[i] == ':') {
229				delim[colons] = i;
230				if (++colons > NUM_COLONS)
231					break;
232			}
233		}
234		if (colons != NUM_COLONS) {
235			error(BADLINE);
236			free(tmpbuf);
237			continue;
238		}
239
240		/* check to see that group name is at least 1 character	*/
241		/* and that all characters are lowrcase or digits.	*/
242
243		if (buf[0] == ':')
244			error(NONAME);
245		else {
246			for (i = 0; buf[i] != ':'; i++) {
247				if (i >= LOGNAME_MAX)
248					longnam++;
249				if (!(islower(buf[i]) || isdigit(buf[i])))
250					badchar++;
251			}
252			if (longnam > 0)
253				error(LONGNAME);
254			if (badchar > 0)
255				error(BADNAME);
256		}
257
258		/*	check that GID is numeric and <= 31 bits	*/
259
260		len = (delim[2] - delim[1]) - 1;
261
262		if (len > 10 || len < 1)
263			error(BADGID);
264		else {
265			for (i = (delim[1]+1); i < delim[2]; i++) {
266				if (! (isdigit(buf[i])))
267					baddigit++;
268				else if (baddigit == 0)
269					gid = gid * 10 + (gid_t)(buf[i] - '0');
270				/* converts ascii GID to decimal */
271			}
272			if (baddigit > 0)
273				error(BADGID);
274			else if (gid > (gid_t)MAXUID)
275				error(BADGID);
276		}
277
278		/*  check that logname appears in the passwd file  */
279
280		nptr = &tmpbuf[delim[2]];
281		nptr++;
282
283		listlen = strlen(nptr) - 1;
284
285		while ((cptr = strchr(nptr, ',')) != NULL) {
286			*cptr = '\0';
287			if (*nptr == '\0') {
288				if (listlen)
289					error(NULLNAME);
290				nptr++;
291				continue;
292			}
293
294			for (t = root; t != NULL; t = t->next) {
295				if (strcmp(t->user, nptr) == 0)
296					break;
297			}
298			if (t == NULL) {
299#ifndef ORIG_SVR4
300				/*
301				 * User entry not found, so check if in
302				 *  password file
303				 */
304				struct passwd *pwp;
305
306				if ((pwp = getpwnam(nptr)) == NULL) {
307#endif
308					badlognam++;
309					error(NOTFOUND);
310					goto getnext;
311#ifndef ORIG_SVR4
312				}
313
314				/* Usrname found, so add entry to user-list */
315				t = (struct node *)
316				    emalloc(sizeof (*t) + strlen(nptr));
317				t->next = root;
318				root = t;
319				strcpy(t->user, nptr);
320				t->ngroups = 1;
321				if (!ngroups_max)
322					t->groups = NULL;
323				else {
324					t->groups = (struct group *)
325					    emalloc(sizeof (struct group));
326					t->groups->grp = pwp->pw_gid;
327					t->groups->cnt = 1;
328					t->groups->nxt = NULL;
329				}
330			}
331#endif
332			if (!ngroups_max)
333				goto getnext;
334
335			t->ngroups++;
336
337			/*
338			 * check for duplicate logname in group
339			 */
340
341			for (gp = t->groups; gp != NULL; gp = gp->nxt) {
342				if (gid == gp->grp) {
343					if (gp->cnt++ == 1) {
344						badlognam++;
345						if (gp->nxt == NULL)
346							error(DUPNAME2);
347						else
348							error(DUPNAME);
349					}
350					goto getnext;
351				}
352			}
353
354			gp = (struct group *)emalloc(sizeof (struct group));
355			gp->grp = gid;
356			gp->cnt = 1;
357			gp->nxt = t->groups;
358			t->groups = gp;
359getnext:
360			nptr = ++cptr;
361		}
362		free(tmpbuf);
363	}
364
365	if (ngroups == 0) {
366		fprintf(stderr, gettext("Group file '%s' is empty\n"), argv[1]);
367		code = 1;
368	}
369
370	if (ngroups_max) {
371		for (t = root; t != NULL; t = t->next) {
372			if (t->ngroups > ngroups_max) {
373				fprintf(stderr, "\n\n%s%s (%d)\n",
374				    NGROUPS, t->user, t->ngroups);
375				code = 1;
376			}
377		}
378	}
379	return (code);
380}
381
382/*	Error printing routine	*/
383
384void
385error(char *msg)
386{
387	code = 1;
388	if (eflag == 0) {
389		fprintf(stderr, "\n\n%s", buf);
390		eflag = 1;
391	}
392	if (longnam != 0) {
393		fprintf(stderr, "\t%s\n", gettext(msg));
394		longnam = 0;
395		return;
396	}
397	if (badchar != 0) {
398		fprintf(stderr, "\t%d %s\n", badchar, gettext(msg));
399		badchar = 0;
400		return;
401	} else if (baddigit != 0) {
402		fprintf(stderr, "\t%s\n", gettext(msg));
403		baddigit = 0;
404		return;
405	} else if (badlognam != 0) {
406		fprintf(stderr, "\t%s - %s\n", nptr, gettext(msg));
407		badlognam = 0;
408		return;
409	} else {
410		fprintf(stderr, "\t%s\n", gettext(msg));
411		return;
412	}
413}
414