/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997 Robert Nordier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include extern int crc(int fd, uint32_t *cval, off_t *clen); #define DISTMD5 1 /* MD5 format */ #define DISTINF 2 /* .inf format */ #define DISTTYPES 2 /* types supported */ #define E_UNKNOWN 1 /* Unknown format */ #define E_BADMD5 2 /* Invalid MD5 format */ #define E_BADINF 3 /* Invalid .inf format */ #define E_NAME 4 /* Can't derive component name */ #define E_LENGTH 5 /* Length mismatch */ #define E_CHKSUM 6 /* Checksum mismatch */ #define E_ERRNO 7 /* sys_errlist[errno] */ #define isfatal(err) ((err) && (err) <= E_NAME) #define NAMESIZE 256 /* filename buffer size */ #define MDSUMLEN 32 /* length of MD5 message digest */ #define isstdin(path) ((path)[0] == '-' && !(path)[1]) static const char *opt_dir; /* where to look for components */ static const char *opt_name; /* name for accessing components */ static int opt_all; /* report on all components */ static int opt_ignore; /* ignore missing components */ static int opt_recurse; /* search directories recursively */ static int opt_silent; /* silent about inaccessible files */ static int opt_type; /* dist type: md5 or inf */ static int opt_exist; /* just verify existence */ static int ckdist(const char *path, int type); static int chkmd5(FILE * fp, const char *path); static int chkinf(FILE * fp, const char *path); static int report(const char *path, const char *name, int error); static const char *distname(const char *path, const char *name, const char *ext); static const char *stripath(const char *path); static int distfile(const char *path); static int disttype(const char *name); static int fail(const char *path, const char *msg); static void usage(void); int main(int argc, char *argv[]) { static char *arg[2]; struct stat sb; FTS *ftsp; FTSENT *f; int rval, c, type; while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1) switch (c) { case 'a': opt_all = 1; break; case 'd': opt_dir = optarg; break; case 'i': opt_ignore = 1; break; case 'n': opt_name = optarg; break; case 'r': opt_recurse = 1; break; case 's': opt_silent = 1; break; case 't': if ((opt_type = disttype(optarg)) == 0) { warnx("illegal argument to -t option"); usage(); } break; case 'x': opt_exist = 1; break; default: usage(); } argc -= optind; argv += optind; if (argc < 1) usage(); if (opt_dir) { if (stat(opt_dir, &sb)) err(2, "%s", opt_dir); if (!S_ISDIR(sb.st_mode)) errx(2, "%s: not a directory", opt_dir); } rval = 0; do { if (isstdin(*argv)) rval |= ckdist(*argv, opt_type); else if (stat(*argv, &sb)) rval |= fail(*argv, NULL); else if (S_ISREG(sb.st_mode)) rval |= ckdist(*argv, opt_type); else { arg[0] = *argv; if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL) err(2, "fts_open"); while (errno = 0, (f = fts_read(ftsp)) != NULL) switch (f->fts_info) { case FTS_DC: rval = fail(f->fts_path, "Directory causes a cycle"); break; case FTS_DNR: case FTS_ERR: case FTS_NS: rval = fail(f->fts_path, sys_errlist[f->fts_errno]); break; case FTS_D: if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL && fts_set(ftsp, f, FTS_SKIP)) err(2, "fts_set"); break; case FTS_F: if ((type = distfile(f->fts_name)) != 0 && (!opt_type || type == opt_type)) rval |= ckdist(f->fts_path, type); break; default: ; } if (errno) err(2, "fts_read"); if (fts_close(ftsp)) err(2, "fts_close"); } } while (*++argv); return rval; } static int ckdist(const char *path, int type) { FILE *fp; int rval, c; if (isstdin(path)) { path = "(stdin)"; fp = stdin; } else if ((fp = fopen(path, "r")) == NULL) return fail(path, NULL); if (!type) { if (fp != stdin) type = distfile(path); if (!type) if ((c = fgetc(fp)) != EOF) { type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0; (void)ungetc(c, fp); } } switch (type) { case DISTMD5: rval = chkmd5(fp, path); break; case DISTINF: rval = chkinf(fp, path); break; default: rval = report(path, NULL, E_UNKNOWN); } if (ferror(fp)) warn("%s", path); if (fp != stdin && fclose(fp)) err(2, "%s", path); return rval; } static int chkmd5(FILE * fp, const char *path) { char buf[298]; /* "MD5 (NAMESIZE = MDSUMLEN" */ char name[NAMESIZE + 1]; char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1]; const char *dname; char *s; int rval, error, c, fd; char ch; rval = 0; while (fgets(buf, sizeof(buf), fp)) { dname = NULL; error = 0; if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum, &ch)) != 3 && (!feof(fp) || c != 2)) || (c == 3 && ch != '\n') || (s = strrchr(name, ')')) == NULL || strlen(sum) != MDSUMLEN) error = E_BADMD5; else { *s = 0; if ((dname = distname(path, name, NULL)) == NULL) error = E_NAME; else if (opt_exist) { if ((fd = open(dname, O_RDONLY)) == -1) error = E_ERRNO; else if (close(fd)) err(2, "%s", dname); } else if (!MD5File(dname, chk)) error = E_ERRNO; else if (strcmp(chk, sum)) error = E_CHKSUM; } if (opt_ignore && error == E_ERRNO && errno == ENOENT) continue; if (error || opt_all) rval |= report(path, dname, error); if (isfatal(error)) break; } return rval; } static int chkinf(FILE * fp, const char *path) { char buf[30]; /* "cksum.2 = 10 6" */ char ext[3]; struct stat sb; const char *dname; off_t len; u_long sum; intmax_t sumlen; uint32_t chk; int rval, error, c, pieces, cnt, fd; char ch; rval = 0; for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) { fd = -1; dname = NULL; error = 0; if (cnt == -1) { if ((c = sscanf(buf, "Pieces = %d%c", &pieces, &ch)) != 2 || ch != '\n' || pieces < 1) error = E_BADINF; } else if (((c = sscanf(buf, "cksum.%2s = %lu %jd%c", ext, &sum, &sumlen, &ch)) != 4 && (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') || ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26) error = E_BADINF; else if ((dname = distname(fp == stdin ? NULL : path, NULL, ext)) == NULL) error = E_NAME; else if ((fd = open(dname, O_RDONLY)) == -1) error = E_ERRNO; else if (fstat(fd, &sb)) error = E_ERRNO; else if (sb.st_size != (off_t)sumlen) error = E_LENGTH; else if (!opt_exist) { if (crc(fd, &chk, &len)) error = E_ERRNO; else if (chk != sum) error = E_CHKSUM; } if (fd != -1 && close(fd)) err(2, "%s", dname); if (opt_ignore && error == E_ERRNO && errno == ENOENT) continue; if (error || (opt_all && cnt >= 0)) rval |= report(path, dname, error); if (isfatal(error)) break; } return rval; } static int report(const char *path, const char *name, int error) { if (name) name = stripath(name); switch (error) { case E_UNKNOWN: printf("%s: Unknown format\n", path); break; case E_BADMD5: printf("%s: Invalid MD5 format\n", path); break; case E_BADINF: printf("%s: Invalid .inf format\n", path); break; case E_NAME: printf("%s: Can't derive component name\n", path); break; case E_LENGTH: printf("%s: %s: Size mismatch\n", path, name); break; case E_CHKSUM: printf("%s: %s: Checksum mismatch\n", path, name); break; case E_ERRNO: printf("%s: %s: %s\n", path, name, sys_errlist[errno]); break; default: printf("%s: %s: OK\n", path, name); } return error != 0; } static const char * distname(const char *path, const char *name, const char *ext) { static char buf[NAMESIZE]; size_t plen, nlen; char *s; if (opt_name) name = opt_name; else if (!name) { if (!path) return NULL; name = stripath(path); } nlen = strlen(name); if (ext && nlen > 4 && name[nlen - 4] == '.' && disttype(name + nlen - 3) == DISTINF) nlen -= 4; if (opt_dir) { path = opt_dir; plen = strlen(path); } else plen = path && (s = strrchr(path, '/')) != NULL ? (size_t)(s - path) : 0; if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf)) return NULL; s = buf; if (plen) { memcpy(s, path, plen); s += plen; *s++ = '/'; } memcpy(s, name, nlen); s += nlen; if (ext) { *s++ = '.'; memcpy(s, ext, 2); s += 2; } *s = 0; return buf; } static const char * stripath(const char *path) { const char *s; return ((s = strrchr(path, '/')) != NULL && s[1] ? s + 1 : path); } static int distfile(const char *path) { const char *s; int type; if ((type = disttype(path)) == DISTMD5 || ((s = strrchr(path, '.')) != NULL && s > path && (type = disttype(s + 1)) != 0)) return type; return 0; } static int disttype(const char *name) { static const char dname[DISTTYPES][4] = {"md5", "inf"}; int i; for (i = 0; i < DISTTYPES; i++) if (!strcmp(dname[i], name)) return 1 + i; return 0; } static int fail(const char *path, const char *msg) { if (opt_silent) return 0; warnx("%s: %s", path, msg ? msg : sys_errlist[errno]); return 2; } static void usage(void) { fprintf(stderr, "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n"); exit(2); }