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 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 char * 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 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 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 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 * 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 * 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 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 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 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 490 usage(void) 491 { 492 fprintf(stderr, 493 "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n"); 494 exit(2); 495 } 496