121918Sjkh/*
221918Sjkh * Copyright (c) 1997 Robert Nordier
321918Sjkh * All rights reserved.
421918Sjkh *
521918Sjkh * Redistribution and use in source and binary forms, with or without
621918Sjkh * modification, are permitted provided that the following conditions
721918Sjkh * are met:
821918Sjkh * 1. Redistributions of source code must retain the above copyright
921918Sjkh *    notice, this list of conditions and the following disclaimer.
1021918Sjkh * 2. Redistributions in binary form must reproduce the above copyright
1121918Sjkh *    notice, this list of conditions and the following disclaimer in
1221918Sjkh *    the documentation and/or other materials provided with the
1321918Sjkh *    distribution.
1421918Sjkh *
1521918Sjkh * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
1621918Sjkh * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1721918Sjkh * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1821918Sjkh * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY
1921918Sjkh * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2021918Sjkh * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
2121918Sjkh * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2221918Sjkh * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
2321918Sjkh * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
2421918Sjkh * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
2521918Sjkh * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2621918Sjkh */
2721918Sjkh
2829449Scharnier#ifndef lint
2929449Scharnierstatic const char rcsid[] =
3050479Speter  "$FreeBSD$";
3129449Scharnier#endif /* not lint */
3229449Scharnier
3321918Sjkh#include <sys/types.h>
3421918Sjkh#include <sys/stat.h>
35112213Srobert
3629449Scharnier#include <err.h>
3729449Scharnier#include <errno.h>
3821918Sjkh#include <fcntl.h>
3921918Sjkh#include <fts.h>
4021918Sjkh#include <md5.h>
4129449Scharnier#include <stdio.h>
42112213Srobert#include <stdint.h>
4321918Sjkh#include <stdlib.h>
4421918Sjkh#include <string.h>
4529449Scharnier#include <unistd.h>
4621918Sjkh
47112213Srobertextern int crc(int fd, uint32_t *cval, off_t *clen);
4821918Sjkh
4921918Sjkh#define DISTMD5     1		/* MD5 format */
5021918Sjkh#define DISTINF     2		/* .inf format */
5121918Sjkh#define DISTTYPES   2		/* types supported */
5221918Sjkh
5321918Sjkh#define E_UNKNOWN   1		/* Unknown format */
5421918Sjkh#define E_BADMD5    2		/* Invalid MD5 format */
5521918Sjkh#define E_BADINF    3		/* Invalid .inf format */
5621918Sjkh#define E_NAME      4		/* Can't derive component name */
5721918Sjkh#define E_LENGTH    5		/* Length mismatch */
5821918Sjkh#define E_CHKSUM    6		/* Checksum mismatch */
5921918Sjkh#define E_ERRNO     7		/* sys_errlist[errno] */
6021918Sjkh
6121918Sjkh#define isfatal(err)   ((err) && (err) <= E_NAME)
6221918Sjkh
6321918Sjkh#define NAMESIZE  256           /* filename buffer size */
6421918Sjkh#define MDSUMLEN   32           /* length of MD5 message digest */
6521918Sjkh
6621918Sjkh#define isstdin(path)  ((path)[0] == '-' && !(path)[1])
6721918Sjkh
6821918Sjkhstatic const char *opt_dir;	/* where to look for components */
6921918Sjkhstatic const char *opt_name;	/* name for accessing components */
7021918Sjkhstatic int opt_all;		/* report on all components */
7121918Sjkhstatic int opt_ignore;		/* ignore missing components */
7221918Sjkhstatic int opt_recurse;		/* search directories recursively */
7321918Sjkhstatic int opt_silent;		/* silent about inaccessible files */
7421918Sjkhstatic int opt_type;		/* dist type: md5 or inf */
7521918Sjkhstatic int opt_exist;		/* just verify existence */
7621918Sjkh
7721918Sjkhstatic int ckdist(const char *path, int type);
7821918Sjkhstatic int chkmd5(FILE * fp, const char *path);
7921918Sjkhstatic int chkinf(FILE * fp, const char *path);
8021918Sjkhstatic int report(const char *path, const char *name, int error);
8121918Sjkhstatic const char *distname(const char *path, const char *name,
8221918Sjkh	                    const char *ext);
83185073Sdelphijstatic const char *stripath(const char *path);
8421918Sjkhstatic int distfile(const char *path);
8521918Sjkhstatic int disttype(const char *name);
8621918Sjkhstatic int fail(const char *path, const char *msg);
8721918Sjkhstatic void usage(void);
8821918Sjkh
8921918Sjkhint
9021918Sjkhmain(int argc, char *argv[])
9121918Sjkh{
9221918Sjkh    static char *arg[2];
9321918Sjkh    struct stat sb;
9421918Sjkh    FTS *ftsp;
9521918Sjkh    FTSENT *f;
9621918Sjkh    int rval, c, type;
9721918Sjkh
9821918Sjkh    while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1)
9921918Sjkh	switch (c) {
10021918Sjkh	case 'a':
10121918Sjkh	    opt_all = 1;
10221918Sjkh	    break;
10321918Sjkh	case 'd':
10421918Sjkh	    opt_dir = optarg;
10521918Sjkh	    break;
10621918Sjkh	case 'i':
10721918Sjkh	    opt_ignore = 1;
10821918Sjkh	    break;
10921918Sjkh	case 'n':
11021918Sjkh	    opt_name = optarg;
11121918Sjkh	    break;
11221918Sjkh	case 'r':
11321918Sjkh	    opt_recurse = 1;
11421918Sjkh	    break;
11521918Sjkh	case 's':
11621918Sjkh	    opt_silent = 1;
11721918Sjkh	    break;
11821918Sjkh	case 't':
11921918Sjkh	    if ((opt_type = disttype(optarg)) == 0) {
12021918Sjkh		warnx("illegal argument to -t option");
12121918Sjkh		usage();
12221918Sjkh	    }
12321918Sjkh	    break;
12421918Sjkh	case 'x':
12521918Sjkh	    opt_exist = 1;
12621918Sjkh	    break;
12721918Sjkh	default:
12821918Sjkh	    usage();
12921918Sjkh	}
13021918Sjkh    argc -= optind;
13121918Sjkh    argv += optind;
13221918Sjkh    if (argc < 1)
13321918Sjkh	usage();
13421918Sjkh    if (opt_dir) {
13521918Sjkh	if (stat(opt_dir, &sb))
13661019Scharnier	    err(2, "%s", opt_dir);
13721918Sjkh	if (!S_ISDIR(sb.st_mode))
13829449Scharnier	    errx(2, "%s: not a directory", opt_dir);
13921918Sjkh    }
14021918Sjkh    rval = 0;
14121918Sjkh    do {
14221918Sjkh	if (isstdin(*argv))
14321918Sjkh	    rval |= ckdist(*argv, opt_type);
14421918Sjkh	else if (stat(*argv, &sb))
14521918Sjkh	    rval |= fail(*argv, NULL);
14621918Sjkh	else if (S_ISREG(sb.st_mode))
14721918Sjkh	    rval |= ckdist(*argv, opt_type);
14821918Sjkh	else {
14921918Sjkh	    arg[0] = *argv;
15021918Sjkh	    if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL)
15121918Sjkh		err(2, "fts_open");
15221918Sjkh	    while ((f = fts_read(ftsp)) != NULL)
15321918Sjkh		switch (f->fts_info) {
15421918Sjkh		case FTS_DC:
15521918Sjkh		    rval = fail(f->fts_path, "Directory causes a cycle");
15621918Sjkh		    break;
15721918Sjkh		case FTS_DNR:
15821918Sjkh		case FTS_ERR:
15921918Sjkh		case FTS_NS:
16021918Sjkh		    rval = fail(f->fts_path, sys_errlist[f->fts_errno]);
16121918Sjkh		    break;
16221918Sjkh		case FTS_D:
16321918Sjkh		    if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL &&
16421918Sjkh			fts_set(ftsp, f, FTS_SKIP))
16521918Sjkh			err(2, "fts_set");
16621918Sjkh		    break;
16721918Sjkh		case FTS_F:
16821918Sjkh		    if ((type = distfile(f->fts_name)) != 0 &&
16921918Sjkh			(!opt_type || type == opt_type))
17021918Sjkh			rval |= ckdist(f->fts_path, type);
17121918Sjkh		    break;
17221918Sjkh                default: ;
17321918Sjkh		}
17421918Sjkh	    if (errno)
17521918Sjkh		err(2, "fts_read");
17621918Sjkh	    if (fts_close(ftsp))
17721918Sjkh		err(2, "fts_close");
17821918Sjkh	}
17921918Sjkh    } while (*++argv);
18021918Sjkh    return rval;
18121918Sjkh}
18221918Sjkh
18321918Sjkhstatic int
18421918Sjkhckdist(const char *path, int type)
18521918Sjkh{
18621918Sjkh    FILE *fp;
18721918Sjkh    int rval, c;
18821918Sjkh
18921918Sjkh    if (isstdin(path)) {
19021918Sjkh	path = "(stdin)";
19121918Sjkh	fp = stdin;
19221918Sjkh    } else if ((fp = fopen(path, "r")) == NULL)
19321918Sjkh	return fail(path, NULL);
19421918Sjkh    if (!type) {
19521918Sjkh	if (fp != stdin)
19621918Sjkh	    type = distfile(path);
19721918Sjkh	if (!type)
19821918Sjkh	    if ((c = fgetc(fp)) != EOF) {
19921918Sjkh		type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0;
20021918Sjkh		(void)ungetc(c, fp);
20121918Sjkh	    }
20221918Sjkh    }
20321918Sjkh    switch (type) {
20421918Sjkh    case DISTMD5:
20521918Sjkh	rval = chkmd5(fp, path);
20621918Sjkh	break;
20721918Sjkh    case DISTINF:
20821918Sjkh	rval = chkinf(fp, path);
20921918Sjkh	break;
21021918Sjkh    default:
21121918Sjkh	rval = report(path, NULL, E_UNKNOWN);
21221918Sjkh    }
21321918Sjkh    if (ferror(fp))
21461019Scharnier	warn("%s", path);
21521918Sjkh    if (fp != stdin && fclose(fp))
21661019Scharnier	err(2, "%s", path);
21721918Sjkh    return rval;
21821918Sjkh}
21921918Sjkh
22021918Sjkhstatic int
22121918Sjkhchkmd5(FILE * fp, const char *path)
22221918Sjkh{
22321918Sjkh    char buf[298];              /* "MD5 (NAMESIZE = MDSUMLEN" */
22421918Sjkh    char name[NAMESIZE + 1];
22521918Sjkh    char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1];
22621918Sjkh    const char *dname;
22721918Sjkh    char *s;
22821918Sjkh    int rval, error, c, fd;
22921918Sjkh    char ch;
23021918Sjkh
23121918Sjkh    rval = 0;
23221918Sjkh    while (fgets(buf, sizeof(buf), fp)) {
23321918Sjkh	dname = NULL;
23421918Sjkh	error = 0;
23521918Sjkh	if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum,
23621918Sjkh			 &ch)) != 3 && (!feof(fp) || c != 2)) ||
23721918Sjkh	    (c == 3 && ch != '\n') ||
23821918Sjkh	    (s = strrchr(name, ')')) == NULL ||
23921918Sjkh	    strlen(sum) != MDSUMLEN)
24021918Sjkh	    error = E_BADMD5;
24121918Sjkh	else {
24221918Sjkh	    *s = 0;
24321918Sjkh	    if ((dname = distname(path, name, NULL)) == NULL)
24421918Sjkh		error = E_NAME;
24521918Sjkh	    else if (opt_exist) {
24621918Sjkh		if ((fd = open(dname, O_RDONLY)) == -1)
24721918Sjkh		    error = E_ERRNO;
24821918Sjkh		else if (close(fd))
24961019Scharnier		    err(2, "%s", dname);
250185073Sdelphij	    } else if (!MD5File(dname, chk))
25121918Sjkh		error = E_ERRNO;
25221918Sjkh	    else if (strcmp(chk, sum))
25321918Sjkh		error = E_CHKSUM;
25421918Sjkh	}
25521918Sjkh	if (opt_ignore && error == E_ERRNO && errno == ENOENT)
25621918Sjkh	    continue;
25721918Sjkh	if (error || opt_all)
25821918Sjkh	    rval |= report(path, dname, error);
25921918Sjkh	if (isfatal(error))
26021918Sjkh	    break;
26121918Sjkh    }
26221918Sjkh    return rval;
26321918Sjkh}
26421918Sjkh
26521918Sjkhstatic int
26621918Sjkhchkinf(FILE * fp, const char *path)
26721918Sjkh{
26821918Sjkh    char buf[30];               /* "cksum.2 = 10 6" */
26921918Sjkh    char ext[3];
27021918Sjkh    struct stat sb;
27121918Sjkh    const char *dname;
272112213Srobert    off_t len;
273112213Srobert    u_long sum;
274112213Srobert    intmax_t sumlen;
275112213Srobert    uint32_t chk;
27621918Sjkh    int rval, error, c, pieces, cnt, fd;
27721918Sjkh    char ch;
27821918Sjkh
27921918Sjkh    rval = 0;
28021918Sjkh    for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) {
28121918Sjkh	fd = -1;
28221918Sjkh	dname = NULL;
28321918Sjkh	error = 0;
28421918Sjkh	if (cnt == -1) {
28521918Sjkh	    if ((c = sscanf(buf, "Pieces =  %d%c", &pieces, &ch)) != 2 ||
28621918Sjkh		ch != '\n' || pieces < 1)
28721918Sjkh		error = E_BADINF;
288112213Srobert	} else if (((c = sscanf(buf, "cksum.%2s = %lu %jd%c", ext, &sum,
289112213Srobert			        &sumlen, &ch)) != 4 &&
29021918Sjkh		    (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') ||
29121918Sjkh		   ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26)
29221918Sjkh	    error = E_BADINF;
29321918Sjkh	else if ((dname = distname(fp == stdin ? NULL : path, NULL,
29421918Sjkh				    ext)) == NULL)
29521918Sjkh	    error = E_NAME;
29621918Sjkh	else if ((fd = open(dname, O_RDONLY)) == -1)
29721918Sjkh	    error = E_ERRNO;
29821918Sjkh	else if (fstat(fd, &sb))
29921918Sjkh	    error = E_ERRNO;
300112213Srobert	else if (sb.st_size != (off_t)sumlen)
30121918Sjkh	    error = E_LENGTH;
30221918Sjkh	else if (!opt_exist) {
30321918Sjkh	    if (crc(fd, &chk, &len))
30421918Sjkh		error = E_ERRNO;
30521918Sjkh	    else if (chk != sum)
30621918Sjkh		error = E_CHKSUM;
30721918Sjkh	}
30821918Sjkh	if (fd != -1 && close(fd))
30961019Scharnier	    err(2, "%s", dname);
31021918Sjkh	if (opt_ignore && error == E_ERRNO && errno == ENOENT)
31121918Sjkh	    continue;
31221918Sjkh	if (error || (opt_all && cnt >= 0))
31321918Sjkh	    rval |= report(path, dname, error);
31421918Sjkh	if (isfatal(error))
31521918Sjkh	    break;
31621918Sjkh    }
31721918Sjkh    return rval;
31821918Sjkh}
31921918Sjkh
32021918Sjkhstatic int
32121918Sjkhreport(const char *path, const char *name, int error)
32221918Sjkh{
32321918Sjkh    if (name)
32421918Sjkh	name = stripath(name);
32521918Sjkh    switch (error) {
32621918Sjkh    case E_UNKNOWN:
32721918Sjkh	printf("%s: Unknown format\n", path);
32821918Sjkh	break;
32921918Sjkh    case E_BADMD5:
33021918Sjkh	printf("%s: Invalid MD5 format\n", path);
33121918Sjkh	break;
33221918Sjkh    case E_BADINF:
33321918Sjkh	printf("%s: Invalid .inf format\n", path);
33421918Sjkh	break;
33521918Sjkh    case E_NAME:
33621918Sjkh	printf("%s: Can't derive component name\n", path);
33721918Sjkh	break;
33821918Sjkh    case E_LENGTH:
33921918Sjkh	printf("%s: %s: Size mismatch\n", path, name);
34021918Sjkh	break;
34121918Sjkh    case E_CHKSUM:
34221918Sjkh	printf("%s: %s: Checksum mismatch\n", path, name);
34321918Sjkh	break;
34421918Sjkh    case E_ERRNO:
34521918Sjkh	printf("%s: %s: %s\n", path, name, sys_errlist[errno]);
34621918Sjkh	break;
34721918Sjkh    default:
34821918Sjkh	printf("%s: %s: OK\n", path, name);
34921918Sjkh    }
35021918Sjkh    return error != 0;
35121918Sjkh}
35221918Sjkh
35321918Sjkhstatic const char *
35421918Sjkhdistname(const char *path, const char *name, const char *ext)
35521918Sjkh{
35621918Sjkh    static char buf[NAMESIZE];
35721918Sjkh    size_t plen, nlen;
35821918Sjkh    char *s;
35921918Sjkh
36021918Sjkh    if (opt_name)
36121918Sjkh	name = opt_name;
36221918Sjkh    else if (!name) {
36321918Sjkh	if (!path)
36421918Sjkh	    return NULL;
36521918Sjkh	name = stripath(path);
36621918Sjkh    }
36721918Sjkh    nlen = strlen(name);
36821918Sjkh    if (ext && nlen > 4 && name[nlen - 4] == '.' &&
36921918Sjkh	disttype(name + nlen - 3) == DISTINF)
37021918Sjkh	nlen -= 4;
37121918Sjkh    if (opt_dir) {
37221918Sjkh	path = opt_dir;
37321918Sjkh	plen = strlen(path);
37421918Sjkh    } else
37521918Sjkh	plen = path && (s = strrchr(path, '/')) != NULL ?
37621918Sjkh            (size_t)(s - path) : 0;
37721918Sjkh    if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf))
37821918Sjkh	return NULL;
37921918Sjkh    s = buf;
38021918Sjkh    if (plen) {
38121918Sjkh	memcpy(s, path, plen);
38221918Sjkh	s += plen;
38321918Sjkh	*s++ = '/';
38421918Sjkh    }
38521918Sjkh    memcpy(s, name, nlen);
38621918Sjkh    s += nlen;
38721918Sjkh    if (ext) {
38821918Sjkh	*s++ = '.';
38921918Sjkh	memcpy(s, ext, 2);
39021918Sjkh	s += 2;
39121918Sjkh    }
39221918Sjkh    *s = 0;
39321918Sjkh    return buf;
39421918Sjkh}
39521918Sjkh
396185073Sdelphijstatic const char *
39721918Sjkhstripath(const char *path)
39821918Sjkh{
39921918Sjkh    const char *s;
40021918Sjkh
401185073Sdelphij    return ((s = strrchr(path, '/')) != NULL && s[1] ?
40221918Sjkh		    s + 1 : path);
40321918Sjkh}
40421918Sjkh
40521918Sjkhstatic int
40621918Sjkhdistfile(const char *path)
40721918Sjkh{
40821918Sjkh    const char *s;
40921918Sjkh    int type;
41021918Sjkh
41121918Sjkh    if ((type = disttype(path)) == DISTMD5 ||
41221918Sjkh	((s = strrchr(path, '.')) != NULL && s > path &&
41321918Sjkh	 (type = disttype(s + 1)) != 0))
41421918Sjkh	return type;
41521918Sjkh    return 0;
41621918Sjkh}
41721918Sjkh
41821918Sjkhstatic int
41921918Sjkhdisttype(const char *name)
42021918Sjkh{
42121918Sjkh    static const char dname[DISTTYPES][4] = {"md5", "inf"};
42221918Sjkh    int i;
42321918Sjkh
42421918Sjkh    for (i = 0; i < DISTTYPES; i++)
42521918Sjkh	if (!strcmp(dname[i], name))
42621918Sjkh	    return 1 + i;
42721918Sjkh    return 0;
42821918Sjkh}
42921918Sjkh
43021918Sjkhstatic int
43121918Sjkhfail(const char *path, const char *msg)
43221918Sjkh{
43321918Sjkh    if (opt_silent)
43421918Sjkh	return 0;
43521918Sjkh    warnx("%s: %s", path, msg ? msg : sys_errlist[errno]);
43621918Sjkh    return 2;
43721918Sjkh}
43821918Sjkh
43921918Sjkhstatic void
44021918Sjkhusage(void)
44121918Sjkh{
44221918Sjkh    fprintf(stderr,
44329449Scharnier	    "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n");
44421918Sjkh    exit(2);
44521918Sjkh}
446