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