/*
Copyright (c) 2009-2010, Dirk Krause
All rights reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.
* Redistributions in binary form must reproduce the above 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/



/**	@file	fchksum.c	The fchksum program.
*/



/*

-c	--check
	Compare checksums against list

-q	--quiet
	No output for succeeded tests when checking.

-s	--status
	No output when checking, set exit code only

-h	--help
	Show help text

-v	--version
	Show version information

-n	--no-header
	Do not write header line.

-l	--list
	The specified file names (or standard input) are lists
	containing file names of files to inspect.

-o	--md5sum
	Check input files was generated by md5sum.

-m dig	--message-digest=dig
	Choose message digest type (MD5, SHA-1, SHA-224, SHA-256, SHA-384,
	SHA-512, RIPEMD-160).

-e enc	--encoding=enc
	Choose message digest encoding (HEX, ASCII-85, R-ASCII-85).

Output format:
<digest> <name>
	<digest>	Encoded message digest.
	<name>		File name.

Exit codes:
0	success
1	checksum mismatch
2	Syntax or option error
3	Attempt to handle a directory
4	Illegal file type
5	Stat failed (file does not exist/no permissions?)
6	Failed to read input file
7	Failed to create checksum
8	Unsupported message digest type
*/


#include "fchksum.h"
#include "dktools-version.h"




#line 102 "fchksum.ctr"




/** Status code returned by the main function. */
static int exval = 0;

/** Flag: Quiet, show failed tests only. */
static unsigned char flag_quiet = 0x00;

/** Flag: Set exit code only. */
static unsigned char flag_status = 0x00;

/** Flag: check file list. */
static unsigned char flag_check = 0x00;

/** Flag: print help text. */
static unsigned char flag_help = 0x00;

/** Flag: Show version information. */
static unsigned char flag_version = 0x00;

/** Flag: Skip header line. */
static unsigned char flag_skip_header = 0x00;

/** Flag: Check list was generated by md5sum. */
static unsigned char flag_md5sum = 0x00;

/** Flag: Build checksums for directories too. */
static unsigned char flag_dir = 0x00;

/** Flag: This is the first file to handle. */
static unsigned char flag_is_first = 0X01;

/** Flag: Header was already printed. */
static unsigned char flag_header_printed = 0x00;

/** Flag: file names are lists of files to build digests for. */
static unsigned char flag_list = 0x00;

/** Flag: message digest type is implemented here. */
static unsigned char flag_implemented = 0x01;

/** Digest type. */
static int digest_type = CHKSUM_DIGEST_UNKNOWN;

/** Digest encoding. */
static int digest_encoding = CHKSUM_ENCODING_UNKNOWN;

/** Program name. */
static char progname[] = { "fchksum" };

/** Input buffer. */
static char buffer[4096];

/** Used to read lines from check list. */
static char inputline[1024];

/** Binary digest value. */
static unsigned char digest_value[256];

/** ASCII encoded digest value. */
static unsigned char digest_ascii[512];

/** Number of files checked. */
static unsigned long files_checked = 0UL;

/** Number of file checks failed. */
static unsigned long files_failed = 0UL;

/** Number of files skipped due to non-implemented MD types. */
static unsigned long files_skipped = 0UL;




#if ON_A_WINDOWS_SYSTEM
#if DK_HAVE__STAT64
/** Bit size for version number. */
#define VERSION_SIZE_BITS "64"
#else
/** Bit size for version number. */
#define VERSION_SIZE_BITS "32"
#endif
#else
#if DK_HAVE_STAT64 && DK_HAVE_LARGEFILE64_SOURCE
/** Bit size for version number. */
#define VERSION_SIZE_BITS "64"
#else
/** Bit size for version number. */
#define VERSION_SIZE_BITS "32"
#endif
#endif

/** Text to show version number and license conditions. */

static char *version_text[] = {
"fchksum - Checksum calculation program, version "
VERSNUMB ", " VERSION_SIZE_BITS "-bit",
"Copyright (C) 2008-2010 - Dipl.-Ing. Dirk Krause",
"http://dktools.sourceforge.net/fchksum.html",
"",
"Redistribution and use in source and binary forms, with or without",
"modification, are permitted provided that the following conditions are met:",
"* Redistributions of source code must retain the above copyright notice, this",
"  list of conditions and the following disclaimer.",
"* Redistributions in binary form must reproduce the above copyright notice,",
"  this list of conditions and the following disclaimer in the documentation",
"  and/or other materials provided with the distribution.",
"* Neither the name of the Dirk Krause nor the names of other contributors may",
"  be used to endorse or promote products derived from this software without",
"  specific prior written permission.",
"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''",
"AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE",
"IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE",
"ARE DISCLAIMED.",
"IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY",
"DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES",
"(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;",
"LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND",
"ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT",
"(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS",
"SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
"",
"This program uses the OpenSSL library.",
"This product includes cryptographic software written by",
"Eric Young (eay@cryptsoft.com).",
"",
NULL
};



/** Help text. */

static char *help_text[] = {
"",
"fchksum -h",
"\t\tprints a help text.",
"",
"fchksum -v",
"\t\tprints version information.",
"",
"fchksum [-m <dig>|--message-digest=<dig>] [-e <enc>|--encoding=<enc>] [-n] [-l] [<files>]",
"\t\tprints messages digests for the specified files.",
"\t\tdig: digest type, one of",
"\t\t     MD5",
"\t\t     RIPEMD-160",
"\t\t     SHA-1 (default)",
#if DK_HAVE_SHA224
"\t\t     SHA-224",
#else
"\t\t     SHA-224 (not implemented here)",
#endif
#if DK_HAVE_SHA256
"\t\t     SHA-256",
#else
"\t\t     SHA-256 (not implemented here)",
#endif
#if DK_HAVE_SHA384
"\t\t     SHA-384",
#else
"\t\t     SHA-384 (not implemented here)",
#endif
#if DK_HAVE_SHA512
"\t\t     SHA-512",
#else
"\t\t     SHA-512 (not implemented here)",
#endif
"\t\tenc: encoding, one of",
"\t\t     HEX, ASCII-85 (default), REVERSE-ASCII-85",
"\t\t-n   suppresses header line output.",
"\t\t     A85 and RA85 can be used as abbreviations for ASCII-85 and",
"\t\t     REVERSE-ASCII-85",
"\t\t-l   The files specified on the command line contain lists of files",
"\t\t     to check.",
"",
"fchksum -c|--check [-q|--quiet|-s|--status] [-o|--md5sum] [<files>]",
"\t\tchecks messages digests for files in the list.",
"\t\t-c    check files against a check list",
"\t\t-q    quiet, do not write report lines for succeeded checks",
"\t\t-s    set exit status code only, do not write any report lines",
"\t\t-o    handle check list produced by the md5sum program",
"\t\t      (default is to handle check lists produced by fchksum itself)",
"",
NULL
};



/** Digest type keywords. */

static char *digest_types[] = {
"SHA-1",
"SHA-224",
"SHA-256",
"SHA-384",
"SHA-512",
"MD5",
"RIPEMD-160",

/* +++++ Further message digest algorithms here */

NULL
};



/** Encoding keywords. */

static char *digest_encodings[] = {
"HEX",
"ASCII-85",
"REVERSE-ASCII-85",
"A85",
"RA85",
"R-ASCII-85",

/* +++++ Further binary-to-ASCII encodings here */

NULL
};



/** Long options keywords. */

static char *long_options[] = {
/*  0 */	"help",
/*  1 */	"version",
/*  2 */	"check",
/*  3 */	"quiet",
/*  4 */	"status",
/*  5 */	"no-header",
/*  6 */	"md5sum",
/*  7 */	"message-digest",
/*  8 */	"encoding",
/*  9 */	"directories",
/* 10 */	"list",
NULL
};



/** Control instructions. */

static char *control_instructions[] = {
/*  0 */	"DIGEST/ENCODING",
NULL
};



