From f5e91c02ff4057002e480ad4933a1b9aa2496c40 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sat, 25 Feb 2012 23:35:18 +0000 Subject: [PATCH] Hash utilities: Check for and report on junk files. That is, when verifying a list of hashes, we optionally detect and report files which are present in the filesystem, alongside files which we're checking, but which aren't in our list. Thanks to Patrick Gosling for the idea. --- cc-hash.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- cc.h | 33 ++++++++++++ dsig.1 | 13 ++++- dsig.c | 15 +++++- hashsum.1 | 10 +++- hashsum.c | 44 ++++++++++++++- 6 files changed, 289 insertions(+), 6 deletions(-) diff --git a/cc-hash.c b/cc-hash.c index 9e23f79..00dcb39 100644 --- a/cc-hash.c +++ b/cc-hash.c @@ -32,11 +32,17 @@ #include "config.h" #include +#include #include #include #include +#include +#include + +#include #include +#include #include #include @@ -166,6 +172,18 @@ const encodeops *getencoding(const char *ename) /*----- File hashing ------------------------------------------------------*/ +enum { + FHETY_DIR, + FHETY_FILE +}; + +typedef struct fhent { + struct fhent *next; + unsigned ty; + struct fhent *sub; + char name[1]; +} fhdir; + /* --- @gethash@ --- * * * Arguments: @const char *name@ = pointer to name string @@ -193,6 +211,27 @@ const gchash *gethash(const char *name) return (gg); } +/* --- @describefile@ --- * + * + * Arguments: @const struct stat *st@ = pointer to file state + * + * Returns: A snappy one-word description of the file. + */ + +const char *describefile(const struct stat *st) +{ + switch (st->st_mode & S_IFMT) { + case S_IFBLK: return ("block-special"); + case S_IFCHR: return ("char-special"); + case S_IFIFO: return ("fifo"); + case S_IFREG: return ("file"); + case S_IFLNK: return ("symlink"); + case S_IFDIR: return ("directory"); + case S_IFSOCK: return ("socket"); + default: return ("unknown"); + } +} + /* --- @fhash_init@ ---* * * Arguments: @fhashstate *fh@ = pointer to fhash state to initialize @@ -205,7 +244,7 @@ const gchash *gethash(const char *name) */ void fhash_init(fhashstate *fh, const gchash *gch, unsigned f) - { fh->f = f; fh->gch = gch; } + { fh->f = f; fh->gch = gch; fh->ents = 0; } /* --- @fhash_free@ --- * * @@ -216,7 +255,19 @@ void fhash_init(fhashstate *fh, const gchash *gch, unsigned f) * Use: Frees an fhash state. */ -void fhash_free(fhashstate *fh) { return; } +static void freefhents(struct fhent *fhe) +{ + struct fhent *ffhe; + + for (; fhe; fhe = ffhe) { + ffhe = fhe->next; + freefhents(fhe->sub); + xfree(fhe); + } +} + +void fhash_free(fhashstate *fh) + { freefhents(fh->ents); } /* --- @fhash@ --- * * @@ -236,6 +287,9 @@ int fhash(fhashstate *fh, const char *file, void *buf) size_t sz; ghash *h; int rc = 0; + struct fhent *fhe, **ffhe; + const char *p, *q; + size_t n; fprogress ff; if (!file || strcmp(file, "-") == 0) @@ -247,6 +301,35 @@ int fhash(fhashstate *fh, const char *file, void *buf) if (fprogress_init(&ff, file, fp)) return (-1); } + if (fh->f & FHF_JUNK) { + p = file; + if (strncmp(p, "./", 2) == 0) p += 2; + q = p; + ffhe = &fh->ents; + for (;;) { + if (*q == '/' || *q == 0) { + n = q - p; + for (; *ffhe; ffhe = &(*ffhe)->next) { + fhe = *ffhe; + if (strncmp(p, fhe->name, n) == 0 && fhe->name[n] == 0) + goto found; + } + fhe = xmalloc(offsetof(struct fhent, name) + n + 1); + fhe->next = 0; + fhe->ty = *q == '/' ? FHETY_DIR : FHETY_FILE; + fhe->sub = 0; + *ffhe = fhe; + memcpy(fhe->name, p, n); fhe->name[n] = 0; + found: + if (!*q) break; + while (*++q == '/'); + p = q; + ffhe = &fhe->sub; + } else + q++; + } + } + h = GH_INIT(fh->gch); while ((sz = fread(fbuf, 1, sizeof(fbuf), fp)) > 0) { GH_HASH(h, fbuf, sz); @@ -260,6 +343,99 @@ int fhash(fhashstate *fh, const char *file, void *buf) return (rc); } +/* --- @fhash_junk@ --- * + * + * Arguments: @fhashstate *fh@ = pointer to fhash state + * @void (*func)(const char *, const struct stat *, void *)@ + * @void *p@ = pointer to pass to function + * + * Returns: Positive if any junk was found, negative on error, zero if + * everything was fine. + * + * Use: Reports junk files in any directories covered by the hash + * state. + */ + +struct fhjunk { + int (*func)(const char *, const struct stat *, void *); + void *p; + dstr *d; +}; + +static int fhjunk(struct fhjunk *fhj, struct fhent *ents) +{ + DIR *dp; + int rc = 0, rrc; + struct stat st; + struct dirent *d; + const char *dname; + size_t n = fhj->d->len; + struct fhent *fhe; + + dname = n ? fhj->d->buf : "."; + if ((dp = opendir(dname)) == 0) { + moan("failed to open directory `%s': %s", dname, strerror(errno)); + rc = -1; + goto subs; + } + if (n) { + dstr_putc(fhj->d, '/'); + n++; + } + while (errno = 0, (d = readdir(dp)) != 0) { + if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) + continue; + for (fhe = ents; fhe; fhe = fhe->next) { + if (strcmp(d->d_name, fhe->name) == 0) goto found; + } + fhj->d->len = n; + dstr_puts(fhj->d, d->d_name); + if (!lstat(fhj->d->buf, &st)) { + if (!rc) rc = 1; + rrc = fhj->func(fhj->d->buf, &st, fhj->p); + } else { + rc = -1; + rrc = fhj->func(fhj->d->buf, 0, fhj->p); + } + if (rrc < 0) rc = -1; + found:; + } + closedir(dp); + if (errno) { + moan("failed to read directory `%s': %s", dname, strerror(errno)); + rc = -1; + } + +subs: + for (fhe = ents; fhe; fhe = fhe->next) { + if (fhe->ty == FHETY_DIR) { + fhj->d->len = n; + dstr_puts(fhj->d, fhe->name); + rrc = fhjunk(fhj, fhe->sub); + if (rrc < 0) rc = -1; + else if (!rc) rc = rrc; + } + } + + return (rc); +} + +int fhash_junk(fhashstate *fh, + int (*func)(const char *, const struct stat *, void *), + void *p) +{ + dstr d = DSTR_INIT; + struct fhjunk fhj; + int rc; + + fhj.func = func; + fhj.p = p; + fhj.d = &d; + rc = fhjunk(&fhj, fh->ents); + dstr_destroy(&d); + return (rc); +} + /* --- @hfparse@ --- * * * Arguments: @hfpctx *hfp@ = pointer to the context structure diff --git a/cc.h b/cc.h index 7d93fa3..f3a03f3 100644 --- a/cc.h +++ b/cc.h @@ -44,6 +44,9 @@ #include #include +#include +#include + #include #include "key.h" @@ -329,10 +332,12 @@ extern const encodeops *getencoding(const char */*ename*/); typedef struct fhashstate { const gchash *gch; unsigned f; + struct fhent *ents; } fhashstate; #define FHF_BINARY 0x100u #define FHF_PROGRESS 0x200u +#define FHF_JUNK 0x400u #define FHF_MASK 0xff00u @@ -347,6 +352,15 @@ typedef struct fhashstate { extern const gchash *gethash(const char */*name*/); +/* --- @describefile@ --- * + * + * Arguments: @const struct stat *st@ = pointer to file state + * + * Returns: A snappy one-word description of the file. + */ + +extern const char *describefile(const struct stat */*st*/); + /* --- @fhash_init@ ---* * * Arguments: @fhashstate *fh@ = pointer to fhash state to initialize @@ -385,6 +399,25 @@ extern void fhash_free(fhashstate */*fh*/); extern int fhash(fhashstate */*fh*/, const char */*file*/, void */*buf*/); +/* --- @fhash_junk@ --- * + * + * Arguments: @fhashstate *fh@ = pointer to fhash state + * @void (*func)(const char *, const struct stat *, void *)@ + * @void *p@ = pointer to pass to function + * + * Returns: Positive if any junk was found, negative on error, zero if + * everything was fine. + * + * Use: Reports junk files in any directories covered by the hash + * state. + */ + +extern int fhash_junk(fhashstate */*fh*/, + int (*/*func*/)(const char *, + const struct stat *, + void *), + void */*p*/); + /* --- @hfparse@ --- * * * Arguments: @hfpctx *hfp@ = pointer to the context structure diff --git a/dsig.1 b/dsig.1 index ad5b6fe..85056aa 100644 --- a/dsig.1 +++ b/dsig.1 @@ -61,7 +61,7 @@ is one of: .IR output ] .br .B verify -.RB [ \-pqvC ] +.RB [ \-pqvjC ] .RI [ file ] .SH DESCRIPTION The @@ -377,6 +377,9 @@ Produce more informational output. The default verbosity level is 1. .B "\-q, \-\-quiet" Produce less information output. .TP +.B "\-j, \-\-junk" +Report files whose hashes have not been checked. +.TP .BI "\-p, \-\-progress" Write a progress meter to standard error while processing large files. .TP @@ -403,6 +406,14 @@ encountered a situation which may or may not invalidate the signature. .BI "OK " message The signature verified correctly. .TP +.BI "JUNK " type " " name +The file +.I name +was found (as a result of the search requested by the +.RB ` \-j ' +option), but it was not mentioned in the signature file and therefore +has not been checked. +.TP .BI "INFO " note Any other information. .PP diff --git a/dsig.c b/dsig.c index 4847ac8..a7fa5a9 100644 --- a/dsig.c +++ b/dsig.c @@ -733,6 +733,13 @@ static int sign(int argc, char *argv[]) /*----- Signature verification --------------------------------------------*/ +static int checkjunk(const char *path, const struct stat *st, void *p) +{ + if (!st) printf("JUNK (error %s) %s\n", strerror(errno), path); + else printf("JUNK %s %s\n", describefile(st), path); + return (0); +} + static int verify(int argc, char *argv[]) { #define f_bogus 1u @@ -760,9 +767,10 @@ static int verify(int argc, char *argv[]) { "progress", 0, 0, 'p' }, { "quiet", 0, 0, 'q' }, { "nocheck", 0, 0, 'C' }, + { "junk", 0, 0, 'j' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "+vpqC", opts, 0, 0, 0); + int i = mdwopt(argc, argv, "+vpqCj", opts, 0, 0, 0); if (i < 0) break; switch (i) { @@ -779,6 +787,9 @@ static int verify(int argc, char *argv[]) case 'C': f |= f_nocheck; break; + case 'j': + f |= FHF_JUNK; + break; default: f |= f_bogus; break; @@ -931,6 +942,8 @@ static int verify(int argc, char *argv[]) } } done: + if ((f & FHF_JUNK) && fhash_junk(&fh, checkjunk, 0)) + f |= f_bogus; fhash_free(&fh); bdestroy(&b); dstr_destroy(&d); diff --git a/hashsum.1 b/hashsum.1 index bc956d0..3bde5e5 100644 --- a/hashsum.1 +++ b/hashsum.1 @@ -12,7 +12,7 @@ hashsum \- compute and verify cryptographic checksums of files .SH SYNOPSIS .B hashsum -.RB [ \-f0ecbpv ] +.RB [ \-f0ecbjpv ] .RB [ \-a .IR algorithm ] .RB [ \-E @@ -119,6 +119,14 @@ the hash list is authentic (e.g., it has been digitally signed, or obtained via some secure medium), this provides strong assurance that the files listed have not been tampered with. .TP +.B "\-j, \-\-junk" +Report files whose hashes have not been checked. This is most useful in +conjunction with +.RB ` \-c ', +though it's valid without. The program merely prints warnings about +junk files when computing hashes, but will exit nonzero if any are found +when checking them. +.TP .B "\-b, \-\-binary" Assume that the files to be hashed are binary files. This doesn't make any difference in Unix systems, although it might on other platforms diff --git a/hashsum.c b/hashsum.c index 8419779..8a14a76 100644 --- a/hashsum.c +++ b/hashsum.c @@ -61,6 +61,35 @@ /*----- Guts --------------------------------------------------------------*/ +static int checkjunk(const char *path, const struct stat *st, void *p) +{ + const char *what; + fhashstate *fh = p; + + if (!st) { + if (fh->f & f_verbose) + fprintf(stderr, "JUNK (error %s) %s\n", strerror(errno), path); + else + moan("couldn't stat junk file `%s': %s", path, strerror(errno)); + } else { + what = describefile(st); + if (fh->f & f_verbose) + fprintf(stderr, "JUNK %s %s\n", what, path); + else + moan("found junk %s `%s'", what, path); + } + return (0); +} + +static int warnjunk(const char *path, const struct stat *st, void *p) +{ + if (st) + moan("unexpected %s `%s'", describefile(st), path); + else + moan("couldn't stat unexpected file `%s': %s", path, strerror(errno)); + return (0); +} + static int checkhash(fhashstate *fh, const char *file, const encodeops *e) { int rc; @@ -277,13 +306,14 @@ int main(int argc, char *argv[]) { "escape", 0, 0, 'e' }, { "check", 0, 0, 'c' }, + { "junk", 0, 0, 'j' }, { "binary", 0, 0, 'b' }, { "verbose", 0, 0, 'v' }, { "progress", 0, 0, 'p' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "hVu a:E:l f0 ecbvp", opts, 0, 0, 0); + int i = mdwopt(argc, argv, "hVu a:E:l f0 ecjbvp", opts, 0, 0, 0); if (i < 0) break; @@ -321,6 +351,9 @@ int main(int argc, char *argv[]) case 'c': fh.f |= f_check; break; + case 'j': + fh.f |= FHF_JUNK; + break; case 'b': fh.f |= FHF_BINARY; break; @@ -363,6 +396,15 @@ int main(int argc, char *argv[]) } } + if (fh.f & FHF_JUNK) { + if (fh.f & f_check) { + if (fhash_junk(&fh, checkjunk, &fh)) rc = EXIT_FAILURE; + } else { + if (fhash_junk(&fh, warnjunk, 0) < 0) rc = EXIT_FAILURE; + } + } + fhash_free(&fh); + return (rc); } -- 2.11.0