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