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