/**	Strings printed as messages.  */

static char *msg[] = {

/*  0 */
"%s: ERROR - Unknown digest type ``%s''!\n",

/*  1 */
"%s: ERROR - long option ``--%s'' needs an argument!\n",

/*  2 */
"%s: ERROR - Unknown encoding ``%s''!\n",

/*  3 */
"%s: ERROR - Unknown long option ``--%s''!\n",

/*  4 */
"%s: ERROR - Long option ``--%s'' too long!\n",

/*  5 */
"%s: ERROR - Unknown option ``-%c''!\n",

/*  6 */
"%s:%s: ERROR - Can not handle directories!\n",

/*  7 */
"%s:%s: Warning - Creating checksum for a device!\n",

/*  8 */
"%s:%s: ERROR - Illegal file type!\n",

/*  9 */
"%s:%s: ERROR - Failed to obtain information about file!\n",

/* 10 */
"%s:%s: ERROR - Failed to open file for reading!\n",

/* 11 */
"%s:%s:%lu: Syntax error!\n",

/* 12 */
"%s:%s:%lu: Syntax error - Not a key=value pair!\n",

/* 13 */
"%s:%s:%lu: Syntax error - Value text is missing!\n",

/* 14 */
"%s:%s:%lu: Syntax error - Unknown instruction \"%s\"!\n",

/* 15 */
"%s:%s:%lu: Syntax error - Missing encoding!\n",

/* 16 */
"%s: OK\n",

/* 17 */
"%s: FAILED\n",

/* 18 */
"%s: WARNING %lu of %lu computed checksums did NOT match!\n",

/* 19 */
"%s:%s: Pipe skipped.\n",

/* 20 */
"%s:%s: ERROR - Failed to create checksum!\n",

/* 21 */
"%s:%s:%lu ERROR - Message digest type \"%s\" not implemented!\n",

/* 22 */
"%s: ERROR - Message digest type \"%s\" not implemented!\n",

/* 23 */
"%s: ERROR - %lu files skipped (message digest not implemented)!\n",

NULL
};



/** File name to print when checksumming standard input. */

static char filename_for_stdin[] = { "-" };



/** Factors to add ASCII85-digits to byte quadrupel. */

static unsigned long f2[] = {
  1UL, 85UL, (85UL * 85UL), (85UL * 85UL * 85UL), (85UL * 85UL * 85UL * 85UL)
};



/** Characters to represent hexadecimal values. */

static char hex_digits[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', '\0'
};


/** Default set of whitespaces. */

static char default_whitespace_set[] = { " \t\r\n" };



/**	Fallback routine for systems without strchr().
	@param	str	The string to search.
	@param	c	The character to search for.
	@return	Pointer to character on success, NULL on error.
*/
char *
dkstr_chr(char *str, int c)
{
  char *back = NULL;	/**< Return value. */
  
  if(str) {
#if DK_HAVE_STRCHR
    back = strchr(str,c);
#else
    char *ptr;
    ptr = str;
    while((*ptr) && (!back)) {
      if(((int)(*ptr)) == c) {
	back = ptr;
      } else {
        ptr++;
      }
    }
#endif
  } 
  return back;
}



/**	Find start of string.
	@param	str	The text to find a string in.
	@param	whsp	Set of whitespaces.
	@return	Pointer to first text character on success, NULL on error.
*/
char *
dkstr_start(char *str, char *whsp)
{
  char *back = NULL;	/**< Return value. */
  char *ptr;		/**< Used to traverse string. */
  char *wh;		/**< Whitespace set. */
  
  if(str) {
    wh = (whsp ? whsp : default_whitespace_set);
    ptr = str;
    while((*ptr) && (!back)) {
      if(dkstr_chr(wh,((int)(*ptr)))) {
	ptr++;
      } else {
	back = ptr;
      }
    }
  } 
  return back;
}



/**	Find start of next string.
	@param	str	The text to find a string in.
	@param	whsp	Set of whitespaces.
	@return	Pointer to next text start on success, NULL on error.
*/
char    *
dkstr_next(char *str, char *whsp)
{
  char *back = NULL;	/**< Return value. */
  char *ptr;		/**< Used to traverse string. */
  char *wh;		/**< Whitespace set. */
  int state;		/**< Current state: 0=no text yet, 1=text, 2=done. */
  
  if(str) {
    ptr = str;
    wh = (whsp ? whsp : default_whitespace_set);
    state = 0;
    while((state < 2) && (*ptr)) {
      if(dkstr_chr(wh,((int)(*ptr)))) {
	if(state == 1) {
	  state = 2;
	  *(ptr++) = '\0';
	  back = dkstr_start(ptr,wh);
	}
	ptr++;
      } else {
	state = 1;
	ptr++;
      }
    }
  } 
  return back;
}



/**	Remove trailing whitespaces.
	@param	str	The string to modify.
	@param	whsp	Set of whitespaces.
*/
void
dkstr_chomp(char *str, char *whsp)
{
  char *wh;	/**< Whitespace set. */
  char *ptr;	/**< Used to traverse text. */
  char *x;	/**< First whitespace after last text. */
  
  if(str) {
    wh = (whsp ? whsp : default_whitespace_set);
    x = NULL; ptr = str;
    while(*ptr) {
      if(dkstr_chr(wh,((int)(*ptr)))) {
	if(!x) {
	  x = ptr;
	}
      } else {
	x = NULL;
      }
      ptr++;
    }
    if(x) { *x = '\0'; }
  }
  
}



#if !DK_HAVE_STRICMP
#if !DK_HAVE_STRCASECMP
/**	Fallback function if neither strcasecmp() nor stricmp() is available.
	@param	a	Left text in comparison.
	@param	b	Right text in comparison.
	@return	Text comparison result -1, 0 or 1 for a<b, a=b or a>b.
*/
static
int
portable_stricmp(char *a, char *b) {
  int back = 0;	/**< Return value. */
  int ende;	/**< Flag: finished. */
  char aval;	/**< Current left side character. */
  char bval;	/**< Current right side character. */
  char *aptr;	/**< Current left side pointer. */
  char *bptr;	/**< Current right side pointer. */
  
  if(a && b) {
    aptr = a; bptr = b;
    ende = 0;
    while((ende == 0) && (back == 0)) {
      aval = *aptr; bval = *bptr;
      if(aval && bval) {
	if(isascii(aval) && islower(aval)) aval = toupper(aval);
	if(isascii(bval) && islower(bval)) bval = toupper(bval);
	if(aval != bval) {
	  ende = 1;
	  if(aval > bval) back = 1;
	  else            back = -1;
	} else {
	  aptr++; bptr++;
	}
      } else {
	ende = 1;
	if(aval) { back = 1; }
	if(bval) { back = -1; }
      }
    }
  } 
  return back;
}
#endif
#endif



/**	Find string in array of strings, return index.
	@param	array	Array of pointers to strings.
	@param	str	String to search for in \a array.
	@param	cs	Flag: case-sensitive.
	@return	Index of \a str in \a array, or -1 on error (not found).
*/
int dkstr_array_index(char **array, char *str, int cs)
{
  int back = -1;	/**< Return value. */
  char **ptr;		/**< Current array element to test. */
  int i;		/**< Index of current element. */
  
  if(array && str) {
    i = 0; ptr = array;
    while((*ptr) && (back == -1)) {
      if(cs) {
	if(strcmp(*ptr,str) == 0) {
	  back = i;
	}
      } else {
	if(STRICMP(*ptr,str) == 0) {
	  back = i;
	}
      }
      if(back == -1) {
	ptr++; i++;
      }
    }
  } 
  return back;
}



/**	Multiplication of unsigned long values.
	@param	u1	One factor.
	@param	u2	Second factor.
	@param	ok	Pointer to variable for error code (if any).
	@return	The product of \a u1 and \a u2.
*/
static
unsigned long
dkma_mul_ulong_ok(unsigned long u1, unsigned long u2, int * ok)
{
  unsigned long back;	/**< Return value. */
  
  if(u1 > (DK_MAX_ULONG / u2)) {
    if(ok) { *ok = DK_ERR_MATH_OOR; }
  }
  back = u1 * u2;
  
  return back;
}



