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 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 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 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 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 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 * 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 * 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 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 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 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 437 usage(void) 438 { 439 fprintf(stderr, 440 "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n"); 441 exit(2); 442 } 443