/*	$OpenBSD: md5.c,v 1.97 2020/10/19 18:15:18 millert Exp $	*/

/*
 * Copyright (c) 2001,2003,2005-2007,2010,2013,2014
 *	Todd C. Miller <millert@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
 */

#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/queue.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <resolv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>

#include <md5.h>
#include <rmd160.h>
#include <sha1.h>
#include <sha2.h>
#include "crc.h"

#define STYLE_MD5	0
#define STYLE_CKSUM	1
#define STYLE_TERSE	2

#define MAX_DIGEST_LEN	128

#define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
#define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))

union ANY_CTX {
#if !defined(SHA2_ONLY)
	CKSUM_CTX cksum;
	MD5_CTX md5;
	RMD160_CTX rmd160;
	SHA1_CTX sha1;
#endif /* !defined(SHA2_ONLY) */
	SHA2_CTX sha2;
};

struct hash_function {
	const char *name;
	size_t digestlen;
	int style;
	int base64;
	void *ctx;	/* XXX - only used by digest_file() */
	void (*init)(void *);
	void (*update)(void *, const unsigned char *, size_t);
	void (*final)(unsigned char *, void *);
	char * (*end)(void *, char *);
	TAILQ_ENTRY(hash_function) tailq;
} functions[] = {
#if !defined(SHA2_ONLY)
	{
		"CKSUM",
		CKSUM_DIGEST_LENGTH,
		STYLE_CKSUM,
		-1,
		NULL,
		(void (*)(void *))CKSUM_Init,
		(void (*)(void *, const unsigned char *, size_t))CKSUM_Update,
		(void (*)(unsigned char *, void *))CKSUM_Final,
		(char *(*)(void *, char *))CKSUM_End
	},
	{
		"MD5",
		MD5_DIGEST_LENGTH,
		STYLE_MD5,
		0,
		NULL,
		(void (*)(void *))MD5Init,
		(void (*)(void *, const unsigned char *, size_t))MD5Update,
		(void (*)(unsigned char *, void *))MD5Final,
		(char *(*)(void *, char *))MD5End
	},
	{
		"RMD160",
		RMD160_DIGEST_LENGTH,
		STYLE_MD5,
		0,
		NULL,
		(void (*)(void *))RMD160Init,
		(void (*)(void *, const unsigned char *, size_t))RMD160Update,
		(void (*)(unsigned char *, void *))RMD160Final,
		(char *(*)(void *, char *))RMD160End
	},
	{
		"SHA1",
		SHA1_DIGEST_LENGTH,
		STYLE_MD5,
		0,
		NULL,
		(void (*)(void *))SHA1Init,
		(void (*)(void *, const unsigned char *, size_t))SHA1Update,
		(void (*)(unsigned char *, void *))SHA1Final,
		(char *(*)(void *, char *))SHA1End
	},
	{
		"SHA224",
		SHA224_DIGEST_LENGTH,
		STYLE_MD5,
		0,
		NULL,
		(void (*)(void *))SHA224Init,
		(void (*)(void *, const unsigned char *, size_t))SHA224Update,
		(void (*)(unsigned char *, void *))SHA224Final,
		(char *(*)(void *, char *))SHA224End
	},
#endif /* !defined(SHA2_ONLY) */
	{
		"SHA256",
		SHA256_DIGEST_LENGTH,
		STYLE_MD5,
		0,
		NULL,
		(void (*)(void *))SHA256Init,
		(void (*)(void *, const unsigned char *, size_t))SHA256Update,
		(void (*)(unsigned char *, void *))SHA256Final,
		(char *(*)(void *, char *))SHA256End
	},
#if !defined(SHA2_ONLY)
	{
		"SHA384",
		SHA384_DIGEST_LENGTH,
		STYLE_MD5,
		0,
		NULL,
		(void (*)(void *))SHA384Init,
		(void (*)(void *, const unsigned char *, size_t))SHA384Update,
		(void (*)(unsigned char *, void *))SHA384Final,
		(char *(*)(void *, char *))SHA384End
	},
	{
		"SHA512/256",
		SHA512_256_DIGEST_LENGTH,
		STYLE_MD5,
		0,
		NULL,
		(void (*)(void *))SHA512_256Init,
		(void (*)(void *, const unsigned char *, size_t))SHA512_256Update,
		(void (*)(unsigned char *, void *))SHA512_256Final,
		(char *(*)(void *, char *))SHA512_256End
	},
#endif /* !defined(SHA2_ONLY) */
	{
		"SHA512",
		SHA512_DIGEST_LENGTH,
		STYLE_MD5,
		0,
		NULL,
		(void (*)(void *))SHA512Init,
		(void (*)(void *, const unsigned char *, size_t))SHA512Update,
		(void (*)(unsigned char *, void *))SHA512Final,
		(char *(*)(void *, char *))SHA512End
	},
	{
		NULL,
	}
};

