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