xref: /openbsd-src/usr.sbin/rpki-client/repo.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: repo.c,v 1.7 2021/05/04 08:16:36 claudio Exp $ */
2 /*
3  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
4  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/queue.h>
20 #include <sys/tree.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 
24 #include <assert.h>
25 #include <err.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <fts.h>
29 #include <limits.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include <imsg.h>
36 
37 #include "extern.h"
38 
39 extern struct stats	stats;
40 extern int		noop;
41 
42 enum repo_state {
43 	REPO_LOADING = 0,
44 	REPO_DONE = 1,
45 	REPO_FAILED = -1,
46 };
47 
48 /*
49  * A ta, rsync or rrdp repository.
50  * Depending on what is needed the generic repository is backed by
51  * a ta, rsync or rrdp repository. Multiple repositories can use the
52  * same backend.
53  */
54 struct rrdprepo {
55 	SLIST_ENTRY(rrdprepo)	 entry;
56 	char			*notifyuri;
57 	char			*basedir;
58 	char			*temp;
59 	struct filepath_tree	 added;
60 	struct filepath_tree	 deleted;
61 	size_t			 id;
62 	enum repo_state		 state;
63 };
64 SLIST_HEAD(, rrdprepo)	rrdprepos = SLIST_HEAD_INITIALIZER(rrdprepos);
65 
66 struct rsyncrepo {
67 	SLIST_ENTRY(rsyncrepo)	 entry;
68 	char			*repouri;
69 	char			*basedir;
70 	size_t			 id;
71 	enum repo_state		 state;
72 };
73 SLIST_HEAD(, rsyncrepo)	rsyncrepos = SLIST_HEAD_INITIALIZER(rsyncrepos);
74 
75 struct tarepo {
76 	SLIST_ENTRY(tarepo)	 entry;
77 	char			*descr;
78 	char			*basedir;
79 	char			*temp;
80 	char			**uri;
81 	size_t			 urisz;
82 	size_t			 uriidx;
83 	size_t			 id;
84 	enum repo_state		 state;
85 };
86 SLIST_HEAD(, tarepo)	tarepos = SLIST_HEAD_INITIALIZER(tarepos);
87 
88 struct	repo {
89 	SLIST_ENTRY(repo)	 entry;
90 	char			*repouri;	/* CA repository base URI */
91 	const struct rrdprepo	*rrdp;
92 	const struct rsyncrepo	*rsync;
93 	const struct tarepo	*ta;
94 	struct entityq		 queue;		/* files waiting for repo */
95 	size_t			 id;		/* identifier */
96 };
97 SLIST_HEAD(, repo)	repos = SLIST_HEAD_INITIALIZER(repos);
98 
99 /* counter for unique repo id */
100 size_t			repoid;
101 
102 /*
103  * Database of all file path accessed during a run.
104  */
105 struct filepath {
106 	RB_ENTRY(filepath)	entry;
107 	char			*file;
108 };
109 
110 static inline int
111 filepathcmp(struct filepath *a, struct filepath *b)
112 {
113 	return strcmp(a->file, b->file);
114 }
115 
116 RB_PROTOTYPE(filepath_tree, filepath, entry, filepathcmp);
117 
118 /*
119  * Functions to lookup which files have been accessed during computation.
120  */
121 int
122 filepath_add(struct filepath_tree *tree, char *file)
123 {
124 	struct filepath *fp;
125 
126 	if ((fp = malloc(sizeof(*fp))) == NULL)
127 		err(1, NULL);
128 	if ((fp->file = strdup(file)) == NULL)
129 		err(1, NULL);
130 
131 	if (RB_INSERT(filepath_tree, tree, fp) != NULL) {
132 		/* already in the tree */
133 		free(fp->file);
134 		free(fp);
135 		return 0;
136 	}
137 
138 	return 1;
139 }
140 
141 /*
142  * Lookup a file path in the tree and return the object if found or NULL.
143  */
144 static struct filepath *
145 filepath_find(struct filepath_tree *tree, char *file)
146 {
147 	struct filepath needle;
148 
149 	needle.file = file;
150 	return RB_FIND(filepath_tree, tree, &needle);
151 }
152 
153 /*
154  * Returns true if file exists in the tree.
155  */
156 static int
157 filepath_exists(struct filepath_tree *tree, char *file)
158 {
159 	return filepath_find(tree, file) != NULL;
160 }
161 
162 /*
163  * Return true if a filepath entry exists that starts with path.
164  */
165 static int
166 filepath_dir_exists(struct filepath_tree *tree, char *path)
167 {
168 	struct filepath needle;
169 	struct filepath *res;
170 
171 	needle.file = path;
172 	res = RB_NFIND(filepath_tree, tree, &needle);
173 	while (res != NULL && strstr(res->file, path) == res->file) {
174 		/* make sure that filepath actually is in that path */
175 		if (res->file[strlen(path)] == '/')
176 			return 1;
177 		res = RB_NEXT(filepath_tree, tree, res);
178 	}
179 	return 0;
180 }
181 
182 /*
183  * Remove entry from tree and free it.
184  */
185 static void
186 filepath_put(struct filepath_tree *tree, struct filepath *fp)
187 {
188 	RB_REMOVE(filepath_tree, tree, fp);
189 	free((void *)fp->file);
190 	free(fp);
191 }
192 
193 /*
194  * Free all elements of a filepath tree.
195  */
196 static void
197 filepath_free(struct filepath_tree *tree)
198 {
199 	struct filepath *fp, *nfp;
200 
201 	RB_FOREACH_SAFE(fp, filepath_tree, tree, nfp)
202 		filepath_put(tree, fp);
203 }
204 
205 RB_GENERATE(filepath_tree, filepath, entry, filepathcmp);
206 
207 /*
208  * Function to hash a string into a unique directory name.
209  * prefixed with dir.
210  */
211 static char *
212 hash_dir(const char *uri, const char *dir)
213 {
214 	const char hex[] = "0123456789abcdef";
215 	unsigned char m[SHA256_DIGEST_LENGTH];
216 	char hash[SHA256_DIGEST_LENGTH * 2 + 1];
217 	char *out;
218 	size_t i;
219 
220 	SHA256(uri, strlen(uri), m);
221 	for (i = 0; i < SHA256_DIGEST_LENGTH; i++) {
222 		hash[i * 2] = hex[m[i] >> 4];
223 		hash[i * 2 + 1] = hex[m[i] & 0xf];
224 	}
225 	hash[SHA256_DIGEST_LENGTH * 2] = '\0';
226 
227 	asprintf(&out, "%s/%s", dir, hash);
228 	return out;
229 }
230 
231 /*
232  * Function to build the directory name based on URI and a directory
233  * as prefix. Skip the proto:// in URI but keep everything else.
234  */
235 static char *
236 rsync_dir(const char *uri, const char *dir)
237 {
238 	char *local, *out;
239 
240 	local = strchr(uri, ':') + strlen("://");
241 
242 	asprintf(&out, "%s/%s", dir, local);
243 	return out;
244 }
245 
246 /*
247  * Function to create all missing directories to a path.
248  * This functions alters the path temporarily.
249  */
250 static void
251 repo_mkpath(char *file)
252 {
253 	char *slash;
254 
255 	/* build directory hierarchy */
256 	slash = strrchr(file, '/');
257 	assert(slash != NULL);
258 	*slash = '\0';
259 	if (mkpath(file) == -1)
260 		err(1, "%s", file);
261 	*slash = '/';
262 }
263 
264 /*
265  * Build TA file name based on the repo info.
266  * If temp is set add Xs for mkostemp.
267  */
268 static char *
269 ta_filename(const struct tarepo *tr, int temp)
270 {
271 	const char *file;
272 	char *nfile;
273 
274 	/* does not matter which URI, all end with same filename */
275 	file = strrchr(tr->uri[0], '/');
276 	assert(file);
277 
278 	if (asprintf(&nfile, "%s%s%s", tr->basedir, file,
279 	    temp ? ".XXXXXXXX": "") == -1)
280 		err(1, NULL);
281 
282 	return nfile;
283 }
284 
285 /*
286  * Build local file name base on the URI and the rrdprepo info.
287  */
288 static char *
289 rrdp_filename(const struct rrdprepo *rr, const char *uri, int temp)
290 {
291 	char *nfile;
292 	char *dir = rr->basedir;
293 
294 	if (temp)
295 		dir = rr->temp;
296 
297 	if (!valid_uri(uri, strlen(uri), "rsync://")) {
298 		warnx("%s: bad URI %s", rr->basedir, uri);
299 		return NULL;
300 	}
301 
302 	uri += strlen("rsync://");	/* skip proto */
303 	if (asprintf(&nfile, "%s/%s", dir, uri) == -1)
304 		err(1, NULL);
305 	return nfile;
306 }
307 
308 /*
309  * Build RRDP state file name based on the repo info.
310  * If temp is set add Xs for mkostemp.
311  */
312 static char *
313 rrdp_state_filename(const struct rrdprepo *rr, int temp)
314 {
315 	char *nfile;
316 
317 	if (asprintf(&nfile, "%s/.state%s", rr->basedir,
318 	    temp ? ".XXXXXXXX": "") == -1)
319 		err(1, NULL);
320 
321 	return nfile;
322 }
323 
324 
325 
326 static void
327 ta_fetch(struct tarepo *tr)
328 {
329 	logx("ta/%s: pulling from %s", tr->descr, tr->uri[tr->uriidx]);
330 
331 	if (strncasecmp(tr->uri[tr->uriidx], "rsync://", 8) == 0) {
332 		/*
333 		 * Create destination location.
334 		 * Build up the tree to this point.
335 		 */
336 		rsync_fetch(tr->id, tr->uri[tr->uriidx], tr->basedir);
337 	} else {
338 		int fd;
339 
340 		tr->temp = ta_filename(tr, 1);
341 		fd = mkostemp(tr->temp, O_CLOEXEC);
342 		if (fd == -1) {
343 			err(1, "mkostemp: %s", tr->temp);
344 			/* XXX switch to soft fail and restart with next file */
345 		}
346 		if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1)
347 			warn("fchmod: %s", tr->temp);
348 
349 		http_fetch(tr->id, tr->uri[tr->uriidx], NULL, fd);
350 	}
351 }
352 
353 static struct tarepo *
354 ta_get(struct tal *tal)
355 {
356 	struct tarepo *tr;
357 
358 	/* no need to look for possible other repo */
359 
360 	if (tal->urisz == 0)
361 		errx(1, "TAL %s has no URI", tal->descr);
362 
363 	if ((tr = calloc(1, sizeof(*tr))) == NULL)
364 		err(1, NULL);
365 	tr->id = ++repoid;
366 	SLIST_INSERT_HEAD(&tarepos, tr, entry);
367 
368 	if ((tr->descr = strdup(tal->descr)) == NULL)
369 		err(1, NULL);
370 	if (asprintf(&tr->basedir, "ta/%s", tal->descr) == -1)
371 		err(1, NULL);
372 
373 	/* steal URI infromation from TAL */
374 	tr->urisz = tal->urisz;
375 	tr->uri = tal->uri;
376 	tal->urisz = 0;
377 	tal->uri = NULL;
378 
379 	/* create base directory */
380 	if (mkpath(tr->basedir) == -1)
381 		err(1, "%s", tr->basedir);
382 
383 	if (noop) {
384 		tr->state = REPO_DONE;
385 		logx("ta/%s: using cache", tr->descr);
386 		/* there is nothing in the queue so no need to flush */
387 	} else
388 		ta_fetch(tr);
389 
390 	return tr;
391 }
392 
393 static struct tarepo *
394 ta_find(size_t id)
395 {
396 	struct tarepo *tr;
397 
398 	SLIST_FOREACH(tr, &tarepos, entry)
399 		if (id == tr->id)
400 			break;
401 	return tr;
402 }
403 
404 static void
405 ta_free(void)
406 {
407 	struct tarepo *tr;
408 
409 	while ((tr = SLIST_FIRST(&tarepos)) != NULL) {
410 		SLIST_REMOVE_HEAD(&tarepos, entry);
411 		free(tr->descr);
412 		free(tr->basedir);
413 		free(tr->temp);
414 		free(tr->uri);
415 		free(tr);
416 	}
417 }
418 
419 static struct rsyncrepo *
420 rsync_get(const char *uri)
421 {
422 	struct rsyncrepo *rr;
423 	char *repo;
424 
425 	if ((repo = rsync_base_uri(uri)) == NULL)
426 		errx(1, "bad caRepository URI: %s", uri);
427 
428 	SLIST_FOREACH(rr, &rsyncrepos, entry)
429 		if (strcmp(rr->repouri, repo) == 0) {
430 			free(repo);
431 			return rr;
432 		}
433 
434 	if ((rr = calloc(1, sizeof(*rr))) == NULL)
435 		err(1, NULL);
436 
437 	rr->id = ++repoid;
438 	SLIST_INSERT_HEAD(&rsyncrepos, rr, entry);
439 
440 	rr->repouri = repo;
441 	rr->basedir = rsync_dir(repo, "rsync");
442 
443 	/* create base directory */
444 	if (mkpath(rr->basedir) == -1)
445 		err(1, "%s", rr->basedir);
446 
447 	if (noop) {
448 		rr->state = REPO_DONE;
449 		logx("%s: using cache", rr->basedir);
450 		/* there is nothing in the queue so no need to flush */
451 	} else {
452 		logx("%s: pulling from %s", rr->basedir, rr->repouri);
453 		rsync_fetch(rr->id, rr->repouri, rr->basedir);
454 	}
455 
456 	return rr;
457 }
458 
459 static struct rsyncrepo *
460 rsync_find(size_t id)
461 {
462 	struct rsyncrepo *rr;
463 
464 	SLIST_FOREACH(rr, &rsyncrepos, entry)
465 		if (id == rr->id)
466 			break;
467 	return rr;
468 }
469 
470 static void
471 rsync_free(void)
472 {
473 	struct rsyncrepo *rr;
474 
475 	while ((rr = SLIST_FIRST(&rsyncrepos)) != NULL) {
476 		SLIST_REMOVE_HEAD(&rsyncrepos, entry);
477 		free(rr->repouri);
478 		free(rr->basedir);
479 		free(rr);
480 	}
481 }
482 
483 static void rrdprepo_fetch(struct rrdprepo *);
484 
485 static struct rrdprepo *
486 rrdp_get(const char *uri)
487 {
488 	struct rrdprepo *rr;
489 
490 	SLIST_FOREACH(rr, &rrdprepos, entry)
491 		if (strcmp(rr->notifyuri, uri) == 0) {
492 			if (rr->state == REPO_FAILED)
493 				return NULL;
494 			return rr;
495 		}
496 
497 	if ((rr = calloc(1, sizeof(*rr))) == NULL)
498 		err(1, NULL);
499 
500 	rr->id = ++repoid;
501 	SLIST_INSERT_HEAD(&rrdprepos, rr, entry);
502 
503 	if ((rr->notifyuri = strdup(uri)) == NULL)
504 		err(1, NULL);
505 	rr->basedir = hash_dir(uri, "rrdp");
506 
507 	RB_INIT(&rr->added);
508 	RB_INIT(&rr->deleted);
509 
510 	/* create base directory */
511 	if (mkpath(rr->basedir) == -1)
512 		err(1, "%s", rr->basedir);
513 
514 	if (noop) {
515 		rr->state = REPO_DONE;
516 		logx("%s: using cache", rr->notifyuri);
517 		/* there is nothing in the queue so no need to flush */
518 	} else {
519 		logx("%s: pulling from %s", rr->notifyuri, "network");
520 		rrdprepo_fetch(rr);
521 	}
522 
523 	return rr;
524 }
525 
526 static struct rrdprepo *
527 rrdp_find(size_t id)
528 {
529 	struct rrdprepo *rr;
530 
531 	SLIST_FOREACH(rr, &rrdprepos, entry)
532 		if (id == rr->id)
533 			break;
534 	return rr;
535 }
536 
537 static void
538 rrdp_free(void)
539 {
540 	struct rrdprepo *rr;
541 
542 	while ((rr = SLIST_FIRST(&rrdprepos)) != NULL) {
543 		SLIST_REMOVE_HEAD(&rrdprepos, entry);
544 
545 		free(rr->notifyuri);
546 		free(rr->basedir);
547 		free(rr->temp);
548 
549 		filepath_free(&rr->added);
550 		filepath_free(&rr->deleted);
551 
552 		free(rr);
553 	}
554 }
555 
556 static struct rrdprepo *
557 rrdp_basedir(const char *dir)
558 {
559 	struct rrdprepo *rr;
560 
561 	SLIST_FOREACH(rr, &rrdprepos, entry)
562 		if (strcmp(dir, rr->basedir) == 0) {
563 			if (rr->state == REPO_FAILED)
564 				return NULL;
565 			return rr;
566 		}
567 
568 	return NULL;
569 }
570 
571 /*
572  * Allocate and insert a new repository.
573  */
574 static struct repo *
575 repo_alloc(void)
576 {
577 	struct repo *rp;
578 
579 	if ((rp = calloc(1, sizeof(*rp))) == NULL)
580 		err(1, NULL);
581 
582 	rp->id = ++repoid;
583 	TAILQ_INIT(&rp->queue);
584 	SLIST_INSERT_HEAD(&repos, rp, entry);
585 
586 	stats.repos++;
587 	return rp;
588 }
589 
590 /*
591  * Return the state of a repository.
592  */
593 static enum repo_state
594 repo_state(struct repo *rp)
595 {
596 	if (rp->ta)
597 		return rp->ta->state;
598 	if (rp->rrdp)
599 		return rp->rrdp->state;
600 	if (rp->rsync)
601 		return rp->rsync->state;
602 	errx(1, "%s: bad repo", rp->repouri);
603 }
604 
605 #if 0
606 /*
607  * locate a repository by ID.
608  */
609 static struct repo *
610 repo_find(size_t id)
611 {
612 	struct repo *rp;
613 
614 	SLIST_FOREACH(rp, &repos, entry)
615 		if (id == rp->id)
616 			break;
617 	return rp;
618 }
619 #endif
620 
621 
622 /*
623  * Parse the RRDP state file if it exists and set the session struct
624  * based on that information.
625  */
626 static void
627 rrdp_parse_state(const struct rrdprepo *rr, struct rrdp_session *state)
628 {
629 	FILE *f;
630 	int fd, ln = 0;
631 	const char *errstr;
632 	char *line = NULL, *file;
633 	size_t len = 0;
634 	ssize_t n;
635 
636 	file = rrdp_state_filename(rr, 0);
637 	if ((fd = open(file, O_RDONLY)) == -1) {
638 		if (errno != ENOENT)
639 			warn("%s: open state file", rr->basedir);
640 		free(file);
641 		return;
642 	}
643 	free(file);
644 	f = fdopen(fd, "r");
645 	if (f == NULL)
646 		err(1, "fdopen");
647 
648 	while ((n = getline(&line, &len, f)) != -1) {
649 		if (line[n - 1] == '\n')
650 			line[n - 1] = '\0';
651 		switch (ln) {
652 		case 0:
653 			if ((state->session_id = strdup(line)) == NULL)
654 				err(1, NULL);
655 			break;
656 		case 1:
657 			state->serial = strtonum(line, 1, LLONG_MAX, &errstr);
658 			if (errstr)
659 				goto fail;
660 			break;
661 		case 2:
662 			if ((state->last_mod = strdup(line)) == NULL)
663 				err(1, NULL);
664 			break;
665 		default:
666 			goto fail;
667 		}
668 		ln++;
669 	}
670 
671 	free(line);
672 	if (ferror(f))
673 		goto fail;
674 	fclose(f);
675 	return;
676 
677 fail:
678 	warnx("%s: troubles reading state file", rr->basedir);
679 	fclose(f);
680 	free(state->session_id);
681 	free(state->last_mod);
682 	memset(state, 0, sizeof(*state));
683 }
684 
685 /*
686  * Carefully write the RRDP session state file back.
687  */
688 void
689 rrdp_save_state(size_t id, struct rrdp_session *state)
690 {
691 	struct rrdprepo *rr;
692 	char *temp, *file;
693 	FILE *f;
694 	int fd;
695 
696 	rr = rrdp_find(id);
697 	if (rr == NULL)
698 		errx(1, "non-existant rrdp repo %zu", id);
699 
700 	file = rrdp_state_filename(rr, 0);
701 	temp = rrdp_state_filename(rr, 1);
702 
703 	if ((fd = mkostemp(temp, O_CLOEXEC)) == -1)
704 		err(1, "%s: mkostemp: %s", rr->basedir, temp);
705 	(void) fchmod(fd, 0644);
706 	f = fdopen(fd, "w");
707 	if (f == NULL)
708 		err(1, "fdopen");
709 
710 	/* write session state file out */
711 	if (fprintf(f, "%s\n%lld\n", state->session_id,
712 	    state->serial) < 0) {
713 		fclose(f);
714 		goto fail;
715 	}
716 	if (state->last_mod != NULL) {
717 		if (fprintf(f, "%s\n", state->last_mod) < 0) {
718 			fclose(f);
719 			goto fail;
720 		}
721 	}
722 	if (fclose(f) != 0)
723 		goto fail;
724 
725 	if (rename(temp, file) == -1)
726 		warn("%s: rename state file", rr->basedir);
727 
728 	free(temp);
729 	free(file);
730 	return;
731 
732 fail:
733 	warnx("%s: failed to save state", rr->basedir);
734 	unlink(temp);
735 	free(temp);
736 	free(file);
737 }
738 
739 int
740 rrdp_handle_file(size_t id, enum publish_type pt, char *uri,
741     char *hash, size_t hlen, char *data, size_t dlen)
742 {
743 	struct rrdprepo *rr;
744 	struct filepath *fp;
745 	ssize_t s;
746 	char *fn;
747 	int fd;
748 
749 	rr = rrdp_find(id);
750 	if (rr == NULL)
751 		errx(1, "non-existant rrdp repo %zu", id);
752 
753 	/* belt and suspenders */
754 	if (!valid_uri(uri, strlen(uri), "rsync://")) {
755 		warnx("%s: bad file URI", rr->basedir);
756 		return 0;
757 	}
758 
759 	if (pt == PUB_UPD || pt == PUB_DEL) {
760 		if (filepath_exists(&rr->deleted, uri)) {
761 			warnx("%s: already deleted", uri);
762 			return 0;
763 		}
764 		fp = filepath_find(&rr->added, uri);
765 		if (fp == NULL) {
766 			if ((fn = rrdp_filename(rr, uri, 0)) == NULL)
767 				return 0;
768 		} else {
769 			filepath_put(&rr->added, fp);
770 			if ((fn = rrdp_filename(rr, uri, 1)) == NULL)
771 				return 0;
772 		}
773 		if (!valid_filehash(fn, hash, hlen)) {
774 			warnx("%s: bad message digest", fn);
775 			free(fn);
776 			return 0;
777 		}
778 		free(fn);
779 	}
780 
781 	if (pt == PUB_DEL) {
782 		filepath_add(&rr->deleted, uri);
783 	} else {
784 		fp = filepath_find(&rr->deleted, uri);
785 		if (fp != NULL)
786 			filepath_put(&rr->deleted, fp);
787 
788 		/* add new file to temp dir */
789 		if ((fn = rrdp_filename(rr, uri, 1)) == NULL)
790 			return 0;
791 
792 		repo_mkpath(fn);
793 		fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, 0644);
794 		if (fd == -1) {
795 			warn("open %s", fn);
796 			free(fn);
797 			return 0;
798 		}
799 
800 		if ((s = write(fd, data, dlen)) == -1) {
801 			warn("write %s", fn);
802 			free(fn);
803 			close(fd);
804 			return 0;
805 		}
806 		close(fd);
807 		if ((size_t)s != dlen) {
808 			warnx("short write %s", fn);
809 			free(fn);
810 			return 0;
811 		}
812 		free(fn);
813 		filepath_add(&rr->added, uri);
814 	}
815 
816 	return 1;
817 }
818 
819 /*
820  * Initiate a RRDP sync, create the required temporary directory and
821  * parse a possible state file before sending the request to the RRDP process.
822  */
823 static void
824 rrdprepo_fetch(struct rrdprepo *rr)
825 {
826 	struct rrdp_session state = { 0 };
827 
828 	if (asprintf(&rr->temp, "%s.XXXXXXXX", rr->basedir) == -1)
829 		err(1, NULL);
830 	if (mkdtemp(rr->temp) == NULL)
831 		err(1, "mkdtemp %s", rr->temp);
832 
833 	rrdp_parse_state(rr, &state);
834 	rrdp_fetch(rr->id, rr->notifyuri, rr->notifyuri, &state);
835 
836 	free(state.session_id);
837 	free(state.last_mod);
838 }
839 
840 static void
841 rrdp_merge_repo(struct rrdprepo *rr)
842 {
843 	struct filepath *fp, *nfp;
844 	char *fn, *rfn;
845 
846 	RB_FOREACH_SAFE(fp, filepath_tree, &rr->added, nfp) {
847 		fn = rrdp_filename(rr, fp->file, 1);
848 		rfn = rrdp_filename(rr, fp->file, 0);
849 
850 		if (fn == NULL || rfn == NULL)
851 			errx(1, "bad filepath");	/* should not happen */
852 
853 		repo_mkpath(rfn);
854 		if (rename(fn, rfn) == -1)
855 			warn("%s: rename", rfn);
856 
857 		free(rfn);
858 		free(fn);
859 		filepath_put(&rr->added, fp);
860 	}
861 }
862 
863 static void
864 rrdp_clean_temp(struct rrdprepo *rr)
865 {
866 	struct filepath *fp, *nfp;
867 	char *fn;
868 
869 	filepath_free(&rr->deleted);
870 
871 	RB_FOREACH_SAFE(fp, filepath_tree, &rr->added, nfp) {
872 		if ((fn = rrdp_filename(rr, fp->file, 1)) != NULL) {
873 			if (unlink(fn) == -1)
874 				warn("%s: unlink", fn);
875 			free(fn);
876 		}
877 		filepath_put(&rr->added, fp);
878 	}
879 }
880 
881 /*
882  * RSYNC sync finished, either with or without success.
883  */
884 void
885 rsync_finish(size_t id, int ok)
886 {
887 	struct rsyncrepo *rr;
888 	struct tarepo *tr;
889 	struct repo *rp;
890 
891 	tr = ta_find(id);
892 	if (tr != NULL) {
893 		if (ok) {
894 			logx("ta/%s: loaded from network", tr->descr);
895 			stats.rsync_repos++;
896 			tr->state = REPO_DONE;
897 		} else if (++tr->uriidx < tr->urisz) {
898 			logx("ta/%s: load from network failed, retry",
899 			    tr->descr);
900 			ta_fetch(tr);
901 			return;
902 		} else {
903 			logx("ta/%s: load from network failed, "
904 			    "fallback to cache", tr->descr);
905 			stats.rsync_fails++;
906 			tr->state = REPO_FAILED;
907 		}
908 		SLIST_FOREACH(rp, &repos, entry)
909 			if (rp->ta == tr)
910 				entityq_flush(&rp->queue, rp);
911 
912 		return;
913 	}
914 
915 	rr = rsync_find(id);
916 	if (rr == NULL)
917 		errx(1, "unknown rsync repo %zu", id);
918 
919 	if (ok) {
920 		logx("%s: loaded from network", rr->basedir);
921 		stats.rsync_repos++;
922 		rr->state = REPO_DONE;
923 	} else {
924 		logx("%s: load from network failed, fallback to cache",
925 		    rr->basedir);
926 		stats.rsync_fails++;
927 		rr->state = REPO_FAILED;
928 	}
929 
930 	SLIST_FOREACH(rp, &repos, entry)
931 		if (rp->rsync == rr)
932 			entityq_flush(&rp->queue, rp);
933 }
934 
935 /*
936  * RRDP sync finshed, either with or without success.
937  */
938 void
939 rrdp_finish(size_t id, int ok)
940 {
941 	struct rrdprepo *rr;
942 	struct repo *rp;
943 
944 	rr = rrdp_find(id);
945 	if (rr == NULL)
946 		errx(1, "unknown RRDP repo %zu", id);
947 
948 	if (ok) {
949 		rrdp_merge_repo(rr);
950 		logx("%s: loaded from network", rr->notifyuri);
951 		rr->state = REPO_DONE;
952 		stats.rrdp_repos++;
953 		SLIST_FOREACH(rp, &repos, entry)
954 			if (rp->rrdp == rr)
955 				entityq_flush(&rp->queue, rp);
956 	} else {
957 		rrdp_clean_temp(rr);
958 		stats.rrdp_fails++;
959 		rr->state = REPO_FAILED;
960 		logx("%s: load from network failed, fallback to rsync",
961 		    rr->notifyuri);
962 		SLIST_FOREACH(rp, &repos, entry)
963 			if (rp->rrdp == rr) {
964 				rp->rrdp = NULL;
965 				rp->rsync = rsync_get(rp->repouri);
966 				/* need to check if it was already loaded */
967 				if (repo_state(rp) != REPO_LOADING)
968 					entityq_flush(&rp->queue, rp);
969 			}
970 	}
971 }
972 
973 /*
974  * Handle responses from the http process. For TA file, either rename
975  * or delete the temporary file. For RRDP requests relay the request
976  * over to the rrdp process.
977  */
978 void
979 http_finish(size_t id, enum http_result res, const char *last_mod)
980 {
981 	struct tarepo *tr;
982 	struct repo *rp;
983 
984 	tr = ta_find(id);
985 	if (tr == NULL) {
986 		/* not a TA fetch therefor RRDP */
987 		rrdp_http_done(id, res, last_mod);
988 		return;
989 	}
990 
991 	/* Move downloaded TA file into place, or unlink on failure. */
992 	if (res == HTTP_OK) {
993 		char *file;
994 
995 		file = ta_filename(tr, 0);
996 		if (rename(tr->temp, file) == -1)
997 			warn("rename to %s", file);
998 		free(file);
999 
1000 		logx("ta/%s: loaded from network", tr->descr);
1001 		tr->state = REPO_DONE;
1002 		stats.http_repos++;
1003 	} else {
1004 		if (unlink(tr->temp) == -1)
1005 			warn("unlink %s", tr->temp);
1006 
1007 		if (++tr->uriidx < tr->urisz) {
1008 			logx("ta/%s: load from network failed, retry",
1009 			    tr->descr);
1010 			ta_fetch(tr);
1011 			return;
1012 		}
1013 
1014 		tr->state = REPO_FAILED;
1015 		logx("ta/%s: load from network failed, "
1016 		    "fallback to cache", tr->descr);
1017 	}
1018 
1019 	SLIST_FOREACH(rp, &repos, entry)
1020 		if (rp->ta == tr)
1021 			entityq_flush(&rp->queue, rp);
1022 }
1023 
1024 
1025 
1026 /*
1027  * Look up a trust anchor, queueing it for download if not found.
1028  */
1029 struct repo *
1030 ta_lookup(struct tal *tal)
1031 {
1032 	struct repo	*rp;
1033 
1034 	/* Look up in repository table. (Lookup should actually fail here) */
1035 	SLIST_FOREACH(rp, &repos, entry) {
1036 		if (strcmp(rp->repouri, tal->descr) == 0)
1037 			return rp;
1038 	}
1039 
1040 	rp = repo_alloc();
1041 	if ((rp->repouri = strdup(tal->descr)) == NULL)
1042 		err(1, NULL);
1043 	rp->ta = ta_get(tal);
1044 
1045 	return rp;
1046 }
1047 
1048 /*
1049  * Look up a repository, queueing it for discovery if not found.
1050  */
1051 struct repo *
1052 repo_lookup(const char *uri, const char *notify)
1053 {
1054 	struct repo *rp;
1055 
1056 	/* Look up in repository table. */
1057 	SLIST_FOREACH(rp, &repos, entry) {
1058 		if (strcmp(rp->repouri, uri) != 0)
1059 			continue;
1060 		return rp;
1061 	}
1062 
1063 	rp = repo_alloc();
1064 	if ((rp->repouri = strdup(uri)) == NULL)
1065 		err(1, NULL);
1066 
1067 	/* try RRDP first if available */
1068 	if (notify != NULL)
1069 		rp->rrdp = rrdp_get(notify);
1070 	if (rp->rrdp == NULL)
1071 		rp->rsync = rsync_get(uri);
1072 
1073 	return rp;
1074 }
1075 
1076 /*
1077  * Build local file name base on the URI and the repo info.
1078  */
1079 char *
1080 repo_filename(const struct repo *rp, const char *uri)
1081 {
1082 	char *nfile;
1083 	char *dir, *repouri;
1084 
1085 	if (uri == NULL && rp->ta)
1086 		return ta_filename(rp->ta, 0);
1087 
1088 	assert(uri != NULL);
1089 	if (rp->rrdp)
1090 		return rrdp_filename(rp->rrdp, uri, 0);
1091 
1092 	/* must be rsync */
1093 	dir = rp->rsync->basedir;
1094 	repouri = rp->rsync->repouri;
1095 
1096 	if (strstr(uri, repouri) != uri) {
1097 		warnx("%s: URI %s outside of repository", repouri, uri);
1098 		return NULL;
1099 	}
1100 
1101 	uri += strlen(repouri) + 1;	/* skip base and '/' */
1102 
1103 	if (asprintf(&nfile, "%s/%s", dir, uri) == -1)
1104 		err(1, NULL);
1105 	return nfile;
1106 }
1107 
1108 int
1109 repo_queued(struct repo *rp, struct entity *p)
1110 {
1111 	if (repo_state(rp) == REPO_LOADING) {
1112 		TAILQ_INSERT_TAIL(&rp->queue, p, entries);
1113 		return 1;
1114 	}
1115 	return 0;
1116 }
1117 
1118 static char **
1119 add_to_del(char **del, size_t *dsz, char *file)
1120 {
1121 	size_t i = *dsz;
1122 
1123 	del = reallocarray(del, i + 1, sizeof(*del));
1124 	if (del == NULL)
1125 		err(1, NULL);
1126 	if ((del[i] = strdup(file)) == NULL)
1127 		err(1, NULL);
1128 	*dsz = i + 1;
1129 	return del;
1130 }
1131 
1132 static char **
1133 repo_rrdp_cleanup(struct filepath_tree *tree, struct rrdprepo *rr,
1134     char **del, size_t *delsz)
1135 {
1136 	struct filepath *fp, *nfp;
1137 	char *fn;
1138 
1139 	RB_FOREACH_SAFE(fp, filepath_tree, &rr->deleted, nfp) {
1140 		fn = rrdp_filename(rr, fp->file, 0);
1141 		/* temp dir will be cleaned up by repo_cleanup() */
1142 
1143 		if (fn == NULL)
1144 			errx(1, "bad filepath");	/* should not happen */
1145 
1146 		if (!filepath_exists(tree, fn))
1147 			del = add_to_del(del, delsz, fn);
1148 		else
1149 			warnx("%s: referenced file supposed to be deleted", fn);
1150 
1151 		free(fn);
1152 		filepath_put(&rr->deleted, fp);
1153 	}
1154 
1155 	return del;
1156 }
1157 
1158 void
1159 repo_cleanup(struct filepath_tree *tree)
1160 {
1161 	size_t i, cnt, delsz = 0, dirsz = 0;
1162 	char **del = NULL, **dir = NULL;
1163 	char *argv[4] = { "ta", "rsync", "rrdp", NULL };
1164 	struct rrdprepo *rr;
1165 	FTS *fts;
1166 	FTSENT *e;
1167 
1168 	if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, NULL)) == NULL)
1169 		err(1, "fts_open");
1170 	errno = 0;
1171 	while ((e = fts_read(fts)) != NULL) {
1172 		switch (e->fts_info) {
1173 		case FTS_NSOK:
1174 			if (!filepath_exists(tree, e->fts_path))
1175 				del = add_to_del(del, &delsz,
1176 				    e->fts_path);
1177 			break;
1178 		case FTS_D:
1179 			/* special cleanup for rrdp directories */
1180 			if ((rr = rrdp_basedir(e->fts_path)) != NULL) {
1181 				del = repo_rrdp_cleanup(tree, rr, del, &delsz);
1182 				if (fts_set(fts, e, FTS_SKIP) == -1)
1183 					err(1, "fts_set");
1184 			}
1185 			break;
1186 		case FTS_DP:
1187 			if (!filepath_dir_exists(tree, e->fts_path))
1188 				dir = add_to_del(dir, &dirsz,
1189 				    e->fts_path);
1190 			break;
1191 		case FTS_SL:
1192 		case FTS_SLNONE:
1193 			warnx("symlink %s", e->fts_path);
1194 			del = add_to_del(del, &delsz, e->fts_path);
1195 			break;
1196 		case FTS_NS:
1197 		case FTS_ERR:
1198 			if (e->fts_errno == ENOENT &&
1199 			    (strcmp(e->fts_path, "rsync") == 0 ||
1200 			    strcmp(e->fts_path, "rrdp") == 0))
1201 				continue;
1202 			warnx("fts_read %s: %s", e->fts_path,
1203 			    strerror(e->fts_errno));
1204 			break;
1205 		default:
1206 			warnx("unhandled[%x] %s", e->fts_info,
1207 			    e->fts_path);
1208 			break;
1209 		}
1210 
1211 		errno = 0;
1212 	}
1213 	if (errno)
1214 		err(1, "fts_read");
1215 	if (fts_close(fts) == -1)
1216 		err(1, "fts_close");
1217 
1218 	cnt = 0;
1219 	for (i = 0; i < delsz; i++) {
1220 		if (unlink(del[i]) == -1) {
1221 			if (errno != ENOENT)
1222 				warn("unlink %s", del[i]);
1223 		} else {
1224 			if (verbose > 1)
1225 				logx("deleted %s", del[i]);
1226 			cnt++;
1227 		}
1228 		free(del[i]);
1229 	}
1230 	free(del);
1231 	stats.del_files = cnt;
1232 
1233 	cnt = 0;
1234 	for (i = 0; i < dirsz; i++) {
1235 		if (rmdir(dir[i]) == -1)
1236 			warn("rmdir %s", dir[i]);
1237 		else
1238 			cnt++;
1239 		free(dir[i]);
1240 	}
1241 	free(dir);
1242 	stats.del_dirs = cnt;
1243 }
1244 
1245 void
1246 repo_free(void)
1247 {
1248 	struct repo *rp;
1249 
1250 	while ((rp = SLIST_FIRST(&repos)) != NULL) {
1251 		SLIST_REMOVE_HEAD(&repos, entry);
1252 		free(rp->repouri);
1253 		free(rp);
1254 	}
1255 
1256 	ta_free();
1257 	rrdp_free();
1258 	rsync_free();
1259 }
1260