xref: /openbsd-src/usr.sbin/rpki-client/repo.c (revision 3374c67d44f9b75b98444cbf63020f777792342e)
1 /*	$OpenBSD: repo.c,v 1.41 2022/12/28 21:30:18 jmc 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 <poll.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include <imsg.h>
37 
38 #include "extern.h"
39 
40 extern struct stats	stats;
41 extern int		noop;
42 extern int		rrdpon;
43 extern int		repo_timeout;
44 extern time_t		deadline;
45 int			nofetch;
46 
47 enum repo_state {
48 	REPO_LOADING = 0,
49 	REPO_DONE = 1,
50 	REPO_FAILED = -1,
51 };
52 
53 /*
54  * A ta, rsync or rrdp repository.
55  * Depending on what is needed the generic repository is backed by
56  * a ta, rsync or rrdp repository. Multiple repositories can use the
57  * same backend.
58  */
59 struct rrdprepo {
60 	SLIST_ENTRY(rrdprepo)	 entry;
61 	char			*notifyuri;
62 	char			*basedir;
63 	struct filepath_tree	 deleted;
64 	unsigned int		 id;
65 	enum repo_state		 state;
66 };
67 static SLIST_HEAD(, rrdprepo)	rrdprepos = SLIST_HEAD_INITIALIZER(rrdprepos);
68 
69 struct rsyncrepo {
70 	SLIST_ENTRY(rsyncrepo)	 entry;
71 	char			*repouri;
72 	char			*basedir;
73 	unsigned int		 id;
74 	enum repo_state		 state;
75 };
76 static SLIST_HEAD(, rsyncrepo)	rsyncrepos = SLIST_HEAD_INITIALIZER(rsyncrepos);
77 
78 struct tarepo {
79 	SLIST_ENTRY(tarepo)	 entry;
80 	char			*descr;
81 	char			*basedir;
82 	char			*temp;
83 	char			**uri;
84 	size_t			 urisz;
85 	size_t			 uriidx;
86 	unsigned int		 id;
87 	enum repo_state		 state;
88 };
89 static SLIST_HEAD(, tarepo)	tarepos = SLIST_HEAD_INITIALIZER(tarepos);
90 
91 struct repo {
92 	SLIST_ENTRY(repo)	 entry;
93 	char			*repouri;
94 	char			*notifyuri;
95 	char			*basedir;
96 	const struct rrdprepo	*rrdp;
97 	const struct rsyncrepo	*rsync;
98 	const struct tarepo	*ta;
99 	struct entityq		 queue;		/* files waiting for repo */
100 	struct repostats	 stats;
101 	struct timespec		 start_time;
102 	time_t			 alarm;		/* sync timeout */
103 	int			 talid;
104 	unsigned int		 id;		/* identifier */
105 };
106 static SLIST_HEAD(, repo)	repos = SLIST_HEAD_INITIALIZER(repos);
107 
108 /* counter for unique repo id */
109 unsigned int		repoid;
110 
111 static struct rsyncrepo	*rsync_get(const char *, const char *);
112 static void		 remove_contents(char *);
113 
114 /*
115  * Database of all file path accessed during a run.
116  */
117 struct filepath {
118 	RB_ENTRY(filepath)	entry;
119 	char			*file;
120 };
121 
122 static inline int
123 filepathcmp(struct filepath *a, struct filepath *b)
124 {
125 	return strcmp(a->file, b->file);
126 }
127 
128 RB_PROTOTYPE(filepath_tree, filepath, entry, filepathcmp);
129 
130 /*
131  * Functions to lookup which files have been accessed during computation.
132  */
133 int
134 filepath_add(struct filepath_tree *tree, char *file)
135 {
136 	struct filepath *fp;
137 
138 	if ((fp = malloc(sizeof(*fp))) == NULL)
139 		err(1, NULL);
140 	if ((fp->file = strdup(file)) == NULL)
141 		err(1, NULL);
142 
143 	if (RB_INSERT(filepath_tree, tree, fp) != NULL) {
144 		/* already in the tree */
145 		free(fp->file);
146 		free(fp);
147 		return 0;
148 	}
149 
150 	return 1;
151 }
152 
153 /*
154  * Lookup a file path in the tree and return the object if found or NULL.
155  */
156 static struct filepath *
157 filepath_find(struct filepath_tree *tree, char *file)
158 {
159 	struct filepath needle = { .file = file };
160 
161 	return RB_FIND(filepath_tree, tree, &needle);
162 }
163 
164 /*
165  * Returns true if file exists in the tree.
166  */
167 static int
168 filepath_exists(struct filepath_tree *tree, char *file)
169 {
170 	return filepath_find(tree, file) != NULL;
171 }
172 
173 /*
174  * Remove entry from tree and free it.
175  */
176 static void
177 filepath_put(struct filepath_tree *tree, struct filepath *fp)
178 {
179 	RB_REMOVE(filepath_tree, tree, fp);
180 	free((void *)fp->file);
181 	free(fp);
182 }
183 
184 /*
185  * Free all elements of a filepath tree.
186  */
187 static void
188 filepath_free(struct filepath_tree *tree)
189 {
190 	struct filepath *fp, *nfp;
191 
192 	RB_FOREACH_SAFE(fp, filepath_tree, tree, nfp)
193 		filepath_put(tree, fp);
194 }
195 
196 RB_GENERATE(filepath_tree, filepath, entry, filepathcmp);
197 
198 /*
199  * Function to hash a string into a unique directory name.
200  * Returned hash needs to be freed.
201  */
202 static char *
203 hash_dir(const char *uri)
204 {
205 	unsigned char m[SHA256_DIGEST_LENGTH];
206 
207 	SHA256(uri, strlen(uri), m);
208 	return hex_encode(m, sizeof(m));
209 }
210 
211 /*
212  * Function to build the directory name based on URI and a directory
213  * as prefix. Skip the proto:// in URI but keep everything else.
214  */
215 static char *
216 repo_dir(const char *uri, const char *dir, int hash)
217 {
218 	const char *local;
219 	char *out, *hdir = NULL;
220 
221 	if (hash) {
222 		local = hdir = hash_dir(uri);
223 	} else {
224 		local = strchr(uri, ':');
225 		if (local != NULL)
226 			local += strlen("://");
227 		else
228 			local = uri;
229 	}
230 
231 	if (dir == NULL) {
232 		if ((out = strdup(local)) == NULL)
233 			err(1, NULL);
234 	} else {
235 		if (asprintf(&out, "%s/%s", dir, local) == -1)
236 			err(1, NULL);
237 	}
238 
239 	free(hdir);
240 	return out;
241 }
242 
243 /*
244  * Function to create all missing directories to a path.
245  * This functions alters the path temporarily.
246  */
247 static int
248 repo_mkpath(int fd, char *file)
249 {
250 	char *slash;
251 
252 	/* build directory hierarchy */
253 	slash = strrchr(file, '/');
254 	assert(slash != NULL);
255 	*slash = '\0';
256 	if (mkpathat(fd, file) == -1) {
257 		warn("mkpath %s", file);
258 		return -1;
259 	}
260 	*slash = '/';
261 	return 0;
262 }
263 
264 /*
265  * Return the state of a repository.
266  */
267 static enum repo_state
268 repo_state(const struct repo *rp)
269 {
270 	if (rp->ta)
271 		return rp->ta->state;
272 	if (rp->rsync)
273 		return rp->rsync->state;
274 	if (rp->rrdp)
275 		return rp->rrdp->state;
276 	/* No backend so sync is by definition done. */
277 	return REPO_DONE;
278 }
279 
280 /*
281  * Function called once a repository is done with the sync. Either
282  * successfully or after failure.
283  */
284 static void
285 repo_done(const void *vp, int ok)
286 {
287 	struct repo *rp;
288 	struct timespec flush_time;
289 
290 	SLIST_FOREACH(rp, &repos, entry) {
291 		if (vp != rp->ta && vp != rp->rsync && vp != rp->rrdp)
292 			continue;
293 
294 		/* for rrdp try to fall back to rsync */
295 		if (vp == rp->rrdp && !ok && !nofetch) {
296 			rp->rrdp = NULL;
297 			rp->rsync = rsync_get(rp->repouri, rp->basedir);
298 			/* need to check if it was already loaded */
299 			if (repo_state(rp) == REPO_LOADING)
300 				continue;
301 		}
302 
303 		entityq_flush(&rp->queue, rp);
304 		clock_gettime(CLOCK_MONOTONIC, &flush_time);
305 		timespecsub(&flush_time, &rp->start_time, &rp->stats.sync_time);
306 	}
307 }
308 
309 /*
310  * Build TA file name based on the repo info.
311  * If temp is set add Xs for mkostemp.
312  */
313 static char *
314 ta_filename(const struct tarepo *tr, int temp)
315 {
316 	const char *file;
317 	char *nfile;
318 
319 	/* does not matter which URI, all end with same filename */
320 	file = strrchr(tr->uri[0], '/');
321 	assert(file);
322 
323 	if (asprintf(&nfile, "%s%s%s", tr->basedir, file,
324 	    temp ? ".XXXXXXXX" : "") == -1)
325 		err(1, NULL);
326 
327 	return nfile;
328 }
329 
330 static void
331 ta_fetch(struct tarepo *tr)
332 {
333 	if (!rrdpon) {
334 		for (; tr->uriidx < tr->urisz; tr->uriidx++) {
335 			if (strncasecmp(tr->uri[tr->uriidx],
336 			    "rsync://", 8) == 0)
337 				break;
338 		}
339 	}
340 
341 	if (tr->uriidx >= tr->urisz) {
342 		tr->state = REPO_FAILED;
343 		logx("ta/%s: fallback to cache", tr->descr);
344 
345 		repo_done(tr, 0);
346 		return;
347 	}
348 
349 	logx("ta/%s: pulling from %s", tr->descr, tr->uri[tr->uriidx]);
350 
351 	if (strncasecmp(tr->uri[tr->uriidx], "rsync://", 8) == 0) {
352 		/*
353 		 * Create destination location.
354 		 * Build up the tree to this point.
355 		 */
356 		rsync_fetch(tr->id, tr->uri[tr->uriidx], tr->basedir, NULL);
357 	} else {
358 		int fd;
359 
360 		tr->temp = ta_filename(tr, 1);
361 		fd = mkostemp(tr->temp, O_CLOEXEC);
362 		if (fd == -1) {
363 			warn("mkostemp: %s", tr->temp);
364 			http_finish(tr->id, HTTP_FAILED, NULL);
365 			return;
366 		}
367 		if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1)
368 			warn("fchmod: %s", tr->temp);
369 
370 		http_fetch(tr->id, tr->uri[tr->uriidx], NULL, fd);
371 	}
372 }
373 
374 static struct tarepo *
375 ta_get(struct tal *tal)
376 {
377 	struct tarepo *tr;
378 
379 	/* no need to look for possible other repo */
380 
381 	if ((tr = calloc(1, sizeof(*tr))) == NULL)
382 		err(1, NULL);
383 	tr->id = ++repoid;
384 	SLIST_INSERT_HEAD(&tarepos, tr, entry);
385 
386 	if ((tr->descr = strdup(tal->descr)) == NULL)
387 		err(1, NULL);
388 	tr->basedir = repo_dir(tal->descr, "ta", 0);
389 
390 	/* steal URI information from TAL */
391 	tr->urisz = tal->urisz;
392 	tr->uri = tal->uri;
393 	tal->urisz = 0;
394 	tal->uri = NULL;
395 
396 	ta_fetch(tr);
397 
398 	return tr;
399 }
400 
401 static struct tarepo *
402 ta_find(unsigned int id)
403 {
404 	struct tarepo *tr;
405 
406 	SLIST_FOREACH(tr, &tarepos, entry)
407 		if (id == tr->id)
408 			break;
409 	return tr;
410 }
411 
412 static void
413 ta_free(void)
414 {
415 	struct tarepo *tr;
416 
417 	while ((tr = SLIST_FIRST(&tarepos)) != NULL) {
418 		SLIST_REMOVE_HEAD(&tarepos, entry);
419 		free(tr->descr);
420 		free(tr->basedir);
421 		free(tr->temp);
422 		free(tr->uri);
423 		free(tr);
424 	}
425 }
426 
427 static struct rsyncrepo *
428 rsync_get(const char *uri, const char *validdir)
429 {
430 	struct rsyncrepo *rr;
431 	char *repo;
432 
433 	if ((repo = rsync_base_uri(uri)) == NULL)
434 		errx(1, "bad caRepository URI: %s", uri);
435 
436 	SLIST_FOREACH(rr, &rsyncrepos, entry)
437 		if (strcmp(rr->repouri, repo) == 0) {
438 			free(repo);
439 			return rr;
440 		}
441 
442 	if ((rr = calloc(1, sizeof(*rr))) == NULL)
443 		err(1, NULL);
444 
445 	rr->id = ++repoid;
446 	SLIST_INSERT_HEAD(&rsyncrepos, rr, entry);
447 
448 	rr->repouri = repo;
449 	rr->basedir = repo_dir(repo, ".rsync", 0);
450 
451 	/* create base directory */
452 	if (mkpath(rr->basedir) == -1) {
453 		warn("mkpath %s", rr->basedir);
454 		rsync_finish(rr->id, 0);
455 		return rr;
456 	}
457 
458 	logx("%s: pulling from %s", rr->basedir, rr->repouri);
459 	rsync_fetch(rr->id, rr->repouri, rr->basedir, validdir);
460 
461 	return rr;
462 }
463 
464 static struct rsyncrepo *
465 rsync_find(unsigned int id)
466 {
467 	struct rsyncrepo *rr;
468 
469 	SLIST_FOREACH(rr, &rsyncrepos, entry)
470 		if (id == rr->id)
471 			break;
472 	return rr;
473 }
474 
475 static void
476 rsync_free(void)
477 {
478 	struct rsyncrepo *rr;
479 
480 	while ((rr = SLIST_FIRST(&rsyncrepos)) != NULL) {
481 		SLIST_REMOVE_HEAD(&rsyncrepos, entry);
482 		free(rr->repouri);
483 		free(rr->basedir);
484 		free(rr);
485 	}
486 }
487 
488 /*
489  * Build local file name base on the URI and the rrdprepo info.
490  */
491 static char *
492 rrdp_filename(const struct rrdprepo *rr, const char *uri, int valid)
493 {
494 	char *nfile;
495 	const char *dir = rr->basedir;
496 
497 	if (!valid_uri(uri, strlen(uri), "rsync://"))
498 		errx(1, "%s: bad URI %s", rr->basedir, uri);
499 	uri += strlen("rsync://");	/* skip proto */
500 	if (valid) {
501 		if ((nfile = strdup(uri)) == NULL)
502 			err(1, NULL);
503 	} else {
504 		if (asprintf(&nfile, "%s/%s", dir, uri) == -1)
505 			err(1, NULL);
506 	}
507 	return nfile;
508 }
509 
510 /*
511  * Build RRDP state file name based on the repo info.
512  * If temp is set add Xs for mkostemp.
513  */
514 static char *
515 rrdp_state_filename(const struct rrdprepo *rr, int temp)
516 {
517 	char *nfile;
518 
519 	if (asprintf(&nfile, "%s/.state%s", rr->basedir,
520 	    temp ? ".XXXXXXXX" : "") == -1)
521 		err(1, NULL);
522 
523 	return nfile;
524 }
525 
526 static struct rrdprepo *
527 rrdp_find(unsigned int 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 
548 		filepath_free(&rr->deleted);
549 
550 		free(rr);
551 	}
552 }
553 
554 /*
555  * Check if a directory is an active rrdp repository.
556  * Returns 1 if found else 0.
557  */
558 static int
559 rrdp_is_active(const char *dir)
560 {
561 	struct rrdprepo *rr;
562 
563 	SLIST_FOREACH(rr, &rrdprepos, entry)
564 		if (strcmp(dir, rr->basedir) == 0)
565 			return rr->state != REPO_FAILED;
566 
567 	return 0;
568 }
569 
570 /*
571  * Check if the URI is actually covered by one of the repositories
572  * that depend on this RRDP repository.
573  * Returns 1 if the URI is valid, 0 if no repouri matches the URI.
574  */
575 static int
576 rrdp_uri_valid(struct rrdprepo *rr, const char *uri)
577 {
578 	struct repo *rp;
579 
580 	SLIST_FOREACH(rp, &repos, entry) {
581 		if (rp->rrdp != rr)
582 			continue;
583 		if (strncmp(uri, rp->repouri, strlen(rp->repouri)) == 0)
584 			return 1;
585 	}
586 	return 0;
587 }
588 
589 /*
590  * Allocate and insert a new repository.
591  */
592 static struct repo *
593 repo_alloc(int talid)
594 {
595 	struct repo *rp;
596 
597 	if ((rp = calloc(1, sizeof(*rp))) == NULL)
598 		err(1, NULL);
599 
600 	rp->id = ++repoid;
601 	rp->talid = talid;
602 	rp->alarm = getmonotime() + repo_timeout;
603 	TAILQ_INIT(&rp->queue);
604 	SLIST_INSERT_HEAD(&repos, rp, entry);
605 	clock_gettime(CLOCK_MONOTONIC, &rp->start_time);
606 
607 	stats.repos++;
608 	return rp;
609 }
610 
611 /*
612  * Parse the RRDP state file if it exists and set the session struct
613  * based on that information.
614  */
615 static void
616 rrdp_parse_state(const struct rrdprepo *rr, struct rrdp_session *state)
617 {
618 	FILE *f;
619 	int fd, ln = 0;
620 	const char *errstr;
621 	char *line = NULL, *file;
622 	size_t len = 0;
623 	ssize_t n;
624 
625 	file = rrdp_state_filename(rr, 0);
626 	if ((fd = open(file, O_RDONLY)) == -1) {
627 		if (errno != ENOENT)
628 			warn("%s: open state file", rr->basedir);
629 		free(file);
630 		return;
631 	}
632 	free(file);
633 	f = fdopen(fd, "r");
634 	if (f == NULL)
635 		err(1, "fdopen");
636 
637 	while ((n = getline(&line, &len, f)) != -1) {
638 		if (line[n - 1] == '\n')
639 			line[n - 1] = '\0';
640 		switch (ln) {
641 		case 0:
642 			if ((state->session_id = strdup(line)) == NULL)
643 				err(1, NULL);
644 			break;
645 		case 1:
646 			state->serial = strtonum(line, 1, LLONG_MAX, &errstr);
647 			if (errstr)
648 				goto fail;
649 			break;
650 		case 2:
651 			if ((state->last_mod = strdup(line)) == NULL)
652 				err(1, NULL);
653 			break;
654 		default:
655 			goto fail;
656 		}
657 		ln++;
658 	}
659 
660 	free(line);
661 	if (ferror(f))
662 		goto fail;
663 	fclose(f);
664 	return;
665 
666 fail:
667 	warnx("%s: troubles reading state file", rr->basedir);
668 	fclose(f);
669 	free(state->session_id);
670 	free(state->last_mod);
671 	memset(state, 0, sizeof(*state));
672 }
673 
674 /*
675  * Carefully write the RRDP session state file back.
676  */
677 void
678 rrdp_save_state(unsigned int id, struct rrdp_session *state)
679 {
680 	struct rrdprepo *rr;
681 	char *temp, *file;
682 	FILE *f;
683 	int fd;
684 
685 	rr = rrdp_find(id);
686 	if (rr == NULL)
687 		errx(1, "non-existent rrdp repo %u", id);
688 
689 	file = rrdp_state_filename(rr, 0);
690 	temp = rrdp_state_filename(rr, 1);
691 
692 	if ((fd = mkostemp(temp, O_CLOEXEC)) == -1) {
693 		warn("mkostemp %s", temp);
694 		goto fail;
695 	}
696 	(void)fchmod(fd, 0644);
697 	f = fdopen(fd, "w");
698 	if (f == NULL)
699 		err(1, "fdopen");
700 
701 	/* write session state file out */
702 	if (fprintf(f, "%s\n%lld\n", state->session_id,
703 	    state->serial) < 0) {
704 		fclose(f);
705 		goto fail;
706 	}
707 	if (state->last_mod != NULL) {
708 		if (fprintf(f, "%s\n", state->last_mod) < 0) {
709 			fclose(f);
710 			goto fail;
711 		}
712 	}
713 	if (fclose(f) != 0)
714 		goto fail;
715 
716 	if (rename(temp, file) == -1)
717 		warn("%s: rename state file", rr->basedir);
718 
719 	free(temp);
720 	free(file);
721 	return;
722 
723 fail:
724 	warnx("%s: failed to save state", rr->basedir);
725 	unlink(temp);
726 	free(temp);
727 	free(file);
728 }
729 
730 static struct rrdprepo *
731 rrdp_get(const char *uri)
732 {
733 	struct rrdp_session state = { 0 };
734 	struct rrdprepo *rr;
735 
736 	SLIST_FOREACH(rr, &rrdprepos, entry)
737 		if (strcmp(rr->notifyuri, uri) == 0) {
738 			if (rr->state == REPO_FAILED)
739 				return NULL;
740 			return rr;
741 		}
742 
743 	if ((rr = calloc(1, sizeof(*rr))) == NULL)
744 		err(1, NULL);
745 
746 	rr->id = ++repoid;
747 	SLIST_INSERT_HEAD(&rrdprepos, rr, entry);
748 
749 	if ((rr->notifyuri = strdup(uri)) == NULL)
750 		err(1, NULL);
751 	rr->basedir = repo_dir(uri, ".rrdp", 1);
752 
753 	RB_INIT(&rr->deleted);
754 
755 	/* create base directory */
756 	if (mkpath(rr->basedir) == -1) {
757 		warn("mkpath %s", rr->basedir);
758 		rrdp_finish(rr->id, 0);
759 		return rr;
760 	}
761 
762 	/* parse state and start the sync */
763 	rrdp_parse_state(rr, &state);
764 	rrdp_fetch(rr->id, rr->notifyuri, rr->notifyuri, &state);
765 	free(state.session_id);
766 	free(state.last_mod);
767 
768 	logx("%s: pulling from %s", rr->notifyuri, "network");
769 
770 	return rr;
771 }
772 
773 /*
774  * Remove RRDP repo and start over.
775  */
776 void
777 rrdp_clear(unsigned int id)
778 {
779 	struct rrdprepo *rr;
780 
781 	rr = rrdp_find(id);
782 	if (rr == NULL)
783 		errx(1, "non-existent rrdp repo %u", id);
784 
785 	/* remove rrdp repository contents */
786 	remove_contents(rr->basedir);
787 }
788 
789 /*
790  * Write a file into the temporary RRDP dir but only after checking
791  * its hash (if required). The function also makes sure that the file
792  * tracking is properly adjusted.
793  * Returns 1 on success, 0 if the repo is corrupt, -1 on IO error
794  */
795 int
796 rrdp_handle_file(unsigned int id, enum publish_type pt, char *uri,
797     char *hash, size_t hlen, char *data, size_t dlen)
798 {
799 	struct rrdprepo *rr;
800 	struct filepath *fp;
801 	ssize_t s;
802 	char *fn = NULL;
803 	int fd = -1, try = 0;
804 
805 	rr = rrdp_find(id);
806 	if (rr == NULL)
807 		errx(1, "non-existent rrdp repo %u", id);
808 	if (rr->state == REPO_FAILED)
809 		return -1;
810 
811 	/* check hash of original file for updates and deletes */
812 	if (pt == PUB_UPD || pt == PUB_DEL) {
813 		if (filepath_exists(&rr->deleted, uri)) {
814 			warnx("%s: already deleted", uri);
815 			return 0;
816 		}
817 		/* try to open file first in rrdp then in valid repo */
818 		do {
819 			free(fn);
820 			if ((fn = rrdp_filename(rr, uri, try++)) == NULL)
821 				return 0;
822 			fd = open(fn, O_RDONLY);
823 		} while (fd == -1 && try < 2);
824 
825 		if (!valid_filehash(fd, hash, hlen)) {
826 			warnx("%s: bad file digest for %s", rr->notifyuri, fn);
827 			free(fn);
828 			return 0;
829 		}
830 		free(fn);
831 	}
832 
833 	/* write new content or mark uri as deleted. */
834 	if (pt == PUB_DEL) {
835 		filepath_add(&rr->deleted, uri);
836 	} else {
837 		fp = filepath_find(&rr->deleted, uri);
838 		if (fp != NULL)
839 			filepath_put(&rr->deleted, fp);
840 
841 		/* add new file to rrdp dir */
842 		if ((fn = rrdp_filename(rr, uri, 0)) == NULL)
843 			return 0;
844 
845 		if (repo_mkpath(AT_FDCWD, fn) == -1)
846 			goto fail;
847 
848 		fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, 0644);
849 		if (fd == -1) {
850 			warn("open %s", fn);
851 			goto fail;
852 		}
853 
854 		if ((s = write(fd, data, dlen)) == -1) {
855 			warn("write %s", fn);
856 			goto fail;
857 		}
858 		close(fd);
859 		if ((size_t)s != dlen)	/* impossible */
860 			errx(1, "short write %s", fn);
861 		free(fn);
862 	}
863 
864 	return 1;
865 
866 fail:
867 	rr->state = REPO_FAILED;
868 	if (fd != -1)
869 		close(fd);
870 	free(fn);
871 	return -1;
872 }
873 
874 /*
875  * RSYNC sync finished, either with or without success.
876  */
877 void
878 rsync_finish(unsigned int id, int ok)
879 {
880 	struct rsyncrepo *rr;
881 	struct tarepo *tr;
882 
883 	tr = ta_find(id);
884 	if (tr != NULL) {
885 		/* repository changed state already, ignore request */
886 		if (tr->state != REPO_LOADING)
887 			return;
888 		if (ok) {
889 			logx("ta/%s: loaded from network", tr->descr);
890 			stats.rsync_repos++;
891 			tr->state = REPO_DONE;
892 			repo_done(tr, 1);
893 		} else {
894 			warnx("ta/%s: load from network failed", tr->descr);
895 			stats.rsync_fails++;
896 			tr->uriidx++;
897 			ta_fetch(tr);
898 		}
899 		return;
900 	}
901 
902 	rr = rsync_find(id);
903 	if (rr == NULL)
904 		errx(1, "unknown rsync repo %u", id);
905 	/* repository changed state already, ignore request */
906 	if (rr->state != REPO_LOADING)
907 		return;
908 
909 	if (ok) {
910 		logx("%s: loaded from network", rr->basedir);
911 		stats.rsync_repos++;
912 		rr->state = REPO_DONE;
913 	} else {
914 		warnx("%s: load from network failed, fallback to cache",
915 		    rr->basedir);
916 		stats.rsync_fails++;
917 		rr->state = REPO_FAILED;
918 		/* clear rsync repo since it failed */
919 		remove_contents(rr->basedir);
920 	}
921 
922 	repo_done(rr, ok);
923 }
924 
925 /*
926  * RRDP sync finished, either with or without success.
927  */
928 void
929 rrdp_finish(unsigned int id, int ok)
930 {
931 	struct rrdprepo *rr;
932 
933 	rr = rrdp_find(id);
934 	if (rr == NULL)
935 		errx(1, "unknown RRDP repo %u", id);
936 	/* repository changed state already, ignore request */
937 	if (rr->state != REPO_LOADING)
938 		return;
939 
940 	if (ok) {
941 		logx("%s: loaded from network", rr->notifyuri);
942 		stats.rrdp_repos++;
943 		rr->state = REPO_DONE;
944 	} else {
945 		warnx("%s: load from network failed, fallback to %s",
946 		    rr->notifyuri, nofetch ? "cache" : "rsync");
947 		stats.rrdp_fails++;
948 		rr->state = REPO_FAILED;
949 		/* clear the RRDP repo since it failed */
950 		remove_contents(rr->basedir);
951 		/* also clear the list of deleted files */
952 		filepath_free(&rr->deleted);
953 	}
954 
955 	repo_done(rr, ok);
956 }
957 
958 /*
959  * Handle responses from the http process. For TA file, either rename
960  * or delete the temporary file. For RRDP requests relay the request
961  * over to the rrdp process.
962  */
963 void
964 http_finish(unsigned int id, enum http_result res, const char *last_mod)
965 {
966 	struct tarepo *tr;
967 
968 	tr = ta_find(id);
969 	if (tr == NULL) {
970 		/* not a TA fetch therefore RRDP */
971 		rrdp_http_done(id, res, last_mod);
972 		return;
973 	}
974 
975 	/* repository changed state already, ignore request */
976 	if (tr->state != REPO_LOADING)
977 		return;
978 
979 	/* Move downloaded TA file into place, or unlink on failure. */
980 	if (res == HTTP_OK) {
981 		char *file;
982 
983 		file = ta_filename(tr, 0);
984 		if (rename(tr->temp, file) == -1)
985 			warn("rename to %s", file);
986 		free(file);
987 
988 		logx("ta/%s: loaded from network", tr->descr);
989 		tr->state = REPO_DONE;
990 		stats.http_repos++;
991 		repo_done(tr, 1);
992 	} else {
993 		if (unlink(tr->temp) == -1 && errno != ENOENT)
994 			warn("unlink %s", tr->temp);
995 
996 		tr->uriidx++;
997 		warnx("ta/%s: load from network failed", tr->descr);
998 		ta_fetch(tr);
999 	}
1000 }
1001 
1002 /*
1003  * Look up a trust anchor, queueing it for download if not found.
1004  */
1005 struct repo *
1006 ta_lookup(int id, struct tal *tal)
1007 {
1008 	struct repo	*rp;
1009 
1010 	if (tal->urisz == 0)
1011 		errx(1, "TAL %s has no URI", tal->descr);
1012 
1013 	/* Look up in repository table. (Lookup should actually fail here) */
1014 	SLIST_FOREACH(rp, &repos, entry) {
1015 		if (strcmp(rp->repouri, tal->descr) == 0)
1016 			return rp;
1017 	}
1018 
1019 	rp = repo_alloc(id);
1020 	rp->basedir = repo_dir(tal->descr, "ta", 0);
1021 	if ((rp->repouri = strdup(tal->descr)) == NULL)
1022 		err(1, NULL);
1023 
1024 	/* check if sync disabled ... */
1025 	if (noop) {
1026 		logx("ta/%s: using cache", rp->repouri);
1027 		entityq_flush(&rp->queue, rp);
1028 		return rp;
1029 	}
1030 
1031 	/* try to create base directory */
1032 	if (mkpath(rp->basedir) == -1)
1033 		warn("mkpath %s", rp->basedir);
1034 
1035 	rp->ta = ta_get(tal);
1036 
1037 	/* need to check if it was already loaded */
1038 	if (repo_state(rp) != REPO_LOADING)
1039 		entityq_flush(&rp->queue, rp);
1040 
1041 	return rp;
1042 }
1043 
1044 /*
1045  * Look up a repository, queueing it for discovery if not found.
1046  */
1047 struct repo *
1048 repo_lookup(int talid, const char *uri, const char *notify)
1049 {
1050 	struct repo	*rp;
1051 	char		*repouri;
1052 
1053 	if ((repouri = rsync_base_uri(uri)) == NULL)
1054 		errx(1, "bad caRepository URI: %s", uri);
1055 
1056 	/* Look up in repository table. */
1057 	SLIST_FOREACH(rp, &repos, entry) {
1058 		if (strcmp(rp->repouri, repouri) != 0)
1059 			continue;
1060 		if (rp->notifyuri != NULL) {
1061 			if (notify == NULL)
1062 				continue;
1063 			if (strcmp(rp->notifyuri, notify) != 0)
1064 				continue;
1065 		} else if (notify != NULL)
1066 			continue;
1067 		/* found matching repo */
1068 		free(repouri);
1069 		return rp;
1070 	}
1071 
1072 	rp = repo_alloc(talid);
1073 	rp->basedir = repo_dir(repouri, NULL, 0);
1074 	rp->repouri = repouri;
1075 	if (notify != NULL)
1076 		if ((rp->notifyuri = strdup(notify)) == NULL)
1077 			err(1, NULL);
1078 
1079 	if (++talrepocnt[talid] >= MAX_REPO_PER_TAL) {
1080 		if (talrepocnt[talid] == MAX_REPO_PER_TAL)
1081 			warnx("too many repositories under %s", tals[talid]);
1082 		nofetch = 1;
1083 	}
1084 
1085 	/* check if sync disabled ... */
1086 	if (noop || nofetch) {
1087 		logx("%s: using cache", rp->basedir);
1088 		entityq_flush(&rp->queue, rp);
1089 		return rp;
1090 	}
1091 
1092 	/* try to create base directory */
1093 	if (mkpath(rp->basedir) == -1)
1094 		warn("mkpath %s", rp->basedir);
1095 
1096 	/* ... else try RRDP first if available then rsync */
1097 	if (notify != NULL)
1098 		rp->rrdp = rrdp_get(notify);
1099 	if (rp->rrdp == NULL)
1100 		rp->rsync = rsync_get(uri, rp->basedir);
1101 
1102 	/* need to check if it was already loaded */
1103 	if (repo_state(rp) != REPO_LOADING)
1104 		entityq_flush(&rp->queue, rp);
1105 
1106 	return rp;
1107 }
1108 
1109 /*
1110  * Find repository by identifier.
1111  */
1112 struct repo *
1113 repo_byid(unsigned int id)
1114 {
1115 	struct repo	*rp;
1116 
1117 	SLIST_FOREACH(rp, &repos, entry) {
1118 		if (rp->id == id)
1119 			return rp;
1120 	}
1121 	return NULL;
1122 }
1123 
1124 /*
1125  * Find repository by base path.
1126  */
1127 static struct repo *
1128 repo_bypath(const char *path)
1129 {
1130 	struct repo	*rp;
1131 
1132 	SLIST_FOREACH(rp, &repos, entry) {
1133 		if (strcmp(rp->basedir, path) == 0)
1134 			return rp;
1135 	}
1136 	return NULL;
1137 }
1138 
1139 /*
1140  * Return the repository base or alternate directory.
1141  * Returned string must be freed by caller.
1142  */
1143 char *
1144 repo_basedir(const struct repo *rp, int wantvalid)
1145 {
1146 	char *path = NULL;
1147 
1148 	if (!wantvalid) {
1149 		if (rp->ta) {
1150 			if ((path = strdup(rp->ta->basedir)) == NULL)
1151 				err(1, NULL);
1152 		} else if (rp->rsync) {
1153 			if ((path = strdup(rp->rsync->basedir)) == NULL)
1154 				err(1, NULL);
1155 		} else if (rp->rrdp) {
1156 			path = rrdp_filename(rp->rrdp, rp->repouri, 0);
1157 		} else
1158 			path = NULL;	/* only valid repo available */
1159 	} else if (rp->basedir != NULL) {
1160 		if ((path = strdup(rp->basedir)) == NULL)
1161 			err(1, NULL);
1162 	}
1163 
1164 	return path;
1165 }
1166 
1167 /*
1168  * Return the repository identifier.
1169  */
1170 unsigned int
1171 repo_id(const struct repo *rp)
1172 {
1173 	return rp->id;
1174 }
1175 
1176 /*
1177  * Return the repository URI.
1178  */
1179 const char *
1180 repo_uri(const struct repo *rp)
1181 {
1182 	return rp->repouri;
1183 }
1184 
1185 /*
1186  * Return the repository URI.
1187  */
1188 void
1189 repo_fetch_uris(const struct repo *rp, const char **carepo,
1190     const char **notifyuri)
1191 {
1192 	*carepo = rp->repouri;
1193 	*notifyuri = rp->notifyuri;
1194 }
1195 
1196 /*
1197  * Return 1 if repository is synced else 0.
1198  */
1199 int
1200 repo_synced(const struct repo *rp)
1201 {
1202 	if (repo_state(rp) == REPO_DONE &&
1203 	    !(rp->rrdp == NULL && rp->rsync == NULL && rp->ta == NULL))
1204 		return 1;
1205 	return 0;
1206 }
1207 
1208 /*
1209  * Return the repository tal ID.
1210  */
1211 int
1212 repo_talid(const struct repo *rp)
1213 {
1214 	return rp->talid;
1215 }
1216 
1217 int
1218 repo_queued(struct repo *rp, struct entity *p)
1219 {
1220 	if (repo_state(rp) == REPO_LOADING) {
1221 		TAILQ_INSERT_TAIL(&rp->queue, p, entries);
1222 		return 1;
1223 	}
1224 	return 0;
1225 }
1226 
1227 static void
1228 repo_fail(struct repo *rp)
1229 {
1230 	/* reset the alarm since code may fallback to rsync */
1231 	rp->alarm = getmonotime() + repo_timeout;
1232 
1233 	if (rp->ta)
1234 		http_finish(rp->ta->id, HTTP_FAILED, NULL);
1235 	else if (rp->rsync)
1236 		rsync_finish(rp->rsync->id, 0);
1237 	else if (rp->rrdp)
1238 		rrdp_finish(rp->rrdp->id, 0);
1239 	else
1240 		errx(1, "%s: bad repo", rp->repouri);
1241 }
1242 
1243 static void
1244 repo_abort(struct repo *rp)
1245 {
1246 	/* reset the alarm */
1247 	rp->alarm = getmonotime() + repo_timeout;
1248 
1249 	if (rp->rsync)
1250 		rsync_abort(rp->rsync->id);
1251 	else if (rp->rrdp)
1252 		rrdp_abort(rp->rrdp->id);
1253 	else
1254 		repo_fail(rp);
1255 }
1256 
1257 int
1258 repo_check_timeout(int timeout)
1259 {
1260 	struct repo	*rp;
1261 	time_t		 now;
1262 	int		 diff;
1263 
1264 	now = getmonotime();
1265 
1266 	/* check against our runtime deadline first */
1267 	if (deadline != 0) {
1268 		if (deadline <= now) {
1269 			warnx("deadline reached, giving up on repository sync");
1270 			nofetch = 1;
1271 			/* clear deadline since nofetch is set */
1272 			deadline = 0;
1273 			/* increase now enough so that all pending repos fail */
1274 			now += repo_timeout;
1275 		} else {
1276 			diff = deadline - now;
1277 			diff *= 1000;
1278 			if (timeout == INFTIM || diff < timeout)
1279 				timeout = diff;
1280 		}
1281 	}
1282 	/* Look up in repository table. (Lookup should actually fail here) */
1283 	SLIST_FOREACH(rp, &repos, entry) {
1284 		if (repo_state(rp) == REPO_LOADING) {
1285 			if (rp->alarm <= now) {
1286 				warnx("%s: synchronisation timeout",
1287 				    rp->repouri);
1288 				repo_abort(rp);
1289 			} else {
1290 				diff = rp->alarm - now;
1291 				diff *= 1000;
1292 				if (timeout == INFTIM || diff < timeout)
1293 					timeout = diff;
1294 			}
1295 		}
1296 	}
1297 	return timeout;
1298 }
1299 
1300 /*
1301  * Update stats object of repository depending on rtype and subtype.
1302  */
1303 void
1304 repo_stat_inc(struct repo *rp, enum rtype type, enum stype subtype)
1305 {
1306 	if (rp == NULL)
1307 		return;
1308 	switch (type) {
1309 	case RTYPE_CER:
1310 		if (subtype == STYPE_OK)
1311 			rp->stats.certs++;
1312 		if (subtype == STYPE_FAIL)
1313 			rp->stats.certs_fail++;
1314 		if (subtype == STYPE_BGPSEC) {
1315 			rp->stats.certs--;
1316 			rp->stats.brks++;
1317 		}
1318 		break;
1319 	case RTYPE_MFT:
1320 		if (subtype == STYPE_OK)
1321 			rp->stats.mfts++;
1322 		if (subtype == STYPE_FAIL)
1323 			rp->stats.mfts_fail++;
1324 		if (subtype == STYPE_STALE)
1325 			rp->stats.mfts_stale++;
1326 		break;
1327 	case RTYPE_ROA:
1328 		switch (subtype) {
1329 		case STYPE_OK:
1330 			rp->stats.roas++;
1331 			break;
1332 		case STYPE_FAIL:
1333 			rp->stats.roas_fail++;
1334 			break;
1335 		case STYPE_INVALID:
1336 			rp->stats.roas_invalid++;
1337 			break;
1338 		case STYPE_TOTAL:
1339 			rp->stats.vrps++;
1340 			break;
1341 		case STYPE_UNIQUE:
1342 			rp->stats.vrps_uniqs++;
1343 			break;
1344 		case STYPE_DEC_UNIQUE:
1345 			rp->stats.vrps_uniqs--;
1346 			break;
1347 		default:
1348 			break;
1349 		}
1350 		break;
1351 	case RTYPE_ASPA:
1352 		switch (subtype) {
1353 		case STYPE_OK:
1354 			rp->stats.aspas++;
1355 			break;
1356 		case STYPE_FAIL:
1357 			rp->stats.aspas_fail++;
1358 			break;
1359 		case STYPE_INVALID:
1360 			rp->stats.aspas_invalid++;
1361 			break;
1362 		case STYPE_TOTAL:
1363 			rp->stats.vaps++;
1364 			break;
1365 		case STYPE_UNIQUE:
1366 			rp->stats.vaps_uniqs++;
1367 			break;
1368 		case STYPE_BOTH:
1369 			rp->stats.vaps_pas++;
1370 			break;
1371 		case STYPE_ONLY_IPV4:
1372 			rp->stats.vaps_pas4++;
1373 			break;
1374 		case STYPE_ONLY_IPV6:
1375 			rp->stats.vaps_pas6++;
1376 			break;
1377 		default:
1378 			break;
1379 		}
1380 		break;
1381 	case RTYPE_CRL:
1382 		rp->stats.crls++;
1383 		break;
1384 	case RTYPE_GBR:
1385 		rp->stats.gbrs++;
1386 		break;
1387 	case RTYPE_TAK:
1388 		rp->stats.taks++;
1389 		break;
1390 	default:
1391 		break;
1392 	}
1393 }
1394 
1395 void
1396 repo_stats_collect(void (*cb)(const struct repo *, const struct repostats *,
1397     void *), void *arg)
1398 {
1399 	struct repo	*rp;
1400 
1401 	SLIST_FOREACH(rp, &repos, entry) {
1402 		cb(rp, &rp->stats, arg);
1403 	}
1404 }
1405 
1406 /*
1407  * Delayed delete of files from RRDP. Since RRDP has no security built-in
1408  * this code needs to check if this RRDP repository is actually allowed to
1409  * remove the file referenced by the URI.
1410  */
1411 static void
1412 repo_cleanup_rrdp(struct filepath_tree *tree)
1413 {
1414 	struct rrdprepo *rr;
1415 	struct filepath *fp, *nfp;
1416 	char *fn;
1417 
1418 	SLIST_FOREACH(rr, &rrdprepos, entry) {
1419 		RB_FOREACH_SAFE(fp, filepath_tree, &rr->deleted, nfp) {
1420 			if (!rrdp_uri_valid(rr, fp->file)) {
1421 				warnx("%s: external URI %s", rr->notifyuri,
1422 				    fp->file);
1423 				filepath_put(&rr->deleted, fp);
1424 				continue;
1425 			}
1426 			/* try to remove file from rrdp repo ... */
1427 			fn = rrdp_filename(rr, fp->file, 0);
1428 
1429 			if (unlink(fn) == -1) {
1430 				if (errno != ENOENT)
1431 					warn("unlink %s", fn);
1432 			} else {
1433 				if (verbose > 1)
1434 					logx("deleted %s", fn);
1435 				stats.del_files++;
1436 			}
1437 			free(fn);
1438 
1439 			/* ... and from the valid repository if unused. */
1440 			fn = rrdp_filename(rr, fp->file, 1);
1441 			if (!filepath_exists(tree, fn)) {
1442 				if (unlink(fn) == -1) {
1443 					if (errno != ENOENT)
1444 						warn("unlink %s", fn);
1445 				} else {
1446 					if (verbose > 1)
1447 						logx("deleted %s", fn);
1448 					stats.del_files++;
1449 				}
1450 			} else
1451 				warnx("%s: referenced file supposed to be "
1452 				    "deleted", fn);
1453 
1454 			free(fn);
1455 			filepath_put(&rr->deleted, fp);
1456 		}
1457 	}
1458 }
1459 
1460 /*
1461  * All files in tree are valid and should be moved to the valid repository
1462  * if not already there. Rename the files to the new path and readd the
1463  * filepath entry with the new path if successful.
1464  */
1465 static void
1466 repo_move_valid(struct filepath_tree *tree)
1467 {
1468 	struct filepath *fp, *nfp;
1469 	size_t rsyncsz = strlen(".rsync/");
1470 	size_t rrdpsz = strlen(".rrdp/");
1471 	char *fn, *base;
1472 
1473 	RB_FOREACH_SAFE(fp, filepath_tree, tree, nfp) {
1474 		if (strncmp(fp->file, ".rsync/", rsyncsz) != 0 &&
1475 		    strncmp(fp->file, ".rrdp/", rrdpsz) != 0)
1476 			continue; /* not a temporary file path */
1477 
1478 		if (strncmp(fp->file, ".rsync/", rsyncsz) == 0) {
1479 			fn = fp->file + rsyncsz;
1480 		} else {
1481 			base = strchr(fp->file + rrdpsz, '/');
1482 			assert(base != NULL);
1483 			fn = base + 1;
1484 		}
1485 
1486 		if (repo_mkpath(AT_FDCWD, fn) == -1)
1487 			continue;
1488 
1489 		if (rename(fp->file, fn) == -1) {
1490 			warn("rename %s", fp->file);
1491 			continue;
1492 		}
1493 
1494 		/* switch filepath node to new path */
1495 		RB_REMOVE(filepath_tree, tree, fp);
1496 		base = fp->file;
1497 		if ((fp->file = strdup(fn)) == NULL)
1498 			err(1, NULL);
1499 		free(base);
1500 		if (RB_INSERT(filepath_tree, tree, fp) != NULL)
1501 			errx(1, "%s: both possibilities of file present",
1502 			    fp->file);
1503 	}
1504 }
1505 
1506 #define	BASE_DIR	(void *)0x01
1507 #define	RSYNC_DIR	(void *)0x02
1508 #define	RRDP_DIR	(void *)0x03
1509 
1510 static const struct rrdprepo *
1511 repo_is_rrdp(struct repo *rp)
1512 {
1513 	/* check for special pointers first these are not a repository */
1514 	if (rp == NULL || rp == BASE_DIR || rp == RSYNC_DIR || rp == RRDP_DIR)
1515 		return NULL;
1516 
1517 	if (rp->rrdp)
1518 		return rp->rrdp->state == REPO_DONE ? rp->rrdp : NULL;
1519 	return NULL;
1520 }
1521 
1522 static inline char *
1523 skip_dotslash(char *in)
1524 {
1525 	if (memcmp(in, "./", 2) == 0)
1526 		return in + 2;
1527 	return in;
1528 }
1529 
1530 void
1531 repo_cleanup(struct filepath_tree *tree, int cachefd)
1532 {
1533 	char *argv[2] = { ".", NULL };
1534 	FTS *fts;
1535 	FTSENT *e;
1536 	const struct rrdprepo *rr;
1537 
1538 	/* first move temp files which have been used to valid dir */
1539 	repo_move_valid(tree);
1540 	/* then delete files requested by rrdp */
1541 	repo_cleanup_rrdp(tree);
1542 
1543 	if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, NULL)) == NULL)
1544 		err(1, "fts_open");
1545 	errno = 0;
1546 	while ((e = fts_read(fts)) != NULL) {
1547 		char *path = skip_dotslash(e->fts_path);
1548 		switch (e->fts_info) {
1549 		case FTS_NSOK:
1550 			if (filepath_exists(tree, path)) {
1551 				e->fts_parent->fts_number++;
1552 				break;
1553 			}
1554 			if (e->fts_parent->fts_pointer == RRDP_DIR) {
1555 				e->fts_parent->fts_number++;
1556 				/* handle rrdp .state files explicitly */
1557 				if (e->fts_level == 3 &&
1558 				    strcmp(e->fts_name, ".state") == 0)
1559 					break;
1560 				/* can't delete these extra files */
1561 				stats.extra_files++;
1562 				if (verbose > 1)
1563 					logx("superfluous %s", path);
1564 				break;
1565 			}
1566 			if (e->fts_parent->fts_pointer == RSYNC_DIR) {
1567 				/* no need to keep rsync files */
1568 				if (verbose > 1)
1569 					logx("superfluous %s", path);
1570 			}
1571 			rr = repo_is_rrdp(e->fts_parent->fts_pointer);
1572 			if (rr != NULL) {
1573 				struct stat st;
1574 				char *fn;
1575 
1576 				if (asprintf(&fn, "%s/%s", rr->basedir,
1577 				    path) == -1)
1578 					err(1, NULL);
1579 
1580 				/*
1581 				 * If the file exists in the rrdp dir
1582 				 * that file is newer and needs to be kept
1583 				 * so unlink this file instead of moving
1584 				 * it over the file in the rrdp dir.
1585 				 */
1586 				if (fstatat(cachefd, fn, &st, 0) == 0 &&
1587 				    S_ISREG(st.st_mode)) {
1588 					free(fn);
1589 					goto unlink;
1590 				}
1591 				if (repo_mkpath(cachefd, fn) == 0) {
1592 					if (renameat(AT_FDCWD, e->fts_accpath,
1593 					    cachefd, fn) == -1)
1594 						warn("rename %s to %s", path,
1595 						    fn);
1596 					else if (verbose > 1)
1597 						logx("moved %s", path);
1598 					stats.extra_files++;
1599 				}
1600 				free(fn);
1601 			} else {
1602  unlink:
1603 				if (unlink(e->fts_accpath) == -1) {
1604 					warn("unlink %s", path);
1605 				} else {
1606 					if (verbose > 1)
1607 						logx("deleted %s", path);
1608 					stats.del_files++;
1609 				}
1610 			}
1611 			break;
1612 		case FTS_D:
1613 			if (e->fts_level == 1) {
1614 				if (strcmp(".rsync", e->fts_name) == 0)
1615 					e->fts_pointer = RSYNC_DIR;
1616 				else if (strcmp(".rrdp", e->fts_name) == 0)
1617 					e->fts_pointer = RRDP_DIR;
1618 				else
1619 					e->fts_pointer = BASE_DIR;
1620 			} else
1621 				e->fts_pointer = e->fts_parent->fts_pointer;
1622 
1623 			/*
1624 			 * special handling for rrdp directories,
1625 			 * clear them if they are not used anymore but
1626 			 * only if rrdp is active.
1627 			 */
1628 			if (e->fts_pointer == RRDP_DIR && e->fts_level == 2) {
1629 				if (!rrdp_is_active(path))
1630 					e->fts_pointer = NULL;
1631 			}
1632 			if (e->fts_pointer == BASE_DIR && e->fts_level > 1) {
1633 				e->fts_pointer = repo_bypath(path);
1634 				if (e->fts_pointer == NULL)
1635 					e->fts_pointer = BASE_DIR;
1636 			}
1637 			break;
1638 		case FTS_DP:
1639 			if (e->fts_level == FTS_ROOTLEVEL)
1640 				break;
1641 			if (e->fts_level == 1)
1642 				/* do not remove .rsync and .rrdp */
1643 				if (e->fts_pointer == RRDP_DIR ||
1644 				    e->fts_pointer == RSYNC_DIR)
1645 					break;
1646 
1647 			e->fts_parent->fts_number += e->fts_number;
1648 
1649 			if (e->fts_number == 0) {
1650 				if (rmdir(e->fts_accpath) == -1)
1651 					warn("rmdir %s", path);
1652 				else
1653 					stats.del_dirs++;
1654 			}
1655 			break;
1656 		case FTS_SL:
1657 		case FTS_SLNONE:
1658 			warnx("symlink %s", path);
1659 			if (unlink(e->fts_accpath) == -1)
1660 				warn("unlink %s", path);
1661 			break;
1662 		case FTS_NS:
1663 		case FTS_ERR:
1664 			if (e->fts_errno == ENOENT &&
1665 			    e->fts_level == FTS_ROOTLEVEL)
1666 				continue;
1667 			warnx("fts_read %s: %s", path,
1668 			    strerror(e->fts_errno));
1669 			break;
1670 		default:
1671 			warnx("fts_read %s: unhandled[%x]", path,
1672 			    e->fts_info);
1673 			break;
1674 		}
1675 
1676 		errno = 0;
1677 	}
1678 	if (errno)
1679 		err(1, "fts_read");
1680 	if (fts_close(fts) == -1)
1681 		err(1, "fts_close");
1682 }
1683 
1684 void
1685 repo_free(void)
1686 {
1687 	struct repo *rp;
1688 
1689 	while ((rp = SLIST_FIRST(&repos)) != NULL) {
1690 		SLIST_REMOVE_HEAD(&repos, entry);
1691 		free(rp->repouri);
1692 		free(rp->notifyuri);
1693 		free(rp->basedir);
1694 		free(rp);
1695 	}
1696 
1697 	ta_free();
1698 	rrdp_free();
1699 	rsync_free();
1700 }
1701 
1702 /*
1703  * Remove all files and directories under base but do not remove base itself.
1704  */
1705 static void
1706 remove_contents(char *base)
1707 {
1708 	char *argv[2] = { base, NULL };
1709 	FTS *fts;
1710 	FTSENT *e;
1711 
1712 	if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, NULL)) == NULL)
1713 		err(1, "fts_open");
1714 	errno = 0;
1715 	while ((e = fts_read(fts)) != NULL) {
1716 		switch (e->fts_info) {
1717 		case FTS_NSOK:
1718 		case FTS_SL:
1719 		case FTS_SLNONE:
1720 			if (unlink(e->fts_accpath) == -1)
1721 				warn("unlink %s", e->fts_path);
1722 			break;
1723 		case FTS_D:
1724 			break;
1725 		case FTS_DP:
1726 			/* keep root directory */
1727 			if (e->fts_level == FTS_ROOTLEVEL)
1728 				break;
1729 			if (rmdir(e->fts_accpath) == -1)
1730 				warn("rmdir %s", e->fts_path);
1731 			break;
1732 		case FTS_NS:
1733 		case FTS_ERR:
1734 			warnx("fts_read %s: %s", e->fts_path,
1735 			    strerror(e->fts_errno));
1736 			break;
1737 		default:
1738 			warnx("unhandled[%x] %s", e->fts_info,
1739 			    e->fts_path);
1740 			break;
1741 		}
1742 		errno = 0;
1743 	}
1744 	if (errno)
1745 		err(1, "fts_read");
1746 	if (fts_close(fts) == -1)
1747 		err(1, "fts_close");
1748 }
1749