P

md5 Utility

Here is a bit of code that others might find useful.

/*********************************************************************\

MODULE NAME:    md5.c

AUTHOR:         Bob Trower 03/11/05

PROJECT:        Crypt Data Packaging

COPYRIGHT:      Copyright (c) Trantor Standard Systems Inc., 2001, 2005

NOTES:          This is free software. This source code may be used as 
                you wish, subject to the LGPL license.  See the
                LICENCE section below.

                Canonical source should be at:
                    http://toogles.sourceforge.net

                This is a big header for a little utility, but it is
                designed to be self-contained, so this is pretty much
                the complete package in one file.


DESCRIPTION:
                This little utility implements the md5
                Message-Digest Algorithm described in RFC1321
                (http://www.faqs.org/rfcs/rfc1321.html).


DESIGN GOALS:   Specifically:
                Code is a stand-alone utility to perform md5 hashing.
                It should be genuinely useful when the need arises and
                it meets a need that is likely to occur for some
                users. Code acts as sample code to show the author's
                design and coding style.

                Generally:
                This program is designed to survive: Everything you
                need is in a single source file. It compiles cleanly
                using a vanilla ANSI C compiler. It does its job
                correctly with a minimum of fuss. The code is not
                overly clever, not overly simplistic and not overly
                verbose. Access is 'cut and paste' from a web page.
                Terms of use are reasonable.

VALIDATION:     Non-trivial code is never without errors.  This file
                likely has some problems, since it has only been
                tested by the author.  It is expected with most source
                code that there is a period of 'burn-in' when problems
                are identified and corrected.  That being said, it is
                possible to have 'reasonably correct' code by
                following a regime of unit test that covers the most
                likely cases and regression testing prior to release.
                This has been done with this code and it has a good
                probability of performing as expected.

                Unit Test Cases:

                Note that this program has a 'self test' function
                that tests the inputs given in the RFC document.

                The following are additional tests on the program
                itself and using test vectors from files.

                case 0:empty file:
                    CASE0.DAT
                    Hash Verified, errorlevel is: 0

                case 1:One input character (a):
                    CASE1.DAT
                    Hash Verified, errorlevel is: 0

                case 2:Three input characters (abc):
                    CASE2.DAT
                    Hash Verified, errorlevel is: 0

                case 3:'message digest':
                    CASE3.DAT
                    Hash Verified, errorlevel is: 0

                case 4:The alphabet:
                    CASE4.DAT
                    Hash Verified, errorlevel is: 0

                case 5:AlphaNumeric Characters:
                    CASE5.DAT
                    Hash Verified, errorlevel is: 0

                case 6:Lots of numbers:
                    CASE6.DAT
                    Hash Verified, errorlevel is: 0

                case 7: Large files:
                    Tested 67 MB file (OOo_1.1.4_Win32Intel_install.zip).
                    Correct hash as tested against another md5 program.

                case 8: Very Large Files:
                    Tested ISO images for Mandrake Linux
                    Correct hash.
                    Approximately 25-40MB/sec on 1GHz machine.
                    Times were the same to copy the files to the nul
                    device, so it is likely that performance is simply
                    I/O bound for this application.

                case 9 Stress:
                    All files in a working directory hashed. Spot
                    checked to ensure correct hashes and ensure
                    that multiple runs do not cause problems
                    such as exhausting file handles, tmp storage, etc.

                -------------

                Syntax, operation and failure:
                    All options/switches tested.  Performs as
                    expected.

                case 10:
                    No Args -- Shows Usage Screen
                    md5
                    Return Code 1 (Invalid Syntax)

                case 11:
                    One Arg -- Defaults to hashing as string
                    md5 "message digest"
                    Return Code 0 (Success)
                    Hash is correct.

                case 12:
                    One Arg Help (-?) -- Shows detailed Usage Screen.
                    Return Code 0 (Success -- help request is valid).

                case 13:
                    One Arg Help (-h) -- Shows detailed Usage Screen.
                    Return Code 0 (Success -- help request is valid).

                case 14:
                    One Arg -'f' (valid) -- Uses stdin/stdout (filter)
                    md5 -f < CASE3.DAT
                    Return Code 0 (Success)
                    CASE3.DAT used -- hash matches correctly.

                case 15:
                    Two Args (invalid filename) -- shows system error.
                    md5 -f :Is:Not:A\Valid/FileName
                    Return Code 2 (File Error)

                case 16:
                    hash non-existent file -- shows system error.
                    md5 -f NotLikelyAFileName.xyz
                    Return Code 2 (File Error)

                case 17:
                    Invalid disk -- shows system error.
                    md5 -f y:NoDiskHere
                    Return Code 2 (File Error)

                case 18:
                    Too many args -- shows system error.
                    md5 x y
                    Return Code 1 (Invalid Syntax)

                case 19:
                    Missing Args -- shows system error.
                    md5 -s
                    md5:004:Missing input -- nothing to hash.
                    Return Code 4 (Missing input)

                case 20:
                    Verify Option Missing Args -- shows error.
                    md5 -v FileNameButNoHash
                    md5:004:Missing input -- nothing to hash.
                    Return Code 4 (Missing input)

                case 21:
                    Verify Option Too Many Args -- shows error.
                    md5 -v x y z
                    md5:006:Syntax: Too many arguments.
                    Return Code 1 (Invalid Syntax)

                case 22:
                    Verify -- Syntax Ok, verify fails.
                    md5 -v CASE3.DAT bogushashvalue
                    bogushashvalue << Expected Hash Value
                    f96b697d7cb7938d525a2f31aaf161d0 << Actual Hash Value
                    Hash not verified. No match.
                    md5:007:Test Failure.

                    Return Code 7 (Test Failure)

                case 23:
                    Verify -- Verify passes.
                    md5 -v CASE3.DAT f96b697d7cb7938d525a2f31aaf161d0
                    f96b697d7cb7938d525a2f31aaf161d0 << Expected Hash Value
                    f96b697d7cb7938d525a2f31aaf161d0 << Actual Hash Value
                    Hash Verified

                    Return Code 0 (Success)

                case 24:
                    Self Test
                    md5 -t
                    d41d8cd98f00b204e9800998ecf8427e << Expected Hash Value
                    d41d8cd98f00b204e9800998ecf8427e << Actual Hash Value
                    0cc175b9c0f1b6a831c399e269772661 << Expected Hash Value
                    0cc175b9c0f1b6a831c399e269772661 << Actual Hash Value
                    900150983cd24fb0d6963f7d28e17f72 << Expected Hash Value
                    900150983cd24fb0d6963f7d28e17f72 << Actual Hash Value
                    f96b697d7cb7938d525a2f31aaf161d0 << Expected Hash Value
                    f96b697d7cb7938d525a2f31aaf161d0 << Actual Hash Value
                    c3fcd3d76192e4007dfb496cca67e13b << Expected Hash Value
                    c3fcd3d76192e4007dfb496cca67e13b << Actual Hash Value
                    d174ab98d277d9f5a5611c2c9f419d9f << Expected Hash Value
                    d174ab98d277d9f5a5611c2c9f419d9f << Actual Hash Value
                    57edf4a22be3c955ac49da2e2107b67a << Expected Hash Value
                    57edf4a22be3c955ac49da2e2107b67a << Actual Hash Value
                    Writing CASE0.DAT
                    d41d8cd98f00b204e9800998ecf8427e << Expected Hash Value
                    d41d8cd98f00b204e9800998ecf8427e << Actual Hash Value
                    Writing CASE1.DAT
                    0cc175b9c0f1b6a831c399e269772661 << Expected Hash Value
                    0cc175b9c0f1b6a831c399e269772661 << Actual Hash Value
                    Writing CASE2.DAT
                    900150983cd24fb0d6963f7d28e17f72 << Expected Hash Value
                    900150983cd24fb0d6963f7d28e17f72 << Actual Hash Value
                    Writing CASE3.DAT
                    f96b697d7cb7938d525a2f31aaf161d0 << Expected Hash Value
                    f96b697d7cb7938d525a2f31aaf161d0 << Actual Hash Value
                    Writing CASE4.DAT
                    c3fcd3d76192e4007dfb496cca67e13b << Expected Hash Value
                    c3fcd3d76192e4007dfb496cca67e13b << Actual Hash Value
                    Writing CASE5.DAT
                    d174ab98d277d9f5a5611c2c9f419d9f << Expected Hash Value
                    d174ab98d277d9f5a5611c2c9f419d9f << Actual Hash Value
                    Writing CASE6.DAT
                    57edf4a22be3c955ac49da2e2107b67a << Expected Hash Value
                    57edf4a22be3c955ac49da2e2107b67a << Actual Hash Value
                    Writing CASE7.DAT
                    Writing CASE8.DAT
                    Self Test Passed

                    Return Code 0 (Success)

                case 25:
                    Test convert test hash to lowercase for comparison.
                    md5 -v CASE3.DAT F96B697D7CB7938D525A2F31AAF161D0
                    f96b697d7cb7938d525a2f31aaf161d0 << Expected Hash Value
                    f96b697d7cb7938d525a2f31aaf161d0 << Actual Hash Value
                    Hash Verified

                    Return Code 0 (Success)

                -------------

                Compile/Regression test:
                    gcc compiled binary under Cygwin:
                        gcc md5.c -omd5.exe
                    Microsoft Visual Studio under Windows 2000
                        cl md5.c
                    Microsoft Version 6.0 C under Windows 2000
                        cl md5.c

DEPENDENCIES:   None

LICENCE:        NOTE: This license should be liberal enough for most
                purposes while still offering some protection to the
                code. If you find the license a problem, get in touch
                through http://www.trantor.ca and we will see what we
                can do. In particular, those who have been kind enough
                to make the original sources and other materials
                available can expect to use the code with more liberal
                permissions.

                Copyright (c) 2005 Bob Trower and Trantor Standard
                Systems Inc.

                This library is free software; you can redistribute it
                and/or modify it under the terms of the GNU Lesser
                General Public License as published by the Free
                Software Foundation; either version 2.1 of the
                License, or (at your option) any later version.

                This library is distributed in the hope that it will
                be useful, but WITHOUT ANY WARRANTY; without even the
                implied warranty of MERCHANTABILITY or FITNESS FOR A
                PARTICULAR PURPOSE.  See the GNU Lesser General Public
                License for more details.

                You should have received a copy of the GNU Lesser
                General Public License along with this library; if
                not, write to the Free Software Foundation, Inc., 59
                Temple Place, Suite 330, Boston, MA  02111-1307  USA

CREDITS:        Algorithm and (I think) sample code for the RFC was
                done by Ron Rivest.

                Colin Plumb wrote a public domain version in 1993.
                Some of this code derives from code that derives from
                that.

                From a header in one of the sources used to create
                this file:

                parts of this file are :

                Written March 1993 by Branko Lankester Modified June
                1993 by Colin Plumb for altered md5.c. Modified
                October 1995 by Erik Troan for RPM

                Although not used, code supplied by Langfine Ltd. was
                consulted and used for some of the validation.

                Although it was not used, code by John Walker was
                consulted. John is a source of inspiration. His
                actions speak for themselves. You can find lots of
                other neat stuff at his website:
                http://www.fourmilab.ch/

VERSION HISTORY:
                Bob Trower 03/11/05 -- Create Version 0.00.00B

\******************************************************************* */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
** typedefs for convenience
*/
typedef unsigned long mULONG;
typedef unsigned char mUCHAR;

