|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <config.h> |
|
|
|
|
|
#include <getopt.h> |
|
|
#include <sys/types.h> |
|
|
|
|
|
#include "assure.h" |
|
|
#include "system.h" |
|
|
#include "argmatch.h" |
|
|
#include "c-ctype.h" |
|
|
#include "quote.h" |
|
|
#include "xdectoint.h" |
|
|
#include "xstrtol.h" |
|
|
|
|
|
#ifndef HASH_ALGO_CKSUM |
|
|
# define HASH_ALGO_CKSUM 0 |
|
|
#endif |
|
|
|
|
|
#if HASH_ALGO_SUM || HASH_ALGO_CKSUM |
|
|
# include "sum.h" |
|
|
#endif |
|
|
#if HASH_ALGO_CKSUM |
|
|
# include "cksum.h" |
|
|
# include "base64.h" |
|
|
#endif |
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
# include "blake2/b2sum.h" |
|
|
#endif |
|
|
#if HASH_ALGO_MD5 || HASH_ALGO_CKSUM |
|
|
# include "md5.h" |
|
|
#endif |
|
|
#if HASH_ALGO_SHA1 || HASH_ALGO_CKSUM |
|
|
# include "sha1.h" |
|
|
#endif |
|
|
#if HASH_ALGO_SHA256 || HASH_ALGO_SHA224 || HASH_ALGO_CKSUM |
|
|
# include "sha256.h" |
|
|
#endif |
|
|
#if HASH_ALGO_SHA512 || HASH_ALGO_SHA384 || HASH_ALGO_CKSUM |
|
|
# include "sha512.h" |
|
|
#endif |
|
|
#if HASH_ALGO_CKSUM |
|
|
# include "sha3.h" |
|
|
#endif |
|
|
#if HASH_ALGO_CKSUM |
|
|
# include "sm3.h" |
|
|
#endif |
|
|
#include "fadvise.h" |
|
|
#include "stdio--.h" |
|
|
#include "xbinary-io.h" |
|
|
|
|
|
|
|
|
#if HASH_ALGO_SUM |
|
|
# define PROGRAM_NAME "sum" |
|
|
# define DIGEST_TYPE_STRING "BSD" |
|
|
# define DIGEST_STREAM sumfns[sum_algorithm] |
|
|
# define DIGEST_OUT sum_output_fns[sum_algorithm] |
|
|
# define DIGEST_BITS 16 |
|
|
# define DIGEST_ALIGN 4 |
|
|
#elif HASH_ALGO_CKSUM |
|
|
# define MAX_DIGEST_BITS 512 |
|
|
# define MAX_DIGEST_ALIGN 8 |
|
|
# define PROGRAM_NAME "cksum" |
|
|
# define DIGEST_TYPE_STRING algorithm_tags[cksum_algorithm] |
|
|
# define DIGEST_STREAM cksumfns[cksum_algorithm] |
|
|
# define DIGEST_OUT cksum_output_fns[cksum_algorithm] |
|
|
# define DIGEST_BITS MAX_DIGEST_BITS |
|
|
# define DIGEST_ALIGN MAX_DIGEST_ALIGN |
|
|
#elif HASH_ALGO_MD5 |
|
|
# define PROGRAM_NAME "md5sum" |
|
|
# define DIGEST_TYPE_STRING "MD5" |
|
|
# define DIGEST_STREAM md5_stream |
|
|
# define DIGEST_BITS 128 |
|
|
# define DIGEST_REFERENCE "RFC 1321" |
|
|
# define DIGEST_ALIGN 4 |
|
|
#elif HASH_ALGO_BLAKE2 |
|
|
# define PROGRAM_NAME "b2sum" |
|
|
# define DIGEST_TYPE_STRING "BLAKE2b" |
|
|
# define DIGEST_STREAM blake2b_stream |
|
|
# define DIGEST_BITS 512 |
|
|
# define DIGEST_REFERENCE "RFC 7693" |
|
|
# define DIGEST_ALIGN 8 |
|
|
#elif HASH_ALGO_SHA1 |
|
|
# define PROGRAM_NAME "sha1sum" |
|
|
# define DIGEST_TYPE_STRING "SHA1" |
|
|
# define DIGEST_STREAM sha1_stream |
|
|
# define DIGEST_BITS 160 |
|
|
# define DIGEST_REFERENCE "FIPS-180-1" |
|
|
# define DIGEST_ALIGN 4 |
|
|
#elif HASH_ALGO_SHA256 |
|
|
# define PROGRAM_NAME "sha256sum" |
|
|
# define DIGEST_TYPE_STRING "SHA256" |
|
|
# define DIGEST_STREAM sha256_stream |
|
|
# define DIGEST_BITS 256 |
|
|
# define DIGEST_REFERENCE "FIPS-180-2" |
|
|
# define DIGEST_ALIGN 4 |
|
|
#elif HASH_ALGO_SHA224 |
|
|
# define PROGRAM_NAME "sha224sum" |
|
|
# define DIGEST_TYPE_STRING "SHA224" |
|
|
# define DIGEST_STREAM sha224_stream |
|
|
# define DIGEST_BITS 224 |
|
|
# define DIGEST_REFERENCE "RFC 3874" |
|
|
# define DIGEST_ALIGN 4 |
|
|
#elif HASH_ALGO_SHA512 |
|
|
# define PROGRAM_NAME "sha512sum" |
|
|
# define DIGEST_TYPE_STRING "SHA512" |
|
|
# define DIGEST_STREAM sha512_stream |
|
|
# define DIGEST_BITS 512 |
|
|
# define DIGEST_REFERENCE "FIPS-180-2" |
|
|
# define DIGEST_ALIGN 8 |
|
|
#elif HASH_ALGO_SHA384 |
|
|
# define PROGRAM_NAME "sha384sum" |
|
|
# define DIGEST_TYPE_STRING "SHA384" |
|
|
# define DIGEST_STREAM sha384_stream |
|
|
# define DIGEST_BITS 384 |
|
|
# define DIGEST_REFERENCE "FIPS-180-2" |
|
|
# define DIGEST_ALIGN 8 |
|
|
#else |
|
|
# error "Can't decide which hash algorithm to compile." |
|
|
#endif |
|
|
#if !HASH_ALGO_SUM && !HASH_ALGO_CKSUM |
|
|
# define DIGEST_OUT output_file |
|
|
#endif |
|
|
|
|
|
#if HASH_ALGO_SUM |
|
|
# define AUTHORS \ |
|
|
proper_name ("Kayvan Aghaiepour"), \ |
|
|
proper_name ("David MacKenzie") |
|
|
#elif HASH_ALGO_CKSUM |
|
|
# define AUTHORS \ |
|
|
proper_name_lite ("Padraig Brady", "P\303\241draig Brady"), \ |
|
|
proper_name ("Q. Frank Xia") |
|
|
#elif HASH_ALGO_BLAKE2 |
|
|
# define AUTHORS \ |
|
|
proper_name_lite ("Padraig Brady", "P\303\241draig Brady"), \ |
|
|
proper_name ("Samuel Neves") |
|
|
#else |
|
|
# define AUTHORS \ |
|
|
proper_name ("Ulrich Drepper"), \ |
|
|
proper_name ("Scott Miller"), \ |
|
|
proper_name ("David Madore") |
|
|
#endif |
|
|
#if !HASH_ALGO_BLAKE2 && !HASH_ALGO_CKSUM |
|
|
# define DIGEST_HEX_BYTES (DIGEST_BITS / 4) |
|
|
#endif |
|
|
#define DIGEST_BIN_BYTES (DIGEST_BITS / 8) |
|
|
|
|
|
|
|
|
|
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
# define MIN_DIGEST_LINE_LENGTH 3 |
|
|
#else |
|
|
# define MIN_DIGEST_LINE_LENGTH \ |
|
|
(DIGEST_HEX_BYTES \ |
|
|
+ 1 \ |
|
|
+ 1 ) |
|
|
#endif |
|
|
|
|
|
#if !HASH_ALGO_SUM |
|
|
static void |
|
|
output_file (char const *file, int binary_file, void const *digest, |
|
|
bool raw, bool tagged, unsigned char delim, bool args, |
|
|
uintmax_t length); |
|
|
#endif |
|
|
|
|
|
|
|
|
static bool have_read_stdin; |
|
|
|
|
|
|
|
|
static size_t min_digest_line_length; |
|
|
|
|
|
|
|
|
static size_t digest_hex_bytes; |
|
|
|
|
|
|
|
|
|
|
|
static bool status_only = false; |
|
|
|
|
|
|
|
|
|
|
|
static bool warn = false; |
|
|
|
|
|
|
|
|
static bool ignore_missing = false; |
|
|
|
|
|
|
|
|
static bool quiet = false; |
|
|
|
|
|
|
|
|
|
|
|
static bool strict = false; |
|
|
|
|
|
|
|
|
static int bsd_reversed = -1; |
|
|
|
|
|
|
|
|
static unsigned char digest_delim = '\n'; |
|
|
|
|
|
#if HASH_ALGO_CKSUM |
|
|
|
|
|
static bool base64_digest = false; |
|
|
#endif |
|
|
|
|
|
|
|
|
static bool raw_digest = false; |
|
|
|
|
|
|
|
|
|
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
# if HASH_ALGO_CKSUM |
|
|
static_assert (BLAKE2B_OUTBYTES == SHA3_512_DIGEST_SIZE); |
|
|
# endif |
|
|
# define DIGEST_MAX_LEN BLAKE2B_OUTBYTES |
|
|
static uintmax_t digest_length; |
|
|
#endif |
|
|
|
|
|
typedef void (*digest_output_fn)(char const *, int, void const *, bool, |
|
|
bool, unsigned char, bool, uintmax_t); |
|
|
#if HASH_ALGO_SUM |
|
|
enum Algorithm |
|
|
{ |
|
|
bsd, |
|
|
sysv, |
|
|
}; |
|
|
|
|
|
static enum Algorithm sum_algorithm; |
|
|
static sumfn sumfns[]= |
|
|
{ |
|
|
bsd_sum_stream, |
|
|
sysv_sum_stream, |
|
|
}; |
|
|
static digest_output_fn sum_output_fns[]= |
|
|
{ |
|
|
output_bsd, |
|
|
output_sysv, |
|
|
}; |
|
|
#endif |
|
|
|
|
|
#if HASH_ALGO_CKSUM |
|
|
static int |
|
|
md5_sum_stream (FILE *stream, void *resstream, MAYBE_UNUSED uintmax_t *length) |
|
|
{ |
|
|
return md5_stream (stream, resstream); |
|
|
} |
|
|
static int |
|
|
sha1_sum_stream (FILE *stream, void *resstream, MAYBE_UNUSED uintmax_t *length) |
|
|
{ |
|
|
return sha1_stream (stream, resstream); |
|
|
} |
|
|
static int |
|
|
sha224_sum_stream (FILE *stream, void *resstream, |
|
|
MAYBE_UNUSED uintmax_t *length) |
|
|
{ |
|
|
return sha224_stream (stream, resstream); |
|
|
} |
|
|
static int |
|
|
sha256_sum_stream (FILE *stream, void *resstream, |
|
|
MAYBE_UNUSED uintmax_t *length) |
|
|
{ |
|
|
return sha256_stream (stream, resstream); |
|
|
} |
|
|
static int |
|
|
sha384_sum_stream (FILE *stream, void *resstream, |
|
|
MAYBE_UNUSED uintmax_t *length) |
|
|
{ |
|
|
return sha384_stream (stream, resstream); |
|
|
} |
|
|
static int |
|
|
sha512_sum_stream (FILE *stream, void *resstream, |
|
|
MAYBE_UNUSED uintmax_t *length) |
|
|
{ |
|
|
return sha512_stream (stream, resstream); |
|
|
} |
|
|
static int |
|
|
sha2_sum_stream (FILE *stream, void *resstream, uintmax_t *length) |
|
|
{ |
|
|
switch (*length) |
|
|
{ |
|
|
case SHA224_DIGEST_SIZE: |
|
|
return sha224_stream (stream, resstream); |
|
|
case SHA256_DIGEST_SIZE: |
|
|
return sha256_stream (stream, resstream); |
|
|
case SHA384_DIGEST_SIZE: |
|
|
return sha384_stream (stream, resstream); |
|
|
case SHA512_DIGEST_SIZE: |
|
|
return sha512_stream (stream, resstream); |
|
|
default: |
|
|
affirm (false); |
|
|
} |
|
|
} |
|
|
static int |
|
|
sha3_sum_stream (FILE *stream, void *resstream, uintmax_t *length) |
|
|
{ |
|
|
switch (*length) |
|
|
{ |
|
|
case SHA3_224_DIGEST_SIZE: |
|
|
return sha3_224_stream (stream, resstream); |
|
|
case SHA3_256_DIGEST_SIZE: |
|
|
return sha3_256_stream (stream, resstream); |
|
|
case SHA3_384_DIGEST_SIZE: |
|
|
return sha3_384_stream (stream, resstream); |
|
|
case SHA3_512_DIGEST_SIZE: |
|
|
return sha3_512_stream (stream, resstream); |
|
|
default: |
|
|
affirm (false); |
|
|
} |
|
|
} |
|
|
static int |
|
|
blake2b_sum_stream (FILE *stream, void *resstream, uintmax_t *length) |
|
|
{ |
|
|
return blake2b_stream (stream, resstream, *length); |
|
|
} |
|
|
static int |
|
|
sm3_sum_stream (FILE *stream, void *resstream, MAYBE_UNUSED uintmax_t *length) |
|
|
{ |
|
|
return sm3_stream (stream, resstream); |
|
|
} |
|
|
|
|
|
enum Algorithm |
|
|
{ |
|
|
bsd, |
|
|
sysv, |
|
|
crc, |
|
|
crc32b, |
|
|
md5, |
|
|
sha1, |
|
|
sha224, |
|
|
sha256, |
|
|
sha384, |
|
|
sha512, |
|
|
sha2, |
|
|
sha3, |
|
|
blake2b, |
|
|
sm3, |
|
|
}; |
|
|
|
|
|
static char const *const algorithm_args[] = |
|
|
{ |
|
|
"bsd", "sysv", "crc", "crc32b", "md5", "sha1", |
|
|
"sha224", "sha256", "sha384", "sha512", |
|
|
"sha2", "sha3", "blake2b", "sm3", nullptr |
|
|
}; |
|
|
static enum Algorithm const algorithm_types[] = |
|
|
{ |
|
|
bsd, sysv, crc, crc32b, md5, sha1, |
|
|
sha224, sha256, sha384, sha512, |
|
|
sha2, sha3, blake2b, sm3, |
|
|
}; |
|
|
ARGMATCH_VERIFY (algorithm_args, algorithm_types); |
|
|
|
|
|
static char const *const algorithm_tags[] = |
|
|
{ |
|
|
"BSD", "SYSV", "CRC", "CRC32B", "MD5", "SHA1", |
|
|
"SHA224", "SHA256", "SHA384", "SHA512", |
|
|
"SHA2", "SHA3", "BLAKE2b", "SM3", nullptr |
|
|
}; |
|
|
static int const algorithm_bits[] = |
|
|
{ |
|
|
16, 16, 32, 32, 128, 160, |
|
|
224, 256, 384, 512, |
|
|
512, 512, 512, 256, 0 |
|
|
}; |
|
|
|
|
|
static_assert (countof (algorithm_bits) == countof (algorithm_args)); |
|
|
|
|
|
static bool algorithm_specified = false; |
|
|
static enum Algorithm cksum_algorithm = crc; |
|
|
static sumfn cksumfns[]= |
|
|
{ |
|
|
bsd_sum_stream, |
|
|
sysv_sum_stream, |
|
|
crc_sum_stream, |
|
|
crc32b_sum_stream, |
|
|
md5_sum_stream, |
|
|
sha1_sum_stream, |
|
|
sha224_sum_stream, |
|
|
sha256_sum_stream, |
|
|
sha384_sum_stream, |
|
|
sha512_sum_stream, |
|
|
sha2_sum_stream, |
|
|
sha3_sum_stream, |
|
|
blake2b_sum_stream, |
|
|
sm3_sum_stream, |
|
|
}; |
|
|
static digest_output_fn cksum_output_fns[]= |
|
|
{ |
|
|
output_bsd, |
|
|
output_sysv, |
|
|
output_crc, |
|
|
output_crc, |
|
|
output_file, |
|
|
output_file, |
|
|
output_file, |
|
|
output_file, |
|
|
output_file, |
|
|
output_file, |
|
|
output_file, |
|
|
output_file, |
|
|
output_file, |
|
|
output_file, |
|
|
}; |
|
|
bool cksum_debug; |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum |
|
|
{ |
|
|
IGNORE_MISSING_OPTION = CHAR_MAX + 1, |
|
|
STATUS_OPTION, |
|
|
QUIET_OPTION, |
|
|
STRICT_OPTION, |
|
|
TAG_OPTION, |
|
|
UNTAG_OPTION, |
|
|
DEBUG_PROGRAM_OPTION, |
|
|
RAW_OPTION, |
|
|
BASE64_OPTION, |
|
|
}; |
|
|
|
|
|
static struct option const long_options[] = |
|
|
{ |
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
{ "length", required_argument, nullptr, 'l'}, |
|
|
#endif |
|
|
|
|
|
#if !HASH_ALGO_SUM |
|
|
{ "check", no_argument, nullptr, 'c' }, |
|
|
{ "ignore-missing", no_argument, nullptr, IGNORE_MISSING_OPTION}, |
|
|
{ "quiet", no_argument, nullptr, QUIET_OPTION }, |
|
|
{ "status", no_argument, nullptr, STATUS_OPTION }, |
|
|
{ "warn", no_argument, nullptr, 'w' }, |
|
|
{ "strict", no_argument, nullptr, STRICT_OPTION }, |
|
|
{ "tag", no_argument, nullptr, TAG_OPTION }, |
|
|
{ "zero", no_argument, nullptr, 'z' }, |
|
|
|
|
|
# if HASH_ALGO_CKSUM |
|
|
{ "algorithm", required_argument, nullptr, 'a'}, |
|
|
{ "base64", no_argument, nullptr, BASE64_OPTION }, |
|
|
{ "debug", no_argument, nullptr, DEBUG_PROGRAM_OPTION}, |
|
|
{ "raw", no_argument, nullptr, RAW_OPTION}, |
|
|
{ "untagged", no_argument, nullptr, UNTAG_OPTION }, |
|
|
# endif |
|
|
{ "binary", no_argument, nullptr, 'b' }, |
|
|
{ "text", no_argument, nullptr, 't' }, |
|
|
|
|
|
#else |
|
|
{"sysv", no_argument, nullptr, 's'}, |
|
|
#endif |
|
|
|
|
|
{ GETOPT_HELP_OPTION_DECL }, |
|
|
{ GETOPT_VERSION_OPTION_DECL }, |
|
|
{ nullptr, 0, nullptr, 0 } |
|
|
}; |
|
|
|
|
|
void |
|
|
usage (int status) |
|
|
{ |
|
|
if (status != EXIT_SUCCESS) |
|
|
emit_try_help (); |
|
|
else |
|
|
{ |
|
|
printf (_("\ |
|
|
Usage: %s [OPTION]... [FILE]...\n\ |
|
|
"), program_name); |
|
|
#if HASH_ALGO_CKSUM |
|
|
fputs (_("\ |
|
|
Print or verify checksums.\n\ |
|
|
By default use the 32 bit CRC algorithm.\n\ |
|
|
"), stdout); |
|
|
#else |
|
|
printf (_("\ |
|
|
Print or check %s (%d-bit) checksums.\n\ |
|
|
"), |
|
|
DIGEST_TYPE_STRING, |
|
|
DIGEST_BITS); |
|
|
#endif |
|
|
|
|
|
emit_stdin_note (); |
|
|
#if HASH_ALGO_SUM |
|
|
fputs (_("\ |
|
|
\n\ |
|
|
-r use BSD sum algorithm (the default), use 1K blocks\n\ |
|
|
-s, --sysv use System V sum algorithm, use 512 bytes blocks\n\ |
|
|
"), stdout); |
|
|
#endif |
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
emit_mandatory_arg_note (); |
|
|
#endif |
|
|
#if HASH_ALGO_CKSUM |
|
|
fputs (_("\ |
|
|
-a, --algorithm=TYPE select the digest type to use. See DIGEST below\ |
|
|
\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
--base64 emit base64-encoded digests, not hexadecimal\ |
|
|
\n\ |
|
|
"), stdout); |
|
|
#endif |
|
|
#if !HASH_ALGO_SUM |
|
|
# if !HASH_ALGO_CKSUM |
|
|
if (O_BINARY) |
|
|
fputs (_("\ |
|
|
-b, --binary read in binary mode (default unless reading tty stdin)\ |
|
|
\n\ |
|
|
"), stdout); |
|
|
else |
|
|
fputs (_("\ |
|
|
-b, --binary read in binary mode\n\ |
|
|
"), stdout); |
|
|
# endif |
|
|
fputs (_("\ |
|
|
-c, --check read checksums from the FILEs and check them\n\ |
|
|
"), stdout); |
|
|
# if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
fputs (_("\ |
|
|
-l, --length=BITS digest length in bits; must not exceed the max size\n\ |
|
|
and must be a multiple of 8 for blake2b;\n\ |
|
|
must be 224, 256, 384, or 512 for sha2 or sha3\n\ |
|
|
"), stdout); |
|
|
# endif |
|
|
# if HASH_ALGO_CKSUM |
|
|
fputs (_("\ |
|
|
--raw emit a raw binary digest, not hexadecimal\ |
|
|
\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
--tag create a BSD-style checksum (the default)\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
--untagged create a reversed style checksum, without digest type\n\ |
|
|
"), stdout); |
|
|
# else |
|
|
fputs (_("\ |
|
|
--tag create a BSD-style checksum\n\ |
|
|
"), stdout); |
|
|
# endif |
|
|
# if !HASH_ALGO_CKSUM |
|
|
if (O_BINARY) |
|
|
fputs (_("\ |
|
|
-t, --text read in text mode (default if reading tty stdin)\n\ |
|
|
"), stdout); |
|
|
else |
|
|
fputs (_("\ |
|
|
-t, --text read in text mode (default)\n\ |
|
|
"), stdout); |
|
|
# endif |
|
|
fputs (_("\ |
|
|
-z, --zero end each output line with NUL, not newline,\n\ |
|
|
and disable file name escaping\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
\n\ |
|
|
The following five options are useful only when verifying checksums:\n\ |
|
|
--ignore-missing don't fail or report status for missing files\n\ |
|
|
--quiet don't print OK for each successfully verified file\n\ |
|
|
--status don't output anything, status code shows success\n\ |
|
|
--strict exit non-zero for improperly formatted checksum lines\n\ |
|
|
-w, --warn warn about improperly formatted checksum lines\n\ |
|
|
\n\ |
|
|
"), stdout); |
|
|
#endif |
|
|
#if HASH_ALGO_CKSUM |
|
|
fputs (_("\ |
|
|
--debug indicate which implementation used\n\ |
|
|
"), stdout); |
|
|
#endif |
|
|
fputs (HELP_OPTION_DESCRIPTION, stdout); |
|
|
fputs (VERSION_OPTION_DESCRIPTION, stdout); |
|
|
#if HASH_ALGO_CKSUM |
|
|
fputs (_("\ |
|
|
\n\ |
|
|
DIGEST determines the digest algorithm and default output format:\n\ |
|
|
sysv (equivalent to sum -s)\n\ |
|
|
bsd (equivalent to sum -r)\n\ |
|
|
crc (equivalent to cksum)\n\ |
|
|
crc32b (only available through cksum)\n\ |
|
|
md5 (equivalent to md5sum)\n\ |
|
|
sha1 (equivalent to sha1sum)\n\ |
|
|
sha2 (equivalent to sha{224,256,384,512}sum)\n\ |
|
|
sha3 (only available through cksum)\n\ |
|
|
blake2b (equivalent to b2sum)\n\ |
|
|
sm3 (only available through cksum)\n\ |
|
|
\n"), stdout); |
|
|
#endif |
|
|
#if !HASH_ALGO_SUM && !HASH_ALGO_CKSUM |
|
|
printf (_("\ |
|
|
\n\ |
|
|
The sums are computed as described in %s.\n"), DIGEST_REFERENCE); |
|
|
fputs (_("\ |
|
|
When checking, the input should be a former output of this program.\n\ |
|
|
The default mode is to print a line with: checksum, a space,\n\ |
|
|
a character indicating input mode ('*' for binary, ' ' for text\n\ |
|
|
or where binary is insignificant), and name for each FILE.\n\ |
|
|
\n\ |
|
|
There is no difference between binary mode and text mode on GNU systems.\ |
|
|
\n"), stdout); |
|
|
#endif |
|
|
#if HASH_ALGO_CKSUM |
|
|
fputs (_("\ |
|
|
When checking, the input should be a former output of this program,\n\ |
|
|
or equivalent standalone program.\ |
|
|
\n"), stdout); |
|
|
#endif |
|
|
emit_ancillary_info (PROGRAM_NAME); |
|
|
} |
|
|
|
|
|
exit (status); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ATTRIBUTE_PURE |
|
|
static bool |
|
|
problematic_chars (char const *s) |
|
|
{ |
|
|
size_t length = strcspn (s, "\\\n\r"); |
|
|
return s[length] != '\0'; |
|
|
} |
|
|
|
|
|
#define ISWHITE(c) ((c) == ' ' || (c) == '\t') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char * |
|
|
filename_unescape (char *s, size_t s_len) |
|
|
{ |
|
|
char *dst = s; |
|
|
|
|
|
for (size_t i = 0; i < s_len; i++) |
|
|
{ |
|
|
switch (s[i]) |
|
|
{ |
|
|
case '\\': |
|
|
if (i == s_len - 1) |
|
|
{ |
|
|
|
|
|
return nullptr; |
|
|
} |
|
|
++i; |
|
|
switch (s[i]) |
|
|
{ |
|
|
case 'n': |
|
|
*dst++ = '\n'; |
|
|
break; |
|
|
case 'r': |
|
|
*dst++ = '\r'; |
|
|
break; |
|
|
case '\\': |
|
|
*dst++ = '\\'; |
|
|
break; |
|
|
default: |
|
|
|
|
|
return nullptr; |
|
|
} |
|
|
break; |
|
|
|
|
|
case '\0': |
|
|
|
|
|
return nullptr; |
|
|
|
|
|
default: |
|
|
*dst++ = s[i]; |
|
|
break; |
|
|
} |
|
|
} |
|
|
if (dst < s + s_len) |
|
|
*dst = '\0'; |
|
|
|
|
|
return s; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ATTRIBUTE_PURE |
|
|
static bool |
|
|
valid_digits (unsigned char const *s, size_t len) |
|
|
{ |
|
|
#if HASH_ALGO_CKSUM |
|
|
if (len == BASE64_LENGTH (digest_length / 8)) |
|
|
{ |
|
|
size_t i; |
|
|
for (i = 0; i < len - digest_length % 3; i++) |
|
|
{ |
|
|
if (!isbase64 (*s)) |
|
|
return false; |
|
|
++s; |
|
|
} |
|
|
for ( ; i < len; i++) |
|
|
{ |
|
|
if (*s != '=') |
|
|
return false; |
|
|
++s; |
|
|
} |
|
|
} |
|
|
else |
|
|
#endif |
|
|
if (len == digest_hex_bytes) |
|
|
{ |
|
|
for (idx_t i = 0; i < digest_hex_bytes; i++) |
|
|
{ |
|
|
if (!c_isxdigit (*s)) |
|
|
return false; |
|
|
++s; |
|
|
} |
|
|
} |
|
|
else |
|
|
return false; |
|
|
|
|
|
return *s == '\0'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
bsd_split_3 (char *s, size_t s_len, |
|
|
unsigned char **digest, size_t *d_len, |
|
|
char **file_name, bool escaped_filename) |
|
|
{ |
|
|
if (s_len == 0) |
|
|
return false; |
|
|
|
|
|
|
|
|
size_t i = s_len - 1; |
|
|
while (i && s[i] != ')') |
|
|
i--; |
|
|
|
|
|
if (s[i] != ')') |
|
|
return false; |
|
|
|
|
|
*file_name = s; |
|
|
|
|
|
if (escaped_filename && filename_unescape (s, i) == nullptr) |
|
|
return false; |
|
|
|
|
|
s[i++] = '\0'; |
|
|
|
|
|
while (ISWHITE (s[i])) |
|
|
i++; |
|
|
|
|
|
if (s[i] != '=') |
|
|
return false; |
|
|
|
|
|
i++; |
|
|
|
|
|
while (ISWHITE (s[i])) |
|
|
i++; |
|
|
|
|
|
*digest = (unsigned char *) &s[i]; |
|
|
|
|
|
*d_len = s_len - i; |
|
|
return valid_digits (*digest, *d_len); |
|
|
} |
|
|
|
|
|
#if HASH_ALGO_CKSUM |
|
|
|
|
|
|
|
|
|
|
|
static ptrdiff_t |
|
|
algorithm_from_tag (char *s) |
|
|
{ |
|
|
|
|
|
static size_t max_tag_len; |
|
|
if (! max_tag_len) |
|
|
{ |
|
|
char const * const * tag = algorithm_tags; |
|
|
while (*tag) |
|
|
{ |
|
|
size_t tag_len = strlen (*tag++); |
|
|
max_tag_len = MAX (tag_len, max_tag_len); |
|
|
} |
|
|
} |
|
|
|
|
|
size_t i = 0; |
|
|
|
|
|
|
|
|
while (i <= max_tag_len && s[i] && ! ISWHITE (s[i]) |
|
|
&& s[i] != '-' && s[i] != '(') |
|
|
++i; |
|
|
|
|
|
if (i > max_tag_len) |
|
|
return -1; |
|
|
|
|
|
|
|
|
char sep = s[i]; |
|
|
s[i] = '\0'; |
|
|
ptrdiff_t algo = argmatch_exact (s, algorithm_tags); |
|
|
s[i] = sep; |
|
|
|
|
|
return algo; |
|
|
} |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
split_3 (char *s, size_t s_len, |
|
|
unsigned char **digest, size_t *d_len, int *binary, char **file_name) |
|
|
{ |
|
|
bool escaped_filename = false; |
|
|
size_t algo_name_len; |
|
|
|
|
|
size_t i = 0; |
|
|
while (ISWHITE (s[i])) |
|
|
++i; |
|
|
|
|
|
if (s[i] == '\\') |
|
|
{ |
|
|
++i; |
|
|
escaped_filename = true; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#if HASH_ALGO_CKSUM |
|
|
if (! algorithm_specified || cksum_algorithm == sha2) |
|
|
{ |
|
|
ptrdiff_t algo_tag = algorithm_from_tag (s + i); |
|
|
if (! algorithm_specified) |
|
|
{ |
|
|
if (algo_tag >= 0) |
|
|
{ |
|
|
if (algo_tag <= crc32b) |
|
|
return false; |
|
|
cksum_algorithm = algo_tag; |
|
|
} |
|
|
else |
|
|
return false; |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (cksum_algorithm == sha2 && (algo_tag == sha2 |
|
|
|| algo_tag == sha224 || algo_tag == sha256 |
|
|
|| algo_tag == sha384 || algo_tag == sha512)) |
|
|
cksum_algorithm = algo_tag; |
|
|
} |
|
|
} |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t parse_offset = i; |
|
|
algo_name_len = strlen (DIGEST_TYPE_STRING); |
|
|
if (STREQ_LEN (s + i, DIGEST_TYPE_STRING, algo_name_len)) |
|
|
{ |
|
|
i += algo_name_len; |
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
|
|
|
# if HASH_ALGO_BLAKE2 |
|
|
digest_length = DIGEST_MAX_LEN * 8; |
|
|
# else |
|
|
digest_length = algorithm_bits[cksum_algorithm]; |
|
|
# endif |
|
|
if (s[i] == '-') |
|
|
{ |
|
|
++i; |
|
|
uintmax_t length; |
|
|
char *siend; |
|
|
if (xstrtoumax (s + i, &siend, 0, &length, nullptr) != LONGINT_OK) |
|
|
return false; |
|
|
# if HASH_ALGO_CKSUM |
|
|
else if (cksum_algorithm == sha2 || cksum_algorithm == sha3) |
|
|
{ |
|
|
if (length != SHA224_DIGEST_SIZE * 8 |
|
|
&& length != SHA256_DIGEST_SIZE * 8 |
|
|
&& length != SHA384_DIGEST_SIZE * 8 |
|
|
&& length != SHA512_DIGEST_SIZE * 8) |
|
|
return false; |
|
|
} |
|
|
# endif |
|
|
else if (!(0 < length && length <= digest_length && length % 8 == 0)) |
|
|
return false; |
|
|
|
|
|
i = siend - s; |
|
|
digest_length = length; |
|
|
} |
|
|
digest_hex_bytes = digest_length / 4; |
|
|
#endif |
|
|
if (s[i] == ' ') |
|
|
++i; |
|
|
if (s[i] == '(') |
|
|
{ |
|
|
++i; |
|
|
*binary = 0; |
|
|
return bsd_split_3 (s + i, s_len - i, |
|
|
digest, d_len, file_name, escaped_filename); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
i = parse_offset; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (s_len - i < min_digest_line_length + (s[i] == '\\')) |
|
|
return false; |
|
|
|
|
|
*digest = (unsigned char *) &s[i]; |
|
|
|
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
|
|
|
# if HASH_ALGO_CKSUM |
|
|
if (cksum_algorithm == blake2b |
|
|
|| cksum_algorithm == sha2 || cksum_algorithm == sha3) { |
|
|
# endif |
|
|
unsigned char const *hp = *digest; |
|
|
digest_hex_bytes = 0; |
|
|
for (; c_isxdigit (*hp); ++hp, ++digest_hex_bytes) |
|
|
; |
|
|
# if HASH_ALGO_CKSUM |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t digest_base64_bytes = digest_hex_bytes; |
|
|
size_t trailing_equals = 0; |
|
|
for (; isubase64 (*hp); ++hp, ++digest_base64_bytes) |
|
|
; |
|
|
for (; *hp == '='; ++hp, ++trailing_equals) |
|
|
; |
|
|
if ((cksum_algorithm == sha2 || cksum_algorithm == sha3) |
|
|
&& digest_hex_bytes / 2 != SHA224_DIGEST_SIZE |
|
|
&& digest_hex_bytes / 2 != SHA256_DIGEST_SIZE |
|
|
&& digest_hex_bytes / 2 != SHA384_DIGEST_SIZE |
|
|
&& digest_hex_bytes / 2 != SHA512_DIGEST_SIZE) |
|
|
{ |
|
|
if (digest_base64_bytes + trailing_equals |
|
|
== BASE64_LENGTH (SHA224_DIGEST_SIZE)) |
|
|
digest_hex_bytes = SHA224_DIGEST_SIZE * 2; |
|
|
else if (digest_base64_bytes + trailing_equals |
|
|
== BASE64_LENGTH (SHA256_DIGEST_SIZE)) |
|
|
digest_hex_bytes = SHA256_DIGEST_SIZE * 2; |
|
|
else if (digest_base64_bytes + trailing_equals |
|
|
== BASE64_LENGTH (SHA384_DIGEST_SIZE)) |
|
|
digest_hex_bytes = SHA384_DIGEST_SIZE * 2; |
|
|
else if (digest_base64_bytes + trailing_equals |
|
|
== BASE64_LENGTH (SHA512_DIGEST_SIZE)) |
|
|
digest_hex_bytes = SHA512_DIGEST_SIZE * 2; |
|
|
else |
|
|
return false; |
|
|
} |
|
|
else if (cksum_algorithm == blake2b |
|
|
&& digest_hex_bytes < digest_base64_bytes) |
|
|
{ |
|
|
for (int j = 8; j <= DIGEST_MAX_LEN * 8; j += 8) |
|
|
{ |
|
|
if (BASE64_LENGTH (j / 8) == digest_base64_bytes + trailing_equals |
|
|
&& j % 3 == trailing_equals) |
|
|
{ |
|
|
digest_hex_bytes = j / 4; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
# endif |
|
|
if (digest_hex_bytes < 2 || digest_hex_bytes % 2 |
|
|
|| DIGEST_MAX_LEN * 2 < digest_hex_bytes) |
|
|
return false; |
|
|
digest_length = digest_hex_bytes * 4; |
|
|
# if HASH_ALGO_CKSUM |
|
|
} |
|
|
# endif |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
while (s[i] && !ISWHITE (s[i])) |
|
|
i++; |
|
|
|
|
|
|
|
|
if (i == s_len) |
|
|
return false; |
|
|
|
|
|
*d_len = &s[i] - (char *) *digest; |
|
|
s[i++] = '\0'; |
|
|
|
|
|
if (! valid_digits (*digest, *d_len)) |
|
|
return false; |
|
|
|
|
|
|
|
|
if ((s_len - i == 1) || (s[i] != ' ' && s[i] != '*')) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (bsd_reversed == 0) |
|
|
return false; |
|
|
bsd_reversed = 1; |
|
|
} |
|
|
else if (bsd_reversed != 1) |
|
|
{ |
|
|
bsd_reversed = 0; |
|
|
*binary = (s[i++] == '*'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
*file_name = &s[i]; |
|
|
|
|
|
if (escaped_filename) |
|
|
return filename_unescape (&s[i], s_len - i) != nullptr; |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void |
|
|
print_filename (char const *file, bool escape) |
|
|
{ |
|
|
if (! escape) |
|
|
{ |
|
|
fputs (file, stdout); |
|
|
return; |
|
|
} |
|
|
|
|
|
while (*file) |
|
|
{ |
|
|
switch (*file) |
|
|
{ |
|
|
case '\n': |
|
|
fputs ("\\n", stdout); |
|
|
break; |
|
|
|
|
|
case '\r': |
|
|
fputs ("\\r", stdout); |
|
|
break; |
|
|
|
|
|
case '\\': |
|
|
fputs ("\\\\", stdout); |
|
|
break; |
|
|
|
|
|
default: |
|
|
putchar (*file); |
|
|
break; |
|
|
} |
|
|
file++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
digest_file (char const *filename, int *binary, unsigned char *bin_result, |
|
|
bool *missing, MAYBE_UNUSED uintmax_t *length) |
|
|
{ |
|
|
FILE *fp; |
|
|
int err; |
|
|
bool is_stdin = streq (filename, "-"); |
|
|
|
|
|
*missing = false; |
|
|
|
|
|
if (is_stdin) |
|
|
{ |
|
|
have_read_stdin = true; |
|
|
fp = stdin; |
|
|
if (O_BINARY && *binary) |
|
|
{ |
|
|
if (*binary < 0) |
|
|
*binary = ! isatty (STDIN_FILENO); |
|
|
if (*binary) |
|
|
xset_binary_mode (STDIN_FILENO, O_BINARY); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
fp = fopen (filename, (O_BINARY && *binary ? "rb" : "r")); |
|
|
if (fp == nullptr) |
|
|
{ |
|
|
if (ignore_missing && errno == ENOENT) |
|
|
{ |
|
|
*missing = true; |
|
|
return true; |
|
|
} |
|
|
error (0, errno, "%s", quotef (filename)); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
fadvise (fp, FADVISE_SEQUENTIAL); |
|
|
|
|
|
#if HASH_ALGO_CKSUM |
|
|
if (cksum_algorithm == blake2b |
|
|
|| cksum_algorithm == sha2 || cksum_algorithm == sha3) |
|
|
*length = digest_length / 8; |
|
|
err = DIGEST_STREAM (fp, bin_result, length); |
|
|
#elif HASH_ALGO_SUM |
|
|
err = DIGEST_STREAM (fp, bin_result, length); |
|
|
#elif HASH_ALGO_BLAKE2 |
|
|
err = DIGEST_STREAM (fp, bin_result, digest_length / 8); |
|
|
#else |
|
|
err = DIGEST_STREAM (fp, bin_result); |
|
|
#endif |
|
|
err = err ? errno : 0; |
|
|
if (is_stdin) |
|
|
clearerr (fp); |
|
|
else if (fclose (fp) != 0 && !err) |
|
|
err = errno; |
|
|
|
|
|
if (err) |
|
|
{ |
|
|
error (0, err, "%s", quotef (filename)); |
|
|
return false; |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
#if !HASH_ALGO_SUM |
|
|
static void |
|
|
output_file (char const *file, int binary_file, void const *digest, |
|
|
MAYBE_UNUSED bool raw, bool tagged, unsigned char delim, |
|
|
MAYBE_UNUSED bool args, MAYBE_UNUSED uintmax_t length) |
|
|
{ |
|
|
# if HASH_ALGO_CKSUM |
|
|
if (raw) |
|
|
{ |
|
|
fwrite (digest, 1, digest_length / 8, stdout); |
|
|
return; |
|
|
} |
|
|
# endif |
|
|
|
|
|
unsigned char const *bin_buffer = digest; |
|
|
|
|
|
|
|
|
bool needs_escape = delim == '\n' && problematic_chars (file); |
|
|
|
|
|
if (needs_escape) |
|
|
putchar ('\\'); |
|
|
|
|
|
if (tagged) |
|
|
{ |
|
|
# if HASH_ALGO_CKSUM |
|
|
if (cksum_algorithm == sha2) |
|
|
printf ("SHA%ju", digest_length); |
|
|
else |
|
|
# endif |
|
|
fputs (DIGEST_TYPE_STRING, stdout); |
|
|
# if HASH_ALGO_BLAKE2 |
|
|
if (digest_length < DIGEST_MAX_LEN * 8) |
|
|
printf ("-%ju", digest_length); |
|
|
# elif HASH_ALGO_CKSUM |
|
|
if (cksum_algorithm == sha3) |
|
|
printf ("-%ju", digest_length); |
|
|
if (cksum_algorithm == blake2b) |
|
|
{ |
|
|
if (digest_length < DIGEST_MAX_LEN * 8) |
|
|
printf ("-%ju", digest_length); |
|
|
} |
|
|
# endif |
|
|
fputs (" (", stdout); |
|
|
print_filename (file, needs_escape); |
|
|
fputs (") = ", stdout); |
|
|
} |
|
|
|
|
|
# if HASH_ALGO_CKSUM |
|
|
if (base64_digest) |
|
|
{ |
|
|
char b64[BASE64_LENGTH (DIGEST_BIN_BYTES) + 1]; |
|
|
base64_encode ((char const *) bin_buffer, digest_length / 8, |
|
|
b64, sizeof b64); |
|
|
fputs (b64, stdout); |
|
|
} |
|
|
else |
|
|
# endif |
|
|
{ |
|
|
for (size_t i = 0; i < (digest_hex_bytes / 2); ++i) |
|
|
printf ("%02x", bin_buffer[i]); |
|
|
} |
|
|
|
|
|
if (!tagged) |
|
|
{ |
|
|
putchar (' '); |
|
|
putchar (binary_file ? '*' : ' '); |
|
|
print_filename (file, needs_escape); |
|
|
} |
|
|
|
|
|
putchar (delim); |
|
|
} |
|
|
#endif |
|
|
|
|
|
#if HASH_ALGO_CKSUM |
|
|
|
|
|
|
|
|
static bool |
|
|
b64_equal (unsigned char const *b64_digest, unsigned char const *bin_buffer) |
|
|
{ |
|
|
size_t b64_n_bytes = BASE64_LENGTH (digest_length / 8); |
|
|
char b64[BASE64_LENGTH (DIGEST_BIN_BYTES) + 1]; |
|
|
base64_encode ((char const *) bin_buffer, digest_length / 8, b64, sizeof b64); |
|
|
return memeq (b64_digest, b64, b64_n_bytes + 1); |
|
|
} |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
hex_equal (unsigned char const *hex_digest, unsigned char const *bin_buffer) |
|
|
{ |
|
|
static const char bin2hex[] = { '0', '1', '2', '3', |
|
|
'4', '5', '6', '7', |
|
|
'8', '9', 'a', 'b', |
|
|
'c', 'd', 'e', 'f' }; |
|
|
size_t digest_bin_bytes = digest_hex_bytes / 2; |
|
|
|
|
|
|
|
|
|
|
|
size_t cnt; |
|
|
for (cnt = 0; cnt < digest_bin_bytes; ++cnt) |
|
|
{ |
|
|
if (c_tolower (hex_digest[2 * cnt]) |
|
|
!= bin2hex[bin_buffer[cnt] >> 4] |
|
|
|| (c_tolower (hex_digest[2 * cnt + 1]) |
|
|
!= (bin2hex[bin_buffer[cnt] & 0xf]))) |
|
|
break; |
|
|
} |
|
|
return cnt == digest_bin_bytes; |
|
|
} |
|
|
|
|
|
static bool |
|
|
digest_check (char const *checkfile_name) |
|
|
{ |
|
|
FILE *checkfile_stream; |
|
|
uintmax_t n_misformatted_lines = 0; |
|
|
uintmax_t n_mismatched_checksums = 0; |
|
|
uintmax_t n_open_or_read_failures = 0; |
|
|
bool properly_formatted_lines = false; |
|
|
bool matched_checksums = false; |
|
|
unsigned char bin_buffer_unaligned[DIGEST_BIN_BYTES + DIGEST_ALIGN]; |
|
|
|
|
|
unsigned char *bin_buffer = ptr_align (bin_buffer_unaligned, DIGEST_ALIGN); |
|
|
uintmax_t line_number; |
|
|
char *line; |
|
|
size_t line_chars_allocated; |
|
|
bool is_stdin = streq (checkfile_name, "-"); |
|
|
|
|
|
if (is_stdin) |
|
|
{ |
|
|
have_read_stdin = true; |
|
|
checkfile_name = _("standard input"); |
|
|
checkfile_stream = stdin; |
|
|
} |
|
|
else |
|
|
{ |
|
|
checkfile_stream = fopen (checkfile_name, "r"); |
|
|
if (checkfile_stream == nullptr) |
|
|
{ |
|
|
error (0, errno, "%s", quotef (checkfile_name)); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
line_number = 0; |
|
|
line = nullptr; |
|
|
line_chars_allocated = 0; |
|
|
do |
|
|
{ |
|
|
char *filename; |
|
|
int binary; |
|
|
unsigned char *digest; |
|
|
ssize_t line_length; |
|
|
|
|
|
++line_number; |
|
|
if (line_number == 0) |
|
|
error (EXIT_FAILURE, 0, _("%s: too many checksum lines"), |
|
|
quotef (checkfile_name)); |
|
|
|
|
|
line_length = getline (&line, &line_chars_allocated, checkfile_stream); |
|
|
if (line_length <= 0) |
|
|
break; |
|
|
|
|
|
|
|
|
if (line[0] == '#') |
|
|
continue; |
|
|
|
|
|
|
|
|
line_length -= line[line_length - 1] == '\n'; |
|
|
|
|
|
line_length -= line[line_length - (0 < line_length)] == '\r'; |
|
|
|
|
|
|
|
|
if (line_length == 0) |
|
|
continue; |
|
|
|
|
|
line[line_length] = '\0'; |
|
|
|
|
|
size_t d_len; |
|
|
if (! (split_3 (line, line_length, &digest, &d_len, &binary, &filename) |
|
|
&& ! (is_stdin && streq (filename, "-")))) |
|
|
{ |
|
|
++n_misformatted_lines; |
|
|
|
|
|
if (warn) |
|
|
{ |
|
|
error (0, 0, |
|
|
_("%s: %ju" |
|
|
": improperly formatted %s checksum line"), |
|
|
quotef (checkfile_name), line_number, |
|
|
DIGEST_TYPE_STRING); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
bool ok; |
|
|
bool missing; |
|
|
bool needs_escape = ! status_only && problematic_chars (filename); |
|
|
|
|
|
properly_formatted_lines = true; |
|
|
|
|
|
uintmax_t length; |
|
|
ok = digest_file (filename, &binary, bin_buffer, &missing, &length); |
|
|
|
|
|
if (!ok) |
|
|
{ |
|
|
++n_open_or_read_failures; |
|
|
if (!status_only) |
|
|
{ |
|
|
if (needs_escape) |
|
|
putchar ('\\'); |
|
|
print_filename (filename, needs_escape); |
|
|
printf (": %s\n", _("FAILED open or read")); |
|
|
} |
|
|
} |
|
|
else if (ignore_missing && missing) |
|
|
{ |
|
|
|
|
|
; |
|
|
} |
|
|
else |
|
|
{ |
|
|
bool match = false; |
|
|
#if HASH_ALGO_CKSUM |
|
|
if (d_len == BASE64_LENGTH (digest_length / 8)) |
|
|
match = b64_equal (digest, bin_buffer); |
|
|
else |
|
|
#endif |
|
|
if (d_len == digest_hex_bytes) |
|
|
match = hex_equal (digest, bin_buffer); |
|
|
|
|
|
if (match) |
|
|
matched_checksums = true; |
|
|
else |
|
|
++n_mismatched_checksums; |
|
|
|
|
|
if (!status_only) |
|
|
{ |
|
|
if (! match || ! quiet) |
|
|
{ |
|
|
if (needs_escape) |
|
|
putchar ('\\'); |
|
|
print_filename (filename, needs_escape); |
|
|
} |
|
|
|
|
|
if (! match) |
|
|
printf (": %s\n", _("FAILED")); |
|
|
else if (!quiet) |
|
|
printf (": %s\n", _("OK")); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
while (!feof (checkfile_stream) && !ferror (checkfile_stream)); |
|
|
|
|
|
free (line); |
|
|
|
|
|
int err = ferror (checkfile_stream) ? 0 : -1; |
|
|
if (is_stdin) |
|
|
clearerr (checkfile_stream); |
|
|
else if (fclose (checkfile_stream) != 0 && err < 0) |
|
|
err = errno; |
|
|
|
|
|
if (0 <= err) |
|
|
{ |
|
|
error (0, err, err ? "%s" : _("%s: read error"), |
|
|
quotef (checkfile_name)); |
|
|
return false; |
|
|
} |
|
|
|
|
|
if (! properly_formatted_lines) |
|
|
{ |
|
|
|
|
|
error (0, 0, _("%s: no properly formatted checksum lines found"), |
|
|
quotef (checkfile_name)); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (!status_only) |
|
|
{ |
|
|
if (n_misformatted_lines != 0) |
|
|
error (0, 0, |
|
|
(ngettext |
|
|
("WARNING: %ju line is improperly formatted", |
|
|
"WARNING: %ju lines are improperly formatted", |
|
|
select_plural (n_misformatted_lines))), |
|
|
n_misformatted_lines); |
|
|
|
|
|
if (n_open_or_read_failures != 0) |
|
|
error (0, 0, |
|
|
(ngettext |
|
|
("WARNING: %ju listed file could not be read", |
|
|
"WARNING: %ju listed files could not be read", |
|
|
select_plural (n_open_or_read_failures))), |
|
|
n_open_or_read_failures); |
|
|
|
|
|
if (n_mismatched_checksums != 0) |
|
|
error (0, 0, |
|
|
(ngettext |
|
|
("WARNING: %ju computed checksum did NOT match", |
|
|
"WARNING: %ju computed checksums did NOT match", |
|
|
select_plural (n_mismatched_checksums))), |
|
|
n_mismatched_checksums); |
|
|
|
|
|
if (ignore_missing && ! matched_checksums) |
|
|
error (0, 0, _("%s: no file was verified"), |
|
|
quotef (checkfile_name)); |
|
|
} |
|
|
} |
|
|
|
|
|
return (properly_formatted_lines |
|
|
&& matched_checksums |
|
|
&& n_mismatched_checksums == 0 |
|
|
&& n_open_or_read_failures == 0 |
|
|
&& (!strict || n_misformatted_lines == 0)); |
|
|
} |
|
|
|
|
|
int |
|
|
main (int argc, char **argv) |
|
|
{ |
|
|
unsigned char bin_buffer_unaligned[DIGEST_BIN_BYTES + DIGEST_ALIGN]; |
|
|
|
|
|
unsigned char *bin_buffer = ptr_align (bin_buffer_unaligned, DIGEST_ALIGN); |
|
|
bool do_check = false; |
|
|
int opt; |
|
|
bool ok = true; |
|
|
int binary = -1; |
|
|
int prefix_tag = -1; |
|
|
|
|
|
|
|
|
initialize_main (&argc, &argv); |
|
|
set_program_name (argv[0]); |
|
|
setlocale (LC_ALL, ""); |
|
|
bindtextdomain (PACKAGE, LOCALEDIR); |
|
|
textdomain (PACKAGE); |
|
|
|
|
|
atexit (close_stdout); |
|
|
|
|
|
|
|
|
|
|
|
setvbuf (stdout, nullptr, _IOLBF, 0); |
|
|
|
|
|
#if HASH_ALGO_SUM |
|
|
char const *short_opts = "rs"; |
|
|
#elif HASH_ALGO_CKSUM |
|
|
char const *short_opts = "a:l:bctwz"; |
|
|
char const *digest_length_str = ""; |
|
|
#elif HASH_ALGO_BLAKE2 |
|
|
char const *short_opts = "l:bctwz"; |
|
|
char const *digest_length_str = ""; |
|
|
#else |
|
|
char const *short_opts = "bctwz"; |
|
|
#endif |
|
|
|
|
|
while ((opt = getopt_long (argc, argv, short_opts, long_options, nullptr)) |
|
|
!= -1) |
|
|
switch (opt) |
|
|
{ |
|
|
#if HASH_ALGO_CKSUM |
|
|
case 'a': |
|
|
cksum_algorithm = XARGMATCH_EXACT ("--algorithm", optarg, |
|
|
algorithm_args, algorithm_types); |
|
|
algorithm_specified = true; |
|
|
break; |
|
|
|
|
|
case DEBUG_PROGRAM_OPTION: |
|
|
cksum_debug = true; |
|
|
break; |
|
|
#endif |
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
case 'l': |
|
|
digest_length = xnumtoumax (optarg, 10, 0, UINTMAX_MAX, "", |
|
|
_("invalid length"), 0, |
|
|
XTOINT_MAX_QUIET); |
|
|
digest_length_str = optarg; |
|
|
break; |
|
|
#endif |
|
|
#if !HASH_ALGO_SUM |
|
|
case 'c': |
|
|
do_check = true; |
|
|
break; |
|
|
case STATUS_OPTION: |
|
|
status_only = true; |
|
|
warn = false; |
|
|
quiet = false; |
|
|
break; |
|
|
case 'b': |
|
|
binary = 1; |
|
|
break; |
|
|
case 't': |
|
|
binary = 0; |
|
|
break; |
|
|
case 'w': |
|
|
status_only = false; |
|
|
warn = true; |
|
|
quiet = false; |
|
|
break; |
|
|
case IGNORE_MISSING_OPTION: |
|
|
ignore_missing = true; |
|
|
break; |
|
|
case QUIET_OPTION: |
|
|
status_only = false; |
|
|
warn = false; |
|
|
quiet = true; |
|
|
break; |
|
|
case STRICT_OPTION: |
|
|
strict = true; |
|
|
break; |
|
|
# if HASH_ALGO_CKSUM |
|
|
case BASE64_OPTION: |
|
|
base64_digest = true; |
|
|
break; |
|
|
case RAW_OPTION: |
|
|
raw_digest = true; |
|
|
break; |
|
|
case UNTAG_OPTION: |
|
|
if (prefix_tag == 1) |
|
|
binary = -1; |
|
|
prefix_tag = 0; |
|
|
break; |
|
|
# endif |
|
|
case TAG_OPTION: |
|
|
prefix_tag = 1; |
|
|
binary = 1; |
|
|
break; |
|
|
case 'z': |
|
|
digest_delim = '\0'; |
|
|
break; |
|
|
#endif |
|
|
#if HASH_ALGO_SUM |
|
|
case 'r': |
|
|
sum_algorithm = bsd; |
|
|
break; |
|
|
|
|
|
case 's': |
|
|
sum_algorithm = sysv; |
|
|
break; |
|
|
#endif |
|
|
case_GETOPT_HELP_CHAR; |
|
|
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); |
|
|
default: |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
min_digest_line_length = MIN_DIGEST_LINE_LENGTH; |
|
|
#if HASH_ALGO_BLAKE2 || HASH_ALGO_CKSUM |
|
|
# if HASH_ALGO_CKSUM |
|
|
if (digest_length && (cksum_algorithm != blake2b |
|
|
&& cksum_algorithm != sha2 |
|
|
&& cksum_algorithm != sha3)) |
|
|
error (EXIT_FAILURE, 0, |
|
|
_("--length is only supported with --algorithm " |
|
|
"blake2b, sha2, or sha3")); |
|
|
if (cksum_algorithm == sha2 || cksum_algorithm == sha3) |
|
|
{ |
|
|
|
|
|
if (digest_length == 0 && *digest_length_str == '\0' && ! do_check) |
|
|
error (EXIT_FAILURE, 0, _("--algorithm=%s requires specifying " |
|
|
"--length 224, 256, 384, or 512"), |
|
|
algorithm_args[cksum_algorithm]); |
|
|
|
|
|
if ((! do_check || *digest_length_str != '\0') |
|
|
&& digest_length != SHA224_DIGEST_SIZE * 8 |
|
|
&& digest_length != SHA256_DIGEST_SIZE * 8 |
|
|
&& digest_length != SHA384_DIGEST_SIZE * 8 |
|
|
&& digest_length != SHA512_DIGEST_SIZE * 8) |
|
|
{ |
|
|
error (0, 0, _("invalid length: %s"), quote (digest_length_str)); |
|
|
error (EXIT_FAILURE, 0, _("digest length for %s must be " |
|
|
"224, 256, 384, or 512"), |
|
|
quote (DIGEST_TYPE_STRING)); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
|
|
|
# else |
|
|
{ |
|
|
# endif |
|
|
if (digest_length > DIGEST_MAX_LEN * 8) |
|
|
{ |
|
|
error (0, 0, _("invalid length: %s"), quote (digest_length_str)); |
|
|
error (EXIT_FAILURE, 0, |
|
|
_("maximum digest length for %s is %d bits"), |
|
|
quote (DIGEST_TYPE_STRING), |
|
|
DIGEST_MAX_LEN * 8); |
|
|
} |
|
|
if (digest_length % 8 != 0) |
|
|
{ |
|
|
error (0, 0, _("invalid length: %s"), quote (digest_length_str)); |
|
|
error (EXIT_FAILURE, 0, _("length is not a multiple of 8")); |
|
|
} |
|
|
} |
|
|
if (digest_length == 0) |
|
|
{ |
|
|
# if HASH_ALGO_BLAKE2 |
|
|
digest_length = DIGEST_MAX_LEN * 8; |
|
|
# else |
|
|
digest_length = algorithm_bits[cksum_algorithm]; |
|
|
# endif |
|
|
} |
|
|
digest_hex_bytes = digest_length / 4; |
|
|
#else |
|
|
digest_hex_bytes = DIGEST_HEX_BYTES; |
|
|
#endif |
|
|
|
|
|
#if HASH_ALGO_CKSUM |
|
|
switch (+cksum_algorithm) |
|
|
{ |
|
|
case bsd: |
|
|
case sysv: |
|
|
case crc: |
|
|
case crc32b: |
|
|
if (do_check && algorithm_specified) |
|
|
error (EXIT_FAILURE, 0, |
|
|
_("--check is not supported with " |
|
|
"--algorithm={bsd,sysv,crc,crc32b}")); |
|
|
break; |
|
|
} |
|
|
|
|
|
if (base64_digest && raw_digest) |
|
|
{ |
|
|
error (0, 0, _("--base64 and --raw are mutually exclusive")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
#endif |
|
|
|
|
|
if (prefix_tag == -1) |
|
|
prefix_tag = HASH_ALGO_CKSUM; |
|
|
|
|
|
if (prefix_tag && !binary) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if !HASH_ALGO_CKSUM |
|
|
error (0, 0, _("--tag does not support --text mode")); |
|
|
#else |
|
|
error (0, 0, _("--text mode is only supported with --untagged")); |
|
|
#endif |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (digest_delim != '\n' && do_check) |
|
|
{ |
|
|
error (0, 0, _("the --zero option is not supported when " |
|
|
"verifying checksums")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
#if !HASH_ALGO_CKSUM |
|
|
if (prefix_tag && do_check) |
|
|
{ |
|
|
error (0, 0, _("the --tag option is meaningless when " |
|
|
"verifying checksums")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
#endif |
|
|
|
|
|
if (0 <= binary && do_check) |
|
|
{ |
|
|
error (0, 0, _("the --binary and --text options are meaningless when " |
|
|
"verifying checksums")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (ignore_missing && !do_check) |
|
|
{ |
|
|
error (0, 0, |
|
|
_("the --ignore-missing option is meaningful only when " |
|
|
"verifying checksums")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (status_only && !do_check) |
|
|
{ |
|
|
error (0, 0, |
|
|
_("the --status option is meaningful only when verifying checksums")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (warn && !do_check) |
|
|
{ |
|
|
error (0, 0, |
|
|
_("the --warn option is meaningful only when verifying checksums")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (quiet && !do_check) |
|
|
{ |
|
|
error (0, 0, |
|
|
_("the --quiet option is meaningful only when verifying checksums")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (strict & !do_check) |
|
|
{ |
|
|
error (0, 0, |
|
|
_("the --strict option is meaningful only when verifying checksums")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (!O_BINARY && binary < 0) |
|
|
binary = 0; |
|
|
|
|
|
char **operand_lim = argv + argc; |
|
|
if (optind == argc) |
|
|
*operand_lim++ = bad_cast ("-"); |
|
|
else if (1 < argc - optind && raw_digest) |
|
|
error (EXIT_FAILURE, 0, |
|
|
_("the --raw option is not supported with multiple files")); |
|
|
|
|
|
for (char **operandp = argv + optind; operandp < operand_lim; operandp++) |
|
|
{ |
|
|
char *file = *operandp; |
|
|
if (do_check) |
|
|
ok &= digest_check (file); |
|
|
else |
|
|
{ |
|
|
int binary_file = binary; |
|
|
bool missing; |
|
|
uintmax_t length; |
|
|
|
|
|
if (! digest_file (file, &binary_file, bin_buffer, &missing, &length)) |
|
|
ok = false; |
|
|
else |
|
|
{ |
|
|
DIGEST_OUT (file, binary_file, bin_buffer, raw_digest, prefix_tag, |
|
|
digest_delim, optind != argc, length); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (have_read_stdin && fclose (stdin) == EOF) |
|
|
error (EXIT_FAILURE, errno, _("standard input")); |
|
|
|
|
|
return ok ? EXIT_SUCCESS : EXIT_FAILURE; |
|
|
} |
|
|
|