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