typedef struct {
        mULONG hash[4];
        mULONG bits[2];
        mUCHAR data[64];
} MD5Context;

/* Basic MD5 functions */

#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) (y ^ (z & (x ^ y)))
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))

/* This is the central step in the MD5 algorithm. */
#define TRANSFORM(f, w, x, y, z, data, s) \
        ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )

/*
**
** md5_transform
**
** The MD5 "basic transformation". Updates the hash
** based on the data block passed.
**
*/
static void md5_transform( mULONG hash[ 4 ], const mULONG data[ 16 ] )
{
    mULONG a = hash[0], b = hash[1], c = hash[2], d = hash[3];

    /* Round 1 */
    TRANSFORM( F1, a, b, c, d, data[ 0] + 0xd76aa478,  7);
    TRANSFORM( F1, d, a, b, c, data[ 1] + 0xe8c7b756, 12);
    TRANSFORM( F1, c, d, a, b, data[ 2] + 0x242070db, 17);
    TRANSFORM( F1, b, c, d, a, data[ 3] + 0xc1bdceee, 22);
    TRANSFORM( F1, a, b, c, d, data[ 4] + 0xf57c0faf,  7);
    TRANSFORM( F1, d, a, b, c, data[ 5] + 0x4787c62a, 12);
    TRANSFORM( F1, c, d, a, b, data[ 6] + 0xa8304613, 17);
    TRANSFORM( F1, b, c, d, a, data[ 7] + 0xfd469501, 22);
    TRANSFORM( F1, a, b, c, d, data[ 8] + 0x698098d8,  7);
    TRANSFORM( F1, d, a, b, c, data[ 9] + 0x8b44f7af, 12);
    TRANSFORM( F1, c, d, a, b, data[10] + 0xffff5bb1, 17);
    TRANSFORM( F1, b, c, d, a, data[11] + 0x895cd7be, 22);
    TRANSFORM( F1, a, b, c, d, data[12] + 0x6b901122,  7);
    TRANSFORM( F1, d, a, b, c, data[13] + 0xfd987193, 12);
    TRANSFORM( F1, c, d, a, b, data[14] + 0xa679438e, 17);
    TRANSFORM( F1, b, c, d, a, data[15] + 0x49b40821, 22);

    /* Round 2 */
    TRANSFORM( F2, a, b, c, d, data[ 1] + 0xf61e2562,  5);
    TRANSFORM( F2, d, a, b, c, data[ 6] + 0xc040b340,  9);
    TRANSFORM( F2, c, d, a, b, data[11] + 0x265e5a51, 14);
    TRANSFORM( F2, b, c, d, a, data[ 0] + 0xe9b6c7aa, 20);
    TRANSFORM( F2, a, b, c, d, data[ 5] + 0xd62f105d,  5);
    TRANSFORM( F2, d, a, b, c, data[10] + 0x02441453,  9);
    TRANSFORM( F2, c, d, a, b, data[15] + 0xd8a1e681, 14);
    TRANSFORM( F2, b, c, d, a, data[ 4] + 0xe7d3fbc8, 20);
    TRANSFORM( F2, a, b, c, d, data[ 9] + 0x21e1cde6,  5);
    TRANSFORM( F2, d, a, b, c, data[14] + 0xc33707d6,  9);
    TRANSFORM( F2, c, d, a, b, data[ 3] + 0xf4d50d87, 14);
    TRANSFORM( F2, b, c, d, a, data[ 8] + 0x455a14ed, 20);
    TRANSFORM( F2, a, b, c, d, data[13] + 0xa9e3e905,  5);
    TRANSFORM( F2, d, a, b, c, data[ 2] + 0xfcefa3f8,  9);
    TRANSFORM( F2, c, d, a, b, data[ 7] + 0x676f02d9, 14);
    TRANSFORM( F2, b, c, d, a, data[12] + 0x8d2a4c8a, 20);

    /* Round 3 */
    TRANSFORM( F3, a, b, c, d, data[ 5] + 0xfffa3942,  4);
    TRANSFORM( F3, d, a, b, c, data[ 8] + 0x8771f681, 11);
    TRANSFORM( F3, c, d, a, b, data[11] + 0x6d9d6122, 16);
    TRANSFORM( F3, b, c, d, a, data[14] + 0xfde5380c, 23);
    TRANSFORM( F3, a, b, c, d, data[ 1] + 0xa4beea44,  4);
    TRANSFORM( F3, d, a, b, c, data[ 4] + 0x4bdecfa9, 11);
    TRANSFORM( F3, c, d, a, b, data[ 7] + 0xf6bb4b60, 16);
    TRANSFORM( F3, b, c, d, a, data[10] + 0xbebfbc70, 23);
    TRANSFORM( F3, a, b, c, d, data[13] + 0x289b7ec6,  4);
    TRANSFORM( F3, d, a, b, c, data[ 0] + 0xeaa127fa, 11);
    TRANSFORM( F3, c, d, a, b, data[ 3] + 0xd4ef3085, 16);
    TRANSFORM( F3, b, c, d, a, data[ 6] + 0x04881d05, 23);
    TRANSFORM( F3, a, b, c, d, data[ 9] + 0xd9d4d039,  4);
    TRANSFORM( F3, d, a, b, c, data[12] + 0xe6db99e5, 11);
    TRANSFORM( F3, c, d, a, b, data[15] + 0x1fa27cf8, 16);
    TRANSFORM( F3, b, c, d, a, data[ 2] + 0xc4ac5665, 23);

    /* Round 4 */
    TRANSFORM( F4, a, b, c, d, data[ 0] + 0xf4292244,  6);
    TRANSFORM( F4, d, a, b, c, data[ 7] + 0x432aff97, 10);
    TRANSFORM( F4, c, d, a, b, data[14] + 0xab9423a7, 15);
    TRANSFORM( F4, b, c, d, a, data[ 5] + 0xfc93a039, 21);
    TRANSFORM( F4, a, b, c, d, data[12] + 0x655b59c3,  6);
    TRANSFORM( F4, d, a, b, c, data[ 3] + 0x8f0ccc92, 10);
    TRANSFORM( F4, c, d, a, b, data[10] + 0xffeff47d, 15);
    TRANSFORM( F4, b, c, d, a, data[ 1] + 0x85845dd1, 21);
    TRANSFORM( F4, a, b, c, d, data[ 8] + 0x6fa87e4f,  6);
    TRANSFORM( F4, d, a, b, c, data[15] + 0xfe2ce6e0, 10);
    TRANSFORM( F4, c, d, a, b, data[ 6] + 0xa3014314, 15);
    TRANSFORM( F4, b, c, d, a, data[13] + 0x4e0811a1, 21);
    TRANSFORM( F4, a, b, c, d, data[ 4] + 0xf7537e82,  6);
    TRANSFORM( F4, d, a, b, c, data[11] + 0xbd3af235, 10);
    TRANSFORM( F4, c, d, a, b, data[ 2] + 0x2ad7d2bb, 15);
    TRANSFORM( F4, b, c, d, a, data[ 9] + 0xeb86d391, 21);

    hash[ 0 ] += a;
    hash[ 1 ] += b;
    hash[ 2 ] += c;
    hash[ 3 ] += d;
}

