Gentoo Archives: gentoo-commits

From: Fabian Groffen <grobian@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] repo/proj/prefix:master commit in: scripts/rsync-generation/
Date: Wed, 28 Feb 2018 14:44:27
Message-Id: 1519818052.c68a2cd50ce42f42aaf785c16b6f7936c6b36bc3.grobian@gentoo
1 commit: c68a2cd50ce42f42aaf785c16b6f7936c6b36bc3
2 Author: Fabian Groffen <grobian <AT> gentoo <DOT> org>
3 AuthorDate: Wed Feb 28 11:40:52 2018 +0000
4 Commit: Fabian Groffen <grobian <AT> gentoo <DOT> org>
5 CommitDate: Wed Feb 28 11:40:52 2018 +0000
6 URL: https://gitweb.gentoo.org/repo/proj/prefix.git/commit/?id=c68a2cd5
7
8 hashgen: first shot at hashverify
9
10 This variant can verify the gx86 tree, or so it seems.
11
12 scripts/rsync-generation/hashgen.c | 748 +++++++++++++++++++++++++++++++++++--
13 1 file changed, 716 insertions(+), 32 deletions(-)
14
15 diff --git a/scripts/rsync-generation/hashgen.c b/scripts/rsync-generation/hashgen.c
16 index fa0519fb04..5eb7b8640b 100644
17 --- a/scripts/rsync-generation/hashgen.c
18 +++ b/scripts/rsync-generation/hashgen.c
19 @@ -1,5 +1,6 @@
20 -/* Copyright 2006-2017 Gentoo Foundation; Distributed under the GPL v2 */
21 +/* Copyright 2006-2018 Gentoo Foundation; Distributed under the GPL v2 */
22 #include <stdio.h>
23 +#include <stdlib.h>
24 #include <string.h>
25 #include <strings.h>
26 #include <ctype.h>
27 @@ -13,6 +14,7 @@
28 #include <openssl/whrlpool.h>
29 #include <blake2.h>
30 #include <zlib.h>
31 +#include <gpgme.h>
32
33 /* Generate thick Manifests based on thin Manifests */
34
35 @@ -20,8 +22,10 @@
36 * - app-crypt/libb2 (for BLAKE2, for as long as openssl doesn't include it)
37 * - dev-libs/openssl (for SHA, WHIRLPOOL)
38 * - sys-libs/zlib (for compressing Manifest files)
39 - * compile like this
40 - * ${CC} -o hashgen -fopenmp ${CFLAGS} -lssl -lcrypto -lb2 -lz hashgen.c
41 + * - app-crypt/gpgme (for signing/verifying the top level manifest)
42 + * compile like this:
43 + * ${CC} -o hashgen -fopenmp ${CFLAGS} \
44 + * -lssl -lcrypto -lb2 -lz `gpgme-config --libs` hashgen.c
45 */
46
47 enum hash_impls {
48 @@ -38,9 +42,57 @@ static int hashes = HASH_DEFAULT;
49 static inline void
50 hex_hash(char *out, const unsigned char *buf, const int length)
51 {
52 - int i;
53 - for (i = 0; i < length; i++) {
54 - snprintf(&out[i * 2], 3, "%02x", buf[i]);
55 + switch (length) {
56 + /* SHA256_DIGEST_LENGTH */
57 + case 32:
58 + snprintf(out, 64 + 1,
59 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
60 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
61 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
62 + "%02x%02x",
63 + buf[ 0], buf[ 1], buf[ 2], buf[ 3], buf[ 4],
64 + buf[ 5], buf[ 6], buf[ 7], buf[ 8], buf[ 9],
65 + buf[10], buf[11], buf[12], buf[13], buf[14],
66 + buf[15], buf[16], buf[17], buf[18], buf[19],
67 + buf[20], buf[21], buf[22], buf[23], buf[24],
68 + buf[25], buf[26], buf[27], buf[28], buf[29],
69 + buf[30], buf[31]
70 + );
71 + break;
72 + /* SHA512_DIGEST_LENGTH, WHIRLPOOL_DIGEST_LENGTH, BLAKE2B_OUTBYTES */
73 + case 64:
74 + snprintf(out, 128 + 1,
75 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
76 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
77 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
78 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
79 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
80 + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
81 + "%02x%02x%02x%02x",
82 + buf[ 0], buf[ 1], buf[ 2], buf[ 3], buf[ 4],
83 + buf[ 5], buf[ 6], buf[ 7], buf[ 8], buf[ 9],
84 + buf[10], buf[11], buf[12], buf[13], buf[14],
85 + buf[15], buf[16], buf[17], buf[18], buf[19],
86 + buf[20], buf[21], buf[22], buf[23], buf[24],
87 + buf[25], buf[26], buf[27], buf[28], buf[29],
88 + buf[30], buf[31], buf[32], buf[33], buf[34],
89 + buf[35], buf[36], buf[37], buf[38], buf[39],
90 + buf[40], buf[41], buf[42], buf[43], buf[44],
91 + buf[45], buf[46], buf[47], buf[48], buf[49],
92 + buf[50], buf[51], buf[52], buf[53], buf[54],
93 + buf[55], buf[56], buf[57], buf[58], buf[59],
94 + buf[60], buf[61], buf[62], buf[63]
95 + );
96 + break;
97 + /* fallback case, should never be necessary */
98 + default:
99 + {
100 + int i;
101 + for (i = 0; i < length; i++) {
102 + snprintf(&out[i * 2], 3, "%02x", buf[i]);
103 + }
104 + }
105 + break;
106 }
107 }
108
109 @@ -59,43 +111,32 @@ update_times(struct timeval *tv, struct stat *s)
110 }
111
112 static void
113 -write_hashes(
114 - struct timeval *tv,
115 - const char *root,
116 - const char *name,
117 - const char *type,
118 - FILE *m,
119 - gzFile gm)
120 +get_hashes(
121 + const char *fname,
122 + char *sha256,
123 + char *sha512,
124 + char *whrlpl,
125 + char *blak2b,
126 + size_t *flen)
127 {
128 FILE *f;
129 - char fname[8192];
130 - size_t flen = 0;
131 - char sha256[(SHA256_DIGEST_LENGTH * 2) + 1];
132 - char sha512[(SHA512_DIGEST_LENGTH * 2) + 1];
133 - char whrlpl[(WHIRLPOOL_DIGEST_LENGTH * 2) + 1];
134 - char blak2b[(BLAKE2B_OUTBYTES * 2) + 1];
135 char data[8192];
136 size_t len;
137 SHA256_CTX s256;
138 SHA512_CTX s512;
139 WHIRLPOOL_CTX whrl;
140 blake2b_state bl2b;
141 - struct stat s;
142
143 - snprintf(fname, sizeof(fname), "%s/%s", root, name);
144 if ((f = fopen(fname, "r")) == NULL)
145 return;
146
147 - if (stat(fname, &s) == 0)
148 - update_times(tv, &s);
149 -
150 SHA256_Init(&s256);
151 SHA512_Init(&s512);
152 WHIRLPOOL_Init(&whrl);
153 blake2b_init(&bl2b, BLAKE2B_OUTBYTES);
154
155 while ((len = fread(data, 1, sizeof(data), f)) > 0) {
156 - flen += len;
157 + *flen += len;
158 #pragma omp parallel sections
159 {
160 #pragma omp section
161 @@ -120,6 +161,7 @@ write_hashes(
162 }
163 }
164 }
165 + fclose(f);
166
167 #pragma omp parallel sections
168 {
169 @@ -155,7 +197,33 @@ write_hashes(
170 }
171 }
172 }
173 - fclose(f);
174 +}
175 +
176 +static void
177 +write_hashes(
178 + struct timeval *tv,
179 + const char *root,
180 + const char *name,
181 + const char *type,
182 + FILE *m,
183 + gzFile gm)
184 +{
185 + size_t flen = 0;
186 + char sha256[(SHA256_DIGEST_LENGTH * 2) + 1];
187 + char sha512[(SHA512_DIGEST_LENGTH * 2) + 1];
188 + char whrlpl[(WHIRLPOOL_DIGEST_LENGTH * 2) + 1];
189 + char blak2b[(BLAKE2B_OUTBYTES * 2) + 1];
190 + char data[8192];
191 + char fname[8192];
192 + size_t len;
193 + struct stat s;
194 +
195 + snprintf(fname, sizeof(fname), "%s/%s", root, name);
196 +
197 + if (stat(fname, &s) == 0)
198 + update_times(tv, &s);
199 +
200 + get_hashes(fname, sha256, sha512, whrlpl, blak2b, &flen);
201
202 len = snprintf(data, sizeof(data), "%s %s %zd", type, name, flen);
203 if (hashes & HASH_BLAKE2B)
204 @@ -321,7 +389,7 @@ static char *str_manifest = "Manifest";
205 static char *str_manifest_gz = "Manifest.gz";
206 static char *str_manifest_files_gz = "Manifest.files.gz";
207 static char *
208 -process_dir(const char *dir)
209 +process_dir_gen(const char *dir)
210 {
211 char manifest[8192];
212 FILE *f;
213 @@ -426,7 +494,7 @@ process_dir(const char *dir)
214 gzwrite(mf, buf, len);
215 }
216 } else {
217 - char *mfest = process_dir(path);
218 + char *mfest = process_dir_gen(path);
219 if (mfest == NULL) {
220 gzclose(mf);
221 return NULL;
222 @@ -560,15 +628,631 @@ process_dir(const char *dir)
223 }
224 }
225
226 +static char
227 +verify_gpg_sig(const char *path)
228 +{
229 + gpgme_ctx_t g_ctx;
230 + gpgme_data_t manifest;
231 + gpgme_data_t out;
232 + gpgme_verify_result_t vres;
233 + gpgme_signature_t sig;
234 + gpgme_key_t key;
235 + char buf[32];
236 + FILE *f;
237 + struct tm *ctime;
238 + char ret = 1; /* fail */
239 +
240 + if ((f = fopen(path, "r")) == NULL) {
241 + fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno));
242 + return ret;
243 + }
244 +
245 + if (gpgme_new(&g_ctx) != GPG_ERR_NO_ERROR) {
246 + fprintf(stderr, "failed to create gpgme context\n");
247 + return ret;
248 + }
249 +
250 + if (gpgme_data_new(&out) != GPG_ERR_NO_ERROR) {
251 + fprintf(stderr, "failed to create new gpgme data\n");
252 + return ret;
253 + }
254 +
255 + if (gpgme_data_new_from_stream(&manifest, f) != GPG_ERR_NO_ERROR) {
256 + fprintf(stderr, "failed to create new gpgme data from stream\n");
257 + return ret;
258 + }
259 +
260 + if (gpgme_op_verify(g_ctx, manifest, NULL, out) != GPG_ERR_NO_ERROR) {
261 + fprintf(stderr, "failed to verify signature\n");
262 + return ret;
263 + }
264 +
265 + vres = gpgme_op_verify_result(g_ctx);
266 + fclose(f);
267 +
268 + for (sig = vres->signatures; sig != NULL; sig = sig->next) {
269 + ctime = gmtime((time_t *)&sig->timestamp);
270 + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", ctime);
271 + printf("%s key fingerprint "
272 + "%.4s %.4s %.4s %.4s %.4s %.4s %.4s %.4s %.4s %.4s\n"
273 + "%s signature made %s by\n",
274 + gpgme_pubkey_algo_name(sig->pubkey_algo),
275 + sig->fpr + 0, sig->fpr + 4, sig->fpr + 8, sig->fpr + 12,
276 + sig->fpr + 16, sig->fpr + 20, sig->fpr + 24, sig->fpr + 28,
277 + sig->fpr + 32, sig->fpr + 36,
278 + sig->status == GPG_ERR_NO_ERROR ? "good" : "BAD",
279 + buf);
280 +
281 + if (gpgme_get_key(g_ctx, sig->fpr, &key, 0) == GPG_ERR_NO_ERROR) {
282 + ret = 0; /* valid */
283 + if (key->uids != NULL)
284 + printf("%s\n", key->uids->uid);
285 + gpgme_key_release(key);
286 + }
287 +
288 + switch (sig->status) {
289 + case GPG_ERR_NO_ERROR:
290 + /* nothing, handled above */
291 + break;
292 + case GPG_ERR_SIG_EXPIRED:
293 + printf("the signature is valid but expired\n");
294 + break;
295 + case GPG_ERR_KEY_EXPIRED:
296 + printf("the signature is valid but the key used to verify "
297 + "the signature has expired\n");
298 + break;
299 + case GPG_ERR_CERT_REVOKED:
300 + printf("the signature is valid but the key used to verify "
301 + "the signature has been revoked\n");
302 + break;
303 + case GPG_ERR_BAD_SIGNATURE:
304 + printf("the signature is invalid\n");
305 + break;
306 + case GPG_ERR_NO_PUBKEY:
307 + printf("the signature could not be verified due to a "
308 + "missing key\n");
309 + break;
310 + default:
311 + printf("there was some other error which prevented the "
312 + "signature verification\n");
313 + break;
314 + }
315 + }
316 +
317 + gpgme_release(g_ctx);
318 +
319 + return ret;
320 +}
321 +
322 +static char
323 +verify_file(const char *dir, char *mfline)
324 +{
325 + char *path;
326 + char *size;
327 + long long int fsize;
328 + char *hashtype;
329 + char *hash;
330 + char *p;
331 + char buf[8192];
332 + size_t flen = 0;
333 + char sha256[(SHA256_DIGEST_LENGTH * 2) + 1];
334 + char sha512[(SHA512_DIGEST_LENGTH * 2) + 1];
335 + char whrlpl[(WHIRLPOOL_DIGEST_LENGTH * 2) + 1];
336 + char blak2b[(BLAKE2B_OUTBYTES * 2) + 1];
337 + char ret = 0;
338 +
339 + /* mfline is a Manifest file line with type stripped, something like:
340 + * path/to/file <SIZE> <HASHTYPE HASH ...>
341 + * we parse this, and verify the size and hashes */
342 +
343 + path = mfline;
344 + p = strchr(path, ' ');
345 + if (p == NULL) {
346 + fprintf(stderr, "%s: corrupt manifest line: %s\n", dir, path);
347 + return 1;
348 + }
349 + *p++ = '\0';
350 +
351 + size = p;
352 + p = strchr(size, ' ');
353 + if (p == NULL) {
354 + fprintf(stderr, "%s: corrupt manifest line, need size for %s\n",
355 + dir, path);
356 + return 1;
357 + }
358 + *p++ = '\0';
359 + fsize = strtoll(size, NULL, 10);
360 + if (fsize == 0 && errno == EINVAL) {
361 + fprintf(stderr, "%s: corrupt manifest line, size is not a number: %s\n",
362 + dir, size);
363 + return 1;
364 + }
365 +
366 + sha256[0] = sha512[0] = whrlpl[0] = blak2b[0] = '\0';
367 + snprintf(buf, sizeof(buf), "%s/%s", dir, path);
368 + get_hashes(buf, sha256, sha512, whrlpl, blak2b, &flen);
369 +
370 + if (flen == 0) {
371 + fprintf(stderr, "cannot locate %s\n", path);
372 + return 1;
373 + }
374 +
375 + if (flen != fsize) {
376 + fprintf(stderr, "%s: size mismatch, got: %zd, expected: %lld\n",
377 + path, flen, fsize);
378 + return 1;
379 + }
380 +
381 + /* now we are in free territory, we read TYPE HASH pairs until we
382 + * drained the string, and match them against what we computed */
383 + while (p != NULL && *p != '\0') {
384 + hashtype = p;
385 + p = strchr(hashtype, ' ');
386 + if (p == NULL) {
387 + fprintf(stderr, "%s: corrupt manifest line, missing hash type\n",
388 + path);
389 + return 1;
390 + }
391 + *p++ = '\0';
392 +
393 + hash = p;
394 + p = strchr(hash, ' ');
395 + if (p != NULL)
396 + *p++ = '\0';
397 +
398 + if (strcmp(hashtype, "SHA256") == 0) {
399 + if (!(hashes & HASH_SHA256)) {
400 + printf("- warning: hash SHA256 ignored as "
401 + "it is not enabled for this repository\n");
402 + } else if (strcmp(hash, sha256) != 0) {
403 + printf("- SHA256 hash mismatch\n"
404 + " computed: '%s'\n"
405 + " recorded in manifest: '%s'\n",
406 + sha256, hash);
407 + ret = 1;
408 + }
409 + sha256[0] = '\0';
410 + } else if (strcmp(hashtype, "SHA512") == 0) {
411 + if (!(hashes & HASH_SHA512)) {
412 + printf("- warning: hash SHA512 ignored as "
413 + "it is not enabled for this repository\n");
414 + } else if (strcmp(hash, sha512) != 0) {
415 + printf("- SHA512 hash mismatch\n"
416 + " computed: '%s'\n"
417 + " recorded in manifest: '%s'\n",
418 + sha512, hash);
419 + ret = 1;
420 + }
421 + sha512[0] = '\0';
422 + } else if (strcmp(hashtype, "WHIRLPOOL") == 0) {
423 + if (!(hashes & HASH_WHIRLPOOL)) {
424 + printf("- warning: hash WHIRLPOOL ignored as "
425 + "it is not enabled for this repository\n");
426 + } else if (strcmp(hash, whrlpl) != 0) {
427 + printf("- WHIRLPOOL hash mismatch\n"
428 + " computed: '%s'\n"
429 + " recorded in manifest: '%s'\n",
430 + whrlpl, hash);
431 + ret = 1;
432 + }
433 + whrlpl[0] = '\0';
434 + } else if (strcmp(hashtype, "BLAKE2B") == 0) {
435 + if (!(hashes & HASH_BLAKE2B)) {
436 + printf("- warning: hash BLAKE2B ignored as "
437 + "it is not enabled for this repository\n");
438 + } else if (strcmp(hash, blak2b) != 0) {
439 + printf("- BLAKE2B hash mismatch\n"
440 + " computed: '%s'\n"
441 + " recorded in manifest: '%s'\n",
442 + blak2b, hash);
443 + ret = 1;
444 + }
445 + blak2b[0] = '\0';
446 + } else {
447 + printf("- unsupported hash: %s\n", hashtype);
448 + ret = 1;
449 + }
450 + }
451 +
452 + if (sha256[0] != '\0') {
453 + printf("- missing hash: SHA256\n");
454 + ret = 1;
455 + }
456 + if (sha512[0] != '\0') {
457 + printf("- missing hash: SHA512\n");
458 + ret = 1;
459 + }
460 + if (whrlpl[0] != '\0') {
461 + printf("- missing hash: WHIRLPOOL\n");
462 + ret = 1;
463 + }
464 + if (blak2b[0] != '\0') {
465 + printf("- missing hash: BLAKE2B\n");
466 + ret = 1;
467 + }
468 +
469 + return ret;
470 +}
471 +
472 +static int
473 +compare_elems(const void *l, const void *r)
474 +{
475 + const char *strl = *((const char **)l) + 2;
476 + const char *strr = *((const char **)r) + 2;
477 + unsigned char cl;
478 + unsigned char cr;
479 + /* compare treating / as end of string */
480 + while ((cl = *strl++) == (cr = *strr++))
481 + if (cl == '\0')
482 + return 0;
483 + if (cl == '/')
484 + cl = '\0';
485 + if (cr == '/')
486 + cr = '\0';
487 + return cl - cr;
488 +}
489 +
490 +static int
491 +compare_strings(const void *l, const void *r)
492 +{
493 + const char **strl = (const char **)l;
494 + const char **strr = (const char **)r;
495 + return strcmp(*strl, *strr);
496 +}
497 +
498 +static char verify_manifest(const char *dir, const char *manifest);
499 +
500 +#define LISTSZ 64
501 +static char
502 +verify_dir(const char *dir, char **elems, size_t elemslen, size_t skippath)
503 +{
504 + DIR *d;
505 + struct dirent *e;
506 + char **dentries = NULL;
507 + size_t dentrieslen = 0;
508 + size_t dentriessize = 0;
509 + size_t curelem = 0;
510 + size_t curdentry = 0;
511 + char *entry;
512 + char *slash;
513 + char etpe;
514 + char ret = 0;
515 + int cmp;
516 +
517 + /* shortcut a single Manifest entry pointing to the same dir
518 + * (happens at top-level) */
519 + if (elemslen == 1 && skippath == 0 &&
520 + **elems == 'M' && strchr(*elems + 2, '/') == NULL)
521 + {
522 + if ((ret = verify_file(dir, *elems + 2)) == 0) {
523 + slash = strchr(*elems + 2, ' ');
524 + if (slash != NULL)
525 + *slash = '\0';
526 + /* else, verify_manifest will fail, so ret will be handled */
527 + ret = verify_manifest(dir, *elems + 2);
528 + }
529 + return ret;
530 + }
531 +
532 + /*
533 + * We have a list of entries from the manifest just read, now we
534 + * need to match these onto the directory layout. From what we got
535 + * - we can ignore TIMESTAMP and DIST entries
536 + * - IGNOREs need to be handled separate (shortcut)
537 + * - MANIFESTs need to be handled on their own, for memory
538 + * consumption reasons, we defer them to until we've verified
539 + * what's left, we treat the path the Manifest refers to as IGNORE
540 + * - DATAs, EBUILDs and MISCs needs verifying
541 + * - AUXs need verifying, but in files/ subdir
542 + * If we sort both lists, we should be able to do a merge-join, to
543 + * easily flag missing entries in either list without hashing or
544 + * anything.
545 + */
546 + if ((d = opendir(dir)) != NULL) {
547 + while ((e = readdir(d)) != NULL) {
548 + /* skip all dotfiles and Manifest files */
549 + if (e->d_name[0] == '.' ||
550 + strcmp(e->d_name, str_manifest) == 0 ||
551 + strcmp(e->d_name, str_manifest_gz) == 0 ||
552 + strcmp(e->d_name, str_manifest_files_gz) == 0)
553 + {
554 + continue;
555 + }
556 +
557 + if (dentrieslen == dentriessize) {
558 + dentriessize += LISTSZ;
559 + dentries = realloc(dentries,
560 + dentriessize * sizeof(dentries[0]));
561 + if (dentries == NULL) {
562 + fprintf(stderr, "out of memory\n");
563 + return 1;
564 + }
565 + }
566 + dentries[dentrieslen] = strdup(e->d_name);
567 + if (dentries[dentrieslen] == NULL) {
568 + fprintf(stderr, "out of memory\n");
569 + return 1;
570 + }
571 + dentrieslen++;
572 + }
573 + closedir(d);
574 +
575 + qsort(dentries, dentrieslen, sizeof(dentries[0]), compare_strings);
576 +
577 + while (curdentry < dentrieslen) {
578 + if (curelem < elemslen) {
579 + entry = elems[curelem] + 2 + skippath;
580 + etpe = *elems[curelem];
581 + } else {
582 + entry = "";
583 + etpe = 'I';
584 + }
585 +
586 + /* handle subdirs first */
587 + if ((slash = strchr(entry, '/')) != NULL) {
588 + size_t sublen = slash - entry;
589 + char ndir[8192];
590 +
591 + if (etpe == 'M') {
592 + size_t skiplen = strlen(dir) + 1 + sublen;
593 + /* sub-Manifest, we need to do a proper recurse */
594 + slash = strrchr(entry, '/'); /* cannot be NULL */
595 + snprintf(ndir, sizeof(ndir),
596 + "%s/%s", dir, entry);
597 + ndir[skiplen] = '\0';
598 + slash = strchr(ndir + skiplen + 1, ' ');
599 + if (slash != NULL) /* path should fit in ndir ... */
600 + *slash = '\0';
601 + if (verify_file(dir, entry) != 0 ||
602 + verify_manifest(ndir, ndir + skiplen + 1) != 0)
603 + ret |= 1;
604 + } else {
605 + int elemstart = curelem;
606 + char **subelems = &elems[curelem];
607 + /* collect all entries like this one (same subdir) into
608 + * a sub-list that we can verify */
609 + curelem++;
610 + while (curelem < elemslen &&
611 + strncmp(entry, elems[curelem] + 2 + skippath,
612 + sublen + 1) == 0)
613 + curelem++;
614 + snprintf(ndir, sizeof(ndir), "%s/%.*s", dir,
615 + (int)sublen, elems[elemstart] + 2 + skippath);
616 + ret |= verify_dir(ndir, subelems,
617 + curelem - elemstart, skippath + sublen + 1);
618 + curelem--; /* move back, see below */
619 + }
620 +
621 + /* modify the last entry to be the subdir, such that we
622 + * can let the code below synchronise with dentries */
623 + elems[curelem][2 + skippath + sublen] = '\0';
624 + entry = elems[curelem] + 2 + skippath;
625 + etpe = 'S'; /* flag this was a subdir */
626 + }
627 +
628 + /* does this entry exist in list? */
629 + if (*entry == '\0') {
630 + /* end of list reached, force dir to catch up */
631 + cmp = 1;
632 + } else {
633 + slash = strchr(entry, ' ');
634 + if (slash != NULL)
635 + *slash = '\0';
636 + cmp = strcmp(entry, dentries[curdentry]);
637 + if (slash != NULL)
638 + *slash = ' ';
639 + }
640 + if (cmp == 0) {
641 + /* equal, so yay */
642 + if (etpe == 'D') {
643 + ret |= verify_file(dir, entry);
644 + }
645 + /* else this is I(GNORE) or S(ubdir), which means it is
646 + * ok in any way (M shouldn't happen) */
647 + curelem++;
648 + curdentry++;
649 + } else if (cmp < 0) {
650 + /* entry is missing from dir */
651 + if (etpe == 'I') {
652 + /* right, we can ignore this */
653 + } else {
654 + ret |= 1;
655 + slash = strchr(entry, ' ');
656 + if (slash != NULL)
657 + *slash = '\0';
658 + fprintf(stderr, "%s: missing %s file: %s\n",
659 + dir, etpe == 'M' ? "MANIFEST" : "DATA", entry);
660 + }
661 + curelem++;
662 + } else if (cmp > 0) {
663 + /* dir has extra element */
664 + ret |= 1;
665 + fprintf(stderr, "%s: stray file not in Manifest: %s\n",
666 + dir, dentries[curdentry]);
667 + curdentry++;
668 + }
669 + }
670 + free(dentries);
671 + return ret;
672 + } else {
673 + return 1;
674 + }
675 +}
676 +
677 +static char
678 +verify_manifest(const char *dir, const char *manifest)
679 +{
680 + char buf[8192];
681 + FILE *f;
682 + gzFile mf;
683 +
684 + size_t elemssize = 0;
685 + size_t elemslen = 0;
686 + char **elems = NULL;
687 +#define append_list(STR) \
688 + if (strncmp(STR, "TIMESTAMP ", 10) != 0 || strncmp(STR, "DIST ", 5) != 0) {\
689 + char *endp = STR + strlen(STR) - 1;\
690 + while (isspace(*endp))\
691 + *endp-- = '\0';\
692 + if (elemslen == elemssize) {\
693 + elemssize += LISTSZ;\
694 + elems = realloc(elems, elemssize * sizeof(elems[0]));\
695 + if (elems == NULL) {\
696 + fprintf(stderr, "out of memory\n");\
697 + return 1;\
698 + }\
699 + }\
700 + if (strncmp(STR, "IGNORE ", 7) == 0) {\
701 + STR[5] = 'I';\
702 + elems[elemslen] = strdup(STR + 5);\
703 + if (elems[elemslen] == NULL) {\
704 + fprintf(stderr, "out of memory\n");\
705 + return 1;\
706 + }\
707 + elemslen++;\
708 + } else if (strncmp(STR, "MANIFEST ", 9) == 0) {\
709 + STR[7] = 'M';\
710 + elems[elemslen] = strdup(STR + 7);\
711 + if (elems[elemslen] == NULL) {\
712 + fprintf(stderr, "out of memory\n");\
713 + return 1;\
714 + }\
715 + elemslen++;\
716 + } else if (strncmp(STR, "DATA ", 5) == 0 ||\
717 + strncmp(STR, "MISC ", 5) == 0 ||\
718 + strncmp(STR, "EBUILD ", 7) == 0)\
719 + {\
720 + if (*STR == 'E') {\
721 + STR[5] = 'D';\
722 + elems[elemslen] = strdup(STR + 5);\
723 + } else {\
724 + STR[3] = 'D';\
725 + elems[elemslen] = strdup(STR + 3);\
726 + }\
727 + if (elems[elemslen] == NULL) {\
728 + fprintf(stderr, "out of memory\n");\
729 + return 1;\
730 + }\
731 + elemslen++;\
732 + } else if (strncmp(STR, "AUX ", 4) == 0) {\
733 + /* translate directly into what it is: DATA in files/ */\
734 + size_t slen = strlen(STR + 2) + sizeof("files/");\
735 + elems[elemslen] = malloc(slen);\
736 + if (elems[elemslen] == NULL) {\
737 + fprintf(stderr, "out of memory\n");\
738 + return 1;\
739 + }\
740 + snprintf(elems[elemslen], slen, "D files/%s", STR + 4);\
741 + elemslen++;\
742 + }\
743 + }
744 +
745 + snprintf(buf, sizeof(buf), "%s/%s", dir, manifest);
746 + if (strcmp(manifest, str_manifest) == 0) {
747 + if ((f = fopen(buf, "r")) == NULL) {
748 + fprintf(stderr, "failed to open %s: %s\n",
749 + buf, strerror(errno));
750 + return 1;
751 + }
752 + while (fgets(buf, sizeof(buf), f) != NULL) {
753 + append_list(buf);
754 + }
755 + fclose(f);
756 + } else if (strcmp(manifest, str_manifest_files_gz) == 0 ||
757 + strcmp(manifest, str_manifest_gz) == 0)
758 + {
759 + if ((mf = gzopen(buf, "rb9")) == NULL) {
760 + fprintf(stderr, "failed to open file '%s' for reading: %s\n",
761 + buf, strerror(errno));
762 + return 1;
763 + }
764 + while (gzgets(mf, buf, sizeof(buf)) != NULL) {
765 + append_list(buf);
766 + }
767 + gzclose(mf);
768 + }
769 +
770 + /* The idea:
771 + * - Manifest without MANIFEST entries, we need to scan the entire
772 + * subtree
773 + * - Manifest with MANIFEST entries, assume they are just one level
774 + * deeper, thus ignore that subdir, further like above
775 + * - Manifest at top-level, needs to be igored as it only points to
776 + * the larger Manifest.files.gz
777 + */
778 + qsort(elems, elemslen, sizeof(elems[0]), compare_elems);
779 + verify_dir(dir, elems, elemslen, 0);
780 + free(elems);
781 +
782 + return 0;
783 +}
784 +
785 +static char *
786 +process_dir_vrfy(const char *dir)
787 +{
788 + char *ret = NULL;
789 + char buf[8192];
790 + int newhashes;
791 +
792 + snprintf(buf, sizeof(buf), "%s/metadata/layout.conf", dir);
793 + if ((newhashes = parse_layout_conf(buf)) != 0) {
794 + hashes = newhashes;
795 + } else {
796 + fprintf(stderr, "verification must be done on a full tree\n");
797 + return "not on full tree";
798 + }
799 +
800 + snprintf(buf, sizeof(buf), "%s/%s", dir, str_manifest);
801 + if (verify_gpg_sig(buf) != 0)
802 + ret = "gpg signature invalid";
803 +
804 + /* verification goes like this:
805 + * - verify the signature of the top-level Manifest file (done
806 + * above)
807 + * - read the contents of the Manifest file, and process the
808 + * entries - verify them, check there are no files which shouldn't
809 + * be there
810 + * - recurse into directories for which Manifest files are defined */
811 +
812 + if (verify_manifest(dir, str_manifest) != 0)
813 + ret = "manifest verification failed";
814 +
815 + return ret;
816 +}
817 +
818 int
819 main(int argc, char *argv[])
820 {
821 + char *prog;
822 + char *(*runfunc)(const char *);
823 + int arg = 1;
824 +
825 + if ((prog = strrchr(argv[0], '/')) == NULL)
826 + prog = argv[0];
827 +
828 + if (argc > 1) {
829 + if (strcmp(argv[1], "hashverify") == 0 ||
830 + strcmp(argv[1], "hashgen") == 0)
831 + {
832 + prog = argv[1];
833 + arg = 2;
834 + }
835 + }
836 +
837 + if (strcmp(prog, "hashverify") == 0) {
838 + runfunc = &process_dir_vrfy;
839 + } else {
840 + /* default mode: hashgen */
841 + runfunc = &process_dir_gen;
842 + }
843 +
844 + gpgme_check_version(NULL);
845 +
846 if (argc > 1) {
847 - int i;
848 - for (i = 1; i < argc; i++)
849 - process_dir(argv[i]);
850 + for (; arg < argc; arg++)
851 + runfunc(argv[arg]);
852 } else {
853 - process_dir(".");
854 + runfunc(".");
855 }
856
857 return 0;