untrusted comment: verify with openbsd-70-base.pub
RWR3KL+gSr4QZ+zANqG+b3zakDQBYBiFF3uRBamy6h3cV6nRYo5CUosWyJfdTU/gdBwzfpKqkryx2RIyyZWpJjQAHWQ+YG4nkAk=

OpenBSD 7.0 errata 004, November 9, 2021:

rpki-client(8) should handle CA misbehaviours as soft-errors.

Apply by doing:
    signify -Vep /etc/signify/openbsd-70-base.pub -x 004_rpki.patch.sig \
        -m - | (cd /usr/src && patch -p0)

And then rebuild and install rpki-client and openrsync
    cd /usr/src/usr.sbin/rpki-client
    make obj
    make
    make install
    cd /usr/src/usr.bin/rsync
    make obj
    make
    make install

Index: usr.sbin/rpki-client/Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/Makefile,v
retrieving revision 1.21
diff -u -p -u -r1.21 Makefile
--- usr.sbin/rpki-client/Makefile	1 Apr 2021 16:04:48 -0000	1.21
+++ usr.sbin/rpki-client/Makefile	6 Nov 2021 18:43:16 -0000
@@ -3,9 +3,9 @@
 PROG=	rpki-client
 SRCS=	as.c cert.c cms.c crl.c encoding.c gbr.c http.c io.c ip.c log.c \
 	main.c mft.c mkdir.c output.c output-bgpd.c output-bird.c \
-	output-csv.c output-json.c parser.c repo.c roa.c rrdp.c rrdp_delta.c \
-	rrdp_notification.c rrdp_snapshot.c rsync.c tal.c validate.c \
-	x509.c
+	output-csv.c output-json.c parser.c print.c repo.c roa.c rrdp.c \
+	rrdp_delta.c rrdp_notification.c rrdp_snapshot.c rsync.c tal.c \
+	validate.c x509.c
 MAN=	rpki-client.8
 
 LDADD+= -lexpat -ltls -lssl -lcrypto -lutil
Index: usr.sbin/rpki-client/cert.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v
retrieving revision 1.32
diff -u -p -u -r1.32 cert.c
--- usr.sbin/rpki-client/cert.c	9 Sep 2021 14:15:49 -0000	1.32
+++ usr.sbin/rpki-client/cert.c	6 Nov 2021 18:43:32 -0000
@@ -1,5 +1,6 @@
 /*	$OpenBSD: cert.c,v 1.32 2021/09/09 14:15:49 claudio Exp $ */
 /*
+ * Copyright (c) 2021 Job Snijders <job@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -79,6 +80,8 @@ append_ip(struct parse *p, const struct 
 
 	if (!ip_addr_check_overlap(ip, p->fn, p->res->ips, p->res->ipsz))
 		return 0;
+	if (res->ipsz >= MAX_IP_SIZE)
+		return 0;
 	res->ips = reallocarray(res->ips, res->ipsz + 1,
 	    sizeof(struct cert_ip));
 	if (res->ips == NULL)
@@ -98,6 +101,8 @@ append_as(struct parse *p, const struct 
 
 	if (!as_check_overlap(as, p->fn, p->res->as, p->res->asz))
 		return 0;
+	if (p->res->asz >= MAX_AS_SIZE)
+		return 0;
 	p->res->as = reallocarray(p->res->as, p->res->asz + 1,
 	    sizeof(struct cert_as));
 	if (p->res->as == NULL)
@@ -975,36 +980,29 @@ out:
  * is also dereferenced.
  */
 static struct cert *
-cert_parse_inner(X509 **xp, const char *fn, int ta)
+cert_parse_inner(X509 **xp, const char *fn, const unsigned char *der,
+    size_t len, int ta)
 {
 	int		 rc = 0, extsz, c;
+	int		 sia_present = 0;
 	size_t		 i;
 	X509		*x = NULL;
 	X509_EXTENSION	*ext = NULL;
 	ASN1_OBJECT	*obj;
 	struct parse	 p;
-	BIO		*bio = NULL;
-	FILE		*f;
 
 	*xp = NULL;
 
-	if ((f = fopen(fn, "rb")) == NULL) {
-		warn("%s", fn);
+	/* just fail for empty buffers, the warning was printed elsewhere */
+	if (der == NULL)
 		return NULL;
-	}
-
-	if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
-		if (verbose > 0)
-			cryptowarnx("%s: BIO_new_file", fn);
-		return NULL;
-	}
 
 	memset(&p, 0, sizeof(struct parse));
 	p.fn = fn;
 	if ((p.res = calloc(1, sizeof(struct cert))) == NULL)
 		err(1, NULL);
 
