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