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 * Copyright (c) 2019 Kristaps Dzonsons * * 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 + #include +#include +#include #include #include #include +#include #include #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 * Copyright (c) 2019 Kristaps Dzonsons * * 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 * Copyright (c) 2019 Kristaps Dzonsons * * 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 + * Copyright (c) 2019 Kristaps Dzonsons + * + * 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 +#include +#include + +#include +#include +#include +#include + +#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 #include #include +#include #include #include #include @@ -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, ¬ify); - 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, ¬ify); + 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 + * Copyright (c) 2021 Claudio Jeker + * + * 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 * Copyright (c) 2019 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any @@ -24,10 +25,20 @@ #include #include +#include #include #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 + * + * 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 /* MAXBSIZE */ + +#include +#include +#include + +#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 #include #include +#include #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 + * + * 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 #include #include @@ -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 #include +#include #include #include #include @@ -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) {