/*
** md5_init
**
** Initialise md5 context structure
**
*/
void md5_init( MD5Context *ctx )
{
    ctx->hash[ 0 ] = 0x67452301;
    ctx->hash[ 1 ] = 0xefcdab89;
    ctx->hash[ 2 ] = 0x98badcfe;
    ctx->hash[ 3 ] = 0x10325476;

    ctx->bits[ 0 ] = 0;
    ctx->bits[ 1 ] = 0;
}

/*
** md5_update
**
** Update context with the next buffer from the stream of data.
** Call with each block of data to update the md5 hash.
**
*/
void md5_update( MD5Context *ctx, const mUCHAR *buf, mULONG buflen )
{
    mULONG idx;

    /* Update bitcount */

    idx = ctx->bits[ 0 ];
    ctx->bits[ 0 ] = idx + (buflen << 3);
    if( ctx->bits[ 0 ] < idx ) {
        ctx->bits[ 1 ]++;         /* Carry from low to high */
    }
    ctx->bits[ 1 ] += buflen >> 29;

    idx = (idx >> 3) & 0x3f;    /* Bytes already in ctx->data */

    /* Handle any leading odd-sized chunks */

    if( idx != 0 ) {
        mUCHAR *p = (mUCHAR *) ctx->data + idx;

        idx = 64 - idx;
        if( buflen < idx ) {
            memcpy( p, buf, (size_t) buflen );
        }
        else {
            memcpy( p, buf, (size_t) idx );
            md5_transform( ctx->hash, (mULONG *) ctx->data );
            buf += idx;
            buflen -= idx;
        }
    }
    if( buflen >= idx ) {
        while( buflen >= 64 ) {
            memcpy( ctx->data, buf, 64 );
            md5_transform( ctx->hash, (mULONG *) ctx->data );
            buf += 64;
            buflen -= 64;
        }
        memcpy( ctx->data, buf, (size_t) buflen );
    }
}

