1/*-
2 * Copyright (c) 1998 Dag-Erling Co��dan Sm��rgrav
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer
10 *    in this position and unchanged.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD: releng/10.3/usr.sbin/chkgrp/chkgrp.c 284994 2015-07-01 07:31:39Z des $");
31
32#include <err.h>
33#include <errno.h>
34#include <ctype.h>
35#include <limits.h>
36#include <stdint.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41#include <sysexits.h>
42
43static void __dead2
44usage(void)
45{
46
47	fprintf(stderr, "usage: chkgrp [-q] [groupfile]\n");
48	exit(EX_USAGE);
49}
50
51int
52main(int argc, char *argv[])
53{
54	FILE *gf;
55	unsigned long gid;
56	unsigned int i;
57	size_t len;
58	int opt, quiet;
59	int n = 0, k, e = 0;
60	const char *cp, *f[4], *gfn, *p;
61	char *line;
62
63	quiet = 0;
64	while ((opt = getopt(argc, argv, "q")) != -1) {
65		switch (opt) {
66		case 'q':
67			quiet = 1;
68			break;
69		default:
70			usage();
71		}
72	}
73
74	argc -= optind;
75	argv += optind;
76
77	if (argc == 0)
78		gfn = "/etc/group";
79	else if (argc == 1)
80		gfn = argv[0];
81	else
82		usage();
83
84	/* open group file */
85	if ((gf = fopen(gfn, "r")) == NULL)
86		err(EX_NOINPUT, "%s", gfn);
87
88	/* check line by line */
89	while (++n) {
90		if ((line = fgetln(gf, &len)) == NULL)
91			break;
92		if (len > 0 && line[len - 1] != '\n') {
93			warnx("%s: line %d: no newline character", gfn, n);
94			e = 1;
95		}
96		while (len && isspace(line[len-1]))
97			len--;
98
99		/* ignore blank lines and comments */
100		for (p = line; p < line + len; p++)
101			if (!isspace(*p)) break;
102		if (!len || *p == '#')
103			continue;
104
105		/*
106		 * Hack: special case for + line
107		 */
108		if (strncmp(line, "+:::", len) == 0)
109			continue;
110
111		/*
112		 * A correct group entry has four colon-separated fields,
113		 * the third of which must be entirely numeric and the
114		 * fourth of which may be empty.
115		 */
116		for (i = k = 0; k < 4; k++) {
117			for (f[k] = line + i; i < len && line[i] != ':'; i++)
118				/* nothing */ ;
119			if (k < 3 && line[i] != ':')
120				break;
121			line[i++] = 0;
122		}
123
124		if (k < 4) {
125			warnx("%s: line %d: missing field(s)", gfn, n);
126			while (k < 4)
127				f[k++] = "";
128			e = 1;
129		}
130
131		for (cp = f[0] ; *cp ; cp++) {
132			if (!isalnum(*cp) && *cp != '.' && *cp != '_' &&
133			    *cp != '-' && (cp > f[0] || *cp != '+')) {
134				warnx("%s: line %d: '%c' invalid character",
135				    gfn, n, *cp);
136				e = 1;
137			}
138		}
139
140		for (cp = f[3] ; *cp ; cp++) {
141			if (!isalnum(*cp) && *cp != '.' && *cp != '_' &&
142			    *cp != '-' && *cp != ',') {
143				warnx("%s: line %d: '%c' invalid character",
144				    gfn, n, *cp);
145				e = 1;
146			}
147		}
148
149		/* check if fourth field ended with a colon */
150		if (i < len) {
151			warnx("%s: line %d: too many fields", gfn, n);
152			e = 1;
153		}
154
155		/* check that none of the fields contain whitespace */
156		for (k = 0; k < 4; k++) {
157			if (strcspn(f[k], " \t") != strlen(f[k])) {
158				warnx("%s: line %d: field %d contains whitespace",
159				    gfn, n, k+1);
160				e = 1;
161			}
162		}
163
164		/* check that the GID is numeric */
165		if (strspn(f[2], "0123456789") != strlen(f[2])) {
166			warnx("%s: line %d: group id is not numeric", gfn, n);
167			e = 1;
168		}
169
170		/* check the range of the group id */
171		errno = 0;
172		gid = strtoul(f[2], NULL, 10);
173		if (errno != 0) {
174			warnx("%s: line %d: strtoul failed", gfn, n);
175		} else if (gid > GID_MAX) {
176			warnx("%s: line %d: group id is too large (%ju > %ju)",
177			    gfn, n, (uintmax_t)gid, (uintmax_t)GID_MAX);
178			e = 1;
179		}
180	}
181
182	/* check what broke the loop */
183	if (ferror(gf))
184		err(EX_IOERR, "%s: line %d", gfn, n);
185
186	/* done */
187	fclose(gf);
188	if (e == 0 && quiet == 0)
189		printf("%s is fine\n", gfn);
190	exit(e ? EX_DATAERR : EX_OK);
191}
192