xref: /illumos-gate/usr/src/cmd/grpck/grpck.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 
58 int eflag, badchar, baddigit, badlognam, colons, len;
59 static int longnam = 0;
60 int code;
61 
62 #define	MYBUFSIZE (LINE_MAX)	/* max line length including newline and null */
63 #define	NUM_COLONS	3
64 
65 char *buf;
66 char *nptr;
67 char *cptr;
68 FILE *fptr;
69 gid_t gid;
70 void error(char *msg);
71 
72 struct group {
73 	struct group *nxt;
74 	int cnt;
75 	gid_t grp;
76 };
77 
78 struct node {
79 	struct node *next;
80 	int ngroups;
81 	struct group *groups;
82 	char user[1];
83 };
84 
85 void *
86 emalloc(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 
97 int
98 main(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;
362 getnext:
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 
387 void
388 error(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