/*
** md5_final
**
** Finalize creation of md5 hash and copy to digest buffer.
**
*/
void md5_final( MD5Context *ctx, mUCHAR digest[ 16 ] )
{
    mULONG count;
    mUCHAR *pad;

    count = (ctx->bits[ 0 ] >> 3) & 0x3F; /* Number of bytes mod 64 */
    pad = ctx->data + count;
    *pad++ = 0x80;

    /* Bytes of padding needed to make 64 bytes */
    count = 64 - 1 - count;

    /* Pad out to 56 mod 64 */
    if( count < 8 ) {
        /* Two lots of padding:  Pad the first block to 64 bytes */
        memset( pad, 0, (size_t) count );
        md5_transform( ctx->hash, (mULONG *) ctx->data );

        /* Now fill the next block with 56 bytes */
        memset( ctx->data, 0, 56 );
    } else {
        /* Pad block to 56 bytes */
        memset( pad, 0, (size_t) (count - 8) );
    }

    /* Append length in bits and transform */
    ((mULONG *) ctx->data)[ 14 ] = ctx->bits[ 0 ];
    ((mULONG *) ctx->data)[ 15 ] = ctx->bits[ 1 ];

    md5_transform( ctx->hash, (mULONG *) ctx->data );
    memcpy( digest, ctx->hash, 16 );
}

