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