/**	Addition of unsigned long values.
	@param	u1	First value.
	@param	u2	Second value.
	@param	ok	Pointer to variable for error code (if any).
	@return	The summary of \a u1 and \a u2.
*/
static
unsigned long
dkma_add_ulong_ok(unsigned long u1, unsigned long u2, int *ok)
{
  unsigned long back;	/**< Return value. */
  
  if((DK_MAX_ULONG - u1) < u2) {
    if(ok) { *ok = DK_ERR_MATH_OOR; }
  }
  back = u1 + u2;
  
  return back;
}



/** Unsigned long value to retrieve LSB from unsigned long. */

static unsigned long last_byte = 0x000000FFUL;



/**	Find size of buffer needed to ASCII-85 encode data.
	@param	s	The size of the binary data.
	@return	Size of buffer needed for ASCII-85 encoded string.
*/
size_t
dkenc_size_bin_to_a85(size_t s) {
  size_t back = 0;	/**< Return value. */
  int ec = 0;		/**< Error code from math operations. */
  unsigned long ul1;	/**< \a s converted to unsigned long. */
  unsigned long ul2;	/**< u1 / 4. */
  unsigned long ul3;	/**< u1 % 4. */
  
  ul1 = (unsigned long)s;
  ul2 = ul1 % 4UL;
  ul3 = ul1 / 4UL;
  if(ul2) ul2++;			/* for last incomplete block */
  ul1 = dkma_add_ulong_ok(
    dkma_mul_ulong_ok(ul3, 5UL, &ec),
    ul2,
    &ec
  );					/* add 25 percent */
  ul1++;				/* final 0x00 byte for string */
  back = (size_t)ul1;
  if(ec) back = 0;			/* error checking */
  if((unsigned long)back != ul1) back = 0;
  
  return back;
}



/**	Do the conversion from binary data to reverse ASCII-85.
	@param	dp	Destination buffer.
	@param	ds	Size of \a dp in bytes.
	@param	sp	Source buffer (binary data).
	@param	ss	Size of \a sp in bytes.
*/
static
void
do_bin_to_ra85(char *dp, size_t ds, char *sp, size_t ss) {
  register char			*mysp;	/**< Pointer into source buffer. */
  register unsigned char	*mydp;	/**< Pointer into destination buffer. */
  register unsigned long	v;	/**< Temporary value. */
  register size_t		i;	/**< Index of current byte processed. */
  register short		vused;	/**< Number of bytes in \a v. */
  
  mydp = (unsigned char *)dp; mysp = sp; v = 0UL; vused = 0;
  for(i = 0; i < ss; i++) {	
    switch(vused++) {
      case 3: {
        v |=
	((((unsigned long)((unsigned char)(*(mysp++)))) << 24) & 0xFF000000UL);
      } break;
      case 2: {
        v |=
	((((unsigned long)((unsigned char)(*(mysp++)))) << 16) & 0x00FF0000UL);
      } break;
      case 1: {
        v |=
	((((unsigned long)((unsigned char)(*(mysp++)))) <<  8) & 0x0000FF00UL);
      } break;
      default: {
        v |=
	((((unsigned long)((unsigned char)(*(mysp++))))      ) & last_byte);
      } break;
    }
    if(vused >= 4) {
      *(mydp++) = (unsigned char)((v % 85UL) + 33UL);
      v = v / 85UL;
      *(mydp++) = (unsigned char)((v % 85UL) + 33UL);
      v = v / 85UL;
      *(mydp++) = (unsigned char)((v % 85UL) + 33UL);
      v = v / 85UL;
      *(mydp++) = (unsigned char)((v % 85UL) + 33UL);
      v = v / 85UL;
      *(mydp++) = (unsigned char)((v % 85UL) + 33UL);
      vused = 0; v = 0UL;
    }
  }
  if(vused) {
    vused++;
    while(vused--) {
      *(mydp++) = (unsigned char)((v % 85UL) + 33UL);
      v = v / 85UL;
    }
  }
  *mydp = '\0';	
}



/**	Create reverse ASCII-85 encoded string for binary data.
	@param	dp	Destination buffer.
	@param	ds	Size of \a dp in bytes.
	@param	sp	Source buffer (binary data).
	@param	ss	Size of \a sp in bytes.
	@return	1 on success, 0 on error (i.e. destination buffer too small).
*/
int
dkenc_bin_to_ra85(char *dp, size_t ds, char *sp, size_t ss) {
  int back = 0;		/**< Return value. */
  size_t needed_size;	/**< Minimum destination buffer size. */
  
  if((dp) && (sp) && (ds) && (ss)) {
    needed_size = dkenc_size_bin_to_a85(ss);
    if(needed_size) {
      if(ds >= needed_size) {
        do_bin_to_ra85(dp, ds, sp, ss);
	back = 1;
      }
    }
  } 
  return back;
}



/**	Do the conversion from binary data to ASCII-85 encoded string.
	@param	dp	Destination buffer.
	@param	ds	Size of \a dp in bytes.
	@param	sp	Source buffer (binary data).
	@param	ss	Size of \a sp in bytes.
*/
static
void
do_bin_to_a85(char *dp, size_t ds, char *sp, size_t ss) {
  register unsigned char *mydp;	/**< Pointer to destination buffer. */
  register unsigned char *mysp;	/**< Pointer to source buffer. */
  register unsigned long v;	/**< Temporary value. */
  register short vused;		/**< Number of bytes in v. */
  register short addval;	/**< Summand to add to index. */
  register size_t i;
  
  mydp = (unsigned char *)dp;
  mysp = (unsigned char *)sp;
  v = 0UL;
  vused = 0;
  for(i = 0; i < ss; i++) {
    switch(vused) {
      case 3: {
        v |= ( ((unsigned long)(*(mysp++)))        & 0x000000FFUL);
      } break;
      case 2: {
        v |= ((((unsigned long)(*(mysp++))) <<  8) & 0x0000FF00UL);
      } break;
      case 1: {
        v |= ((((unsigned long)(*(mysp++))) << 16) & 0x00FF0000UL);
      } break;
      default: {
        v |= ((((unsigned long)(*(mysp++))) << 24) & 0xFF000000UL);
      } break;
    }
    if(++vused >= 4) {
      vused = 5;
      while(vused--) {
        *(mydp++) = (unsigned char)(33UL + v / f2[vused]);
	v = v % f2[vused];
      }
      v = 0UL; vused = 0;
    }
  }
  if(vused) {
    vused++; addval = 5 - vused;
    while(vused--) {
      *(mydp++) = (unsigned char)(33UL + v / f2[vused + addval]);
      v = v % f2[vused + addval];
    }
  }
  *mydp = '\0'; 
}



/**	Create ASCII-85 encoded string representing binary data.
	@param	dp	Destination buffer.
	@param	ds	Size of \a dp in bytes.
	@param	sp	Source buffer (binary data).
	@param	ss	Size of \a sp in bytes.
	@return	1 on success, 0 on error (i.e. destination buffer too small).
*/
int
dkenc_bin_to_a85(char *dp, size_t ds, char *sp, size_t ss) {
  int back = 0;		/**< Return value. */
  size_t needed_size;	/**< Minimum size of destination buffer. */
  
  if((dp) && (sp) && (ds) && (ss)) {
    needed_size = dkenc_size_bin_to_a85(ss);
    if(needed_size) {
      if(ds >= needed_size) {
        do_bin_to_a85(dp, ds, sp, ss);
	back = 1;
      }
    }
  } 
  return back;
}



