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