-	if ((x = *xp = d2i_X509_bio(bio, NULL)) == NULL) {
+	if ((x = *xp = d2i_X509(NULL, &der, len)) == NULL) {
 		cryptowarnx("%s: d2i_X509_bio", p.fn);
 		goto out;
 	}
@@ -1029,6 +1027,7 @@ cert_parse_inner(X509 **xp, const char *
 			c = sbgp_assysnum(&p, ext);
 			break;
 		case NID_sinfo_access:
+			sia_present = 1;
 			c = sbgp_sia(&p, ext);
 			break;
 		case NID_crl_distribution_points:
@@ -1040,6 +1039,8 @@ cert_parse_inner(X509 **xp, const char *
 			break;
 		case NID_subject_key_identifier:
 			break;
+		case NID_ext_key_usage:
+			break;
 		default:
 			/* {
 				char objn[64];
@@ -1059,12 +1060,52 @@ cert_parse_inner(X509 **xp, const char *
 		p.res->aia = x509_get_aia(x, p.fn);
 		p.res->crl = x509_get_crl(x, p.fn);
 	}
+	if (!x509_get_expire(x, p.fn, &p.res->expires))
+		goto out;
+	p.res->purpose = x509_get_purpose(x, p.fn);
 
 	/* Validation on required fields. */
 
+	switch (p.res->purpose) {
+	case CERT_PURPOSE_CA:
+		if (p.res->mft == NULL) {
+			warnx("%s: RFC 6487 section 4.8.8: missing SIA", p.fn);
+			goto out;
+		}
+		if (p.res->asz == 0 && p.res->ipsz == 0) {
+			warnx("%s: missing IP or AS resources", p.fn);
+			goto out;
+		}
+		break;
+	case CERT_PURPOSE_BGPSEC_ROUTER:
+		p.res->pubkey = x509_get_pubkey(x, p.fn);
+		if (p.res->pubkey == NULL) {
+			warnx("%s: x509_get_pubkey failed", p.fn);
+			goto out;
+		}
+		if (p.res->ipsz > 0) {
+			warnx("%s: unexpected IP resources in BGPsec cert",
+			   p.fn);
+			goto out;
+		}
+		if (sia_present) {
+			warnx("%s: unexpected SIA extension in BGPsec cert",
+			   p.fn);
+			goto out;
+		}
+		if (ta) {
+			warnx("%s: BGPsec cert can not be a trust anchor",
+			   p.fn);
+			goto out;
+		}
+		break;
+	default:
+		warnx("%s: x509_get_purpose failed in %s", p.fn, __func__);
+		goto out;
+	}
+
 	if (p.res->ski == NULL) {
-		warnx("%s: RFC 6487 section 8.4.2: "
-		    "missing SKI", p.fn);
+		warnx("%s: RFC 6487 section 8.4.2: missing SKI", p.fn);
 		goto out;
 	}
 
@@ -1100,25 +1141,13 @@ cert_parse_inner(X509 **xp, const char *
 		goto out;
 	}
 
-	if (p.res->asz == 0 && p.res->ipsz == 0) {
-		warnx("%s: RFC 6487 section 4.8.10 and 4.8.11: "
-		    "missing IP or AS resources", p.fn);
-		goto out;
-	}
-
-	if (p.res->mft == NULL) {
-		warnx("%s: RFC 6487 section 4.8.8: "
-		    "missing SIA", p.fn);
-		goto out;
-	}
 	if (X509_up_ref(x) == 0)
-		errx(1, "king bula");
+		errx(1, "%s: X509_up_ref failed", __func__);
 
 	p.res->x509 = x;
 
 	rc = 1;
 out:
-	BIO_free_all(bio);
 	if (rc == 0) {
 		cert_free(p.res);
 		X509_free(x);
@@ -1128,19 +1157,20 @@ out:
 }
 
 struct cert *
-cert_parse(X509 **xp, const char *fn)
+cert_parse(X509 **xp, const char *fn, const unsigned char *der, size_t len)
 {
-	return cert_parse_inner(xp, fn, 0);
+	return cert_parse_inner(xp, fn, der, len, 0);
 }
 
 struct cert *
-ta_parse(X509 **xp, const char *fn, const unsigned char *pkey, size_t pkeysz)
+ta_parse(X509 **xp, const char *fn, const unsigned char *der, size_t len,
+    const unsigned char *pkey, size_t pkeysz)
 {
 	EVP_PKEY	*pk = NULL, *opk = NULL;
 	struct cert	*p;
 	int		 rc = 0;
 
-	if ((p = cert_parse_inner(xp, fn, 1)) == NULL)
+	if ((p = cert_parse_inner(xp, fn, der, len, 1)) == NULL)
 		return NULL;
 
 	if (pkey != NULL) {
@@ -1190,38 +1220,11 @@ cert_free(struct cert *p)
 	free(p->aia);
 	free(p->aki);
 	free(p->ski);
+	free(p->pubkey);
 	X509_free(p->x509);
 	free(p);
 }
 
-static void
-cert_ip_buffer(struct ibuf *b, const struct cert_ip *p)
-{
-	io_simple_buffer(b, &p->afi, sizeof(enum afi));
-	io_simple_buffer(b, &p->type, sizeof(enum cert_ip_type));
-
-	if (p->type != CERT_IP_INHERIT) {
-		io_simple_buffer(b, &p->min, sizeof(p->min));
-		io_simple_buffer(b, &p->max, sizeof(p->max));
-	}
-
-	if (p->type == CERT_IP_RANGE)
-		ip_addr_range_buffer(b, &p->range);
-	else if (p->type == CERT_IP_ADDR)
-		ip_addr_buffer(b, &p->ip);
-}
-
-static void
-cert_as_buffer(struct ibuf *b, const struct cert_as *p)
-{
-	io_simple_buffer(b, &p->type, sizeof(enum cert_as_type));
-	if (p->type == CERT_AS_RANGE) {
-		io_simple_buffer(b, &p->range.min, sizeof(uint32_t));
-		io_simple_buffer(b, &p->range.max, sizeof(uint32_t));
-	} else if (p->type == CERT_AS_ID)
-		io_simple_buffer(b, &p->id, sizeof(uint32_t));
-}
-
 /*
  * Write certificate parsed content into buffer.
  * See cert_read() for the other side of the pipe.
@@ -1229,16 +1232,14 @@ cert_as_buffer(struct ibuf *b, const str
 void
 cert_buffer(struct ibuf *b, const struct cert *p)
 {
-	size_t	 i;
+	io_simple_buffer(b, &p->expires, sizeof(p->expires));
+	io_simple_buffer(b, &p->purpose, sizeof(p->purpose));
+	io_simple_buffer(b, &p->talid, sizeof(p->talid));
+	io_simple_buffer(b, &p->ipsz, sizeof(p->ipsz));
+	io_simple_buffer(b, &p->asz, sizeof(p->asz));
 
-	io_simple_buffer(b, &p->valid, sizeof(int));
-	io_simple_buffer(b, &p->ipsz, sizeof(size_t));
-	for (i = 0; i < p->ipsz; i++)
-		cert_ip_buffer(b, &p->ips[i]);
-
-	io_simple_buffer(b, &p->asz, sizeof(size_t));
-	for (i = 0; i < p->asz; i++)
-		cert_as_buffer(b, &p->as[i]);
+	io_simple_buffer(b, p->ips, p->ipsz * sizeof(p->ips[0]));
+	io_simple_buffer(b, p->as, p->asz * sizeof(p->as[0]));
 
 	io_str_buffer(b, p->mft);
 	io_str_buffer(b, p->notify);
@@ -1247,36 +1248,7 @@ cert_buffer(struct ibuf *b, const struct
 	io_str_buffer(b, p->aia);
 	io_str_buffer(b, p->aki);
 	io_str_buffer(b, p->ski);
-}
-
-static void
-cert_ip_read(int fd, struct cert_ip *p)
-{
-
-	io_simple_read(fd, &p->afi, sizeof(enum afi));
-	io_simple_read(fd, &p->type, sizeof(enum cert_ip_type));
-
-	if (p->type != CERT_IP_INHERIT) {
-		io_simple_read(fd, &p->min, sizeof(p->min));
-		io_simple_read(fd, &p->max, sizeof(p->max));
-	}
-
-	if (p->type == CERT_IP_RANGE)
-		ip_addr_range_read(fd, &p->range);
-	else if (p->type == CERT_IP_ADDR)
-		ip_addr_read(fd, &p->ip);
-}
-
-static void
-cert_as_read(int fd, struct cert_as *p)
-{
-
-	io_simple_read(fd, &p->type, sizeof(enum cert_as_type));
-	if (p->type == CERT_AS_RANGE) {
-		io_simple_read(fd, &p->range.min, sizeof(uint32_t));
-		io_simple_read(fd, &p->range.max, sizeof(uint32_t));
-	} else if (p->type == CERT_AS_ID)
-		io_simple_read(fd, &p->id, sizeof(uint32_t));
+	io_str_buffer(b, p->pubkey);
 }
 
 /*
@@ -1285,39 +1257,40 @@ cert_as_read(int fd, struct cert_as *p)
  * Always returns a valid pointer.
  */
 struct cert *
-cert_read(int fd)
+cert_read(struct ibuf *b)
 {
 	struct cert	*p;
-	size_t		 i;
 
 	if ((p = calloc(1, sizeof(struct cert))) == NULL)
 		err(1, NULL);
 
-	io_simple_read(fd, &p->valid, sizeof(int));
-	io_simple_read(fd, &p->ipsz, sizeof(size_t));
+	io_read_buf(b, &p->expires, sizeof(p->expires));
+	io_read_buf(b, &p->purpose, sizeof(p->purpose));
+	io_read_buf(b, &p->talid, sizeof(p->talid));
+	io_read_buf(b, &p->ipsz, sizeof(p->ipsz));
+	io_read_buf(b, &p->asz, sizeof(p->asz));
+
 	p->ips = calloc(p->ipsz, sizeof(struct cert_ip));
 	if (p->ips == NULL)
 		err(1, NULL);
-	for (i = 0; i < p->ipsz; i++)
-		cert_ip_read(fd, &p->ips[i]);
+	io_read_buf(b, p->ips, p->ipsz * sizeof(p->ips[0]));
 
-	io_simple_read(fd, &p->asz, sizeof(size_t));
 	p->as = calloc(p->asz, sizeof(struct cert_as));
 	if (p->as == NULL)
 		err(1, NULL);
-	for (i = 0; i < p->asz; i++)
-		cert_as_read(fd, &p->as[i]);
+	io_read_buf(b, p->as, p->asz * sizeof(p->as[0]));
 
-	io_str_read(fd, &p->mft);
-	assert(p->mft);
-	io_str_read(fd, &p->notify);
-	io_str_read(fd, &p->repo);
-	io_str_read(fd, &p->crl);
-	io_str_read(fd, &p->aia);
-	io_str_read(fd, &p->aki);
-	io_str_read(fd, &p->ski);
-	assert(p->ski);
+	io_read_str(b, &p->mft);
+	io_read_str(b, &p->notify);
+	io_read_str(b, &p->repo);
+	io_read_str(b, &p->crl);
+	io_read_str(b, &p->aia);
+	io_read_str(b, &p->aki);
+	io_read_str(b, &p->ski);
+	io_read_str(b, &p->pubkey);
 
+	assert(p->mft != NULL || p->purpose == CERT_PURPOSE_BGPSEC_ROUTER);
+	assert(p->ski);
 	return p;
 }
 
@@ -1334,6 +1307,24 @@ auth_find(struct auth_tree *auths, const
 	return RB_FIND(auth_tree, auths, &a);
 }
 
+int
+auth_insert(struct auth_tree *auths, struct cert *cert, struct auth *parent)
+{
+	struct auth *na;
+
+	na = malloc(sizeof(*na));
+	if (na == NULL)
+		err(1, NULL);
+
+	na->parent = parent;
+	na->cert = cert;
+
+	if (RB_INSERT(auth_tree, auths, na) != NULL)
+		err(1, "auth tree corrupted");
+
+	return 1;
+}
+
 static inline int
 authcmp(struct auth *a, struct auth *b)
 {
@@ -1341,3 +1332,80 @@ authcmp(struct auth *a, struct auth *b)
 }
 
 RB_GENERATE(auth_tree, auth, entry, authcmp);
+
+static void
+insert_brk(struct brk_tree *tree, struct cert *cert, int asid)
+{
+	struct brk	*b, *found;
+
+	if ((b = calloc(1, sizeof(*b))) == NULL)
+		err(1, NULL);
+
+	b->asid = asid;
+	b->expires = cert->expires;
+	b->talid = cert->talid;
+	if ((b->ski = strdup(cert->ski)) == NULL)
+		err(1, NULL);
+	if ((b->pubkey = strdup(cert->pubkey)) == NULL)
+		err(1, NULL);
+
+	/*
+	 * Check if a similar BRK already exists in the tree. If the found BRK
+	 * expires sooner, update it to this BRK's later expiry moment.
+	 */
+	if ((found = RB_INSERT(brk_tree, tree, b)) != NULL) {
+		if (found->expires < b->expires) {
+			found->expires = b->expires;
+			found->talid = b->talid;
+		}
+		free(b->ski);
+		free(b->pubkey);
+		free(b);
+	}
+}
+
+/*
+ * Add each BGPsec Router Key into the BRK tree.
+ */
+void
+cert_insert_brks(struct brk_tree *tree, struct cert *cert)
+{
+	size_t		 i, asid;
+
+	for (i = 0; i < cert->asz; i++) {
+		switch (cert->as[i].type) {
+		case CERT_AS_ID:
+			insert_brk(tree, cert, cert->as[i].id);
+			break;
+		case CERT_AS_RANGE:
+			for (asid = cert->as[i].range.min;
+			    asid <= cert->as[i].range.max; asid++)
+				insert_brk(tree, cert, asid);
+			break;
+		default:
+			warnx("invalid AS identifier type");
+			continue;
+		}
+	}
+}
+
+static inline int
+brkcmp(struct brk *a, struct brk *b)
+{
+	int rv;
+
+	if (a->asid > b->asid)
+		return 1;
+	if (a->asid < b->asid)
+		return -1;
+
+	rv = strcmp(a->ski, b->ski);
+	if (rv > 0)
+		return 1;
+	if (rv < 0)
+		return -1;
+
+	return strcmp(a->pubkey, b->pubkey);
+}
+
+RB_GENERATE(brk_tree, brk, entry, brkcmp);
Index: usr.sbin/rpki-client/cms.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cms.c,v
retrieving revision 1.10
diff -u -p -u -r1.10 cms.c
--- usr.sbin/rpki-client/cms.c	9 Sep 2021 14:15:49 -0000	1.10
+++ usr.sbin/rpki-client/cms.c	6 Nov 2021 18:43:45 -0000
@@ -35,14 +35,12 @@
  * Return the eContent as a string and set "rsz" to be its length.
  */
 unsigned char *
-cms_parse_validate(X509 **xp, const char *fn, const ASN1_OBJECT *oid,
-    size_t *rsz)
+cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der,
+    size_t derlen, const ASN1_OBJECT *oid, size_t *rsz)
 {
 	const ASN1_OBJECT	*obj;
 	ASN1_OCTET_STRING	**os = NULL;
-	BIO			*bio = NULL;
 	CMS_ContentInfo		*cms;
-	FILE			*f;
 	int			 rc = 0;
 	STACK_OF(X509)		*certs = NULL;
 	unsigned char		*res = NULL;
@@ -50,21 +48,11 @@ cms_parse_validate(X509 **xp, const char
 	*rsz = 0;
 	*xp = NULL;
 
-	/*
-	 * This is usually fopen() failure, so let it pass through to
-	 * the handler, which will in turn ignore the entity.
-	 */
-	if ((f = fopen(fn, "rb")) == NULL) {
-		warn("%s", fn);
-		return NULL;
-	}
-
-	if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
-		cryptowarnx("%s: BIO_new_fp", fn);
+	/* just fail for empty buffers, the warning was printed elsewhere */
+	if (der == NULL)
 		return NULL;
-	}
 
-	if ((cms = d2i_CMS_bio(bio, NULL)) == NULL) {
+	if ((cms = d2i_CMS_ContentInfo(NULL, &der, derlen)) == NULL) {
 		cryptowarnx("%s: RFC 6488: failed CMS parse", fn);
 		goto out;
 	}
@@ -74,8 +62,8 @@ cms_parse_validate(X509 **xp, const char
 	 * Verify that the self-signage is correct.
 	 */
 
-	if (!CMS_verify(cms, NULL, NULL,
-	    NULL, NULL, CMS_NO_SIGNER_CERT_VERIFY)) {
+	if (!CMS_verify(cms, NULL, NULL, NULL, NULL,
+	    CMS_NO_SIGNER_CERT_VERIFY)) {
 		cryptowarnx("%s: RFC 6488: CMS not self-signed", fn);
 		goto out;
 	}
@@ -134,7 +122,6 @@ cms_parse_validate(X509 **xp, const char
 
 	rc = 1;
 out:
-	BIO_free_all(bio);
 	sk_X509_free(certs);
 	CMS_ContentInfo_free(cms);
 
Index: usr.sbin/rpki-client/crl.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/crl.c,v
retrieving revision 1.10
diff -u -p -u -r1.10 crl.c
--- usr.sbin/rpki-client/crl.c	29 Jan 2021 10:13:16 -0000	1.10
+++ usr.sbin/rpki-client/crl.c	6 Nov 2021 18:43:55 -0000
@@ -29,32 +29,22 @@
 #include "extern.h"
 
 X509_CRL *
-crl_parse(const char *fn)
+crl_parse(const char *fn, const unsigned char *der, size_t len)
 {
 	int		 rc = 0;
 	X509_CRL	*x = NULL;
-	BIO		*bio = NULL;
-	FILE		*f;
 
-	if ((f = fopen(fn, "rb")) == NULL) {
-		warn("%s", fn);
+	/* just fail for empty buffers, the warning was printed elsewhere */
+	if (der == NULL)
 		return NULL;
-	}
-
-	if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
-		if (verbose > 0)
-			cryptowarnx("%s: BIO_new_file", fn);
-		return NULL;
-	}
 
-	if ((x = d2i_X509_CRL_bio(bio, NULL)) == NULL) {
-		cryptowarnx("%s: d2i_X509_CRL_bio", fn);
+	if ((x = d2i_X509_CRL(NULL, &der, len)) == NULL) {
+		cryptowarnx("%s: d2i_X509_CRL", fn);
 		goto out;
 	}
 
 	rc = 1;
 out:
-	BIO_free_all(bio);
 	if (rc == 0) {
 		X509_CRL_free(x);
 		x = NULL;
Index: usr.sbin/rpki-client/encoding.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/encoding.c,v
retrieving revision 1.3
diff -u -p -u -r1.3 encoding.c
--- usr.sbin/rpki-client/encoding.c	1 Sep 2021 08:09:41 -0000	1.3
+++ usr.sbin/rpki-client/encoding.c	6 Nov 2021 18:44:02 -0000
@@ -14,27 +14,91 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#include <sys/stat.h>
+
 #include <err.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include <openssl/evp.h>
 
 #include "extern.h"
 
 /*
+ * Load file from disk and return the buffer and size.
+ */
+unsigned char *
+load_file(const char *name, size_t *len)
+{
+	unsigned char *buf = NULL;
+	struct stat st;
+	ssize_t n;
+	size_t size;
+	int fd, saved_errno;
+
+	*len = 0;
+
+	if ((fd = open(name, O_RDONLY)) == -1)
+		return NULL;
+	if (fstat(fd, &st) != 0)
+		goto err;
+	if (st.st_size <= 0 || st.st_size > MAX_FILE_SIZE) {
+		errno = EFBIG;
+		goto err;
+	}
+	size = (size_t)st.st_size;
+	if ((buf = malloc(size)) == NULL)
+		goto err;
+	n = read(fd, buf, size);
+	if (n == -1)
+		goto err;
+	if ((size_t)n != size) {
+		errno = EIO;
+		goto err;
+	}
+	close(fd);
+	*len = size;
+	return buf;
+
+err:
+	saved_errno = errno;
+	close(fd);
+	free(buf);
+	errno = saved_errno;
+	return NULL;
+}
+
+/*
+ * Return the size of the data blob in outlen for an inlen sized base64 buffer.
+ * Returns 0 on success and -1 if inlen would overflow an int.
+ */
+int
+base64_decode_len(size_t inlen, size_t *outlen)
+{
+	*outlen = 0;
+	if (inlen >= INT_MAX - 3)
+		return -1;
+	*outlen = ((inlen + 3) / 4) * 3 + 1;
+	return 0;
+}
+
+/*
  * Decode base64 encoded string into binary buffer returned in out.
  * The out buffer size is stored in outlen.
  * Returns 0 on success or -1 for any errors.
  */
 int
-base64_decode(const unsigned char *in, unsigned char **out, size_t *outlen)
+base64_decode(const unsigned char *in, size_t inlen,
+    unsigned char **out, size_t *outlen)
 {
 	static EVP_ENCODE_CTX *ctx;
 	unsigned char *to;
-	size_t inlen;
-	int tolen;
+	size_t tolen;
+	int evplen;
 
 	if (ctx == NULL && (ctx = EVP_ENCODE_CTX_new()) == NULL)
 		err(1, "EVP_ENCODE_CTX_new");
@@ -42,20 +106,19 @@ base64_decode(const unsigned char *in, u
 	*out = NULL;
 	*outlen = 0;
 
-	inlen = strlen(in);
-	if (inlen >= INT_MAX - 3)
+	if (base64_decode_len(inlen, &tolen) == -1)
 		return -1;
-	tolen = ((inlen + 3) / 4) * 3 + 1;
 	if ((to = malloc(tolen)) == NULL)
 		return -1;
 
+	evplen = tolen;
 	EVP_DecodeInit(ctx);
-	if (EVP_DecodeUpdate(ctx, to, &tolen, in, inlen) == -1)
+	if (EVP_DecodeUpdate(ctx, to, &evplen, in, inlen) == -1)
 		goto fail;
-	*outlen = tolen;
-	if (EVP_DecodeFinal(ctx, to + tolen, &tolen) == -1)
+	*outlen = evplen;
+	if (EVP_DecodeFinal(ctx, to + evplen, &evplen) == -1)
 		goto fail;
-	*outlen += tolen;
+	*outlen += evplen;
 	*out = to;
 	return 0;
 
@@ -64,34 +127,40 @@ fail:
 	return -1;
 }
 
+/*
+ * Return the size of the base64 blob in outlen for a inlen sized binary buffer.
+ * Returns 0 on success and -1 if inlen would overflow the calculation.
+ */
+int
+base64_encode_len(size_t inlen, size_t *outlen)
+{
+	*outlen = 0;
+	if (inlen >= INT_MAX / 2)
+		return -1;
+	*outlen = ((inlen + 2) / 3) * 4 + 1;
+	return 0;
+}
+
+/*
+ * Encode a binary buffer into a base64 encoded string returned in out.
+ * Returns 0 on success or -1 for any errors.
+ */
 int
 base64_encode(const unsigned char *in, size_t inlen, char **out)
 {
-	static EVP_ENCODE_CTX *ctx;
 	unsigned char *to;
-	int tolen;
-
-	if (ctx == NULL && (ctx = EVP_ENCODE_CTX_new()) == NULL)
-		err(1, "EVP_ENCODE_CTX_new");
+	size_t tolen;
 
 	*out = NULL;
 
-	if (inlen >= INT_MAX / 2)
+	if (base64_encode_len(inlen, &tolen) == -1)
 		return -1;
-	tolen = ((inlen + 2) / 3) * 4 + 1;
 	if ((to = malloc(tolen)) == NULL)
 		return -1;
 
-	EVP_EncodeInit(ctx);
-	if (EVP_EncodeUpdate(ctx, to, &tolen, in, inlen) != 1)
-		goto fail;
-	EVP_EncodeFinal(ctx, to + tolen, &tolen);
+	EVP_EncodeBlock(to, in, inlen);
 	*out = to;
 	return 0;
-
-fail:
-	free(to);
-	return -1;
 }
 
 /*
Index: usr.sbin/rpki-client/extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
retrieving revision 1.67
diff -u -p -u -r1.67 extern.h
--- usr.sbin/rpki-client/extern.h	9 Sep 2021 14:15:49 -0000	1.67
+++ usr.sbin/rpki-client/extern.h	6 Nov 2021 18:44:10 -0000
@@ -101,6 +101,12 @@ struct cert_ip {
 	};
 };
 
+enum cert_purpose {
+	CERT_PURPOSE_INVALID,
+	CERT_PURPOSE_CA,
+	CERT_PURPOSE_BGPSEC_ROUTER
+};
+
 /*
  * Parsed components of a validated X509 certificate stipulated by RFC
  * 6847 and further (within) by RFC 3779.
@@ -112,6 +118,7 @@ struct cert {
 	size_t		 ipsz; /* length of "ips" */
 	struct cert_as	*as; /* list of AS numbers and ranges */
 	size_t		 asz; /* length of "asz" */
+	int		 talid; /* cert is covered by which TAL */
 	char		*repo; /* CA repository (rsync:// uri) */
 	char		*mft; /* manifest (rsync:// uri) */
 	char		*notify; /* RRDP notify (https:// uri) */
@@ -119,8 +126,10 @@ struct cert {
 	char		*aia; /* AIA (or NULL, for trust anchor) */
 	char		*aki; /* AKI (or NULL, for trust anchor) */
 	char		*ski; /* SKI */
-	int		 valid; /* validated resources */
+	enum cert_purpose	 purpose; /* BGPSec or CA */
+	char		*pubkey; /* Subject Public Key Info */
 	X509		*x509; /* the cert */
+	time_t		 expires; /* do not use after */
 };
 
 /*
@@ -136,6 +145,7 @@ struct tal {
 	unsigned char	*pkey; /* DER-encoded public key */
 	size_t		 pkeysz; /* length of pkey */
 	char		*descr; /* basename of tal file */
+	int		 id; /* ID of this TAL */
 };
 
 /*
@@ -183,11 +193,11 @@ struct roa {
 	uint32_t	 asid; /* asID of ROA (if 0, RFC 6483 sec 4) */
 	struct roa_ip	*ips; /* IP prefixes */
 	size_t		 ipsz; /* number of IP prefixes */
+	int		 talid; /* ROAs are covered by which TAL */
 	int		 valid; /* validated resources */
 	char		*aia; /* AIA */
 	char		*aki; /* AKI */
 	char		*ski; /* SKI */
-	char		*tal; /* basename of TAL for this cert */
 	time_t		 expires; /* do not use after */
 };
 
@@ -207,8 +217,8 @@ struct gbr {
 struct vrp {
 	RB_ENTRY(vrp)	entry;
 	struct ip_addr	addr;
+	int		talid; /* covered by which TAL */
 	uint32_t	asid;
-	char		*tal; /* basename of TAL for this cert */
 	enum afi	afi;
 	unsigned char	maxlength;
 	time_t		expires; /* transitive expiry moment */
@@ -220,12 +230,30 @@ RB_HEAD(vrp_tree, vrp);
 RB_PROTOTYPE(vrp_tree, vrp, entry, vrpcmp);
 
 /*
+ * A single BGPsec Router Key (including ASID)
+ */
+struct brk {
+	RB_ENTRY(brk)	 entry;
+	uint32_t	 asid;
+	int		 talid; /* covered by which TAL */
+	char		*ski; /* Subject Key Identifier */
+	char		*pubkey; /* Subject Public Key Info */
+	time_t		 expires; /* transitive expiry moment */
+};
+/*
+ * Tree of BRK sorted by asid
+ */
+RB_HEAD(brk_tree, brk);
+RB_PROTOTYPE(brk_tree, brk, entry, brkcmp);
+
+/*
  * A single CRL
  */
 struct crl {
 	RB_ENTRY(crl)	 entry;
 	char		*aki;
 	X509_CRL	*x509_crl;
+	time_t		 expires; /* do not use after */
 };
 /*
  * Tree of CRLs sorted by uri
@@ -242,8 +270,6 @@ struct auth {
 	RB_ENTRY(auth)	 entry;
 	struct cert	*cert; /* owner information */
 	struct auth	*parent; /* pointer to parent or NULL for TA cert */
-	char		*tal; /* basename of TAL for this cert */
-	char		*fn; /* FIXME: debugging */
 };
 /*
  * Tree of auth sorted by ski
@@ -251,7 +277,8 @@ struct auth {
 RB_HEAD(auth_tree, auth);
 RB_PROTOTYPE(auth_tree, auth, entry, authcmp);
 
-struct auth *auth_find(struct auth_tree *, const char *);
+struct auth	*auth_find(struct auth_tree *, const char *);
+int		 auth_insert(struct auth_tree *, struct cert *, struct auth *);
 
 /*
  * Resource types specified by the RPKI profiles.
@@ -308,13 +335,13 @@ enum publish_type {
  * An entity (MFT, ROA, certificate, etc.) that needs to be downloaded
  * and parsed.
  */
-struct	entity {
-	enum rtype	 type; /* type of entity (not RTYPE_EOF) */
-	char		*file; /* local path to file */
-	int		 has_pkey; /* whether pkey/sz is specified */
-	unsigned char	*pkey; /* public key (optional) */
-	size_t		 pkeysz; /* public key length (optional) */
-	char		*descr; /* tal description */
+struct entity {
+	enum rtype	 type;		/* type of entity (not RTYPE_EOF) */
+	char		*file;		/* local path to file */
+	int		 has_data;	/* whether data blob is specified */
+	unsigned char	*data;		/* optional data blob */
+	size_t		 datasz; 	/* length of optional data blob */
+	int		 talid;		/* tal identifier */
 	TAILQ_ENTRY(entity) entries;
 };
 TAILQ_HEAD(entityq, entity);
@@ -327,14 +354,13 @@ RB_HEAD(filepath_tree, filepath);
 /*
  * Statistics collected during run-time.
  */
-struct	stats {
+struct stats {
 	size_t	 tals; /* total number of locators */
 	size_t	 mfts; /* total number of manifests */
 	size_t	 mfts_fail; /* failing syntactic parse */
 	size_t	 mfts_stale; /* stale manifests */
 	size_t	 certs; /* certificates */
-	size_t	 certs_fail; /* failing syntactic parse */
-	size_t	 certs_invalid; /* invalid resources */
+	size_t	 certs_fail; /* invalid certificate */
 	size_t	 roas; /* route origin authorizations */
 	size_t	 roas_fail; /* failing syntactic parse */
 	size_t	 roas_invalid; /* invalid resources */
@@ -351,49 +377,59 @@ struct	stats {
 	size_t	 uniqs; /* number of unique vrps */
 	size_t	 del_files; /* number of files removed in cleanup */
 	size_t	 del_dirs; /* number of directories removed in cleanup */
-	char	*talnames;
+	size_t	 brks; /* number of BGPsec Router Key (BRK) certificates */
 	struct timeval	elapsed_time;
 	struct timeval	user_time;
 	struct timeval	system_time;
 };
 
 struct ibuf;
+struct msgbuf;
 
 /* global variables */
 extern int verbose;
+extern const char *tals[];
+extern const char *taldescs[];
+extern unsigned int talrepocnt[];
+extern size_t talsz;
 
 /* Routines for RPKI entities. */
 
 void		 tal_buffer(struct ibuf *, const struct tal *);
 void		 tal_free(struct tal *);
-struct tal	*tal_parse(const char *, char *);
-char		*tal_read_file(const char *);
-struct tal	*tal_read(int);
+struct tal	*tal_parse(const char *, char *, size_t);
+struct tal	*tal_read(struct ibuf *);
 
 void		 cert_buffer(struct ibuf *, const struct cert *);
 void		 cert_free(struct cert *);
-struct cert	*cert_parse(X509 **, const char *);
-struct cert	*ta_parse(X509 **, const char *, const unsigned char *, size_t);
-struct cert	*cert_read(int);
+struct cert	*cert_parse(X509 **, const char *, const unsigned char *,
+		    size_t);
+struct cert	*ta_parse(X509 **, const char *, const unsigned char *, size_t,
+		    const unsigned char *, size_t);
+struct cert	*cert_read(struct ibuf *);
+void		 cert_insert_brks(struct brk_tree *, struct cert *);
 
 void		 mft_buffer(struct ibuf *, const struct mft *);
 void		 mft_free(struct mft *);
-struct mft	*mft_parse(X509 **, const char *);
+struct mft	*mft_parse(X509 **, const char *, const unsigned char *,
+		    size_t);
 int		 mft_check(const char *, struct mft *);
-struct mft	*mft_read(int);
+struct mft	*mft_read(struct ibuf *);
 
 void		 roa_buffer(struct ibuf *, const struct roa *);
 void		 roa_free(struct roa *);
-struct roa	*roa_parse(X509 **, const char *);
-struct roa	*roa_read(int);
+struct roa	*roa_parse(X509 **, const char *, const unsigned char *,
+		    size_t);
+struct roa	*roa_read(struct ibuf *);
 void		 roa_insert_vrps(struct vrp_tree *, struct roa *, size_t *,
 		    size_t *);
 
 void		 gbr_free(struct gbr *);
-struct gbr	*gbr_parse(X509 **, const char *);
+struct gbr	*gbr_parse(X509 **, const char *, const unsigned char *,
+		    size_t);
 
 /* crl.c */
-X509_CRL	*crl_parse(const char *);
+X509_CRL	*crl_parse(const char *, const unsigned char *, size_t);
 void		 free_crl(struct crl *);
 
 /* Validation of our objects. */
@@ -405,11 +441,14 @@ int		 valid_ta(const char *, struct auth
 int		 valid_cert(const char *, struct auth_tree *,
 		    const struct cert *);
 int		 valid_roa(const char *, struct auth_tree *, struct roa *);
+int		 valid_filename(const char *);
 int		 valid_filehash(const char *, const char *, size_t);
 int		 valid_uri(const char *, size_t, const char *);
+int		 valid_origin(const char *, const char *);
 
 /* Working with CMS. */
 unsigned char	*cms_parse_validate(X509 **, const char *,
+		    const unsigned char *, size_t,
 		    const ASN1_OBJECT *, size_t *);
 int		 cms_econtent_version(const char *, const unsigned char **,
 		    size_t, long *);
@@ -425,11 +464,6 @@ int		 ip_addr_parse(const ASN1_BIT_STRIN
 			enum afi, const char *, struct ip_addr *);
 void		 ip_addr_print(const struct ip_addr *, enum afi, char *,
 			size_t);
-void		 ip_addr_buffer(struct ibuf *, const struct ip_addr *);
-void		 ip_addr_range_buffer(struct ibuf *,
-			const struct ip_addr_range *);
-void		 ip_addr_read(int, struct ip_addr *);
-void		 ip_addr_range_read(int, struct ip_addr_range *);
 int		 ip_addr_cmp(const struct ip_addr *, const struct ip_addr *);
 int		 ip_addr_check_overlap(const struct cert_ip *,
 			const char *, const struct cert_ip *, size_t);
@@ -448,7 +482,7 @@ int		 as_check_covered(uint32_t, uint32_
 
 /* Parser-specific */
 void		 entity_free(struct entity *);
-void		 entity_read_req(int fd, struct entity *);
+void		 entity_read_req(struct ibuf *, struct entity *);
 void		 entityq_flush(struct entityq *, struct repo *);
 void		 proc_parser(int) __attribute__((noreturn));
 
@@ -468,8 +502,8 @@ void		 rrdp_save_state(size_t, struct rr
 int		 rrdp_handle_file(size_t, enum publish_type, char *,
 		    char *, size_t, char *, size_t);
 char		*repo_filename(const struct repo *, const char *);
-struct repo	*ta_lookup(struct tal *);
-struct repo	*repo_lookup(const char *, const char *);
+struct repo	*ta_lookup(int, struct tal *);
+struct repo	*repo_lookup(int, const char *, const char *);
 int		 repo_queued(struct repo *, struct entity *);
 void		 repo_cleanup(struct filepath_tree *);
 void		 repo_free(void);
@@ -484,6 +518,8 @@ void		 rrdp_fetch(size_t, const char *, 
 		    struct rrdp_session *);
 void		 rrdp_http_done(size_t, enum http_result, const char *);
 
+int		 repo_next_timeout(int);
+void		 repo_check_timeout(void);
 
 /* Logging (though really used for OpenSSL errors). */
 
@@ -495,32 +531,45 @@ void		 cryptoerrx(const char *, ...)
 
 /* Encoding functions for hex and base64. */
 
-int		 base64_decode(const unsigned char *, unsigned char **,
-		    size_t *);
+unsigned char	*load_file(const char *, size_t *);
+int		 base64_decode_len(size_t, size_t *);
+int		 base64_decode(const unsigned char *, size_t,
+		    unsigned char **, size_t *);
+int		 base64_encode_len(size_t, size_t *);
 int		 base64_encode(const unsigned char *, size_t, char **);
 char		*hex_encode(const unsigned char *, size_t);
 
 
 /* Functions for moving data between processes. */
 
-void		 io_socket_blocking(int);
-void		 io_socket_nonblocking(int);
+struct ibuf	*io_new_buffer(void);
 void		 io_simple_buffer(struct ibuf *, const void *, size_t);
 void		 io_buf_buffer(struct ibuf *, const void *, size_t);
 void		 io_str_buffer(struct ibuf *, const char *);
-void		 io_simple_read(int, void *, size_t);
-void		 io_buf_read_alloc(int, void **, size_t *);
-void		 io_str_read(int, char **);
-int		 io_recvfd(int, void *, size_t);
+void		 io_close_buffer(struct msgbuf *, struct ibuf *);
+void		 io_read_buf(struct ibuf *, void *, size_t);
+void		 io_read_str(struct ibuf *, char **);
+void		 io_read_buf_alloc(struct ibuf *, void **, size_t *);
+struct ibuf	*io_buf_read(int, struct ibuf **);
+struct ibuf	*io_buf_recvfd(int, struct ibuf **);
 
 /* X509 helpers. */
 
-char		*hex_encode(const unsigned char *, size_t);
 char		*x509_get_aia(X509 *, const char *);
 char		*x509_get_aki(X509 *, int, const char *);
 char		*x509_get_ski(X509 *, const char *);
+int		 x509_get_expire(X509 *, const char *, time_t *);
 char		*x509_get_crl(X509 *, const char *);
 char		*x509_crl_get_aki(X509_CRL *, const char *);
+char		*x509_get_pubkey(X509 *, const char *);
+enum cert_purpose	 x509_get_purpose(X509 *, const char *);
+
+/* printers */
+void		tal_print(const struct tal *);
+void		cert_print(const struct cert *);
+void		mft_print(const struct mft *);
+void		roa_print(const struct roa *);
+void		gbr_print(const struct gbr *);
 
 /* Output! */
 
@@ -530,21 +579,54 @@ extern int	 outformats;
 #define FORMAT_CSV	0x04
 #define FORMAT_JSON	0x08
 
-int		 outputfiles(struct vrp_tree *v, struct stats *);
+int		 outputfiles(struct vrp_tree *v, struct brk_tree *b,
+		    struct stats *);
 int		 outputheader(FILE *, struct stats *);
-int		 output_bgpd(FILE *, struct vrp_tree *, struct stats *);
-int		 output_bird1v4(FILE *, struct vrp_tree *, struct stats *);
-int		 output_bird1v6(FILE *, struct vrp_tree *, struct stats *);
-int		 output_bird2(FILE *, struct vrp_tree *, struct stats *);
-int		 output_csv(FILE *, struct vrp_tree *, struct stats *);
-int		 output_json(FILE *, struct vrp_tree *, struct stats *);
+int		 output_bgpd(FILE *, struct vrp_tree *, struct brk_tree *,
+		    struct stats *);
+int		 output_bird1v4(FILE *, struct vrp_tree *, struct brk_tree *,
+		    struct stats *);
+int		 output_bird1v6(FILE *, struct vrp_tree *, struct brk_tree *,
+		    struct stats *);
+int		 output_bird2(FILE *, struct vrp_tree *, struct brk_tree *,
+		    struct stats *);
+int		 output_csv(FILE *, struct vrp_tree *, struct brk_tree *,
+		    struct stats *);
+int		 output_json(FILE *, struct vrp_tree *, struct brk_tree *,
+		    struct stats *);
 
-void	logx(const char *fmt, ...)
+void		logx(const char *fmt, ...)
 		    __attribute__((format(printf, 1, 2)));
+time_t		getmonotime(void);
 
 int	mkpath(const char *);
 
-#define		RPKI_PATH_OUT_DIR	"/var/db/rpki-client"
-#define		RPKI_PATH_BASE_DIR	"/var/cache/rpki-client"
+#define RPKI_PATH_OUT_DIR	"/var/db/rpki-client"
+#define RPKI_PATH_BASE_DIR	"/var/cache/rpki-client"
+
+/* Maximum number of IP and AS ranges accepted in any single file */
+#define MAX_IP_SIZE		200000
+#define MAX_AS_SIZE		200000
+
+/* Maximum acceptable URI length */
+#define MAX_URI_LENGTH		2048
+
+/* Maximum acceptable file size */
+#define MAX_FILE_SIZE		2000000
+
+/* Maximum number of FileAndHash entries per manifest. */
+#define MAX_MANIFEST_ENTRIES	100000
+
+/* Maximum depth of the RPKI tree. */
+#define MAX_CERT_DEPTH		12
+
+/* Maximum number of concurrent rsync processes. */
+#define MAX_RSYNC_PROCESSES	16
+
+/* Maximum allowd repositories per tal */
+#define MAX_REPO_PER_TAL	1000
+
+/* Timeout for repository synchronisation, in seconds */
+#define MAX_REPO_TIMEOUT	(15 * 60)
 
 #endif /* ! EXTERN_H */
Index: usr.sbin/rpki-client/gbr.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/gbr.c,v
retrieving revision 1.10
diff -u -p -u -r1.10 gbr.c
--- usr.sbin/rpki-client/gbr.c	9 Sep 2021 14:15:49 -0000	1.10
+++ usr.sbin/rpki-client/gbr.c	6 Nov 2021 18:44:20 -0000
@@ -44,7 +44,7 @@ static ASN1_OBJECT	*gbr_oid;
  * Returns the payload or NULL if the document was malformed.
  */
 struct gbr *
-gbr_parse(X509 **x509, const char *fn)
+gbr_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len)
 {
 	struct parse	 p;
 	size_t		 cmsz;
@@ -61,7 +61,7 @@ gbr_parse(X509 **x509, const char *fn)
 			    "1.2.840.113549.1.9.16.1.35");
 	}
 
-	cms = cms_parse_validate(x509, fn, gbr_oid, &cmsz);
+	cms = cms_parse_validate(x509, fn, der, len, gbr_oid, &cmsz);
 	if (cms == NULL)
 		return NULL;
 
Index: usr.sbin/rpki-client/http.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/http.c,v
retrieving revision 1.40
diff -u -p -u -r1.40 http.c
--- usr.sbin/rpki-client/http.c	23 Sep 2021 13:26:51 -0000	1.40
+++ usr.sbin/rpki-client/http.c	6 Nov 2021 18:44:38 -0000
@@ -72,6 +72,7 @@
 #define HTTP_IDLE_TIMEOUT	10
 #define HTTP_IO_TIMEOUT		(3 * 60)
 #define MAX_CONNECTIONS		64
+#define MAX_CONTENTLEN		(2 * 1024 * 1024 * 1024LL)
 #define NPFDS			(MAX_CONNECTIONS + 1)
 
 enum res {
@@ -119,6 +120,7 @@ struct http_connection {
 	size_t			bufsz;
 	size_t			bufpos;
 	off_t			iosz;
+	off_t			totalsz;
 	time_t			idle_time;
 	time_t			io_time;
 	int			status;
@@ -157,7 +159,7 @@ static uint8_t *tls_ca_mem;
 static size_t tls_ca_size;
 
 /* HTTP request API */
-static void	http_req_new(size_t, char *, char *, int);
+static void	http_req_new(size_t, char *, char *, int, int);
 static void	http_req_free(struct http_request *);
 static void	http_req_done(size_t, enum http_result, const char *);
 static void	http_req_fail(size_t);
@@ -191,16 +193,6 @@ static enum res	proxy_read(struct http_c
 static enum res	proxy_write(struct http_connection *);
 static enum res	data_write(struct http_connection *);
 
-static time_t
-getmonotime(void)
-{
-	struct timespec ts;
-
-	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
-		err(1, "clock_gettime");
-	return (ts.tv_sec);
-}
-
 /*
  * Return a string that can be used in error message to identify the
  * connection.
@@ -515,7 +507,7 @@ http_resolv(struct addrinfo **res, const
  * Create and queue a new request.
  */
 static void
-http_req_new(size_t id, char *uri, char *modified_since, int outfd)
+http_req_new(size_t id, char *uri, char *modified_since, int count, int outfd)
 {
 	struct http_request *req;
 	char *host, *port, *path;
@@ -538,6 +530,7 @@ http_req_new(size_t id, char *uri, char 
 	req->path = path;
 	req->uri = uri;
 	req->modified_since = modified_since;
+	req->redirect_loop = count;
 
 	TAILQ_INSERT_TAIL(&queue, req, entry);
 }
@@ -569,12 +562,11 @@ http_req_done(size_t id, enum http_resul
 {
 	struct ibuf *b;
 
-	if ((b = ibuf_dynamic(64, UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &id, sizeof(id));
 	io_simple_buffer(b, &res, sizeof(res));
 	io_str_buffer(b, last_modified);
-	ibuf_close(&msgq, b);
+	io_close_buffer(&msgq, b);
 }
 
 /*
@@ -586,12 +578,11 @@ http_req_fail(size_t id)
 	struct ibuf *b;
 	enum http_result res = HTTP_FAILED;
 
-	if ((b = ibuf_dynamic(8, UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &id, sizeof(id));
 	io_simple_buffer(b, &res, sizeof(res));
 	io_str_buffer(b, NULL);
-	ibuf_close(&msgq, b);
+	io_close_buffer(&msgq, b);
 }
 
 /*
@@ -988,8 +979,6 @@ http_request(struct http_connection *con
 	assert(conn->state == STATE_IDLE || conn->state == STATE_TLSCONNECT);
 	conn->state = STATE_REQUEST;
 
-	/* TODO adjust request for HTTP proxy setups */
-
 	/*
 	 * Send port number only if it's specified and does not equal
 	 * the default. Some broken HTTP servers get confused if you explicitly
@@ -1147,7 +1136,8 @@ http_redirect(struct http_connection *co
 			err(1, NULL);
 
 	logx("redirect to %s", http_info(uri));
-	http_req_new(conn->req->id, uri, mod_since, outfd);	
+	http_req_new(conn->req->id, uri, mod_since, conn->req->redirect_loop,
+	    outfd);	
 
 	/* clear request before moving connection to idle */
 	http_req_free(conn->req);
@@ -1175,7 +1165,7 @@ http_parse_header(struct http_connection
 		cp += sizeof(CONTENTLEN) - 1;
 		if ((s = strcspn(cp, " \t")) != 0)
 			*(cp+s) = 0;
-		conn->iosz = strtonum(cp, 0, LLONG_MAX, &errstr);
+		conn->iosz = strtonum(cp, 0, MAX_CONTENTLEN, &errstr);
 		if (errstr != NULL) {
 			warnx("Content-Length of %s is %s",
 			    http_info(conn->req->uri), errstr);
@@ -1311,6 +1301,9 @@ http_read(struct http_connection *conn)
 	char *buf;
 	int done;
 
+	if (conn->bufpos > 0)
+		goto again;
+
 read_more:
 	s = tls_read(conn->tls, conn->buf + conn->bufpos,
 	    conn->bufsz - conn->bufpos);
@@ -1390,7 +1383,7 @@ again:
 
 			if (rv == -1)
 				return http_failed(conn);
-			if (rv ==  0)
+			if (rv == 0)
 				done = 1;
 		}
 
@@ -1399,6 +1392,7 @@ again:
 			if (http_isredirect(conn))
 				http_redirect(conn);
 
+			conn->totalsz = 0;
 			if (conn->chunked)
 				conn->state = STATE_RESPONSE_CHUNKED_HEADER;
 			else
@@ -1422,10 +1416,10 @@ again:
 			 * After redirects all data needs to be discarded.
 			 */
 			if (conn->iosz < (off_t)conn->bufpos) {
-				conn->bufpos  -= conn->iosz;
+				conn->bufpos -= conn->iosz;
 				conn->iosz = 0;
 			} else {
-				conn->iosz  -= conn->bufpos;
+				conn->iosz -= conn->bufpos;
 				conn->bufpos = 0;
 			}
 			if (conn->chunked)
@@ -1656,12 +1650,17 @@ data_write(struct http_connection *conn)
 		bsz = conn->iosz;
 
 	s = write(conn->req->outfd, conn->buf, bsz);
-
 	if (s == -1) {
 		warn("%s: data write", http_info(conn->req->uri));
 		return http_failed(conn);
 	}
 
+	conn->totalsz += s;
+	if (conn->totalsz > MAX_CONTENTLEN) {
+		warn("%s: too much data offered", http_info(conn->req->uri));
+		return http_failed(conn);
+	}
+
 	conn->bufpos -= s;
 	conn->iosz -= s;
 	memmove(conn->buf, conn->buf + s, conn->bufpos);
@@ -1672,7 +1671,7 @@ data_write(struct http_connection *conn)
 
 	/* all data written, switch back to read */
 	if (conn->bufpos == 0 || conn->iosz == 0) {
-		if (conn->chunked)
+		if (conn->chunked && conn->iosz == 0)
 			conn->state = STATE_RESPONSE_CHUNKED_TRAILER;
 		else
 			conn->state = STATE_RESPONSE_DATA;
@@ -1768,6 +1767,7 @@ proc_http(char *bind_addr, int fd)
 	struct pollfd pfds[NPFDS];
 	struct http_connection *conn, *nc;
 	struct http_request *req, *nr;
+	struct ibuf *b, *inbuf = NULL;
 
 	if (bind_addr != NULL) {
 		struct addrinfo hints, *res;
@@ -1858,17 +1858,20 @@ proc_http(char *bind_addr, int fd)
 			}
 		}
 		if (pfds[0].revents & POLLIN) {
-			size_t id;
-			int outfd;
-			char *uri;
-			char *mod;
-
-			outfd = io_recvfd(fd, &id, sizeof(id));
-			io_str_read(fd, &uri);
-			io_str_read(fd, &mod);
-
-			/* queue up new requests */
-			http_req_new(id, uri, mod, outfd);
+			b = io_buf_recvfd(fd, &inbuf);
+			if (b != NULL) {
+				size_t id;
+				char *uri;
+				char *mod;
+
+				io_read_buf(b, &id, sizeof(id));
+				io_read_str(b, &uri);
+				io_read_str(b, &mod);
+
+				/* queue up new requests */
+				http_req_new(id, uri, mod, 0, b->fd);
+				ibuf_free(b);
+			}
 		}
 
 		now = getmonotime();
Index: usr.sbin/rpki-client/io.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/io.c,v
retrieving revision 1.13
diff -u -p -u -r1.13 io.c
--- usr.sbin/rpki-client/io.c	4 Mar 2021 13:01:41 -0000	1.13
+++ usr.sbin/rpki-client/io.c	6 Nov 2021 18:44:48 -0000
@@ -1,5 +1,6 @@
 /*	$OpenBSD: io.c,v 1.13 2021/03/04 13:01:41 claudio Exp $ */
 /*
+ * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -30,30 +31,23 @@
 
 #include "extern.h"
 
-void
-io_socket_blocking(int fd)
-{
-	int	 fl;
-
-	if ((fl = fcntl(fd, F_GETFL, 0)) == -1)
-		err(1, "fcntl");
-	if (fcntl(fd, F_SETFL, fl & ~O_NONBLOCK) == -1)
-		err(1, "fcntl");
-}
-
-void
-io_socket_nonblocking(int fd)
+/*
+ * Create new io buffer, call io_close() when done with it.
+ * Function always returns a new buffer.
+ */
+struct ibuf *
+io_new_buffer(void)
 {
-	int	 fl;
+	struct ibuf *b;
 
-	if ((fl = fcntl(fd, F_GETFL, 0)) == -1)
-		err(1, "fcntl");
-	if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) == -1)
-		err(1, "fcntl");
+	if ((b = ibuf_dynamic(64, INT32_MAX)) == NULL)
+		err(1, NULL);
+	ibuf_reserve(b, sizeof(size_t));	/* can not fail */
+	return b;
 }
 
 /*
- * Like io_simple_write() but into a buffer.
+ * Add a simple object of static size to the io buffer.
  */
 void
 io_simple_buffer(struct ibuf *b, const void *res, size_t sz)
@@ -87,74 +81,155 @@ io_str_buffer(struct ibuf *b, const char
 }
 
 /*
- * Read of a binary buffer that must be on a blocking descriptor.
+ * Finish and enqueue a io buffer.
+ */
+void
+io_close_buffer(struct msgbuf *msgbuf, struct ibuf *b)
+{
+	size_t len;
+
+	len = ibuf_size(b) - sizeof(len);
+	memcpy(ibuf_seek(b, 0, sizeof(len)), &len, sizeof(len));
+	ibuf_close(msgbuf, b);
+}
+
+/*
+ * Read of an ibuf and extract sz byte from there.
  * Does nothing if "sz" is zero.
- * This will fail and exit on EOF.
+ * Return 1 on success or 0 if there was not enough data.
  */
 void
-io_simple_read(int fd, void *res, size_t sz)
+io_read_buf(struct ibuf *b, void *res, size_t sz)
 {
-	ssize_t	 ssz;
 	char	*tmp;
 
-	tmp = res; /* arithmetic on a pointer to void is a GNU extension */
-again:
 	if (sz == 0)
 		return;
-	if ((ssz = read(fd, tmp, sz)) == -1)
-		err(1, "read");
-	else if (ssz == 0)
-		errx(1, "read: unexpected end of file");
-	else if ((size_t)ssz == sz)
+	tmp = ibuf_seek(b, b->rpos, sz);
+	if (tmp == NULL)
+		errx(1, "bad internal framing, buffer too short");
+	b->rpos += sz;
+	memcpy(res, tmp, sz);
+}
+
+/*
+ * Read a string (returns NULL for zero-length strings), allocating
+ * space for it.
+ * Return 1 on success or 0 if there was not enough data.
+ */
+void
+io_read_str(struct ibuf *b, char **res)
+{
+	size_t	 sz;
+
+	io_read_buf(b, &sz, sizeof(sz));
+	if (sz == 0) {
+		*res = NULL;
 		return;
-	sz -= ssz;
-	tmp += ssz;
-	goto again;
+	}
+	if ((*res = calloc(sz + 1, 1)) == NULL)
+		err(1, NULL);
+	io_read_buf(b, *res, sz);
 }
 
 /*
  * Read a binary buffer, allocating space for it.
  * If the buffer is zero-sized, this won't allocate "res", but
  * will still initialise it to NULL.
+ * Return 1 on success or 0 if there was not enough data.
  */
 void
-io_buf_read_alloc(int fd, void **res, size_t *sz)
+io_read_buf_alloc(struct ibuf *b, void **res, size_t *sz)
 {
-
 	*res = NULL;
-	io_simple_read(fd, sz, sizeof(size_t));
+	io_read_buf(b, sz, sizeof(sz));
 	if (*sz == 0)
 		return;
 	if ((*res = malloc(*sz)) == NULL)
 		err(1, NULL);
-	io_simple_read(fd, *res, *sz);
+	io_read_buf(b, *res, *sz);
+}
+
+/* XXX copy from imsg-buffer.c */
+static int
+ibuf_realloc(struct ibuf *buf, size_t len)
+{
+	unsigned char	*b;
+
+	/* on static buffers max is eq size and so the following fails */
+	if (buf->wpos + len > buf->max) {
+		errno = ERANGE;
+		return (-1);
+	}
+
+	b = recallocarray(buf->buf, buf->size, buf->wpos + len, 1);
+	if (b == NULL)
+		return (-1);
+	buf->buf = b;
+	buf->size = buf->wpos + len;
+
+	return (0);
 }
 
 /*
- * Read a string (returns NULL for zero-length strings), allocating
- * space for it.
+ * Read once and fill a ibuf until it is finished.
+ * Returns NULL if more data is needed, returns a full ibuf once
+ * all data is received.
  */
-void
-io_str_read(int fd, char **res)
+struct ibuf *
+io_buf_read(int fd, struct ibuf **ib)
 {
-	size_t	 sz;
+	struct ibuf *b = *ib;
+	ssize_t n;
+	size_t sz;
 
-	io_simple_read(fd, &sz, sizeof(size_t));
-	if (sz == 0) {
-		*res = NULL;
-		return;
+	/* if ibuf == NULL allocate a new buffer */
+	if (b == NULL) {
+		if ((b = ibuf_dynamic(sizeof(sz), INT32_MAX)) == NULL)
+			err(1, NULL);
+		*ib = b;
 	}
-	if ((*res = calloc(sz + 1, 1)) == NULL)
-		err(1, NULL);
-	io_simple_read(fd, *res, sz);
+
+	/* read some data */
+	while ((n = read(fd, b->buf + b->wpos, b->size - b->wpos)) == -1) {
+		if (errno == EINTR)
+			continue;
+		err(1, "read");
+	}
+
+	if (n == 0)
+		errx(1, "read: unexpected end of file");
+	b->wpos += n;
+
+	/* got full message */
+	if (b->wpos == b->size) {
+		/* only header received */
+		if (b->wpos == sizeof(sz)) {
+			memcpy(&sz, b->buf, sizeof(sz));
+			if (sz == 0 || sz > INT32_MAX)
+				errx(1, "bad internal framing, bad size");
+			if (ibuf_realloc(b, sz) == -1)
+				err(1, "ibuf_realloc");
+			return NULL;
+		}
+
+		/* skip over initial size header */
+		b->rpos += sizeof(sz);
+		*ib = NULL;
+		return b;
+	}
+
+	return NULL;
 }
 
+
 /*
  * Read data from socket but receive a file descriptor at the same time.
  */
-int
-io_recvfd(int fd, void *res, size_t sz)
+struct ibuf *
+io_buf_recvfd(int fd, struct ibuf **ib)
 {
+	struct ibuf *b = *ib;
 	struct iovec iov;
 	struct msghdr msg;
 	struct cmsghdr *cmsg;
@@ -162,15 +237,22 @@ io_recvfd(int fd, void *res, size_t sz)
 		struct cmsghdr	hdr;
 		char		buf[CMSG_SPACE(sizeof(int))];
 	} cmsgbuf;
-	int outfd = -1;
-	char *b = res;
 	ssize_t n;
+	size_t sz;
+
+	/* fd are only passed on the head, just use regular read afterwards */
+	if (b != NULL)
+		return io_buf_read(fd, ib);
 
+	if ((b = ibuf_dynamic(sizeof(sz), INT32_MAX)) == NULL)
+		err(1, NULL);
+	*ib = b;
+	
 	memset(&msg, 0, sizeof(msg));
 	memset(&cmsgbuf, 0, sizeof(cmsgbuf));
 
-	iov.iov_base = res;
-	iov.iov_len = sz;
+	iov.iov_base = b->buf;
+	iov.iov_len = b->size;
 
 	msg.msg_iov = &iov;
 	msg.msg_iovlen = 1;
@@ -197,29 +279,32 @@ io_recvfd(int fd, void *res, size_t sz)
 			for (i = 0; i < j; i++) {
 				f = ((int *)CMSG_DATA(cmsg))[i];
 				if (i == 0)
-					outfd = f;
+					b->fd = f;
 				else
 					close(f);
 			}
 		}
 	}
 
-	b += n;
-	sz -= n;
-	while (sz > 0) {
-		/* short receive */
-		n = recv(fd, b, sz, 0);
-		if (n == -1) {
-			if (errno == EINTR)
-				continue;
-			err(1, "recv");
+	b->wpos += n;
+
+	/* got full message */
+	if (b->wpos == b->size) {
+		/* only header received */
+		if (b->wpos == sizeof(sz)) {
+			memcpy(&sz, b->buf, sizeof(sz));
+			if (sz == 0 || sz > INT32_MAX)
+				errx(1, "read: bad internal framing, %zu", sz);
+			if (ibuf_realloc(b, sz) == -1)
+				err(1, "ibuf_realloc");
+			return NULL;
 		}
-		if (n == 0)
-			errx(1, "recv: unexpected end of file");
 
-		b += n;
-		sz -= n;
+		/* skip over initial size header */
+		b->rpos += sizeof(sz);
+		*ib = NULL;
+		return b;
 	}
 
-	return outfd;
+	return NULL;
 }
Index: usr.sbin/rpki-client/ip.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/ip.c,v
retrieving revision 1.17
diff -u -p -u -r1.17 ip.c
--- usr.sbin/rpki-client/ip.c	19 Apr 2021 17:04:35 -0000	1.17
+++ usr.sbin/rpki-client/ip.c	6 Nov 2021 18:45:04 -0000
@@ -285,58 +285,6 @@ ip_addr_print(const struct ip_addr *addr
 }
 
 /*
- * Serialise an ip_addr for sending over the wire.
- * Matched with ip_addr_read().
- */
-void
-ip_addr_buffer(struct ibuf *b, const struct ip_addr *p)
-{
-	size_t sz = PREFIX_SIZE(p->prefixlen);
-
-	assert(sz <= 16);
-	io_simple_buffer(b, &p->prefixlen, sizeof(unsigned char));
-	io_simple_buffer(b, p->addr, sz);
-}
-
-/*
- * Serialise an ip_addr_range for sending over the wire.
- * Matched with ip_addr_range_read().
- */
-void
-ip_addr_range_buffer(struct ibuf *b, const struct ip_addr_range *p)
-{
-	ip_addr_buffer(b, &p->min);
-	ip_addr_buffer(b, &p->max);
-}
-
-/*
- * Read an ip_addr from the wire.
- * Matched with ip_addr_buffer().
- */
-void
-ip_addr_read(int fd, struct ip_addr *p)
-{
-	size_t sz;
-
-	io_simple_read(fd, &p->prefixlen, sizeof(unsigned char));
-	sz = PREFIX_SIZE(p->prefixlen);
-	assert(sz <= 16);
-	io_simple_read(fd, p->addr, sz);
-}
-
-/*
- * Read an ip_addr_range from the wire.
- * Matched with ip_addr_range_buffer().
- */
-void
-ip_addr_range_read(int fd, struct ip_addr_range *p)
-{
-
-	ip_addr_read(fd, &p->min);
-	ip_addr_read(fd, &p->max);
-}
-
-/*
  * Given the addresses (range or IP) in cert_ip, fill in the "min" and
  * "max" fields with the minimum and maximum possible IP addresses given
  * those ranges (or singleton prefixed range).
Index: usr.sbin/rpki-client/main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
retrieving revision 1.145
diff -u -p -u -r1.145 main.c
--- usr.sbin/rpki-client/main.c	30 Aug 2021 16:05:55 -0000	1.145
+++ usr.sbin/rpki-client/main.c	6 Nov 2021 18:45:13 -0000
@@ -1,5 +1,6 @@
 /*	$OpenBSD: main.c,v 1.145 2021/08/30 16:05:55 job Exp $ */
 /*
+ * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -48,6 +49,11 @@
  */
 #define	TALSZ_MAX	8
 
+const char	*tals[TALSZ_MAX];
+const char	*taldescs[TALSZ_MAX];
+unsigned int	 talrepocnt[TALSZ_MAX];
+size_t		 talsz;
+
 size_t	entity_queue;
 int	timeout = 60*60;
 volatile sig_atomic_t killme;
@@ -81,16 +87,24 @@ logx(const char *fmt, ...)
 	}
 }
 
+time_t
+getmonotime(void)
+{
+	struct timespec ts;
+
+	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+		err(1, "clock_gettime");
+	return (ts.tv_sec);
+}
+
 void
 entity_free(struct entity *ent)
 {
-
 	if (ent == NULL)
 		return;
 
-	free(ent->pkey);
+	free(ent->data);
 	free(ent->file);
-	free(ent->descr);
 	free(ent);
 }
 
@@ -100,15 +114,14 @@ entity_free(struct entity *ent)
  * The pointer must be passed entity_free().
  */
 void
-entity_read_req(int fd, struct entity *ent)
+entity_read_req(struct ibuf *b, struct entity *ent)
 {
-
-	io_simple_read(fd, &ent->type, sizeof(enum rtype));
-	io_str_read(fd, &ent->file);
-	io_simple_read(fd, &ent->has_pkey, sizeof(int));
-	if (ent->has_pkey)
-		io_buf_read_alloc(fd, (void **)&ent->pkey, &ent->pkeysz);
-	io_str_read(fd, &ent->descr);
+	io_read_buf(b, &ent->type, sizeof(ent->type));
+	io_read_buf(b, &ent->talid, sizeof(ent->talid));
+	io_read_str(b, &ent->file);
+	io_read_buf(b, &ent->has_data, sizeof(ent->has_data));
+	if (ent->has_data)
+		io_read_buf_alloc(b, (void **)&ent->data, &ent->datasz);
 }
 
 /*
@@ -126,15 +139,14 @@ entity_write_req(const struct entity *en
 		return;
 	}
 
-	if ((b = ibuf_dynamic(sizeof(*ent), UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &ent->type, sizeof(ent->type));
+	io_simple_buffer(b, &ent->talid, sizeof(ent->talid));
 	io_str_buffer(b, ent->file);
-	io_simple_buffer(b, &ent->has_pkey, sizeof(int));
-	if (ent->has_pkey)
-		io_buf_buffer(b, ent->pkey, ent->pkeysz);
-	io_str_buffer(b, ent->descr);
-	ibuf_close(&procq, b);
+	io_simple_buffer(b, &ent->has_data, sizeof(int));
+	if (ent->has_data)
+		io_buf_buffer(b, ent->data, ent->datasz);
+	io_close_buffer(&procq, b);
 }
 
 /*
@@ -171,7 +183,7 @@ entityq_flush(struct entityq *q, struct 
  */
 static void
 entityq_add(char *file, enum rtype type, struct repo *rp,
-    const unsigned char *pkey, size_t pkeysz, char *descr)
+    unsigned char *data, size_t datasz, int talid)
 {
 	struct entity	*p;
 
@@ -179,17 +191,13 @@ entityq_add(char *file, enum rtype type,
 		err(1, NULL);
 
 	p->type = type;
+	p->talid = talid;
 	p->file = file;
-	p->has_pkey = pkey != NULL;
-	if (p->has_pkey) {
-		p->pkeysz = pkeysz;
-		if ((p->pkey = malloc(pkeysz)) == NULL)
-			err(1, NULL);
-		memcpy(p->pkey, pkey, pkeysz);
+	p->has_data = data != NULL;
+	if (p->has_data) {
+		p->data = data;
+		p->datasz = datasz;
 	}
-	if (descr != NULL)
-		if ((p->descr = strdup(descr)) == NULL)
-			err(1, NULL);
 
 	entity_queue++;
 
@@ -224,12 +232,11 @@ rrdp_file_resp(size_t id, int ok)
 	enum rrdp_msg type = RRDP_FILE;
 	struct ibuf *b;
 
-	if ((b = ibuf_open(sizeof(type) + sizeof(id) + sizeof(ok))) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &type, sizeof(type));
 	io_simple_buffer(b, &id, sizeof(id));
 	io_simple_buffer(b, &ok, sizeof(ok));
-	ibuf_close(&rrdpq, b);
+	io_close_buffer(&rrdpq, b);
 }
 
 void
@@ -239,8 +246,7 @@ rrdp_fetch(size_t id, const char *uri, c
 	enum rrdp_msg type = RRDP_START;
 	struct ibuf *b;
 
-	if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &type, sizeof(type));
 	io_simple_buffer(b, &id, sizeof(id));
 	io_str_buffer(b, local);
@@ -248,7 +254,7 @@ rrdp_fetch(size_t id, const char *uri, c
 	io_str_buffer(b, s->session_id);
 	io_simple_buffer(b, &s->serial, sizeof(s->serial));
 	io_str_buffer(b, s->last_mod);
-	ibuf_close(&rrdpq, b);
+	io_close_buffer(&rrdpq, b);
 }
 
 /*
@@ -259,12 +265,11 @@ rsync_fetch(size_t id, const char *uri, 
 {
 	struct ibuf	*b;
 
-	if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &id, sizeof(id));
 	io_str_buffer(b, local);
 	io_str_buffer(b, uri);
-	ibuf_close(&rsyncq, b);
+	io_close_buffer(&rsyncq, b);
 }
 
 /*
@@ -275,14 +280,13 @@ http_fetch(size_t id, const char *uri, c
 {
 	struct ibuf	*b;
 
-	if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &id, sizeof(id));
 	io_str_buffer(b, uri);
 	io_str_buffer(b, last_mod);
 	/* pass file as fd */
 	b->fd = fd;
-	ibuf_close(&httpq, b);
+	io_close_buffer(&httpq, b);
 }
 
 /*
@@ -299,12 +303,11 @@ rrdp_http_fetch(size_t id, const char *u
 	if (pipe2(pi, O_CLOEXEC | O_NONBLOCK) == -1)
 		err(1, "pipe");
 
-	if ((b = ibuf_open(sizeof(type) + sizeof(id))) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &type, sizeof(type));
 	io_simple_buffer(b, &id, sizeof(id));
 	b->fd = pi[0];
-	ibuf_close(&rrdpq, b);
+	io_close_buffer(&rrdpq, b);
 
 	http_fetch(id, uri, last_mod, pi[1]);
 }
@@ -316,13 +319,12 @@ rrdp_http_done(size_t id, enum http_resu
 	struct ibuf *b;
 
 	/* RRDP request, relay response over to the rrdp process */
-	if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &type, sizeof(type));
 	io_simple_buffer(b, &id, sizeof(id));
 	io_simple_buffer(b, &res, sizeof(res));
 	io_str_buffer(b, last_mod);
-	ibuf_close(&rrdpq, b);
+	io_close_buffer(&rrdpq, b);
 }
 
 /*
@@ -346,7 +348,7 @@ queue_add_from_mft(const char *mft, cons
 	 * that the repository has already been loaded.
 	 */
 
-	entityq_add(nfile, type, NULL, NULL, 0, NULL);
+	entityq_add(nfile, type, NULL, NULL, 0, -1);
 }
 
 /*
@@ -394,31 +396,22 @@ queue_add_from_mft_set(const struct mft 
  * Add a local TAL file (RFC 7730) to the queue of files to fetch.
  */
 static void
-queue_add_tal(const char *file)
+queue_add_tal(const char *file, int id)
 {
-	char	*nfile, *buf;
+	unsigned char	*buf;
+	char		*nfile;
+	size_t		 len;
 
 	if ((nfile = strdup(file)) == NULL)
 		err(1, NULL);
-	buf = tal_read_file(file);
-
-	/* Record tal for later reporting */
-	if (stats.talnames == NULL) {
-		if ((stats.talnames = strdup(file)) == NULL)
-			err(1, NULL);
-	} else {
-		char *tmp;
-
-		if (asprintf(&tmp, "%s %s", stats.talnames, file) == -1)
-			err(1, NULL);
-		free(stats.talnames);
-		stats.talnames = tmp;
+	buf = load_file(file, &len);
+	if (buf == NULL) {
+		warn("%s", file);
+		return;
 	}
 
 	/* Not in a repository, so directly add to queue. */
-	entityq_add(nfile, RTYPE_TAL, NULL, NULL, 0, buf);
-	/* entityq_add makes a copy of buf */
-	free(buf);
+	entityq_add(nfile, RTYPE_TAL, NULL, buf, len, id);
 }
 
 /*
@@ -428,14 +421,23 @@ static void
 queue_add_from_tal(struct tal *tal)
 {
 	struct repo	*repo;
+	unsigned char	*data;
 
 	assert(tal->urisz);
 
+	if ((taldescs[tal->id] = strdup(tal->descr)) == NULL)
+		err(1, NULL);
+
 	/* Look up the repository. */
-	repo = ta_lookup(tal);
+	repo = ta_lookup(tal->id, tal);
+	if (repo == NULL)
+		return;
 
-	entityq_add(NULL, RTYPE_CER, repo, tal->pkey,
-	    tal->pkeysz, tal->descr);
+	/* steal the pkey from the tal structure */
+	data = tal->pkey;
+	tal->pkey = NULL;
+	entityq_add(NULL, RTYPE_CER, repo, data,
+	    tal->pkeysz, tal->id);
 }
 
 /*
@@ -447,15 +449,14 @@ queue_add_from_cert(const struct cert *c
 	struct repo	*repo;
 	char		*nfile;
 
-	repo = repo_lookup(cert->repo, rrdpon ? cert->notify : NULL);
-	if (repo == NULL) {
-		warnx("%s: repository lookup failed", cert->repo);
+	repo = repo_lookup(cert->talid, cert->repo,
+	    rrdpon ? cert->notify : NULL);
+	if (repo == NULL)
 		return;
-	}
 
 	if ((nfile = strdup(cert->mft)) == NULL)
 		err(1, NULL);
-	entityq_add(nfile, RTYPE_MFT, repo, NULL, 0, NULL);
+	entityq_add(nfile, RTYPE_MFT, repo, NULL, 0, -1);
 }
 
 /*
@@ -465,9 +466,10 @@ queue_add_from_cert(const struct cert *c
  * In all cases, we gather statistics.
  */
 static void
-entity_process(int proc, struct stats *st, struct vrp_tree *tree)
+entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree,
+    struct brk_tree *brktree)
 {
-	enum rtype	type;
+	enum rtype	 type;
 	struct tal	*tal;
 	struct cert	*cert;
 	struct mft	*mft;
@@ -480,24 +482,24 @@ entity_process(int proc, struct stats *s
 	 * certificate, for example).
 	 * We follow that up with whether the resources didn't parse.
 	 */
-	io_simple_read(proc, &type, sizeof(type));
+	io_read_buf(b, &type, sizeof(type));
 
 	switch (type) {
 	case RTYPE_TAL:
 		st->tals++;
-		tal = tal_read(proc);
+		tal = tal_read(b);
 		queue_add_from_tal(tal);
 		tal_free(tal);
 		break;
 	case RTYPE_CER:
 		st->certs++;
-		io_simple_read(proc, &c, sizeof(int));
+		io_read_buf(b, &c, sizeof(c));
 		if (c == 0) {
 			st->certs_fail++;
 			break;
 		}
-		cert = cert_read(proc);
-		if (cert->valid) {
+		cert = cert_read(b);
+		if (cert->purpose == CERT_PURPOSE_CA) {
 			/*
 			 * Process the revocation list from the
 			 * certificate *first*, since it might mark that
@@ -505,18 +507,21 @@ entity_process(int proc, struct stats *s
 			 * process the MFT.
 			 */
 			queue_add_from_cert(cert);
+		} else if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER) {
+			cert_insert_brks(brktree, cert);
+			st->brks++;
 		} else
-			st->certs_invalid++;
+			st->certs_fail++;
 		cert_free(cert);
 		break;
 	case RTYPE_MFT:
 		st->mfts++;
-		io_simple_read(proc, &c, sizeof(int));
+		io_read_buf(b, &c, sizeof(c));
 		if (c == 0) {
 			st->mfts_fail++;
 			break;
 		}
-		mft = mft_read(proc);
+		mft = mft_read(b);
 		if (mft->stale)
 			st->mfts_stale++;
 		queue_add_from_mft_set(mft);
@@ -527,12 +532,12 @@ entity_process(int proc, struct stats *s
 		break;
 	case RTYPE_ROA:
 		st->roas++;
-		io_simple_read(proc, &c, sizeof(int));
+		io_read_buf(b, &c, sizeof(c));
 		if (c == 0) {
 			st->roas_fail++;
 			break;
 		}
-		roa = roa_read(proc);
+		roa = roa_read(b);
 		if (roa->valid)
 			roa_insert_vrps(tree, roa, &st->vrps, &st->uniqs);
 		else
@@ -543,12 +548,63 @@ entity_process(int proc, struct stats *s
 		st->gbrs++;
 		break;
 	default:
-		errx(1, "unknown entity type");
+		errx(1, "unknown entity type %d", type);
 	}
 
 	entity_queue--;
 }
 
+static void
+rrdp_process(struct ibuf *b)
+{
+	enum rrdp_msg type;
+	enum publish_type pt;
+	struct rrdp_session s;
+	char *uri, *last_mod, *data;
+	char hash[SHA256_DIGEST_LENGTH];
+	size_t dsz, id;
+	int ok;
+
+	io_read_buf(b, &type, sizeof(type));
+	io_read_buf(b, &id, sizeof(id));
+
+	switch (type) {
+	case RRDP_END:
+		io_read_buf(b, &ok, sizeof(ok));
+		rrdp_finish(id, ok);
+		break;
+	case RRDP_HTTP_REQ:
+		io_read_str(b, &uri);
+		io_read_str(b, &last_mod);
+		rrdp_http_fetch(id, uri, last_mod);
+		break;
+	case RRDP_SESSION:
+		io_read_str(b, &s.session_id);
+		io_read_buf(b, &s.serial, sizeof(s.serial));
+		io_read_str(b, &s.last_mod);
+		rrdp_save_state(id, &s);
+		free(s.session_id);
+		free(s.last_mod);
+		break;
+	case RRDP_FILE:
+		io_read_buf(b, &pt, sizeof(pt));
+		if (pt != PUB_ADD)
+			io_read_buf(b, &hash, sizeof(hash));
+		io_read_str(b, &uri);
+		io_read_buf_alloc(b, (void **)&data, &dsz);
+
+		ok = rrdp_handle_file(id, pt, uri, hash, sizeof(hash),
+		    data, dsz);
+		rrdp_file_resp(id, ok);
+
+		free(uri);
+		free(data);
+		break;
+	default:
+		errx(1, "unexpected rrdp response");
+	}
+}
+
 /*
  * Assign filenames ending in ".tal" in "/etc/rpki" into "tals",
  * returning the number of files found and filled-in.
@@ -556,7 +612,7 @@ entity_process(int proc, struct stats *s
  * Don't exceded "max" filenames.
  */
 static size_t
-tal_load_default(const char *tals[], size_t max)
+tal_load_default(void)
 {
 	static const char *confdir = "/etc/rpki";
 	size_t s = 0;
@@ -570,7 +626,7 @@ tal_load_default(const char *tals[], siz
 	while ((dp = readdir(dirp)) != NULL) {
 		if (fnmatch("*.tal", dp->d_name, FNM_PERIOD) == FNM_NOMATCH)
 			continue;
-		if (s >= max)
+		if (s >= TALSZ_MAX)
 			err(1, "too many tal files found in %s",
 			    confdir);
 		if (asprintf(&path, "%s/%s", confdir, dp->d_name) == -1)
@@ -617,19 +673,22 @@ suicide(int sig __attribute__((unused)))
 int
 main(int argc, char *argv[])
 {
-	int		 rc, c, st, proc, rsync, http, rrdp, ok,
-			 hangup = 0, fl = SOCK_STREAM | SOCK_CLOEXEC;
-	size_t		 i, id, outsz = 0, talsz = 0;
+	int		 rc, c, st, proc, rsync, http, rrdp, ok, hangup = 0;
+	int		 fl = SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK;
+	size_t		 i, id;
 	pid_t		 pid, procpid, rsyncpid, httppid, rrdppid;
 	int		 fd[2];
 	struct pollfd	 pfd[NPFD];
 	struct msgbuf	*queues[NPFD];
-	struct roa	**out = NULL;
+	struct ibuf	*b, *httpbuf = NULL, *procbuf = NULL;
+	struct ibuf	*rrdpbuf = NULL, *rsyncbuf = NULL;
 	char		*rsync_prog = "openrsync";
 	char		*bind_addr = NULL;
 	const char	*cachedir = NULL, *outputdir = NULL;
-	const char	*tals[TALSZ_MAX], *errs, *name;
-	struct vrp_tree	 v = RB_INITIALIZER(&v);
+	const char	*errs, *name;
+	const char	*file = NULL;
+	struct vrp_tree	 vrps = RB_INITIALIZER(&vrps);
+	struct brk_tree  brks = RB_INITIALIZER(&brks);
 	struct rusage	ru;
 	struct timeval	start_time, now_time;
 
@@ -654,7 +713,7 @@ main(int argc, char *argv[])
 	    "proc exec unveil", NULL) == -1)
 		err(1, "pledge");
 
-	while ((c = getopt(argc, argv, "b:Bcd:e:jnorRs:t:T:vV")) != -1)
+	while ((c = getopt(argc, argv, "b:Bcd:e:f:jnorRs:t:T:vV")) != -1)
 		switch (c) {
 		case 'b':
 			bind_addr = optarg;
@@ -671,6 +730,10 @@ main(int argc, char *argv[])
 		case 'e':
 			rsync_prog = optarg;
 			break;
+		case 'f':
+			file = optarg;
+			noop = 1;
+			break;
 		case 'j':
 			outformats |= FORMAT_JSON;
 			break;
@@ -728,9 +791,9 @@ main(int argc, char *argv[])
 		goto usage;
 	}
 
-	if ((cachefd = open(cachedir, O_RDONLY | O_DIRECTORY, 0)) == -1)
+	if ((cachefd = open(cachedir, O_RDONLY | O_DIRECTORY)) == -1)
 		err(1, "cache directory %s", cachedir);
-	if ((outdirfd = open(outputdir, O_RDONLY | O_DIRECTORY, 0)) == -1)
+	if ((outdirfd = open(outputdir, O_RDONLY | O_DIRECTORY)) == -1)
 		err(1, "output directory %s", outputdir);
 
 	check_fs_size(cachefd, cachedir);
@@ -739,7 +802,7 @@ main(int argc, char *argv[])
 		outformats = FORMAT_OPENBGPD;
 
 	if (talsz == 0)
-		talsz = tal_load_default(tals, TALSZ_MAX);
+		talsz = tal_load_default();
 	if (talsz == 0)
 		err(1, "no TAL files found in %s", "/etc/rpki");
 
@@ -939,53 +1002,55 @@ main(int argc, char *argv[])
 	 */
 
 	for (i = 0; i < talsz; i++)
-		queue_add_tal(tals[i]);
+		queue_add_tal(tals[i], i);
 
 	/* change working directory to the cache directory */
 	if (fchdir(cachefd) == -1)
 		err(1, "fchdir");
 
 	while (entity_queue > 0 && !killme) {
+		int polltim;
+
 		for (i = 0; i < NPFD; i++) {
 			pfd[i].events = POLLIN;
 			if (queues[i]->queued)
 				pfd[i].events |= POLLOUT;
 		}
 
-		if ((c = poll(pfd, NPFD, INFTIM)) == -1) {
+		polltim = repo_next_timeout(INFTIM);
+
+		if ((c = poll(pfd, NPFD, polltim)) == -1) {
 			if (errno == EINTR)
 				continue;
 			err(1, "poll");
 		}
 
 		for (i = 0; i < NPFD; i++) {
-			if (pfd[i].revents & (POLLERR|POLLNVAL))
-				errx(1, "poll[%zu]: bad fd", i);
-			if (pfd[i].revents & POLLHUP) {
-				warnx("poll[%zu]: hangup", i);
+			if (pfd[i].revents & (POLLERR|POLLNVAL)) {
+				warnx("poll[%zu]: bad fd", i);
 				hangup = 1;
 			}
+			if (pfd[i].revents & POLLHUP)
+				hangup = 1;
 			if (pfd[i].revents & POLLOUT) {
-				/*
-				 * XXX work around deadlocks because of
-				 * blocking read vs non-blocking writes.
-				 */
-				if (i > 1)
-					io_socket_nonblocking(pfd[i].fd);
 				switch (msgbuf_write(queues[i])) {
 				case 0:
-					errx(1, "write[%zu]: "
+					warnx("write[%zu]: "
 					    "connection closed", i);
+					hangup = 1;
+					break;
 				case -1:
-					err(1, "write[%zu]", i);
+					warn("write[%zu]", i);
+					hangup = 1;
+					break;
 				}
-				if (i > 1)
-					io_socket_blocking(pfd[i].fd);
 			}
 		}
 		if (hangup)
 			break;
 
+		repo_check_timeout();
+
 		/*
 		 * Check the rsync and http process.
 		 * This means that one of our modules has completed
@@ -994,72 +1059,38 @@ main(int argc, char *argv[])
 		 */
 
 		if ((pfd[1].revents & POLLIN)) {
-			io_simple_read(rsync, &id, sizeof(id));
-			io_simple_read(rsync, &ok, sizeof(ok));
-			rsync_finish(id, ok);
+			b = io_buf_read(rsync, &rsyncbuf);
+			if (b != NULL) {
+				io_read_buf(b, &id, sizeof(id));
+				io_read_buf(b, &ok, sizeof(ok));
+				rsync_finish(id, ok);
+				ibuf_free(b);
+			}
 		}
 
 		if ((pfd[2].revents & POLLIN)) {
-			enum http_result res;
-			char *last_mod;
-
-			io_simple_read(http, &id, sizeof(id));
-			io_simple_read(http, &res, sizeof(res));
-			io_str_read(http, &last_mod);
-			http_finish(id, res, last_mod);
-			free(last_mod);
+			b = io_buf_read(http, &httpbuf);
+			if (b != NULL) {
+				enum http_result res;
+				char *last_mod;
+
+				io_read_buf(b, &id, sizeof(id));
+				io_read_buf(b, &res, sizeof(res));
+				io_read_str(b, &last_mod);
+				http_finish(id, res, last_mod);
+				free(last_mod);
+				ibuf_free(b);
+			}
 		}
 
 		/*
 		 * Handle RRDP requests here.
 		 */
 		if ((pfd[3].revents & POLLIN)) {
-			enum rrdp_msg type;
-			enum publish_type pt;
-			struct rrdp_session s;
-			char *uri, *last_mod, *data;
-			char hash[SHA256_DIGEST_LENGTH];
-			size_t dsz;
-
-			io_simple_read(rrdp, &type, sizeof(type));
-			io_simple_read(rrdp, &id, sizeof(id));
-
-			switch (type) {
-			case RRDP_END:
-				io_simple_read(rrdp, &ok, sizeof(ok));
-				rrdp_finish(id, ok);
-				break;
-			case RRDP_HTTP_REQ:
-				io_str_read(rrdp, &uri);
-				io_str_read(rrdp, &last_mod);
-				rrdp_http_fetch(id, uri, last_mod);
-				break;
-			case RRDP_SESSION:
-				io_str_read(rrdp, &s.session_id);
-				io_simple_read(rrdp, &s.serial,
-				    sizeof(s.serial));
-				io_str_read(rrdp, &s.last_mod);
-				rrdp_save_state(id, &s);
-				free(s.session_id);
-				free(s.last_mod);
-				break;
-			case RRDP_FILE:
-				io_simple_read(rrdp, &pt, sizeof(pt));
-				if (pt != PUB_ADD)
-					io_simple_read(rrdp, &hash,
-					    sizeof(hash));
-				io_str_read(rrdp, &uri);
-				io_buf_read_alloc(rrdp, (void **)&data, &dsz);
-
-				ok = rrdp_handle_file(id, pt, uri,
-				    hash, sizeof(hash), data, dsz);
-				rrdp_file_resp(id, ok);
-
-				free(uri);
-				free(data);
-				break;
-			default:
-				errx(1, "unexpected rrdp response");
+			b = io_buf_read(rrdp, &rrdpbuf);
+			if (b != NULL) {
+				rrdp_process(b);
+				ibuf_free(b);
 			}
 		}
 
@@ -1069,7 +1100,11 @@ main(int argc, char *argv[])
 		 */
 
 		if ((pfd[0].revents & POLLIN)) {
-			entity_process(proc, &stats, &v);
+			b = io_buf_read(proc, &procbuf);
+			if (b != NULL) {
+				entity_process(b, &stats, &vrps, &brks);
+				ibuf_free(b);
+			}
 		}
 	}
 
@@ -1124,7 +1159,7 @@ main(int argc, char *argv[])
 
 	/* processing did not finish because of error */
 	if (entity_queue != 0)
-		return 1;
+		errx(1, "not all files processed, giving up");
 
 	logx("all files parsed: generating output");
 
@@ -1145,15 +1180,21 @@ main(int argc, char *argv[])
 	if (fchdir(outdirfd) == -1)
 		err(1, "fchdir output dir");
 
-	if (outputfiles(&v, &stats))
+	if (outputfiles(&vrps, &brks, &stats))
 		rc = 1;
 
-
+	logx("Processing time %lld seconds "
+	    "(%lld seconds user, %lld seconds system)",
+	    (long long)stats.elapsed_time.tv_sec,
+	    (long long)stats.user_time.tv_sec,
+	    (long long)stats.system_time.tv_sec);
 	logx("Route Origin Authorizations: %zu (%zu failed parse, %zu invalid)",
 	    stats.roas, stats.roas_fail, stats.roas_invalid);
-	logx("Certificates: %zu (%zu failed parse, %zu invalid)",
-	    stats.certs, stats.certs_fail, stats.certs_invalid);
-	logx("Trust Anchor Locators: %zu", stats.tals);
+	logx("BGPsec Router Certificates: %zu", stats.brks);
+	logx("Certificates: %zu (%zu invalid)",
+	    stats.certs, stats.certs_fail);
+	logx("Trust Anchor Locators: %zu (%zu invalid)",
+	    stats.tals, talsz - stats.tals);
 	logx("Manifests: %zu (%zu failed parse, %zu stale)",
 	    stats.mfts, stats.mfts_fail, stats.mfts_stale);
 	logx("Certificate revocation lists: %zu", stats.crls);
@@ -1165,10 +1206,6 @@ main(int argc, char *argv[])
 
 	/* Memory cleanup. */
 	repo_free();
-
-	for (i = 0; i < outsz; i++)
-		roa_free(out[i]);
-	free(out);
 
 	return rc;
 
Index: usr.sbin/rpki-client/mft.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/mft.c,v
retrieving revision 1.38
diff -u -p -u -r1.38 mft.c
--- usr.sbin/rpki-client/mft.c	9 Sep 2021 14:15:49 -0000	1.38
+++ usr.sbin/rpki-client/mft.c	6 Nov 2021 18:45:20 -0000
@@ -228,6 +228,12 @@ mft_parse_flist(struct parse *p, const A
 		goto out;
 	}
 
+	if (sk_ASN1_TYPE_num(seq) > MAX_MANIFEST_ENTRIES) {
+		warnx("%s: %d exceeds manifest entry limit (%d)", p->fn,
+		    sk_ASN1_TYPE_num(seq), MAX_MANIFEST_ENTRIES);
+		goto out;
+	}
+
 	p->res->files = calloc(sk_ASN1_TYPE_num(seq), sizeof(struct mftfile));
 	if (p->res->files == NULL)
 		err(1, NULL);
@@ -244,7 +250,7 @@ mft_parse_flist(struct parse *p, const A
 	}
 
 	rc = 1;
-out:
+ out:
 	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
 	return rc;
 }
@@ -409,7 +415,7 @@ out:
  * The MFT content is otherwise returned.
  */
 struct mft *
-mft_parse(X509 **x509, const char *fn)
+mft_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len)
 {
 	struct parse	 p;
 	int		 c, rc = 0;
@@ -426,7 +432,7 @@ mft_parse(X509 **x509, const char *fn)
 			    "1.2.840.113549.1.9.16.1.26");
 	}
 
-	cms = cms_parse_validate(x509, fn, mft_oid, &cmsz);
+	cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz);
 	if (cms == NULL)
 		return NULL;
 	assert(*x509 != NULL);
@@ -489,7 +495,7 @@ mft_check(const char *fn, struct mft *p)
 {
 	size_t	i;
 	int	rc = 1;
-	char	*cp, *path = NULL;
+	char	*cp, *h, *path = NULL;
 
 	/* Check hash of file now, but first build path for it */
 	cp = strrchr(fn, '/');
@@ -498,6 +504,13 @@ mft_check(const char *fn, struct mft *p)
 
 	for (i = 0; i < p->filesz; i++) {
 		const struct mftfile *m = &p->files[i];
+		if (!valid_filename(m->file)) {
+			if (base64_encode(m->hash, sizeof(m->hash), &h) == -1)
+				errx(1, "base64_encode failed in %s", __func__);
+			warnx("%s: unsupported filename for %s", fn, h);
+			free(h);
+			continue;
+		}
 		if (asprintf(&path, "%.*s/%s", (int)(cp - fn), fn,
 		    m->file) == -1)
 			err(1, NULL);
@@ -564,7 +577,7 @@ mft_buffer(struct ibuf *b, const struct 
  * Result must be passed to mft_free().
  */
 struct mft *
-mft_read(int fd)
+mft_read(struct ibuf *b)
 {
 	struct mft	*p = NULL;
 	size_t		 i;
@@ -572,22 +585,22 @@ mft_read(int fd)
 	if ((p = calloc(1, sizeof(struct mft))) == NULL)
 		err(1, NULL);
 
-	io_simple_read(fd, &p->stale, sizeof(int));
-	io_str_read(fd, &p->file);
-	assert(p->file);
-	io_simple_read(fd, &p->filesz, sizeof(size_t));
+	io_read_buf(b, &p->stale, sizeof(int));
+	io_read_str(b, &p->file);
+	io_read_buf(b, &p->filesz, sizeof(size_t));
 
+	assert(p->file);
 	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
 		err(1, NULL);
 
 	for (i = 0; i < p->filesz; i++) {
-		io_str_read(fd, &p->files[i].file);
-		io_simple_read(fd, p->files[i].hash, SHA256_DIGEST_LENGTH);
+		io_read_str(b, &p->files[i].file);
+		io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
 	}
 
-	io_str_read(fd, &p->aia);
-	io_str_read(fd, &p->aki);
-	io_str_read(fd, &p->ski);
+	io_read_str(b, &p->aia);
+	io_read_str(b, &p->aki);
+	io_read_str(b, &p->ski);
 	assert(p->aia && p->aki && p->ski);
 
 	return p;
Index: usr.sbin/rpki-client/output-bgpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-bgpd.c,v
retrieving revision 1.22
diff -u -p -u -r1.22 output-bgpd.c
--- usr.sbin/rpki-client/output-bgpd.c	1 Sep 2021 15:21:10 -0000	1.22
+++ usr.sbin/rpki-client/output-bgpd.c	6 Nov 2021 18:45:31 -0000
@@ -20,7 +20,8 @@
 #include "extern.h"
 
 int
-output_bgpd(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_bgpd(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+    struct stats *st)
 {
 	struct vrp	*v;
 
Index: usr.sbin/rpki-client/output-bird.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-bird.c,v
retrieving revision 1.11
diff -u -p -u -r1.11 output-bird.c
--- usr.sbin/rpki-client/output-bird.c	19 Apr 2021 17:04:35 -0000	1.11
+++ usr.sbin/rpki-client/output-bird.c	6 Nov 2021 18:45:40 -0000
@@ -21,7 +21,8 @@
 #include "extern.h"
 
 int
-output_bird1v4(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_bird1v4(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+    struct stats *st)
 {
 	extern		const char *bird_tablename;
 	struct vrp	*v;
@@ -49,7 +50,8 @@ output_bird1v4(FILE *out, struct vrp_tre
 }
 
 int
-output_bird1v6(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_bird1v6(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+    struct stats *st)
 {
 	extern		const char *bird_tablename;
 	struct vrp	*v;
@@ -77,7 +79,8 @@ output_bird1v6(FILE *out, struct vrp_tre
 }
 
 int
-output_bird2(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_bird2(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+    struct stats *st)
 {
 	extern		const char *bird_tablename;
 	struct vrp	*v;
Index: usr.sbin/rpki-client/output-csv.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-csv.c,v
retrieving revision 1.10
diff -u -p -u -r1.10 output-csv.c
--- usr.sbin/rpki-client/output-csv.c	6 May 2021 17:03:57 -0000	1.10
+++ usr.sbin/rpki-client/output-csv.c	6 Nov 2021 18:45:47 -0000
@@ -20,7 +20,8 @@
 #include "extern.h"
 
 int
-output_csv(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_csv(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+    struct stats *st)
 {
 	struct vrp	*v;
 
@@ -33,7 +34,8 @@ output_csv(FILE *out, struct vrp_tree *v
 		ip_addr_print(&v->addr, v->afi, buf, sizeof(buf));
 
 		if (fprintf(out, "AS%u,%s,%u,%s,%lld\n", v->asid, buf,
-		    v->maxlength, v->tal, (long long)v->expires) < 0)
+		    v->maxlength, taldescs[v->talid],
+		    (long long)v->expires) < 0)
 			return -1;
 	}
 	return 0;
Index: usr.sbin/rpki-client/output-json.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-json.c,v
retrieving revision 1.17
diff -u -p -u -r1.17 output-json.c
--- usr.sbin/rpki-client/output-json.c	6 May 2021 17:03:57 -0000	1.17
+++ usr.sbin/rpki-client/output-json.c	6 Nov 2021 18:46:01 -0000
@@ -28,6 +28,7 @@ outputheader_json(FILE *out, struct stat
 	char		hn[NI_MAXHOST], tbuf[26];
 	struct tm	*tp;
 	time_t		t;
+	size_t		i;
 
 	time(&t);
 	setenv("TZ", "UTC", 1);
@@ -46,11 +47,28 @@ outputheader_json(FILE *out, struct stat
 	    "\t\t\"roas\": %zu,\n"
 	    "\t\t\"failedroas\": %zu,\n"
 	    "\t\t\"invalidroas\": %zu,\n"
+	    "\t\t\"bgpsec_pubkeys\": %zu,\n"
 	    "\t\t\"certificates\": %zu,\n"
-	    "\t\t\"failcertificates\": %zu,\n"
 	    "\t\t\"invalidcertificates\": %zu,\n"
 	    "\t\t\"tals\": %zu,\n"
-	    "\t\t\"talfiles\": \"%s\",\n"
+	    "\t\t\"invalidtals\": %zu,\n"
+	    "\t\t\"talfiles\": [\n",
+	    hn, tbuf, (long long)st->elapsed_time.tv_sec,
+	    (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
+	    st->roas, st->roas_fail, st->roas_invalid,
+	    st->brks, st->certs, st->certs_fail,
+	    st->tals, talsz - st->tals) < 0)
+		return -1;
+
+	for (i = 0; i < talsz; i++) {
+		if (fprintf(out,
+		    "\t\t\t\"%s\"%s\n",
+		    tals[i], i == talsz - 1 ? "" : ",") < 0)
+			return -1;
+	}
+
+	if (fprintf(out,
+	    "\t\t],\n"
 	    "\t\t\"manifests\": %zu,\n"
 	    "\t\t\"failedmanifests\": %zu,\n"
 	    "\t\t\"stalemanifests\": %zu,\n"
@@ -62,11 +80,6 @@ outputheader_json(FILE *out, struct stat
 	    "\t\t\"cachedir_del_files\": %zu,\n"
 	    "\t\t\"cachedir_del_dirs\": %zu\n"
 	    "\t},\n\n",
-	    hn, tbuf, (long long)st->elapsed_time.tv_sec,
-	    (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
-	    st->roas, st->roas_fail, st->roas_invalid,
-	    st->certs, st->certs_fail, st->certs_invalid,
-	    st->tals, st->talnames,
 	    st->mfts, st->mfts_fail, st->mfts_stale,
 	    st->crls,
 	    st->gbrs,
@@ -78,10 +91,12 @@ outputheader_json(FILE *out, struct stat
 }
 
 int
-output_json(FILE *out, struct vrp_tree *vrps, struct stats *st)
+output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
+    struct stats *st)
 {
 	char		 buf[64];
 	struct vrp	*v;
+	struct brk	*b;
 	int		 first = 1;
 
 	if (outputheader_json(out, st) < 0)
@@ -91,19 +106,37 @@ output_json(FILE *out, struct vrp_tree *
 		return -1;
 
 	RB_FOREACH(v, vrp_tree, vrps) {
-		if (first)
-			first = 0;
-		else {
+		if (!first) {
 			if (fprintf(out, ",\n") < 0)
 				return -1;
 		}
+		first = 0;
 
 		ip_addr_print(&v->addr, v->afi, buf, sizeof(buf));
 
 		if (fprintf(out, "\t\t{ \"asn\": %u, \"prefix\": \"%s\", "
 		    "\"maxLength\": %u, \"ta\": \"%s\", \"expires\": %lld }",
-		    v->asid, buf, v->maxlength, v->tal, (long long)v->expires)
+		    v->asid, buf, v->maxlength, taldescs[v->talid],
+		    (long long)v->expires)
 		    < 0)
+			return -1;
+	}
+
+	if (fprintf(out, "\n\t],\n\n\t\"bgpsec_keys\": [\n") < 0)
+		return -1;
+
+	first = 1;
+	RB_FOREACH(b, brk_tree, brks) {
+		if (!first) {
+			if (fprintf(out, ",\n") < 0)
+				return -1;
+		}
+		first = 0;
+
+		if (fprintf(out, "\t\t{ \"asn\": %u, \"ski\": \"%s\", "
+		    "\"pubkey\": \"%s\", \"ta\": \"%s\", \"expires\": %lld }",
+		    b->asid, b->ski, b->pubkey, taldescs[b->talid],
+		    (long long)b->expires) < 0)
 			return -1;
 	}
 
Index: usr.sbin/rpki-client/output.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output.c,v
retrieving revision 1.21
diff -u -p -u -r1.21 output.c
--- usr.sbin/rpki-client/output.c	2 Mar 2021 09:08:59 -0000	1.21
+++ usr.sbin/rpki-client/output.c	6 Nov 2021 18:46:14 -0000
@@ -64,7 +64,8 @@ static char	 output_name[PATH_MAX];
 static const struct outputs {
 	int	 format;
 	char	*name;
-	int	(*fn)(FILE *, struct vrp_tree *, struct stats *);
+	int	(*fn)(FILE *, struct vrp_tree *, struct brk_tree *,
+		    struct stats *);
 } outputs[] = {
 	{ FORMAT_OPENBGPD, "openbgpd", output_bgpd },
 	{ FORMAT_BIRD, "bird1v4", output_bird1v4 },
@@ -82,7 +83,7 @@ static void	 sig_handler(int);
 static void	 set_signal_handler(void);
 
 int
-outputfiles(struct vrp_tree *v, struct stats *st)
+outputfiles(struct vrp_tree *v, struct brk_tree *b, struct stats *st)
 {
 	int i, rc = 0;
 
@@ -101,7 +102,7 @@ outputfiles(struct vrp_tree *v, struct s
 			rc = 1;
 			continue;
 		}
-		if ((*outputs[i].fn)(fout, v, st) != 0) {
+		if ((*outputs[i].fn)(fout, v, b, st) != 0) {
 			warn("output for %s format failed", outputs[i].name);
 			fclose(fout);
 			output_cleantmp();
@@ -200,6 +201,7 @@ outputheader(FILE *out, struct stats *st
 	char		hn[NI_MAXHOST], tbuf[80];
 	struct tm	*tp;
 	time_t		t;
+	size_t		i;
 
 	time(&t);
 	setenv("TZ", "UTC", 1);
@@ -210,20 +212,31 @@ outputheader(FILE *out, struct stats *st
 
 	if (fprintf(out,
 	    "# Generated on host %s at %s\n"
-	    "# Processing time %lld seconds (%lld seconds user, %lld seconds system)\n"
+	    "# Processing time %lld seconds (%llds user, %llds system)\n"
 	    "# Route Origin Authorizations: %zu (%zu failed parse, %zu invalid)\n"
-	    "# Certificates: %zu (%zu failed parse, %zu invalid)\n"
-	    "# Trust Anchor Locators: %zu (%s)\n"
+	    "# BGPsec Router Certificates: %zu\n"
+	    "# Certificates: %zu (%zu invalid)\n",
+	    hn, tbuf, (long long)st->elapsed_time.tv_sec,
+	    (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
+	    st->roas, st->roas_fail, st->roas_invalid,
+	    st->brks, st->certs, st->certs_fail) < 0)
+		return -1;
+
+	if (fprintf(out,
+	    "# Trust Anchor Locators: %zu (%zu invalid) [", st->tals,
+	    talsz - st->tals) < 0)
+		return -1;
+	for (i = 0; i < talsz; i++)
+		if (fprintf(out, " %s", tals[i]) < 0)
+			return -1;
+
+	if (fprintf(out,
+	    " ]\n"
 	    "# Manifests: %zu (%zu failed parse, %zu stale)\n"
 	    "# Certificate revocation lists: %zu\n"
 	    "# Ghostbuster records: %zu\n"
 	    "# Repositories: %zu\n"
 	    "# VRP Entries: %zu (%zu unique)\n",
-	    hn, tbuf, (long long)st->elapsed_time.tv_sec,
-	    (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
-	    st->roas, st->roas_fail, st->roas_invalid,
-	    st->certs, st->certs_fail, st->certs_invalid,
-	    st->tals, st->talnames,
 	    st->mfts, st->mfts_fail, st->mfts_stale,
 	    st->crls,
 	    st->gbrs,
Index: usr.sbin/rpki-client/parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
retrieving revision 1.11
diff -u -p -u -r1.11 parser.c
--- usr.sbin/rpki-client/parser.c	15 Sep 2021 15:51:05 -0000	1.11
+++ usr.sbin/rpki-client/parser.c	8 Nov 2021 13:33:34 -0000
@@ -37,12 +37,14 @@
 
 #include "extern.h"
 
-static void	build_chain(const struct auth *, STACK_OF(X509) **);
-static void	build_crls(const struct auth *, struct crl_tree *,
-		    STACK_OF(X509_CRL) **);
-
-/* Limit how deep the RPKI tree can be. */
-#define	MAX_CERT_DEPTH	12
+static void		 build_chain(const struct auth *, STACK_OF(X509) **);
+static struct crl	*get_crl(const struct auth *);
+static void		 build_crls(const struct crl *, STACK_OF(X509_CRL) **);
+
+static X509_STORE_CTX	*ctx;
+static X509_STORE	*store;
+static struct auth_tree  auths = RB_INITIALIZER(&auths);
+static struct crl_tree	 crlt = RB_INITIALIZER(&crlt);
 
 /*
  * Parse and validate a ROA.
@@ -50,34 +52,31 @@ static void	build_crls(const struct auth
  * Returns the roa on success, NULL on failure.
  */
 static struct roa *
-proc_parser_roa(struct entity *entp,
-    X509_STORE *store, X509_STORE_CTX *ctx,
-    struct auth_tree *auths, struct crl_tree *crlt)
+proc_parser_roa(struct entity *entp, const unsigned char *der, size_t len)
 {
 	struct roa		*roa;
 	X509			*x509;
-	int			 c, i;
+	int			 c;
 	struct auth		*a;
 	STACK_OF(X509)		*chain;
 	STACK_OF(X509_CRL)	*crls;
-	const ASN1_TIME		*at;
-	struct tm		 expires_tm;
-	time_t			 expires;
+	struct crl		*crl;
 
-	if ((roa = roa_parse(&x509, entp->file)) == NULL)
+	if ((roa = roa_parse(&x509, entp->file, der, len)) == NULL)
 		return NULL;
 
-	a = valid_ski_aki(entp->file, auths, roa->ski, roa->aki);
-
+	a = valid_ski_aki(entp->file, &auths, roa->ski, roa->aki);
 	build_chain(a, &chain);
-	build_crls(a, crlt, &crls);
+	crl = get_crl(a);
+	build_crls(crl, &crls);
 
 	assert(x509 != NULL);
-	if (!X509_STORE_CTX_init(ctx, store, x509, chain))
+	if (!X509_STORE_CTX_init(ctx, store, x509, NULL))
 		cryptoerrx("X509_STORE_CTX_init");
 	X509_STORE_CTX_set_flags(ctx,
 	    X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK);
 	X509_STORE_CTX_set_depth(ctx, MAX_CERT_DEPTH);
+	X509_STORE_CTX_set0_trusted_stack(ctx, chain);
 	X509_STORE_CTX_set0_crls(ctx, crls);
 
 	if (X509_verify_cert(ctx) <= 0) {
@@ -95,55 +94,18 @@ proc_parser_roa(struct entity *entp,
 	X509_STORE_CTX_cleanup(ctx);
 
 	/*
-	 * Scan the stack of CRLs to figure out the soonest transitive
-	 * expiry moment
+	 * Check CRL to figure out the soonest transitive expiry moment
 	 */
-	for (i = 0; i < sk_X509_CRL_num(crls); i++) {
-		X509_CRL *ci = sk_X509_CRL_value(crls, i);
-
-		at = X509_CRL_get0_nextUpdate(ci);
-		if (at == NULL) {
-			err(1, "X509_CRL_get0_nextUpdate failed");
-			goto out;
-		}
-		memset(&expires_tm, 0, sizeof(expires_tm));
-		if (ASN1_time_parse(at->data, at->length, &expires_tm,
-		    V_ASN1_UTCTIME) != V_ASN1_UTCTIME) {
-			err(1, "ASN1_time_parse failed");
-			goto out;
-		}
-		if ((expires = mktime(&expires_tm)) == -1) {
-			err(1, "mktime failed");
-			goto out;
-		}
-		if (roa->expires > expires)
-			roa->expires = expires;
-	}
+	if (crl != NULL && roa->expires > crl->expires)
+		roa->expires = crl->expires;
 
 	/*
-	 * Scan the stack of CAs to figure out the soonest transitive
+	 * Scan the cert tree to figure out the soonest transitive
 	 * expiry moment
 	 */
-	for (i = 0; i < sk_X509_num(chain); i++) {
-		X509 *xi = sk_X509_value(chain, i);
-
-		at = X509_get0_notAfter(xi);
-		if (at == NULL) {
-			err(1, "X509_get0_notafter failed");
-			goto out;
-		}
-		memset(&expires_tm, 0, sizeof(expires_tm));
-		if (ASN1_time_parse(at->data, at->length, &expires_tm,
-		    V_ASN1_UTCTIME) != V_ASN1_UTCTIME) {
-			err(1, "ASN1_time_parse failed");
-			goto out;
-		}
-		if ((expires = mktime(&expires_tm)) == -1) {
-			err(1, "mktime failed");
-			goto out;
-		}
-		if (roa->expires > expires)
-			roa->expires = expires;
+	for (; a != NULL; a = a->parent) {
+		if (roa->expires > a->cert->expires)
+			roa->expires = a->cert->expires;
 	}
 
 	/*
@@ -151,10 +113,9 @@ proc_parser_roa(struct entity *entp,
 	 * the code around roa_read() to check the "valid" field itself.
 	 */
 
-	if (valid_roa(entp->file, auths, roa))
+	if (valid_roa(entp->file, &auths, roa))
 		roa->valid = 1;
 
-out:
 	sk_X509_free(chain);
 	sk_X509_CRL_free(crls);
 	X509_free(x509);
@@ -173,8 +134,7 @@ out:
  * Return the mft on success or NULL on failure.
  */
 static struct mft *
-proc_parser_mft(struct entity *entp, X509_STORE *store, X509_STORE_CTX *ctx,
-	struct auth_tree *auths, struct crl_tree *crlt)
+proc_parser_mft(struct entity *entp, const unsigned char *der, size_t len)
 {
 	struct mft		*mft;
 	X509			*x509;
@@ -182,18 +142,19 @@ proc_parser_mft(struct entity *entp, X50
 	struct auth		*a;
 	STACK_OF(X509)		*chain;
 
-	if ((mft = mft_parse(&x509, entp->file)) == NULL)
+	if ((mft = mft_parse(&x509, entp->file, der, len)) == NULL)
 		return NULL;
 
-	a = valid_ski_aki(entp->file, auths, mft->ski, mft->aki);
+	a = valid_ski_aki(entp->file, &auths, mft->ski, mft->aki);
 	build_chain(a, &chain);
 
-	if (!X509_STORE_CTX_init(ctx, store, x509, chain))
+	if (!X509_STORE_CTX_init(ctx, store, x509, NULL))
 		cryptoerrx("X509_STORE_CTX_init");
 
 	/* CRL checked disabled here because CRL is referenced from mft */
 	X509_STORE_CTX_set_flags(ctx, X509_V_FLAG_IGNORE_CRITICAL);
 	X509_STORE_CTX_set_depth(ctx, MAX_CERT_DEPTH);
+	X509_STORE_CTX_set0_trusted_stack(ctx, chain);
 
 	if (X509_verify_cert(ctx) <= 0) {
 		c = X509_STORE_CTX_get_error(ctx);
@@ -225,41 +186,36 @@ proc_parser_mft(struct entity *entp, X50
  * parse failure.
  */
 static struct cert *
-proc_parser_cert(const struct entity *entp,
-    X509_STORE *store, X509_STORE_CTX *ctx,
-    struct auth_tree *auths, struct crl_tree *crlt)
+proc_parser_cert(const struct entity *entp, const unsigned char *der,
+    size_t len)
 {
 	struct cert		*cert;
 	X509			*x509;
 	int			 c;
-	struct auth		*a = NULL, *na;
-	char			*tal;
+	struct auth		*a = NULL;
 	STACK_OF(X509)		*chain;
 	STACK_OF(X509_CRL)	*crls;
 
-	assert(!entp->has_pkey);
+	assert(!entp->has_data);
 
 	/* Extract certificate data and X509. */
 
-	cert = cert_parse(&x509, entp->file);
+	cert = cert_parse(&x509, entp->file, der, len);
 	if (cert == NULL)
 		return NULL;
 
-	a = valid_ski_aki(entp->file, auths, cert->ski, cert->aki);
+	a = valid_ski_aki(entp->file, &auths, cert->ski, cert->aki);
 	build_chain(a, &chain);
-	build_crls(a, crlt, &crls);
-
-	/*
-	 * Validate certificate chain w/CRLs.
-	 * Only check the CRLs if specifically asked.
-	 */
+	build_crls(get_crl(a), &crls);
 
 	assert(x509 != NULL);
-	if (!X509_STORE_CTX_init(ctx, store, x509, chain))
+	if (!X509_STORE_CTX_init(ctx, store, x509, NULL))
 		cryptoerrx("X509_STORE_CTX_init");
+
 	X509_STORE_CTX_set_flags(ctx,
 	    X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK);
 	X509_STORE_CTX_set_depth(ctx, MAX_CERT_DEPTH);
+	X509_STORE_CTX_set0_trusted_stack(ctx, chain);
 	X509_STORE_CTX_set0_crls(ctx, crls);
 
 	if (X509_verify_cert(ctx) <= 0) {
@@ -277,67 +233,53 @@ proc_parser_cert(const struct entity *en
 	X509_STORE_CTX_cleanup(ctx);
 	sk_X509_free(chain);
 	sk_X509_CRL_free(crls);
+	X509_free(x509);
+
+	cert->talid = a->cert->talid;
 
 	/* Validate the cert to get the parent */
-	if (!valid_cert(entp->file, auths, cert)) {
-		X509_free(x509); // needed? XXX
-		return cert;
+	if (!valid_cert(entp->file, &auths, cert)) {
+		cert_free(cert);
+		return NULL;
 	}
 
 	/*
-	 * Add validated certs to the RPKI auth tree.
+	 * Add validated CA certs to the RPKI auth tree.
 	 */
-
-	cert->valid = 1;
-
-	na = malloc(sizeof(*na));
-	if (na == NULL)
-		err(1, NULL);
-
-	tal = a->tal;
-
-	na->parent = a;
-	na->cert = cert;
-	na->tal = tal;
-	na->fn = strdup(entp->file);
-	if (na->fn == NULL)
-		err(1, NULL);
-
-	if (RB_INSERT(auth_tree, auths, na) != NULL)
-		err(1, "auth tree corrupted");
+	if (cert->purpose == CERT_PURPOSE_CA) {
+		if (!auth_insert(&auths, cert, a)) {
+			cert_free(cert);
+			return NULL;
+		}
+	}
 
 	return cert;
 }
 
-
 /*
  * Root certificates come from TALs (has a pkey and is self-signed).
  * Parse the certificate, ensure that it's public key matches the
  * known public key from the TAL, and then validate the RPKI
- * content. If valid, we add it as a trusted root (trust anchor) to
- * "store".
+ * content.
  *
  * This returns a certificate (which must not be freed) or NULL on
  * parse failure.
  */
 static struct cert *
-proc_parser_root_cert(const struct entity *entp,
-    X509_STORE *store, X509_STORE_CTX *ctx,
-    struct auth_tree *auths, struct crl_tree *crlt)
+proc_parser_root_cert(const struct entity *entp, const unsigned char *der,
+    size_t len)
 {
 	char			subject[256];
 	ASN1_TIME		*notBefore, *notAfter;
 	X509_NAME		*name;
 	struct cert		*cert;
 	X509			*x509;
-	struct auth		*na;
-	char			*tal;
 
-	assert(entp->has_pkey);
+	assert(entp->has_data);
 
 	/* Extract certificate data and X509. */
 
-	cert = ta_parse(&x509, entp->file, entp->pkey, entp->pkeysz);
+	cert = ta_parse(&x509, entp->file, der, len, entp->data, entp->datasz);
 	if (cert == NULL)
 		return NULL;
 
@@ -370,78 +312,88 @@ proc_parser_root_cert(const struct entit
 		    subject);
 		goto badcert;
 	}
-	if (!valid_ta(entp->file, auths, cert)) {
+	if (!valid_ta(entp->file, &auths, cert)) {
 		warnx("%s: certificate not a valid ta, subject='%s'",
 		    entp->file, subject);
 		goto badcert;
 	}
 
-	/*
-	 * Add valid roots to the RPKI auth tree and as a trusted root
-	 * for chain validation to the X509_STORE.
-	 */
-
-	cert->valid = 1;
-
-	na = malloc(sizeof(*na));
-	if (na == NULL)
-		err(1, NULL);
-
-	if ((tal = strdup(entp->descr)) == NULL)
-		err(1, NULL);
-
-	na->parent = NULL;
-	na->cert = cert;
-	na->tal = tal;
-	na->fn = strdup(entp->file);
-	if (na->fn == NULL)
-		err(1, NULL);
+	X509_free(x509);
 
-	if (RB_INSERT(auth_tree, auths, na) != NULL)
-		err(1, "auth tree corrupted");
+	cert->talid = entp->talid;
 
-	X509_STORE_add_cert(store, x509);
+	/*
+	 * Add valid roots to the RPKI auth tree.
+	 */
+	if (!auth_insert(&auths, cert, NULL)) {
+		cert_free(cert);
+		return NULL;
+	}
 
 	return cert;
+
  badcert:
-	X509_free(x509); // needed? XXX
-	return cert;
+	X509_free(x509);
+	cert_free(cert);
+	return NULL;
 }
 
 /*
  * Parse a certificate revocation list
  * This simply parses the CRL content itself, optionally validating it
  * within the digest if it comes from a manifest, then adds it to the
- * store of CRLs.
+ * CRL tree.
  */
 static void
-proc_parser_crl(struct entity *entp, X509_STORE *store,
-    X509_STORE_CTX *ctx, struct crl_tree *crlt)
+proc_parser_crl(struct entity *entp, const unsigned char *der, size_t len)
 {
 	X509_CRL		*x509_crl;
 	struct crl		*crl;
+	const ASN1_TIME		*at;
+	struct tm		 expires_tm;
 
-	if ((x509_crl = crl_parse(entp->file)) != NULL) {
+	if ((x509_crl = crl_parse(entp->file, der, len)) != NULL) {
 		if ((crl = malloc(sizeof(*crl))) == NULL)
 			err(1, NULL);
 		if ((crl->aki = x509_crl_get_aki(x509_crl, entp->file)) ==
-		    NULL)
-			errx(1, "x509_crl_get_aki failed");
+		    NULL) {
+			warnx("x509_crl_get_aki failed");
+			goto err;
+		}
+
 		crl->x509_crl = x509_crl;
 
-		if (RB_INSERT(crl_tree, crlt, crl) != NULL) {
+		/* extract expire time for later use */
+		at = X509_CRL_get0_nextUpdate(x509_crl);
+		if (at == NULL) {
+			warnx("%s: X509_CRL_get0_nextUpdate failed",
+			    entp->file);
+			goto err;
+		}
+		memset(&expires_tm, 0, sizeof(expires_tm));
+		if (ASN1_time_parse(at->data, at->length, &expires_tm,
+		    0) == -1) {
+			warnx("%s: ASN1_time_parse failed", entp->file);
+			goto err;
+		}
+		if ((crl->expires = mktime(&expires_tm)) == -1)
+			errx(1, "%s: mktime failed", entp->file);
+
+		if (RB_INSERT(crl_tree, &crlt, crl) != NULL) {
 			warnx("%s: duplicate AKI %s", entp->file, crl->aki);
-			free_crl(crl);
+			goto err;
 		}
 	}
+	return;
+ err:
+	free_crl(crl);
 }
 
 /*
  * Parse a ghostbuster record
  */
 static void
-proc_parser_gbr(struct entity *entp, X509_STORE *store,
-    X509_STORE_CTX *ctx, struct auth_tree *auths, struct crl_tree *crlt)
+proc_parser_gbr(struct entity *entp, const unsigned char *der, size_t len)
 {
 	struct gbr		*gbr;
 	X509			*x509;
@@ -450,20 +402,21 @@ proc_parser_gbr(struct entity *entp, X50
 	STACK_OF(X509)		*chain;
 	STACK_OF(X509_CRL)	*crls;
 
-	if ((gbr = gbr_parse(&x509, entp->file)) == NULL)
+	if ((gbr = gbr_parse(&x509, entp->file, der, len)) == NULL)
 		return;
 
-	a = valid_ski_aki(entp->file, auths, gbr->ski, gbr->aki);
+	a = valid_ski_aki(entp->file, &auths, gbr->ski, gbr->aki);
 
 	build_chain(a, &chain);
-	build_crls(a, crlt, &crls);
+	build_crls(get_crl(a), &crls);
 
 	assert(x509 != NULL);
-	if (!X509_STORE_CTX_init(ctx, store, x509, chain))
+	if (!X509_STORE_CTX_init(ctx, store, x509, NULL))
 		cryptoerrx("X509_STORE_CTX_init");
 	X509_STORE_CTX_set_flags(ctx,
 	    X509_V_FLAG_IGNORE_CRITICAL | X509_V_FLAG_CRL_CHECK);
 	X509_STORE_CTX_set_depth(ctx, MAX_CERT_DEPTH);
+	X509_STORE_CTX_set0_trusted_stack(ctx, chain);
 	X509_STORE_CTX_set0_crls(ctx, crls);
 
 	if (X509_verify_cert(ctx) <= 0) {
@@ -481,9 +434,9 @@ proc_parser_gbr(struct entity *entp, X50
 }
 
 /*
- * Use the parent to walk the tree to the root and build a certificate
- * chain from cert->x509. Do not include the root node since this node
- * should already be in the X509_STORE as a trust anchor.
+ * Walk the certificate tree to the root and build a certificate
+ * chain from cert->x509. All certs in the tree are validated and
+ * can be loaded as trusted stack into the validator.
  */
 static void
 build_chain(const struct auth *a, STACK_OF(X509) **chain)
@@ -495,7 +448,7 @@ build_chain(const struct auth *a, STACK_
 
 	if ((*chain = sk_X509_new_null()) == NULL)
 		err(1, "sk_X509_new_null");
-	for (; a->parent != NULL; a = a->parent) {
+	for (; a != NULL; a = a->parent) {
 		assert(a->cert->x509 != NULL);
 		if (!sk_X509_push(*chain, a->cert->x509))
 			errx(1, "sk_X509_push");
@@ -503,29 +456,122 @@ build_chain(const struct auth *a, STACK_
 }
 
 /*
+ * Find a CRL based on the auth SKI value.
+ */
+static struct crl *
+get_crl(const struct auth *a)
+{
+	struct crl	find;
+
+	if (a == NULL)
+		return NULL;
+
+	find.aki = a->cert->ski;
+	return RB_FIND(crl_tree, &crlt, &find);
+}
+
+/*
  * Add the CRL based on the certs SKI value.
  * No need to insert any other CRL since those were already checked.
  */
 static void
-build_crls(const struct auth *a, struct crl_tree *crlt,
-    STACK_OF(X509_CRL) **crls)
+build_crls(const struct crl *crl, STACK_OF(X509_CRL) **crls)
 {
-	struct crl	find, *found;
-
 	*crls = NULL;
 
-	if (a == NULL)
+	if (crl == NULL)
 		return;
 
 	if ((*crls = sk_X509_CRL_new_null()) == NULL)
 		errx(1, "sk_X509_CRL_new_null");
 
-	find.aki = a->cert->ski;
-	found = RB_FIND(crl_tree, crlt, &find);
-	if (found && !sk_X509_CRL_push(*crls, found->x509_crl))
+	if (!sk_X509_CRL_push(*crls, crl->x509_crl))
 		err(1, "sk_X509_CRL_push");
 }
 
+static void
+parse_entity(struct entityq *q, struct msgbuf *msgq)
+{
+	struct entity	*entp;
+	struct tal	*tal;
+	struct cert	*cert;
+	struct mft	*mft;
+	struct roa	*roa;
+	struct ibuf	*b;
+	unsigned char	*f;
+	size_t		 flen;
+	int		 c;
+
+	while ((entp = TAILQ_FIRST(q)) != NULL) {
+		TAILQ_REMOVE(q, entp, entries);
+
+		b = io_new_buffer();
+		io_simple_buffer(b, &entp->type, sizeof(entp->type));
+
+		f = NULL;
+		if (entp->type != RTYPE_TAL) {
+			f = load_file(entp->file, &flen);
+			if (f == NULL)
+				warn("%s", entp->file);
+		}
+
+		switch (entp->type) {
+		case RTYPE_TAL:
+			if ((tal = tal_parse(entp->file, entp->data,
+			    entp->datasz)) == NULL)
+				errx(1, "%s: could not parse tal file",
+				    entp->file);
+			tal->id = entp->talid;
+			tal_buffer(b, tal);
+			tal_free(tal);
+			break;
+		case RTYPE_CER:
+			if (entp->has_data)
+				cert = proc_parser_root_cert(entp, f, flen);
+			else
+				cert = proc_parser_cert(entp, f, flen);
+			c = (cert != NULL);
+			io_simple_buffer(b, &c, sizeof(int));
+			if (cert != NULL)
+				cert_buffer(b, cert);
+			/*
+			 * The parsed certificate data "cert" is now
+			 * managed in the "auths" table, so don't free
+			 * it here (see the loop after "out").
+			 */
+			break;
+		case RTYPE_CRL:
+			proc_parser_crl(entp, f, flen);
+			break;
+		case RTYPE_MFT:
+			mft = proc_parser_mft(entp, f, flen);
+			c = (mft != NULL);
+			io_simple_buffer(b, &c, sizeof(int));
+			if (mft != NULL)
+				mft_buffer(b, mft);
+			mft_free(mft);
+			break;
+		case RTYPE_ROA:
+			roa = proc_parser_roa(entp, f, flen);
+			c = (roa != NULL);
+			io_simple_buffer(b, &c, sizeof(int));
+			if (roa != NULL)
+				roa_buffer(b, roa);
+			roa_free(roa);
+			break;
+		case RTYPE_GBR:
+			proc_parser_gbr(entp, f, flen);
+			break;
+		default:
+			abort();
+		}
+
+		free(f);
+		io_close_buffer(msgq, b);
+		entity_free(entp);
+	}
+}
+
 /*
  * Process responsible for parsing and validating content.
  * All this process does is wait to be told about a file to parse, then
@@ -536,29 +582,20 @@ build_crls(const struct auth *a, struct 
 void
 proc_parser(int fd)
 {
-	struct tal	*tal;
-	struct cert	*cert;
-	struct mft	*mft;
-	struct roa	*roa;
-	struct entity	*entp;
 	struct entityq	 q;
-	int		 c, rc = 1;
 	struct msgbuf	 msgq;
 	struct pollfd	 pfd;
-	struct ibuf	*b;
-	X509_STORE	*store;
-	X509_STORE_CTX	*ctx;
-	struct auth_tree auths = RB_INITIALIZER(&auths);
-	struct crl_tree	 crlt = RB_INITIALIZER(&crlt);
+	struct entity	*entp;
+	struct ibuf	*b, *inbuf = NULL;
 
 	ERR_load_crypto_strings();
 	OpenSSL_add_all_ciphers();
 	OpenSSL_add_all_digests();
 
-	if ((store = X509_STORE_new()) == NULL)
-		cryptoerrx("X509_STORE_new");
 	if ((ctx = X509_STORE_CTX_new()) == NULL)
 		cryptoerrx("X509_STORE_CTX_new");
+	if ((store = X509_STORE_new()) == NULL)
+		cryptoerrx("X509_STORE_new");
 
 	TAILQ_INIT(&q);
 
@@ -567,8 +604,6 @@ proc_parser(int fd)
 
 	pfd.fd = fd;
 
-	io_socket_nonblocking(pfd.fd);
-
 	for (;;) {
 		pfd.events = POLLIN;
 		if (msgq.queued)
@@ -584,22 +619,16 @@ proc_parser(int fd)
 		if ((pfd.revents & POLLHUP))
 			break;
 
-		/*
-		 * Start with read events.
-		 * This means that the parent process is sending us
-		 * something we need to parse.
-		 * We don't actually parse it til we have space in our
-		 * outgoing buffer for responding, though.
-		 */
-
 		if ((pfd.revents & POLLIN)) {
-			io_socket_blocking(fd);
-			entp = calloc(1, sizeof(struct entity));
-			if (entp == NULL)
-				err(1, NULL);
-			entity_read_req(fd, entp);
-			TAILQ_INSERT_TAIL(&q, entp, entries);
-			io_socket_nonblocking(fd);
+			b = io_buf_read(fd, &inbuf);
+			if (b != NULL) {
+				entp = calloc(1, sizeof(struct entity));
+				if (entp == NULL)
+					err(1, NULL);
+				entity_read_req(b, entp);
+				TAILQ_INSERT_TAIL(&q, entp, entries);
+				ibuf_free(b);
+			}
 		}
 
 		if (pfd.revents & POLLOUT) {
@@ -611,80 +640,9 @@ proc_parser(int fd)
 			}
 		}
 
-		/*
-		 * If there's nothing to parse, then stop waiting for
-		 * the write signal.
-		 */
-
-		if (TAILQ_EMPTY(&q)) {
-			pfd.events &= ~POLLOUT;
-			continue;
-		}
-
-		entp = TAILQ_FIRST(&q);
-		assert(entp != NULL);
-
-		if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL)
-			err(1, NULL);
-		io_simple_buffer(b, &entp->type, sizeof(entp->type));
-
-		switch (entp->type) {
-		case RTYPE_TAL:
-			if ((tal = tal_parse(entp->file, entp->descr)) == NULL)
-				goto out;
-			tal_buffer(b, tal);
-			tal_free(tal);
-			break;
-		case RTYPE_CER:
-			if (entp->has_pkey)
-				cert = proc_parser_root_cert(entp, store, ctx,
-				    &auths, &crlt);
-			else
-				cert = proc_parser_cert(entp, store, ctx,
-				    &auths, &crlt);
-			c = (cert != NULL);
-			io_simple_buffer(b, &c, sizeof(int));
-			if (cert != NULL)
-				cert_buffer(b, cert);
-			/*
-			 * The parsed certificate data "cert" is now
-			 * managed in the "auths" table, so don't free
-			 * it here (see the loop after "out").
-			 */
-			break;
-		case RTYPE_MFT:
-			mft = proc_parser_mft(entp, store, ctx, &auths, &crlt);
-			c = (mft != NULL);
-			io_simple_buffer(b, &c, sizeof(int));
-			if (mft != NULL)
-				mft_buffer(b, mft);
-			mft_free(mft);
-			break;
-		case RTYPE_CRL:
-			proc_parser_crl(entp, store, ctx, &crlt);
-			break;
-		case RTYPE_ROA:
-			roa = proc_parser_roa(entp, store, ctx, &auths, &crlt);
-			c = (roa != NULL);
-			io_simple_buffer(b, &c, sizeof(int));
-			if (roa != NULL)
-				roa_buffer(b, roa);
-			roa_free(roa);
-			break;
-		case RTYPE_GBR:
-			proc_parser_gbr(entp, store, ctx, &auths, &crlt);
-			break;
-		default:
-			abort();
-		}
-
-		ibuf_close(&msgq, b);
-		TAILQ_REMOVE(&q, entp, entries);
-		entity_free(entp);
+		parse_entity(&q, &msgq);
 	}
 
-	rc = 0;
-out:
 	while ((entp = TAILQ_FIRST(&q)) != NULL) {
 		TAILQ_REMOVE(&q, entp, entries);
 		entity_free(entp);
@@ -693,9 +651,7 @@ out:
 	/* XXX free auths and crl tree */
 
 	X509_STORE_CTX_free(ctx);
-	X509_STORE_free(store);
-
 	msgbuf_clear(&msgq);
 
-	exit(rc);
+	exit(0);
 }
Index: usr.sbin/rpki-client/print.c
===================================================================
RCS file: usr.sbin/rpki-client/print.c
diff -N usr.sbin/rpki-client/print.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ usr.sbin/rpki-client/print.c	6 Nov 2021 14:20:41 -0000
@@ -0,0 +1,166 @@
+/*	$OpenBSD: print.c,v 1.2 2021/10/25 14:07:56 claudio Exp $ */
+/*
+ * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "extern.h"
+
+static const char *
+pretty_key_id(char *hex)
+{
+	static char buf[128];	/* bigger than SHA_DIGEST_LENGTH * 3 */
+	size_t i;
+
+	for (i = 0; i < sizeof(buf) && *hex != '\0'; i++) {
+		if  (i % 3 == 2 && *hex != '\0')
+			buf[i] = ':';
+		else
+			buf[i] = *hex++;
+	}
+	if (i == sizeof(buf))
+		memcpy(buf + sizeof(buf) - 4, "...", 4);
+	return buf;
+}
+
+void
+tal_print(const struct tal *p)
+{
+	size_t	 i;
+
+	for (i = 0; i < p->urisz; i++)
+		printf("%5zu: URI: %s\n", i + 1, p->uri[i]);
+}
+
+void
+cert_print(const struct cert *p)
+{
+	size_t	 i;
+	char	 buf1[64], buf2[64];
+	int	 sockt;
+	char	 tbuf[21];
+
+	printf("Subject key identifier: %s\n", pretty_key_id(p->ski));
+	if (p->aki != NULL)
+		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
+	if (p->aia != NULL)
+		printf("Authority info access: %s\n", p->aia);
+	if (p->mft != NULL)
+		printf("Manifest: %s\n", p->mft);
+	if (p->repo != NULL)
+		printf("caRepository: %s\n", p->repo);
+	if (p->notify != NULL)
+		printf("Notify URL: %s\n", p->notify);
+	if (p->pubkey != NULL)
+		printf("BGPsec P-256 ECDSA public key: %s\n", p->pubkey);
+	strftime(tbuf, sizeof(tbuf), "%FT%TZ", gmtime(&p->expires));
+	printf("Valid until: %s\n", tbuf);
+
+	printf("Subordinate Resources:\n");
+
+	for (i = 0; i < p->asz; i++)
+		switch (p->as[i].type) {
+		case CERT_AS_ID:
+			printf("%5zu: AS: %u\n", i + 1, p->as[i].id);
+			break;
+		case CERT_AS_INHERIT:
+			printf("%5zu: AS: inherit\n", i + 1);
+			break;
+		case CERT_AS_RANGE:
+			printf("%5zu: AS: %u -- %u\n", i + 1,
+				p->as[i].range.min, p->as[i].range.max);
+			break;
+		}
+
+	for (i = 0; i < p->ipsz; i++)
+		switch (p->ips[i].type) {
+		case CERT_IP_INHERIT:
+			printf("%5zu: IP: inherit\n", i + 1);
+			break;
+		case CERT_IP_ADDR:
+			ip_addr_print(&p->ips[i].ip,
+				p->ips[i].afi, buf1, sizeof(buf1));
+			printf("%5zu: IP: %s\n", i + 1, buf1);
+			break;
+		case CERT_IP_RANGE:
+			sockt = (p->ips[i].afi == AFI_IPV4) ?
+				AF_INET : AF_INET6;
+			inet_ntop(sockt, p->ips[i].min, buf1, sizeof(buf1));
+			inet_ntop(sockt, p->ips[i].max, buf2, sizeof(buf2));
+			printf("%5zu: IP: %s -- %s\n", i + 1, buf1, buf2);
+			break;
+		}
+
+}
+
+void
+mft_print(const struct mft *p)
+{
+	size_t i;
+	char *hash;
+
+	printf("Subject key identifier: %s\n", pretty_key_id(p->ski));
+	printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
+	printf("Authority info access: %s\n", p->aia);
+	printf("Manifest Number: %s\n", p->seqnum);
+	for (i = 0; i < p->filesz; i++) {
+		if (base64_encode(p->files[i].hash, sizeof(p->files[i].hash),
+		    &hash) == -1)
+			errx(1, "base64_encode failure");
+		printf("%5zu: %s\n", i + 1, p->files[i].file);
+		printf("\thash %s\n", hash);
+		free(hash);
+	}
+}
+
+void
+roa_print(const struct roa *p)
+{
+	char	 buf[128];
+	size_t	 i;
+	char	 tbuf[21];
+
+	printf("Subject key identifier: %s\n", pretty_key_id(p->ski));
+	printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
+	printf("Authority info access: %s\n", p->aia);
+	strftime(tbuf, sizeof(tbuf), "%FT%TZ", gmtime(&p->expires));
+	printf("ROA valid until: %s\n", tbuf);
+	
+	printf("asID: %u\n", p->asid);
+	for (i = 0; i < p->ipsz; i++) {
+		ip_addr_print(&p->ips[i].addr,
+			p->ips[i].afi, buf, sizeof(buf));
+		printf("%5zu: %s maxlen: %zu\n", i + 1,
+			buf, p->ips[i].maxlength);
+	}
+}
+
+void
+gbr_print(const struct gbr *p)
+{
+	printf("Subject key identifier: %s\n", pretty_key_id(p->ski));
+	printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
+	printf("Authority info access: %s\n", p->aia);
+	printf("vcard:\n%s", p->vcard);
+}
Index: usr.sbin/rpki-client/repo.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/repo.c,v
retrieving revision 1.9
diff -u -p -u -r1.9 repo.c
--- usr.sbin/rpki-client/repo.c	12 Aug 2021 15:27:15 -0000	1.9
+++ usr.sbin/rpki-client/repo.c	6 Nov 2021 18:46:25 -0000
@@ -27,6 +27,7 @@
 #include <fcntl.h>
 #include <fts.h>
 #include <limits.h>
+#include <poll.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -88,11 +89,14 @@ SLIST_HEAD(, tarepo)	tarepos = SLIST_HEA
 
 struct	repo {
 	SLIST_ENTRY(repo)	 entry;
-	char			*repouri;	/* CA repository base URI */
+	char			*repouri;
+	char			*notifyuri;
 	const struct rrdprepo	*rrdp;
 	const struct rsyncrepo	*rsync;
 	const struct tarepo	*ta;
 	struct entityq		 queue;		/* files waiting for repo */
+	time_t			 alarm;		/* sync timeout */
+	int			 talid;
 	size_t			 id;		/* identifier */
 };
 SLIST_HEAD(, repo)	repos = SLIST_HEAD_INITIALIZER(repos);
@@ -607,14 +611,22 @@ rrdp_basedir(const char *dir)
  * Allocate and insert a new repository.
  */
 static struct repo *
-repo_alloc(void)
+repo_alloc(int talid)
 {
 	struct repo *rp;
 
+	if (++talrepocnt[talid] >= MAX_REPO_PER_TAL) {
+		if (talrepocnt[talid] == MAX_REPO_PER_TAL)
+			warnx("too many repositories under %s", tals[talid]);
+		return NULL;
+	}
+
 	if ((rp = calloc(1, sizeof(*rp))) == NULL)
 		err(1, NULL);
 
 	rp->id = ++repoid;
+	rp->talid = talid;
+	rp->alarm = getmonotime() + MAX_REPO_TIMEOUT;
 	TAILQ_INIT(&rp->queue);
 	SLIST_INSERT_HEAD(&repos, rp, entry);
 
@@ -931,6 +943,9 @@ rsync_finish(size_t id, int ok)
 
 	tr = ta_find(id);
 	if (tr != NULL) {
+		/* repository changed state already, ignore request */
+		if (tr->state != REPO_LOADING)
+			return;
 		if (ok) {
 			logx("ta/%s: loaded from network", tr->descr);
 			stats.rsync_repos++;
@@ -953,6 +968,9 @@ rsync_finish(size_t id, int ok)
 	if (rr == NULL)
 		errx(1, "unknown rsync repo %zu", id);
 
+	/* repository changed state already, ignore request */
+	if (rr->state != REPO_LOADING)
+		return;
 	if (ok) {
 		logx("%s: loaded from network", rr->basedir);
 		stats.rsync_repos++;
@@ -981,6 +999,9 @@ rrdp_finish(size_t id, int ok)
 	rr = rrdp_find(id);
 	if (rr == NULL)
 		errx(1, "unknown RRDP repo %zu", id);
+	/* repository changed state already, ignore request */
+	if (rr->state != REPO_LOADING)
+		return;
 
 	if (ok && rrdp_merge_repo(rr)) {
 		logx("%s: loaded from network", rr->notifyuri);
@@ -1032,6 +1053,10 @@ http_finish(size_t id, enum http_result 
 		return;
 	}
 
+	/* repository changed state already, ignore request */
+	if (tr->state != REPO_LOADING)
+		return;
+
 	/* Move downloaded TA file into place, or unlink on failure. */
 	if (res == HTTP_OK) {
 		char *file;
@@ -1065,7 +1090,7 @@ http_finish(size_t id, enum http_result 
  * Look up a trust anchor, queueing it for download if not found.
  */
 struct repo *
-ta_lookup(struct tal *tal)
+ta_lookup(int id, struct tal *tal)
 {
 	struct repo	*rp;
 
@@ -1075,7 +1100,10 @@ ta_lookup(struct tal *tal)
 			return rp;
 	}
 
-	rp = repo_alloc();
+	rp = repo_alloc(id);
+	if (rp == NULL)
+		return NULL;
+
 	if ((rp->repouri = strdup(tal->descr)) == NULL)
 		err(1, NULL);
 	rp->ta = ta_get(tal);
@@ -1087,20 +1115,40 @@ ta_lookup(struct tal *tal)
  * Look up a repository, queueing it for discovery if not found.
  */
 struct repo *
-repo_lookup(const char *uri, const char *notify)
+repo_lookup(int id, const char *uri, const char *notify)
 {
-	struct repo *rp;
+	struct repo	*rp;
+	char		*repouri;
+
+	if ((repouri = rsync_base_uri(uri)) == NULL)
+		errx(1, "bad caRepository URI: %s", uri);
 
 	/* Look up in repository table. */
 	SLIST_FOREACH(rp, &repos, entry) {
-		if (strcmp(rp->repouri, uri) != 0)
+		if (strcmp(rp->repouri, repouri) != 0)
 			continue;
+		if (rp->notifyuri != NULL) {
+			if (notify == NULL)
+				continue;
+			if (strcmp(rp->notifyuri, notify) != 0)
+				continue;
+		} else if (notify != NULL)
+			continue;
+		/* found matching repo */
+		free(repouri);
 		return rp;
 	}
 
-	rp = repo_alloc();
-	if ((rp->repouri = strdup(uri)) == NULL)
-		err(1, NULL);
+	rp = repo_alloc(id);
+	if (rp == NULL) {
+		free(repouri);
+		return NULL;
+	}
+
+	rp->repouri = repouri;
+	if (notify != NULL)
+		if ((rp->notifyuri = strdup(notify)) == NULL)
+			err(1, NULL);
 
 	/* try RRDP first if available */
 	if (notify != NULL)
@@ -1151,6 +1199,60 @@ repo_queued(struct repo *rp, struct enti
 		return 1;
 	}
 	return 0;
+}
+
+int
+repo_next_timeout(int timeout)
+{
+	struct repo	*rp;
+	time_t		 now;
+
+	now = getmonotime();
+	/* Look up in repository table. (Lookup should actually fail here) */
+	SLIST_FOREACH(rp, &repos, entry) {
+		if (repo_state(rp) == REPO_LOADING) {
+			int diff = rp->alarm - now;
+			diff *= 1000;
+			if (timeout == INFTIM || diff < timeout)
+				timeout = diff;
+		}
+	}
+	return timeout;
+}
+
+static void
+repo_fail(struct repo *rp)
+{
+	/* reset the alarm since code may fallback to rsync */
+	rp->alarm = getmonotime() + MAX_REPO_TIMEOUT;
+
+	if (rp->ta)
+		http_finish(rp->ta->id, HTTP_FAILED, NULL);
+	else if (rp->rrdp)
+		rrdp_finish(rp->rrdp->id, 0);
+	else if (rp->rsync)
+		rsync_finish(rp->rsync->id, 0);
+	else
+		errx(1, "%s: bad repo", rp->repouri);
+}
+
+void
+repo_check_timeout(void)
+{
+	struct repo	*rp;
+	time_t		 now;
+
+	now = getmonotime();
+	/* Look up in repository table. (Lookup should actually fail here) */
+	SLIST_FOREACH(rp, &repos, entry) {
+		if (repo_state(rp) == REPO_LOADING) {
+			if (rp->alarm <= now) {
+				warnx("%s: synchronisation timeout",
+				    rp->repouri);
+				repo_fail(rp);
+			}
+		}
+	}
 }
 
 static char **
Index: usr.sbin/rpki-client/roa.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/roa.c,v
retrieving revision 1.25
diff -u -p -u -r1.25 roa.c
--- usr.sbin/rpki-client/roa.c	9 Sep 2021 14:15:49 -0000	1.25
+++ usr.sbin/rpki-client/roa.c	6 Nov 2021 18:46:30 -0000
@@ -327,7 +327,7 @@ out:
  * Returns the ROA or NULL if the document was malformed.
  */
 struct roa *
-roa_parse(X509 **x509, const char *fn)
+roa_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len)
 {
 	struct parse	 p;
 	size_t		 cmsz;
@@ -348,7 +348,7 @@ roa_parse(X509 **x509, const char *fn)
 			    "1.2.840.113549.1.9.16.1.24");
 	}
 
-	cms = cms_parse_validate(x509, fn, roa_oid, &cmsz);
+	cms = cms_parse_validate(x509, fn, der, len, roa_oid, &cmsz);
 	if (cms == NULL)
 		return NULL;
 
@@ -374,12 +374,11 @@ roa_parse(X509 **x509, const char *fn)
 		warnx("%s: ASN1_time_parse failed", fn);
 		goto out;
 	}
-	if ((expires = mktime(&expires_tm)) == -1) {
-		err(1, "mktime failed");
-		goto out;
-	}
+	if ((expires = mktime(&expires_tm)) == -1)
+		errx(1, "mktime failed");
+
 	p.res->expires = expires;
-	
+
 	if (!roa_parse_econtent(cms, cmsz, &p))
 		goto out;
 
@@ -410,7 +409,6 @@ roa_free(struct roa *p)
 	free(p->aki);
 	free(p->ski);
 	free(p->ips);
-	free(p->tal);
 	free(p);
 }
 
@@ -421,25 +419,17 @@ roa_free(struct roa *p)
 void
 roa_buffer(struct ibuf *b, const struct roa *p)
 {
-	size_t	 i;
+	io_simple_buffer(b, &p->valid, sizeof(p->valid));
+	io_simple_buffer(b, &p->asid, sizeof(p->asid));
+	io_simple_buffer(b, &p->talid, sizeof(p->talid));
+	io_simple_buffer(b, &p->ipsz, sizeof(p->ipsz));
+	io_simple_buffer(b, &p->expires, sizeof(p->expires));
 
-	io_simple_buffer(b, &p->valid, sizeof(int));
-	io_simple_buffer(b, &p->asid, sizeof(uint32_t));
-	io_simple_buffer(b, &p->ipsz, sizeof(size_t));
-	io_simple_buffer(b, &p->expires, sizeof(time_t));
-
-	for (i = 0; i < p->ipsz; i++) {
-		io_simple_buffer(b, &p->ips[i].afi, sizeof(enum afi));
-		io_simple_buffer(b, &p->ips[i].maxlength, sizeof(size_t));
-		io_simple_buffer(b, p->ips[i].min, sizeof(p->ips[i].min));
-		io_simple_buffer(b, p->ips[i].max, sizeof(p->ips[i].max));
-		ip_addr_buffer(b, &p->ips[i].addr);
-	}
+	io_simple_buffer(b, p->ips, p->ipsz * sizeof(p->ips[0]));
 
 	io_str_buffer(b, p->aia);
 	io_str_buffer(b, p->aki);
 	io_str_buffer(b, p->ski);
-	io_str_buffer(b, p->tal);
 }
 
 /*
@@ -448,35 +438,27 @@ roa_buffer(struct ibuf *b, const struct 
  * Result must be passed to roa_free().
  */
 struct roa *
-roa_read(int fd)
+roa_read(struct ibuf *b)
 {
 	struct roa	*p;
-	size_t		 i;
 
 	if ((p = calloc(1, sizeof(struct roa))) == NULL)
 		err(1, NULL);
 
-	io_simple_read(fd, &p->valid, sizeof(int));
-	io_simple_read(fd, &p->asid, sizeof(uint32_t));
-	io_simple_read(fd, &p->ipsz, sizeof(size_t));
-	io_simple_read(fd, &p->expires, sizeof(time_t));
+	io_read_buf(b, &p->valid, sizeof(p->valid));
+	io_read_buf(b, &p->asid, sizeof(p->asid));
+	io_read_buf(b, &p->talid, sizeof(p->talid));
+	io_read_buf(b, &p->ipsz, sizeof(p->ipsz));
+	io_read_buf(b, &p->expires, sizeof(p->expires));
 
 	if ((p->ips = calloc(p->ipsz, sizeof(struct roa_ip))) == NULL)
 		err(1, NULL);
+	io_read_buf(b, p->ips, p->ipsz * sizeof(p->ips[0]));
 
-	for (i = 0; i < p->ipsz; i++) {
-		io_simple_read(fd, &p->ips[i].afi, sizeof(enum afi));
-		io_simple_read(fd, &p->ips[i].maxlength, sizeof(size_t));
-		io_simple_read(fd, &p->ips[i].min, sizeof(p->ips[i].min));
-		io_simple_read(fd, &p->ips[i].max, sizeof(p->ips[i].max));
-		ip_addr_read(fd, &p->ips[i].addr);
-	}
-
-	io_str_read(fd, &p->aia);
-	io_str_read(fd, &p->aki);
-	io_str_read(fd, &p->ski);
-	io_str_read(fd, &p->tal);
-	assert(p->aia && p->aki && p->ski && p->tal);
+	io_read_str(b, &p->aia);
+	io_read_str(b, &p->aki);
+	io_read_str(b, &p->ski);
+	assert(p->aia && p->aki && p->ski);
 
 	return p;
 }
@@ -500,8 +482,7 @@ roa_insert_vrps(struct vrp_tree *tree, s
 		v->addr = roa->ips[i].addr;
 		v->maxlength = roa->ips[i].maxlength;
 		v->asid = roa->asid;
-		if ((v->tal = strdup(roa->tal)) == NULL)
-			err(1, NULL);
+		v->talid = roa->talid;
 		v->expires = roa->expires;
 
 		/*
@@ -513,12 +494,9 @@ roa_insert_vrps(struct vrp_tree *tree, s
 			/* already exists */
 			if (found->expires < v->expires) {
 				/* update found with preferred data */
-				found->expires = roa->expires;
-				free(found->tal);
-				found->tal = v->tal;
-				v->tal = NULL;
+				found->talid = v->talid;
+				found->expires = v->expires;
 			}
-			free(v->tal);
 			free(v);
 		} else
 			(*uniqs)++;
Index: usr.sbin/rpki-client/rpki-client.8
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rpki-client.8,v
retrieving revision 1.47
diff -u -p -u -r1.47 rpki-client.8
--- usr.sbin/rpki-client/rpki-client.8	1 Sep 2021 08:17:37 -0000	1.47
+++ usr.sbin/rpki-client/rpki-client.8	6 Nov 2021 18:46:38 -0000
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: September 1 2021 $
+.Dd $Mdocdate: October 26 2021 $
 .Dt RPKI-CLIENT 8
 .Os
 .Sh NAME
@@ -233,10 +233,13 @@ A Profile for X.509 PKIX Resource Certif
 Signed Object Template for the Resource Public Key Infrastructure (RPKI).
 .It RFC 6493
 The Resource Public Key Infrastructure (RPKI) Ghostbusters Record.
-.It RFC 7730
-Resource Public Key Infrastructure (RPKI) Trust Anchor Locator.
 .It RFC 8182
 The RPKI Repository Delta Protocol (RRDP).
+.It RFC 8209
+A Profile for BGPsec Router Certificates, Certificate Revocation Lists, and
+Certification Requests.
+.It RFC 8630
+Resource Public Key Infrastructure (RPKI) Trust Anchor Locator.
 .El
 .\" .Sh HISTORY
 .Sh AUTHORS
Index: usr.sbin/rpki-client/rrdp.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rrdp.c,v
retrieving revision 1.11
diff -u -p -u -r1.11 rrdp.c
--- usr.sbin/rpki-client/rrdp.c	31 Aug 2021 15:18:53 -0000	1.11
+++ usr.sbin/rpki-client/rrdp.c	6 Nov 2021 18:46:45 -0000
@@ -80,7 +80,7 @@ struct publish_xml {
 	char			*uri;
 	char			*data;
 	char			 hash[SHA256_DIGEST_LENGTH];
-	int			 data_length;
+	size_t			 data_length;
 	enum publish_type	 type;
 };
 
@@ -140,12 +140,11 @@ rrdp_done(size_t id, int ok)
 	enum rrdp_msg type = RRDP_END;
 	struct ibuf *b;
 
-	if ((b = ibuf_open(sizeof(type) + sizeof(id) + sizeof(ok))) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &type, sizeof(type));
 	io_simple_buffer(b, &id, sizeof(id));
 	io_simple_buffer(b, &ok, sizeof(ok));
-	ibuf_close(&msgq, b);
+	io_close_buffer(&msgq, b);
 }
 
 /*
@@ -162,13 +161,12 @@ rrdp_http_req(size_t id, const char *uri
 	enum rrdp_msg type = RRDP_HTTP_REQ;
 	struct ibuf *b;
 
-	if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &type, sizeof(type));
 	io_simple_buffer(b, &id, sizeof(id));
 	io_str_buffer(b, uri);
 	io_str_buffer(b, last_mod);
-	ibuf_close(&msgq, b);
+	io_close_buffer(&msgq, b);
 }
 
 /*
@@ -180,14 +178,13 @@ rrdp_state_send(struct rrdp *s)
 	enum rrdp_msg type = RRDP_SESSION;
 	struct ibuf *b;
 
-	if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL)
-		err(1, NULL);
+	b = io_new_buffer();
 	io_simple_buffer(b, &type, sizeof(type));
 	io_simple_buffer(b, &s->id, sizeof(s->id));
 	io_str_buffer(b, s->current.session_id);
 	io_simple_buffer(b, &s->current.serial, sizeof(s->current.serial));
 	io_str_buffer(b, s->current.last_mod);
-	ibuf_close(&msgq, b);
+	io_close_buffer(&msgq, b);
 }
 
 static struct rrdp *
@@ -211,7 +208,8 @@ rrdp_new(size_t id, char *local, char *n
 	if ((s->parser = XML_ParserCreate("US-ASCII")) == NULL)
 		err(1, "XML_ParserCreate");
 
-	s->nxml = new_notification_xml(s->parser, &s->repository, &s->current);
+	s->nxml = new_notification_xml(s->parser, &s->repository, &s->current,
+	    notify);
 
 	TAILQ_INSERT_TAIL(&states, s, entry);
 
@@ -381,31 +379,37 @@ rrdp_finished(struct rrdp *s)
 static void
 rrdp_input_handler(int fd)
 {
+	static struct ibuf *inbuf;
 	char *local, *notify, *session_id, *last_mod;
+	struct ibuf *b;
 	struct rrdp *s;
 	enum rrdp_msg type;
 	enum http_result res;
 	long long serial;
 	size_t id;
-	int infd, ok;
+	int ok;
 
-	infd = io_recvfd(fd, &type, sizeof(type));
-	io_simple_read(fd, &id, sizeof(id));
+	b = io_buf_recvfd(fd, &inbuf);
+	if (b == NULL)
+		return;
+
+	io_read_buf(b, &type, sizeof(type));
+	io_read_buf(b, &id, sizeof(id));
 
 	switch (type) {
 	case RRDP_START:
-		io_str_read(fd, &local);
-		io_str_read(fd, &notify);
-		io_str_read(fd, &session_id);
-		io_simple_read(fd, &serial, sizeof(serial));
-		io_str_read(fd, &last_mod);
-		if (infd != -1)
-			errx(1, "received unexpected fd %d", infd);
+		io_read_str(b, &local);
+		io_read_str(b, &notify);
+		io_read_str(b, &session_id);
+		io_read_buf(b, &serial, sizeof(serial));
+		io_read_str(b, &last_mod);
+		if (b->fd != -1)
+			errx(1, "received unexpected fd");
 
 		s = rrdp_new(id, local, notify, session_id, serial, last_mod);
 		break;
 	case RRDP_HTTP_INI:
-		if (infd == -1)
+		if (b->fd == -1)
 			errx(1, "expected fd not received");
 		s = rrdp_get(id);
 		if (s == NULL)
@@ -413,13 +417,13 @@ rrdp_input_handler(int fd)
 		if (s->state != RRDP_STATE_WAIT)
 			errx(1, "%s: bad internal state", s->local);
 
-		s->infd = infd;
+		s->infd = b->fd;
 		s->state = RRDP_STATE_PARSE;
 		break;
 	case RRDP_HTTP_FIN:
-		io_simple_read(fd, &res, sizeof(res));
-		io_str_read(fd, &last_mod);
-		if (infd != -1)
+		io_read_buf(b, &res, sizeof(res));
+		io_read_str(b, &last_mod);
+		if (b->fd != -1)
 			errx(1, "received unexpected fd");
 
 		s = rrdp_get(id);
@@ -437,12 +441,11 @@ rrdp_input_handler(int fd)
 		s = rrdp_get(id);
 		if (s == NULL)
 			errx(1, "rrdp session %zu does not exist", id);
-		if (infd != -1)
-			errx(1, "received unexpected fd %d", infd);
-		io_simple_read(fd, &ok, sizeof(ok));
-		if (ok != 1) {
+		if (b->fd != -1)
+			errx(1, "received unexpected fd");
+		io_read_buf(b, &ok, sizeof(ok));
+		if (ok != 1)
 			s->file_failed++;
-		}
 		s->file_pending--;
 		if (s->file_pending == 0)
 			rrdp_finished(s);
@@ -450,6 +453,7 @@ rrdp_input_handler(int fd)
 	default:
 		errx(1, "unexpected message %d", type);
 	}
+	ibuf_free(b);
 }
 
 static void
@@ -560,14 +564,12 @@ proc_rrdp(int fd)
 		if (pfds[0].revents & POLLHUP)
 			break;
 		if (pfds[0].revents & POLLOUT) {
-			io_socket_nonblocking(fd);
 			switch (msgbuf_write(&msgq)) {
 			case 0:
 				errx(1, "write: connection closed");
 			case -1:
 				err(1, "write");
 			}
-			io_socket_blocking(fd);
 		}
 		if (pfds[0].revents & POLLIN)
 			rrdp_input_handler(fd);
@@ -621,27 +623,34 @@ free_publish_xml(struct publish_xml *pxm
  * Add buf to the base64 data string, ensure that this remains a proper
  * string by NUL-terminating the string.
  */
-void
+int
 publish_add_content(struct publish_xml *pxml, const char *buf, int length)
 {
-	int new_length;
+	size_t newlen, outlen;
 
 	/*
 	 * optmisiation, this often gets called with '\n' as the
 	 * only data... seems wasteful
 	 */
 	if (length == 1 && buf[0] == '\n')
-		return;
+		return 0;
 
 	/* append content to data */
-	new_length = pxml->data_length + length;
-	pxml->data = realloc(pxml->data, new_length + 1);
+	if (SIZE_MAX - length - 1 <= pxml->data_length)
+		return -1;
+	newlen = pxml->data_length + length;
+	if (base64_decode_len(newlen, &outlen) == -1 ||
+	    outlen > MAX_FILE_SIZE)
+		return -1;
+
+	pxml->data = realloc(pxml->data, newlen + 1);
 	if (pxml->data == NULL)
 		err(1, "%s", __func__);
 
 	memcpy(pxml->data + pxml->data_length, buf, length);
-	pxml->data[new_length] = '\0';
-	pxml->data_length = new_length;
+	pxml->data[newlen] = '\0';
+	pxml->data_length = newlen;
+	return 0;
 }
 
 /*
@@ -660,13 +669,13 @@ publish_done(struct rrdp *s, struct publ
 	size_t datasz = 0;
 
 	if (pxml->data_length > 0)
-		if ((base64_decode(pxml->data, &data, &datasz)) == -1)
+		if ((base64_decode(pxml->data, pxml->data_length,
+		    &data, &datasz)) == -1)
 			return -1;
 
 	/* only send files if the fetch did not fail already */
 	if (s->file_failed == 0) {
-		if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL)
-			err(1, NULL);
+		b = io_new_buffer();
 		io_simple_buffer(b, &type, sizeof(type));
 		io_simple_buffer(b, &s->id, sizeof(s->id));
 		io_simple_buffer(b, &pxml->type, sizeof(pxml->type));
@@ -674,7 +683,7 @@ publish_done(struct rrdp *s, struct publ
 			io_simple_buffer(b, &pxml->hash, sizeof(pxml->hash));
 		io_str_buffer(b, pxml->uri);
 		io_buf_buffer(b, data, datasz);
-		ibuf_close(&msgq, b);
+		io_close_buffer(&msgq, b);
 		s->file_pending++;
 	}
 
Index: usr.sbin/rpki-client/rrdp.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rrdp.h,v
retrieving revision 1.3
diff -u -p -u -r1.3 rrdp.h
--- usr.sbin/rpki-client/rrdp.h	9 May 2021 11:19:30 -0000	1.3
+++ usr.sbin/rpki-client/rrdp.h	6 Nov 2021 14:20:41 -0000
@@ -1,3 +1,20 @@
+/*	$OpenBSD: rrdp.h,v 1.6 2021/10/29 09:27:36 claudio Exp $ */
+/*
+ * Copyright (c) 2020 Nils Fisher <nils_fisher@hotmail.com>
+ * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
 #ifndef _RRDPH_
 #define _RRDPH_
 
@@ -29,7 +46,7 @@ struct publish_xml;
 struct publish_xml	*new_publish_xml(enum publish_type, char *,
 			    char *, size_t);
 void			 free_publish_xml(struct publish_xml *);
-void			 publish_add_content(struct publish_xml *,
+int			 publish_add_content(struct publish_xml *,
 			    const char *, int);
 int			 publish_done(struct rrdp *, struct publish_xml *);
 
@@ -37,7 +54,8 @@ int			 publish_done(struct rrdp *, struc
 struct notification_xml;
 
 struct notification_xml	*new_notification_xml(XML_Parser,
-			    struct rrdp_session *, struct rrdp_session *);
+			    struct rrdp_session *, struct rrdp_session *,
+			    const char *);
 void			 free_notification_xml(struct notification_xml *);
 enum rrdp_task		 notification_done(struct notification_xml *,
 			    char *);
Index: usr.sbin/rpki-client/rrdp_delta.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rrdp_delta.c,v
retrieving revision 1.2
diff -u -p -u -r1.2 rrdp_delta.c
--- usr.sbin/rpki-client/rrdp_delta.c	11 May 2021 11:48:02 -0000	1.2
+++ usr.sbin/rpki-client/rrdp_delta.c	6 Nov 2021 18:47:27 -0000
@@ -86,7 +86,7 @@ start_delta_elem(struct delta_xml *dxml,
 				continue;
 		}
 		PARSE_FAIL(p, "parse failed - non conforming "
-		    "attribute found in delta elem");
+		    "attribute '%s' found in delta elem", attr[i]);
 	}
 	if (!(has_xmlns && dxml->version && dxml->session_id && dxml->serial))
 		PARSE_FAIL(p, "parse failed - incomplete delta attributes");
@@ -135,7 +135,7 @@ start_publish_withdraw_elem(struct delta
 				continue;
 		}
 		PARSE_FAIL(p, "parse failed - non conforming "
-		    "attribute found in publish/withdraw elem");
+		    "attribute '%s' found in publish/withdraw elem", attr[i]);
 	}
 	if (hasUri != 1)
 		PARSE_FAIL(p,
@@ -217,9 +217,21 @@ static void
 delta_content_handler(void *data, const char *content, int length)
 {
 	struct delta_xml *dxml = data;
+	XML_Parser p = dxml->parser;
 
 	if (dxml->scope == DELTA_SCOPE_PUBLISH)
-		publish_add_content(dxml->pxml, content, length);
+		if (publish_add_content(dxml->pxml, content, length) == -1)
+			PARSE_FAIL(p, "parse failed - content too big");
+}
+
+static void
+delta_doctype_handler(void *data, const char *doctypeName,
+    const char *sysid, const char *pubid, int subset)
+{
+	struct delta_xml *dxml = data;
+	XML_Parser p = dxml->parser;
+
+	PARSE_FAIL(p, "parse failed - DOCTYPE not allowed");
 }
 
 struct delta_xml *
@@ -240,6 +252,7 @@ new_delta_xml(XML_Parser p, struct rrdp_
 	    delta_xml_elem_end);
 	XML_SetCharacterDataHandler(dxml->parser, delta_content_handler);
 	XML_SetUserData(dxml->parser, dxml);
+	XML_SetDoctypeDeclHandler(dxml->parser, delta_doctype_handler, NULL);
 
 	return dxml;
 }
Index: usr.sbin/rpki-client/rrdp_notification.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rrdp_notification.c,v
retrieving revision 1.7
diff -u -p -u -r1.7 rrdp_notification.c
--- usr.sbin/rpki-client/rrdp_notification.c	14 Sep 2021 11:38:44 -0000	1.7
+++ usr.sbin/rpki-client/rrdp_notification.c	6 Nov 2021 18:47:38 -0000
@@ -53,6 +53,7 @@ struct notification_xml {
 	XML_Parser		 parser;
 	struct rrdp_session	*repository;
 	struct rrdp_session	*current;
+	const char		*notifyuri;
 	char			*session_id;
 	char			*snapshot_uri;
 	char			 snapshot_hash[SHA256_DIGEST_LENGTH];
@@ -139,7 +140,7 @@ start_notification_elem(struct notificat
 				continue;
 		}
 		PARSE_FAIL(p, "parse failed - non conforming "
-		    "attribute found in notification elem");
+		    "attribute '%s' found in notification elem", attr[i]);
 	}
 	if (!(has_xmlns && nxml->version && nxml->session_id && nxml->serial))
 		PARSE_FAIL(p, "parse failed - incomplete "
@@ -171,7 +172,8 @@ start_snapshot_elem(struct notification_
 	for (i = 0; attr[i]; i += 2) {
 		if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) {
 			if (valid_uri(attr[i + 1], strlen(attr[i + 1]),
-			    "https://")) {
+			    "https://") &&
+			    valid_origin(attr[i + 1], nxml->notifyuri)) {
 				nxml->snapshot_uri = xstrdup(attr[i + 1]);
 				continue;
 			}
@@ -182,7 +184,7 @@ start_snapshot_elem(struct notification_
 				continue;
 		}
 		PARSE_FAIL(p, "parse failed - non conforming "
-		    "attribute found in snapshot elem");
+		    "attribute '%s' found in snapshot elem", attr[i]);
 	}
 	if (hasUri != 1 || hasHash != 1)
 		PARSE_FAIL(p, "parse failed - incomplete snapshot attributes");
@@ -216,7 +218,8 @@ start_delta_elem(struct notification_xml
 	for (i = 0; attr[i]; i += 2) {
 		if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) {
 			if (valid_uri(attr[i + 1], strlen(attr[i + 1]),
-			    "https://")) {
+			    "https://") &&
+			    valid_origin(attr[i + 1], nxml->notifyuri)) {
 				delta_uri = attr[i + 1];
 				continue;
 			}
@@ -235,7 +238,7 @@ start_delta_elem(struct notification_xml
 				continue;
 		}
 		PARSE_FAIL(p, "parse failed - non conforming "
-		    "attribute found in snapshot elem");
+		    "attribute '%s' found in snapshot elem", attr[i]);
 	}
 	/* Only add to the list if we are relevant */
 	if (hasUri != 1 || hasHash != 1 || delta_serial == 0)
@@ -304,9 +307,19 @@ notification_xml_elem_end(void *data, co
 		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
 }
 
+static void
+notification_doctype_handler(void *data, const char *doctypeName,
+    const char *sysid, const char *pubid, int subset)
+{
+	struct notification_xml *nxml = data;
+	XML_Parser p = nxml->parser;
+
+	PARSE_FAIL(p, "parse failed - DOCTYPE not allowed");
+}
+
 struct notification_xml *
 new_notification_xml(XML_Parser p, struct rrdp_session *repository,
-    struct rrdp_session *current)
+    struct rrdp_session *current, const char *notifyuri)
 {
 	struct notification_xml *nxml;
 
@@ -316,10 +329,13 @@ new_notification_xml(XML_Parser p, struc
 	nxml->parser = p;
 	nxml->repository = repository;
 	nxml->current = current;
+	nxml->notifyuri = notifyuri;
 
 	XML_SetElementHandler(nxml->parser, notification_xml_elem_start,
 	    notification_xml_elem_end);
 	XML_SetUserData(nxml->parser, nxml);
+	XML_SetDoctypeDeclHandler(nxml->parser, notification_doctype_handler,
+	    NULL);
 
 	return nxml;
 }
Index: usr.sbin/rpki-client/rrdp_snapshot.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rrdp_snapshot.c,v
retrieving revision 1.1
diff -u -p -u -r1.1 rrdp_snapshot.c
--- usr.sbin/rpki-client/rrdp_snapshot.c	1 Apr 2021 16:04:48 -0000	1.1
+++ usr.sbin/rpki-client/rrdp_snapshot.c	6 Nov 2021 18:47:44 -0000
@@ -79,7 +79,7 @@ start_snapshot_elem(struct snapshot_xml 
 		}
 		PARSE_FAIL(p,
 		    "parse failed - non conforming "
-		    "attribute found in snapshot elem");
+		    "attribute '%s' found in snapshot elem", attr[i]);
 	}
 	if (!(has_xmlns && sxml->version && sxml->session_id && sxml->serial))
 		PARSE_FAIL(p,
@@ -193,9 +193,21 @@ static void
 snapshot_content_handler(void *data, const char *content, int length)
 {
 	struct snapshot_xml *sxml = data;
+	XML_Parser p = sxml->parser;
 
 	if (sxml->scope == SNAPSHOT_SCOPE_PUBLISH)
-		publish_add_content(sxml->pxml, content, length);
+		if (publish_add_content(sxml->pxml, content, length) == -1)
+			PARSE_FAIL(p, "parse failed - content too big");
+}
+
+static void
+snapshot_doctype_handler(void *data, const char *doctypeName,
+    const char *sysid, const char *pubid, int subset)
+{
+	struct snapshot_xml *sxml = data;
+	XML_Parser p = sxml->parser;
+
+	PARSE_FAIL(p, "parse failed - DOCTYPE not allowed");
 }
 
 struct snapshot_xml *
@@ -216,6 +228,8 @@ new_snapshot_xml(XML_Parser p, struct rr
 	    snapshot_xml_elem_end);
 	XML_SetCharacterDataHandler(sxml->parser, snapshot_content_handler);
 	XML_SetUserData(sxml->parser, sxml);
+	XML_SetDoctypeDeclHandler(sxml->parser, snapshot_doctype_handler,
+	    NULL);
 
 	return sxml;
 }
Index: usr.sbin/rpki-client/rsync.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/rsync.c,v
retrieving revision 1.25
diff -u -p -u -r1.25 rsync.c
--- usr.sbin/rpki-client/rsync.c	1 Sep 2021 12:26:26 -0000	1.25
+++ usr.sbin/rpki-client/rsync.c	6 Nov 2021 18:47:53 -0000
@@ -33,6 +33,9 @@
 
 #include "extern.h"
 
+#define	__STRINGIFY(x)	#x
+#define	STRINGIFY(x)	__STRINGIFY(x)
+
 /*
  * A running rsync process.
  * We can have multiple of these simultaneously and need to keep track
@@ -116,10 +119,11 @@ proc_child(int signal)
 void
 proc_rsync(char *prog, char *bind_addr, int fd)
 {
-	size_t			 i, idsz = 0;
+	size_t			 i, idsz = 0, nprocs = 0;
 	int			 rc = 0;
 	struct pollfd		 pfd;
 	struct msgbuf		 msgq;
+	struct ibuf		*b, *inbuf = NULL;
 	sigset_t		 mask, oldmask;
 	struct rsyncproc	*ids = NULL;
 
@@ -178,12 +182,13 @@ proc_rsync(char *prog, char *bind_addr, 
 
 	for (;;) {
 		char *uri = NULL, *dst = NULL;
-		ssize_t ssz;
 		size_t id;
 		pid_t pid;
 		int st;
 
-		pfd.events = POLLIN;
+		pfd.events = 0;
+		if (nprocs < MAX_RSYNC_PROCESSES)
+			pfd.events |= POLLIN;
 		if (msgq.queued)
 			pfd.events |= POLLOUT;
 
@@ -199,7 +204,6 @@ proc_rsync(char *prog, char *bind_addr, 
 			 */
 
 			while ((pid = waitpid(WAIT_ANY, &st, WNOHANG)) > 0) {
-				struct ibuf *b;
 				int ok = 1;
 
 				for (i = 0; i < idsz; i++)
@@ -217,17 +221,16 @@ proc_rsync(char *prog, char *bind_addr, 
 					ok = 0;
 				}
 
-				b = ibuf_open(sizeof(size_t) + sizeof(ok));
-				if (b == NULL)
-					err(1, NULL);
+				b = io_new_buffer();
 				io_simple_buffer(b, &ids[i].id, sizeof(size_t));
 				io_simple_buffer(b, &ok, sizeof(ok));
-				ibuf_close(&msgq, b);
+				io_close_buffer(&msgq, b);
 
 				free(ids[i].uri);
 				ids[i].uri = NULL;
 				ids[i].pid = 0;
 				ids[i].id = 0;
+				nprocs--;
 			}
 			if (pid == -1 && errno != ECHILD)
 				err(1, "waitpid");
@@ -243,23 +246,24 @@ proc_rsync(char *prog, char *bind_addr, 
 			}
 		}
 
+		/* connection closed */
+		if (pfd.revents & POLLHUP)
+			break;
+
 		if (!(pfd.revents & POLLIN))
 			continue;
 
-		/*
-		 * Read til the parent exits.
-		 * That will mean that we can safely exit.
-		 */
-
-		if ((ssz = read(fd, &id, sizeof(size_t))) == -1)
-			err(1, "read");
-		if (ssz == 0)
-			break;
+		b = io_buf_read(fd, &inbuf);
+		if (b == NULL)
+			continue;
 
 		/* Read host and module. */
+		io_read_buf(b, &id, sizeof(id));
+		io_read_str(b, &dst);
+		io_read_str(b, &uri);
+
+		ibuf_free(b);
 
-		io_str_read(fd, &dst);
-		io_str_read(fd, &uri);
 		assert(dst);
 		assert(uri);
 
@@ -277,6 +281,7 @@ proc_rsync(char *prog, char *bind_addr, 
 			args[i++] = (char *)prog;
 			args[i++] = "-rt";
 			args[i++] = "--no-motd";
+			args[i++] = "--max-size=" STRINGIFY(MAX_FILE_SIZE);
 			args[i++] = "--timeout=180";
 			args[i++] = "--include=*/";
 			args[i++] = "--include=*.cer";
@@ -312,6 +317,7 @@ proc_rsync(char *prog, char *bind_addr, 
 		ids[i].id = id;
 		ids[i].pid = pid;
 		ids[i].uri = uri;
+		nprocs++;
 
 		/* Clean up temporary values. */
 
Index: usr.sbin/rpki-client/tal.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/tal.c,v
retrieving revision 1.30
diff -u -p -u -r1.30 tal.c
--- usr.sbin/rpki-client/tal.c	1 Apr 2021 06:43:23 -0000	1.30
+++ usr.sbin/rpki-client/tal.c	6 Nov 2021 19:02:45 -0000
@@ -41,7 +41,7 @@ tal_cmp(const void *a, const void *b)
  * The pointer must be freed with tal_free().
  */
 static struct tal *
-tal_parse_buffer(const char *fn, char *buf)
+tal_parse_buffer(const char *fn, char *buf, size_t len)
 {
 	char		*nl, *line, *f, *file = NULL;
 	unsigned char	*der;
@@ -49,18 +49,33 @@ tal_parse_buffer(const char *fn, char *b
 	int		 rc = 0;
 	struct tal	*tal = NULL;
 	EVP_PKEY	*pkey = NULL;
+	int		 optcomment = 1;
 
 	if ((tal = calloc(1, sizeof(struct tal))) == NULL)
 		err(1, NULL);
 
 	/* Begin with the URI section, comment section already removed. */
-	while ((nl = strchr(buf, '\n')) != NULL) {
+	while ((nl = memchr(buf, '\n', len)) != NULL) {
 		line = buf;
-		*nl = '\0';
 
 		/* advance buffer to next line */
+		len -= nl + 1 - buf;
 		buf = nl + 1;
 
+		/* replace LF and optional CR with NUL, point nl at first NUL */
+		*nl = '\0';
+		if (nl > line && nl[-1] == '\r') {
+			nl[-1] = '\0';
+			nl--;
+		}
+
+		if (optcomment) {
+			/* if this is a comment, just eat the line */
+			if (line[0] == '#')
+				continue;
+			optcomment = 0;
+		}
+
 		/* Zero-length line is end of section. */
 		if (*line == '\0')
 			break;
@@ -112,7 +127,7 @@ tal_parse_buffer(const char *fn, char *b
 	qsort(tal->uri, tal->urisz, sizeof(tal->uri[0]), tal_cmp);
 
 	/* Now the Base64-encoded public key. */
-	if ((base64_decode(buf, &der, &dersz)) == -1) {
+	if ((base64_decode(buf, len, &der, &dersz)) == -1) {
 		warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: "
 		    "bad public key", fn);
 		goto out;
@@ -144,13 +159,13 @@ out:
  * Returns the encoded data or NULL on syntax failure.
  */
 struct tal *
-tal_parse(const char *fn, char *buf)
+tal_parse(const char *fn, char *buf, size_t len)
 {
 	struct tal	*p;
 	const char	*d;
 	size_t		 dlen;
 
-	p = tal_parse_buffer(fn, buf);
+	p = tal_parse_buffer(fn, buf, len);
 	if (p == NULL)
 		return NULL;
 
@@ -170,76 +185,6 @@ tal_parse(const char *fn, char *buf)
 }
 
 /*
- * Read the file named "file" into a returned, NUL-terminated buffer.
- * This replaces CRLF terminators with plain LF, if found, and also
- * elides document-leading comment lines starting with "#".
- * Files may not exceeds 4096 bytes.
- * This function exits on failure, so it always returns a buffer with
- * TAL data.
- */
-char *
-tal_read_file(const char *file)
-{
-	char		*nbuf, *line = NULL, *buf = NULL;
-	FILE		*in;
-	ssize_t		 n, i;
-	size_t		 sz = 0, bsz = 0;
-	int		 optcomment = 1;
-
-	if ((in = fopen(file, "r")) == NULL)
-		err(1, "fopen: %s", file);
-
-	while ((n = getline(&line, &sz, in)) != -1) {
-		/* replace CRLF with just LF */
-		if (n > 1 && line[n - 1] == '\n' && line[n - 2] == '\r') {
-			line[n - 2] = '\n';
-			line[n - 1] = '\0';
-			n--;
-		}
-		if (optcomment) {
-			/* if this is comment, just eat the line */
-			if (line[0] == '#')
-				continue;
-			optcomment = 0;
-			/*
-			 * Empty line is end of section and needs
-			 * to be eaten as well.
-			 */
-			if (line[0] == '\n')
-				continue;
-		}
-
-		/* make sure every line is valid ascii */
-		for (i = 0; i < n; i++)
-			if (!isprint((unsigned char)line[i]) &&
-			    !isspace((unsigned char)line[i]))
-				errx(1, "getline: %s: "
-				    "invalid content", file);
-
-		/* concat line to buf */
-		if ((nbuf = realloc(buf, bsz + n + 1)) == NULL)
-			err(1, NULL);
-		if (buf == NULL)
-			nbuf[0] = '\0';	/* initialize buffer */
-		buf = nbuf;
-		bsz += n + 1;
-		if (strlcat(buf, line, bsz) >= bsz)
-			errx(1, "strlcat overflow");
-		/* limit the buffer size */
-		if (bsz > 4096)
-			errx(1, "%s: file too big", file);
-	}
-
-	free(line);
-	if (ferror(in))
-		err(1, "getline: %s", file);
-	fclose(in);
-	if (buf == NULL)
-		errx(1, "%s: no data", file);
-	return buf;
-}
-
-/*
  * Free a TAL pointer.
  * Safe to call with NULL.
  */
@@ -270,9 +215,10 @@ tal_buffer(struct ibuf *b, const struct 
 {
 	size_t	 i;
 
+	io_simple_buffer(b, &p->id, sizeof(p->id));
 	io_buf_buffer(b, p->pkey, p->pkeysz);
 	io_str_buffer(b, p->descr);
-	io_simple_buffer(b, &p->urisz, sizeof(size_t));
+	io_simple_buffer(b, &p->urisz, sizeof(p->urisz));
 
 	for (i = 0; i < p->urisz; i++)
 		io_str_buffer(b, p->uri[i]);
@@ -284,7 +230,7 @@ tal_buffer(struct ibuf *b, const struct 
  * A returned pointer must be freed with tal_free().
  */
 struct tal *
-tal_read(int fd)
+tal_read(struct ibuf *b)
 {
 	size_t		 i;
 	struct tal	*p;
@@ -292,18 +238,19 @@ tal_read(int fd)
 	if ((p = calloc(1, sizeof(struct tal))) == NULL)
 		err(1, NULL);
 
-	io_buf_read_alloc(fd, (void **)&p->pkey, &p->pkeysz);
+	io_read_buf(b, &p->id, sizeof(p->id));
+	io_read_buf_alloc(b, (void **)&p->pkey, &p->pkeysz);
+	io_read_str(b, &p->descr);
+	io_read_buf(b, &p->urisz, sizeof(p->urisz));
 	assert(p->pkeysz > 0);
-	io_str_read(fd, &p->descr);
 	assert(p->descr);
-	io_simple_read(fd, &p->urisz, sizeof(size_t));
 	assert(p->urisz > 0);
 
 	if ((p->uri = calloc(p->urisz, sizeof(char *))) == NULL)
 		err(1, NULL);
 
 	for (i = 0; i < p->urisz; i++) {
-		io_str_read(fd, &p->uri[i]);
+		io_read_str(b, &p->uri[i]);
 		assert(p->uri[i]);
 	}
 
Index: usr.sbin/rpki-client/validate.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/validate.c,v
retrieving revision 1.15
diff -u -p -u -r1.15 validate.c
--- usr.sbin/rpki-client/validate.c	16 Aug 2021 10:38:57 -0000	1.15
+++ usr.sbin/rpki-client/validate.c	6 Nov 2021 18:48:12 -0000
@@ -30,14 +30,6 @@
 
 #include "extern.h"
 
-static void
-tracewarn(const struct auth *a)
-{
-
-	for (; a != NULL; a = a->parent)
-		warnx(" ...inheriting from: %s", a->fn);
-}
-
 /*
  * Walk up the chain of certificates trying to match our AS number to
  * one of the allocations in that chain.
@@ -163,8 +155,11 @@ valid_cert(const char *fn, struct auth_t
 		return 0;
 
 	for (i = 0; i < cert->asz; i++) {
-		if (cert->as[i].type == CERT_AS_INHERIT)
+		if (cert->as[i].type == CERT_AS_INHERIT) {
+			if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER)
+				return 0; /* BGPsec doesn't permit inheriting */
 			continue;
+		}
 		min = cert->as[i].type == CERT_AS_ID ?
 		    cert->as[i].id : cert->as[i].range.min;
 		max = cert->as[i].type == CERT_AS_ID ?
@@ -173,7 +168,6 @@ valid_cert(const char *fn, struct auth_t
 			continue;
 		warnx("%s: RFC 6487: uncovered AS: "
 		    "%u--%u", fn, min, max);
-		tracewarn(a);
 		return 0;
 	}
 
@@ -201,7 +195,6 @@ valid_cert(const char *fn, struct auth_t
 			    "(inherit)", fn);
 			break;
 		}
-		tracewarn(a);
 		return 0;
 	}
 
@@ -224,8 +217,7 @@ valid_roa(const char *fn, struct auth_tr
 	if (a == NULL)
 		return 0;
 
-	if ((roa->tal = strdup(a->tal)) == NULL)
-		err(1, NULL);
+	roa->talid = a->cert->talid;
 
 	for (i = 0; i < roa->ipsz; i++) {
 		if (valid_ip(a, roa->ips[i].afi, roa->ips[i].min,
@@ -235,7 +227,6 @@ valid_roa(const char *fn, struct auth_tr
 		    roa->ips[i].afi, buf, sizeof(buf));
 		warnx("%s: RFC 6482: uncovered IP: "
 		    "%s", fn, buf);
-		tracewarn(a);
 		return 0;
 	}
 
@@ -243,6 +234,40 @@ valid_roa(const char *fn, struct auth_tr
 }
 
 /*
+ * Validate a filename listed on a Manifest.
+ * draft-ietf-sidrops-6486bis section 4.2.2
+ * Returns 1 if filename is valid, otherwise 0.
+ */
+int
+valid_filename(const char *fn)
+{
+	size_t			 sz;
+	const unsigned char	*c;
+
+	sz = strlen(fn);
+	if (sz < 5)
+		return 0;
+
+	for (c = fn; *c != '\0'; ++c)
+		if (!isalnum(*c) && *c != '-' && *c != '_' && *c != '.')
+			return 0;
+
+	if (strchr(fn, '.') != strrchr(fn, '.'))
+		return 0;
+
+	if (strcasecmp(fn + sz - 4, ".cer") == 0)
+		return 1;
+	if (strcasecmp(fn + sz - 4, ".crl") == 0)
+		return 1;
+	if (strcasecmp(fn + sz - 4, ".gbr") == 0)
+		return 1;
+	if (strcasecmp(fn + sz - 4, ".roa") == 0)
+		return 1;
+
+	return 0;
+}
+
+/*
  * Validate a file by verifying the SHA256 hash of that file.
  * Returns 1 if valid, 0 otherwise.
  */
@@ -284,6 +309,9 @@ valid_uri(const char *uri, size_t usz, c
 {
 	size_t s;
 
+	if (usz > MAX_URI_LENGTH)
+		return 0;
+
 	for (s = 0; s < usz; s++)
 		if (!isalnum((unsigned char)uri[s]) &&
 		    !ispunct((unsigned char)uri[s]))
@@ -297,6 +325,30 @@ valid_uri(const char *uri, size_t usz, c
 
 	/* do not allow files or directories to start with a '.' */
 	if (strstr(uri, "/.") != NULL)
+		return 0;
+
+	return 1;
+}
+
+/*
+ * Validate that a URI has the same host as the URI passed in proto.
+ * Returns 1 if valid, 0 otherwise.
+ */
+int
+valid_origin(const char *uri, const char *proto)
+{
+	const char *to;
+
+	/* extract end of host from proto URI */
+	to = strstr(proto, "://");
+	if (to == NULL)
+		return 0;
+	to += strlen("://");
+	if ((to = strchr(to, '/')) == NULL)
+		return 0;
+
+	/* compare hosts including the / for the start of the path section */
+	if (strncasecmp(uri, proto, to - proto + 1) != 0)
 		return 0;
 
 	return 1;
Index: usr.sbin/rpki-client/version.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/version.h,v
retrieving revision 1.5
diff -u -p -u -r1.5 version.h
--- usr.sbin/rpki-client/version.h	21 Sep 2021 12:41:05 -0000	1.5
+++ usr.sbin/rpki-client/version.h	8 Nov 2021 13:36:33 -0000
@@ -1,3 +1,3 @@
 /* $OpenBSD: version.h,v 1.5 2021/09/21 12:41:05 benno Exp $ */
 
-#define RPKI_VERSION	"7.3"
+#define RPKI_VERSION	"7.5"
Index: usr.sbin/rpki-client/x509.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/x509.c,v
retrieving revision 1.21
diff -u -p -u -r1.21 x509.c
--- usr.sbin/rpki-client/x509.c	1 Apr 2021 06:43:23 -0000	1.21
+++ usr.sbin/rpki-client/x509.c	6 Nov 2021 18:48:28 -0000
@@ -1,5 +1,6 @@
 /*	$OpenBSD: x509.c,v 1.21 2021/04/01 06:43:23 claudio Exp $ */
 /*
+ * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -24,10 +25,20 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <openssl/evp.h>
 #include <openssl/x509v3.h>
 
 #include "extern.h"
 
+static ASN1_OBJECT	*bgpsec_oid;	/* id-kp-bgpsec-router */
+
+static void
+init_oid(void)
+{
+	if ((bgpsec_oid = OBJ_txt2obj("1.3.6.1.5.5.7.3.30", 1)) == NULL)
+		errx(1, "OBJ_txt2obj for %s failed", "1.3.6.1.5.5.7.3.30");
+}
+
 /*
  * Parse X509v3 authority key identifier (AKI), RFC 6487 sec. 4.8.3.
  * Returns the AKI or NULL if it could not be parsed.
@@ -79,6 +90,7 @@ x509_get_aki(X509 *x, int ta, const char
 	}
 
 	res = hex_encode(d, dsz);
+
 out:
 	AUTHORITY_KEYID_free(akid);
 	return res;
@@ -125,6 +137,109 @@ out:
 }
 
 /*
+ * Check the certificate's purpose: CA or BGPsec Router.
+ * Return a member of enum cert_purpose.
+ */
+enum cert_purpose
+x509_get_purpose(X509 *x, const char *fn)
+{
+	EXTENDED_KEY_USAGE		*eku = NULL;
+	int				 crit;
+	enum cert_purpose		 purpose = 0;
+
+	if (X509_check_ca(x) == 1) {
+		purpose = CERT_PURPOSE_CA;
+		goto out;
+	}
+
+	eku = X509_get_ext_d2i(x, NID_ext_key_usage, &crit, NULL);
+	if (eku == NULL) {
+		warnx("%s: EKU: extension missing", fn);
+		goto out;
+	}
+	if (crit != 0) {
+		warnx("%s: EKU: extension must not be marked critical", fn);
+		goto out;
+	}
+	if (sk_ASN1_OBJECT_num(eku) != 1) {
+		warnx("%s: EKU: expected 1 purpose, have %d", fn,
+		    sk_ASN1_OBJECT_num(eku));
+		goto out;
+	}
+
+	if (bgpsec_oid == NULL)
+		init_oid();
+
+	if (OBJ_cmp(bgpsec_oid, sk_ASN1_OBJECT_value(eku, 0)) == 0) {
+		purpose = CERT_PURPOSE_BGPSEC_ROUTER;
+		goto out;
+	}
+
+ out:
+	EXTENDED_KEY_USAGE_free(eku);
+	return purpose;
+}
+
+/*
+ * Extract Subject Public Key Info (SPKI) from BGPsec X.509 Certificate.
+ * Returns NULL on failure, on success return the SPKI as base64 encoded pubkey
+ */
+char *
+x509_get_pubkey(X509 *x, const char *fn)
+{
+	EVP_PKEY	*pkey;
+	EC_KEY		*eckey;
+	int		 nid;
+	const char	*cname;
+	uint8_t		*pubkey = NULL;
+	char		*res = NULL;
+	int		 len;
+
+	pkey = X509_get0_pubkey(x);
+	if (pkey == NULL) {
+		warnx("%s: X509_get_pubkey failed in %s", fn, __func__);
+		goto out;
+	}
+	if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) {
+		warnx("%s: Expected EVP_PKEY_EC, got %d", fn,
+		    EVP_PKEY_base_id(pkey));
+		goto out;
+	}
+
+	eckey = EVP_PKEY_get0_EC_KEY(pkey);
+	if (eckey == NULL) {
+		warnx("%s: Incorrect key type", fn);
+		goto out;
+	}
+
+	nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey));
+	if (nid != NID_X9_62_prime256v1) {
+		if ((cname = EC_curve_nid2nist(nid)) == NULL)
+			cname = OBJ_nid2sn(nid);
+		warnx("%s: Expected P-256, got %s", fn, cname);
+		goto out;
+	}
+
+	if (!EC_KEY_check_key(eckey)) {
+		warnx("%s: EC_KEY_check_key failed in %s", fn, __func__);
+		goto out;
+	}
+
+	len = i2d_PUBKEY(pkey, &pubkey);
+	if (len <= 0) {
+		warnx("%s: i2d_PUBKEY failed in %s", fn, __func__);
+		goto out;
+	}
+
+	if (base64_encode(pubkey, len, &res) == -1)
+		errx(1, "base64_encode failed in %s", __func__);
+
+ out:
+	free(pubkey);
+	return res;
+}
+
+/*
  * Parse the Authority Information Access (AIA) extension
  * See RFC 6487, section 4.8.7 for details.
  * Returns NULL on failure, on success returns the AIA URI
@@ -167,6 +282,13 @@ x509_get_aia(X509 *x, const char *fn)
 		goto out;
 	}
 
+	if (ASN1_STRING_length(ad->location->d.uniformResourceIdentifier)
+	    > MAX_URI_LENGTH) {
+		warnx("%s: RFC 6487 section 4.8.7: AIA: "
+		    "URI exceeds max length of %d", fn, MAX_URI_LENGTH);
+		goto out;
+	}
+
 	aia = strndup(
 	    ASN1_STRING_get0_data(ad->location->d.uniformResourceIdentifier),
 	    ASN1_STRING_length(ad->location->d.uniformResourceIdentifier));
@@ -179,6 +301,34 @@ out:
 }
 
 /*
+ * Extract the expire time (not-after) of a certificate.
+ */
+int
+x509_get_expire(X509 *x, const char *fn, time_t *tt)
+{
+	const ASN1_TIME	*at;
+	struct tm	 expires_tm;
+	time_t		 expires;
+
+	at = X509_get0_notAfter(x);
+	if (at == NULL) {
+		warnx("%s: X509_get0_notafter failed", fn);
+		return 0;
+	}
+	memset(&expires_tm, 0, sizeof(expires_tm));
+	if (ASN1_time_parse(at->data, at->length, &expires_tm, 0) == -1) {
+		warnx("%s: ASN1_time_parse failed", fn);
+		return 0;
+	}
+	if ((expires = mktime(&expires_tm)) == -1)
+		errx(1, "%s: mktime failed", fn);
+
+	*tt = expires;
+	return 1;
+
+}
+
+/*
  * Parse the very specific subset of information in the CRL distribution
  * point extension.
  * See RFC 6487, sectoin 4.8.6 for details.
@@ -236,6 +386,13 @@ x509_get_crl(X509 *x, const char *fn)
 	if (name->type != GEN_URI) {
 		warnx("%s: RFC 6487 section 4.8.6: CRL: "
 		    "want URI type, have %d", fn, name->type);
+		goto out;
+	}
+
+	if (ASN1_STRING_length(name->d.uniformResourceIdentifier)
+	    > MAX_URI_LENGTH) {
+		warnx("%s: RFC 6487 section 4.8.6: CRL: "
+		    "URI exceeds max length of %d", fn, MAX_URI_LENGTH);
 		goto out;
 	}
 
Index: usr.bin/rsync/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/rsync/Makefile,v
retrieving revision 1.11
diff -u -p -u -r1.11 Makefile
--- usr.bin/rsync/Makefile	29 Aug 2021 13:43:46 -0000	1.11
+++ usr.bin/rsync/Makefile	6 Nov 2021 18:51:56 -0000
@@ -1,14 +1,18 @@
 #	$OpenBSD: Makefile,v 1.11 2021/08/29 13:43:46 claudio Exp $
 
 PROG=	openrsync
-SRCS=	blocks.c client.c downloader.c fargs.c flist.c hash.c ids.c \
+SRCS=	blocks.c client.c copy.c downloader.c fargs.c flist.c hash.c ids.c \
 	io.c log.c main.c misc.c mkpath.c mktemp.c receiver.c rmatch.c \
 	rules.c sender.c server.c session.c socket.c symlinks.c uploader.c
-LDADD+= -lcrypto -lm
-DPADD+= ${LIBCRYPTO} ${LIBM}
+LDADD+= -lcrypto -lm -lutil
+DPADD+= ${LIBCRYPTO} ${LIBM} ${LIBUTIL}
 MAN=	openrsync.1
 
-CFLAGS+=-g -W -Wall -Wextra
+CFLAGS+= -Wall -Wextra
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow
+
 
 openrsync.1: rsync.1
 	ln -sf ${.CURDIR}/rsync.1 openrsync.1
Index: usr.bin/rsync/blocks.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/blocks.c,v
retrieving revision 1.20
diff -u -p -u -r1.20 blocks.c
--- usr.bin/rsync/blocks.c	30 Jun 2021 13:10:04 -0000	1.20
+++ usr.bin/rsync/blocks.c	6 Nov 2021 18:52:19 -0000
@@ -331,10 +331,10 @@ blk_recv_ack(char buf[20], const struct 
 	size_t	 pos = 0, sz;
 
 	sz = sizeof(int32_t) + /* index */
-	     sizeof(int32_t) + /* block count */
-	     sizeof(int32_t) + /* block length */
-	     sizeof(int32_t) + /* checksum length */
-	     sizeof(int32_t); /* block remainder */
+	    sizeof(int32_t) + /* block count */
+	    sizeof(int32_t) + /* block length */
+	    sizeof(int32_t) + /* checksum length */
+	    sizeof(int32_t); /* block remainder */
 	assert(sz == 20);
 
 	io_buffer_int(buf, &pos, sz, idx);
@@ -457,9 +457,9 @@ blk_send_ack(struct sess *sess, int fd, 
 	/* Put the entire send routine into a buffer. */
 
 	sz = sizeof(int32_t) + /* block count */
-	     sizeof(int32_t) + /* block length */
-	     sizeof(int32_t) + /* checksum length */
-	     sizeof(int32_t); /* block remainder */
+	    sizeof(int32_t) + /* block length */
+	    sizeof(int32_t) + /* checksum length */
+	    sizeof(int32_t); /* block remainder */
 	assert(sz <= sizeof(buf));
 
 	if (!io_read_buf(sess, fd, buf, sz)) {
Index: usr.bin/rsync/copy.c
===================================================================
RCS file: usr.bin/rsync/copy.c
diff -N usr.bin/rsync/copy.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ usr.bin/rsync/copy.c	6 Nov 2021 14:20:41 -0000
@@ -0,0 +1,90 @@
+/*	$OpenBSD: copy.c,v 1.2 2021/10/24 21:24:17 deraadt Exp $ */
+/*
+ * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>	/* MAXBSIZE */
+
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+/*
+ * Return true if all bytes in buffer are zero.
+ * A buffer of zero lenght is also considered a zero buffer.
+ */
+static int
+iszero(const void *b, size_t len)
+{
+	const unsigned char *c = b;
+
+	for (; len > 0; len--) {
+		if (*c++ != '\0')
+			return 0;
+	}
+	return 1;
+}
+
+static int
+copy_internal(int fromfd, int tofd)
+{
+	char buf[MAXBSIZE];
+	ssize_t r, w;
+
+	while ((r = read(fromfd, buf, sizeof(buf))) > 0) {
+		if (iszero(buf, sizeof(buf))) {
+			if (lseek(tofd, r, SEEK_CUR) == -1)
+				return -1;
+		} else {
+			w = write(tofd, buf, r);
+			if (r != w || w == -1)
+				return -1;
+		}
+	}
+	if (r == -1)
+		return -1;
+	if (ftruncate(tofd, lseek(tofd, 0, SEEK_CUR)) == -1)
+		return -1;
+	return 0;
+}
+
+void
+copy_file(int rootfd, const char *basedir, const struct flist *f)
+{
+	int fromfd, tofd, dfd;
+
+	dfd = openat(rootfd, basedir, O_RDONLY | O_DIRECTORY);
+	if (dfd == -1)
+		err(ERR_FILE_IO, "%s: openat", basedir);
+
+	fromfd = openat(dfd, f->path, O_RDONLY | O_NOFOLLOW);
+	if (fromfd == -1)
+		err(ERR_FILE_IO, "%s/%s: openat", basedir, f->path);
+	close(dfd);
+
+	tofd = openat(rootfd, f->path,
+	    O_WRONLY | O_NOFOLLOW | O_TRUNC | O_CREAT | O_EXCL,
+	    0600);
+	if (tofd == -1)
+		err(ERR_FILE_IO, "%s: openat", f->path);
+
+	if (copy_internal(fromfd, tofd) == -1)
+		err(ERR_FILE_IO, "%s: copy file", f->path);
+
+	close(fromfd);
+	close(tofd);
+}
Index: usr.bin/rsync/downloader.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/downloader.c,v
retrieving revision 1.22
diff -u -p -u -r1.22 downloader.c
--- usr.bin/rsync/downloader.c	30 Jun 2021 13:10:04 -0000	1.22
+++ usr.bin/rsync/downloader.c	6 Nov 2021 18:52:25 -0000
@@ -350,7 +350,7 @@ rsync_downloader(struct download *p, str
 
 		p->state = DOWNLOAD_READ_LOCAL;
 		f = &p->fl[idx];
-		p->ofd = openat(p->rootfd, f->path, O_RDONLY | O_NONBLOCK, 0);
+		p->ofd = openat(p->rootfd, f->path, O_RDONLY | O_NONBLOCK);
 
 		if (p->ofd == -1 && errno != ENOENT) {
 			ERR("%s: openat", f->path);
Index: usr.bin/rsync/extern.h
===================================================================
RCS file: /cvs/src/usr.bin/rsync/extern.h,v
retrieving revision 1.41
diff -u -p -u -r1.41 extern.h
--- usr.bin/rsync/extern.h	1 Sep 2021 09:48:08 -0000	1.41
+++ usr.bin/rsync/extern.h	6 Nov 2021 18:52:31 -0000
@@ -34,6 +34,15 @@
 #define	BLOCK_SIZE_MIN  (700)
 
 /*
+ * Maximum number of base directories that can be used.
+ */
+#define MAX_BASEDIR	20
+
+#define BASE_MODE_COMPARE	1
+#define BASE_MODE_COPY		2
+#define BASE_MODE_LINK		3
+
+/*
  * The sender and receiver use a two-phase synchronisation process.
  * The first uses two-byte hashes; the second, 16-byte.
  * (The second must hold a full MD4 digest.)
@@ -131,10 +140,14 @@ struct	opts {
 	int		 no_motd;		/* --no-motd */
 	int		 numeric_ids;		/* --numeric-ids */
 	int		 one_file_system;	/* -x */
+	int		 alt_base_mode;
+	off_t		 max_size;		/* --max-size */
+	off_t		 min_size;		/* --min-size */
 	char		*rsync_path;		/* --rsync-path */
 	char		*ssh_prog;		/* --rsh or -e */
 	char		*port;			/* --port */
 	char		*address;		/* --address */
+	char		*basedir[MAX_BASEDIR];
 };
 
 enum rule_type {
@@ -298,7 +311,8 @@ int	flist_send(struct sess *, int, int, 
 int	flist_gen_dels(struct sess *, const char *, struct flist **, size_t *,
 	    const struct flist *, size_t);
 
-char	**fargs_cmdline(struct sess *, const struct fargs *, size_t *);
+const char	 *alt_base_mode(int);
+char		**fargs_cmdline(struct sess *, const struct fargs *, size_t *);
 
 int	io_read_buf(struct sess *, int, void *, size_t);
 int	io_read_byte(struct sess *, int, uint8_t *);
@@ -367,6 +381,8 @@ void		 hash_slow(const void *, size_t, u
 		    const struct sess *);
 void		 hash_file(const void *, size_t, unsigned char *,
 		    const struct sess *);
+
+void		 copy_file(int, const char *, const struct flist *);
 
 int		 mkpath(char *);
 
Index: usr.bin/rsync/fargs.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/fargs.c,v
retrieving revision 1.19
diff -u -p -u -r1.19 fargs.c
--- usr.bin/rsync/fargs.c	30 Jun 2021 13:10:04 -0000	1.19
+++ usr.bin/rsync/fargs.c	6 Nov 2021 18:52:39 -0000
@@ -26,6 +26,21 @@
 
 #define	RSYNC_PATH	"rsync"
 
+const char *
+alt_base_mode(int mode)
+{
+	switch (mode) {
+	case BASE_MODE_COMPARE:
+		return "--compare-dest";
+	case BASE_MODE_COPY:
+		return "--copy-dest";
+	case BASE_MODE_LINK:
+		return "--link-dest";
+	default:
+		errx(1, "unknown base mode %d", mode);
+	}
+}
+
 char **
 fargs_cmdline(struct sess *sess, const struct fargs *f, size_t *skip)
 {
@@ -116,6 +131,22 @@ fargs_cmdline(struct sess *sess, const s
 	if (!sess->opts->specials && sess->opts->devices)
 		/* --devices is sent as -D --no-specials */
 		addargs(&args, "--no-specials");
+	if (sess->opts->max_size >= 0)
+		addargs(&args, "--max-size=%lld", sess->opts->max_size);
+	if (sess->opts->min_size >= 0)
+		addargs(&args, "--min-size=%lld", sess->opts->min_size);
+
+	/* only add --compare-dest, etc if this is the sender */
+	if (sess->opts->alt_base_mode != 0 &&
+	    f->mode == FARGS_SENDER) {
+		for (j = 0; j < MAX_BASEDIR; j++) {
+			if (sess->opts->basedir[j] == NULL)
+				break;
+			addargs(&args, "%s=%s",
+			    alt_base_mode(sess->opts->alt_base_mode),
+			    sess->opts->basedir[j]);
+		}
+	}
 
 	/* Terminate with a full-stop for reasons unknown. */
 
Index: usr.bin/rsync/flist.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/flist.c,v
retrieving revision 1.34
diff -u -p -u -r1.34 flist.c
--- usr.bin/rsync/flist.c	2 Sep 2021 21:06:06 -0000	1.34
+++ usr.bin/rsync/flist.c	6 Nov 2021 18:52:45 -0000
@@ -283,7 +283,7 @@ flist_send(struct sess *sess, int fdin, 
 
 		if (sess->mplex_reads &&
 		    io_read_check(fdin) &&
-		     !io_read_flush(sess, fdin)) {
+		    !io_read_flush(sess, fdin)) {
 			ERRX1("io_read_flush");
 			goto out;
 		}
@@ -356,7 +356,7 @@ flist_send(struct sess *sess, int fdin, 
 		/* Conditional part: devices & special files. */
 
 		if ((sess->opts->devices && (S_ISBLK(f->st.mode) ||
-		     S_ISCHR(f->st.mode))) ||
+		    S_ISCHR(f->st.mode))) ||
 		    (sess->opts->specials && (S_ISFIFO(f->st.mode) ||
 		    S_ISSOCK(f->st.mode)))) {
 			if (!io_write_int(sess, fdout, f->st.rdev)) {
@@ -694,7 +694,7 @@ flist_recv(struct sess *sess, int fd, st
 		/* Conditional part: devices & special files. */
 
 		if ((sess->opts->devices && (S_ISBLK(ff->st.mode) ||
-		     S_ISCHR(ff->st.mode))) ||
+		    S_ISCHR(ff->st.mode))) ||
 		    (sess->opts->specials && (S_ISFIFO(ff->st.mode) ||
 		    S_ISSOCK(ff->st.mode)))) {
 			if (!(FLIST_RDEV_SAME & flag)) {
@@ -992,7 +992,7 @@ flist_gen_dirent(struct sess *sess, char
 		/* Optionally copy link information. */
 
 		if (S_ISLNK(ent->fts_statp->st_mode)) {
-			f->link = symlink_read(f->path);
+			f->link = symlink_read(ent->fts_accpath);
 			if (f->link == NULL) {
 				ERRX1("symlink_read");
 				goto out;
Index: usr.bin/rsync/main.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/main.c,v
retrieving revision 1.59
diff -u -p -u -r1.59 main.c
--- usr.bin/rsync/main.c	1 Sep 2021 09:48:08 -0000	1.59
+++ usr.bin/rsync/main.c	6 Nov 2021 18:52:51 -0000
@@ -26,6 +26,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <util.h>
 
 #include "extern.h"
 
@@ -280,24 +281,36 @@ static struct opts	 opts;
 #define OP_INCLUDE	1006
 #define OP_EXCLUDE_FROM	1007
 #define OP_INCLUDE_FROM	1008
+#define OP_COMP_DEST	1009
+#define OP_COPY_DEST	1010
+#define OP_LINK_DEST	1011
+#define OP_MAX_SIZE	1012
+#define OP_MIN_SIZE	1013
 
 const struct option	 lopts[] = {
     { "address",	required_argument, NULL,		OP_ADDRESS },
     { "archive",	no_argument,	NULL,			'a' },
+    { "compare-dest",	required_argument, NULL,		OP_COMP_DEST },
+#if 0
+    { "copy-dest",	required_argument, NULL,		OP_COPY_DEST },
+    { "link-dest",	required_argument, NULL,		OP_LINK_DEST },
+#endif
     { "compress",	no_argument,	NULL,			'z' },
     { "del",		no_argument,	&opts.del,		1 },
     { "delete",		no_argument,	&opts.del,		1 },
     { "devices",	no_argument,	&opts.devices,		1 },
     { "no-devices",	no_argument,	&opts.devices,		0 },
     { "dry-run",	no_argument,	&opts.dry_run,		1 },
-    { "exclude",	required_argument, NULL, 		OP_EXCLUDE },
+    { "exclude",	required_argument, NULL,		OP_EXCLUDE },
     { "exclude-from",	required_argument, NULL,		OP_EXCLUDE_FROM },
     { "group",		no_argument,	&opts.preserve_gids,	1 },
     { "no-group",	no_argument,	&opts.preserve_gids,	0 },
     { "help",		no_argument,	NULL,			'h' },
-    { "include",	required_argument, NULL, 		OP_INCLUDE },
+    { "include",	required_argument, NULL,		OP_INCLUDE },
     { "include-from",	required_argument, NULL,		OP_INCLUDE_FROM },
     { "links",		no_argument,	&opts.preserve_links,	1 },
+    { "max-size",	required_argument, NULL,		OP_MAX_SIZE },
+    { "min-size",	required_argument, NULL,		OP_MIN_SIZE },
     { "no-links",	no_argument,	&opts.preserve_links,	0 },
     { "no-motd",	no_argument,	&opts.no_motd,		1 },
     { "numeric-ids",	no_argument,	&opts.numeric_ids,	1 },
@@ -327,11 +340,12 @@ int
 main(int argc, char *argv[])
 {
 	pid_t		 child;
-	int		 fds[2], sd = -1, rc, c, st, i;
-	struct sess	  sess;
+	int		 fds[2], sd = -1, rc, c, st, i, lidx;
+	size_t		 basedir_cnt = 0;
+	struct sess	 sess;
 	struct fargs	*fargs;
 	char		**args;
-	const char 	*errstr;
+	const char	*errstr;
 
 	/* Global pledge. */
 
@@ -339,7 +353,9 @@ main(int argc, char *argv[])
 	    NULL) == -1)
 		err(ERR_IPC, "pledge");
 
-	while ((c = getopt_long(argc, argv, "Dae:ghlnoprtvxz", lopts, NULL))
+	opts.max_size = opts.min_size = -1;
+
+	while ((c = getopt_long(argc, argv, "Dae:ghlnoprtvxz", lopts, &lidx))
 	    != -1) {
 		switch (c) {
 		case 'D':
@@ -423,6 +439,49 @@ main(int argc, char *argv[])
 		case OP_INCLUDE_FROM:
 			parse_file(optarg, RULE_INCLUDE);
 			break;
+		case OP_COMP_DEST:
+			if (opts.alt_base_mode !=0 &&
+			    opts.alt_base_mode != BASE_MODE_COMPARE) {
+				errx(1, "option --%s conflicts with %s",
+				    lopts[lidx].name,
+				    alt_base_mode(opts.alt_base_mode));
+			}
+			opts.alt_base_mode = BASE_MODE_COMPARE;
+#if 0
+			goto basedir;
+		case OP_COPY_DEST:
+			if (opts.alt_base_mode !=0 &&
+			    opts.alt_base_mode != BASE_MODE_COPY) {
+				errx(1, "option --%s conflicts with %s",
+				    lopts[lidx].name,
+				    alt_base_mode(opts.alt_base_mode));
+			}
+			opts.alt_base_mode = BASE_MODE_COPY;
+			goto basedir;
+		case OP_LINK_DEST:
+			if (opts.alt_base_mode !=0 &&
+			    opts.alt_base_mode != BASE_MODE_LINK) {
+				errx(1, "option --%s conflicts with %s",
+				    lopts[lidx].name,
+				    alt_base_mode(opts.alt_base_mode));
+			}
+			opts.alt_base_mode = BASE_MODE_LINK;
+
+basedir:
+#endif
+			if (basedir_cnt >= MAX_BASEDIR)
+				errx(1, "too many --%s directories specified",
+				    lopts[lidx].name);
+			opts.basedir[basedir_cnt++] = optarg;
+			break;
+		case OP_MAX_SIZE:
+			if (scan_scaled(optarg, &opts.max_size) == -1)
+				err(1, "bad max-size");
+			break;
+		case OP_MIN_SIZE:
+			if (scan_scaled(optarg, &opts.min_size) == -1)
+				err(1, "bad min-size");
+			break;
 		case OP_VERSION:
 			fprintf(stderr, "openrsync: protocol version %u\n",
 			    RSYNC_PROTOCOL);
@@ -554,12 +613,11 @@ main(int argc, char *argv[])
 	exit(rc);
 usage:
 	fprintf(stderr, "usage: %s"
-	    " [-aDglnoprtvx] [-e program] [--address=sourceaddr] [--del]\n"
-	    "\t[--exclude] [--exclude-from=file] [--include] "
-	    "[--include-from=file]\n"
-	    "\t[--no-motd] [--numeric-ids] [--port=portnumber] "
-	    "[--rsync-path=program]\n\t[--timeout=seconds] [--version] "
-            "source ... directory\n",
+	    " [-aDglnoprtvx] [-e program] [--address=sourceaddr]\n"
+	    "\t[--compare-dest=dir] [--del] [--exclude] [--exclude-from=file]\n"
+	    "\t[--include] [--include-from=file] [--no-motd] [--numeric-ids]\n"
+	    "\t[--port=portnumber] [--rsync-path=program] [--timeout=seconds]\n"
+	    "\t[--version] source ... directory\n",
 	    getprogname());
 	exit(ERR_SYNTAX);
 }
Index: usr.bin/rsync/receiver.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/receiver.c,v
retrieving revision 1.29
diff -u -p -u -r1.29 receiver.c
--- usr.bin/rsync/receiver.c	29 Aug 2021 13:43:46 -0000	1.29
+++ usr.bin/rsync/receiver.c	6 Nov 2021 18:52:57 -0000
@@ -184,6 +184,44 @@ rsync_receiver(struct sess *sess, int fd
 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw unveil", NULL) == -1)
 		err(ERR_IPC, "pledge");
 
+	/*
+	 * Create the path for our destination directory, if we're not
+	 * in dry-run mode (which would otherwise crash w/the pledge).
+	 * This uses our current umask: we might set the permissions on
+	 * this directory in post_dir().
+	 */
+
+	if (!sess->opts->dry_run) {
+		if ((tofree = strdup(root)) == NULL)
+			err(ERR_NOMEM, NULL);
+		if (mkpath(tofree) < 0)
+			err(ERR_FILE_IO, "%s: mkpath", tofree);
+		free(tofree);
+	}
+
+	/*
+	 * Make our entire view of the file-system be limited to what's
+	 * in the root directory.
+	 * This prevents us from accidentally (or "under the influence")
+	 * writing into other parts of the file-system.
+	 */
+	if (sess->opts->basedir[0]) {
+		/*
+		 * XXX just unveil everything for read
+		 * Could unveil each basedir or maybe a common path
+		 * also the fact that relative path are relative to the
+		 * root does not help.
+		 */
+		if (unveil("/", "r") == -1)
+			err(ERR_IPC, "%s: unveil", root);
+	}
+
+	if (unveil(root, "rwc") == -1)
+		err(ERR_IPC, "%s: unveil", root);
+
+	if (unveil(NULL, NULL) == -1)
+		err(ERR_IPC, "unveil");
+
 	/* Client sends exclusions. */
 	if (!sess->opts->server)
 		send_rules(sess, fdout);
@@ -222,21 +260,6 @@ rsync_receiver(struct sess *sess, int fd
 	LOG2("%s: receiver destination", root);
 
 	/*
-	 * Create the path for our destination directory, if we're not
-	 * in dry-run mode (which would otherwise crash w/the pledge).
-	 * This uses our current umask: we might set the permissions on
-	 * this directory in post_dir().
-	 */
-
-	if (!sess->opts->dry_run) {
-		if ((tofree = strdup(root)) == NULL)
-			err(ERR_NOMEM, NULL);
-		if (mkpath(tofree) < 0)
-			err(ERR_FILE_IO, "%s: mkpath", tofree);
-		free(tofree);
-	}
-
-	/*
 	 * Disable umask() so we can set permissions fully.
 	 * Then open the directory iff we're not in dry_run.
 	 */
@@ -244,7 +267,7 @@ rsync_receiver(struct sess *sess, int fd
 	oumask = umask(0);
 
 	if (!sess->opts->dry_run) {
-		dfd = open(root, O_RDONLY | O_DIRECTORY, 0);
+		dfd = open(root, O_RDONLY | O_DIRECTORY);
 		if (dfd == -1)
 			err(ERR_FILE_IO, "%s: open", root);
 	}
@@ -260,18 +283,6 @@ rsync_receiver(struct sess *sess, int fd
 		ERRX1("flist_gen_local");
 		goto out;
 	}
-
-	/*
-	 * Make our entire view of the file-system be limited to what's
-	 * in the root directory.
-	 * This prevents us from accidentally (or "under the influence")
-	 * writing into other parts of the file-system.
-	 */
-
-	if (unveil(root, "rwc") == -1)
-		err(ERR_IPC, "%s: unveil", root);
-	if (unveil(NULL, NULL) == -1)
-		err(ERR_IPC, "unveil");
 
 	/* If we have a local set, go for the deletion. */
 
Index: usr.bin/rsync/rsync.1
===================================================================
RCS file: /cvs/src/usr.bin/rsync/rsync.1,v
retrieving revision 1.25
diff -u -p -u -r1.25 rsync.1
--- usr.bin/rsync/rsync.1	30 Aug 2021 20:25:24 -0000	1.25
+++ usr.bin/rsync/rsync.1	6 Nov 2021 18:53:04 -0000
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: August 30 2021 $
+.Dd $Mdocdate: October 29 2021 $
 .Dt OPENRSYNC 1
 .Os
 .Sh NAME
@@ -25,11 +25,14 @@
 .Op Fl aDglnoprtvx
 .Op Fl e Ar program
 .Op Fl -address Ns = Ns Ar sourceaddr
+.Op Fl -compare-dest Ns = Ns Ar directory
 .Op Fl -del
 .Op Fl -exclude Ar pattern
 .Op Fl -exclude-from Ns = Ns Ar file
 .Op Fl -include Ar pattern
 .Op Fl -include-from Ns = Ns Ar file
+.Op Fl -max-size Ns = Ns size
+.Op Fl -min-size Ns = Ns size
 .Op Fl -no-motd
 .Op Fl -numeric-ids
 .Op Fl -port Ns = Ns Ar service
@@ -62,6 +65,18 @@ When connecting to an rsync daemon, use
 .Ar sourceaddr
 as the source address for connections, which is useful on machines with
 multiple interfaces.
+.It Fl -compare-dest Ns = Ns Ar directory
+Use directory as an alternate base directory to compare files against on the
+destination machine.
+If file in
+.Ar directory
+is found and identical to the sender's file, the file will not be transferred.
+Multiple
+.Fl -compare-dest
+directories may be provided.
+If
+.Ar directory
+is a relative path, it is relative to the destination directory.
 .It Fl D
 Also transfer device and special files.
 Shorthand for
@@ -114,6 +129,22 @@ set the numeric group ID to match the so
 Also transfer symbolic links.
 The link is transferred as a standalone file: if the destination does
 not exist, it will be broken.
+.It Fl -max-size Ar size
+Don't transfer any file that is larger than
+.Ar size
+bytes.
+Alternatively
+.Ar size
+may instead use a multiplier, as documented in
+.Xr scan_scaled 3 ,
+to specify the size.
+.It Fl -min-size Ar size
+Don't transfer any file that is smaller than
+.Ar size
+bytes.
+See
+.Fl -max-size
+on the definiton of size.
 .It Fl n , -dry-run
 Do not actually modify the destination.
 Mainly useful in combination with
Index: usr.bin/rsync/rules.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/rules.c,v
retrieving revision 1.2
diff -u -p -u -r1.2 rules.c
--- usr.bin/rsync/rules.c	1 Sep 2021 09:48:08 -0000	1.2
+++ usr.bin/rsync/rules.c	6 Nov 2021 14:20:41 -0000
@@ -1,3 +1,19 @@
+/*	$OpenBSD: rules.c,v 1.4 2021/11/03 14:42:12 deraadt Exp $ */
+/*
+ * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
 #include <err.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -7,9 +23,9 @@
 
 struct rule {
 	char			*pattern;
-	enum rule_type		type;
+	enum rule_type		 type;
 #ifdef NOTYET
-	unsigned int		modifiers;
+	unsigned int		 modifiers;
 #endif
 	short			 numseg;
 	unsigned char		 anchored;
@@ -110,7 +126,7 @@ parse_command(const char *command, size_
 {
 	const char *mod;
 	size_t	i;
-	
+
 	mod = memchr(command, ',', len);
 	if (mod != NULL) {
 		/* XXX modifiers not yet implemented */
@@ -163,7 +179,7 @@ parse_pattern(struct rule *r, char *patt
 			nseg++;
 	r->numseg = nseg;
 
-	/* check if this pattern only matches against the basename */ 
+	/* check if this pattern only matches against the basename */
 	if (nseg == 1 && !r->anchored)
 		r->fileonly = 1;
 
@@ -205,7 +221,7 @@ parse_rule(char *line, enum rule_type de
 				return -1;
 			type = def;
 			pattern = line;
-		} else 
+		} else
 			pattern = line + len + 1;
 
 		if (*pattern == '\0' && type != RULE_CLEAR)
@@ -396,12 +412,12 @@ rules_match(const char *path, int isdir)
 	struct rule *r;
 	size_t i;
 
-	basename = strrchr(path, '/');	
+	basename = strrchr(path, '/');
 	if (basename != NULL)
 		basename += 1;
 	else
 		basename = path;
-	
+
 	for (i = 0; i < numrules; i++) {
 		r = &rules[i];
 
Index: usr.bin/rsync/server.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/server.c,v
retrieving revision 1.14
diff -u -p -u -r1.14 server.c
--- usr.bin/rsync/server.c	30 Jun 2021 13:10:04 -0000	1.14
+++ usr.bin/rsync/server.c	6 Nov 2021 18:53:13 -0000
@@ -65,7 +65,7 @@ rsync_server(const struct opts *opts, si
 	/* Begin by making descriptors non-blocking. */
 
 	if (!fcntl_nonblock(fdin) ||
-	     !fcntl_nonblock(fdout)) {
+	    !fcntl_nonblock(fdout)) {
 		ERRX1("fcntl_nonblock");
 		goto out;
 	}
Index: usr.bin/rsync/uploader.c
===================================================================
RCS file: /cvs/src/usr.bin/rsync/uploader.c,v
retrieving revision 1.29
diff -u -p -u -r1.29 uploader.c
--- usr.bin/rsync/uploader.c	30 Jun 2021 13:10:04 -0000	1.29
+++ usr.bin/rsync/uploader.c	6 Nov 2021 18:53:20 -0000
@@ -19,6 +19,7 @@
 #include <sys/stat.h>
 
 #include <assert.h>
+#include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -166,7 +167,7 @@ init_blk(struct blk *p, const struct blk
  * Return <0 on failure 0 on success.
  */
 static int
-pre_link(struct upload *p, struct sess *sess)
+pre_symlink(struct upload *p, struct sess *sess)
 {
 	struct stat		 st;
 	const struct flist	*f;
@@ -266,7 +267,7 @@ pre_link(struct upload *p, struct sess *
 }
 
 /*
- * See pre_link(), but for devices.
+ * See pre_symlink(), but for devices.
  * FIXME: this is very similar to the other pre_xxx() functions.
  * Return <0 on failure 0 on success.
  */
@@ -355,7 +356,7 @@ pre_dev(struct upload *p, struct sess *s
 }
 
 /*
- * See pre_link(), but for FIFOs.
+ * See pre_symlink(), but for FIFOs.
  * FIXME: this is very similar to the other pre_xxx() functions.
  * Return <0 on failure 0 on success.
  */
@@ -432,7 +433,7 @@ pre_fifo(struct upload *p, struct sess *
 }
 
 /*
- * See pre_link(), but for socket files.
+ * See pre_symlink(), but for socket files.
  * FIXME: this is very similar to the other pre_xxx() functions.
  * Return <0 on failure 0 on success.
  */
@@ -641,17 +642,55 @@ post_dir(struct sess *sess, const struct
 }
 
 /*
+ * Check if file exists in the specified root directory.
+ * Returns:
+ *    -1 on error
+ *     0 if file is considered the same
+ *     1 if file exists and is possible match
+ *     2 if file exists but quick check failed
+ *     3 if file does not exist
+ * The stat pointer st is only valid for 0, 1, and 2 returns.
+ */
+static int
+check_file(int rootfd, const struct flist *f, struct stat *st)
+{
+	if (fstatat(rootfd, f->path, st, AT_SYMLINK_NOFOLLOW) == -1) {
+		if (errno == ENOENT)
+			return 3;
+
+		ERR("%s: fstatat", f->path);
+		return -1;
+	}
+
+	/* non-regular file needs attention */
+	if (!S_ISREG(st->st_mode))
+		return 2;
+
+	/* quick check if file is the same */
+	/* TODO: add support for --checksum, --size-only and --ignore-times */
+	if (st->st_size == f->st.size) {
+		if (st->st_mtime == f->st.mtime)
+			return 0;
+		return 1;
+	}
+
+	/* file needs attention */
+	return 2;
+}
+
+/*
  * Try to open the file at the current index.
  * If the file does not exist, returns with >0.
  * Return <0 on failure, 0 on success w/nothing to be done, >0 on
  * success and the file needs attention.
  */
 static int
-pre_file(const struct upload *p, int *filefd, struct stat *st,
+pre_file(const struct upload *p, int *filefd, off_t *size,
     struct sess *sess)
 {
 	const struct flist *f;
-	int rc;
+	struct stat st;
+	int i, rc, match = -1;
 
 	f = &p->fl[p->idx];
 	assert(S_ISREG(f->st.mode));
@@ -665,42 +704,83 @@ pre_file(const struct upload *p, int *fi
 		return 0;
 	}
 
+	if (sess->opts->max_size >= 0 && f->st.size > sess->opts->max_size) {
+		WARNX("skipping over max-size file %s", f->path);
+		return 0;
+	}
+	if (sess->opts->min_size >= 0 && f->st.size < sess->opts->min_size) {
+		WARNX("skipping under min-size file %s", f->path);
+		return 0;
+	}
+
 	/*
 	 * For non dry-run cases, we'll write the acknowledgement later
 	 * in the rsync_uploader() function.
 	 */
 
+	*size = 0;
 	*filefd = -1;
-	rc = fstatat(p->rootfd, f->path, st, AT_SYMLINK_NOFOLLOW);
-
-	if (rc == -1) {
-		if (errno == ENOENT)
-			return 1;
 
-		ERR("%s: fstatat", f->path);
+	rc = check_file(p->rootfd, f, &st);
+	if (rc == -1)
 		return -1;
-	}
-	if (!S_ISREG(st->st_mode)) {
-		if (S_ISDIR(st->st_mode) &&
+	if (rc == 2 && !S_ISREG(st.st_mode)) {
+		if (S_ISDIR(st.st_mode) &&
 		    unlinkat(p->rootfd, f->path, AT_REMOVEDIR) == -1) {
 			ERR("%s: unlinkat", f->path);
 			return -1;
 		}
-		return 1;
 	}
-
-	/* quick check if file is the same */
-	if (st->st_size == f->st.size &&
-	    st->st_mtime == f->st.mtime) {
-		LOG3("%s: skipping: up to date", f->path);
+	if (rc == 0) {
 		if (!rsync_set_metadata_at(sess, 0, p->rootfd, f, f->path)) {
 			ERRX1("rsync_set_metadata");
 			return -1;
 		}
+		LOG3("%s: skipping: up to date", f->path);
 		return 0;
 	}
 
-	*filefd = openat(p->rootfd, f->path, O_RDONLY | O_NOFOLLOW, 0);
+	/* check alternative locations for better match */
+	for (i = 0; sess->opts->basedir[i] != NULL; i++) {
+		const char *root = sess->opts->basedir[i];
+		int dfd, x;
+
+		dfd = openat(p->rootfd, root, O_RDONLY | O_DIRECTORY);
+		if (dfd == -1)
+			err(ERR_FILE_IO, "%s: openat", root);
+		x = check_file(dfd, f, &st);
+		/* found a match */
+		if (x == 0) {
+			if (rc >= 0) {
+				/* found better match, delete file in rootfd */
+				if (unlinkat(p->rootfd, f->path, 0) == -1 &&
+				    errno != ENOENT) {
+					ERR("%s: unlinkat", f->path);
+					return -1;
+				}
+			}
+			LOG3("%s: skipping: up to date in %s", f->path, root);
+			/* TODO: depending on mode link or copy file */
+			close(dfd);
+			return 0;
+		} else if (x == 1 && match == -1) {
+			/* found a local file that is a close match */
+			match = i;
+		}
+		close(dfd);
+	}
+	if (match != -1) {
+		/* copy match from basedir into root as a start point */
+		copy_file(p->rootfd, sess->opts->basedir[match], f);
+		if (fstatat(p->rootfd, f->path, &st, AT_SYMLINK_NOFOLLOW) ==
+		    -1) {
+			ERR("%s: fstatat", f->path);
+			return -1;
+		}
+	}
+
+	*size = st.st_size;
+	*filefd = openat(p->rootfd, f->path, O_RDONLY | O_NOFOLLOW);
 	if (*filefd == -1 && errno != ENOENT) {
 		ERR("%s: openat", f->path);
 		return -1;
@@ -778,11 +858,10 @@ rsync_uploader(struct upload *u, int *fi
 	struct sess *sess, int *fileoutfd)
 {
 	struct blkset	    blk;
-	struct stat	    st;
 	void		   *mbuf, *bufp;
 	ssize_t		    msz;
 	size_t		    i, pos, sz;
-	off_t		    offs;
+	off_t		    offs, filesize;
 	int		    c;
 
 	/* Once finished this should never get called again. */
@@ -849,9 +928,9 @@ rsync_uploader(struct upload *u, int *fi
 			if (S_ISDIR(u->fl[u->idx].st.mode))
 				c = pre_dir(u, sess);
 			else if (S_ISLNK(u->fl[u->idx].st.mode))
-				c = pre_link(u, sess);
+				c = pre_symlink(u, sess);
 			else if (S_ISREG(u->fl[u->idx].st.mode))
-				c = pre_file(u, fileinfd, &st, sess);
+				c = pre_file(u, fileinfd, &filesize, sess);
 			else if (S_ISBLK(u->fl[u->idx].st.mode) ||
 			    S_ISCHR(u->fl[u->idx].st.mode))
 				c = pre_dev(u, sess);
@@ -896,8 +975,8 @@ rsync_uploader(struct upload *u, int *fi
 	memset(&blk, 0, sizeof(struct blkset));
 	blk.csum = u->csumlen;
 
-	if (*fileinfd != -1 && st.st_size > 0) {
-		init_blkset(&blk, st.st_size);
+	if (*fileinfd != -1 && filesize > 0) {
+		init_blkset(&blk, filesize);
 		assert(blk.blksz);
 
 		blk.blks = calloc(blk.blksz, sizeof(struct blk));
@@ -956,14 +1035,14 @@ rsync_uploader(struct upload *u, int *fi
 	/* Make sure the block metadata buffer is big enough. */
 
 	u->bufsz =
-	     sizeof(int32_t) + /* identifier */
-	     sizeof(int32_t) + /* block count */
-	     sizeof(int32_t) + /* block length */
-	     sizeof(int32_t) + /* checksum length */
-	     sizeof(int32_t) + /* block remainder */
-	     blk.blksz *
-	     (sizeof(int32_t) + /* short checksum */
-	      blk.csum); /* long checksum */
+	    sizeof(int32_t) + /* identifier */
+	    sizeof(int32_t) + /* block count */
+	    sizeof(int32_t) + /* block length */
+	    sizeof(int32_t) + /* checksum length */
+	    sizeof(int32_t) + /* block remainder */
+	    blk.blksz *
+	    (sizeof(int32_t) + /* short checksum */
+	    blk.csum); /* long checksum */
 
 	if (u->bufsz > u->bufmax) {
 		if ((bufp = realloc(u->buf, u->bufsz)) == NULL) {