/**	Get size of buffer needed for a ASCII-Hex encoded string.
	@param	s	Size of the original (binary) data.
	@return	Minimum buffer size needed for the ASCII-Hex encoded string.
*/
size_t
dkenc_size_bin_to_hex(size_t s) {
  size_t back = 0;	/**< Return value. */
  int ec = 0;		/**< Mathematical error code. */
  unsigned long ul1;	/**< s converted to unsigned long. */
  
  ul1 = (unsigned long)s;
  ul1 = dkma_mul_ulong_ok(ul1, 2UL, &ec);
  ul1 = dkma_add_ulong_ok(ul1, 1UL, &ec);
  back = (size_t)ul1;
  if(ec) back = 0;
  if((unsigned long)back != ul1) back = 0;
  
  return back;
}



/**	Get character representing the higher half-byte in a character.
	@param	c	Character to get half-byte from.
	@return	Hexadecimal character.
*/
static
char
high_nibble_hex(char c) {
  char back;	/**< Return value. */
  back = hex_digits[ (((unsigned short)c) >> 4) & 0x000FU ];
  return back;
}



/**	Get character representing the lower half-byte in a character.
	@param	c	Character to get half-byte from.
	@return	Hexadecimal character.
*/
static
char
low_nibble_hex(char c) {
  char back;	/**< Return value. */
  back = hex_digits[  ((unsigned short)c)       & 0x000FU ];
  return back;
}



/**	Do the conversion from binary data to ASCII-Hex encoded string.
	@param	dp	Destination buffer.
	@param	ds	Size of \a dp in bytes.
	@param	sp	Source buffer (binary data).
	@param	ss	Size of \a sp in bytes.
*/
static
void
do_bin_to_hex(char *dp, size_t ds, char *sp, size_t ss) {
  register char *mydp;	/**< Pointer into destination buffer. */
  register char *mysp;	/**< Pointer into source buffer. */
  register size_t i;	/**< Running index to process all bytes in sp. */
  
  mydp = dp; mysp = sp;
  for(i = 0; i < ss; i++) {
    *(mydp++) = high_nibble_hex(*mysp);
    *(mydp++) = low_nibble_hex(*(mysp++));
  }
  *mydp = '\0'; 
}



/**	Create ASCII-Hex encoded string for binary data.
	@param	dp	Destination buffer.
	@param	ds	Size of \a dp in bytes.
	@param	sp	Source buffer (binary data).
	@param	ss	Size of \a sp in bytes.
	@return	1 on success, 0 on error (i.e. destination buffer too small).
*/
int
dkenc_bin_to_hex(char *dp, size_t ds, char *sp, size_t ss) {
  int back = 0;		/**< Return value. */
  size_t needed_bytes;	/**< Minimum size of destination buffer. */
  
  if((dp) && (ds) && (sp) && (ss)) {
    needed_bytes = dkenc_size_bin_to_hex(ss);
    if(needed_bytes) {
      if(ds >= needed_bytes) {
        do_bin_to_hex(dp, ds, sp, ss); back = 1;
      }
    }
  } 
  return back;
}



/**	Report error for failed checksumming.
	@param	fn	File name for which checksumming failed.
*/
static
void
err_checksum_failed(char *fn) {
  fprintf(stderr, msg[20], progname, fn); fflush(stderr);
  if(exval != 1) exval = 7;
}



/**	Error message for a missing message digest implementation.
	@param	fn	Name of configuration file (may be NULL).
	@param	lineno	Line number in configuration file (may be 0UL).
	@param	t	Text containing the message digest name.
*/
static
void
err_not_implemented(char *fn, unsigned long lineno, char *t) {
  if((fn) && (lineno)) {
    fprintf(stderr, msg[21], progname, fn, lineno, t);
  } else {
    fprintf(stderr, msg[22], progname, t);
  }
  fflush(stderr);
}


/**	Error message: Text is not a key/value pair.
	@param	fn	Source file name.
	@param	lineno	Line number.
*/
static
void
err_not_key_value(char *fn, unsigned long lineno) {
  fprintf(stderr, msg[12], progname, fn, lineno); fflush(stderr);
  if(exval != 1) exval = 2;
}



/**	Error message: No value text.
	@param	fn	Source file name.
	@param	lineno	Line number.
*/
static
void
err_missing_value(char *fn, unsigned long lineno) {
  fprintf(stderr, msg[13], progname, fn, lineno); fflush(stderr);
  if(exval != 1) exval = 2;
}



/**	Error message: Unknown instruction.
	@param	fn	Source file name.
	@param	lineno	Line number.
	@param	t	Text to print.
*/
static
void
err_unknown_instruction(char *fn, unsigned long lineno, char *t) {
  fprintf(stderr, msg[14], progname, fn, lineno, t); fflush(stderr);
  if(exval != 1) exval = 2;
}



/**	Error message: The encoding description is missing.
	@param	fn	Source file name.
	@param	lineno	Line number.
*/
static
void
err_missing_encoding(char *fn, unsigned long lineno) {
  fprintf(stderr, msg[15], progname, fn, lineno); fflush(stderr);
  if(exval != 1) exval = 2;
}



/**	Error message: Syntax error.
	@param	fn	Source file name.
	@param	lineno	Line number.
*/
static
void
err_syntax_general(char *fn, unsigned long lineno) {
  fprintf(stderr, msg[11], progname, fn, lineno);
}



/**	Error message: Failed to open file for reading.
	@param	s	File name.
*/
static
void
err_open_read(char *s) {
  fprintf(stderr, msg[10], progname, s); fflush(stderr);
  if(exval != 1) exval = 6;
}



/**	Error message: stat() failed.
	@param	s	File name.
*/
static
void
err_stat_failed(char *s) {
  fprintf(stderr, msg[9], progname, s); fflush(stderr);
  if(exval != 1) exval = 5;
}



/**	Error message: File is of wrong type.
	@param	s	File name.
*/
static
void
err_illegal_file_type(char *s) {
  fprintf(stderr, msg[8], progname, s); fflush(stderr);
  if(exval != 1) exval = 4;
}



/**	Warning: This path name is a device.
	@param	s	File name.
*/
static
void
warn_device(char *s) {
  fprintf(stderr, msg[7], progname, s); fflush(stderr);
}



#if ON_A_WINDOWS_SYSTEM
/**	Warning: Skipping pipe.
	@param	s	File name.
*/
static
void
warn_pipe(char *s) {
  fprintf(stderr, msg[19], progname, s); fflush(stderr);
}
#endif



/**	Error message: Directory.
	@param	s	File name.
*/
static
void
err_directory(char *s) {
  fprintf(stderr, msg[6], progname, s); fflush(stderr);
  if(exval != 1) exval = 3;
}



/**	Error message: Illegal option.
	@param	c	The option to complain about.
*/
static
void
err_unknown_option(char c) {
  fprintf(stderr, msg[5], progname, c); fflush(stderr);
  if(exval != 1) exval = 2; flag_help = 0x01;
}



/**	Error message: Long option too long for buffer.
	@param	s	The long option to complain about.
*/
static
void
err_option_too_long(char *s) {
  fprintf(stderr, msg[4], progname, s); fflush(stderr);
  if(exval != 1) exval = 2; flag_help = 0x01;
}



/**	Error message: Unknown long option.
	@param	s	The option to complain about.
*/
static
void
err_unknown_long_option(char *s) {
  fprintf(stderr, msg[3], progname, s); fflush(stderr);
  if(exval != 1) exval = 2; flag_help = 0x01;
}



/**	Error message: Unknown digest type.
	@param	s	The digest type.
*/
static
void
err_unknown_digest_type(char *s) {
  fprintf(stderr, msg[0], progname, s); fflush(stderr);
  if(exval != 1) exval = 2; flag_help = 0x01;
}



/**	Error message: Unknown encoding.
	@param	s	The encoding.
*/
static
void
err_unknown_encoding(char *s) {
  fprintf(stderr, msg[2], progname, s); fflush(stderr);
  if(exval != 1) exval = 2; flag_help = 0x01;
}



