xref: /openbsd-src/usr.sbin/rpki-client/repo.c (revision 24bb5fcea3ed904bc467217bdaadb5dfc618d5bf)
1 /*	$OpenBSD: repo.c,v 1.8 2021/06/14 10:01:23 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 int
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 		warn("mkpath %s", file);
261 		return -1;
262 	}
263 	*slash = '/';
264 	return 0;
265 }
266 
267 /*
268  * Build TA file name based on the repo info.
269  * If temp is set add Xs for mkostemp.
270  */
271 static char *
272 ta_filename(const struct tarepo *tr, int temp)
273 {
274 	const char *file;
275 	char *nfile;
276 
277 	/* does not matter which URI, all end with same filename */
278 	file = strrchr(tr->uri[0], '/');
279 	assert(file);
280 
281 	if (asprintf(&nfile, "%s%s%s", tr->basedir, file,
282 	    temp ? ".XXXXXXXX": "") == -1)
283 		err(1, NULL);
284 
285 	return nfile;
286 }
287 
288 /*
289  * Build local file name base on the URI and the rrdprepo info.
290  */
291 static char *
292 rrdp_filename(const struct rrdprepo *rr, const char *uri, int temp)
293 {
294 	char *nfile;
295 	char *dir = rr->basedir;
296 
297 	if (temp)
298 		dir = rr->temp;
299 
300 	if (!valid_uri(uri, strlen(uri), "rsync://")) {
301 		warnx("%s: bad URI %s", rr->basedir, uri);
302 		return NULL;
303 	}
304 
305 	uri += strlen("rsync://");	/* skip proto */
306 	if (asprintf(&nfile, "%s/%s", dir, uri) == -1)
307 		err(1, NULL);
308 	return nfile;
309 }
310 
311 /*
312  * Build RRDP state file name based on the repo info.
313  * If temp is set add Xs for mkostemp.
314  */
315 static char *
316 rrdp_state_filename(const struct rrdprepo *rr, int temp)
317 {
318 	char *nfile;
319 
320 	if (asprintf(&nfile, "%s/.state%s", rr->basedir,
321 	    temp ? ".XXXXXXXX": "") == -1)
322 		err(1, NULL);
323 
324 	return nfile;
325 }
326 
327 
328 
329 static void
330 ta_fetch(struct tarepo *tr)
331 {
332 	logx("ta/%s: pulling from %s", tr->descr, tr->uri[tr->uriidx]);
333 
334 	if (strncasecmp(tr->uri[tr->uriidx], "rsync://", 8) == 0) {
335 		/*
336 		 * Create destination location.
337 		 * Build up the tree to this point.
338 		 */
339 		rsync_fetch(tr->id, tr->uri[tr->uriidx], tr->basedir);
340 	} else {
341 		int fd;
342 
343 		tr->temp = ta_filename(tr, 1);
344 		fd = mkostemp(tr->temp, O_CLOEXEC);
345 		if (fd == -1) {
346 			warn("mkostemp: %s", tr->temp);
347 			http_finish(tr->id, HTTP_FAILED, NULL);
348 			return;
349 		}
350 		if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1)
351 			warn("fchmod: %s", tr->temp);
352 
353 		http_fetch(tr->id, tr->uri[tr->uriidx], NULL, fd);
354 	}
355 }
356 
357 static struct tarepo *
358 ta_get(struct tal *tal)
359 {
360 	struct tarepo *tr;
361 
362 	/* no need to look for possible other repo */
363 
364 	if (tal->urisz == 0)
365 		errx(1, "TAL %s has no URI", tal->descr);
366 
367 	if ((tr = calloc(1, sizeof(*tr))) == NULL)
368 		err(1, NULL);
369 	tr->id = ++repoid;
370 	SLIST_INSERT_HEAD(&tarepos, tr, entry);
371 
372 	if ((tr->descr = strdup(tal->descr)) == NULL)
373 		err(1, NULL);
374 	if (asprintf(&tr->basedir, "ta/%s", tal->descr) == -1)
375 		err(1, NULL);
376 
377 	/* steal URI infromation from TAL */
378 	tr->urisz = tal->urisz;
379 	tr->uri = tal->uri;
380 	tal->urisz = 0;
381 	tal->uri = NULL;
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 		/* try to create base directory */
389 		if (mkpath(tr->basedir) == -1)
390 			warn("mkpath %s", tr->basedir);
391 
392 		ta_fetch(tr);
393 	}
394 
395 	return tr;
396 }
397 
398 static struct tarepo *
399 ta_find(size_t id)
400 {
401 	struct tarepo *tr;
402 
403 	SLIST_FOREACH(tr, &tarepos, entry)
404 		if (id == tr->id)
405 			break;
406 	return tr;
407 }
408 
409 static void
410 ta_free(void)
411 {
412 	struct tarepo *tr;
413 
414 	while ((tr = SLIST_FIRST(&tarepos)) != NULL) {
415 		SLIST_REMOVE_HEAD(&tarepos, entry);
416 		free(tr->descr);
417 		free(tr->basedir);
418 		free(tr->temp);
419 		free(tr->uri);
420 		free(tr);
421 	}
422 }
423 
424 static struct rsyncrepo *
425 rsync_get(const char *uri)
426 {
427 	struct rsyncrepo *rr;
428 	char *repo;
429 
430 	if ((repo = rsync_base_uri(uri)) == NULL)
431 		errx(1, "bad caRepository URI: %s", uri);
432 
433 	SLIST_FOREACH(rr, &rsyncrepos, entry)
434 		if (strcmp(rr->repouri, repo) == 0) {
435 			free(repo);
436 			return rr;
437 		}
438 
439 	if ((rr = calloc(1, sizeof(*rr))) == NULL)
440 		err(1, NULL);
441 
442 	rr->id = ++repoid;
443 	SLIST_INSERT_HEAD(&rsyncrepos, rr, entry);
444 
445 	rr->repouri = repo;
446 	rr->basedir = rsync_dir(repo, "rsync");
447 
448 	if (noop) {
449 		rr->state = REPO_DONE;
450 		logx("%s: using cache", rr->basedir);
451 		/* there is nothing in the queue so no need to flush */
452 	} else {
453 		/* create base directory */
454 		if (mkpath(rr->basedir) == -1) {
455 			warn("mkpath %s", rr->basedir);
456 			rsync_finish(rr->id, 0);
457 			return rr;
458 		}
459 
460 		logx("%s: pulling from %s", rr->basedir, rr->repouri);
461 		rsync_fetch(rr->id, rr->repouri, rr->basedir);
462 	}
463 
464 	return rr;
465 }
466 
467 static struct rsyncrepo *
468 rsync_find(size_t id)
469 {
470 	struct rsyncrepo *rr;
471 
472 	SLIST_FOREACH(rr, &rsyncrepos, entry)
473 		if (id == rr->id)
474 			break;
475 	return rr;
476 }
477 
478 static void
479 rsync_free(void)
480 {
481 	struct rsyncrepo *rr;
482 
483 	while ((rr = SLIST_FIRST(&rsyncrepos)) != NULL) {
484 		SLIST_REMOVE_HEAD(&rsyncrepos, entry);
485 		free(rr->repouri);
486 		free(rr->basedir);
487 		free(rr);
488 	}
489 }
490 
491 static int rrdprepo_fetch(struct rrdprepo *);
492 
493 static struct rrdprepo *
494 rrdp_get(const char *uri)
495 {
496 	struct rrdprepo *rr;
497 
498 	SLIST_FOREACH(rr, &rrdprepos, entry)
499 		if (strcmp(rr->notifyuri, uri) == 0) {
500 			if (rr->state == REPO_FAILED)
501 				return NULL;
502 			return rr;
503 		}
504 
505 	if ((rr = calloc(1, sizeof(*rr))) == NULL)
506 		err(1, NULL);
507 
508 	rr->id = ++repoid;
509 	SLIST_INSERT_HEAD(&rrdprepos, rr, entry);
510 
511 	if ((rr->notifyuri = strdup(uri)) == NULL)
512 		err(1, NULL);
513 	rr->basedir = hash_dir(uri, "rrdp");
514 
515 	RB_INIT(&rr->added);
516 	RB_INIT(&rr->deleted);
517 
518 	if (noop) {
519 		rr->state = REPO_DONE;
520 		logx("%s: using cache", rr->notifyuri);
521 		/* there is nothing in the queue so no need to flush */
522 	} else {
523 		/* create base directory */
524 		if (mkpath(rr->basedir) == -1) {
525 			warn("mkpath %s", rr->basedir);
526 			rrdp_finish(rr->id, 0);
527 			return rr;
528 		}
529 		if (rrdprepo_fetch(rr) == -1) {
530 			rrdp_finish(rr->id, 0);
531 			return rr;
532 		}
533 
534 		logx("%s: pulling from %s", rr->notifyuri, "network");
535 	}
536 
537 	return rr;
538 }
539 
540 static struct rrdprepo *
541 rrdp_find(size_t id)
542 {
543 	struct rrdprepo *rr;
544 
545 	SLIST_FOREACH(rr, &rrdprepos, entry)
546 		if (id == rr->id)
547 			break;
548 	return rr;
549 }
550 
551 static void
552 rrdp_free(void)
553 {
554 	struct rrdprepo *rr;
555 
556 	while ((rr = SLIST_FIRST(&rrdprepos)) != NULL) {
557 		SLIST_REMOVE_HEAD(&rrdprepos, entry);
558 
559 		free(rr->notifyuri);
560 		free(rr->basedir);
561 		free(rr->temp);
562 
563 		filepath_free(&rr->added);
564 		filepath_free(&rr->deleted);
565 
566 		free(rr);
567 	}
568 }
569 
570 static struct rrdprepo *
571 rrdp_basedir(const char *dir)
572 {
573 	struct rrdprepo *rr;
574 
575 	SLIST_FOREACH(rr, &rrdprepos, entry)
576 		if (strcmp(dir, rr->basedir) == 0) {
577 			if (rr->state == REPO_FAILED)
578 				return NULL;
579 			return rr;
580 		}
581 
582 	return NULL;
583 }
584 
585 /*
586  * Allocate and insert a new repository.
587  */
588 static struct repo *
589 repo_alloc(void)
590 {
591 	struct repo *rp;
592 
593 	if ((rp = calloc(1, sizeof(*rp))) == NULL)
594 		err(1, NULL);
595 
596 	rp->id = ++repoid;
597 	TAILQ_INIT(&rp->queue);
598 	SLIST_INSERT_HEAD(&repos, rp, entry);
599 
600 	stats.repos++;
601 	return rp;
602 }
603 
604 /*
605  * Return the state of a repository.
606  */
607 static enum repo_state
608 repo_state(struct repo *rp)
609 {
610 	if (rp->ta)
611 		return rp->ta->state;
612 	if (rp->rrdp)
613 		return rp->rrdp->state;
614 	if (rp->rsync)
615 		return rp->rsync->state;
616 	errx(1, "%s: bad repo", rp->repouri);
617 }
618 
619 /*
620  * Parse the RRDP state file if it exists and set the session struct
621  * based on that information.
622  */
623 static void
624 rrdp_parse_state(const struct rrdprepo *rr, struct rrdp_session *state)
625 {
626 	FILE *f;
627 	int fd, ln = 0;
628 	const char *errstr;
629 	char *line = NULL, *file;
630 	size_t len = 0;
631 	ssize_t n;
632 
633 	file = rrdp_state_filename(rr, 0);
634 	if ((fd = open(file, O_RDONLY)) == -1) {
635 		if (errno != ENOENT)
636 			warn("%s: open state file", rr->basedir);
637 		free(file);
638 		return;
639 	}
640 	free(file);
641 	f = fdopen(fd, "r");
642 	if (f == NULL)
643 		err(1, "fdopen");
644 
645 	while ((n = getline(&line, &len, f)) != -1) {
646 		if (line[n - 1] == '\n')
647 			line[n - 1] = '\0';
648 		switch (ln) {
649 		case 0:
650 			if ((state->session_id = strdup(line)) == NULL)
651 				err(1, NULL);
652 			break;
653 		case 1:
654 			state->serial = strtonum(line, 1, LLONG_MAX, &errstr);
655 			if (errstr)
656 				goto fail;
657 			break;
658 		case 2:
659 			if ((state->last_mod = strdup(line)) == NULL)
660 				err(1, NULL);
661 			break;
662 		default:
663 			goto fail;
664 		}
665 		ln++;
666 	}
667 
668 	free(line);
669 	if (ferror(f))
670 		goto fail;
671 	fclose(f);
672 	return;
673 
674 fail:
675 	warnx("%s: troubles reading state file", rr->basedir);
676 	fclose(f);
677 	free(state->session_id);
678 	free(state->last_mod);
679 	memset(state, 0, sizeof(*state));
680 }
681 
682 /*
683  * Carefully write the RRDP session state file back.
684  */
685 void
686 rrdp_save_state(size_t id, struct rrdp_session *state)
687 {
688 	struct rrdprepo *rr;
689 	char *temp, *file;
690 	FILE *f;
691 	int fd;
692 
693 	rr = rrdp_find(id);
694 	if (rr == NULL)
695 		errx(1, "non-existant rrdp repo %zu", id);
696 
697 	file = rrdp_state_filename(rr, 0);
698 	temp = rrdp_state_filename(rr, 1);
699 
700 	if ((fd = mkostemp(temp, O_CLOEXEC)) == -1) {
701 		warn("mkostemp %s", temp);
702 		goto fail;
703 	}
704 	(void) fchmod(fd, 0644);
705 	f = fdopen(fd, "w");
706 	if (f == NULL)
707 		err(1, "fdopen");
708 
709 	/* write session state file out */
710 	if (fprintf(f, "%s\n%lld\n", state->session_id,
711 	    state->serial) < 0) {
712 		fclose(f);
713 		goto fail;
714 	}
715 	if (state->last_mod != NULL) {
716 		if (fprintf(f, "%s\n", state->last_mod) < 0) {
717 			fclose(f);
718 			goto fail;
719 		}
720 	}
721 	if (fclose(f) != 0)
722 		goto fail;
723 
724 	if (rename(temp, file) == -1)
725 		warn("%s: rename state file", rr->basedir);
726 
727 	free(temp);
728 	free(file);
729 	return;
730 
731 fail:
732 	warnx("%s: failed to save state", rr->basedir);
733 	unlink(temp);
734 	free(temp);
735 	free(file);
736 }
737 
738 /*
739  * Write a file into the temporary RRDP dir but only after checking
740  * its hash (if required). The function also makes sure that the file
741  * tracking is properly adjusted.
742  * Returns 1 on success, 0 if the repo is corrupt, -1 on IO error
743  */
744 int
745 rrdp_handle_file(size_t id, enum publish_type pt, char *uri,
746     char *hash, size_t hlen, char *data, size_t dlen)
747 {
748 	struct rrdprepo *rr;
749 	struct filepath *fp;
750 	ssize_t s;
751 	char *fn;
752 	int fd = -1;
753 
754 	rr = rrdp_find(id);
755 	if (rr == NULL)
756 		errx(1, "non-existant rrdp repo %zu", id);
757 	if (rr->state == REPO_FAILED)
758 		return -1;
759 
760 	if (pt == PUB_UPD || pt == PUB_DEL) {
761 		if (filepath_exists(&rr->deleted, uri)) {
762 			warnx("%s: already deleted", uri);
763 			return 0;
764 		}
765 		fp = filepath_find(&rr->added, uri);
766 		if (fp == NULL) {
767 			if ((fn = rrdp_filename(rr, uri, 0)) == NULL)
768 				return 0;
769 		} else {
770 			filepath_put(&rr->added, fp);
771 			if ((fn = rrdp_filename(rr, uri, 1)) == NULL)
772 				return 0;
773 		}
774 		if (!valid_filehash(fn, hash, hlen)) {
775 			warnx("%s: bad message digest", fn);
776 			free(fn);
777 			return 0;
778 		}
779 		free(fn);
780 	}
781 
782 	if (pt == PUB_DEL) {
783 		filepath_add(&rr->deleted, uri);
784 	} else {
785 		fp = filepath_find(&rr->deleted, uri);
786 		if (fp != NULL)
787 			filepath_put(&rr->deleted, fp);
788 
789 		/* add new file to temp dir */
790 		if ((fn = rrdp_filename(rr, uri, 1)) == NULL)
791 			return 0;
792 
793 		if (repo_mkpath(fn) == -1)
794 			goto fail;
795 
796 		fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, 0644);
797 		if (fd == -1) {
798 			warn("open %s", fn);
799 			goto fail;
800 		}
801 
802 		if ((s = write(fd, data, dlen)) == -1) {
803 			warn("write %s", fn);
804 			goto fail;
805 		}
806 		close(fd);
807 		if ((size_t)s != dlen)	/* impossible */
808 			errx(1, "short write %s", fn);
809 		free(fn);
810 		filepath_add(&rr->added, uri);
811 	}
812 
813 	return 1;
814 
815 fail:
816 	rr->state = REPO_FAILED;
817 	if (fd != -1)
818 		close(fd);
819 	free(fn);
820 	return -1;
821 }
822 
823 /*
824  * Initiate a RRDP sync, create the required temporary directory and
825  * parse a possible state file before sending the request to the RRDP process.
826  */
827 static int
828 rrdprepo_fetch(struct rrdprepo *rr)
829 {
830 	struct rrdp_session state = { 0 };
831 
832 	if (asprintf(&rr->temp, "%s.XXXXXXXX", rr->basedir) == -1)
833 		err(1, NULL);
834 	if (mkdtemp(rr->temp) == NULL) {
835 		warn("mkdtemp %s", rr->temp);
836 		return -1;
837 	}
838 
839 	rrdp_parse_state(rr, &state);
840 	rrdp_fetch(rr->id, rr->notifyuri, rr->notifyuri, &state);
841 
842 	free(state.session_id);
843 	free(state.last_mod);
844 
845 	return 0;
846 }
847 
848 static int
849 rrdp_merge_repo(struct rrdprepo *rr)
850 {
851 	struct filepath *fp, *nfp;
852 	char *fn, *rfn;
853 
854 	RB_FOREACH_SAFE(fp, filepath_tree, &rr->added, nfp) {
855 		fn = rrdp_filename(rr, fp->file, 1);
856 		rfn = rrdp_filename(rr, fp->file, 0);
857 
858 		if (fn == NULL || rfn == NULL)
859 			errx(1, "bad filepath");	/* should not happen */
860 
861 		if (repo_mkpath(rfn) == -1) {
862 			goto fail;
863 		}
864 
865 		if (rename(fn, rfn) == -1) {
866 			warn("rename %s", rfn);
867 			goto fail;
868 		}
869 
870 		free(rfn);
871 		free(fn);
872 		filepath_put(&rr->added, fp);
873 	}
874 
875 	return 1;
876 
877 fail:
878 	free(rfn);
879 	free(fn);
880 	return 0;
881 }
882 
883 static void
884 rrdp_clean_temp(struct rrdprepo *rr)
885 {
886 	struct filepath *fp, *nfp;
887 	char *fn;
888 
889 	filepath_free(&rr->deleted);
890 
891 	RB_FOREACH_SAFE(fp, filepath_tree, &rr->added, nfp) {
892 		if ((fn = rrdp_filename(rr, fp->file, 1)) != NULL) {
893 			if (unlink(fn) == -1)
894 				warn("unlink %s", fn);
895 			free(fn);
896 		}
897 		filepath_put(&rr->added, fp);
898 	}
899 }
900 
901 /*
902  * RSYNC sync finished, either with or without success.
903  */
904 void
905 rsync_finish(size_t id, int ok)
906 {
907 	struct rsyncrepo *rr;
908 	struct tarepo *tr;
909 	struct repo *rp;
910 
911 	tr = ta_find(id);
912 	if (tr != NULL) {
913 		if (ok) {
914 			logx("ta/%s: loaded from network", tr->descr);
915 			stats.rsync_repos++;
916 			tr->state = REPO_DONE;
917 		} else if (++tr->uriidx < tr->urisz) {
918 			logx("ta/%s: load from network failed, retry",
919 			    tr->descr);
920 			ta_fetch(tr);
921 			return;
922 		} else {
923 			logx("ta/%s: load from network failed, "
924 			    "fallback to cache", tr->descr);
925 			stats.rsync_fails++;
926 			tr->state = REPO_FAILED;
927 		}
928 		SLIST_FOREACH(rp, &repos, entry)
929 			if (rp->ta == tr)
930 				entityq_flush(&rp->queue, rp);
931 
932 		return;
933 	}
934 
935 	rr = rsync_find(id);
936 	if (rr == NULL)
937 		errx(1, "unknown rsync repo %zu", id);
938 
939 	if (ok) {
940 		logx("%s: loaded from network", rr->basedir);
941 		stats.rsync_repos++;
942 		rr->state = REPO_DONE;
943 	} else {
944 		logx("%s: load from network failed, fallback to cache",
945 		    rr->basedir);
946 		stats.rsync_fails++;
947 		rr->state = REPO_FAILED;
948 	}
949 
950 	SLIST_FOREACH(rp, &repos, entry)
951 		if (rp->rsync == rr)
952 			entityq_flush(&rp->queue, rp);
953 }
954 
955 /*
956  * RRDP sync finshed, either with or without success.
957  */
958 void
959 rrdp_finish(size_t id, int ok)
960 {
961 	struct rrdprepo *rr;
962 	struct repo *rp;
963 
964 	rr = rrdp_find(id);
965 	if (rr == NULL)
966 		errx(1, "unknown RRDP repo %zu", id);
967 
968 	if (ok && rrdp_merge_repo(rr)) {
969 		logx("%s: loaded from network", rr->notifyuri);
970 		rr->state = REPO_DONE;
971 		stats.rrdp_repos++;
972 		SLIST_FOREACH(rp, &repos, entry)
973 			if (rp->rrdp == rr)
974 				entityq_flush(&rp->queue, rp);
975 	} else if (!ok) {
976 		rrdp_clean_temp(rr);
977 		stats.rrdp_fails++;
978 		rr->state = REPO_FAILED;
979 		logx("%s: load from network failed, fallback to rsync",
980 		    rr->notifyuri);
981 		SLIST_FOREACH(rp, &repos, entry)
982 			if (rp->rrdp == rr) {
983 				rp->rrdp = NULL;
984 				rp->rsync = rsync_get(rp->repouri);
985 				/* need to check if it was already loaded */
986 				if (repo_state(rp) != REPO_LOADING)
987 					entityq_flush(&rp->queue, rp);
988 			}
989 	} else {
990 		rrdp_clean_temp(rr);
991 		stats.rrdp_fails++;
992 		rr->state = REPO_FAILED;
993 		logx("%s: load from network failed", rr->notifyuri);
994 		SLIST_FOREACH(rp, &repos, entry)
995 			if (rp->rrdp == rr)
996 				entityq_flush(&rp->queue, rp);
997 	}
998 }
999 
1000 /*
1001  * Handle responses from the http process. For TA file, either rename
1002  * or delete the temporary file. For RRDP requests relay the request
1003  * over to the rrdp process.
1004  */
1005 void
1006 http_finish(size_t id, enum http_result res, const char *last_mod)
1007 {
1008 	struct tarepo *tr;
1009 	struct repo *rp;
1010 
1011 	tr = ta_find(id);
1012 	if (tr == NULL) {
1013 		/* not a TA fetch therefor RRDP */
1014 		rrdp_http_done(id, res, last_mod);
1015 		return;
1016 	}
1017 
1018 	/* Move downloaded TA file into place, or unlink on failure. */
1019 	if (res == HTTP_OK) {
1020 		char *file;
1021 
1022 		file = ta_filename(tr, 0);
1023 		if (rename(tr->temp, file) == -1)
1024 			warn("rename to %s", file);
1025 		free(file);
1026 
1027 		logx("ta/%s: loaded from network", tr->descr);
1028 		tr->state = REPO_DONE;
1029 		stats.http_repos++;
1030 	} else {
1031 		if (unlink(tr->temp) == -1 && errno != ENOENT)
1032 			warn("unlink %s", tr->temp);
1033 
1034 		if (++tr->uriidx < tr->urisz) {
1035 			logx("ta/%s: load from network failed, retry",
1036 			    tr->descr);
1037 			ta_fetch(tr);
1038 			return;
1039 		}
1040 
1041 		tr->state = REPO_FAILED;
1042 		logx("ta/%s: load from network failed, "
1043 		    "fallback to cache", tr->descr);
1044 	}
1045 
1046 	SLIST_FOREACH(rp, &repos, entry)
1047 		if (rp->ta == tr)
1048 			entityq_flush(&rp->queue, rp);
1049 }
1050 
1051 
1052 
1053 /*
1054  * Look up a trust anchor, queueing it for download if not found.
1055  */
1056 struct repo *
1057 ta_lookup(struct tal *tal)
1058 {
1059 	struct repo	*rp;
1060 
1061 	/* Look up in repository table. (Lookup should actually fail here) */
1062 	SLIST_FOREACH(rp, &repos, entry) {
1063 		if (strcmp(rp->repouri, tal->descr) == 0)
1064 			return rp;
1065 	}
1066 
1067 	rp = repo_alloc();
1068 	if ((rp->repouri = strdup(tal->descr)) == NULL)
1069 		err(1, NULL);
1070 	rp->ta = ta_get(tal);
1071 
1072 	return rp;
1073 }
1074 
1075 /*
1076  * Look up a repository, queueing it for discovery if not found.
1077  */
1078 struct repo *
1079 repo_lookup(const char *uri, const char *notify)
1080 {
1081 	struct repo *rp;
1082 
1083 	/* Look up in repository table. */
1084 	SLIST_FOREACH(rp, &repos, entry) {
1085 		if (strcmp(rp->repouri, uri) != 0)
1086 			continue;
1087 		return rp;
1088 	}
1089 
1090 	rp = repo_alloc();
1091 	if ((rp->repouri = strdup(uri)) == NULL)
1092 		err(1, NULL);
1093 
1094 	/* try RRDP first if available */
1095 	if (notify != NULL)
1096 		rp->rrdp = rrdp_get(notify);
1097 	if (rp->rrdp == NULL)
1098 		rp->rsync = rsync_get(uri);
1099 
1100 	return rp;
1101 }
1102 
1103 /*
1104  * Build local file name base on the URI and the repo info.
1105  */
1106 char *
1107 repo_filename(const struct repo *rp, const char *uri)
1108 {
1109 	char *nfile;
1110 	char *dir, *repouri;
1111 
1112 	if (uri == NULL && rp->ta)
1113 		return ta_filename(rp->ta, 0);
1114 
1115 	assert(uri != NULL);
1116 	if (rp->rrdp)
1117 		return rrdp_filename(rp->rrdp, uri, 0);
1118 
1119 	/* must be rsync */
1120 	dir = rp->rsync->basedir;
1121 	repouri = rp->rsync->repouri;
1122 
1123 	if (strstr(uri, repouri) != uri) {
1124 		warnx("%s: URI %s outside of repository", repouri, uri);
1125 		return NULL;
1126 	}
1127 
1128 	uri += strlen(repouri) + 1;	/* skip base and '/' */
1129 
1130 	if (asprintf(&nfile, "%s/%s", dir, uri) == -1)
1131 		err(1, NULL);
1132 	return nfile;
1133 }
1134 
1135 int
1136 repo_queued(struct repo *rp, struct entity *p)
1137 {
1138 	if (repo_state(rp) == REPO_LOADING) {
1139 		TAILQ_INSERT_TAIL(&rp->queue, p, entries);
1140 		return 1;
1141 	}
1142 	return 0;
1143 }
1144 
1145 static char **
1146 add_to_del(char **del, size_t *dsz, char *file)
1147 {
1148 	size_t i = *dsz;
1149 
1150 	del = reallocarray(del, i + 1, sizeof(*del));
1151 	if (del == NULL)
1152 		err(1, NULL);
1153 	if ((del[i] = strdup(file)) == NULL)
1154 		err(1, NULL);
1155 	*dsz = i + 1;
1156 	return del;
1157 }
1158 
1159 static char **
1160 repo_rrdp_cleanup(struct filepath_tree *tree, struct rrdprepo *rr,
1161     char **del, size_t *delsz)
1162 {
1163 	struct filepath *fp, *nfp;
1164 	char *fn;
1165 
1166 	RB_FOREACH_SAFE(fp, filepath_tree, &rr->deleted, nfp) {
1167 		fn = rrdp_filename(rr, fp->file, 0);
1168 		/* temp dir will be cleaned up by repo_cleanup() */
1169 
1170 		if (fn == NULL)
1171 			errx(1, "bad filepath");	/* should not happen */
1172 
1173 		if (!filepath_exists(tree, fn))
1174 			del = add_to_del(del, delsz, fn);
1175 		else
1176 			warnx("%s: referenced file supposed to be deleted", fn);
1177 
1178 		free(fn);
1179 		filepath_put(&rr->deleted, fp);
1180 	}
1181 
1182 	return del;
1183 }
1184 
1185 void
1186 repo_cleanup(struct filepath_tree *tree)
1187 {
1188 	size_t i, cnt, delsz = 0, dirsz = 0;
1189 	char **del = NULL, **dir = NULL;
1190 	char *argv[4] = { "ta", "rsync", "rrdp", NULL };
1191 	struct rrdprepo *rr;
1192 	FTS *fts;
1193 	FTSENT *e;
1194 
1195 	if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, NULL)) == NULL)
1196 		err(1, "fts_open");
1197 	errno = 0;
1198 	while ((e = fts_read(fts)) != NULL) {
1199 		switch (e->fts_info) {
1200 		case FTS_NSOK:
1201 			if (!filepath_exists(tree, e->fts_path))
1202 				del = add_to_del(del, &delsz,
1203 				    e->fts_path);
1204 			break;
1205 		case FTS_D:
1206 			/* special cleanup for rrdp directories */
1207 			if ((rr = rrdp_basedir(e->fts_path)) != NULL) {
1208 				del = repo_rrdp_cleanup(tree, rr, del, &delsz);
1209 				if (fts_set(fts, e, FTS_SKIP) == -1)
1210 					err(1, "fts_set");
1211 			}
1212 			break;
1213 		case FTS_DP:
1214 			if (!filepath_dir_exists(tree, e->fts_path))
1215 				dir = add_to_del(dir, &dirsz,
1216 				    e->fts_path);
1217 			break;
1218 		case FTS_SL:
1219 		case FTS_SLNONE:
1220 			warnx("symlink %s", e->fts_path);
1221 			del = add_to_del(del, &delsz, e->fts_path);
1222 			break;
1223 		case FTS_NS:
1224 		case FTS_ERR:
1225 			if (e->fts_errno == ENOENT &&
1226 			    (strcmp(e->fts_path, "rsync") == 0 ||
1227 			    strcmp(e->fts_path, "rrdp") == 0))
1228 				continue;
1229 			warnx("fts_read %s: %s", e->fts_path,
1230 			    strerror(e->fts_errno));
1231 			break;
1232 		default:
1233 			warnx("unhandled[%x] %s", e->fts_info,
1234 			    e->fts_path);
1235 			break;
1236 		}
1237 
1238 		errno = 0;
1239 	}
1240 	if (errno)
1241 		err(1, "fts_read");
1242 	if (fts_close(fts) == -1)
1243 		err(1, "fts_close");
1244 
1245 	cnt = 0;
1246 	for (i = 0; i < delsz; i++) {
1247 		if (unlink(del[i]) == -1) {
1248 			if (errno != ENOENT)
1249 				warn("unlink %s", del[i]);
1250 		} else {
1251 			if (verbose > 1)
1252 				logx("deleted %s", del[i]);
1253 			cnt++;
1254 		}
1255 		free(del[i]);
1256 	}
1257 	free(del);
1258 	stats.del_files = cnt;
1259 
1260 	cnt = 0;
1261 	for (i = 0; i < dirsz; i++) {
1262 		if (rmdir(dir[i]) == -1)
1263 			warn("rmdir %s", dir[i]);
1264 		else
1265 			cnt++;
1266 		free(dir[i]);
1267 	}
1268 	free(dir);
1269 	stats.del_dirs = cnt;
1270 }
1271 
1272 void
1273 repo_free(void)
1274 {
1275 	struct repo *rp;
1276 
1277 	while ((rp = SLIST_FIRST(&repos)) != NULL) {
1278 		SLIST_REMOVE_HEAD(&repos, entry);
1279 		free(rp->repouri);
1280 		free(rp);
1281 	}
1282 
1283 	ta_free();
1284 	rrdp_free();
1285 	rsync_free();
1286 }
1287