1 /* 2 * Copyright (c) 1997 Robert Nordier 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in 12 * the documentation and/or other materials provided with the 13 * distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS 16 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY 19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 21 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 23 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 25 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #ifndef lint 29 static const char rcsid[] = 30 "$FreeBSD$"; 31 #endif /* not lint */ 32 33 #include <sys/types.h> 34 #include <sys/stat.h> 35 36 #include <err.h> 37 #include <errno.h> 38 #include <fcntl.h> 39 #include <fts.h> 40 #include <md5.h> 41 #include <stdio.h> 42 #include <stdint.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 47 extern int crc(int fd, uint32_t *cval, off_t *clen); 48 49 #define DISTMD5 1 /* MD5 format */ 50 #define DISTINF 2 /* .inf format */ 51 #define DISTTYPES 2 /* types supported */ 52 53 #define E_UNKNOWN 1 /* Unknown format */ 54 #define E_BADMD5 2 /* Invalid MD5 format */ 55 #define E_BADINF 3 /* Invalid .inf format */ 56 #define E_NAME 4 /* Can't derive component name */ 57 #define E_LENGTH 5 /* Length mismatch */ 58 #define E_CHKSUM 6 /* Checksum mismatch */ 59 #define E_ERRNO 7 /* sys_errlist[errno] */ 60 61 #define isfatal(err) ((err) && (err) <= E_NAME) 62 63 #define NAMESIZE 256 /* filename buffer size */ 64 #define MDSUMLEN 32 /* length of MD5 message digest */ 65 66 #define isstdin(path) ((path)[0] == '-' && !(path)[1]) 67 68 static const char *opt_dir; /* where to look for components */ 69 static const char *opt_name; /* name for accessing components */ 70 static int opt_all; /* report on all components */ 71 static int opt_ignore; /* ignore missing components */ 72 static int opt_recurse; /* search directories recursively */ 73 static int opt_silent; /* silent about inaccessible files */ 74 static int opt_type; /* dist type: md5 or inf */ 75 static int opt_exist; /* just verify existence */ 76 77 static int ckdist(const char *path, int type); 78 static int chkmd5(FILE * fp, const char *path); 79 static int chkinf(FILE * fp, const char *path); 80 static int report(const char *path, const char *name, int error); 81 static const char *distname(const char *path, const char *name, 82 const char *ext); 83 static const char *stripath(const char *path); 84 static int distfile(const char *path); 85 static int disttype(const char *name); 86 static int fail(const char *path, const char *msg); 87 static void usage(void); 88 89 int 90 main(int argc, char *argv[]) 91 { 92 static char *arg[2]; 93 struct stat sb; 94 FTS *ftsp; 95 FTSENT *f; 96 int rval, c, type; 97 98 while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1) 99 switch (c) { 100 case 'a': 101 opt_all = 1; 102 break; 103 case 'd': 104 opt_dir = optarg; 105 break; 106 case 'i': 107 opt_ignore = 1; 108 break; 109 case 'n': 110 opt_name = optarg; 111 break; 112 case 'r': 113 opt_recurse = 1; 114 break; 115 case 's': 116 opt_silent = 1; 117 break; 118 case 't': 119 if ((opt_type = disttype(optarg)) == 0) { 120 warnx("illegal argument to -t option"); 121 usage(); 122 } 123 break; 124 case 'x': 125 opt_exist = 1; 126 break; 127 default: 128 usage(); 129 } 130 argc -= optind; 131 argv += optind; 132 if (argc < 1) 133 usage(); 134 if (opt_dir) { 135 if (stat(opt_dir, &sb)) 136 err(2, "%s", opt_dir); 137 if (!S_ISDIR(sb.st_mode)) 138 errx(2, "%s: not a directory", opt_dir); 139 } 140 rval = 0; 141 do { 142 if (isstdin(*argv)) 143 rval |= ckdist(*argv, opt_type); 144 else if (stat(*argv, &sb)) 145 rval |= fail(*argv, NULL); 146 else if (S_ISREG(sb.st_mode)) 147 rval |= ckdist(*argv, opt_type); 148 else { 149 arg[0] = *argv; 150 if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL) 151 err(2, "fts_open"); 152 while ((f = fts_read(ftsp)) != NULL) 153 switch (f->fts_info) { 154 case FTS_DC: 155 rval = fail(f->fts_path, "Directory causes a cycle"); 156 break; 157 case FTS_DNR: 158 case FTS_ERR: 159 case FTS_NS: 160 rval = fail(f->fts_path, sys_errlist[f->fts_errno]); 161 break; 162 case FTS_D: 163 if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL && 164 fts_set(ftsp, f, FTS_SKIP)) 165 err(2, "fts_set"); 166 break; 167 case FTS_F: 168 if ((type = distfile(f->fts_name)) != 0 && 169 (!opt_type || type == opt_type)) 170 rval |= ckdist(f->fts_path, type); 171 break; 172 default: ; 173 } 174 if (errno) 175 err(2, "fts_read"); 176 if (fts_close(ftsp)) 177 err(2, "fts_close"); 178 } 179 } while (*++argv); 180 return rval; 181 } 182 183 static int 184 ckdist(const char *path, int type) 185 { 186 FILE *fp; 187 int rval, c; 188 189 if (isstdin(path)) { 190 path = "(stdin)"; 191 fp = stdin; 192 } else if ((fp = fopen(path, "r")) == NULL) 193 return fail(path, NULL); 194 if (!type) { 195 if (fp != stdin) 196 type = distfile(path); 197 if (!type) 198 if ((c = fgetc(fp)) != EOF) { 199 type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0; 200 (void)ungetc(c, fp); 201 } 202 } 203 switch (type) { 204 case DISTMD5: 205 rval = chkmd5(fp, path); 206 break; 207 case DISTINF: 208 rval = chkinf(fp, path); 209 break; 210 default: 211 rval = report(path, NULL, E_UNKNOWN); 212 } 213 if (ferror(fp)) 214 warn("%s", path); 215 if (fp != stdin && fclose(fp)) 216 err(2, "%s", path); 217 return rval; 218 } 219 220 static int 221 chkmd5(FILE * fp, const char *path) 222 { 223 char buf[298]; /* "MD5 (NAMESIZE = MDSUMLEN" */ 224 char name[NAMESIZE + 1]; 225 char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1]; 226 const char *dname; 227 char *s; 228 int rval, error, c, fd; 229 char ch; 230 231 rval = 0; 232 while (fgets(buf, sizeof(buf), fp)) { 233 dname = NULL; 234 error = 0; 235 if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum, 236 &ch)) != 3 && (!feof(fp) || c != 2)) || 237 (c == 3 && ch != '\n') || 238 (s = strrchr(name, ')')) == NULL || 239 strlen(sum) != MDSUMLEN) 240 error = E_BADMD5; 241 else { 242 *s = 0; 243 if ((dname = distname(path, name, NULL)) == NULL) 244 error = E_NAME; 245 else if (opt_exist) { 246 if ((fd = open(dname, O_RDONLY)) == -1) 247 error = E_ERRNO; 248 else if (close(fd)) 249 err(2, "%s", dname); 250 } else if (!MD5File(dname, chk)) 251 error = E_ERRNO; 252 else if (strcmp(chk, sum)) 253 error = E_CHKSUM; 254 } 255 if (opt_ignore && error == E_ERRNO && errno == ENOENT) 256 continue; 257 if (error || opt_all) 258 rval |= report(path, dname, error); 259 if (isfatal(error)) 260 break; 261 } 262 return rval; 263 } 264 265 static int 266 chkinf(FILE * fp, const char *path) 267 { 268 char buf[30]; /* "cksum.2 = 10 6" */ 269 char ext[3]; 270 struct stat sb; 271 const char *dname; 272 off_t len; 273 u_long sum; 274 intmax_t sumlen; 275 uint32_t chk; 276 int rval, error, c, pieces, cnt, fd; 277 char ch; 278 279 rval = 0; 280 for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) { 281 fd = -1; 282 dname = NULL; 283 error = 0; 284 if (cnt == -1) { 285 if ((c = sscanf(buf, "Pieces = %d%c", &pieces, &ch)) != 2 || 286 ch != '\n' || pieces < 1) 287 error = E_BADINF; 288 } else if (((c = sscanf(buf, "cksum.%2s = %lu %jd%c", ext, &sum, 289 &sumlen, &ch)) != 4 && 290 (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') || 291 ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26) 292 error = E_BADINF; 293 else if ((dname = distname(fp == stdin ? NULL : path, NULL, 294 ext)) == NULL) 295 error = E_NAME; 296 else if ((fd = open(dname, O_RDONLY)) == -1) 297 error = E_ERRNO; 298 else if (fstat(fd, &sb)) 299 error = E_ERRNO; 300 else if (sb.st_size != (off_t)sumlen) 301 error = E_LENGTH; 302 else if (!opt_exist) { 303 if (crc(fd, &chk, &len)) 304 error = E_ERRNO; 305 else if (chk != sum) 306 error = E_CHKSUM; 307 } 308 if (fd != -1 && close(fd)) 309 err(2, "%s", dname); 310 if (opt_ignore && error == E_ERRNO && errno == ENOENT) 311 continue; 312 if (error || (opt_all && cnt >= 0)) 313 rval |= report(path, dname, error); 314 if (isfatal(error)) 315 break; 316 } 317 return rval; 318 } 319 320 static int 321 report(const char *path, const char *name, int error) 322 { 323 if (name) 324 name = stripath(name); 325 switch (error) { 326 case E_UNKNOWN: 327 printf("%s: Unknown format\n", path); 328 break; 329 case E_BADMD5: 330 printf("%s: Invalid MD5 format\n", path); 331 break; 332 case E_BADINF: 333 printf("%s: Invalid .inf format\n", path); 334 break; 335 case E_NAME: 336 printf("%s: Can't derive component name\n", path); 337 break; 338 case E_LENGTH: 339 printf("%s: %s: Size mismatch\n", path, name); 340 break; 341 case E_CHKSUM: 342 printf("%s: %s: Checksum mismatch\n", path, name); 343 break; 344 case E_ERRNO: 345 printf("%s: %s: %s\n", path, name, sys_errlist[errno]); 346 break; 347 default: 348 printf("%s: %s: OK\n", path, name); 349 } 350 return error != 0; 351 } 352 353 static const char * 354 distname(const char *path, const char *name, const char *ext) 355 { 356 static char buf[NAMESIZE]; 357 size_t plen, nlen; 358 char *s; 359 360 if (opt_name) 361 name = opt_name; 362 else if (!name) { 363 if (!path) 364 return NULL; 365 name = stripath(path); 366 } 367 nlen = strlen(name); 368 if (ext && nlen > 4 && name[nlen - 4] == '.' && 369 disttype(name + nlen - 3) == DISTINF) 370 nlen -= 4; 371 if (opt_dir) { 372 path = opt_dir; 373 plen = strlen(path); 374 } else 375 plen = path && (s = strrchr(path, '/')) != NULL ? 376 (size_t)(s - path) : 0; 377 if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf)) 378 return NULL; 379 s = buf; 380 if (plen) { 381 memcpy(s, path, plen); 382 s += plen; 383 *s++ = '/'; 384 } 385 memcpy(s, name, nlen); 386 s += nlen; 387 if (ext) { 388 *s++ = '.'; 389 memcpy(s, ext, 2); 390 s += 2; 391 } 392 *s = 0; 393 return buf; 394 } 395 396 static const char * 397 stripath(const char *path) 398 { 399 const char *s; 400 401 return ((s = strrchr(path, '/')) != NULL && s[1] ? 402 s + 1 : path); 403 } 404 405 static int 406 distfile(const char *path) 407 { 408 const char *s; 409 int type; 410 411 if ((type = disttype(path)) == DISTMD5 || 412 ((s = strrchr(path, '.')) != NULL && s > path && 413 (type = disttype(s + 1)) != 0)) 414 return type; 415 return 0; 416 } 417 418 static int 419 disttype(const char *name) 420 { 421 static const char dname[DISTTYPES][4] = {"md5", "inf"}; 422 int i; 423 424 for (i = 0; i < DISTTYPES; i++) 425 if (!strcmp(dname[i], name)) 426 return 1 + i; 427 return 0; 428 } 429 430 static int 431 fail(const char *path, const char *msg) 432 { 433 if (opt_silent) 434 return 0; 435 warnx("%s: %s", path, msg ? msg : sys_errlist[errno]); 436 return 2; 437 } 438 439 static void 440 usage(void) 441 { 442 fprintf(stderr, 443 "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n"); 444 exit(2); 445 } 446