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 <stdio.h>
40 #include <stdint.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <openssl/md5.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
main(int argc,char * argv[])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
ckdist(const char * path,int type)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 char *
md5_file(const char * filename,char * const buf)220 md5_file(const char *filename, char * const buf)
221 {
222 unsigned char digest[MD5_DIGEST_LENGTH];
223 static const char hex[]="0123456789abcdef";
224 MD5_CTX ctx;
225 unsigned char buffer[4096];
226 struct stat st;
227 off_t size;
228 int fd, bytes, i;
229
230 if (!buf)
231 return NULL;
232
233 fd = open(filename, O_RDONLY);
234 if (fd < 0)
235 return NULL;
236 if (fstat(fd, &st) < 0) {
237 bytes = -1;
238 goto err;
239 }
240
241 MD5_Init(&ctx);
242 size = st.st_size;
243 bytes = 0;
244 while (size > 0) {
245 if ((size_t)size > sizeof(buffer))
246 bytes = read(fd, buffer, sizeof(buffer));
247 else
248 bytes = read(fd, buffer, size);
249 if (bytes < 0)
250 break;
251 MD5_Update(&ctx, buffer, bytes);
252 size -= bytes;
253 }
254
255 err:
256 close(fd);
257 if (bytes < 0)
258 return NULL;
259
260 MD5_Final(digest, &ctx);
261 for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
262 buf[2*i] = hex[digest[i] >> 4];
263 buf[2*i+1] = hex[digest[i] & 0x0f];
264 }
265 buf[MD5_DIGEST_LENGTH * 2] = '\0';
266
267 return buf;
268 }
269
270 static int
chkmd5(FILE * fp,const char * path)271 chkmd5(FILE * fp, const char *path)
272 {
273 char buf[298]; /* "MD5 (NAMESIZE = MDSUMLEN" */
274 char name[NAMESIZE + 1];
275 char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1];
276 const char *dname;
277 char *s;
278 int rval, error, c, fd;
279 char ch;
280
281 rval = 0;
282 while (fgets(buf, sizeof(buf), fp)) {
283 dname = NULL;
284 error = 0;
285 if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum,
286 &ch)) != 3 && (!feof(fp) || c != 2)) ||
287 (c == 3 && ch != '\n') ||
288 (s = strrchr(name, ')')) == NULL ||
289 strlen(sum) != MDSUMLEN)
290 error = E_BADMD5;
291 else {
292 *s = 0;
293 if ((dname = distname(path, name, NULL)) == NULL)
294 error = E_NAME;
295 else if (opt_exist) {
296 if ((fd = open(dname, O_RDONLY)) == -1)
297 error = E_ERRNO;
298 else if (close(fd))
299 err(2, "%s", dname);
300 } else if (!md5_file(dname, chk))
301 error = E_ERRNO;
302 else if (strcmp(chk, sum))
303 error = E_CHKSUM;
304 }
305 if (opt_ignore && error == E_ERRNO && errno == ENOENT)
306 continue;
307 if (error || opt_all)
308 rval |= report(path, dname, error);
309 if (isfatal(error))
310 break;
311 }
312 return rval;
313 }
314
315 static int
chkinf(FILE * fp,const char * path)316 chkinf(FILE * fp, const char *path)
317 {
318 char buf[30]; /* "cksum.2 = 10 6" */
319 char ext[3];
320 struct stat sb;
321 const char *dname;
322 off_t len;
323 u_long sum;
324 intmax_t sumlen;
325 uint32_t chk;
326 int rval, error, c, pieces, cnt, fd;
327 char ch;
328
329 rval = 0;
330 for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) {
331 fd = -1;
332 dname = NULL;
333 error = 0;
334 if (cnt == -1) {
335 if ((c = sscanf(buf, "Pieces = %d%c", &pieces, &ch)) != 2 ||
336 ch != '\n' || pieces < 1)
337 error = E_BADINF;
338 } else if (((c = sscanf(buf, "cksum.%2s = %lu %jd%c", ext, &sum,
339 &sumlen, &ch)) != 4 &&
340 (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') ||
341 ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26)
342 error = E_BADINF;
343 else if ((dname = distname(fp == stdin ? NULL : path, NULL,
344 ext)) == NULL)
345 error = E_NAME;
346 else if ((fd = open(dname, O_RDONLY)) == -1)
347 error = E_ERRNO;
348 else if (fstat(fd, &sb))
349 error = E_ERRNO;
350 else if (sb.st_size != (off_t)sumlen)
351 error = E_LENGTH;
352 else if (!opt_exist) {
353 if (crc(fd, &chk, &len))
354 error = E_ERRNO;
355 else if (chk != sum)
356 error = E_CHKSUM;
357 }
358 if (fd != -1 && close(fd))
359 err(2, "%s", dname);
360 if (opt_ignore && error == E_ERRNO && errno == ENOENT)
361 continue;
362 if (error || (opt_all && cnt >= 0))
363 rval |= report(path, dname, error);
364 if (isfatal(error))
365 break;
366 }
367 return rval;
368 }
369
370 static int
report(const char * path,const char * name,int error)371 report(const char *path, const char *name, int error)
372 {
373 if (name)
374 name = stripath(name);
375 switch (error) {
376 case E_UNKNOWN:
377 printf("%s: Unknown format\n", path);
378 break;
379 case E_BADMD5:
380 printf("%s: Invalid MD5 format\n", path);
381 break;
382 case E_BADINF:
383 printf("%s: Invalid .inf format\n", path);
384 break;
385 case E_NAME:
386 printf("%s: Can't derive component name\n", path);
387 break;
388 case E_LENGTH:
389 printf("%s: %s: Size mismatch\n", path, name);
390 break;
391 case E_CHKSUM:
392 printf("%s: %s: Checksum mismatch\n", path, name);
393 break;
394 case E_ERRNO:
395 printf("%s: %s: %s\n", path, name, sys_errlist[errno]);
396 break;
397 default:
398 printf("%s: %s: OK\n", path, name);
399 }
400 return error != 0;
401 }
402
403 static const char *
distname(const char * path,const char * name,const char * ext)404 distname(const char *path, const char *name, const char *ext)
405 {
406 static char buf[NAMESIZE];
407 size_t plen, nlen;
408 char *s;
409
410 if (opt_name)
411 name = opt_name;
412 else if (!name) {
413 if (!path)
414 return NULL;
415 name = stripath(path);
416 }
417 nlen = strlen(name);
418 if (ext && nlen > 4 && name[nlen - 4] == '.' &&
419 disttype(name + nlen - 3) == DISTINF)
420 nlen -= 4;
421 if (opt_dir) {
422 path = opt_dir;
423 plen = strlen(path);
424 } else
425 plen = path && (s = strrchr(path, '/')) != NULL ?
426 (size_t)(s - path) : 0;
427 if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf))
428 return NULL;
429 s = buf;
430 if (plen) {
431 memcpy(s, path, plen);
432 s += plen;
433 *s++ = '/';
434 }
435 memcpy(s, name, nlen);
436 s += nlen;
437 if (ext) {
438 *s++ = '.';
439 memcpy(s, ext, 2);
440 s += 2;
441 }
442 *s = 0;
443 return buf;
444 }
445
446 static const char *
stripath(const char * path)447 stripath(const char *path)
448 {
449 const char *s;
450
451 return ((s = strrchr(path, '/')) != NULL && s[1] ?
452 s + 1 : path);
453 }
454
455 static int
distfile(const char * path)456 distfile(const char *path)
457 {
458 const char *s;
459 int type;
460
461 if ((type = disttype(path)) == DISTMD5 ||
462 ((s = strrchr(path, '.')) != NULL && s > path &&
463 (type = disttype(s + 1)) != 0))
464 return type;
465 return 0;
466 }
467
468 static int
disttype(const char * name)469 disttype(const char *name)
470 {
471 static const char dname[DISTTYPES][4] = {"md5", "inf"};
472 int i;
473
474 for (i = 0; i < DISTTYPES; i++)
475 if (!strcmp(dname[i], name))
476 return 1 + i;
477 return 0;
478 }
479
480 static int
fail(const char * path,const char * msg)481 fail(const char *path, const char *msg)
482 {
483 if (opt_silent)
484 return 0;
485 warnx("%s: %s", path, msg ? msg : sys_errlist[errno]);
486 return 2;
487 }
488
489 static void
usage(void)490 usage(void)
491 {
492 fprintf(stderr,
493 "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n");
494 exit(2);
495 }
496