/*
** md5_digest_string
**
** Supply the digest and a buffer for the string.
** This routine will populate the buffer and
** return the value as a C string.
**
*/
char *md5_digest_string( mUCHAR d[ 16 ], char digest_string[ 33 ] )
{
    int i;

    digest_string[ 32 ] = 0;
    for( i = 0; i < 16; i++ ) {
        sprintf( &(digest_string[ i * 2 ]), "%2.2x", d[ i ] );
    }

    return( digest_string );
}


/*
** returnable errors
**
** Error codes returned to the caller
** and to the operating system.
**
*/
#define MD5_SYNTAX_ERROR        1
#define MD5_FILE_ERROR          2
#define MD5_FILE_IO_ERROR       3
#define MD5_MISSING_INPUT       4
#define MD5_ERROR_OUT_CLOSE     5
#define MD5_SYNTAX_TOOMANYARGS  6
#define MD5_TEST_FAILURE        7

/*
** md5_message
**
** Gather text messages in one place.
**
*/
char *md5_message( int errcode )
{
    #define MD5_MAX_MESSAGES 8
    char *msgs[ MD5_MAX_MESSAGES ] = {
            "md5:000:Invalid Message Code.",
            "md5:001:Syntax Error -- check help for usage.",
            "md5:002:File Error Opening/Creating Files.",
            "md5:003:File I/O Error -- Note: file cleanup not done.",
            "md5:004:Missing input -- nothing to hash.",
            "md5:005:Error on output file close.",
            "md5:006:Syntax: Too many arguments.",
            "md5:007:Test Failure."
    };
    char *msg = msgs[ 0 ];

    if( errcode > 0 && errcode < MD5_MAX_MESSAGES ) {
        msg = msgs[ errcode ];
    }

    return( msg );
}

