xref: /freebsd/usr.sbin/ckdist/ckdist.c (revision eba230afba4932f02a1ca44efc797cf7499a5cb0)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 1997 Robert Nordier
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 
33 #include <err.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <fts.h>
37 #include <md5.h>
38 #include <stdio.h>
39 #include <stdint.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 extern int crc(int fd, uint32_t *cval, off_t *clen);
45 
46 #define DISTMD5     1		/* MD5 format */
47 #define DISTINF     2		/* .inf format */
48 #define DISTTYPES   2		/* types supported */
49 
50 #define E_UNKNOWN   1		/* Unknown format */
51 #define E_BADMD5    2		/* Invalid MD5 format */
52 #define E_BADINF    3		/* Invalid .inf format */
53 #define E_NAME      4		/* Can't derive component name */
54 #define E_LENGTH    5		/* Length mismatch */
55 #define E_CHKSUM    6		/* Checksum mismatch */
56 #define E_ERRNO     7		/* sys_errlist[errno] */
57 
58 #define isfatal(err)   ((err) && (err) <= E_NAME)
59 
60 #define NAMESIZE  256           /* filename buffer size */
61 #define MDSUMLEN   32           /* length of MD5 message digest */
62 
63 #define isstdin(path)  ((path)[0] == '-' && !(path)[1])
64 
65 static const char *opt_dir;	/* where to look for components */
66 static const char *opt_name;	/* name for accessing components */
67 static int opt_all;		/* report on all components */
68 static int opt_ignore;		/* ignore missing components */
69 static int opt_recurse;		/* search directories recursively */
70 static int opt_silent;		/* silent about inaccessible files */
71 static int opt_type;		/* dist type: md5 or inf */
72 static int opt_exist;		/* just verify existence */
73 
74 static int ckdist(const char *path, int type);
75 static int chkmd5(FILE * fp, const char *path);
76 static int chkinf(FILE * fp, const char *path);
77 static int report(const char *path, const char *name, int error);
78 static const char *distname(const char *path, const char *name,
79 	                    const char *ext);
80 static const char *stripath(const char *path);
81 static int distfile(const char *path);
82 static int disttype(const char *name);
83 static int fail(const char *path, const char *msg);
84 static void usage(void) __dead2;
85 
86 int
main(int argc,char * argv[])87 main(int argc, char *argv[])
88 {
89     static char *arg[2];
90     struct stat sb;
91     FTS *ftsp;
92     FTSENT *f;
93     int rval, c, type;
94 
95     while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1)
96 	switch (c) {
97 	case 'a':
98 	    opt_all = 1;
99 	    break;
100 	case 'd':
101 	    opt_dir = optarg;
102 	    break;
103 	case 'i':
104 	    opt_ignore = 1;
105 	    break;
106 	case 'n':
107 	    opt_name = optarg;
108 	    break;
109 	case 'r':
110 	    opt_recurse = 1;
111 	    break;
112 	case 's':
113 	    opt_silent = 1;
114 	    break;
115 	case 't':
116 	    if ((opt_type = disttype(optarg)) == 0) {
117 		warnx("illegal argument to -t option");
118 		usage();
119 	    }
120 	    break;
121 	case 'x':
122 	    opt_exist = 1;
123 	    break;
124 	default:
125 	    usage();
126 	}
127     argc -= optind;
128     argv += optind;
129     if (argc < 1)
130 	usage();
131     if (opt_dir) {
132 	if (stat(opt_dir, &sb))
133 	    err(2, "%s", opt_dir);
134 	if (!S_ISDIR(sb.st_mode))
135 	    errx(2, "%s: not a directory", opt_dir);
136     }
137     rval = 0;
138     do {
139 	if (isstdin(*argv))
140 	    rval |= ckdist(*argv, opt_type);
141 	else if (stat(*argv, &sb))
142 	    rval |= fail(*argv, NULL);
143 	else if (S_ISREG(sb.st_mode))
144 	    rval |= ckdist(*argv, opt_type);
145 	else {
146 	    arg[0] = *argv;
147 	    if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL)
148 		err(2, "fts_open");
149 	    while (errno = 0, (f = fts_read(ftsp)) != NULL)
150 		switch (f->fts_info) {
151 		case FTS_DC:
152 		    rval = fail(f->fts_path, "Directory causes a cycle");
153 		    break;
154 		case FTS_DNR:
155 		case FTS_ERR:
156 		case FTS_NS:
157 		    rval = fail(f->fts_path, sys_errlist[f->fts_errno]);
158 		    break;
159 		case FTS_D:
160 		    if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL &&
161 			fts_set(ftsp, f, FTS_SKIP))
162 			err(2, "fts_set");
163 		    break;
164 		case FTS_F:
165 		    if ((type = distfile(f->fts_name)) != 0 &&
166 			(!opt_type || type == opt_type))
167 			rval |= ckdist(f->fts_path, type);
168 		    break;
169                 default: ;
170 		}
171 	    if (errno)
172 		err(2, "fts_read");
173 	    if (fts_close(ftsp))
174 		err(2, "fts_close");
175 	}
176     } while (*++argv);
177     return rval;
178 }
179 
180 static int
ckdist(const char * path,int type)181 ckdist(const char *path, int type)
182 {
183     FILE *fp;
184     int rval, c;
185 
186     if (isstdin(path)) {
187 	path = "(stdin)";
188 	fp = stdin;
189     } else if ((fp = fopen(path, "r")) == NULL)
190 	return fail(path, NULL);
191     if (!type) {
192 	if (fp != stdin)
193 	    type = distfile(path);
194 	if (!type)
195 	    if ((c = fgetc(fp)) != EOF) {
196 		type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0;
197 		(void)ungetc(c, fp);
198 	    }
199     }
200     switch (type) {
201     case DISTMD5:
202 	rval = chkmd5(fp, path);
203 	break;
204     case DISTINF:
205 	rval = chkinf(fp, path);
206 	break;
207     default:
208 	rval = report(path, NULL, E_UNKNOWN);
209     }
210     if (ferror(fp))
211 	warn("%s", path);
212     if (fp != stdin && fclose(fp))
213 	err(2, "%s", path);
214     return rval;
215 }
216 
217 static int
chkmd5(FILE * fp,const char * path)218 chkmd5(FILE * fp, const char *path)
219 {
220     char buf[298];              /* "MD5 (NAMESIZE = MDSUMLEN" */
221     char name[NAMESIZE + 1];
222     char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1];
223     const char *dname;
224     char *s;
225     int rval, error, c, fd;
226     char ch;
227 
228     rval = 0;
229     while (fgets(buf, sizeof(buf), fp)) {
230 	dname = NULL;
231 	error = 0;
232 	if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum,
233 			 &ch)) != 3 && (!feof(fp) || c != 2)) ||
234 	    (c == 3 && ch != '\n') ||
235 	    (s = strrchr(name, ')')) == NULL ||
236 	    strlen(sum) != MDSUMLEN)
237 	    error = E_BADMD5;
238 	else {
239 	    *s = 0;
240 	    if ((dname = distname(path, name, NULL)) == NULL)
241 		error = E_NAME;
242 	    else if (opt_exist) {
243 		if ((fd = open(dname, O_RDONLY)) == -1)
244 		    error = E_ERRNO;
245 		else if (close(fd))
246 		    err(2, "%s", dname);
247 	    } else if (!MD5File(dname, chk))
248 		error = E_ERRNO;
249 	    else if (strcmp(chk, sum))
250 		error = E_CHKSUM;
251 	}
252 	if (opt_ignore && error == E_ERRNO && errno == ENOENT)
253 	    continue;
254 	if (error || opt_all)
255 	    rval |= report(path, dname, error);
256 	if (isfatal(error))
257 	    break;
258     }
259     return rval;
260 }
261 
262 static int
chkinf(FILE * fp,const char * path)263 chkinf(FILE * fp, const char *path)
264 {
265     char buf[30];               /* "cksum.2 = 10 6" */
266     char ext[3];
267     struct stat sb;
268     const char *dname;
269     off_t len;
270     u_long sum;
271     intmax_t sumlen;
272     uint32_t chk;
273     int rval, error, c, pieces, cnt, fd;
274     char ch;
275 
276     rval = 0;
277     for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) {
278 	fd = -1;
279 	dname = NULL;
280 	error = 0;
281 	if (cnt == -1) {
282 	    if ((c = sscanf(buf, "Pieces =  %d%c", &pieces, &ch)) != 2 ||
283 		ch != '\n' || pieces < 1)
284 		error = E_BADINF;
285 	} else if (((c = sscanf(buf, "cksum.%2s = %lu %jd%c", ext, &sum,
286 			        &sumlen, &ch)) != 4 &&
287 		    (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') ||
288 		   ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26)
289 	    error = E_BADINF;
290 	else if ((dname = distname(fp == stdin ? NULL : path, NULL,
291 				    ext)) == NULL)
292 	    error = E_NAME;
293 	else if ((fd = open(dname, O_RDONLY)) == -1)
294 	    error = E_ERRNO;
295 	else if (fstat(fd, &sb))
296 	    error = E_ERRNO;
297 	else if (sb.st_size != (off_t)sumlen)
298 	    error = E_LENGTH;
299 	else if (!opt_exist) {
300 	    if (crc(fd, &chk, &len))
301 		error = E_ERRNO;
302 	    else if (chk != sum)
303 		error = E_CHKSUM;
304 	}
305 	if (fd != -1 && close(fd))
306 	    err(2, "%s", dname);
307 	if (opt_ignore && error == E_ERRNO && errno == ENOENT)
308 	    continue;
309 	if (error || (opt_all && cnt >= 0))
310 	    rval |= report(path, dname, error);
311 	if (isfatal(error))
312 	    break;
313     }
314     return rval;
315 }
316 
317 static int
report(const char * path,const char * name,int error)318 report(const char *path, const char *name, int error)
319 {
320     if (name)
321 	name = stripath(name);
322     switch (error) {
323     case E_UNKNOWN:
324 	printf("%s: Unknown format\n", path);
325 	break;
326     case E_BADMD5:
327 	printf("%s: Invalid MD5 format\n", path);
328 	break;
329     case E_BADINF:
330 	printf("%s: Invalid .inf format\n", path);
331 	break;
332     case E_NAME:
333 	printf("%s: Can't derive component name\n", path);
334 	break;
335     case E_LENGTH:
336 	printf("%s: %s: Size mismatch\n", path, name);
337 	break;
338     case E_CHKSUM:
339 	printf("%s: %s: Checksum mismatch\n", path, name);
340 	break;
341     case E_ERRNO:
342 	printf("%s: %s: %s\n", path, name, sys_errlist[errno]);
343 	break;
344     default:
345 	printf("%s: %s: OK\n", path, name);
346     }
347     return error != 0;
348 }
349 
350 static const char *
distname(const char * path,const char * name,const char * ext)351 distname(const char *path, const char *name, const char *ext)
352 {
353     static char buf[NAMESIZE];
354     size_t plen, nlen;
355     char *s;
356 
357     if (opt_name)
358 	name = opt_name;
359     else if (!name) {
360 	if (!path)
361 	    return NULL;
362 	name = stripath(path);
363     }
364     nlen = strlen(name);
365     if (ext && nlen > 4 && name[nlen - 4] == '.' &&
366 	disttype(name + nlen - 3) == DISTINF)
367 	nlen -= 4;
368     if (opt_dir) {
369 	path = opt_dir;
370 	plen = strlen(path);
371     } else
372 	plen = path && (s = strrchr(path, '/')) != NULL ?
373             (size_t)(s - path) : 0;
374     if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf))
375 	return NULL;
376     s = buf;
377     if (plen) {
378 	memcpy(s, path, plen);
379 	s += plen;
380 	*s++ = '/';
381     }
382     memcpy(s, name, nlen);
383     s += nlen;
384     if (ext) {
385 	*s++ = '.';
386 	memcpy(s, ext, 2);
387 	s += 2;
388     }
389     *s = 0;
390     return buf;
391 }
392 
393 static const char *
stripath(const char * path)394 stripath(const char *path)
395 {
396     const char *s;
397 
398     return ((s = strrchr(path, '/')) != NULL && s[1] ?
399 		    s + 1 : path);
400 }
401 
402 static int
distfile(const char * path)403 distfile(const char *path)
404 {
405     const char *s;
406     int type;
407 
408     if ((type = disttype(path)) == DISTMD5 ||
409 	((s = strrchr(path, '.')) != NULL && s > path &&
410 	 (type = disttype(s + 1)) != 0))
411 	return type;
412     return 0;
413 }
414 
415 static int
disttype(const char * name)416 disttype(const char *name)
417 {
418     static const char dname[DISTTYPES][4] = {"md5", "inf"};
419     int i;
420 
421     for (i = 0; i < DISTTYPES; i++)
422 	if (!strcmp(dname[i], name))
423 	    return 1 + i;
424     return 0;
425 }
426 
427 static int
fail(const char * path,const char * msg)428 fail(const char *path, const char *msg)
429 {
430     if (opt_silent)
431 	return 0;
432     warnx("%s: %s", path, msg ? msg : sys_errlist[errno]);
433     return 2;
434 }
435 
436 static void
usage(void)437 usage(void)
438 {
439     fprintf(stderr,
440 	    "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n");
441     exit(2);
442 }
443