commit: c68a2cd50ce42f42aaf785c16b6f7936c6b36bc3
Author: Fabian Groffen <grobian <AT> gentoo <DOT> org>
AuthorDate: Wed Feb 28 11:40:52 2018 +0000
Commit: Fabian Groffen <grobian <AT> gentoo <DOT> org>
CommitDate: Wed Feb 28 11:40:52 2018 +0000
URL: https://gitweb.gentoo.org/repo/proj/prefix.git/commit/?id=c68a2cd5
hashgen: first shot at hashverify
This variant can verify the gx86 tree, or so it seems.
scripts/rsync-generation/hashgen.c | 748 +++++++++++++++++++++++++++++++++++--
1 file changed, 716 insertions(+), 32 deletions(-)
diff --git a/scripts/rsync-generation/hashgen.c
b/scripts/rsync-generation/hashgen.c
index fa0519fb04..5eb7b8640b 100644
--- a/scripts/rsync-generation/hashgen.c
+++ b/scripts/rsync-generation/hashgen.c
@@ -1,5 +1,6 @@
-/* Copyright 2006-2017 Gentoo Foundation; Distributed under the GPL v2 */
+/* Copyright 2006-2018 Gentoo Foundation; Distributed under the GPL v2 */
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
@@ -13,6 +14,7 @@
#include <openssl/whrlpool.h>
#include <blake2.h>
#include <zlib.h>
+#include <gpgme.h>
/* Generate thick Manifests based on thin Manifests */
@@ -20,8 +22,10 @@
* - app-crypt/libb2 (for BLAKE2, for as long as openssl doesn't include it)
* - dev-libs/openssl (for SHA, WHIRLPOOL)
* - sys-libs/zlib (for compressing Manifest files)
- * compile like this
- * ${CC} -o hashgen -fopenmp ${CFLAGS} -lssl -lcrypto -lb2 -lz hashgen.c
+ * - app-crypt/gpgme (for signing/verifying the top level manifest)
+ * compile like this:
+ * ${CC} -o hashgen -fopenmp ${CFLAGS} \
+ * -lssl -lcrypto -lb2 -lz `gpgme-config --libs` hashgen.c
*/
enum hash_impls {
@@ -38,9 +42,57 @@ static int hashes = HASH_DEFAULT;
static inline void
hex_hash(char *out, const unsigned char *buf, const int length)
{
- int i;
- for (i = 0; i < length; i++) {
- snprintf(&out[i * 2], 3, "%02x", buf[i]);
+ switch (length) {
+ /* SHA256_DIGEST_LENGTH */
+ case 32:
+ snprintf(out, 64 + 1,
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+ "%02x%02x",
+ buf[ 0], buf[ 1], buf[ 2], buf[ 3],
buf[ 4],
+ buf[ 5], buf[ 6], buf[ 7], buf[ 8],
buf[ 9],
+ buf[10], buf[11], buf[12], buf[13],
buf[14],
+ buf[15], buf[16], buf[17], buf[18],
buf[19],
+ buf[20], buf[21], buf[22], buf[23],
buf[24],
+ buf[25], buf[26], buf[27], buf[28],
buf[29],
+ buf[30], buf[31]
+ );
+ break;
+ /* SHA512_DIGEST_LENGTH, WHIRLPOOL_DIGEST_LENGTH,
BLAKE2B_OUTBYTES */
+ case 64:
+ snprintf(out, 128 + 1,
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+ "%02x%02x%02x%02x",
+ buf[ 0], buf[ 1], buf[ 2], buf[ 3],
buf[ 4],
+ buf[ 5], buf[ 6], buf[ 7], buf[ 8],
buf[ 9],
+ buf[10], buf[11], buf[12], buf[13],
buf[14],
+ buf[15], buf[16], buf[17], buf[18],
buf[19],
+ buf[20], buf[21], buf[22], buf[23],
buf[24],
+ buf[25], buf[26], buf[27], buf[28],
buf[29],
+ buf[30], buf[31], buf[32], buf[33],
buf[34],
+ buf[35], buf[36], buf[37], buf[38],
buf[39],
+ buf[40], buf[41], buf[42], buf[43],
buf[44],
+ buf[45], buf[46], buf[47], buf[48],
buf[49],
+ buf[50], buf[51], buf[52], buf[53],
buf[54],
+ buf[55], buf[56], buf[57], buf[58],
buf[59],
+ buf[60], buf[61], buf[62], buf[63]
+ );
+ break;
+ /* fallback case, should never be necessary */
+ default:
+ {
+ int i;
+ for (i = 0; i < length; i++) {
+ snprintf(&out[i * 2], 3, "%02x",
buf[i]);
+ }
+ }
+ break;
}
}
@@ -59,43 +111,32 @@ update_times(struct timeval *tv, struct stat *s)
}
static void
-write_hashes(
- struct timeval *tv,
- const char *root,
- const char *name,
- const char *type,
- FILE *m,
- gzFile gm)
+get_hashes(
+ const char *fname,
+ char *sha256,
+ char *sha512,
+ char *whrlpl,
+ char *blak2b,
+ size_t *flen)
{
FILE *f;
- char fname[8192];
- size_t flen = 0;
- char sha256[(SHA256_DIGEST_LENGTH * 2) + 1];
- char sha512[(SHA512_DIGEST_LENGTH * 2) + 1];
- char whrlpl[(WHIRLPOOL_DIGEST_LENGTH * 2) + 1];
- char blak2b[(BLAKE2B_OUTBYTES * 2) + 1];
char data[8192];
size_t len;
SHA256_CTX s256;
SHA512_CTX s512;
WHIRLPOOL_CTX whrl;
blake2b_state bl2b;
- struct stat s;
- snprintf(fname, sizeof(fname), "%s/%s", root, name);
if ((f = fopen(fname, "r")) == NULL)
return;
- if (stat(fname, &s) == 0)
- update_times(tv, &s);
-
SHA256_Init(&s256);
SHA512_Init(&s512);
WHIRLPOOL_Init(&whrl);
blake2b_init(&bl2b, BLAKE2B_OUTBYTES);
while ((len = fread(data, 1, sizeof(data), f)) > 0) {
- flen += len;
+ *flen += len;
#pragma omp parallel sections
{
#pragma omp section
@@ -120,6 +161,7 @@ write_hashes(
}
}
}
+ fclose(f);
#pragma omp parallel sections
{
@@ -155,7 +197,33 @@ write_hashes(
}
}
}
- fclose(f);
+}
+
+static void
+write_hashes(
+ struct timeval *tv,
+ const char *root,
+ const char *name,
+ const char *type,
+ FILE *m,
+ gzFile gm)
+{
+ size_t flen = 0;
+ char sha256[(SHA256_DIGEST_LENGTH * 2) + 1];
+ char sha512[(SHA512_DIGEST_LENGTH * 2) + 1];
+ char whrlpl[(WHIRLPOOL_DIGEST_LENGTH * 2) + 1];
+ char blak2b[(BLAKE2B_OUTBYTES * 2) + 1];
+ char data[8192];
+ char fname[8192];
+ size_t len;
+ struct stat s;
+
+ snprintf(fname, sizeof(fname), "%s/%s", root, name);
+
+ if (stat(fname, &s) == 0)
+ update_times(tv, &s);
+
+ get_hashes(fname, sha256, sha512, whrlpl, blak2b, &flen);
len = snprintf(data, sizeof(data), "%s %s %zd", type, name, flen);
if (hashes & HASH_BLAKE2B)
@@ -321,7 +389,7 @@ static char *str_manifest = "Manifest";
static char *str_manifest_gz = "Manifest.gz";
static char *str_manifest_files_gz = "Manifest.files.gz";
static char *
-process_dir(const char *dir)
+process_dir_gen(const char *dir)
{
char manifest[8192];
FILE *f;
@@ -426,7 +494,7 @@ process_dir(const char *dir)
gzwrite(mf,
buf, len);
}
} else {
- char *mfest =
process_dir(path);
+ char *mfest =
process_dir_gen(path);
if (mfest == NULL) {
gzclose(mf);
return NULL;
@@ -560,15 +628,631 @@ process_dir(const char *dir)
}
}
+static char
+verify_gpg_sig(const char *path)
+{
+ gpgme_ctx_t g_ctx;
+ gpgme_data_t manifest;
+ gpgme_data_t out;
+ gpgme_verify_result_t vres;
+ gpgme_signature_t sig;
+ gpgme_key_t key;
+ char buf[32];
+ FILE *f;
+ struct tm *ctime;
+ char ret = 1; /* fail */
+
+ if ((f = fopen(path, "r")) == NULL) {
+ fprintf(stderr, "failed to open %s: %s\n", path,
strerror(errno));
+ return ret;
+ }
+
+ if (gpgme_new(&g_ctx) != GPG_ERR_NO_ERROR) {
+ fprintf(stderr, "failed to create gpgme context\n");
+ return ret;
+ }
+
+ if (gpgme_data_new(&out) != GPG_ERR_NO_ERROR) {
+ fprintf(stderr, "failed to create new gpgme data\n");
+ return ret;
+ }
+
+ if (gpgme_data_new_from_stream(&manifest, f) != GPG_ERR_NO_ERROR) {
+ fprintf(stderr, "failed to create new gpgme data from
stream\n");
+ return ret;
+ }
+
+ if (gpgme_op_verify(g_ctx, manifest, NULL, out) != GPG_ERR_NO_ERROR) {
+ fprintf(stderr, "failed to verify signature\n");
+ return ret;
+ }
+
+ vres = gpgme_op_verify_result(g_ctx);
+ fclose(f);
+
+ for (sig = vres->signatures; sig != NULL; sig = sig->next) {
+ ctime = gmtime((time_t *)&sig->timestamp);
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", ctime);
+ printf("%s key fingerprint "
+ "%.4s %.4s %.4s %.4s %.4s %.4s %.4s %.4s %.4s
%.4s\n"
+ "%s signature made %s by\n",
+ gpgme_pubkey_algo_name(sig->pubkey_algo),
+ sig->fpr + 0, sig->fpr + 4, sig->fpr + 8,
sig->fpr + 12,
+ sig->fpr + 16, sig->fpr + 20, sig->fpr + 24,
sig->fpr + 28,
+ sig->fpr + 32, sig->fpr + 36,
+ sig->status == GPG_ERR_NO_ERROR ? "good" :
"BAD",
+ buf);
+
+ if (gpgme_get_key(g_ctx, sig->fpr, &key, 0) ==
GPG_ERR_NO_ERROR) {
+ ret = 0; /* valid */
+ if (key->uids != NULL)
+ printf("%s\n", key->uids->uid);
+ gpgme_key_release(key);
+ }
+
+ switch (sig->status) {
+ case GPG_ERR_NO_ERROR:
+ /* nothing, handled above */
+ break;
+ case GPG_ERR_SIG_EXPIRED:
+ printf("the signature is valid but expired\n");
+ break;
+ case GPG_ERR_KEY_EXPIRED:
+ printf("the signature is valid but the key used
to verify "
+ "the signature has expired\n");
+ break;
+ case GPG_ERR_CERT_REVOKED:
+ printf("the signature is valid but the key used
to verify "
+ "the signature has been
revoked\n");
+ break;
+ case GPG_ERR_BAD_SIGNATURE:
+ printf("the signature is invalid\n");
+ break;
+ case GPG_ERR_NO_PUBKEY:
+ printf("the signature could not be verified due
to a "
+ "missing key\n");
+ break;
+ default:
+ printf("there was some other error which
prevented the "
+ "signature verification\n");
+ break;
+ }
+ }
+
+ gpgme_release(g_ctx);
+
+ return ret;
+}
+
+static char
+verify_file(const char *dir, char *mfline)
+{
+ char *path;
+ char *size;
+ long long int fsize;
+ char *hashtype;
+ char *hash;
+ char *p;
+ char buf[8192];
+ size_t flen = 0;
+ char sha256[(SHA256_DIGEST_LENGTH * 2) + 1];
+ char sha512[(SHA512_DIGEST_LENGTH * 2) + 1];
+ char whrlpl[(WHIRLPOOL_DIGEST_LENGTH * 2) + 1];
+ char blak2b[(BLAKE2B_OUTBYTES * 2) + 1];
+ char ret = 0;
+
+ /* mfline is a Manifest file line with type stripped, something like:
+ * path/to/file <SIZE> <HASHTYPE HASH ...>
+ * we parse this, and verify the size and hashes */
+
+ path = mfline;
+ p = strchr(path, ' ');
+ if (p == NULL) {
+ fprintf(stderr, "%s: corrupt manifest line: %s\n", dir, path);
+ return 1;
+ }
+ *p++ = '\0';
+
+ size = p;
+ p = strchr(size, ' ');
+ if (p == NULL) {
+ fprintf(stderr, "%s: corrupt manifest line, need size for %s\n",
+ dir, path);
+ return 1;
+ }
+ *p++ = '\0';
+ fsize = strtoll(size, NULL, 10);
+ if (fsize == 0 && errno == EINVAL) {
+ fprintf(stderr, "%s: corrupt manifest line, size is not a
number: %s\n",
+ dir, size);
+ return 1;
+ }
+
+ sha256[0] = sha512[0] = whrlpl[0] = blak2b[0] = '\0';
+ snprintf(buf, sizeof(buf), "%s/%s", dir, path);
+ get_hashes(buf, sha256, sha512, whrlpl, blak2b, &flen);
+
+ if (flen == 0) {
+ fprintf(stderr, "cannot locate %s\n", path);
+ return 1;
+ }
+
+ if (flen != fsize) {
+ fprintf(stderr, "%s: size mismatch, got: %zd, expected: %lld\n",
+ path, flen, fsize);
+ return 1;
+ }
+
+ /* now we are in free territory, we read TYPE HASH pairs until we
+ * drained the string, and match them against what we computed */
+ while (p != NULL && *p != '\0') {
+ hashtype = p;
+ p = strchr(hashtype, ' ');
+ if (p == NULL) {
+ fprintf(stderr, "%s: corrupt manifest line, missing
hash type\n",
+ path);
+ return 1;
+ }
+ *p++ = '\0';
+
+ hash = p;
+ p = strchr(hash, ' ');
+ if (p != NULL)
+ *p++ = '\0';
+
+ if (strcmp(hashtype, "SHA256") == 0) {
+ if (!(hashes & HASH_SHA256)) {
+ printf("- warning: hash SHA256 ignored as "
+ "it is not enabled for this
repository\n");
+ } else if (strcmp(hash, sha256) != 0) {
+ printf("- SHA256 hash mismatch\n"
+ " computed: '%s'\n"
+ " recorded in manifest:
'%s'\n",
+ sha256, hash);
+ ret = 1;
+ }
+ sha256[0] = '\0';
+ } else if (strcmp(hashtype, "SHA512") == 0) {
+ if (!(hashes & HASH_SHA512)) {
+ printf("- warning: hash SHA512 ignored as "
+ "it is not enabled for this
repository\n");
+ } else if (strcmp(hash, sha512) != 0) {
+ printf("- SHA512 hash mismatch\n"
+ " computed: '%s'\n"
+ " recorded in manifest:
'%s'\n",
+ sha512, hash);
+ ret = 1;
+ }
+ sha512[0] = '\0';
+ } else if (strcmp(hashtype, "WHIRLPOOL") == 0) {
+ if (!(hashes & HASH_WHIRLPOOL)) {
+ printf("- warning: hash WHIRLPOOL ignored as "
+ "it is not enabled for this
repository\n");
+ } else if (strcmp(hash, whrlpl) != 0) {
+ printf("- WHIRLPOOL hash mismatch\n"
+ " computed: '%s'\n"
+ " recorded in manifest:
'%s'\n",
+ whrlpl, hash);
+ ret = 1;
+ }
+ whrlpl[0] = '\0';
+ } else if (strcmp(hashtype, "BLAKE2B") == 0) {
+ if (!(hashes & HASH_BLAKE2B)) {
+ printf("- warning: hash BLAKE2B ignored as "
+ "it is not enabled for this
repository\n");
+ } else if (strcmp(hash, blak2b) != 0) {
+ printf("- BLAKE2B hash mismatch\n"
+ " computed: '%s'\n"
+ " recorded in manifest:
'%s'\n",
+ blak2b, hash);
+ ret = 1;
+ }
+ blak2b[0] = '\0';
+ } else {
+ printf("- unsupported hash: %s\n", hashtype);
+ ret = 1;
+ }
+ }
+
+ if (sha256[0] != '\0') {
+ printf("- missing hash: SHA256\n");
+ ret = 1;
+ }
+ if (sha512[0] != '\0') {
+ printf("- missing hash: SHA512\n");
+ ret = 1;
+ }
+ if (whrlpl[0] != '\0') {
+ printf("- missing hash: WHIRLPOOL\n");
+ ret = 1;
+ }
+ if (blak2b[0] != '\0') {
+ printf("- missing hash: BLAKE2B\n");
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int
+compare_elems(const void *l, const void *r)
+{
+ const char *strl = *((const char **)l) + 2;
+ const char *strr = *((const char **)r) + 2;
+ unsigned char cl;
+ unsigned char cr;
+ /* compare treating / as end of string */
+ while ((cl = *strl++) == (cr = *strr++))
+ if (cl == '\0')
+ return 0;
+ if (cl == '/')
+ cl = '\0';
+ if (cr == '/')
+ cr = '\0';
+ return cl - cr;
+}
+
+static int
+compare_strings(const void *l, const void *r)
+{
+ const char **strl = (const char **)l;
+ const char **strr = (const char **)r;
+ return strcmp(*strl, *strr);
+}
+
+static char verify_manifest(const char *dir, const char *manifest);
+
+#define LISTSZ 64
+static char
+verify_dir(const char *dir, char **elems, size_t elemslen, size_t skippath)
+{
+ DIR *d;
+ struct dirent *e;
+ char **dentries = NULL;
+ size_t dentrieslen = 0;
+ size_t dentriessize = 0;
+ size_t curelem = 0;
+ size_t curdentry = 0;
+ char *entry;
+ char *slash;
+ char etpe;
+ char ret = 0;
+ int cmp;
+
+ /* shortcut a single Manifest entry pointing to the same dir
+ * (happens at top-level) */
+ if (elemslen == 1 && skippath == 0 &&
+ **elems == 'M' && strchr(*elems + 2, '/') == NULL)
+ {
+ if ((ret = verify_file(dir, *elems + 2)) == 0) {
+ slash = strchr(*elems + 2, ' ');
+ if (slash != NULL)
+ *slash = '\0';
+ /* else, verify_manifest will fail, so ret will be
handled */
+ ret = verify_manifest(dir, *elems + 2);
+ }
+ return ret;
+ }
+
+ /*
+ * We have a list of entries from the manifest just read, now we
+ * need to match these onto the directory layout. From what we got
+ * - we can ignore TIMESTAMP and DIST entries
+ * - IGNOREs need to be handled separate (shortcut)
+ * - MANIFESTs need to be handled on their own, for memory
+ * consumption reasons, we defer them to until we've verified
+ * what's left, we treat the path the Manifest refers to as IGNORE
+ * - DATAs, EBUILDs and MISCs needs verifying
+ * - AUXs need verifying, but in files/ subdir
+ * If we sort both lists, we should be able to do a merge-join, to
+ * easily flag missing entries in either list without hashing or
+ * anything.
+ */
+ if ((d = opendir(dir)) != NULL) {
+ while ((e = readdir(d)) != NULL) {
+ /* skip all dotfiles and Manifest files */
+ if (e->d_name[0] == '.' ||
+ strcmp(e->d_name, str_manifest) == 0 ||
+ strcmp(e->d_name, str_manifest_gz) == 0
||
+ strcmp(e->d_name,
str_manifest_files_gz) == 0)
+ {
+ continue;
+ }
+
+ if (dentrieslen == dentriessize) {
+ dentriessize += LISTSZ;
+ dentries = realloc(dentries,
+ dentriessize *
sizeof(dentries[0]));
+ if (dentries == NULL) {
+ fprintf(stderr, "out of memory\n");
+ return 1;
+ }
+ }
+ dentries[dentrieslen] = strdup(e->d_name);
+ if (dentries[dentrieslen] == NULL) {
+ fprintf(stderr, "out of memory\n");
+ return 1;
+ }
+ dentrieslen++;
+ }
+ closedir(d);
+
+ qsort(dentries, dentrieslen, sizeof(dentries[0]),
compare_strings);
+
+ while (curdentry < dentrieslen) {
+ if (curelem < elemslen) {
+ entry = elems[curelem] + 2 + skippath;
+ etpe = *elems[curelem];
+ } else {
+ entry = "";
+ etpe = 'I';
+ }
+
+ /* handle subdirs first */
+ if ((slash = strchr(entry, '/')) != NULL) {
+ size_t sublen = slash - entry;
+ char ndir[8192];
+
+ if (etpe == 'M') {
+ size_t skiplen = strlen(dir) + 1 +
sublen;
+ /* sub-Manifest, we need to do a proper
recurse */
+ slash = strrchr(entry, '/'); /* cannot
be NULL */
+ snprintf(ndir, sizeof(ndir),
+ "%s/%s", dir, entry);
+ ndir[skiplen] = '\0';
+ slash = strchr(ndir + skiplen + 1, ' ');
+ if (slash != NULL) /* path should fit
in ndir ... */
+ *slash = '\0';
+ if (verify_file(dir, entry) != 0 ||
+ verify_manifest(ndir, ndir +
skiplen + 1) != 0)
+ ret |= 1;
+ } else {
+ int elemstart = curelem;
+ char **subelems = &elems[curelem];
+ /* collect all entries like this one
(same subdir) into
+ * a sub-list that we can verify */
+ curelem++;
+ while (curelem < elemslen &&
+ strncmp(entry,
elems[curelem] + 2 + skippath,
+ sublen + 1) ==
0)
+ curelem++;
+ snprintf(ndir, sizeof(ndir), "%s/%.*s",
dir,
+ (int)sublen,
elems[elemstart] + 2 + skippath);
+ ret |= verify_dir(ndir, subelems,
+ curelem - elemstart,
skippath + sublen + 1);
+ curelem--; /* move back, see below */
+ }
+
+ /* modify the last entry to be the subdir, such
that we
+ * can let the code below synchronise with
dentries */
+ elems[curelem][2 + skippath + sublen] = '\0';
+ entry = elems[curelem] + 2 + skippath;
+ etpe = 'S'; /* flag this was a subdir */
+ }
+
+ /* does this entry exist in list? */
+ if (*entry == '\0') {
+ /* end of list reached, force dir to catch up */
+ cmp = 1;
+ } else {
+ slash = strchr(entry, ' ');
+ if (slash != NULL)
+ *slash = '\0';
+ cmp = strcmp(entry, dentries[curdentry]);
+ if (slash != NULL)
+ *slash = ' ';
+ }
+ if (cmp == 0) {
+ /* equal, so yay */
+ if (etpe == 'D') {
+ ret |= verify_file(dir, entry);
+ }
+ /* else this is I(GNORE) or S(ubdir), which
means it is
+ * ok in any way (M shouldn't happen) */
+ curelem++;
+ curdentry++;
+ } else if (cmp < 0) {
+ /* entry is missing from dir */
+ if (etpe == 'I') {
+ /* right, we can ignore this */
+ } else {
+ ret |= 1;
+ slash = strchr(entry, ' ');
+ if (slash != NULL)
+ *slash = '\0';
+ fprintf(stderr, "%s: missing %s file:
%s\n",
+ dir, etpe == 'M' ?
"MANIFEST" : "DATA", entry);
+ }
+ curelem++;
+ } else if (cmp > 0) {
+ /* dir has extra element */
+ ret |= 1;
+ fprintf(stderr, "%s: stray file not in
Manifest: %s\n",
+ dir, dentries[curdentry]);
+ curdentry++;
+ }
+ }
+ free(dentries);
+ return ret;
+ } else {
+ return 1;
+ }
+}
+
+static char
+verify_manifest(const char *dir, const char *manifest)
+{
+ char buf[8192];
+ FILE *f;
+ gzFile mf;
+
+ size_t elemssize = 0;
+ size_t elemslen = 0;
+ char **elems = NULL;
+#define append_list(STR) \
+ if (strncmp(STR, "TIMESTAMP ", 10) != 0 || strncmp(STR, "DIST ", 5) !=
0) {\
+ char *endp = STR + strlen(STR) - 1;\
+ while (isspace(*endp))\
+ *endp-- = '\0';\
+ if (elemslen == elemssize) {\
+ elemssize += LISTSZ;\
+ elems = realloc(elems, elemssize * sizeof(elems[0]));\
+ if (elems == NULL) {\
+ fprintf(stderr, "out of memory\n");\
+ return 1;\
+ }\
+ }\
+ if (strncmp(STR, "IGNORE ", 7) == 0) {\
+ STR[5] = 'I';\
+ elems[elemslen] = strdup(STR + 5);\
+ if (elems[elemslen] == NULL) {\
+ fprintf(stderr, "out of memory\n");\
+ return 1;\
+ }\
+ elemslen++;\
+ } else if (strncmp(STR, "MANIFEST ", 9) == 0) {\
+ STR[7] = 'M';\
+ elems[elemslen] = strdup(STR + 7);\
+ if (elems[elemslen] == NULL) {\
+ fprintf(stderr, "out of memory\n");\
+ return 1;\
+ }\
+ elemslen++;\
+ } else if (strncmp(STR, "DATA ", 5) == 0 ||\
+ strncmp(STR, "MISC ", 5) == 0 ||\
+ strncmp(STR, "EBUILD ", 7) == 0)\
+ {\
+ if (*STR == 'E') {\
+ STR[5] = 'D';\
+ elems[elemslen] = strdup(STR + 5);\
+ } else {\
+ STR[3] = 'D';\
+ elems[elemslen] = strdup(STR + 3);\
+ }\
+ if (elems[elemslen] == NULL) {\
+ fprintf(stderr, "out of memory\n");\
+ return 1;\
+ }\
+ elemslen++;\
+ } else if (strncmp(STR, "AUX ", 4) == 0) {\
+ /* translate directly into what it is: DATA in files/
*/\
+ size_t slen = strlen(STR + 2) + sizeof("files/");\
+ elems[elemslen] = malloc(slen);\
+ if (elems[elemslen] == NULL) {\
+ fprintf(stderr, "out of memory\n");\
+ return 1;\
+ }\
+ snprintf(elems[elemslen], slen, "D files/%s", STR + 4);\
+ elemslen++;\
+ }\
+ }
+
+ snprintf(buf, sizeof(buf), "%s/%s", dir, manifest);
+ if (strcmp(manifest, str_manifest) == 0) {
+ if ((f = fopen(buf, "r")) == NULL) {
+ fprintf(stderr, "failed to open %s: %s\n",
+ buf, strerror(errno));
+ return 1;
+ }
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ append_list(buf);
+ }
+ fclose(f);
+ } else if (strcmp(manifest, str_manifest_files_gz) == 0 ||
+ strcmp(manifest, str_manifest_gz) == 0)
+ {
+ if ((mf = gzopen(buf, "rb9")) == NULL) {
+ fprintf(stderr, "failed to open file '%s' for reading:
%s\n",
+ buf, strerror(errno));
+ return 1;
+ }
+ while (gzgets(mf, buf, sizeof(buf)) != NULL) {
+ append_list(buf);
+ }
+ gzclose(mf);
+ }
+
+ /* The idea:
+ * - Manifest without MANIFEST entries, we need to scan the entire
+ * subtree
+ * - Manifest with MANIFEST entries, assume they are just one level
+ * deeper, thus ignore that subdir, further like above
+ * - Manifest at top-level, needs to be igored as it only points to
+ * the larger Manifest.files.gz
+ */
+ qsort(elems, elemslen, sizeof(elems[0]), compare_elems);
+ verify_dir(dir, elems, elemslen, 0);
+ free(elems);
+
+ return 0;
+}
+
+static char *
+process_dir_vrfy(const char *dir)
+{
+ char *ret = NULL;
+ char buf[8192];
+ int newhashes;
+
+ snprintf(buf, sizeof(buf), "%s/metadata/layout.conf", dir);
+ if ((newhashes = parse_layout_conf(buf)) != 0) {
+ hashes = newhashes;
+ } else {
+ fprintf(stderr, "verification must be done on a full tree\n");
+ return "not on full tree";
+ }
+
+ snprintf(buf, sizeof(buf), "%s/%s", dir, str_manifest);
+ if (verify_gpg_sig(buf) != 0)
+ ret = "gpg signature invalid";
+
+ /* verification goes like this:
+ * - verify the signature of the top-level Manifest file (done
+ * above)
+ * - read the contents of the Manifest file, and process the
+ * entries - verify them, check there are no files which shouldn't
+ * be there
+ * - recurse into directories for which Manifest files are defined */
+
+ if (verify_manifest(dir, str_manifest) != 0)
+ ret = "manifest verification failed";
+
+ return ret;
+}
+
int
main(int argc, char *argv[])
{
+ char *prog;
+ char *(*runfunc)(const char *);
+ int arg = 1;
+
+ if ((prog = strrchr(argv[0], '/')) == NULL)
+ prog = argv[0];
+
+ if (argc > 1) {
+ if (strcmp(argv[1], "hashverify") == 0 ||
+ strcmp(argv[1], "hashgen") == 0)
+ {
+ prog = argv[1];
+ arg = 2;
+ }
+ }
+
+ if (strcmp(prog, "hashverify") == 0) {
+ runfunc = &process_dir_vrfy;
+ } else {
+ /* default mode: hashgen */
+ runfunc = &process_dir_gen;
+ }
+
+ gpgme_check_version(NULL);
+
if (argc > 1) {
- int i;
- for (i = 1; i < argc; i++)
- process_dir(argv[i]);
+ for (; arg < argc; arg++)
+ runfunc(argv[arg]);
} else {
- process_dir(".");
+ runfunc(".");
}
return 0;