/*
**
** md5_file
**
** Compute hash on an open file handle.
**
*/
#define MD5_BUFSIZE 4096
int md5_file( FILE *infile, mUCHAR digest[ 16 ] )
{
    int retcode;
    MD5Context ctx;
    mUCHAR buf[ MD5_BUFSIZE ];
    mULONG bytes_read;

    md5_init( &ctx );

    while( (bytes_read = fread (buf, sizeof (mUCHAR), MD5_BUFSIZE, infile)) > 0 ) {
        md5_update( &ctx, buf, bytes_read );
    }
    if( ferror( infile ) ) {
        retcode = MD5_FILE_IO_ERROR;
    }
    else {
        md5_final( &ctx, digest );
        retcode = 0;
    }

    return( retcode );
}

/*
**
** md5_filename
**
** Compute the hash on a file.
**
*/
int md5_filename( const char *infilename, mUCHAR digest[ 16 ] )
{
    FILE *infile;
    int retcode = MD5_FILE_ERROR;

    if( !infilename ) {
        infile = stdin;
    }
    else {
        infile = fopen( infilename, "rb" );
    }
    if( !infile ) {
        printf( "Error: FileName='%s' -- %s\n", infilename, strerror( errno ) );
    }
    else {
        retcode = md5_file( infile, digest );
        if( infile != stdin ) {
            if( fclose( infile ) != 0 ) {
                char *ErrM = md5_message( MD5_ERROR_OUT_CLOSE );
                printf( "Error: %s -- %s\n", ErrM, strerror( errno ) );
                retcode = MD5_FILE_IO_ERROR;
            }
        }
    }

    return( retcode );
}

/*
**
** md5_verify_filename
**
** Verify that a given file has a given hash.
**
*/
int md5_verify_filename( char *infilename, char *hash )
{
    int retcode = 0;
    mUCHAR digest[ 16 ];
    char digest_string[ 33 ];
    char *hd;

    md5_filename( infilename, digest );
    hd = md5_digest_string( digest, digest_string );
    printf( "%s << Expected Hash Value\n%s << Actual Hash Value\n", hash, hd );
    if( strcmp( hash, hd ) ){
        retcode = MD5_TEST_FAILURE;
    }

    return( retcode );
}

