Add an internal-representation no-op function.
[u/mdw/catacomb] / hashsum.c
CommitLineData
e375fe33 1/* -*-c-*-
2 *
e7dc130f 3 * $Id: hashsum.c,v 1.8 2001/04/19 18:26:33 mdw Exp $
e375fe33 4 *
5 * Hash files using some secure hash function
6 *
7 * (c) 2000 Straylight/Edgeware
8 */
9
10/*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of Catacomb.
13 *
14 * Catacomb is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU Library General Public License as
16 * published by the Free Software Foundation; either version 2 of the
17 * License, or (at your option) any later version.
18 *
19 * Catacomb is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Library General Public License for more details.
23 *
24 * You should have received a copy of the GNU Library General Public
25 * License along with Catacomb; if not, write to the Free
26 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27 * MA 02111-1307, USA.
28 */
29
30/*----- Revision history --------------------------------------------------*
31 *
32 * $Log: hashsum.c,v $
e7dc130f 33 * Revision 1.8 2001/04/19 18:26:33 mdw
34 * Add CRC as another hash function.
35 *
d3187d77 36 * Revision 1.7 2001/02/21 20:03:22 mdw
37 * Added support for MD2 hash function.
38 *
87ffb3ae 39 * Revision 1.6 2001/01/25 21:40:14 mdw
40 * Support for new SHA variants added.
41 *
16efd15b 42 * Revision 1.5 2000/12/06 20:33:27 mdw
43 * Make flags be macros rather than enumerations, to ensure that they're
44 * unsigned.
45 *
d470270a 46 * Revision 1.4 2000/08/04 23:23:44 mdw
47 * Various <ctype.h> fixes.
48 *
12902a5c 49 * Revision 1.3 2000/07/29 17:02:43 mdw
50 * (checkhash): Be pettier about spaces between the hash and filename, for
51 * compatiblity with `md5sum'.
52 *
5af0f9cb 53 * Revision 1.2 2000/07/15 21:14:05 mdw
54 * Missed `-e' out of the usage string.
55 *
e375fe33 56 * Revision 1.1 2000/07/15 20:52:34 mdw
57 * Useful replacement for `md5sum' with support for many different hash
58 * functions and for reading filename lists from `find'.
59 *
60 */
61
62/*----- Header files ------------------------------------------------------*/
63
64#include "config.h"
65
66#include <ctype.h>
67#include <errno.h>
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
71
72#include <mLib/alloc.h>
73#include <mLib/dstr.h>
74#include <mLib/mdwopt.h>
75#include <mLib/quis.h>
76#include <mLib/report.h>
77#include <mLib/sub.h>
78#include <mLib/str.h>
79
80#include "ghash.h"
81
e7dc130f 82#include "crc32.h"
d3187d77 83#include "md2.h"
e375fe33 84#include "md4.h"
85#include "md5.h"
86#include "rmd128.h"
87#include "rmd160.h"
88#include "rmd256.h"
89#include "rmd320.h"
90#include "sha.h"
87ffb3ae 91#include "sha256.h"
92#include "sha384.h"
93#include "sha512.h"
e375fe33 94#include "tiger.h"
95
96/*----- Static variables --------------------------------------------------*/
97
98static const gchash *hashtab[] = {
d3187d77 99 &md5, &md4, &md2,
87ffb3ae 100 &sha, &sha256, &sha384, &sha512,
101 &rmd128, &rmd160, &rmd256, &rmd320,
e7dc130f 102 &tiger, &gcrc32,
e375fe33 103 0
104};
105
16efd15b 106#define f_binary 1u
107#define f_bogus 2u
108#define f_verbose 4u
109#define f_check 8u
110#define f_files 16u
111#define f_raw 32u
112#define f_oddhash 64u
113#define f_escape 128u
e375fe33 114
115/*----- Support functions -------------------------------------------------*/
116
117/* --- @fhash@ --- *
118 *
119 * Arguments: @const char *file@ = file name to be hashed (null for stdin)
120 * @unsigned f@ = flags to set
121 * @const gchash *gch@ = pointer to hash function to use
122 * @void *buf@ = pointer to hash output buffer
123 *
124 * Returns: Zero if it worked, nonzero on error.
125 *
126 * Use: Hashes a file.
127 */
128
129static int fhash(const char *file, unsigned f, const gchash *gch, void *buf)
130{
131 FILE *fp;
132 char fbuf[BUFSIZ];
133 size_t sz;
134 ghash *h;
135 int e;
136
137 if (!file)
138 fp = stdin;
139 else if ((fp = fopen(file, f & f_binary ? "rb" : "r")) == 0)
140 return (-1);
141
142 h = gch->init();
143 while ((sz = fread(fbuf, 1, sizeof(fbuf), fp)) > 0)
144 h->ops->hash(h, fbuf, sz);
145 h->ops->done(h, buf);
146 h->ops->destroy(h);
147 e = ferror(fp);
148 if (file)
149 fclose(fp);
150 return (e ? -1 : 0);
151}
152
153/* --- @puthex@ --- *
154 *
155 * Arguments: @const octet *buf@ = pointer to a binary buffer
156 * @size_t sz@ = size of the buffer
157 * @FILE *fp@ = pointer to output file handle
158 *
159 * Returns: ---
160 *
161 * Use: Writes a hex dump of a block of memory.
162 */
163
164static void puthex(const octet *buf, size_t sz, FILE *fp)
165{
166 while (sz) {
167 fprintf(fp, "%02x", *buf++);
168 sz--;
169 }
170}
171
172/* --- @gethex@ --- *
173 *
174 * Arguments: @const char *p@ = pointer to input string
175 * @octet *q@ = pointer to output buffer
176 * @size_t sz@ = size of the output buffer
177 * @char **pp@ = where to put the end pointer
178 *
179 * Returns: The number of bytes written to the buffer.
180 *
181 * Use: Reads hex dumps from the input string.
182 */
183
184static size_t gethex(const char *p, octet *q, size_t sz, char **pp)
185{
186 size_t i = 0;
187 while (sz > 0 &&
188 isxdigit((unsigned char)p[0]) &&
189 isxdigit((unsigned char)p[1])) {
190 char buf[3];
191 buf[0] = p[0];
192 buf[1] = p[1];
193 buf[2] = 0;
194 *q++ = strtoul(buf, 0, 16);
195 sz--;
196 p += 2;
197 i++;
198 }
199 if (pp)
200 *pp = (char *)p;
201 return (i);
202}
203
204/* --- @gethash@ --- *
205 *
206 * Arguments: @const char *name@ = pointer to name string
207 *
208 * Returns: Pointer to appropriate hash class.
209 *
210 * Use: Chooses a hash function by name.
211 */
212
213static const gchash *gethash(const char *name)
214{
215 const gchash **g, *gg = 0;
216 size_t sz = strlen(name);
217 for (g = hashtab; *g; g++) {
218 if (strncmp(name, (*g)->name, sz) == 0) {
219 if ((*g)->name[sz] == 0) {
220 gg = *g;
221 break;
222 } else if (gg)
223 return (0);
224 else
225 gg = *g;
226 }
227 }
228 return (gg);
229}
230
231/* --- @getstring@ --- *
232 *
233 * Arguments: @FILE *fp@ = stream from which to read
234 * @const char *p@ = string to read from instead
235 * @dstr *d@ = destination string
236 * @unsigned raw@ = raw or cooked read
237 *
238 * Returns: Zero if OK, nonzero on end-of-file.
239 *
240 * Use: Reads a filename (or something similar) from a stream.
241 */
242
243static int getstring(FILE *fp, const char *p, dstr *d, unsigned raw)
244{
245 int ch;
246 int q = 0;
247
248 /* --- Raw: just read exactly what's written up to a null byte --- */
249
d470270a 250#define NEXTCH (fp ? getc(fp) : (unsigned char)*p++)
e375fe33 251#define EOFCH (fp ? EOF : 0)
252
253 if (raw) {
254 if ((ch = NEXTCH) == EOFCH)
255 return (EOF);
256 for (;;) {
257 if (!ch)
258 break;
259 DPUTC(d, ch);
260 if ((ch = NEXTCH) == EOFCH)
261 break;
262 }
263 DPUTZ(d);
264 return (0);
265 }
266
267 /* --- Skip as far as whitespace --- *
268 *
269 * Also skip past comments.
270 */
271
272again:
273 ch = NEXTCH;
d470270a 274 while (isspace(ch))
e375fe33 275 ch = NEXTCH;
276 if (ch == '#') {
277 do ch = NEXTCH; while (ch != '\n' && ch != EOFCH);
278 goto again;
279 }
280 if (ch == EOFCH)
281 return (EOF);
282
283 /* --- If the character is a quote then read a quoted string --- */
284
285 switch (ch) {
286 case '`':
287 ch = '\'';
288 case '\'':
289 case '\"':
290 q = ch;
291 ch = NEXTCH;
292 break;
293 }
294
295 /* --- Now read all sorts of interesting things --- */
296
297 for (;;) {
298
299 /* --- Handle an escaped thing --- */
300
301 if (ch == '\\') {
302 ch = NEXTCH;
303 if (ch == EOFCH)
304 break;
305 switch (ch) {
306 case 'a': ch = '\a'; break;
307 case 'b': ch = '\b'; break;
308 case 'f': ch = '\f'; break;
309 case 'n': ch = '\n'; break;
310 case 'r': ch = '\r'; break;
311 case 't': ch = '\t'; break;
312 case 'v': ch = '\v'; break;
313 }
314 DPUTC(d, ch);
315 ch = NEXTCH;
316 continue;
317 }
318
319 /* --- If it's a quote or some other end marker then stop --- */
320
321 if (ch == q)
322 break;
d470270a 323 if (!q && isspace(ch))
e375fe33 324 break;
325
326 /* --- Otherwise contribute and continue --- */
327
328 DPUTC(d, ch);
329 if ((ch = NEXTCH) == EOFCH)
330 break;
331 }
332
333 /* --- Done --- */
334
335 DPUTZ(d);
336 return (0);
337
338#undef NEXTCH
339#undef EOFCH
340}
341
342/* --- @putstring@ --- *
343 *
344 * Arguments: @FILE *fp@ = stream to write on
345 * @const char *p@ = pointer to text
346 * @unsigned raw@ = whether the string is to be written raw
347 *
348 * Returns: ---
349 *
350 * Use: Emits a string to a stream.
351 */
352
353static void putstring(FILE *fp, const char *p, unsigned raw)
354{
355 size_t sz = strlen(p);
356 unsigned qq;
357 const char *q;
358
359 /* --- Just write the string null terminated if raw --- */
360
361 if (raw) {
362 fwrite(p, 1, sz + 1, fp);
363 return;
364 }
365
366 /* --- Check for any dodgy characters --- */
367
368 qq = 0;
369 for (q = p; *q; q++) {
370 if (isspace((unsigned char)*q)) {
371 qq = '\"';
372 break;
373 }
374 }
375
376 if (qq)
377 putc(qq, fp);
378
379 /* --- Emit the string --- */
380
381 for (q = p; *q; q++) {
382 switch (*q) {
383 case '\a': fputc('\\', fp); fputc('a', fp); break;
384 case '\b': fputc('\\', fp); fputc('b', fp); break;
385 case '\f': fputc('\\', fp); fputc('f', fp); break;
386 case '\n': fputc('\\', fp); fputc('n', fp); break;
387 case '\r': fputc('\\', fp); fputc('r', fp); break;
388 case '\t': fputc('\\', fp); fputc('t', fp); break;
389 case '\v': fputc('\\', fp); fputc('v', fp); break;
390 case '`': fputc('\\', fp); fputc('`', fp); break;
391 case '\'': fputc('\\', fp); fputc('\'', fp); break;
392 case '\"': fputc('\\', fp); fputc('\"', fp); break;
393 case '#': fputc('\\', fp); fputc('#', fp); break;
394 default:
395 putc(*q, fp);
396 break;
397 }
398 }
399
400 /* --- Done --- */
401
402 if (qq)
403 putc(qq, fp);
404}
405
406/*----- Guts --------------------------------------------------------------*/
407
408static int checkhash(const char *file, unsigned f, const gchash *gch)
409{
410 int rc;
411 FILE *fp;
412 dstr d = DSTR_INIT;
413 dstr dd = DSTR_INIT;
414 unsigned long n = 0, nfail = 0;
415 octet *buf = xmalloc(2 * gch->hashsz);
416
417 if (!file)
418 fp = stdin;
419 else if ((fp = fopen(file, f & f_raw ? "r" : "rb")) == 0) {
420 moan("couldn't open `%s': %s", file, strerror(errno));
421 return (EXIT_FAILURE);
422 }
423
424 while (DRESET(&d), dstr_putline(&d, fp) != EOF) {
425 char *p = d.buf;
426 char *q;
427 unsigned ff = f;
428
429 /* --- Handle a directive --- */
430
431 if (*p == '#') {
432 p++;
433 if ((q = str_getword(&p)) == 0)
434 continue;
435 if (strcmp(q, "hash") == 0) {
436 const gchash *g;
437 if ((q = str_getword(&p)) == 0)
438 continue;
439 if ((g = gethash(q)) == 0)
440 continue;
441 gch = g;
442 xfree(buf);
443 buf = xmalloc(2 * gch->hashsz);
444 } else if (strcmp(q, "escape") == 0)
445 f |= f_escape;
446 continue;
447 }
448
449 /* --- Otherwise it's a hex thing --- */
450
12902a5c 451 q = p;
452 while (*p && *p != ' ')
453 p++;
454 if (!*p)
e375fe33 455 continue;
12902a5c 456 *p++ = 0;
e375fe33 457 if (gethex(q, buf, gch->hashsz, 0) < gch->hashsz)
458 continue;
12902a5c 459 if (*p == '*')
e375fe33 460 ff |= f_binary;
12902a5c 461 else if (*p != ' ')
e375fe33 462 continue;
12902a5c 463 p++;
e375fe33 464
465 if (f & f_escape) {
466 DRESET(&dd);
467 getstring(0, p, &dd, 0);
468 p = dd.buf;
469 }
470
471 if (fhash(p, ff, gch, buf + gch->hashsz)) {
472 moan("couldn't read `%s': %s", p, strerror(errno));
473 rc = EXIT_FAILURE;
474 continue;
475 }
476 if (memcmp(buf, buf + gch->hashsz, gch->hashsz) != 0) {
477 if (ff & f_verbose)
478 fprintf(stderr, "FAIL %s\n", p);
479 else
480 moan("%s check failed for `%s'", gch->name, p);
481 nfail++;
482 rc = EXIT_FAILURE;
483 } else {
484 if (ff & f_verbose)
485 fprintf(stderr, "OK %s\n", p);
486 }
487 n++;
488 }
489
490 dstr_destroy(&d);
491 dstr_destroy(&dd);
492 xfree(buf);
493 if ((f & f_verbose) && nfail)
494 moan("%lu of %lu file(s) failed %s check", nfail, n, gch->name);
495 else if (!n)
496 moan("no files checked");
497 return (0);
498}
499
500static int dohash(const char *file, unsigned f, const gchash *gch)
501{
502 int rc = 0;
503 octet *p = xmalloc(gch->hashsz);
504
505 if (fhash(file, f, gch, p)) {
506 moan("couldn't read `%s': %s", file ? file : "<stdin>", strerror(errno));
507 rc = EXIT_FAILURE;
508 } else {
509 puthex(p, gch->hashsz, stdout);
510 if (file) {
511 fputc(' ', stdout);
512 fputc(f & f_binary ? '*' : ' ', stdout);
513 if (f & f_escape)
514 putstring(stdout, file, 0);
515 else
516 fputs(file, stdout);
517 }
518 fputc('\n', stdout);
519 }
520
521 xfree(p);
522 return (rc);
523}
524
12902a5c 525static int dofile(const char *file, unsigned f, const gchash *gch)
526{
527 return (f & f_check ? checkhash : dohash)(file, f, gch);
528}
529
e375fe33 530static int hashfiles(const char *file, unsigned f, const gchash *gch)
531{
532 FILE *fp;
533 dstr d = DSTR_INIT;
534 int rc = 0;
535 int rrc;
536
537 if (!file)
538 fp = stdin;
539 else if ((fp = fopen(file, f & f_raw ? "r" : "rb")) == 0) {
540 moan("couldn't open `%s': %s", file, strerror(errno));
541 return (EXIT_FAILURE);
542 }
543
544 for (;;) {
545 DRESET(&d);
546 if (getstring(fp, 0, &d, f & f_raw))
547 break;
12902a5c 548 if ((rrc = dofile(d.buf, f, gch)) != 0)
e375fe33 549 rc = rrc;
550 }
551
552 return (rc);
553}
554
555static int hashsum(const char *file, unsigned f, const gchash *gch)
556{
12902a5c 557 return (f & f_files ? hashfiles : dofile)(file, f, gch);
e375fe33 558}
559
560/*----- Main driver -------------------------------------------------------*/
561
562static void version(FILE *fp)
563{
564 pquis(fp, "$, Catacomb version " VERSION "\n");
565}
566
567static void usage(FILE *fp)
568{
5af0f9cb 569 pquis(fp, "Usage: $ [-f0ebcv] [-a algorithm] [files...]\n");
e375fe33 570}
571
572static void help(FILE *fp, const gchash *gch)
573{
574 version(fp);
575 fputc('\n', fp);
576 usage(fp);
577 pquis(fp, "\n\
578Generates or checks message digests on files. Options available:\n\
579\n\
580-h, --help Display this help message.\n\
581-V, --version Display program's version number.\n\
582-u, --usage Display a terse usage message.\n\
583\n\
584-a, --algorithm=ALG Use the message digest algorithm ALG.\n\
585\n\
586-f, --files Read a list of file names from standard input.\n\
587-0, --null File names are null terminated, not plain text.\n\
588\n\
589-e, --escape Escape funny characters in filenames.\n\
590-c, --check Check message digests rather than emitting them.\n\
591-b, --binary When reading files, treat them as binary.\n\
592-v, --verbose Be verbose when checking digests.\n\
593\n\
594For a list of supported message digest algorithms, type `$ --list'.\n\
595");
596 if (gch)
597 fprintf(fp, "The default message digest algorithm is %s.\n", gch->name);
598}
599
600int main(int argc, char *argv[])
601{
602 unsigned f = 0;
603 const gchash *gch = 0;
604 int rc;
605
606 /* --- Initialization --- */
607
608 ego(argv[0]);
609 sub_init();
610
611 /* --- Choose a hash function from the name --- */
612
613 {
614 char *q = xstrdup(QUIS);
615 size_t len = strlen(q);
616 if (len > 3 && strcmp(q + len - 3, "sum") == 0) {
617 q[len - 3] = 0;
618 gch = gethash(q);
619 }
620 if (!gch)
621 gch = hashtab[0];
622 xfree(q);
623 }
624
625 /* --- Read options --- */
626
627 for (;;) {
628 static struct option opts[] = {
629 { "help", 0, 0, 'h' },
630 { "verbose", 0, 0, 'V' },
631 { "usage", 0, 0, 'u' },
632
633 { "algorithm", OPTF_ARGREQ, 0, 'a' },
634 { "hash", OPTF_ARGREQ, 0, 'a' },
635 { "list", 0, 0, 'l' },
636
637 { "files", 0, 0, 'f' },
638 { "find", 0, 0, 'f' },
639 { "null", 0, 0, '0' },
640
641 { "escape", 0, 0, 'e' },
642 { "check", 0, 0, 'c' },
643 { "binary", 0, 0, 'b' },
644 { "verbose", 0, 0, 'v' },
645
646 { 0, 0, 0, 0 }
647 };
648 int i = mdwopt(argc, argv, "hVu a:l f0 ecbv", opts, 0, 0, 0);
649 if (i < 0)
650 break;
651
652 switch (i) {
653 case 'h':
654 help(stdout, gch);
655 exit(0);
656 case 'V':
657 version(stdout);
658 exit(0);
659 case 'u':
660 usage(stdout);
661 exit(0);
662 case 'a':
663 if ((gch = gethash(optarg)) == 0)
664 die(EXIT_FAILURE, "unknown hash algorithm `%s'", optarg);
665 f |= f_oddhash;
666 break;
667 case 'l': {
668 unsigned j;
669 for (j = 0; hashtab[j]; j++) {
670 if (j)
671 fputc(' ', stdout);
672 printf("%s", hashtab[j]->name);
673 }
674 fputc('\n', stdout);
675 exit(0);
676 } break;
677 case 'f':
678 f |= f_files;
679 break;
680 case '0':
681 f |= f_raw;
682 break;
683 case 'e':
684 f |= f_escape;
685 break;
686 case 'c':
687 f |= f_check;
688 break;
689 case 'b':
690 f |= f_binary;
691 break;
692 case 'v':
693 f |= f_verbose;
694 break;
695 default:
696 f |= f_bogus;
697 break;
698 }
699 }
700
701 if (f & f_bogus) {
702 usage(stderr);
703 exit(EXIT_FAILURE);
704 }
705 argv += optind;
706 argc -= optind;
707
708 /* --- Generate output --- */
709
710 if (!(f & f_check)) {
711 if (f & f_oddhash)
712 printf("#hash %s\n", gch->name);
713 if (f & f_escape)
714 fputs("#escape\n", stdout);
715 }
716
717 if (argc) {
718 int i;
719 int rrc;
720 rc = 0;
721 for (i = 0; i < argc; i++) {
722 if ((rrc = hashsum(argv[i], f, gch)) != 0)
723 rc = rrc;
724 }
725 } else
726 rc = hashsum(0, f, gch);
727
728 return (rc);
729}
730
731/*----- That's all, folks -------------------------------------------------*/