TAILQ_HEAD(hash_list, hash_function);

void digest_end(const struct hash_function *, void *, char *, size_t, int);
int  digest_file(const char *, struct hash_list *, int);
void digest_print(const struct hash_function *, const char *, const char *);
#if !defined(SHA2_ONLY)
int  digest_filelist(const char *, struct hash_function *, int, char **);
void digest_printstr(const struct hash_function *, const char *, const char *);
void digest_string(char *, struct hash_list *);
void digest_test(struct hash_list *);
void digest_time(struct hash_list *, int);
#endif /* !defined(SHA2_ONLY) */
void hash_insert(struct hash_list *, struct hash_function *, int);
void usage(void) __attribute__((__noreturn__));

extern char *__progname;
int qflag = 0;
FILE *ofile = NULL;

int
main(int argc, char **argv)
{
	struct hash_function *hf, *hftmp;
	struct hash_list hl;
	size_t len;
	char *cp, *input_string, *selective_checklist;
	const char *optstr;
	int fl, error, base64;
	int bflag, cflag, pflag, rflag, tflag, xflag;

	if (pledge("stdio rpath wpath cpath", NULL) == -1)
		err(1, "pledge");

	TAILQ_INIT(&hl);
	input_string = NULL;
	selective_checklist = NULL;
	error = bflag = cflag = pflag = qflag = rflag = tflag = xflag = 0;

#if !defined(SHA2_ONLY)
	if (strcmp(__progname, "cksum") == 0)
		optstr = "a:bC:ch:pqrs:tx";
	else
#endif /* !defined(SHA2_ONLY) */
		optstr = "bC:ch:pqrs:tx";

	/* Check for -b option early since it changes behavior. */
	while ((fl = getopt(argc, argv, optstr)) != -1) {
		switch (fl) {
		case 'b':
			bflag = 1;
			break;
		case '?':
			usage();
		}
	}
	optind = 1;
	optreset = 1;
	while ((fl = getopt(argc, argv, optstr)) != -1) {
		switch (fl) {
		case 'a':
			while ((cp = strsep(&optarg, " \t,")) != NULL) {
				if (*cp == '\0')
					continue;
				base64 = -1;
				for (hf = functions; hf->name != NULL; hf++) {
					len = strlen(hf->name);
					if (strncasecmp(cp, hf->name, len) != 0)
						continue;
					if (cp[len] == '\0') {
						if (hf->base64 != -1)
							base64 = bflag;
						break;	/* exact match */
					}
					if (cp[len + 1] == '\0' &&
					    (cp[len] == 'b' || cp[len] == 'x')) {
						base64 =
						    cp[len] == 'b' ?  1 : 0;
						break;	/* match w/ suffix */
					}
				}
				if (hf->name == NULL) {
					warnx("unknown algorithm \"%s\"", cp);
					usage();
				}
				if (hf->base64 == -1 && base64 != -1) {
					warnx("%s doesn't support %s",
					    hf->name,
					    base64 ? "base64" : "hex");
					usage();
				}
				/* Check for dupes. */
				TAILQ_FOREACH(hftmp, &hl, tailq) {
					if (hftmp->base64 == base64 &&
					    strcmp(hf->name, hftmp->name) == 0)
						break;
				}
				if (hftmp == NULL)
					hash_insert(&hl, hf, base64);
			}
			break;
		case 'b':
			/* has already been parsed */
			break;
		case 'h':
			ofile = fopen(optarg, "w");
			if (ofile == NULL)
				err(1, "%s", optarg);
			break;
#if !defined(SHA2_ONLY)
		case 'C':
			selective_checklist = optarg;
			break;
		case 'c':
			cflag = 1;
			break;
#endif /* !defined(SHA2_ONLY) */
		case 'p':
			pflag = 1;
			break;
		case 'q':
			qflag = 1;
			break;
		case 'r':
			rflag = 1;
			break;
		case 's':
			input_string = optarg;
			break;
		case 't':
			tflag++;
			break;
		case 'x':
			xflag = 1;
			break;
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	if (ofile == NULL)
		ofile = stdout;

	if (pledge("stdio rpath", NULL) == -1)
		err(1, "pledge");

	/* Most arguments are mutually exclusive */
	fl = pflag + (tflag ? 1 : 0) + xflag + cflag + (input_string != NULL);
	if (fl > 1 || (fl && argc && cflag == 0) || (rflag && qflag) ||
	    (selective_checklist != NULL && argc == 0))
		usage();
	if (selective_checklist || cflag) {
		if (TAILQ_FIRST(&hl) != TAILQ_LAST(&hl, hash_list))
			errx(1, "only a single algorithm may be specified "
			    "in -C or -c mode");
	}

	/* No algorithm specified, check the name we were called as. */
	if (TAILQ_EMPTY(&hl)) {
		for (hf = functions; hf->name != NULL; hf++) {
			if (strcasecmp(hf->name, __progname) == 0)
				break;
		}
		if (hf->name == NULL)
			hf = &functions[0];	/* default to cksum */
		hash_insert(&hl, hf, (hf->base64 == -1 ? 0 : bflag));
	}

	if (rflag || qflag) {
		const int new_style = rflag ? STYLE_CKSUM : STYLE_TERSE;
		TAILQ_FOREACH(hf, &hl, tailq) {
			hf->style = new_style;
		}
	}

#if !defined(SHA2_ONLY)
	if (tflag)
		digest_time(&hl, tflag);
	else if (xflag)
		digest_test(&hl);
	else if (input_string)
		digest_string(input_string, &hl);
	else if (selective_checklist) {
		int i;

		error = digest_filelist(selective_checklist, TAILQ_FIRST(&hl),
		    argc, argv);
		for (i = 0; i < argc; i++) {
			if (argv[i] != NULL) {
				warnx("%s does not exist in %s", argv[i],
				    selective_checklist);
				error++;
			}
		}
	} else if (cflag) {
		if (argc == 0)
			error = digest_filelist("-", TAILQ_FIRST(&hl), 0, NULL);
		else
			while (argc--)
				error += digest_filelist(*argv++,
				    TAILQ_FIRST(&hl), 0, NULL);
	} else
#endif /* !defined(SHA2_ONLY) */
	if (pflag || argc == 0)
		error = digest_file("-", &hl, pflag);
	else
		while (argc--)
			error += digest_file(*argv++, &hl, 0);

	return(error ? EXIT_FAILURE : EXIT_SUCCESS);
}

void
hash_insert(struct hash_list *hl, struct hash_function *hf, int base64)
{
	struct hash_function *hftmp;

	hftmp = malloc(sizeof(*hftmp));
	if (hftmp == NULL)
		err(1, NULL);
	*hftmp = *hf;
	hftmp->base64 = base64;
	TAILQ_INSERT_TAIL(hl, hftmp, tailq);
}

void
digest_end(const struct hash_function *hf, void *ctx, char *buf, size_t bsize,
    int base64)
{
	u_char *digest;

	if (base64 == 1) {
		if ((digest = malloc(hf->digestlen)) == NULL)
			err(1, NULL);
		hf->final(digest, ctx);
		if (b64_ntop(digest, hf->digestlen, buf, bsize) == -1)
			errx(1, "error encoding base64");
		free(digest);
	} else {
		hf->end(ctx, buf);
	}
}

#if !defined(SHA2_ONLY)
void
digest_string(char *string, struct hash_list *hl)
{
	struct hash_function *hf;
	char digest[MAX_DIGEST_LEN + 1];
	union ANY_CTX context;

	TAILQ_FOREACH(hf, hl, tailq) {
		hf->init(&context);
		hf->update(&context, string, strlen(string));
		digest_end(hf, &context, digest, sizeof(digest),
		    hf->base64);
		digest_printstr(hf, string, digest);
	}
}
#endif /* !defined(SHA2_ONLY) */

void
digest_print(const struct hash_function *hf, const char *what,
    const char *digest)
{
	switch (hf->style) {
	case STYLE_MD5:
		(void)fprintf(ofile, "%s (%s) = %s\n", hf->name, what, digest);
		break;
	case STYLE_CKSUM:
		(void)fprintf(ofile, "%s %s\n", digest, what);
		break;
	case STYLE_TERSE:
		(void)fprintf(ofile, "%s\n", digest);
		break;
	}
}

#if !defined(SHA2_ONLY)
void
digest_printstr(const struct hash_function *hf, const char *what,
    const char *digest)
{
	switch (hf->style) {
	case STYLE_MD5:
		(void)fprintf(ofile, "%s (\"%s\") = %s\n", hf->name, what, digest);
		break;
	case STYLE_CKSUM:
		(void)fprintf(ofile, "%s %s\n", digest, what);
		break;
	case STYLE_TERSE:
		(void)fprintf(ofile, "%s\n", digest);
		break;
	}
}
#endif /* !defined(SHA2_ONLY) */

int
digest_file(const char *file, struct hash_list *hl, int echo)
{
	struct hash_function *hf;
	FILE *fp;
	size_t nread;
	u_char data[32 * 1024];
	char digest[MAX_DIGEST_LEN + 1];

	if (strcmp(file, "-") == 0)
		fp = stdin;
	else if ((fp = fopen(file, "r")) == NULL) {
		warn("cannot open %s", file);
		return(1);
	}

	TAILQ_FOREACH(hf, hl, tailq) {
		if ((hf->ctx = malloc(sizeof(union ANY_CTX))) == NULL)
			err(1, NULL);
		hf->init(hf->ctx);
	}
	while ((nread = fread(data, 1UL, sizeof(data), fp)) != 0) {
		if (echo) {
			(void)fwrite(data, nread, 1UL, stdout);
			(void)fflush(stdout);
			if (ferror(stdout))
				err(1, "stdout: write error");
		}
		TAILQ_FOREACH(hf, hl, tailq)
			hf->update(hf->ctx, data, nread);
	}
	if (ferror(fp)) {
		warn("%s: read error", file);
		if (fp != stdin)
			fclose(fp);
		TAILQ_FOREACH(hf, hl, tailq) {
			free(hf->ctx);
			hf->ctx = NULL;
		}
		return(1);
	}
	if (fp != stdin)
		fclose(fp);
	TAILQ_FOREACH(hf, hl, tailq) {
		digest_end(hf, hf->ctx, digest, sizeof(digest), hf->base64);
		free(hf->ctx);
		hf->ctx = NULL;
		if (fp == stdin)
			fprintf(ofile, "%s\n", digest);
		else
			digest_print(hf, file, digest);
	}
	return(0);
}

#if !defined(SHA2_ONLY)
/*
 * Parse through the input file looking for valid lines.
 * If one is found, use this checksum and file as a reference and
 * generate a new checksum against the file on the filesystem.
 * Print out the result of each comparison.
 */
int
digest_filelist(const char *file, struct hash_function *defhash, int selcount,
    char **sel)
{
	int found, base64, error, cmp, i;
	size_t algorithm_max, algorithm_min;
	const char *algorithm;
	char *filename, *checksum, *line, *p, *tmpline;
	char digest[MAX_DIGEST_LEN + 1];
	ssize_t linelen;
	FILE *listfp, *fp;
	size_t len, linesize, nread;
	int *sel_found = NULL;
	u_char data[32 * 1024];
	union ANY_CTX context;
	struct hash_function *hf;

	if (strcmp(file, "-") == 0) {
		listfp = stdin;
	} else if ((listfp = fopen(file, "r")) == NULL) {
		warn("cannot open %s", file);
		return(1);
	}

	if (sel != NULL) {
		sel_found = calloc((size_t)selcount, sizeof(*sel_found));
		if (sel_found == NULL)
			err(1, NULL);
	}

	algorithm_max = algorithm_min = strlen(functions[0].name);
	for (hf = &functions[1]; hf->name != NULL; hf++) {
		len = strlen(hf->name);
		algorithm_max = MAXIMUM(algorithm_max, len);
		algorithm_min = MINIMUM(algorithm_min, len);
	}

	error = found = 0;
	line = NULL;
	linesize = 0;
	while ((linelen = getline(&line, &linesize, listfp)) != -1) {
		tmpline = line;
		base64 = 0;
		if (line[linelen - 1] == '\n')
			line[linelen - 1] = '\0';
		while (isspace((unsigned char)*tmpline))
			tmpline++;

		/*
		 * Crack the line into an algorithm, filename, and checksum.
		 * Lines are of the form:
		 *  ALGORITHM (FILENAME) = CHECKSUM
		 *
		 * Fallback on GNU form:
		 *  CHECKSUM  FILENAME
		 */
		p = strchr(tmpline, ' ');
		if (p != NULL && *(p + 1) == '(') {
			/* BSD form */
			*p = '\0';
			algorithm = tmpline;
			len = strlen(algorithm);
			if (len > algorithm_max || len < algorithm_min)
				continue;

			filename = p + 2;
			p = strrchr(filename, ')');
			if (p == NULL || strncmp(p + 1, " = ", (size_t)3) != 0)
				continue;
			*p = '\0';

			checksum = p + 4;
			p = strpbrk(checksum, " \t\r");
			if (p != NULL)
				*p = '\0';

			/*
			 * Check that the algorithm is one we recognize.
			 */
			for (hf = functions; hf->name != NULL; hf++) {
				if (strcasecmp(algorithm, hf->name) == 0)
					break;
			}
			if (hf->name == NULL || *checksum == '\0')
				continue;
			/*
			 * Check the length to see if this could be
			 * a valid checksum.  If hex, it will be 2x the
			 * size of the binary data.  For base64, we have
			 * to check both with and without the '=' padding.
			 */
			len = strlen(checksum);
			if (len != hf->digestlen * 2) {
				size_t len2;

				if (checksum[len - 1] == '=') {
					/* use padding */
					len2 = 4 * ((hf->digestlen + 2) / 3);
				} else {
					/* no padding */
					len2 = (4 * hf->digestlen + 2) / 3;
				}
				if (len != len2)
					continue;
				base64 = 1;
			}
		} else {
			/* could be GNU form */
			if ((hf = defhash) == NULL)
				continue;
			algorithm = hf->name;
			checksum = tmpline;
			if ((p = strchr(checksum, ' ')) == NULL)
				continue;
			if (hf->style == STYLE_CKSUM) {
				if ((p = strchr(p + 1, ' ')) == NULL)
					continue;
			}
			*p++ = '\0';
			while (isspace((unsigned char)*p))
				p++;
			if (*p == '\0')
				continue;
			filename = p;
			p = strpbrk(filename, "\t\r");
			if (p != NULL)
				*p = '\0';
		}
		found = 1;

		/*
		 * If only a selection of files is wanted, proceed only
		 * if the filename matches one of those in the selection.
		 */
		if (sel != NULL) {
			for (i = 0; i < selcount; i++) {
				if (strcmp(sel[i], filename) == 0) {
					sel_found[i] = 1;
					break;
				}
			}
			if (i == selcount)
				continue;
		}

		if ((fp = fopen(filename, "r")) == NULL) {
			warn("cannot open %s", filename);
			(void)printf("(%s) %s: %s\n", algorithm, filename,
			    (errno == ENOENT ? "MISSING" : "FAILED"));
			error = 1;
			continue;
		}

		hf->init(&context);
		while ((nread = fread(data, 1UL, sizeof(data), fp)) > 0)
			hf->update(&context, data, nread);
		if (ferror(fp)) {
			warn("%s: read error", file);
			error = 1;
			fclose(fp);
			continue;
		}
		fclose(fp);
		digest_end(hf, &context, digest, sizeof(digest), base64);

		if (base64)
			cmp = strncmp(checksum, digest, len);
		else
			cmp = strcasecmp(checksum, digest);
		if (cmp == 0) {
			if (qflag == 0)
				(void)printf("(%s) %s: OK\n", algorithm,
				    filename);
		} else {
			(void)printf("(%s) %s: FAILED\n", algorithm, filename);
			error = 1;
		}
	}
	free(line);
	if (ferror(listfp)) {
		warn("%s: getline", file);
		error = 1;
	}
	if (listfp != stdin)
		fclose(listfp);
	if (!found)
		warnx("%s: no properly formatted checksum lines found", file);
	if (sel_found != NULL) {
		/*
		 * Mark found files by setting them to NULL so that we can
		 * detect files that are missing from the checklist later.
		 */
		for (i = 0; i < selcount; i++) {
			if (sel_found[i])
				sel[i] = NULL;
		}
		free(sel_found);
	}
	return(error || !found);
}

#define TEST_BLOCK_LEN 10000
#define TEST_BLOCK_COUNT 10000

void
digest_time(struct hash_list *hl, int times)
{
	struct hash_function *hf;
	struct rusage start, stop;
	struct timeval res;
	union ANY_CTX context;
	u_int i;
	u_char data[TEST_BLOCK_LEN];
	char digest[MAX_DIGEST_LEN + 1];
	double elapsed;
	int count = TEST_BLOCK_COUNT;
	while (--times > 0 && count < INT_MAX / 10)
		count *= 10;

	TAILQ_FOREACH(hf, hl, tailq) {
		(void)printf("%s time trial.  Processing %d %d-byte blocks...",
		    hf->name, count, TEST_BLOCK_LEN);
		fflush(stdout);

		/* Initialize data based on block number. */
		for (i = 0; i < TEST_BLOCK_LEN; i++)
			data[i] = (u_char)(i & 0xff);

		getrusage(RUSAGE_SELF, &start);
		hf->init(&context);
		for (i = 0; i < count; i++)
			hf->update(&context, data, (size_t)TEST_BLOCK_LEN);
		digest_end(hf, &context, digest, sizeof(digest), hf->base64);
		getrusage(RUSAGE_SELF, &stop);
		timersub(&stop.ru_utime, &start.ru_utime, &res);
		elapsed = (double)res.tv_sec + (double)res.tv_usec / 1000000.0;

		(void)printf("\nDigest = %s\n", digest);
		(void)printf("Time   = %f seconds\n", elapsed);
		(void)printf("Speed  = %f bytes/second\n",
		    (double)TEST_BLOCK_LEN * count / elapsed);
	}
}

void
digest_test(struct hash_list *hl)
{
	struct hash_function *hf;
	union ANY_CTX context;
	int i;
	char digest[MAX_DIGEST_LEN + 1];
	unsigned char buf[1000];
	unsigned const char *test_strings[] = {
		"",
		"a",
		"abc",
		"message digest",
		"abcdefghijklmnopqrstuvwxyz",
		"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
		    "0123456789",
		"12345678901234567890123456789012345678901234567890123456789"
		    "012345678901234567890",
	};

	TAILQ_FOREACH(hf, hl, tailq) {
		(void)printf("%s test suite:\n", hf->name);

		for (i = 0; i < 8; i++) {
			hf->init(&context);
			hf->update(&context, test_strings[i],
			    strlen(test_strings[i]));
			digest_end(hf, &context, digest, sizeof(digest),
			    hf->base64);
			digest_printstr(hf, test_strings[i], digest);
		}

		/* Now simulate a string of a million 'a' characters. */
		memset(buf, 'a', sizeof(buf));
		hf->init(&context);
		for (i = 0; i < 1000; i++)
			hf->update(&context, buf, sizeof(buf));
		digest_end(hf, &context, digest, sizeof(digest), hf->base64);
		digest_print(hf, "one million 'a' characters",
		    digest);
	}
}
#endif /* !defined(SHA2_ONLY) */

void
usage(void)
{
#if !defined(SHA2_ONLY)
	if (strcmp(__progname, "cksum") == 0)
		fprintf(stderr, "usage: %s [-bcpqrtx] [-a algorithms] [-C checklist] "
		    "[-h hashfile]\n"
		    "	[-s string] [file ...]\n",
		    __progname);
	else
#endif /* !defined(SHA2_ONLY) */
		fprintf(stderr, "usage:"
		    "\t%s [-bcpqrtx] [-C checklist] [-h hashfile] [-s string] "
		    "[file ...]\n",
		    __progname);

	exit(EXIT_FAILURE);
}