/*
**
** md5_buffer
**
** Compute the md5 hash on a buffer.
**
*/
void md5_buffer( const mUCHAR *buf, int buflen, mUCHAR digest[ 16 ] )
{
    MD5Context ctx;

    md5_init( &ctx );
    md5_update( &ctx, buf, buflen );
    md5_final( &ctx, digest );
}

/*
**
** Standard test vectors and expected hashes.
** Data below is used in the self-test function.
**
** The standard test vectors are augmented by
** a couple more cases to ease regression test.
**
*/
#define NUM_VECTORS 7
#define NUM_CASES   9
char *test_vector[] = {
    "",
    "a",
    "abc",
    "message digest",
    "abcdefghijklmnopqrstuvwxyz",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
    "12345678901234567890123456789012345678901234567890123456789012345678901234567890",

    "CASE7.DAT should be replaced with your own 10MB to 100MB file\n",
    "CASE8.DAT should be replaced with your own 100MB+  file\n"
};

char *test_hash[] = {
    "d41d8cd98f00b204e9800998ecf8427e",
    "0cc175b9c0f1b6a831c399e269772661",
    "900150983cd24fb0d6963f7d28e17f72",
    "f96b697d7cb7938d525a2f31aaf161d0",
    "c3fcd3d76192e4007dfb496cca67e13b",
    "d174ab98d277d9f5a5611c2c9f419d9f",
    "57edf4a22be3c955ac49da2e2107b67a",

    "04842407f40f4fc21f159bf89a7bf63e",
    "472c5207d61df9454c1e732930d1c496"
};

/*
**
** GenAndTestFileCases()
**
** Generates CASE data files for regression test
** and runs md5_verify_filename check.
**
*/
int GenAndTestFileCases()
{
    int i;
    char outfilename[16];
    FILE *outfile;
    int retcode = 0;

    for( i = 0; i < NUM_CASES && retcode == 0; i++ ) {
        sprintf( outfilename, "CASE%d.DAT", i );
        printf( "Writing %s\n", outfilename );
        outfile = fopen( outfilename, "wb" );
        if( !outfile ) {
            printf( "Error: FileName='%s' -- %s\n", outfilename, strerror( errno ) );
        }
        else {
            if( fprintf( outfile, "%s", test_vector[ i ] ) < 0 ) {
                printf( "Error: FileName='%s' -- %s\n", outfilename, strerror( errno ) );
                retcode = MD5_FILE_IO_ERROR;
            }
            if( fclose( outfile ) != 0 ) {
                char *ErrM = md5_message( MD5_ERROR_OUT_CLOSE );
                printf( "Error: %s -- %s\n", ErrM, strerror( errno ) );
                retcode = MD5_FILE_IO_ERROR;
            }
            else {
                if( i < NUM_VECTORS ) {
                    retcode = md5_verify_filename( outfilename, test_hash[ i ] );
                }
            }
        }
    }

    return( retcode );
}

/*
**
** md5_self_test
**
** Tests the core hashing functions against the
** test suite given in the RFC.
**
*/
int md5_self_test( void )
{
    int i, retcode = 0;
    mUCHAR digest[ 16 ];
    char digest_string[ 33 ];
    char *tv;
    char *hd;

    for( i = 0; i < NUM_VECTORS && retcode == 0; i++ ) {
        tv = test_vector[ i ];
        md5_buffer( (mUCHAR *) tv, strlen( tv ), digest );
        hd = md5_digest_string( digest, digest_string );
        printf( "%s << Expected Hash Value\n%s << Actual Hash Value\n", test_hash[ i ], hd );
        if( strcmp( hd, test_hash[ i ] ) ) {
            retcode = MD5_TEST_FAILURE;
        }
    }
    if( retcode == 0 ) {
        retcode = GenAndTestFileCases();
    }

    return( retcode );
}

/*
**
** LCaseHex
**
** Forces Hex string to lowercase.
**
** Make sure that we do apples to apples
** comparison of input hash to calculated hash.
**
*/
static char *LCaseHex( char *h )
{
    char *p;

    for( p = h; *p; p++ ) {
        *p = (char) (*p | 0x20);
    }

    return( h );
}