/**	Error message: Long option needs a value.
	@param	s	Option.
*/
static
void
err_long_opt_arg_missing(char *s) {
  fprintf(stderr, msg[1], progname, s); fflush(stderr);
  if(exval != 1) exval = 2; flag_help = 0x01;
}



/**	Show text (NULL terminated array of string pointers).
	@param	t	Text to show.
*/
static
void
show_text(char **t) {
  char **ptr;	/**< Pointer to traverse the array. */
  ptr = t;
  while(*ptr) {
    fputs(*(ptr++), stdout); fputc('\n', stdout);
  }
}



/**	Set digest type.
	@param	t	Text representation of digest.
	@param	fn	File name (used for error reporting, may be NULL).
	@param	lineno	Line number (used for error reporting, may be 0UL).
	@return	1 on success, 0 on error.
*/
static
int
set_digest_type(char *t, char *fn, unsigned long lineno) {
  int back = 0;	/**< Return value. */
  int res;	/**< Index of t in array of digest types. */
  res = dkstr_array_index(digest_types, t, 0);
  if(res > -1) {
    digest_type = res + 1;
    flag_implemented = 0x01;
    switch(digest_type) {
      case CHKSUM_DIGEST_SHA_224: {
#if !DK_HAVE_SHA224
        flag_implemented = 0x00;
#endif
      } break;
      case CHKSUM_DIGEST_SHA_256: {
#if !DK_HAVE_SHA256
        flag_implemented = 0x00;
#endif
      } break;
      case CHKSUM_DIGEST_SHA_384: {
#if !DK_HAVE_SHA384
        flag_implemented = 0x00;
#endif
      } break;
      case CHKSUM_DIGEST_SHA_512: {
#if !DK_HAVE_SHA512
	flag_implemented = 0x00;
#endif
      } break;
    }
    if(!flag_implemented) {
      err_not_implemented(fn, lineno, t);
    }
    back = 1;
  } else {
    if((fn) && (lineno)) {
      err_syntax_general(fn, lineno);
    }
    err_unknown_digest_type(t);
  }
  return back;
}



/**	Set encoding.
	@param	t	Text representation of encoding.
	@param	fn	File name (used for error reporting, may be NULL).
	@param	lineno	Line number (used for error reporting, may be 0UL).
	@return	1 on success, 0 on error.
*/
static
int
set_digest_encoding(char *t, char *fn, unsigned long lineno) {
  int back = 0;	/**< Return value. */
  int res;	/**< Index of t in encoding types array. */
  res = dkstr_array_index(digest_encodings, t, 0);
  if(res > -1) {
    switch(res) {
      case 1: case 3: { digest_encoding = CHKSUM_ENCODING_ASCII85; } break;
      case 2: case 4: case 5: {
        digest_encoding = CHKSUM_ENCODING_RA85;
      } break;
      default: { digest_encoding = CHKSUM_ENCODING_HEX; } break;
    }
  } else {
    if((fn) && (lineno)) {
      err_syntax_general(fn, lineno);
    }
    err_unknown_encoding(t);
  }
  return back;
}



/**	Process command line arguments.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
*/
static
void
process_arguments(int argc, char **argv) {
  char **lfdptr;	/**< Traverse arguments. */
  char *ptr;		/**< Current argument. */
  char *argptr;		/**< Long option value (if any). */
  int	i;		/**< Current argument number. */
  int	res;		/**< Search result. */
  
  lfdptr = argv; lfdptr++; i = 1;
  while(i < argc) {	
    ptr = *lfdptr;	
    if(*ptr == '-') {
      ptr++;
      switch(*ptr) {
        case '-': {	
	  ptr++;
	  if(strlen(ptr) < sizeof(buffer)) {
	    strcpy(buffer, ptr);
	    argptr = dkstr_chr(buffer, '=');
	    if(argptr) { *(argptr++) = '\0'; }
	    res = dkstr_array_index(long_options, buffer, 1);
	    if(res >= 0) {
	      switch(res) {
	        case 0: { flag_help = 0x01; } break;
	        case 1: { flag_version = 0x01; } break;
	        case 2: { flag_check = 0x01; } break;
	        case 3: { flag_quiet = 0x01; } break;
	        case 4: { flag_status = 0x01; } break;
	        case 5: { flag_skip_header = 0x01; } break;
	        case 6: { flag_md5sum = 0x01; } break;
	        case 7: {	
	          if(argptr) {
		    set_digest_type(argptr, NULL, 0UL);
		  } else {
		    err_long_opt_arg_missing(buffer);
		  }
	        } break;
	        case 8: {	
	          if(argptr) {
		    set_digest_encoding(argptr, NULL, 0UL);
		  } else {
		    err_long_opt_arg_missing(buffer);
		  }
	        } break;
	        case 9: { flag_dir = 0x01; } break;
		case 10: { flag_list = 0x01; } break;
	      }
	    } else {
	      err_unknown_long_option(buffer);
	    }
	  } else {
	    err_option_too_long(ptr);
	  }
	} break;
	case 'h': { flag_help = 0x01; } break;
	case 'v': { flag_version = 0x01; } break;
	case 'c': { flag_check = 0x01; } break;
	case 's': { flag_status = 0x01; } break;
	case 'q': { flag_quiet = 0x01; } break;
	case 'l': { flag_list = 0x01; } break;
	case 'm': {
	  ptr++;	
	  if(!(*ptr)) {
	    ptr = NULL;
	    lfdptr++; i++;
	    if(i < argc) {
	      ptr = *lfdptr;
	    }
	  }
	  if(ptr) {
	    set_digest_type(ptr, NULL, 0UL);
	  }		
	} break;
	case 'e': {
	  ptr++;
	  if(!(*ptr)) {
	    ptr = NULL;
	    lfdptr++; i++;
	    if(i < argc) {
	      ptr = *lfdptr;
	    }
	  }
	  if(ptr) {
	    set_digest_encoding(ptr, NULL, 0UL);
	  }
	} break;
	case 'n': { flag_skip_header = 0x01; } break;
	case 'o': { flag_md5sum = 0x01; } break;
	case 'd': { flag_dir = 0x01; } break;
	case '\0': break;
	default: { err_unknown_option(*ptr); } break;
      }
    }
    lfdptr++; i++; 
  } 
}



/**	Process control line from check list.
	@param	l	The control line to process without tab,#,#.
	@param	fn	File name (used for error reporting, may be NULL).
	@param	lineno	Line number (used for error reporting, may be 0UL).
*/
static
void
use_control_line(char *l, char *fn, unsigned long lineno) {
  char *pinst;		/**< Instruction. */
  char *pval;		/**< Value. */
  char *p2;		/**< second word in value. */
  char *p3;		/**< third word in value. */
  int  res;		/**< Check result. */
  pinst = pval = p2 = p3 = NULL;
  pinst = dkstr_start(l, NULL);
  if(pinst) {
    pval = dkstr_chr(pinst, '=');
    if(pval) {
      dkstr_chomp(pinst, NULL);
      *(pval++) = '\0';
      pval = dkstr_start(pval, NULL);
      if(pval) {
        p2 = dkstr_next(pval, NULL);
	if(p2) {
	  p3 = dkstr_next(p2, NULL);
	}
	if(pinst) dkstr_chomp(pinst, NULL);
	if(pval) dkstr_chomp(pval, NULL);
	if(p2) dkstr_chomp(p2, NULL);
	if(p3) dkstr_chomp(p3, NULL);
	res = dkstr_array_index(control_instructions, pinst, 0);
	switch(res) {
	  case 0: {
	    if(p2) {
	      set_digest_type(pval, fn, lineno);
	      set_digest_encoding(p2, fn, lineno);
	    } else {
	      err_missing_encoding(fn, lineno);
	    }
	  } break;
	  default: {
	    err_unknown_instruction(fn, lineno, pinst);
	  } break;
	}
      } else {
        err_missing_value(fn, lineno);
      }
    } else {
      err_not_key_value(fn, lineno);
    }
  }
}



