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