/*
** showuse
**
** display usage information, help, version info
*/
void showuse( int morehelp )
{
    {
        printf( "\n" );
        printf( "  md5      (create md5 hash)           Bob Trower 03/11/05 \n" );
        printf( "           (C) Copr Bob Trower 1986-2005.    Version 0.00B \n" );
        printf( "  Usage:   md5 [-option] <Input> [testhash]\n" );
        printf( "  Purpose: This program is a simple utility that\n" );
        printf( "           implements the md5 hashing algorithm (RFC1321).\n" );
    }
    if( !morehelp ) {
        printf( "           Use -h option for additional help.\n" );
    }
    else {
        printf( "  Options: -f  Input is filename. -s  Input is string.\n" );
        printf( "           -h  This help text.    -?  This help text.\n" );
        printf( "           -t  Self Test.         -v  Verify file hash.\n" );
        printf( "  Note:    -s  Is the default. It hashes the input as a\n" );
        printf( "               string\n" );
        printf( "  Returns: 0 = success.  Non-zero is an error code.\n" );
        printf( "  ErrCode: 1 = Bad Syntax         2 = File Open error\n" );
        printf( "           3 = File I/O error     4 = Missing input\n" );
        printf( "  Example: md5 -s Some_String         <- hash that string.\n" );
        printf( "           md5 -f SomeFileName        <- hash that file.\n" );
        printf( "           md5 -t                     <- Perform Self Test.\n" );
        printf( "           md5 -v filename, testhash  <- Verify file hash.\n" );
        printf( "  Source:  Source code and latest releases can be found at:\n" );
        printf( "           http://toogles.sourceforge.net\n" );
        printf( "  Release: 0.00.00, Fri Mar 11 03:30:00 2005, ANSI-SOURCE C\n" );
    }
}

#define THIS_OPT(ac, av) (ac > 1 ? av[ 1 ][ 0 ] == '-' ? av[ 1 ][ 1 ] : 0 : 0)

/*
** main
**
** parse and validate arguments and call md5 routines or help
**
*/
int main( int argc, char **argv )
{
    int opt = 0;
    int retcode = 0;
    char *input = NULL;
    mUCHAR digest[ 16 ];
    char digest_string[ 33 ];

    while( THIS_OPT( argc, argv ) ) {
        switch( THIS_OPT(argc, argv) ) {
            case 'f':
                    opt = 'f';
                    break;
            case '?':
            case 'h':
                    opt = 'h';
                    break;
            case 's':
                    opt = 's';
                    break;
            case 't':
                    opt = 't';
                    break;
            case 'v':
                    opt = 'v';
                    break;
             default:
                    opt = 's';
                    break;
        }
        argv++;
        argc--;
    }
    if( opt == 'v' && argc > 3  || (opt !='v' && argc > 2) ) {
        printf( "%s\n", md5_message( MD5_SYNTAX_TOOMANYARGS ) );
        opt = 0;
    }
    else {
        if( opt == 0 && argc == 2 ) {
            opt = 's';
        }
    }
    switch( opt ) {
        case 'f':
            input = argc > 1 ? argv[ 1 ] : NULL;
            retcode = md5_filename( input, digest );
            break;
        case 's':
            input = argc > 1 ? argv[ 1 ] : NULL;
            if( input ) {
                md5_buffer( (mUCHAR *) input, strlen( input ), digest );
                retcode = 0;
            }
            else {
                retcode = MD5_MISSING_INPUT;
            }
            break;
        case 't':
            retcode = md5_self_test();
            if( retcode == 0 ) {
                printf( "Self Test Passed\n" );
            }
            else {
                printf( "Self Test Failed\n" );
            }
            break;
        case 'v':
            if( argc < 3 ) {
                retcode = MD5_MISSING_INPUT;
            }
            else {
                retcode = md5_verify_filename( argv[ 1 ], LCaseHex( argv[ 2 ] ) );
                if( retcode == 0 ) {
                    printf( "Hash Verified\n" );
                }
                else {
                    printf( "Hash not verified. No match.\n" );
                }
            }                                                                   
            break;
        case 0:
            retcode = MD5_SYNTAX_ERROR;
        case 'h':
            showuse( opt );
            break;

    }
    if( retcode ) {
        printf( "%s\n", md5_message( retcode ) );
    }
    else {
        if( opt == 'f' || opt == 's' ) {
            printf( "%s\n", md5_digest_string( digest, digest_string ) );
        }
    }

    return( retcode );
}