/**	Create ASCII check sum for an opened file.
	@param	f	The file to process.
	@return	1 on success, 0 on error.
*/
static
int
create_ascii_checksum(FILE *f) {
  int back = 1;			/**< Return value. */
  size_t usedbytes = 0;		/**< Number of bytes in binary md. */
#if DK_HAVE_OPENSSL_SHA_H
  SHA_CTX	sha_ctx;	/**< Message digest context. */
#endif
#if DK_HAVE_SHA256 || DK_HAVE_SHA224
  SHA256_CTX	sha256_ctx;	/**< Message digest context. */
#endif
#if DK_HAVE_SHA512 || DK_HAVE_SHA384
  SHA512_CTX	sha512_ctx;	/**< Message digest context. */
#endif
#if DK_HAVE_OPENSSL_RIPEMD_H
  RIPEMD160_CTX	ripemd160_ctx;	/**< Message digest context. */
#endif
#if DK_HAVE_OPENSSL_MD5_H
  MD5_CTX	md5_ctx;	/**< Message digest context. */
#endif
  size_t read_bytes;		/**< Number of bytes read into buffer. */
  digest_ascii[0] = '\0';
  /*
  	First calculate binary message digest.
  */
  switch(digest_type) {
    case CHKSUM_DIGEST_SHA_512: {	
#if DK_HAVE_SHA512
      SHA512_Init(&sha512_ctx);
      do {
        read_bytes = FREAD(buffer,1,sizeof(buffer),f);
	if(read_bytes > 0) {
	  SHA512_Update(&sha512_ctx, buffer, read_bytes);
	}
      } while(read_bytes > 0);
      SHA512_Final(digest_value, &sha512_ctx);
      usedbytes = 64;
#endif
    } break;
    case CHKSUM_DIGEST_SHA_384: {	
#if DK_HAVE_SHA384
      SHA384_Init(&sha512_ctx);
      do {
        read_bytes = FREAD(buffer,1,sizeof(buffer),f);
	if(read_bytes > 0) {
	  SHA384_Update(&sha512_ctx, buffer, read_bytes);
	}
      } while(read_bytes > 0);
      SHA384_Final(digest_value, &sha512_ctx);
      usedbytes = 48;
#endif
    } break;
    case CHKSUM_DIGEST_SHA_256: {	
#if DK_HAVE_SHA256
      SHA256_Init(&sha256_ctx);
      do {
        read_bytes = FREAD(buffer,1,sizeof(buffer),f);
	if(read_bytes > 0) {
	  SHA256_Update(&sha256_ctx, buffer, read_bytes);
	}
      } while(read_bytes > 0);
      SHA256_Final(digest_value, &sha256_ctx);
      usedbytes = 32;
#endif
    } break;
    case CHKSUM_DIGEST_SHA_224: {	
#if DK_HAVE_SHA224
      SHA224_Init(&sha256_ctx);
      do {
        read_bytes = FREAD(buffer,1,sizeof(buffer),f);
	if(read_bytes > 0) {
	  SHA224_Update(&sha256_ctx, buffer, read_bytes);
	}
      } while(read_bytes > 0);
      SHA224_Final(digest_value, &sha256_ctx);
      usedbytes = 28;
#endif
    } break;
    case CHKSUM_DIGEST_MD5: {		
#if DK_HAVE_OPENSSL_MD5_H
      MD5_Init(&md5_ctx);
      do {
        read_bytes = FREAD(buffer,1,sizeof(buffer),f);
	if(read_bytes > 0) {
	  MD5_Update(&md5_ctx, buffer, read_bytes);
	}
      } while(read_bytes > 0);
      MD5_Final(digest_value, &md5_ctx);
      usedbytes = 16;
#endif
    } break;
    case CHKSUM_DIGEST_RIPEMD_160: {	
#if DK_HAVE_OPENSSL_RIPEMD_H
      RIPEMD160_Init(&ripemd160_ctx);
      do {
        read_bytes = FREAD(buffer,1,sizeof(buffer),f);
	if(read_bytes > 0) {
	  RIPEMD160_Update(&ripemd160_ctx, buffer, read_bytes);
	}
      } while(read_bytes > 0);
      RIPEMD160_Final(digest_value, &ripemd160_ctx);
      usedbytes = 20;
#endif
    } break;

    /* +++++ Further message digest algorithms here */

    default: {				
#if DK_HAVE_OPENSSL_SHA_H
      SHA1_Init(&sha_ctx);
      do {
        read_bytes = FREAD(buffer,1,sizeof(buffer),f);
	if(read_bytes > 0) {
	  SHA1_Update(&sha_ctx, buffer, read_bytes);
	}
      } while(read_bytes > 0);
      SHA1_Final(digest_value, &sha_ctx);
      usedbytes = 20;
#endif
    } break;
  }
  /*
  	Convert binary message digest to ASCII representation.
  */
  if(usedbytes) {
    switch(digest_encoding) {
      case CHKSUM_ENCODING_HEX: {	
        back = dkenc_bin_to_hex(
	  (char *)digest_ascii, sizeof(digest_ascii),
	  (char *)digest_value, usedbytes
	);
      } break;
      case CHKSUM_ENCODING_RA85: {	
        back = dkenc_bin_to_ra85(
	  (char *)digest_ascii, sizeof(digest_ascii),
	  (char *)digest_value, usedbytes
	);
      } break;

      /* +++++ Further binary-to-ASCII encodings here */

      default: {			
        back = dkenc_bin_to_a85(
	  (char *)digest_ascii, sizeof(digest_ascii),
	  (char *)digest_value, usedbytes
	);
      } break;
    }
  }
  return back;
}



/**	Process a checksum list produced by md5sum.
	@param	f	File to process.
	@param	fn	File name (used for error reporting, may NOT be NULL).
*/
static
void
check_md5sum_file(FILE *f, char *fn) {
  int old_digest_type;		/**< Save digest type. */
  int old_digest_encoding;	/**< Save encoding. */
  int res;			/**< Comparison result. */
  unsigned long lineno;		/**< Line number. */
  char *pname = NULL;		/**< Pointer to file name. */
  char *pmd = NULL;		/**< Pointer to message digest. */
  FILE *fipo = NULL;		/**< Used to read input. */
  int use_bin_mode;		/**< Flag: Use binary mode. */

  old_digest_type = digest_type;
  old_digest_encoding = digest_encoding;
  digest_type = CHKSUM_DIGEST_MD5;
  digest_encoding = CHKSUM_ENCODING_HEX;
  lineno = 0UL;
  while(fgets(inputline, sizeof(inputline), f)) {
    lineno++;
    dkstr_chomp(inputline, NULL);
    pname = dkstr_chr(inputline, ' ');
    if(pname) {
      *(pname++) = '\0';
#if ON_A_WINDOWS_SYSTEM
      use_bin_mode = 1;
#else
      use_bin_mode = 0;
#endif
      switch(*pname) {
        case ' ': {
	  use_bin_mode = 0; pname++;
	} break;
	case '*': {
	  use_bin_mode = 1; pname++;
	} break;
      }
      pmd = dkstr_start(inputline, NULL);
      if(pmd) {
        dkstr_chomp(pmd, NULL);
	dkstr_chomp(pname, NULL);
	if(strcmp(pname, "-")) {	
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
	  fipo = fopen64(pname, (use_bin_mode ? "rb" : "r"));
#else
	  fipo = fopen(pname, (use_bin_mode ? "rb" : "r"));
#endif
	  if(fipo) {
	    if(create_ascii_checksum(fipo)) {
	      res = 0;
	      files_checked++;
	      if(files_checked == 0UL) files_checked = 0xFFFFFFFFUL;
	      switch(digest_encoding) {
	        case CHKSUM_ENCODING_HEX: {
		  if(STRICMP((char *)digest_ascii,pmd) == 0) { res = 1; }
		} break;
		default: {
		  if(strcmp((char *)digest_ascii,pmd) == 0) { res = 1; }
		} break;
	      }
	      if(res) {
	        if(!((flag_quiet) || (flag_status))) {
		  printf(msg[16], pname);
		}
	      } else {
	        if(!flag_status) {
		  printf(msg[17], pname);
		}
		exval = 1;
		files_failed++;
		if(files_failed == 0UL) files_failed = 0xFFFFFFFFUL;
	      }
	    } else {
	      err_checksum_failed(fn);
	    }
	    fclose(fipo); fipo = NULL;
	  } else {
	    err_open_read(pname);
	  }
	} else {			
	}
      } else {
        err_syntax_general(fn, lineno);
      }
    } else {
      err_syntax_general(fn, lineno);
    }
  }
  digest_encoding = old_digest_encoding;
  digest_type = old_digest_type;
}



