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