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; |