diff --git a/usr.sbin/ckdist/Makefile b/usr.sbin/ckdist/Makefile new file mode 100644 index 000000000000..884fe9e42a65 --- /dev/null +++ b/usr.sbin/ckdist/Makefile @@ -0,0 +1,12 @@ +# $Id: Makefile,v 1.1 1997/01/14 14:50:52 rnordier Exp $ + +PROG= ckdist + +.PATH: ${.CURDIR}/../../usr.bin/cksum + +SRCS= ckdist.c crc.c + +DPADD= ${LIBMD} +LDADD= -lmd + +.include diff --git a/usr.sbin/ckdist/ckdist.1 b/usr.sbin/ckdist/ckdist.1 new file mode 100644 index 000000000000..b9cef545f379 --- /dev/null +++ b/usr.sbin/ckdist/ckdist.1 @@ -0,0 +1,96 @@ +.\" Copyright (c) 1997 Robert Nordier +.\" All rights reserved. +.\" +.\" $Id: ckdist.1,v 1.2 1997/01/20 16:47:04 rnordier Exp $ +.\" +.\" 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. +.\" +.Dd January 20, 1997 +.Dt CKDIST 1 +.Os +.Sh NAME +.Nm ckdist +.Nd check software distributions +.Sh SYNOPSIS +.Nm ckdist +.Bq Fl airsx +.Bq Fl d Ar dir +.Bq Fl n Ar name +.Bq Fl t Ar type +.Ar file ... +.Sh DESCRIPTION +The +.Nm ckdist +utility reads "checksum" files (which are assumed to specify components +of a software distribution) and verifies the integrity of the +distribution by validating the checksum of each component file. +Both MD5 (128-bit "message digest") and .inf (32-bit CRC) checksum +formats are supported. +.Pp +The +.Ar file +operands may refer to regular files or to directories. Regular files +named "md5", or which have an ".md5" or an ".inf" extension, are +assumed to be of the implied type, otherwise format is determined from +content. If a directory is specified, it is searched for +appropriately-named files only. +.Pp +Options are as follows: +.Bl -tag -width 8n -offset indent +.It Fl a +Report on all distribution components, not just those in respect of +which errors are detected. +.It Fl i +Ignore missing distribution components. +.It Fl r +Search specified directories recursively. +.It Fl s +Suppress complaints about inaccessible checksum files and directories. +.It Fl x +Verify the existence of distribution components (and also check sizes, +in the case of .inf files), but omit the more time-consuming step of +actually computing and comparing checksums. +.It Fl d Ar dir +Look for distribution components in the directory +.Ar dir . +.It Fl n Ar name +Access distribution components using the filename +.Ar name . +(When accessing .inf file components, append the appropriate +extension to the filename.) +.It Fl t Ar type +Assume that all specified checksum files are of the format +.Ar type , +and search directories only for files in this format (where +.Ar type +is either "md5" or "inf"). +.El +.Sh SEE ALSO +cksum(1), md5(1) +.Sh DIAGNOSTICS +Exit status is 0 if no errors were detected, 1 if errors were found in +a distribution, and 2 if usage errors, inaccessible input files, or +other system errors were encountered. +.Sh NOTES +Both BSD and DOS versions of +.Nm ckdist +are available. diff --git a/usr.sbin/ckdist/ckdist.c b/usr.sbin/ckdist/ckdist.c new file mode 100644 index 000000000000..c0bb6df0492c --- /dev/null +++ b/usr.sbin/ckdist/ckdist.c @@ -0,0 +1,439 @@ +/* + * Copyright (c) 1997 Robert Nordier + * All rights reserved. + * + * $Id: ckdist.c,v 1.6 1997/01/20 16:47:06 rnordier Exp $ + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +extern int crc(int fd, u_long * cval, u_long * 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 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, 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 ((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(path); + if (fp != stdin && fclose(fp)) + err(2, 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, dname); + } else if (!MD5File((char *)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; + u_long sum, len, 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 %lu%c", ext, &sum, + &len, &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)len) + 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, 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 char * +stripath(const char *path) +{ + const char *s; + + return (char *)((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); +}