/*
 * md5sum: compute/check md5 checksums
 */

static const char rcsid[] = "Mastodon $Id: s.md5sum.c 1.3 00/05/21 16:33:42-00:00 orc $";

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <basis/options.h>
#include <basis/md5lib.h>
#include <string.h>
#include <unistd.h>

char *pgm;

/*
 * options that can be passed in to md5sum
 */
struct x_option options[] = {
    { 'c', 'c', "check", 0,		"compare checksums" },
    { 'v', 'v', "verbose", 0,		"print filenames during --check" },
    { 'b', 'b', "binary", 0,		"do binary checksum (ignored on Unix)" },
    { 't', 't', "text", 0,		"do text checksum (ignored on Unix)" },
    { 'h', 'h', "help", 0, 		"show this help message" },
    { 'V', 'V', "version", 0,		"show the version, then exit" },
};
#define NROPTIONS	(sizeof options/sizeof options[0])

/*
 * spit out a usage message, then quit
 */
static void
usage(char *pgm, int is_an_error)
{
    char *p;

    /* find the file part of the program name, so when we spit
     * out the usage message we don't include an unattractive
     * path component
     */
    if ( (p = strrchr(pgm, '/')) != 0)
	++p;
    else
	p = pgm;

    fprintf(stderr, "\nusage: %s [options]\n\n", p);
    showopts(stderr, NROPTIONS, options);
    putc('\n', stderr);

    exit (is_an_error ? 1 : 0);
} /* usage */


int verbose = 0;
enum {TEXT,BINARY} mode = BINARY;


/*
 * get_checksum() computes the md5 checksum for the given FILE*
 */
char *
get_checksum(int f)
{
    char blk[5120];
    int size;
    JH key;

    key = md5_init();

    while ((size = read(f, blk, sizeof blk)) > 0)
	md5_put(blk, size, key);

    return md5_finish(key, 0);
} /* get_checksum */


/*
 * checksum() writes out the checksum for a given FILE*
 */
void
checksum(int fd, char *fname)
{
    unsigned char *sum = get_checksum(fd);
    int x;

    for (x=0; x < 16; x++)
	printf("%02x", sum[x]);
    if (fname)
	printf("  %s\n", fname);
    else
	putchar('\n');
} /* checksum */


/*
 * verify_checksums() picks checksums off stdin, and compares them against
 * the current file checksums.
 */
void
verify_checksums()
{
    char line[2000];
    char format[33];
    char *p;
    unsigned char *newmd5;
    int c;
    int f;
    int x;
    int checked=0, validated=0;

    while (fgets(line, sizeof line, stdin)) {
	if ((p = strchr(line, '\n')) == 0) {
	    fprintf(stderr, "checksum line too long\n");
	    while ((c = getchar()) != EOF && c != '\n')
		;
	    if (c == EOF)
		break;
	    continue;
	}
	*p = 0;	/* trim at newline */
	p = line+32;			/* move pointer past md5 checksum */
	*p++ = 0;			/* trim md5 checksum */
	while (isspace(*p) && *p)	/* move to start of filename */
	    ++p;
	checked++;
	if ((f = open(p, O_RDONLY)) == -1) {
	    fprintf(stderr, "%s: can't open %s\n", pgm, p);
	    continue;
	}
	newmd5 = get_checksum(f);
	close(f);
	for (x=0; x < 16; x++)
	    sprintf(format+(x+x), "%02x", newmd5[x]);

	if (memcmp(line, format, 32) == 0) {
	    if (verbose)
		printf("%-24s OK\n", p);
	    validated++;
	}
	else {
	    if (verbose)
		printf("%-24s FAILED\n", p);
	    else
		printf("%s: md5check failed for %s\n", pgm, p);
	}
    }

    exit ( (checked == validated) ? 0 : 1);
} /* verify_checksums */


void /* I love standards */
main(int argc, char **argv)
{
    int opt;
    int x;
    int check = 0;

    pgm = basename(argv[0]);

    while ((opt = x_getopt(argc, argv, NROPTIONS, options)) != EOF)
	switch (opt) {
	case 'b':	mode = BINARY;			break;
	case 'c':	check = 1;			break;
	case 'v':	verbose = 1;			break;
	case 't':	mode = TEXT;			break;
	case 'V':	puts("md5sum $Revision: 1.3 $");	exit(0);
	default:
	case 'h':	usage(pgm, (opt == 'h'));	break;
	}

    argc -= x_optind;
    argv += x_optind;

    if (check)
	verify_checksums();
    else if (argc < 1)
	checksum(0, 0);
    else for (x=0; x<argc; x++) {
	int fd;

	if ((fd = open(argv[x], O_RDONLY)) == -1)
	    perror(argv[x]);
	else {
	    checksum(fd, argv[x]);
	    close(fd);
	}
    }
    exit(0);
} /* md5sum */
