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