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 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 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 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 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 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 * 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 * 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 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 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 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 442 usage(void) 443 { 444 fprintf(stderr, 445 "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n"); 446 exit(2); 447 } 448