/**	Process a checksum list produced by fchksum.
	@param	f	File to process.
	@param	fn	File name (used for error reporting, may NOT be NULL).
*/
static
void
check_fchksum_file(FILE *f, char *fn) {
  int old_digest_type;		/**< Save digest type. */
  int old_digest_encoding;	/**< Save encoding. */
  int res;			/**< Comparison result. */
  unsigned long lineno;		/**< Line number. */
  char *pname = NULL;		/**< Pointer to file name. */
  char *pmd = NULL;		/**< Pointer to message digest. */
  FILE *fipo = NULL;		/**< Used to read input. */
  
  lineno = 0UL;
  old_digest_type = digest_type;
  old_digest_encoding = digest_encoding;
  while(fgets(inputline, sizeof(inputline), f)) {
    lineno++;
    dkstr_chomp(inputline, NULL);
    if(inputline[0] == '\t') {
      if(inputline[1] == '#') {
        if(inputline[2] == '#') {
	  use_control_line(&(inputline[3]), fn, lineno);
	} else {
	  err_syntax_general(fn, lineno);
	}
      } else {
        err_syntax_general(fn, lineno);
      }
    } else {
      pname = dkstr_chr(inputline, ' ');
      if(pname) {
        *(pname++) = '\0';
	pmd = dkstr_start(inputline, NULL);
	if(pmd) {
	  pname = dkstr_start(pname, NULL);
	  if(pname) {
	    if(strcmp(pname, "-")) {	
	      if(flag_implemented) {
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
		fipo = fopen64(pname, "rb");
#else
	        fipo = fopen(pname, "rb");
#endif
	        if(fipo) {
	          if(create_ascii_checksum(fipo)) {
		    res = 0;
		    files_checked++;
		    if(files_checked == 0UL) files_checked = 0xFFFFFFFFUL;
		    switch(digest_encoding) {
		      case CHKSUM_ENCODING_HEX: {
		        if(STRICMP((char *)digest_ascii,pmd) == 0) { res = 1; }
		      } break;
		      default: {
		        if(strcmp((char *)digest_ascii,pmd) == 0) { res = 1; }
		      } break;
		    }
		    if(res) {	
		      if(!((flag_quiet) || (flag_status))) {
		        printf(msg[16], pname);
		      }
		    } else {	
		      if(!flag_status) {
		        printf(msg[17], pname);
		      }
		      exval = 1;
		      files_failed++;
		      if(files_failed == 0UL) files_failed = 0xFFFFFFFFUL;
		    }
		  } else {
		    err_checksum_failed(fn);
		  }
	          fclose(fipo); fipo = NULL;
	        } else {
	          err_open_read(pname);
	        }
	      } else {
	        files_skipped++;
		if(files_skipped == 0UL) files_skipped = 0xFFFFFFFFUL;
	      }
	    } else {			
	    }
	  } else {
	    err_syntax_general(fn, lineno);
	  }
	} else {
	  err_syntax_general(fn, lineno);
	}
      } else {
        err_syntax_general(fn, lineno);
      }
    }
  }
  digest_encoding = old_digest_encoding;
  digest_type = old_digest_type; 
}



/**	Process checksum lists created by md5sum.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
*/
static
void
check_md5sum(int argc, char **argv) {
  FILE *f;		/**< Used to read the check list. */
  char	**lfdptr;	/**< Used to traverse command line arguments. */
  int	i;		/**< Used to traverse command line arguments. */
  char	*ptr;		/**< Current command line argument. */
  
  lfdptr = argv; lfdptr++; i = 1;
  while(i < argc) {
    ptr = *lfdptr;
    if(*ptr == '-') {
      ptr++;
      switch(*ptr) {
        case 'm': case 'e': {
	  ptr++;
	  if(!(*ptr)) { i++; lfdptr++; }
	} break;
	case '\0': {
	  /* Standard input */
	  check_md5sum_file(stdin, filename_for_stdin);
	  flag_is_first = 0x00;
	} break;
      }
    } else {
      flag_is_first = 0x00;
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
      f = fopen64(ptr, "r");
#else
      f = fopen(ptr, "r");
#endif
      if(f) {
        check_md5sum_file(f, ptr);
        fclose(f); f = NULL;
      } else {
	err_open_read(ptr);
      }
    }
    lfdptr++; i++;
  }
  if(flag_is_first) {
    check_md5sum_file(stdin, filename_for_stdin);
    flag_is_first = 0x00;
  }
}



/**	Process checksum lists created by fchksum.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
*/
static
void
check_fchksum(int argc, char **argv) {
  FILE *f;		/**< Used to read the check list. */
  char	**lfdptr;	/**< Used to traverse command line arguments. */
  int	i;		/**< Used to traverse command line arguments. */
  char	*ptr;		/**< Current command line argument. */
  
  lfdptr = argv; lfdptr++; i = 1;
  while(i < argc) {
    ptr = *lfdptr;
    if(*ptr == '-') {
      ptr++;
      switch(*ptr) {
        case 'm': case 'e': {
	  ptr++;
	  if(!(*ptr)) { i++; lfdptr++; }
	} break;
	case '\0': {
	  /* Standard input */
	  check_fchksum_file(stdin, filename_for_stdin);
	  flag_is_first = 0x00;
	} break;
      }
    } else {
      flag_is_first = 0x00;
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
      f = fopen64(ptr, "r");
#else
      f = fopen(ptr, "r");
#endif
      if(f) {
        check_fchksum_file(f, ptr);
        fclose(f); f = NULL;
      } else {
	err_open_read(ptr);
      }
    }
    lfdptr++; i++;
  }
  if(flag_is_first) {
    check_fchksum_file(stdin, filename_for_stdin);
    flag_is_first = 0x00;
  }
  
}



/**	Process checksum lists.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
*/
static
void
check_files(int argc, char **argv) {
  
  if(flag_md5sum) {
    check_md5sum(argc, argv);
  } else {
    check_fchksum(argc, argv);
  }
  if(files_failed) {
    if(!flag_status) {
      fprintf(stderr, msg[18], progname, files_failed, files_checked);
      fflush(stderr);
    }
  }
  if(files_skipped) {
    if(!flag_status) {
      fprintf(stderr, msg[23], progname, files_skipped);
      if(exval != 1) exval = 8;
    }
  }
  
}



