public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "Fabian Groffen" <grobian@gentoo.org>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] repo/proj/prefix:master commit in: scripts/rsync-generation/
Date: Wed, 28 Feb 2018 14:44:22 +0000 (UTC)	[thread overview]
Message-ID: <1519818052.c68a2cd50ce42f42aaf785c16b6f7936c6b36bc3.grobian@gentoo> (raw)

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;


             reply	other threads:[~2018-02-28 14:44 UTC|newest]

Thread overview: 79+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-02-28 14:44 Fabian Groffen [this message]
  -- strict thread matches above, loose matches on Subject: below --
2025-09-07 10:46 [gentoo-commits] repo/proj/prefix:master commit in: scripts/rsync-generation/ Fabian Groffen
2024-07-10 18:24 Fabian Groffen
2024-06-14 20:13 Fabian Groffen
2024-03-31 10:26 Fabian Groffen
2024-03-31 10:09 Fabian Groffen
2024-03-31  8:53 Fabian Groffen
2024-03-31  8:46 Fabian Groffen
2024-03-31  8:27 Fabian Groffen
2024-03-30 18:44 Fabian Groffen
2024-03-30 11:46 Fabian Groffen
2024-03-29 10:31 Fabian Groffen
2024-03-29 10:31 Fabian Groffen
2024-03-28 16:12 Fabian Groffen
2024-03-28 16:12 Fabian Groffen
2024-03-28 16:12 Fabian Groffen
2024-03-28 16:12 Fabian Groffen
2023-09-11 10:39 Fabian Groffen
2023-08-01  2:40 Benda XU
2023-04-09 16:06 Fabian Groffen
2022-08-17 19:27 Fabian Groffen
2022-07-24 20:11 Fabian Groffen
2019-06-07  5:44 Fabian Groffen
2019-06-07  5:44 Fabian Groffen
2018-05-14 15:54 Fabian Groffen
2018-03-29  5:55 Fabian Groffen
2018-03-27 14:03 Fabian Groffen
2018-03-17 20:59 Fabian Groffen
2018-03-17 20:59 Fabian Groffen
2018-03-12 10:06 Fabian Groffen
2018-03-10 15:04 Fabian Groffen
2018-03-07 18:04 Fabian Groffen
2018-03-03 21:42 Fabian Groffen
2018-03-01 16:36 Fabian Groffen
2018-03-01 14:03 Fabian Groffen
2018-03-01 13:00 Fabian Groffen
2018-03-01 10:55 Fabian Groffen
2018-03-01  6:42 Fabian Groffen
2018-02-28 19:09 Fabian Groffen
2018-02-28 18:44 Fabian Groffen
2018-02-28 18:44 Fabian Groffen
2018-02-28 18:44 Fabian Groffen
2018-02-28 14:44 Fabian Groffen
2018-02-22 19:45 Fabian Groffen
2018-02-22  7:29 Fabian Groffen
2018-02-21  8:53 Fabian Groffen
2018-02-17 17:19 Fabian Groffen
2018-02-17  8:13 Fabian Groffen
2017-12-01 13:45 Fabian Groffen
2017-11-29 21:36 Fabian Groffen
2017-11-29 21:36 Fabian Groffen
2017-11-29 19:30 Fabian Groffen
2017-11-29 19:30 Fabian Groffen
2017-11-29 19:30 Fabian Groffen
2017-11-29 16:46 Fabian Groffen
2017-11-29 16:46 Fabian Groffen
2017-11-29 14:38 Fabian Groffen
2017-11-27 14:10 Fabian Groffen
2017-11-27 13:07 Fabian Groffen
2017-11-27 13:07 Fabian Groffen
2017-09-09 18:39 Fabian Groffen
2016-10-12  7:24 Fabian Groffen
2016-09-09 13:38 Fabian Groffen
2016-09-07 11:02 Fabian Groffen
2016-08-17  4:26 Fabian Groffen
2016-08-16  7:57 Fabian Groffen
2016-07-29  9:01 Fabian Groffen
2016-07-29  8:08 Fabian Groffen
2016-05-03 18:35 Fabian Groffen
2016-05-03 16:08 Fabian Groffen
2016-04-14 15:38 Fabian Groffen
2016-04-14 13:39 Fabian Groffen
2016-04-06 12:32 Fabian Groffen
2016-04-06 11:28 Fabian Groffen
2016-04-06 11:28 Fabian Groffen
2016-04-06 10:50 Fabian Groffen
2016-04-06 10:49 Fabian Groffen
2016-01-05 19:08 Fabian Groffen
2015-08-31 18:53 Fabian Groffen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1519818052.c68a2cd50ce42f42aaf785c16b6f7936c6b36bc3.grobian@gentoo \
    --to=grobian@gentoo.org \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox