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 via email to