/**	Check whether we can build a checksum for the file type.
	Print error message if file type is not acceptable.
	@param	filename	File name.
	@return	1 for yes, 0 for no.
*/
static
int
is_acceptable_file_type(char *filename) {
  int back = 0;	/**< Return value. */

#if ON_A_WINDOWS_SYSTEM

#if DK_HAVE__STAT64
  struct __stat64 stbuf;
  
  if(_stat64(filename, &stbuf) == 0) {
    if(((stbuf.st_mode) & _S_IFMT) == _S_IFREG) {
      back = 1;
    } else {
      if(((stbuf.st_mode) & _S_IFMT) == _S_IFDIR) {
        if(flag_dir) {
	  back = 1;
	} else {
	  err_directory(filename);
	}
      } else {
        if(((stbuf.st_mode) & _S_IFMT) == _S_IFCHR) {
	  back = 1;
	  warn_device(filename);
	} else {
	  if(((stbuf.st_mode) & _S_IFMT) == _S_IFIFO) {
	    back = 0;
	    warn_pipe(filename);
	  } else {
	    err_illegal_file_type(filename);
	  }
	}
      }
    }
  } else {
    err_stat_failed(filename);
  }
#else
  struct _stat stbuf;
  
  if(_stat(filename, &stbuf) == 0) {
    if(((stbuf.st_mode) & _S_IFMT) == _S_IFREG) {
      back = 1;
    } else {
      if(((stbuf.st_mode) & _S_IFMT) == _S_IFDIR) {
        if(flag_dir) {
	  back = 1;
	} else {
	  err_directory(filename);
	}
      } else {
        if(((stbuf.st_mode) & _S_IFMT) == _S_IFCHR) {
	  back = 1;
	  warn_device(filename);
	} else {
	  if(((stbuf.st_mode) & _S_IFMT) == _S_IFIFO) {
	    back = 0;
	    warn_pipe(filename);
	  } else {
	    err_illegal_file_type(filename);
	  }
	}
      }
    }
  } else {
    err_stat_failed(filename);
  }
#endif

#else

#if DK_HAVE_STAT64 && DK_HAVE_LARGEFILE64_SOURCE
  struct stat64 stbuf;
  
  if(stat64(filename, &stbuf) == 0) {
    if(S_ISREG(stbuf.st_mode)) {
      back = 1;
    } else {
      if(S_ISDIR(stbuf.st_mode)) {
        if(flag_dir) {
	  back = 1;
	} else {	
	  err_directory(filename);
	}
      } else {
        if(S_ISCHR(stbuf.st_mode)) {
	  back = 1;
	  warn_device(filename);
	} else {
	  if(S_ISBLK(stbuf.st_mode)) {
	    warn_device(filename);
	    back = 1;
	  } else {	
	    err_illegal_file_type(filename);
	  }
	}
      }
    }
  } else {		
    err_stat_failed(filename);
  }
#else
  struct stat stbuf;
  
  if(stat(filename, &stbuf) == 0) {
    if(S_ISREG(stbuf.st_mode)) {
      back = 1;
    } else {
      if(S_ISDIR(stbuf.st_mode)) {
        if(flag_dir) {
	  back = 1;
	} else {	
	  err_directory(filename);
	}
      } else {
        if(S_ISCHR(stbuf.st_mode)) {
	  back = 1;
	  warn_device(filename);
	} else {
	  if(S_ISBLK(stbuf.st_mode)) {
	    warn_device(filename);
	    back = 1;
	  } else {	
	    err_illegal_file_type(filename);
	  }
	}
      }
    }
  } else {		
    err_stat_failed(filename);
  }
#endif

#endif
  return back;
}



/**	Create and print checksum for one file.
	@param	f	File to check.
	@param	n	File name (used for error reporting, may NOT be NULL).
*/
static
void
handle_file(FILE *f, char *n) {
  digest_ascii[0] = '\0';
  if(flag_implemented) {
    if(create_ascii_checksum(f)) {
      if(!flag_header_printed) {
        if(!flag_skip_header) {
          printf(
	    "\t## %s = %s %s\n",
	    control_instructions[0],
	    digest_types[digest_type ? (digest_type - 1) : 0],
	    digest_encodings[digest_encoding ? (digest_encoding - 1) : 0]
	  );
        }
        flag_header_printed = 0x01;
      }
      fputs((char *)digest_ascii, stdout);
      fputc(' ', stdout);
      fputs(n, stdout);
      fputc('\n', stdout);
    } else {				
      err_checksum_failed(n);
    }
  } else {
    files_skipped++; if(files_skipped == 0UL) files_skipped = 0xFFFFFFFFUL;
  }
}



/**	Attempt to open file an create checksum.
	@param	filename	File name.
*/
static
void
attempt_to_run_for(char *filename) {
  FILE *f;	/**< File descriptor to read input. */
  if(is_acceptable_file_type(filename)) {
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
    f = fopen64(filename, "rb");
#else
    f = fopen(filename, "rb");
#endif
    if(f) {
      handle_file(f, filename);
      fclose(f);
    } else {
      err_open_read(filename);
    }
  }
}



/**	Build checksum for standard input.
*/
static
void
run_for_stdin(void) {
#if ON_A_WINDOWS_SYSTEM
    int oldmode;	/**< Old text/binary mode. */
    oldmode = _setmode(_fileno(stdin), _O_BINARY);
#endif
    handle_file(stdin, filename_for_stdin);
#if ON_A_WINDOWS_SYSTEM
    _setmode(_fileno(stdin), oldmode);
#endif
}



/**	Create checksums for all file names listed in input.
	@param	fipo	File containing the file names to build checksums.
*/
static
void
run_for_list(FILE *fipo) {
  char *p1;	/**< Start of line. */
  while(fgets(inputline, sizeof(inputline), fipo)) {
    p1 = dkstr_start(inputline, NULL);
    if(p1) {
      dkstr_chomp(p1, NULL);
      attempt_to_run_for(p1);
    }
  }
}



/**	Process the file names specified on the command line.
	@param	argv	Number of command line arguments.
	@param	argc	Command line arguments array.
*/
static
void
process_files(int argc, char **argv) {
  char	**lfdptr;	/**< Used to traverse command line arguments. */
  int	i;		/**< Used to traverse command line arguments. */
  char	*ptr;		/**< Current command line argument. */
  FILE	*fl;		/**< File to read list from. */
  
  if(digest_type == CHKSUM_DIGEST_UNKNOWN) {
    digest_type =
    (flag_md5sum ? CHKSUM_DIGEST_MD5 : CHKSUM_DIGEST_SHA_1);
  }
  if(digest_encoding == CHKSUM_ENCODING_UNKNOWN) {
    digest_encoding =
    (flag_md5sum ? CHKSUM_ENCODING_HEX : CHKSUM_ENCODING_ASCII85);
  }
  lfdptr = argv; lfdptr++; i = 1;
  while(i < argc) {
    ptr = *lfdptr;
    if(*ptr == '-') {
      ptr++;
      switch(*ptr) {
        case 'm': case 'e': {
	  ptr++; if(!(*ptr)) { lfdptr++; i++; }
	} break;
	case '\0': {
	  if(flag_list) {
	    run_for_list(stdin);
	  } else {
	    run_for_stdin();
	  }
	  flag_is_first = 0x00;
	} break;
      }
    } else {
      if(flag_list) {
#if DK_HAVE_LARGEFILE64_SOURCE && DK_HAVE_FOPEN64
	fl = fopen64(ptr, "r");
#else
        fl = fopen(ptr, "r");
#endif
	if(fl) {
	  run_for_list(fl);
	  fclose(fl);
	} else {
	  err_open_read(ptr);
	}
      } else {
        attempt_to_run_for(ptr);
      }
      flag_is_first = 0x00;
    }
    lfdptr++; i++;
  }
  if(flag_is_first) {	
    if(flag_list) {
      run_for_list(stdin);
    } else {
      run_for_stdin();
    }
  }
  
}



/**	After option inspection now run.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
*/
static
void
run_for_files(int argc, char **argv) {
  
  if(flag_check) {
    check_files(argc, argv);
  } else {
    process_files(argc, argv);
  } 
}



/**	The main function of the fchksum program.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
	@return	0 on success, positive values for errors.
*/
int main(int argc, char *argv[]) {
  
#line 2332 "fchksum.ctr"

  
  process_arguments(argc, argv);
  if(exval == 0) {
    if(flag_help | flag_version) {
      show_text(version_text);
      if(flag_help) { show_text(help_text); }
    } else {
      run_for_files(argc, argv);
    }
  } else {
    if(flag_help) {
      show_text(version_text);
      show_text(help_text);
    }
  }
  
  
#line 2349 "fchksum.ctr"

  exit(exval); return exval;
}


