From: Alberto Leiva Popper Date: Wed, 24 Sep 2025 22:43:37 +0000 (-0600) Subject: Merge branch 'main' into fort2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=33c61247d22ec375ed886ae45e4326b0ff2fcc86;p=thirdparty%2FFORT-validator.git Merge branch 'main' into fort2 --- 33c61247d22ec375ed886ae45e4326b0ff2fcc86 diff --cc docs/usage.md index 873b1984,84223472..4e82e853 --- a/docs/usage.md +++ b/docs/usage.md @@@ -96,8 -102,11 +98,9 @@@ description: Guide to use arguments of [--server.interval.retry=] [--server.interval.expire=] [--server.deltas.lifetime=] + [--prometheus.port=] [--rsync.enabled=true|false] [--rsync.priority=] - [--rsync.retry.count=] - [--rsync.retry.interval=] [--rsync.transfer-timeout=] [--http.enabled=true|false] [--http.priority=] diff --cc man/fort.8 index 2bea78bb,40b5af82..931f5162 --- a/man/fort.8 +++ b/man/fort.8 @@@ -830,19 -864,15 +840,28 @@@ By default, the path has a NULL value .RE .P + .B \-\-http.proxy=\fIURL\fR + .RS 4 + .P + Set a proxy to use for HTTP transfers. + .P + It can be a hostname, a dotted numerical IPv4 address or a numerical IPv6 address enclosed in brackets. The port defaults to 1080; append \fI:\fR to override. + .RE + .P + +.B \-\-rrdp.delta-threshold=\fIUNSIGNED_INTEGER\fR +.RS 4 +Maximum deltas to explode per RRDP session, per iteration. +.P +(If the RRDP notification lists more than this amount of unprocessed deltas, +Fort will reset the session, exploding the snapshot instead.) +.P +Per draft-spaghetti-sidrops-rrdp-desynchronization's recommendation, this is +also the maximum number of delta hashes Fort will remember per RRDP session, to +detect session desynchronization. +.RE +.P + .B \-\-rsync.enabled=\fItrue\fR|\fIfalse\fR .RS 4 Enables RSYNC requests. diff --cc src/Makefile.am index 12b13a76,215e29ca..f1e79c40 --- a/src/Makefile.am +++ b/src/Makefile.am @@@ -8,17 -40,24 +8,18 @@@ fort_SOURCES += asn1/content_info.h asn fort_SOURCES += asn1/decode.h asn1/decode.c fort_SOURCES += asn1/oid.h asn1/oid.c fort_SOURCES += asn1/signed_data.h asn1/signed_data.c - -fort_SOURCES += types/address.h types/address.c -fort_SOURCES += types/bio_seq.c types/bio_seq.h -fort_SOURCES += types/delta.c types/delta.h -fort_SOURCES += types/router_key.c types/router_key.h -fort_SOURCES += types/serial.h types/serial.c -fort_SOURCES += types/uri.h types/uri.c -fort_SOURCES += types/vrp.c types/vrp.h - -fort_SOURCES += cache/local_cache.c cache/local_cache.h - +fort_SOURCES += base64.h base64.c +fort_SOURCES += cache.c cache.h +fort_SOURCES += cachetmp.c cachetmp.h +fort_SOURCES += common.c common.h fort_SOURCES += config/boolean.c config/boolean.h + fort_SOURCES += config/curl_offset.c config/curl_offset.h -fort_SOURCES += config/file_type.h config/file_type.c fort_SOURCES += config/filename_format.h config/filename_format.c +fort_SOURCES += config/file_type.h config/file_type.c +fort_SOURCES += config.h config.c +fort_SOURCES += config/incidences.h config/incidences.c fort_SOURCES += config/log_conf.h config/log_conf.c fort_SOURCES += config/mode.c config/mode.h -fort_SOURCES += config/incidences.h config/incidences.c fort_SOURCES += config/output_format.h config/output_format.c fort_SOURCES += config/str.c config/str.h fort_SOURCES += config/string_array.h config/string_array.c @@@ -46,51 -87,33 +47,53 @@@ fort_SOURCES += object/roa.h object/roa fort_SOURCES += object/signed_object.h object/signed_object.c fort_SOURCES += object/tal.h object/tal.c fort_SOURCES += object/vcard.h object/vcard.c - +fort_SOURCES += output_printer.h output_printer.c +fort_SOURCES += print_file.h print_file.c ++fort_SOURCES += prometheus.h prometheus.c +fort_SOURCES += relax_ng.c relax_ng.h +fort_SOURCES += resource/asn.h resource/asn.c +fort_SOURCES += resource.h resource.c fort_SOURCES += resource/ip4.h resource/ip4.c fort_SOURCES += resource/ip6.h resource/ip6.c -fort_SOURCES += resource/asn.h resource/asn.c - -fort_SOURCES += rsync/rsync.h rsync/rsync.c - -fort_SOURCES += rtr/pdu_stream.c rtr/pdu_stream.h +fort_SOURCES += rrdp.h rrdp.c +fort_SOURCES += rsync.h rsync.c +fort_SOURCES += rtr/db/db_table.c rtr/db/db_table.h +fort_SOURCES += rtr/db/delta.c rtr/db/delta.h +fort_SOURCES += rtr/db/deltas_array.c rtr/db/deltas_array.h +fort_SOURCES += rtr/db/vrps.c rtr/db/vrps.h fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h +fort_SOURCES += rtr/pdu.c rtr/pdu.h fort_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h fort_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h -fort_SOURCES += rtr/pdu.c rtr/pdu.h +fort_SOURCES += rtr/pdu_stream.c rtr/pdu_stream.h fort_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h fort_SOURCES += rtr/rtr.c rtr/rtr.h - -fort_SOURCES += rtr/db/db_table.c rtr/db/db_table.h -fort_SOURCES += rtr/db/delta.c rtr/db/delta.h -fort_SOURCES += rtr/db/deltas_array.c rtr/db/deltas_array.h -fort_SOURCES += rtr/db/vrps.c rtr/db/vrps.h - +fort_SOURCES += sig.h sig.c fort_SOURCES += slurm/db_slurm.c slurm/db_slurm.h fort_SOURCES += slurm/slurm_loader.c slurm/slurm_loader.h fort_SOURCES += slurm/slurm_parser.c slurm/slurm_parser.h - -fort_SOURCES += thread/thread_pool.c thread/thread_pool.h - -fort_SOURCES += xml/relax_ng.c xml/relax_ng.h ++fort_SOURCES += stats.h stats.c +fort_SOURCES += stream.h stream.c +fort_SOURCES += task.h task.c +fort_SOURCES += thread_pool.c thread_pool.h +fort_SOURCES += thread_var.h thread_var.c +fort_SOURCES += types/address.h types/address.c +fort_SOURCES += types/arraylist.h +fort_SOURCES += types/asn.h +fort_SOURCES += types/bio_seq.c types/bio_seq.h +fort_SOURCES += types/delta.c types/delta.h +fort_SOURCES += types/map.h types/map.c +fort_SOURCES += types/name.h types/name.c +fort_SOURCES += types/path.h types/path.c +fort_SOURCES += types/router_key.c types/router_key.h +fort_SOURCES += types/rpp.h types/rpp.c +fort_SOURCES += types/serial.h types/serial.c +fort_SOURCES += types/sorted_array.h types/sorted_array.c +fort_SOURCES += types/str.h types/str.c +fort_SOURCES += types/uri.c types/uri.h +fort_SOURCES += types/uthash.h +fort_SOURCES += types/vrp.c types/vrp.h +fort_SOURCES += validation_handler.h validation_handler.c include asn1/asn1c/Makefile.include fort_SOURCES += $(ASN_MODULE_SRCS) $(ASN_MODULE_HDRS) @@@ -101,11 -124,13 +104,11 @@@ fort_CFLAGS += -std=c99 -D_XOPEN_SOURCE fort_CFLAGS += -O2 -g $(FORT_FLAGS) ${XML2_CFLAGS} if BACKTRACE_ENABLED fort_CFLAGS += -DBACKTRACE_ENABLED +fort_LDFLAGS = -rdynamic endif - fort_LDADD = ${JANSSON_LIBS} ${CURL_LIBS} ${XML2_LIBS} -fort_LDFLAGS = $(LDFLAGS_DEBUG) + fort_LDADD = ${JANSSON_LIBS} ${CURL_LIBS} ${XML2_LIBS} ${MICROHTTPD_LIBS} -# I'm tired of scrolling up, but feel free to comment this out. -GCC_WARNS = -fmax-errors=1 - +GCC_WARNS = -fmax-errors=1 -fanalyzer GCC_WARNS += -pedantic-errors -Waddress -Walloc-zero -Walloca GCC_WARNS += -Wno-aggressive-loop-optimizations -Warray-bounds=2 -Wbool-compare GCC_WARNS += -Wbool-operation -Wno-builtin-declaration-mismatch -Wcast-align @@@ -145,11 -180,18 +148,11 @@@ GCC_WARNS += -Wunused-value -Wunused-va GCC_WARNS += -Wunused-but-set-parameter -Wunused-but-set-variable GCC_WARNS += -Wvariadic-macros -Wvector-operation-performance -Wvla GCC_WARNS += -Wvolatile-register-var -Wwrite-strings - -# "Issue a warning when HSAIL cannot be emitted for the compiled function or -# OpenMP construct." -# Uh-huh. GCC_WARNS += -Whsa - -# I don't mind too much increasing these. -# Just make sure that you know what you're doing. +# Relatively arbitrary GCC_WARNS += -Wlarger-than=2048 -Walloc-size-larger-than=4096 GCC_WARNS += -Wframe-larger-than=1024 -Wstack-usage=1024 - # Can't use because of dependencies: -Waggregate-return -# Want to add, but needs work: -Wconversion, -Wsign-compare, -Wsign-conversion +# Want to add, but need work: -Wconversion, -Wsign-compare, -Wsign-conversion # Seem to require other compiler features: -Wchkp, -Wstack-protector, --# -Wstrict-aliasing ++# -Wstrict-aliasing diff --cc src/asn1/asn1c/INTEGER.c index 7e953975,84391913..01e5202e --- a/src/asn1/asn1c/INTEGER.c +++ b/src/asn1/asn1c/INTEGER.c @@@ -7,11 -7,9 +7,9 @@@ #include #include -#include - #include "alloc.h" - #include "common.h" - #include "asn1/asn1c/asn_codecs_prim.h" #include "asn1/asn1c/asn_internal.h" ++#include "common.h" #include "json_util.h" /* diff --cc src/asn1/asn1c/OBJECT_IDENTIFIER.c index 53acaaea,c522f057..dd64b200 --- a/src/asn1/asn1c/OBJECT_IDENTIFIER.c +++ b/src/asn1/asn1c/OBJECT_IDENTIFIER.c @@@ -10,8 -10,9 +10,7 @@@ #include #include #include -#include --#include "asn1/asn1c/INTEGER.h" #include "asn1/asn1c/asn_internal.h" #include "json_util.h" diff --cc src/asn1/asn1c/OCTET_STRING.c index 8ed51f41,c1efb026..1abaa5ff --- a/src/asn1/asn1c/OCTET_STRING.c +++ b/src/asn1/asn1c/OCTET_STRING.c @@@ -7,8 -7,9 +7,7 @@@ #include #include -#include --#include "alloc.h" #include "asn1/asn1c/BIT_STRING.h" #include "asn1/asn1c/asn_internal.h" #include "asn1/asn1c/ber_decoder.h" diff --cc src/asn1/asn1c/RsyncRequest.c index d527079c,00000000..bb376ca6 mode 100644,000000..100644 --- a/src/asn1/asn1c/RsyncRequest.c +++ b/src/asn1/asn1c/RsyncRequest.c @@@ -1,70 -1,0 +1,72 @@@ +/* + * Generated by asn1c-0.9.29 (http://lionet.info/asn1c) + * From ASN.1 module "RsyncSpawnerChannel" + * found in "rsync.asn1" + * `asn1c -Werror -fcompound-names -fwide-types -D asn1/asn1c -no-gen-PER -no-gen-example` + */ + +#include "asn1/asn1c/RsyncRequest.h" + ++#include "asn1/asn1c/constr_SEQUENCE.h" ++ +static asn_TYPE_member_t asn_MBR_RsyncRequest_1[] = { + { ATF_NOFLAGS, 0, offsetof(struct RsyncRequest, url), + (ASN_TAG_CLASS_UNIVERSAL | (4 << 2)), + 0, + &asn_DEF_OCTET_STRING, + 0, + { 0, 0, 0 }, + 0, 0, /* No default value */ + "url" + }, + { ATF_NOFLAGS, 0, offsetof(struct RsyncRequest, path), + (ASN_TAG_CLASS_UNIVERSAL | (4 << 2)), + 0, + &asn_DEF_OCTET_STRING, + 0, + { 0, 0, 0 }, + 0, 0, /* No default value */ + "path" + }, +}; +static const ber_tlv_tag_t asn_DEF_RsyncRequest_tags_1[] = { + (ASN_TAG_CLASS_UNIVERSAL | (16 << 2)) +}; +static const asn_TYPE_tag2member_t asn_MAP_RsyncRequest_tag2el_1[] = { + { (ASN_TAG_CLASS_UNIVERSAL | (4 << 2)), 0, 0, 1 }, /* url */ + { (ASN_TAG_CLASS_UNIVERSAL | (4 << 2)), 1, -1, 0 } /* path */ +}; +static asn_SEQUENCE_specifics_t asn_SPC_RsyncRequest_specs_1 = { + sizeof(struct RsyncRequest), + offsetof(struct RsyncRequest, _asn_ctx), + asn_MAP_RsyncRequest_tag2el_1, + 2, /* Count of tags in the map */ + -1, /* First extension addition */ +}; +asn_TYPE_descriptor_t asn_DEF_RsyncRequest = { + "RsyncRequest", + "RsyncRequest", + &asn_OP_SEQUENCE, + asn_DEF_RsyncRequest_tags_1, + sizeof(asn_DEF_RsyncRequest_tags_1) + /sizeof(asn_DEF_RsyncRequest_tags_1[0]), /* 1 */ + asn_DEF_RsyncRequest_tags_1, /* Same as above */ + sizeof(asn_DEF_RsyncRequest_tags_1) + /sizeof(asn_DEF_RsyncRequest_tags_1[0]), /* 1 */ + { 0, 0, SEQUENCE_constraint }, + asn_MBR_RsyncRequest_1, + 2, /* Elements count */ + &asn_SPC_RsyncRequest_specs_1 /* Additional specs */ +}; + +int +RsyncRequest_init(struct RsyncRequest *req, struct uri const *url, + char const *path) +{ + memset(req, 0, sizeof(*req)); + if (OCTET_STRING_fromBuf(&req->url, uri_str(url), uri_len(url)) < 0) + return -1; + if (OCTET_STRING_fromString(&req->path, path) < 0) + return -1; + return 0; +} diff --cc src/asn1/asn1c/RsyncRequest.h index db9398dd,00000000..7fb1d080 mode 100644,000000..100644 --- a/src/asn1/asn1c/RsyncRequest.h +++ b/src/asn1/asn1c/RsyncRequest.h @@@ -1,39 -1,0 +1,29 @@@ +/* + * Generated by asn1c-0.9.29 (http://lionet.info/asn1c) + * From ASN.1 module "RsyncSpawnerChannel" + * found in "rsync.asn1" + * `asn1c -Werror -fcompound-names -fwide-types -D asn1/asn1c -no-gen-PER -no-gen-example` + */ + +#ifndef _RsyncRequest_H_ +#define _RsyncRequest_H_ + +/* Including external dependencies */ - #include "types/uri.h" +#include "asn1/asn1c/OCTET_STRING.h" - #include "asn1/asn1c/constr_SEQUENCE.h" - - #ifdef __cplusplus - extern "C" { - #endif ++#include "types/uri.h" + +/* RsyncRequest */ +typedef struct RsyncRequest { + OCTET_STRING_t url; + OCTET_STRING_t path; + + /* Context for parsing across buffer boundaries */ + asn_struct_ctx_t _asn_ctx; +} RsyncRequest_t; + +/* Implementation */ +extern asn_TYPE_descriptor_t asn_DEF_RsyncRequest; + +int RsyncRequest_init(struct RsyncRequest *, struct uri const *, char const *); + - #ifdef __cplusplus - } - #endif - +#endif /* _RsyncRequest_H_ */ - #include "asn1/asn1c/asn_internal.h" diff --cc src/asn1/asn1c/constr_SEQUENCE.c index 5702b238,dbf112ba..d1e7465c --- a/src/asn1/asn1c/constr_SEQUENCE.c +++ b/src/asn1/asn1c/constr_SEQUENCE.c @@@ -11,7 -12,7 +11,6 @@@ #include "asn1/asn1c/OPEN_TYPE.h" #include "asn1/asn1c/asn_internal.h" #include "asn1/asn1c/ber_decoder.h" --#include "asn1/asn1c/constraints.h" #include "asn1/asn1c/der_encoder.h" #include "asn1/asn1c/xer_encoder.h" #include "json_util.h" diff --cc src/asn1/asn1c/constr_SET_OF.c index d58a731a,aef8f45f..307aab22 --- a/src/asn1/asn1c/constr_SET_OF.c +++ b/src/asn1/asn1c/constr_SET_OF.c @@@ -11,7 -12,7 +11,6 @@@ #include "asn1/asn1c/asn_SET_OF.h" #include "asn1/asn1c/asn_internal.h" #include "asn1/asn1c/ber_decoder.h" --#include "asn1/asn1c/constraints.h" #include "asn1/asn1c/der_encoder.h" #include "asn1/asn1c/xer_encoder.h" #include "json_util.h" diff --cc src/asn1/signed_data.c index 960f2023,40f7e6f4..a8085ca5 --- a/src/asn1/signed_data.c +++ b/src/asn1/signed_data.c @@@ -7,11 -6,10 +6,10 @@@ #include "asn1/asn1c/SignedDataPKCS7.h" #include "asn1/decode.h" #include "asn1/oid.h" -#include "crypto/hash.h" +#include "hash.h" #include "log.h" #include "object/certificate.h" -#include "object/name.h" +#include "object/signed_object.h" - #include "types/name.h" static const OID oid_cta = OID_CONTENT_TYPE_ATTR; static const OID oid_mda = OID_MESSAGE_DIGEST_ATTR; diff --cc src/base64.c index 560a1a50,00000000..2d6e17e9 mode 100644,000000..100644 --- a/src/base64.c +++ b/src/base64.c @@@ -1,176 -1,0 +1,178 @@@ +#include "base64.h" + +#include +#include +#include +#include ++#include ++#include + +#include "alloc.h" +#include "log.h" + +/* Simple decode base64 string. Returns true on success, false on failure. */ +bool +base64_decode(char *in, size_t in_len, unsigned char **out, size_t *out_len) +{ + unsigned char *result; + EVP_ENCODE_CTX *ctx; + int outl; + int status; + + if (in_len == 0) + in_len = strlen(in); + + /* + * Will usually allocate more because of the newlines, + * but I'm at peace with it. + */ + result = pmalloc(EVP_DECODE_LENGTH(in_len)); + + ctx = EVP_ENCODE_CTX_new(); + if (ctx == NULL) + enomem_panic(); + + EVP_DecodeInit(ctx); + + status = EVP_DecodeUpdate(ctx, result, &outl, (unsigned char *)in, in_len); + if (status == -1) + goto cancel; + + *out_len = outl; + + status = EVP_DecodeFinal(ctx, result + outl, &outl); + if (status != 1) + goto cancel; + + EVP_ENCODE_CTX_free(ctx); + *out = result; + *out_len += outl; + return true; + +cancel: EVP_ENCODE_CTX_free(ctx); + return false; +} + +/* + * Decode a base64 encoded string (@str_encoded), the decoded value is + * allocated at @result with a length of @result_len. + * + * Return 0 on success, or the error code if something went wrong. Don't forget + * to free @result after a successful decoding. + */ +bool +base64url_decode(char const *str_encoded, unsigned char **result, + size_t *result_len) +{ + char *str_copy; + size_t encoded_len; + size_t pad; + size_t i; + bool success; + + /* + * Apparently there isn't a base64url decoder, and there isn't + * much difference between base64 codification and base64url, just as + * stated in RFC 4648 section 5: "This encoding is technically + * identical to the previous one, except for the 62:nd and 63:rd + * alphabet character, as indicated in Table 2". + * + * The existing base64 can be used if the 62:nd and 63:rd base64url + * alphabet chars are replaced with the corresponding base64 chars, and + * also if we add the optional padding that the member should have. + */ + encoded_len = strlen(str_encoded); + pad = (encoded_len % 4) > 0 ? 4 - (encoded_len % 4) : 0; + + str_copy = pmalloc(encoded_len + pad + 1); + /* Set all with pad char, then replace with the original string */ + memset(str_copy, '=', encoded_len + pad); + memcpy(str_copy, str_encoded, encoded_len); + str_copy[encoded_len + pad] = '\0'; + + for (i = 0; i < encoded_len; i++) { + if (str_copy[i] == '-') + str_copy[i] = '+'; + else if (str_copy[i] == '_') + str_copy[i] = '/'; + } + + /* Now decode as regular base64 */ + success = base64_decode(str_copy, encoded_len + pad, result, result_len); + + free(str_copy); + return success; +} + +static char * +to_base64url(char const *base, size_t base_len) +{ + char const *pad; + char *tmp; + size_t len; + int i; + + /* Remove padding, if present */ + len = base_len; + do { + pad = strchr(base, '='); + if (pad == NULL) + break; + len = pad - base; + } while(0); + + tmp = pmalloc(len + 1); + memcpy(tmp, base, len); + tmp[len] = '\0'; + + for (i = 0; i < len; i++) { + if (tmp[i] == '+') + tmp[i] = '-'; + else if (tmp[i] == '/') + tmp[i] = '_'; + } + + return tmp; +} + +/* + * Encode @in (with size @in_len) as base64url without trailing pad, and + * allocate at @result. + * + * TODO (SLURM, RK) From the way this function keeps being called in pairs and + * failing too late, it would appear the code should be caching the encoded + * result during construction. + */ +bool +base64url_encode(unsigned char const *in, int in_len, char **result) +{ + BIO *b64, *mem; + BUF_MEM *mem_buf; + + ERR_clear_error(); + + mem = BIO_new(BIO_s_mem()); + if (mem == NULL) + return false; + + b64 = BIO_new(BIO_f_base64()); + if (b64 == NULL) { + BIO_free(mem); + return false; + } + + /* + * TODO (SLURM, RK) WHY IS THERE NO ERROR HANDLING HERE + * ARGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHH + */ + mem = BIO_push(b64, mem); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + BIO_write(b64, in, in_len); + BIO_flush(b64); + BIO_get_mem_ptr(mem, &mem_buf); + + *result = to_base64url(mem_buf->data, mem_buf->length); + + BIO_free_all(b64); + return true; +} diff --cc src/base64.h index 5dbae8f8,d7a71d4c..67c1d459 --- a/src/base64.h +++ b/src/base64.h @@@ -1,18 -1,13 +1,12 @@@ #ifndef SRC_BASE64_H_ #define SRC_BASE64_H_ -#include #include #include - #include - #include - #include - #include - #include - #include -bool base64_decode(BIO *, unsigned char *, bool, size_t, size_t *); -bool base64url_decode(char const *, unsigned char **, size_t *); +bool base64_decode(char *, size_t, unsigned char **, size_t *); +bool base64url_decode(char const *, unsigned char **, size_t *); bool base64url_encode(unsigned char const *, int, char **); #endif /* SRC_BASE64_H_ */ diff --cc src/cache.c index a22bfa0f,00000000..3696db8c mode 100644,000000..100644 --- a/src/cache.c +++ b/src/cache.c @@@ -1,1553 -1,0 +1,1543 @@@ +#include "cache.h" + +#include - #include +#include - #include - #include - #include - #include - #include + - #include "alloc.h" +#include "cachetmp.h" - #include "common.h" +#include "config.h" +#include "configure_ac.h" +#include "file.h" +#include "http.h" +#include "json_util.h" +#include "log.h" +#include "rrdp.h" +#include "rsync.h" +#include "task.h" - #include "types/array.h" +#include "types/path.h" +#include "types/str.h" - #include "types/uri.h" +#include "types/uthash.h" + +enum node_state { + /* Refresh nodes: Not downloaded yet (stale) */ + /* Fallback nodes: Queued for commit */ + DLS_OUTDATED = 0, + /* Refresh nodes: Download in progress */ + /* Fallback nodes: N/A */ + DLS_ONGOING, + /* Refresh nodes: Download complete */ + /* Fallback nodes: Committed */ + DLS_FRESH, +}; + +struct node_key { + /* + * Hash table indexer. + * + * If this is an rsync node, @id is the caRepository. + * If this is an HTTP node, @id is the simple URL. + * If this is an RRDP refresh node, @id is the rpkiNotify. + * If this is an RRDP fallback node, @id is `rpkiNotify\0caRepository`. + */ + char *id; + size_t idlen; + + /* + * If node is rsync, @http is NULL. + * If node is HTTP, @http is the simple URL. + * If node is RRDP, @http is the rpkiNotify. + * + * Points to @id; do not clean. + */ + struct uri http; + /* + * If node is rsync, @rsync is the simple URL. + * If node is HTTP, @rsync is NULL. + * If node is RRDP, @rsync is the caRepository. + * + * Points to @id; do not clean. + */ + struct uri rsync; +}; + +/* + * This is a delicate structure; pay attention. + * + * During the multithreaded stage of the validation cycle, one thread will + * switch @state from DLS_OUTDATED to DLS_ONGOING, and become the only writer + * for the given node. Other threads are only allowed to lock, and with the + * lock, read @state (to find out they shouldn't touch anything else). + * + * The entire cache_node (except @hh) becomes (effectively) constant when the + * writer thread upgrades @state to DLS_FRESH. + * + * This is intended to allow the cache (ie. this module) to pass the node to the + * validation code (through cache_cage) without having to allocate a deep copy + * (@rrdp can be somewhat large), and to allow the validation code to read-only + * the node (except @hh) without having to hold the table mutex. + * + * C cannot entirely ensure the node remains constant after it's handed outside; + * this must be done through careful coding and review. + */ +struct cache_node { + struct node_key key; + char *path; + + enum node_state state; + /* Result code of recent dl attempt (DLS_FRESH only) */ + validation_verdict verdict; + time_t attempt_ts; /* Refresh: Dl attempt. Fallback: Commit */ + time_t success_ts; /* Refresh: Dl success. Fallback: Commit */ + + struct mft_meta mft; /* RPP fallbacks only */ + struct rrdp_state *rrdp; + + UT_hash_handle hh; /* Hash table hook */ +}; + +typedef validation_verdict (*dl_cb)(struct cache_node *rpp); + +/* + * When concurrency is at play, you need @lock to access @nodes and @seq. + * @name, @enabled and @download stay constant through the validation. + * + * @lock also protects the nodes' @state and @hh, which have additional rules. + * (See cache_node.) + */ +struct cache_table { + char *name; + bool enabled; + struct cache_sequence seq; + struct cache_node *nodes; /* Hash Table */ + dl_cb download; + pthread_mutex_t lock; +}; + +static struct rpki_cache { + /* Latest view of the remote rsync modules */ + /* rsync modules (repositories); indexed by plain rsync URL */ + struct cache_table rsync; + /* Latest view of the remote HTTPS TAs */ + /* HTTPS files; indexed by plain HTTPS URL */ + struct cache_table https; + /* Latest view of the remote RRDP cages */ + /* RRDP modules (repositories); indexed by rpkiNotify */ + struct cache_table rrdp; + + /* Committed (offline fallback hard links) RPPs and TAs */ + /* RPPs indexed by [rpkiNotif] + caRepo; TAs indexed by plain URL. */ + struct cache_table fallback; +} cache; + +/* "Is the lockfile ours?" */ +static volatile sig_atomic_t lockfile_owned; + +struct cache_cage { + struct cache_node const *refresh; + struct cache_node const *fallback; + struct uri rpkiNotify; + struct mft_meta *mft; /* Fallback */ +}; + +struct cache_commit { + struct uri rpkiNotify; + struct uri caRepository; + struct cache_mapping *files; + size_t nfiles; + struct mft_meta mft; /* RPPs commits only */ + STAILQ_ENTRY(cache_commit) lh; +}; + +static STAILQ_HEAD(cache_commits, cache_commit) commits = STAILQ_HEAD_INITIALIZER(commits); +static pthread_mutex_t commits_lock = PTHREAD_MUTEX_INITIALIZER; + +#define LOCKFILE ".lock" +#define METAFILE "meta.json" +#define TAGNAME_VERSION "fort-version" + +#ifdef UNIT_TESTING +static void __delete_node_cb(struct cache_node const *); +#endif + +static void +delete_node(struct cache_table *tbl, struct cache_node *node, void *arg) +{ +#ifdef UNIT_TESTING + __delete_node_cb(node); +#endif + + if (tbl) + HASH_DEL(tbl->nodes, node); + + free(node->key.id); + free(node->path); + rrdp_state_free(node->rrdp); + free(node); +} + +static void +foreach_node(void (*cb)(struct cache_table *, struct cache_node *, void *), + void *arg) +{ + struct cache_node *node, *tmp; + + HASH_ITER(hh, cache.rsync.nodes, node, tmp) + cb(&cache.rsync, node, arg); + HASH_ITER(hh, cache.https.nodes, node, tmp) + cb(&cache.https, node, arg); + HASH_ITER(hh, cache.rrdp.nodes, node, tmp) + cb(&cache.rrdp, node, arg); + HASH_ITER(hh, cache.fallback.nodes, node, tmp) + cb(&cache.fallback, node, arg); +} + +static void +flush_nodes(void) +{ + foreach_node(delete_node, NULL); +} + +/* + * - Result must not be cleant. + * - strlen(uri_str(module)) should not be trusted. + */ +static bool +get_rsync_module(struct uri const *url, struct uri *module) +{ + char const *str; + array_index u; + unsigned int slashes; + + str = uri_str(url); + slashes = 0; + for (u = 0; str[u] != 0; u++) + if (str[u] == '/') { + slashes++; + if (slashes == 4) { + __uri_init(module, str, u); + return true; + } + } + + if (slashes == 3 && str[u - 1] != '/') { + *module = *url; + return true; + } + + pr_val_err("Url '%s' does not appear to have an rsync module.", str); + return false; +} + +char const * +strip_rsync_module(char const *url) +{ + array_index u; + unsigned int slashes; + + slashes = 0; + for (u = 0; url[u] != 0; u++) + if (url[u] == '/') { + slashes++; + if (slashes == 4) + return url + u + 1; + } + + return NULL; +} + +static json_t * +node2json(struct cache_node *node) +{ + char const *str; + json_t *json; + + json = json_obj_new(); + if (json == NULL) + return NULL; + + str = uri_str(&node->key.http); + if (str && json_add_str(json, "http", str)) + goto fail; + str = uri_str(&node->key.rsync); + if (str && json_add_str(json, "rsync", str)) + goto fail; + if (json_add_str(json, "path", node->path)) + goto fail; + if (node->attempt_ts && json_add_ts(json, "attempt", node->attempt_ts)) + goto fail; + if (node->success_ts && json_add_ts(json, "success", node->success_ts)) + goto fail; + if (node->mft.num.size && json_add_bigint(json, "mftNum", &node->mft.num)) + goto fail; + if (node->mft.update && json_add_ts(json, "mftUpdate", node->mft.update)) + goto fail; + if (node->rrdp) + if (json_object_add(json, "rrdp", rrdp_state2json(node->rrdp))) + goto fail; + + return json; + +fail: json_decref(json); + return NULL; +} + +static validation_verdict dl_rsync(struct cache_node *); +static validation_verdict dl_http(struct cache_node *); +static validation_verdict dl_rrdp(struct cache_node *); + +static void +init_table(struct cache_table *tbl, char *name, bool enabled, dl_cb dl) +{ + memset(tbl, 0, sizeof(*tbl)); + tbl->name = name; + tbl->enabled = enabled; + cseq_init(&tbl->seq, name, 0, false); + tbl->download = dl; + panic_on_fail(pthread_mutex_init(&tbl->lock, NULL), + "pthread_mutex_init"); +} + +static void +init_tables(void) +{ + init_table(&cache.rsync, "rsync", config_get_rsync_enabled(), dl_rsync); + init_table(&cache.https, "https", config_get_http_enabled(), dl_http); + init_table(&cache.rrdp, "rrdp", config_get_http_enabled(), dl_rrdp); + init_table(&cache.fallback, "fallback", true, NULL); +} + +static int +reset_cache_dir(void) +{ + DIR *dir; + struct dirent *file; + int tmperr, abserr; + unsigned int deleted; + + pr_op_debug("Resetting cache..."); + + abserr = 0; + deleted = 0; + + dir = opendir("."); + if (dir == NULL) + goto end; + + FOREACH_DIR_FILE(dir, file) + if (!S_ISDOTS(file) && strcmp(file->d_name, LOCKFILE) != 0) { + tmperr = file_rm_rf(file->d_name); + if (tmperr) + abserr = tmperr; + deleted++; + } + +end: tmperr = errno; + if (tmperr) + abserr = tmperr; + if (abserr) + pr_op_warn("Cannot reset cache: %s", strerror(abserr)); + else + pr_op_debug(deleted > 0 ? "Cache reset." : "Cache was empty."); + closedir(dir); + return abserr; +} + +static void +init_cachedir_tag(void) +{ + static char const *filename = "CACHEDIR.TAG"; + if (file_stat_errno(filename) == ENOENT) + file_write_txt(filename, + "Signature: 8a477f597d28d172789f06886806bc55\n" + "# This file is a cache directory tag created by Fort.\n" + "# For information about cache directory tags, see:\n" + "# https://bford.info/cachedir/\n"); +} + +static int +lock_cache(void) +{ + int fd; + int error; + + pr_op_debug("touch " LOCKFILE); + + /* + * Suppose we get SIGTERM in the middle of this function. + * + * 1. open() then lockfile_owned = 1, we're interrupted between them: + * The handler doesn't delete our lock. + * 2. lockfile_owned = 1 then open(), we're interrupted between them: + * The handler deletes some other instance's lock. + * + * 1 is better because we already couldn't guarantee the lock was + * deleted on every situation. (SIGKILL) + */ + + fd = open(LOCKFILE, O_CREAT | O_EXCL, 0644); + if (fd < 0) { + error = errno; + pr_op_err("Cannot create lockfile '%s/" LOCKFILE "': %s", + config_get_local_repository(), strerror(error)); + return error; + } + close(fd); + + lockfile_owned = 1; + return 0; +} + +static void +unlock_cache(void) +{ + pr_op_debug("rm " LOCKFILE); + + if (!lockfile_owned) { + pr_op_debug("The cache wasn't locked."); + return; + } + + if (unlink(LOCKFILE) < 0) { + int error = errno; + pr_op_err("Cannot remove lockfile: %s", strerror(error)); + if (error != ENOENT) + return; + } + + lockfile_owned = 0; +} + +/* THIS FUNCTION CAN BE CALLED FROM A SIGNAL HANDLER. */ +void +cache_atexit(void) +{ + if (lockfile_owned) + unlink(LOCKFILE); +} + +int +cache_setup1(void) +{ + char const *cachedir; + int error; + + cachedir = config_get_local_repository(); + + error = file_mkdir(cachedir, true); + if (error) + return error; + + pr_op_debug("cd %s", cachedir); + if (chdir(cachedir) < 0) { + error = errno; + pr_op_err("Cannot cd to %s: %s", cachedir, strerror(error)); + return error; + } + + return 0; +} + +int +cache_setup2(void) +{ + int error; + + init_tables(); + + errno = 0; + error = atexit(cache_atexit); + if (error) { + int err2 = errno; + pr_op_err("Cannot register cache's exit function."); + pr_op_err("Error message attempt 1: %s", strerror(error)); + pr_op_err("Error message attempt 2: %s", strerror(err2)); + return error; + } + + return 0; +} + +static void +init_rrdp_fallback_key(struct node_key *key, struct uri const *http, + struct uri const *rsync) +{ + size_t hlen, rlen; + + hlen = uri_len(http); + rlen = uri_len(rsync); + + key->idlen = hlen + rlen + 1; + key->id = pmalloc(key->idlen + 1); + __uri_init(&key->http, key->id, hlen); + __uri_init(&key->rsync, key->id + hlen + 1, rlen); + + memcpy(key->id, uri_str(http), hlen + 1); + memcpy(key->id + hlen + 1, uri_str(rsync), rlen + 1); +} + +static int +init_node_key(struct node_key *key, struct uri const *http, + struct uri const *rsync) +{ + if (http && (uri_str(http) == NULL)) + http = NULL; + if (rsync && (uri_str(rsync) == NULL)) + rsync = NULL; + + if (http != NULL && rsync != NULL) { + init_rrdp_fallback_key(key, http, rsync); + + } else if (rsync != NULL) { + key->idlen = uri_len(rsync); + key->id = pstrndup(uri_str(rsync), key->idlen); + memset(&key->http, 0, sizeof(key->http)); + __uri_init(&key->rsync, key->id, key->idlen); + + } else if (http != NULL) { + key->idlen = uri_len(http); + key->id = pstrndup(uri_str(http), key->idlen); + __uri_init(&key->http, key->id, key->idlen); + memset(&key->rsync, 0, sizeof(key->rsync)); + + } else { + return false; + } + + return true; +} + +static struct cache_node * +json2node(json_t *json) +{ + struct cache_node *node; + struct uri http; + struct uri rsync; + char const *path; + json_t *rrdp; + int error; + + error = json_get_uri(json, "http", &http); + if (error && (error != ENOENT)) { + pr_op_debug("http: %s", strerror(error)); + return NULL; + } + + error = json_get_uri(json, "rsync", &rsync); + if (error && (error != ENOENT)) { + pr_op_debug("rsync: %s", strerror(error)); + uri_cleanup(&http); + return NULL; + } + + node = pzalloc(sizeof(struct cache_node)); + + if (!init_node_key(&node->key, &http, &rsync)) { + pr_op_debug("JSON node is missing both http and rsync tags."); + uri_cleanup(&rsync); + uri_cleanup(&http); + goto nde; + } + + uri_cleanup(&http); + uri_cleanup(&rsync); + + error = json_get_str(json, "path", &path); + if (error) { + pr_op_debug("path: %s", strerror(error)); + goto key; + } + node->path = pstrdup(path); + + error = json_get_ts(json, "attempt", &node->attempt_ts); + if (error != 0 && error != ENOENT) { + pr_op_debug("attempt: %s", strerror(error)); + goto pth; + } + + error = json_get_ts(json, "success", &node->success_ts); + if (error != 0 && error != ENOENT) { + pr_op_debug("success: %s", strerror(error)); + goto pth; + } + + error = json_get_bigint(json, "mftNum", &node->mft.num); + if (error < 0) { + pr_op_debug("mftNum: %s", strerror(error)); + goto pth; + } + + error = json_get_ts(json, "mftUpdate", &node->mft.update); + if (error != 0 && error != ENOENT) { + pr_op_debug("mftUpdate: %s", strerror(error)); + goto mft; + } + + error = json_get_object(json, "rrdp", &rrdp); + if (error < 0) { + pr_op_debug("rrdp: %s", strerror(error)); + goto mft; + } + if (error == 0 && rrdp_json2state(rrdp, node->path, &node->rrdp)) + goto mft; + + return node; + +mft: INTEGER_cleanup(&node->mft.num); +pth: free(node->path); +key: free(node->key.id); +nde: free(node); + return NULL; +} + +static int +check_root_metafile(void) +{ + json_error_t jerr; + json_t *root; + char const *file_version; + int error; + + pr_op_debug("Loading " METAFILE "..."); + + root = json_load_file(METAFILE, 0, &jerr); + if (root == NULL) { + if (json_error_code(&jerr) == json_error_cannot_open_file) { + pr_op_debug(METAFILE " does not exist."); + return ENOENT; + } else { + pr_op_err("Json parsing failure at %s (%d:%d): %s", + METAFILE, jerr.line, jerr.column, jerr.text); + return EINVAL; + } + } + + if (json_typeof(root) != JSON_OBJECT) { + pr_op_err("The root tag of " METAFILE " is not an object."); + goto fail; + } + + error = json_get_str(root, TAGNAME_VERSION, &file_version); + if (error) { + if (error > 0) + pr_op_err(METAFILE " is missing the '" + TAGNAME_VERSION "' tag."); + goto fail; + } + if (strcmp(file_version, PACKAGE_VERSION) != 0) { + pr_op_err("The cache was written by Fort %s; " + "I need to clear it.", file_version); + goto fail; + } + + json_decref(root); + pr_op_debug(METAFILE " loaded."); + return 0; + +fail: json_decref(root); + return EINVAL; +} + +static void +collect_meta(struct cache_table *tbl, struct dirent *dir) +{ + char filename[64]; + int wrt; + json_error_t jerr; + json_t *root; + struct cache_node *node; + + if (S_ISDOTS(dir)) + return; + + wrt = snprintf(filename, 64, "%s/%s.json", tbl->name, dir->d_name); + if (wrt >= 64) + pr_crit("collect_meta: %d %s %s", wrt, tbl->name, dir->d_name); + + pr_clutter("%s: Loading...", filename); + + root = json_load_file(filename, 0, &jerr); + if (root == NULL) { + if (json_error_code(&jerr) == json_error_cannot_open_file) + pr_op_warn("%s: File does not exist.", filename); + else + pr_op_warn("%s: Json parsing failure at (%d:%d): %s", + filename, jerr.line, jerr.column, jerr.text); + return; + } + + if (json_typeof(root) != JSON_OBJECT) { + pr_op_warn("%s: Root tag is not an object.", filename); + goto end; + } + + node = json2node(root); + if (node != NULL) { + // XXX worry about dupes + HASH_ADD_KEYPTR(hh, tbl->nodes, node->key.id, node->key.idlen, + node); + } + + pr_clutter("%s: Loaded.", filename); +end: json_decref(root); +} + +static void +collect_metas(struct cache_table *tbl) +{ + DIR *dir; + struct dirent *file; + unsigned long id, max_id; + int error; + + dir = opendir(tbl->name); + if (dir == NULL) { + error = errno; + if (error != ENOENT) + pr_op_warn("Cannot open %s: %s", + tbl->name, strerror(error)); + return; + } + + max_id = 0; + FOREACH_DIR_FILE(dir, file) { + if (hex2ulong(file->d_name, &id) != 0) + continue; + if (id > max_id) + max_id = id; + collect_meta(tbl, file); + } + error = errno; + if (error) + pr_op_warn("Could not finish traversing %s: %s", + tbl->name, strerror(error)); + + closedir(dir); + + tbl->seq.prefix = tbl->name; + tbl->seq.next_id = max_id + 1; + tbl->seq.pathlen = strlen(tbl->name); + tbl->seq.free_prefix = false; +} + +static int +load_index(void) +{ + int error; + + error = check_root_metafile(); + if (error) + return error; + + collect_metas(&cache.rsync); + collect_metas(&cache.https); + collect_metas(&cache.rrdp); + collect_metas(&cache.fallback); + return 0; +} + +int +cache_prepare(void) +{ + int error; + + error = lock_cache(); + if (error) + return error; + + if (load_index() != 0) { + error = reset_cache_dir(); + if (error) + goto fail; + } + + error = file_mkdir("rsync", true); + if (error) + goto fail; + error = file_mkdir("https", true); + if (error) + goto fail; + error = file_mkdir("rrdp", true); + if (error) + goto fail; + error = file_mkdir("fallback", true); + if (error) + goto fail; + error = file_mkdir(CACHE_TMPDIR, true); + if (error) + goto fail; + init_cachedir_tag(); + + return 0; + +fail: flush_nodes(); + unlock_cache(); + return error; +} + +static validation_verdict +dl_rsync(struct cache_node *module) +{ + int error; + error = rsync_queue(&module->key.rsync, module->path); + return error ? VV_FAIL : VV_BUSY; +} + +static validation_verdict +dl_rrdp(struct cache_node *notif) +{ + bool changed; + + if (rrdp_update(¬if->key.http, notif->path, notif->success_ts, + &changed, ¬if->rrdp)) + return VV_FAIL; + if (changed) + notif->success_ts = notif->attempt_ts; + return VV_CONTINUE; +} + +static validation_verdict +dl_http(struct cache_node *file) +{ + bool changed; + + if (http_download(&file->key.http, file->path, file->success_ts, + &changed)) + return VV_FAIL; + if (changed) + file->success_ts = file->attempt_ts; + return VV_CONTINUE; +} + +/* Caller must lock @tbl->lock */ +static struct cache_node * +find_node(struct cache_table *tbl, char const *url, size_t urlen) +{ + struct cache_node *node; + HASH_FIND(hh, tbl->nodes, url, urlen, node); + return node; +} + +static struct cache_node * +provide_node(struct cache_table *tbl, struct uri const *http, + struct uri const *rsync) +{ + struct node_key key; + struct cache_node *node; + + if (!init_node_key(&key, http, rsync)) { + pr_val_debug("Can't build node identifier: Both HTTP and rsync URLs are NULL."); + return NULL; + } + + node = find_node(tbl, key.id, key.idlen); + if (node) { + free(key.id); + return node; + } + + node = pzalloc(sizeof(struct cache_node)); + node->key = key; + node->path = cseq_next(&tbl->seq); + if (!node->path) { + free(node); + free(key.id); + return NULL; + } + + HASH_ADD_KEYPTR(hh, tbl->nodes, node->key.id, node->key.idlen, node); + return node; + +} + +static void +rm_metadata(struct cache_node *node) +{ + char *filename; + int error; + + filename = str_concat(node->path, ".json"); + pr_op_debug("rm %s", filename); + if (unlink(filename) < 0) { + error = errno; + if (error == ENOENT) + pr_op_debug("%s already doesn't exist.", filename); + else + pr_op_warn("Cannot rm %s: %s", filename, strerror(errno)); + } + + free(filename); +} + +static void +write_metadata(struct cache_node *node) +{ + char *filename; + json_t *json; + + json = node2json(node); + if (!json) + return; + filename = str_concat(node->path, ".json"); + + pr_op_debug("echo \"$json\" > %s", filename); + if (json_dump_file(json, filename, JSON_INDENT(2))) + pr_op_err("Unable to write %s; unknown cause.", filename); + + free(filename); + json_decref(json); +} + +/* + * @uri is either a caRepository or a rpkiNotify + * By contract, only sets @result on return 0. + * By contract, @result->state will be DLS_FRESH on return 0. + */ +static validation_verdict +do_refresh(struct cache_table *tbl, struct uri const *uri, + struct cache_node **result) +{ + struct uri module; + struct cache_node *node; + bool downloaded = false; + + pr_val_debug("Trying %s (online)...", uri_str(uri)); + + if (!tbl->enabled) { + pr_val_debug("Protocol disabled."); + return VV_FAIL; + } + + if (tbl == &cache.rsync) { + if (!get_rsync_module(uri, &module)) + return VV_FAIL; + mutex_lock(&tbl->lock); + node = provide_node(tbl, NULL, &module); + } else { + mutex_lock(&tbl->lock); + node = provide_node(tbl, uri, NULL); + } + if (!node) { + mutex_unlock(&tbl->lock); + return VV_FAIL; + } + + /* + * Reminder: If the state is ONGOING, DO NOT read anything other than + * the lock and state. + */ + + switch (node->state) { + case DLS_OUTDATED: + node->state = DLS_ONGOING; + mutex_unlock(&tbl->lock); + + node->attempt_ts = time_fatal(); + rm_metadata(node); + node->verdict = tbl->download(node); + if (node->verdict == VV_BUSY) + goto ongoing; + write_metadata(node); + downloaded = true; + + mutex_lock(&tbl->lock); + node->state = DLS_FRESH; + break; + case DLS_ONGOING: +ongoing: mutex_unlock(&tbl->lock); + pr_val_debug("Refresh ongoing."); + return VV_BUSY; + case DLS_FRESH: + break; + default: + pr_crit("Unknown node state: %d", node->state); + } + + mutex_unlock(&tbl->lock); + /* node->state is guaranteed to be DLS_FRESH at this point. */ + + if (downloaded) /* Kickstart tasks that fell into DLS_ONGOING */ + task_wakeup_dormants(); + + if (node->verdict == VV_FAIL) { + pr_val_debug("Refresh failed."); + return VV_FAIL; + } + + pr_val_debug("Refresh succeeded."); + *result = node; + return VV_CONTINUE; +} + +static struct cache_node * +find_rrdp_fallback_node(struct extension_uris *uris) +{ + struct node_key key; + struct cache_node *result; + + if (!uri_str(&uris->rpkiNotify) || !uri_str(&uris->caRepository)) + return NULL; + + init_rrdp_fallback_key(&key, &uris->rpkiNotify, &uris->caRepository); + result = find_node(&cache.fallback, key.id, key.idlen); + free(key.id); + + return result; +} + +static struct cache_node * +get_fallback(struct extension_uris *uris) +{ + struct cache_node *rrdp; + struct cache_node *rsync; + + rrdp = find_rrdp_fallback_node(uris); + rsync = find_node(&cache.fallback, uri_str(&uris->caRepository), + uri_len(&uris->caRepository)); + + if (rrdp == NULL) + return rsync; + if (rsync == NULL) + return rrdp; + return (difftime(rsync->success_ts, rrdp->success_ts) > 0) ? rsync : rrdp; +} + +validation_verdict +cache_refresh_by_url(struct uri const *url, char **result) +{ + struct cache_node *node = NULL; + validation_verdict vv; + + if (uri_is_https(url)) { + vv = do_refresh(&cache.https, url, &node); + if (vv != VV_CONTINUE) + goto oops; + *result = node ? pstrdup(node->path) : NULL; + return VV_CONTINUE; + } + + if (uri_is_rsync(url)) { + vv = do_refresh(&cache.rsync, url, &node); + if (vv != VV_CONTINUE) + goto oops; + *result = path_join(node->path, strip_rsync_module(uri_str(url))); + return VV_CONTINUE; + } + + vv = VV_FAIL; +oops: *result = NULL; + return vv; +} + +/* HTTPS (TAs) and rsync only; don't use this for RRDP. */ +validation_verdict +cache_get_fallback(struct uri const *url, char **result) +{ + struct cache_node *node; + + /* + * The fallback table is read-only until the cleanup. + * Mutex not needed here. + */ + + pr_val_debug("Trying %s (offline)...", uri_str(url)); + + node = find_node(&cache.fallback, uri_str(url), uri_len(url)); + if (!node) { + pr_val_debug("Cache data unavailable."); + *result = NULL; + return VV_CONTINUE; + } + + *result = pstrdup(node->path); + return VV_CONTINUE; +} + +/* + * Attempts to refresh the RPP described by @sias, returns the resulting + * repository's mapper. + * + * XXX Fallback only if parent is fallback + */ +validation_verdict +cache_refresh_by_uris(struct extension_uris *uris, struct cache_cage **result) +{ + struct cache_node *node; + struct cache_cage *cage; + struct uri rpkiNotify; + validation_verdict vv; + + // XXX Make sure somewhere validates rpkiManifest matches caRepository. + + /* Try RRDP + optional fallback */ + if (uri_str(&uris->rpkiNotify) != NULL) { + vv = do_refresh(&cache.rrdp, &uris->rpkiNotify, &node); + if (vv == VV_CONTINUE) { + rpkiNotify = uris->rpkiNotify; + goto refresh_success; + } + if (vv == VV_BUSY) + return VV_BUSY; + } + + /* Try rsync + optional fallback */ + vv = do_refresh(&cache.rsync, &uris->caRepository, &node); + if (vv == VV_CONTINUE) { + memset(&rpkiNotify, 0, sizeof(rpkiNotify)); + goto refresh_success; + } + if (vv == VV_BUSY) + return VV_BUSY; + + /* Try fallback only */ + node = get_fallback(uris); + if (!node) + return VV_FAIL; /* Nothing to work with */ + + *result = cage = pzalloc(sizeof(struct cache_cage)); + cage->fallback = node; + return VV_CONTINUE; + +refresh_success: + *result = cage = pzalloc(sizeof(struct cache_cage)); + cage->rpkiNotify = rpkiNotify; + cage->refresh = node; + cage->fallback = get_fallback(uris); + return VV_CONTINUE; +} + +/* Result needs free() */ +static char * +node2file(struct cache_node const *node, struct uri const *url) +{ + if (node == NULL) + return NULL; + return (node->rrdp) + ? /* RRDP */ pstrdup(rrdp_file(node->rrdp, url)) + : /* rsync */ path_join(node->path, strip_rsync_module(uri_str(url))); +} + +/* Result needs free() */ +char * +cage_map_file(struct cache_cage *cage, struct uri const *url) +{ + /* + * Remember: In addition to honoring the consts of cache->refresh and + * cache->fallback, anything these structures point to MUST NOT be + * modified either. + */ + + char *file; + + file = node2file(cage->refresh, url); + if (!file) + file = node2file(cage->fallback, url); + + return file; +} + +/* + * If refresh enabled, + * Switches to fallback. + * If fallback unavailable, disables the cage. + * Else, if fallback enabled, disables the cage. + * Else (if cage disabled) does nothing. + * + * Returns true if the next option should be attempted. + */ +bool +cage_downgrade(struct cache_cage *cage) +{ + /* + * Remember: In addition to honoring the consts of cache->refresh and + * cache->fallback, anything these structures point to MUST NOT be + * modified either. + */ + + if (cage->refresh) { + cage->refresh = NULL; + return cage->fallback != NULL; + } + if (cage->fallback) + cage->fallback = NULL; + return false; +} + +struct mft_meta const * +cage_mft_fallback(struct cache_cage *cage) +{ + return cage->mft; +} + +/* + * Steals ownership of @rpp->files, @rpp->nfiles and @rpp->mft.num, but they're + * not going to be modified nor deleted until the cache cleanup. + */ +void +cache_commit_rpp(struct uri const *rpkiNotify, struct uri const *caRepository, + struct rpp *rpp) +{ + struct cache_commit *commit; + + commit = pmalloc(sizeof(struct cache_commit)); + uri_copy(&commit->rpkiNotify, rpkiNotify); + uri_copy(&commit->caRepository, caRepository); + commit->files = rpp->files; + commit->nfiles = rpp->nfiles; + INTEGER_move(&commit->mft.num, &rpp->mft.num); + commit->mft.update = rpp->mft.update; + + mutex_lock(&commits_lock); + STAILQ_INSERT_TAIL(&commits, commit, lh); + mutex_unlock(&commits_lock); + + rpp->files = NULL; + rpp->nfiles = 0; +} + +void +cache_commit_file(struct cache_mapping const *map) +{ + struct cache_commit *commit; + + commit = pzalloc(sizeof(struct cache_commit)); + memset(&commit->rpkiNotify, 0, sizeof(commit->rpkiNotify)); + memset(&commit->caRepository, 0, sizeof(commit->caRepository)); + commit->files = pmalloc(sizeof(*map)); + map_copy(commit->files, map); + commit->nfiles = 1; + memset(&commit->mft, 0, sizeof(commit->mft)); + + mutex_lock(&commits_lock); + STAILQ_INSERT_TAIL(&commits, commit, lh); + mutex_unlock(&commits_lock); +} + +void +rsync_finished(struct uri const *url, char const *path) +{ + struct cache_node *node; + + mutex_lock(&cache.rsync.lock); + + node = find_node(&cache.rsync, uri_str(url), uri_len(url)); + if (node == NULL) { + mutex_unlock(&cache.rsync.lock); + pr_op_err("rsync '%s -> %s' finished, but cache node does not exist.", + uri_str(url), path); + return; + } + if (node->state != DLS_ONGOING) + pr_op_warn("rsync '%s -> %s' finished, but existing node was not in ONGOING state.", + uri_str(url), path); + + node->state = DLS_FRESH; + node->verdict = VV_CONTINUE; + node->success_ts = node->attempt_ts; + mutex_unlock(&cache.rsync.lock); + + task_wakeup_dormants(); +} + +struct uri const * +cage_rpkiNotify(struct cache_cage *cage) +{ + return &cage->rpkiNotify; +} + +static void +cachent_print(struct cache_node *node) +{ + if (!node) + return; + + printf("\thttp:%s rsync:%s (%s): ", uri_str(&node->key.http), + uri_str(&node->key.rsync), node->path); + switch (node->state) { + case DLS_OUTDATED: + printf("stale "); + break; + case DLS_ONGOING: + printf("downloading "); + break; + case DLS_FRESH: + printf("fresh (%s) ", node->verdict); + break; + } + + printf("attempt:%lx success:%lx ", node->attempt_ts, node->success_ts); + printf("mftUpdate:%lx ", node->mft.update); + rrdp_print(node->rrdp); + printf("\n"); +} + +static void +table_print(struct cache_table *tbl) +{ + struct cache_node *node, *tmp; + + printf("%s enabled:%d seq:%s/%lx\n", + tbl->name, tbl->enabled, + tbl->seq.prefix, tbl->seq.next_id); + HASH_ITER(hh, tbl->nodes, node, tmp) + cachent_print(node); +} + +void +cache_print(void) +{ + table_print(&cache.rsync); + table_print(&cache.https); + table_print(&cache.rrdp); + table_print(&cache.fallback); +} + +static bool +is_fallback(char const *path) +{ + return str_starts_with(path, "fallback/"); +} + +/* Hard-links @rpp's approved files into the fallback directory. */ +static void +commit_rpp(struct cache_commit *commit, struct cache_node *fb) +{ + struct cache_mapping *src; + char const *dst; + array_index i; + + INTEGER_move(&fb->mft.num, &commit->mft.num); + fb->mft.update = commit->mft.update; + + for (i = 0; i < commit->nfiles; i++) { + src = commit->files + i; + + if (is_fallback(src->path)) + continue; + + /* + * (fine) + * Note, this is accidentally working perfectly for rsync too. + * Might want to rename some of this. + */ + dst = rrdp_create_fallback(fb->path, &fb->rrdp, &src->url); + if (!dst) + goto skip; + + file_ln(src->path, dst); + +skip: free(src->path); + src->path = pstrdup(dst); + } +} + +/* Deletes abandoned (ie. no longer ref'd by manifests) fallback hard links. */ +static void +discard_trash(struct cache_commit *commit, struct cache_node *fallback) +{ + DIR *dir; + struct dirent *file; + char *file_path; + array_index i; + + dir = opendir(fallback->path); + if (dir == NULL) { + pr_op_err("opendir() error: %s", strerror(errno)); + return; + } + + FOREACH_DIR_FILE(dir, file) { + if (S_ISDOTS(file)) + continue; + + /* + * TODO (fine) Bit slow; wants a hash table, + * and maybe skip @file_path's reallocation. + */ + + file_path = path_join(fallback->path, file->d_name); + + for (i = 0; i < commit->nfiles; i++) { + if (commit->files[i].path == NULL) + continue; + if (strcmp(file_path, commit->files[i].path) == 0) + goto next; + } + + /* + * Uh... maybe keep the file until an expiration threshold? + * None of the current requirements seem to mandate it. + * It sounds pretty unreasonable for a signed valid manifest to + * "forget" a file, then legitimately relist it without actually + * providing it. + */ + pr_op_debug("Removing hard link: %s", file_path); + if (unlink(file_path) < 0) + pr_op_warn("Could not unlink %s: %s", + file_path, strerror(errno)); + +next: free(file_path); + } + + if (errno) + pr_op_err("Fallback directory traversal errored: %s", + strerror(errno)); + closedir(dir); +} + +static void +commit_fallbacks(time_t now) +{ + struct cache_commit *commit; + struct cache_node *fb; + array_index i; + int error; + + while (!STAILQ_EMPTY(&commits)) { + commit = STAILQ_FIRST(&commits); + STAILQ_REMOVE_HEAD(&commits, lh); + + if (uri_str(&commit->caRepository) != NULL) { + pr_op_debug("Creating fallback for %s (%s)", + uri_str(&commit->caRepository), + uri_str(&commit->rpkiNotify)); + + fb = provide_node(&cache.fallback, + &commit->rpkiNotify, + &commit->caRepository); + fb->attempt_ts = now; + fb->success_ts = now; + + pr_op_debug("mkdir -f %s", fb->path); + if (mkdir(fb->path, CACHE_FILEMODE) < 0) { + error = errno; + if (error != EEXIST) { + pr_op_err("Cannot create '%s': %s", + fb->path, strerror(error)); + goto skip; + } + + rm_metadata(fb); /* error == EEXIST */ + } + + commit_rpp(commit, fb); + discard_trash(commit, fb); + + } else { /* TA */ + struct cache_mapping *map = &commit->files[0]; + + pr_op_debug("Creating fallback for %s", + uri_str(&map->url)); + + fb = provide_node(&cache.fallback, &map->url, NULL); + fb->attempt_ts = now; + fb->success_ts = now; + if (is_fallback(map->path)) + goto freshen; + + file_ln(map->path, fb->path); + } + + write_metadata(fb); + +freshen: fb->state = DLS_FRESH; +skip: uri_cleanup(&commit->rpkiNotify); + uri_cleanup(&commit->caRepository); + for (i = 0; i < commit->nfiles; i++) { + uri_cleanup(&commit->files[i].url); + free(commit->files[i].path); + } + free(commit->files); + mftm_cleanup(&commit->mft); + free(commit); + } +} + +static void +remove_abandoned(struct cache_table *table, struct cache_node *node, void *arg) +{ + time_t now; + + if (node->state == DLS_FRESH) + return; + + now = *((time_t *)arg); + if (difftime(node->attempt_ts + cfg_cache_threshold(), now) < 0) { + rm_metadata(node); + file_rm_rf(node->path); + delete_node(table, node, NULL); + } +} + +static void +remove_orphaned_nodes(struct cache_table *table, struct cache_node *node, + void *arg) +{ + if (file_stat_errno(node->path) == ENOENT) { + pr_op_debug("Missing file; deleting node: %s", node->path); + delete_node(table, node, NULL); + } +} + +static void +remove_orphaned_files(void) +{ + // XXX +} + +/* Deletes obsolete files and nodes from the cache. */ +static void +cleanup_cache(void) +{ + time_t now = time_fatal(); + + /* Delete the entirety of cache/tmp/. */ + pr_op_debug("Cleaning up temporal files."); + file_rm_rf(CACHE_TMPDIR); + + /* + * Ensure valid RPPs and TAs are linked in fallback, + * by hard-linking the new files. + */ + pr_op_debug("Committing fallbacks."); + commit_fallbacks(now); + + /* + * Delete refresh nodes that haven't been downloaded in a while, + * and fallback nodes that haven't been valid in a while. + */ + pr_op_debug("Cleaning up abandoned cache files."); + foreach_node(remove_abandoned, &now); + + /* (Paranoid) Delete nodes that are no longer mapped to files. */ + pr_op_debug("Cleaning up orphaned nodes."); + foreach_node(remove_orphaned_nodes, NULL); + + /* (Paranoid) Delete files that are no longer mapped to nodes. */ + pr_op_debug("Cleaning up orphaned files."); + remove_orphaned_files(); +} + +void +cache_commit(void) +{ + cleanup_cache(); + file_write_txt(METAFILE, "{ \"fort-version\": \"" PACKAGE_VERSION "\" }"); + unlock_cache(); + flush_nodes(); +} + +void +exturis_init(struct extension_uris *uris) +{ + memset(uris, 0, sizeof(*uris)); +} + +void +exturis_cleanup(struct extension_uris *uris) +{ + uri_cleanup(&uris->caRepository); + uri_cleanup(&uris->rpkiNotify); + uri_cleanup(&uris->rpkiManifest); + uri_cleanup(&uris->crldp); + uri_cleanup(&uris->caIssuers); + uri_cleanup(&uris->signedObject); +} diff --cc src/cache.h index d78b125f,00000000..bebd072a mode 100644,000000..100644 --- a/src/cache.h +++ b/src/cache.h @@@ -1,58 -1,0 +1,55 @@@ +#ifndef SRC_CACHE_LOCAL_CACHE_H_ +#define SRC_CACHE_LOCAL_CACHE_H_ + - #include +#include "common.h" - #include "types/map.h" +#include "types/rpp.h" - #include "types/uri.h" + +int cache_setup1(void); +int cache_setup2(void); +void cache_atexit(void); + +int cache_prepare(void); /* Prepare cache for new validation cycle */ +void cache_commit(void); /* Finish validation cycle */ + +struct extension_uris { + struct uri caRepository; /* RPP cage */ + struct uri rpkiNotify; /* RRDP Notification */ + struct uri rpkiManifest; + + /** + * CRL Distribution Points's fullName. Non-TA certificates only. + * RFC 6487, section 4.8.6. + */ + struct uri crldp; + /** + * AIA's caIssuers. Non-TA certificates only. + * RFC 6487, section 4.8.7. + */ + struct uri caIssuers; + /** + * SIA's signedObject. EE certificates only. + * RFC 6487, section 4.8.8.2. + */ + struct uri signedObject; +}; + +void exturis_init(struct extension_uris *); +void exturis_cleanup(struct extension_uris *); + +validation_verdict cache_refresh_by_url(struct uri const *, char **); +validation_verdict cache_get_fallback(struct uri const *, char **); + +struct cache_cage; +validation_verdict cache_refresh_by_uris(struct extension_uris *, + struct cache_cage **); +char *cage_map_file(struct cache_cage *, struct uri const *); +bool cage_downgrade(struct cache_cage *); +struct mft_meta const *cage_mft_fallback(struct cache_cage *); +void cache_commit_rpp(struct uri const *, struct uri const *, struct rpp *); +void cache_commit_file(struct cache_mapping const *); + +struct uri const *cage_rpkiNotify(struct cache_cage *); + +void cache_print(void); /* Dump cache in stdout */ + +#endif /* SRC_CACHE_LOCAL_CACHE_H_ */ diff --cc src/cachetmp.c index b9cccc8d,00000000..42ffa0c0 mode 100644,000000..100644 --- a/src/cachetmp.c +++ b/src/cachetmp.c @@@ -1,31 -1,0 +1,29 @@@ +#include "cachetmp.h" + +#include - #include + +#include "log.h" - #include "types/path.h" + +static atomic_uint file_counter; + +/* + * Returns (in @buf, which needs to length CACHE_TMPFILE_BUFLEN) a unique + * temporary file name in the local cache. + * Note, it's a name, and it's pretty much reserved. The file itself will not be + * created. + * + * The file will not be automatically deleted when it is closed or the program + * terminates. + */ +void +cache_tmpfile(char *buf) +{ + unsigned int next; + int written; + + next = atomic_fetch_add(&file_counter, 1u); + + written = snprintf(buf, CACHE_TMPFILE_BUFLEN, CACHE_TMPDIR "/%X", next); + if (written >= CACHE_TMPFILE_BUFLEN) + pr_crit("I ran out of temporal directories: %u", next); +} diff --cc src/common.c index 12383c7a,46deb10a..0fbef3b1 --- a/src/common.c +++ b/src/common.c @@@ -6,16 -3,14 +6,18 @@@ #include #include #include ++#include + #include #include + #include #include "alloc.h" --#include "config.h" #include "log.h" +validation_verdict const VV_CONTINUE = "Continue"; +validation_verdict const VV_FAIL = "Failure"; +validation_verdict const VV_BUSY = "Busy"; + bool str_starts_with(char const *str, char const *prefix) { diff --cc src/common.h index 09eb8e43,01c3a788..0d4d2bec --- a/src/common.h +++ b/src/common.h @@@ -1,9 -1,8 +1,11 @@@ #ifndef SRC_RTR_COMMON_H_ #define SRC_RTR_COMMON_H_ ++#include #include #include ++#include +#include #include #include diff --cc src/config.c index bfbf0488,25a5c767..6bc99a6b --- a/src/config.c +++ b/src/config.c @@@ -1,19 -1,17 +1,19 @@@ #include "config.h" - #include --#include #include #include +#include + #include #include - #include ++#include #include -#include "common.h" +#include "alloc.h" #include "config/boolean.h" + #include "config/curl_offset.h" #include "config/incidences.h" #include "config/str.h" +#include "config/time.h" #include "config/uint.h" #include "config/work_offline.h" #include "configure_ac.h" @@@ -22,9 -19,8 +22,10 @@@ #include "init.h" #include "json_handler.h" #include "log.h" -#include "state.h" -#include "thread/thread_pool.h" ++#include "object/tal.h" +#include "thread_pool.h" +#include "types/array.h" +#include "types/path.h" /** * To add a member to this structure, @@@ -119,31 -126,14 +124,30 @@@ struct rpki_config unsigned int low_speed_limit; /* CURLOPT_LOW_SPEED_TIME for our HTTP transfers. */ unsigned int low_speed_time; - /* - * CURLOPT_MAXFILESIZE, except it also works for unknown size - * files. (Though this is reactive, not preventive.) - */ - unsigned int max_file_size; + /* CURLOPT_MAXFILESIZE_LARGE for our HTTP transfers. */ + curl_off_t max_file_size; /* Directory where CA certs to verify peers are found */ char *ca_path; + /* See CURLOPT_PROXY */ + char *proxy; } http; + struct { + /* + * Maximum deltas to explode per RRDP session, per iteration. + * + * (If the RRDP notification lists more than this amount of + * unprocessed deltas, Fort will reset the session, exploding + * the snapshot instead.) + * + * Per draft-spaghetti-sidrops-rrdp-desynchronization's + * recommendation, this is also the maximum number of delta + * hashes Fort will remember per RRDP session, to detect session + * desynchronization. + */ + unsigned int delta_threshold; + } rrdp; + struct { /** Enables operation logs **/ bool enabled; @@@ -600,21 -610,17 +617,29 @@@ static const struct option_field option .offset = offsetof(struct rpki_config, http.ca_path), .doc = "Directory where CA certificates are found, used to verify the peer", .arg_doc = "", - .json_null_allowed = false, + .json_null_allowed = true, + }, { + .id = 9013, + .name = "http.proxy", + .type = >_string, + .offset = offsetof(struct rpki_config, http.proxy), + .doc = "Name of proxy to use", + .arg_doc = "", + .json_null_allowed = true, }, + /* RRDP */ + { + .id = 10000, + .name = "rrdp.delta-threshold", + .type = >_uint, + .offset = offsetof(struct rpki_config, rrdp.delta_threshold), + .doc = "Maximum deltas to explode per RRDP session, per iteration. " + "(Fall back to snapshot if threshold exceeded.)", + .min = 1, + .max = 128, + }, + /* Logging fields */ { .id = 4000, @@@ -986,28 -986,35 +1014,31 @@@ set_default_values(void rpki_config.server.interval.expire = 7200; rpki_config.server.deltas_lifetime = 2; + rpki_config.prometheus.port = 0; + rpki_config.rsync.enabled = true; rpki_config.rsync.priority = 50; + rpki_config.rsync.max = 1; rpki_config.rsync.strategy = pstrdup(""); - rpki_config.rsync.retry.count = 1; - rpki_config.rsync.retry.interval = 4; rpki_config.rsync.transfer_timeout = 900; rpki_config.rsync.program = pstrdup("rsync"); - string_array_init(&rpki_config.rsync.args.flat, - flat_rsync_args, ARRAY_LEN(flat_rsync_args)); - string_array_init(&rpki_config.rsync.args.recursive, - recursive_rsync_args, ARRAY_LEN(recursive_rsync_args)); + string_array_init(&rpki_config.rsync.args, trash, ARRAY_LEN(trash)); rpki_config.http.enabled = true; - /* Higher priority than rsync by default */ rpki_config.http.priority = 60; - rpki_config.http.retry.count = 1; - rpki_config.http.retry.interval = 4; rpki_config.http.user_agent = pstrdup(PACKAGE_NAME "/" PACKAGE_VERSION); rpki_config.http.max_redirs = 10; rpki_config.http.connect_timeout = 30; rpki_config.http.transfer_timeout = 900; rpki_config.http.low_speed_limit = 100000; rpki_config.http.low_speed_time = 10; - rpki_config.http.max_file_size = 1000000000; + rpki_config.http.max_file_size = 2000000000; rpki_config.http.ca_path = NULL; /* Use system default */ + rpki_config.http.proxy = NULL; + /* TODO (fine) 64 may be too much; optimize it. */ + rpki_config.rrdp.delta_threshold = 64; + rpki_config.log.enabled = true; rpki_config.log.tag = NULL; rpki_config.log.color = false; @@@ -1064,9 -1073,17 +1097,17 @@@ validate_config(void rpki_config.server.interval.retry) return pr_op_err("Expire interval must be greater than refresh and retry intervals"); - if (rpki_config.slurm != NULL && !valid_file_or_dir(rpki_config.slurm, true)) + if (rpki_config.slurm != NULL && !file_is_valid(rpki_config.slurm, true)) return pr_op_err("Invalid slurm location."); + if (rpki_config.http.proxy == NULL) { + proxy = curl_getenv("https_proxy"); + if (proxy == NULL) + proxy = curl_getenv("HTTPS_PROXY"); + if (proxy != NULL && proxy[0] != '\0') + rpki_config.http.proxy = pstrdup(proxy); + } + return 0; } @@@ -1448,6 -1434,30 +1495,12 @@@ config_get_http_enabled(void return !rpki_config.work_offline && rpki_config.http.enabled; } -unsigned int -config_get_http_priority(void) -{ - return rpki_config.http.priority; -} - -unsigned int -config_get_http_retry_count(void) -{ - return rpki_config.http.retry.count; -} - -unsigned int -config_get_http_retry_interval(void) -{ - return rpki_config.http.retry.interval; -} - + char const * + config_get_http_proxy(void) + { + return rpki_config.http.proxy; + } + char const * config_get_http_user_agent(void) { diff --cc src/config.h index f64bd78f,ee21f0d5..68aaaf2c --- a/src/config.h +++ b/src/config.h @@@ -5,7 -6,6 +6,8 @@@ #include #include #include +#include ++#include #include "config/file_type.h" #include "config/filename_format.h" @@@ -40,14 -40,20 +43,15 @@@ long config_get_http_connect_timeout(vo long config_get_http_transfer_timeout(void); long config_get_http_low_speed_limit(void); long config_get_http_low_speed_time(void); - long config_get_http_max_file_size(void); + curl_off_t config_get_http_max_file_size(void); char const *config_get_http_ca_path(void); +unsigned int config_get_rrdp_delta_threshold(void); bool config_get_rsync_enabled(void); -unsigned int config_get_rsync_priority(void); -unsigned int config_get_rsync_retry_count(void); -unsigned int config_get_rsync_retry_interval(void); -long config_get_rsync_transfer_timeout(void); -char *config_get_rsync_program(void); -struct string_array const *config_get_rsync_args(void); +unsigned int config_rsync_max(void); +long config_rsync_timeout(void); +char const *config_get_rsync_program(void); bool config_get_http_enabled(void); -unsigned int config_get_http_priority(void); -unsigned int config_get_http_retry_count(void); -unsigned int config_get_http_retry_interval(void); + char const *config_get_http_proxy(void); char const *config_get_output_roa(void); char const *config_get_output_bgpsec(void); enum output_format config_get_output_format(void); diff --cc src/config/string_array.c index b05e62f5,a8645e2d..323df95f --- a/src/config/string_array.c +++ b/src/config/string_array.c @@@ -5,8 -6,7 +6,7 @@@ #include "alloc.h" #include "config/str.h" #include "log.h" - #include "types/path.h" -#include "str_token.h" +#include "types/str.h" void string_array_init(struct string_array *array, char const *const *values, diff --cc src/config/time.c index fc1deee8,00000000..fcac29c6 mode 100644,000000..100644 --- a/src/config/time.c +++ b/src/config/time.c @@@ -1,55 -1,0 +1,54 @@@ +#include "config/time.h" + - #include +#include - #include ++#include + +#include "common.h" +#include "log.h" + +static void +print_time(struct option_field const *field, void *value) +{ + time_t tt; + char str[FORT_TS_LEN]; + int error; + + tt = *((time_t *)value); + if (tt == 0) + return; + + error = time2str(tt, str); + if (error) + pr_crit("time2str: %s", strerror(error)); + + pr_op_info("%s: %s", field->name, str); +} + +static int +parse_argv_time(struct option_field const *field, char const *str, + void *result) +{ + if (str == NULL || strlen(str) == 0) + return pr_op_err("--%s needs an argument.", field->name); + + return str2time(str, result); +} + +static int +parse_json_time(struct option_field const *opt, json_t *json, void *result) +{ + if (!json_is_string(json)) + return pr_op_err("The '%s' element is not a JSON string.", + opt->name); + + return str2time(json_string_value(json), result); +} + +const struct global_type gt_time = { + .has_arg = required_argument, + .size = sizeof(time_t), + .print = print_time, + .parse.argv = parse_argv_time, + .parse.json = parse_json_time, + .arg_doc = FORT_TS_FORMAT, +}; diff --cc src/ext.c index d4dab681,72531a36..39221be3 --- a/src/ext.c +++ b/src/ext.c @@@ -3,8 -3,8 +3,11 @@@ #include #include #include ++#include -#include "crypto/hash.h" ++#include "asn1/asn1c/ASIdentifiers.h" ++#include "asn1/asn1c/IPAddressFamily.h" +#include "hash.h" #include "json_util.h" #include "libcrypto_util.h" #include "log.h" diff --cc src/file.c index a46a9bd1,29b1dce9..a2a349f1 --- a/src/file.c +++ b/src/file.c @@@ -3,11 -3,8 +3,9 @@@ #include #include "alloc.h" +#include "common.h" #include "config/mode.h" #include "log.h" - #include "types/path.h" int file_open(char const *file_name, FILE **result, struct stat *stat) diff --cc src/file.h index 8041c695,f334dd22..f8058a06 --- a/src/file.h +++ b/src/file.h @@@ -3,12 -3,9 +3,10 @@@ #include #include +#include #include #include - #include #include - #include #include #include #include diff --cc src/hash.c index cb7a7a42,00000000..d27f6b64 mode 100644,000000..100644 --- a/src/hash.c +++ b/src/hash.c @@@ -1,251 -1,0 +1,252 @@@ +#include "hash.h" + +#include ++#include + +#include "alloc.h" +#include "file.h" +#include "log.h" + +/* + * TODO (fine) Delete this structure (use md directly) once OpenSSL < 3 support + * is dropped. + */ +struct hash_algorithm { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD *md; +#else + EVP_MD const *md; +#endif + size_t size; + char const *name; +}; + +/* + * EVP_sha256() and EVP_sha1() are now mildly deprecated ("present for + * compatibility with OpenSSL before version 3.0"). + * + * This is because they want to encourage explicit fetching, but also because + * they want us to stop hardcoding the algorithms in the code. + * + * But we're RFC-bound to use these algorithms, so we only want the explicit + * fetching part. (Which is done during hash_setup().) + */ +static struct hash_algorithm sha1; +static struct hash_algorithm sha256; + +int +hash_setup(void) +{ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + sha1.md = EVP_MD_fetch(NULL, "SHA1", NULL); + if (sha1.md == NULL) + return pr_op_err("This version of libcrypto does not seem to support SHA1."); + sha1.size = EVP_MD_get_size(sha1.md); + sha1.name = EVP_MD_get0_name(sha1.md); + + sha256.md = EVP_MD_fetch(NULL, "SHA256", NULL); + if (sha256.md == NULL) { + EVP_MD_free(sha1.md); + return pr_op_err("This version of libcrypto does not seem to support SHA256."); + } + sha256.size = EVP_MD_get_size(sha256.md); + sha256.name = EVP_MD_get0_name(sha256.md); + +#else + sha1.md = EVP_get_digestbyname("sha1"); + if (sha1.md == NULL) + return pr_op_err("This version of libcrypto does not seem to support SHA1."); + sha1.size = EVP_MD_size(sha1.md); + sha1.name = EVP_MD_name(sha1.md); + + sha256.md = EVP_get_digestbyname("sha256"); + if (sha256.md == NULL) + return pr_op_err("This version of libcrypto does not seem to support SHA256."); + sha256.size = EVP_MD_size(sha256.md); + sha256.name = EVP_MD_name(sha256.md); + +#endif + + return 0; +} + +void +hash_teardown(void) +{ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD_free(sha256.md); + EVP_MD_free(sha1.md); +#endif +} + +struct hash_algorithm const * +hash_get_sha1(void) +{ + return &sha1; +} + +struct hash_algorithm const * +hash_get_sha256(void) +{ + return &sha256; +} + +int +hash_file(struct hash_algorithm const *algorithm, char const *filename, + unsigned char *result, size_t *result_size) +{ + FILE *file; + struct stat stat; + unsigned char *buffer; + size_t consumed; + EVP_MD_CTX *ctx; + unsigned int hash_size; + int error; + + error = file_open(filename, &file, &stat); + if (error) + return error; + + buffer = pmalloc(stat.st_blksize); + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) + enomem_panic(); + + if (!EVP_DigestInit_ex(ctx, algorithm->md, NULL)) { + error = val_crypto_err("EVP_DigestInit_ex() failed"); + goto end; + } + + do { + consumed = fread(buffer, 1, stat.st_blksize, file); + error = ferror(file); + if (error) { + pr_val_err("File reading error. Error message (apparently): %s", + strerror(error)); + goto end; + } + + if (!EVP_DigestUpdate(ctx, buffer, consumed)) { + error = val_crypto_err("EVP_DigestUpdate() failed"); + goto end; + } + + } while (!feof(file)); + + if (!EVP_DigestFinal_ex(ctx, result, &hash_size)) { + error = val_crypto_err("EVP_DigestFinal_ex() failed"); + goto end; + } + if (hash_size != algorithm->size) { + error = pr_op_err("libcrypto returned a %s hash sized %u bytes.", + algorithm->name, hash_size); + } + + if (result_size) + *result_size = hash_size; + +end: + EVP_MD_CTX_free(ctx); + free(buffer); + file_close(file); + return error; +} + +int +hash_validate_file(struct hash_algorithm const *algorithm, char const *path, + unsigned char const *expected, size_t expected_len) +{ + unsigned char actual[EVP_MAX_MD_SIZE]; + size_t actual_len; + int error; + + pr_clutter("Validating file hash: %s", path); + + if (expected_len != hash_get_size(algorithm)) + return pr_val_err("%s string has bogus size: %zu", + hash_get_name(algorithm), expected_len); + + error = hash_file(algorithm, path, actual, &actual_len); + if (error) + return error; + + if (expected_len != actual_len) + goto fail; + if (memcmp(expected, actual, expected_len) != 0) + goto fail; + + return 0; + +fail: + error = pr_val_err("File '%s' does not match its expected hash.", path); +#ifdef UNIT_TESTING + size_t i; + printf("Expected: "); + for (i = 0; i < expected_len; i++) + printf("%02x", expected[i]); + printf("\nActual: "); + for (i = 0; i < actual_len; i++) + printf("%02x", actual[i]); + printf("\n"); +#endif + return error; +} + +static int +hash_buffer(struct hash_algorithm const *algorithm, + unsigned char const *content, size_t content_len, unsigned char *hash) +{ + EVP_MD_CTX *ctx; + unsigned int actual_len; + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) + enomem_panic(); + + if (!EVP_DigestInit_ex(ctx, algorithm->md, NULL) || + !EVP_DigestUpdate(ctx, content, content_len) || + !EVP_DigestFinal_ex(ctx, hash, &actual_len)) { + EVP_MD_CTX_free(ctx); + return val_crypto_err("Buffer hashing failed"); + } + + EVP_MD_CTX_free(ctx); + + if (actual_len != algorithm->size) + pr_crit("libcrypto returned a %s hash sized %u bytes.", + algorithm->name, actual_len); + + return 0; +} + +int +hash_validate(struct hash_algorithm const *algorithm, unsigned char const *data, + size_t data_len, unsigned char const *expected, size_t expected_len) +{ + unsigned char actual[EVP_MAX_MD_SIZE]; + int error; + + error = hash_buffer(algorithm, data, data_len, actual); + if (error) + return -error; + + if (expected_len != algorithm->size) + return EINVAL; + if (memcmp(expected, actual, expected_len) != 0) + return EINVAL; + + return 0; +} + +char const * +hash_get_name(struct hash_algorithm const *algorithm) +{ + return algorithm->name; +} + +size_t +hash_get_size(struct hash_algorithm const *algorithm) +{ + return algorithm->size; +} diff --cc src/hash.h index 386ecb41,00000000..1269b3b1 mode 100644,000000..100644 --- a/src/hash.h +++ b/src/hash.h @@@ -1,31 -1,0 +1,25 @@@ +#ifndef SRC_HASH_H_ +#define SRC_HASH_H_ + +#include - #include - #include - #include - #include - #include - #include + +struct hash_algorithm; + +int hash_setup(void); +void hash_teardown(void); + +struct hash_algorithm const *hash_get_sha1(void); +struct hash_algorithm const *hash_get_sha256(void); + +int hash_file(struct hash_algorithm const *, char const *, unsigned char *, + size_t *); + +int hash_validate_file(struct hash_algorithm const *, char const *, + unsigned char const *, size_t); +int hash_validate(struct hash_algorithm const *, unsigned char const *, size_t, + unsigned char const *, size_t); + +char const *hash_get_name(struct hash_algorithm const *); +size_t hash_get_size(struct hash_algorithm const *); + +#endif /* SRC_HASH_H_ */ diff --cc src/http.c index 17e5d70f,2bd31519..cf4c98e0 --- a/src/http.c +++ b/src/http.c @@@ -1,7 -1,7 +1,5 @@@ -#include "http/http.h" +#include "http.h" - #include "alloc.h" -#include "cache/local_cache.h" --#include "common.h" #include "config.h" #include "file.h" #include "log.h" diff --cc src/http.h index 8f352d74,00000000..7d18cfb1 mode 100644,000000..100644 --- a/src/http.h +++ b/src/http.h @@@ -1,14 -1,0 +1,13 @@@ +#ifndef SRC_HTTP_H_ +#define SRC_HTTP_H_ + +#include - #include + +#include "types/uri.h" + +int http_init(void); +void http_cleanup(void); + +int http_download(struct uri const *, char const *, curl_off_t, bool *); + +#endif /* SRC_HTTP_H_ */ diff --cc src/init.c index b2bafbc7,a4d7e7e6..924315e2 --- a/src/init.c +++ b/src/init.c @@@ -1,9 -1,8 +1,11 @@@ #include "init.h" +#include ++#include ++ #include "config.h" -#include "data_structure/path_builder.h" -#include "http/http.h" +#include "http.h" +#include "types/path.h" static int fetch_url(char const *url, char const *filename) diff --cc src/json_util.c index af9e0ce0,58d9f2a1..aa557e8b --- a/src/json_util.c +++ b/src/json_util.c @@@ -1,13 -1,19 +1,10 @@@ #include "json_util.h" #include --#include -#include --#include - #include "alloc.h" +#include "common.h" #include "log.h" -/* - * Careful with this; several of the conversion specification characters - * documented in the Linux man page are not actually portable. - */ -#define JSON_TS_FORMAT "%Y-%m-%dT%H:%M:%SZ" -#define JSON_TS_LEN 21 /* strlen("YYYY-mm-ddTHH:MM:SSZ") + 1 */ - int json_get_str(json_t *parent, char const *name, char const **result) { diff --cc src/json_util.h index 3e08b75b,dcd22dbf..08d82078 --- a/src/json_util.h +++ b/src/json_util.h @@@ -1,24 -1,15 +1,11 @@@ #ifndef SRC_JSON_UTIL_H_ #define SRC_JSON_UTIL_H_ --#include --#include #include --#include --#include --#include --#include - #include - #include - #include - #include #include --#include - #include + +#include "asn1/asn1c/INTEGER.h" - #include "file.h" +#include "types/uri.h" /* * Contract of get functions: diff --cc src/libcrypto_util.c index f4e60d28,74437c55..bc2ada0c --- a/src/libcrypto_util.c +++ b/src/libcrypto_util.c @@@ -7,9 -7,9 +7,8 @@@ #include #include --#include "alloc.h" #include "asn1/asn1c/OBJECT_IDENTIFIER.h" -#include "extension.h" +#include "ext.h" #include "json_util.h" #include "log.h" diff --cc src/log.c index f63ef23f,ecff284e..24314041 --- a/src/log.c +++ b/src/log.c @@@ -6,9 -6,11 +6,10 @@@ #endif #include #include -#include #include -#include #include #include ++#include #include "config.h" #include "thread_var.h" diff --cc src/main.c index 5a3eaaeb,84cc2d58..cb015262 --- a/src/main.c +++ b/src/main.c @@@ -1,21 -1,16 +1,23 @@@ #include +#include "cache.h" #include "config.h" -#include "extension.h" -#include "http/http.h" +#include "ext.h" +#include "hash.h" +#include "http.h" #include "log.h" #include "nid.h" +#include "output_printer.h" #include "print_file.h" + #include "prometheus.h" +#include "relax_ng.h" ++#include "rsync.h" +#include "rtr/db/vrps.h" #include "rtr/rtr.h" - #include "rsync.h" +#include "sig.h" + #include "stats.h" +#include "task.h" #include "thread_var.h" -#include "xml/relax_ng.h" static int fort_standalone(void) @@@ -66,10 -59,11 +66,10 @@@ fort_server(void pr_op_info("Main loop: Time to work!"); error = vrps_update(&changed); - if (error == -EINTR) + if (error == EINTR) break; if (error) { - pr_op_debug("Main loop: %s", strerror(error)); - pr_op_debug("Main loop: Error %d (%s)", error, - strerror(abs(error))); ++ pr_op_debug("Main loop: %s", strerror(abs(error))); continue; } if (changed) @@@ -119,28 -113,29 +119,34 @@@ main(int argc, char **argv int error; /* Initializations */ + /* (Do not start any threads until after rsync_setup() has forked.) */ - error = log_setup(false); + error = log_setup(); if (error) goto just_quit; - - error = thvar_init(); + error = handle_flags_config(argc, argv); if (error) goto revert_log; - error = incidence_init(); + + error = cache_setup1(); if (error) - goto revert_log; - error = handle_flags_config(argc, argv); + goto revert_config; + + rsync_setup(NULL, NULL); /* Fork rsync spawner ASAP */ + register_signal_handlers(); + + error = thvar_init(); if (error) - goto revert_log; + goto revert_rsync; - error = nid_init(); + error = stats_setup(); if (error) - goto revert_config; + goto revert_rsync; + error = prometheus_setup(); + if (error) + goto revert_stats; + error = nid_init(); + if (error) + goto revert_prometheus; error = extension_init(); if (error) goto revert_nid; @@@ -186,12 -171,12 +192,16 @@@ revert_relax_ng relax_ng_cleanup(); revert_http: http_cleanup(); +revert_hash: + hash_teardown(); revert_nid: nid_destroy(); + revert_prometheus: + prometheus_teardown(); + revert_stats: + stats_teardown(); +revert_rsync: + rsync_teardown(); revert_config: free_rpki_config(); revert_log: diff --cc src/object/bgpsec.c index 0eba44bb,8f5a45d6..f81a0191 --- a/src/object/bgpsec.c +++ b/src/object/bgpsec.c @@@ -2,7 -2,7 +2,7 @@@ #include "log.h" #include "object/certificate.h" --#include "validation_handler.h" ++#include "types/router_key.h" struct resource_params { unsigned char const *ski; diff --cc src/object/certificate.c index 87079659,731c9b82..7e4b416a --- a/src/object/certificate.c +++ b/src/object/certificate.c @@@ -8,30 -8,21 +8,23 @@@ #include #include #include --#include --#include #include "algorithm.h" #include "asn1/asn1c/IPAddrBlocks.h" #include "asn1/decode.h" - #include "cache.h" --#include "common.h" #include "config.h" -#include "extension.h" +#include "ext.h" #include "libcrypto_util.h" #include "log.h" #include "nid.h" +#include "object/ghostbusters.h" #include "object/manifest.h" -#include "str_token.h" +#include "object/roa.h" +#include "object/signed_object.h" - #include "object/tal.h" +#include "task.h" #include "thread_var.h" +#include "types/name.h" - #include "types/path.h" +#include "types/str.h" - #include "types/uri.h" /* * The X509V3_EXT_METHOD that references NID_sinfo_access uses the AIA item. @@@ -108,29 -154,70 +101,63 @@@ validate_signature_algorithm(X509 *cert return validate_certificate_signature_algorithm(nid, "Certificate"); } + static bool + is_valid_printable_string_char(char c) + { + if ('A' <= c && c <= 'Z') + return true; + if ('a' <= c && c <= 'z') + return true; + if ('0' <= c && c <= '9') + return true; + if (c == ' ') + return true; + if ('\'' <= c && c <= ')') + return true; + if ('+' <= c && c <= '/') + return true; + if (c == ':' || c == '=' || c == '?') + return true; + return false; + } + + static int + validate_printable_string(char const *str, char const *what) + { + for (; *str != '\0'; str++) + if (!is_valid_printable_string_char(*str)) + return pr_val_err("Invalid character in '%s' PrintableString: 0x%X", + what, *str); + return 0; + } + static int -validate_issuer(X509 *cert, bool is_ta) +validate_issuer(struct rpki_certificate *cert) { X509_NAME *issuer; struct rfc5280_name *name; + char const *commonName; int error; - issuer = X509_get_issuer_name(cert); - - if (!is_ta) - return validate_issuer_name("Certificate", issuer); + issuer = X509_get_issuer_name(cert->x509); - /* TODO wait. Shouldn't we check subject == issuer? */ + if (cert->type != CERTYPE_TA) + return validate_issuer_name(issuer, cert->parent->x509); error = x509_name_decode(issuer, "issuer", &name); if (error) return error; - pr_clutter("Issuer: %s", x509_name_commonName(name)); - x509_name_put(name); - return 0; + commonName = x509_name_commonName(name); - pr_val_debug("Issuer: %s", commonName); ++ pr_clutter("Issuer: %s", commonName); + error = validate_printable_string(commonName, "Issuer"); + + x509_name_put(name); + return error; } -/* - * Compare public keys, call @diff_alg_cb when the algorithm is different, call - * @diff_pk_cb when the public key is different; return 0 if both are equal. - */ static int -spki_cmp(X509_PUBKEY *tal_spki, X509_PUBKEY *cert_spki, - int (*diff_alg_cb)(void), int (*diff_pk_cb)(void)) +spki_cmp(X509_PUBKEY *tal_spki, X509_PUBKEY *cert_spki) { ASN1_OBJECT *tal_alg; ASN1_OBJECT *cert_alg; @@@ -178,7 -261,10 +206,10 @@@ validate_subject(X509 *cert error = x509_name_decode(X509_get_subject_name(cert), "subject", &name); if (error) return error; - pr_clutter("Subject: %s", x509_name_commonName(name)); + + commonName = x509_name_commonName(name); - pr_val_debug("Subject: %s", commonName); ++ pr_clutter("Subject: %s", commonName); + error = validate_printable_string(commonName, "Subject"); x509_name_put(name); return error; diff --cc src/object/certificate.h index db3c704b,37413f52..7a3e2e35 --- a/src/object/certificate.h +++ b/src/object/certificate.h @@@ -1,15 -1,10 +1,13 @@@ #ifndef SRC_OBJECT_CERTIFICATE_H_ #define SRC_OBJECT_CERTIFICATE_H_ - #include -#include "asn1/asn1c/ANY.h" -#include "asn1/asn1c/SignatureValue.h" -#include "certificate_refs.h" +#include +#include + - #include "asn1/asn1c/ANY.h" - #include "asn1/asn1c/SignatureValue.h" ++#include "asn1/signed_data.h" +#include "cache.h" ++#include "object/tal.h" #include "resource.h" - #include "types/rpp.h" /* Certificate types in the RPKI */ enum cert_type { diff --cc src/object/crl.c index 98e49f3b,62c5efdd..f5e5ae0b --- a/src/object/crl.c +++ b/src/object/crl.c @@@ -2,16 -2,15 +2,15 @@@ #include #include --#include #include "algorithm.h" -#include "extension.h" +#include "ext.h" #include "log.h" -#include "object/name.h" +#include "thread_var.h" +#include "types/name.h" static int -__crl_load(struct rpki_uri *uri, X509_CRL **result) +__crl_load(char const *path, X509_CRL **result) { X509_CRL *crl; BIO *bio; diff --cc src/object/crl.h index 04240ff3,7aacffdf..a47f098d --- a/src/object/crl.h +++ b/src/object/crl.h @@@ -2,8 -2,9 +2,9 @@@ #define SRC_OBJECT_CRL_H_ #include + -#include "types/uri.h" +#include "types/map.h" -int crl_load(struct rpki_uri *uri, X509_CRL **); +int crl_load(struct cache_mapping *, X509 *, X509_CRL **); #endif /* SRC_OBJECT_CRL_H_ */ diff --cc src/object/ghostbusters.c index 900e66b7,2aae9912..bd49617f --- a/src/object/ghostbusters.c +++ b/src/object/ghostbusters.c @@@ -1,8 -1,6 +1,6 @@@ #include "object/ghostbusters.h" - #include - --#include "log.h" ++#include "object/certificate.h" #include "object/signed_object.h" #include "object/vcard.h" #include "thread_var.h" diff --cc src/object/ghostbusters.h index 39350737,84087b75..86430da5 --- a/src/object/ghostbusters.h +++ b/src/object/ghostbusters.h @@@ -1,8 -1,8 +1,9 @@@ #ifndef SRC_OBJECT_GHOSTBUSTERS_H_ #define SRC_OBJECT_GHOSTBUSTERS_H_ - #include "object/certificate.h" -#include "rpp.h" ++#include "asn1/signed_data.h" ++#include "types/map.h" -int ghostbusters_traverse(struct rpki_uri *, struct rpp *); +int ghostbusters_traverse(struct cache_mapping *, struct rpki_certificate *); #endif /* SRC_OBJECT_GHOSTBUSTERS_H_ */ diff --cc src/object/manifest.c index 514db9bc,8e404aa7..1a132306 --- a/src/object/manifest.c +++ b/src/object/manifest.c @@@ -4,24 -1,32 +4,21 @@@ #include "object/manifest.h" #include "algorithm.h" - #include "alloc.h" #include "asn1/asn1c/Manifest.h" #include "asn1/decode.h" - #include "common.h" -#include "crypto/hash.h" +#include "config.h" +#include "hash.h" #include "log.h" ++#include "object/certificate.h" +#include "object/crl.h" #include "object/signed_object.h" #include "thread_var.h" - #include "types/path.h" - #include "types/uri.h" static int -cage(struct rpki_uri **uri, struct rpki_uri *notif) -{ - if (notif == NULL) { - /* No need to cage */ - uri_refget(*uri); - return 0; - } - - return uri_create_caged(uri, - tal_get_file_name(validation_tal(state_retrieve())), notif, - uri_get_global(*uri), uri_get_global_len(*uri)); -} - -static int -decode_manifest(struct signed_object *sobj, struct Manifest **result) +decode_manifest(struct signed_object *so, struct Manifest **result) { return asn1_decode_octet_string( - sobj->sdata->encapContentInfo.eContent, + so->sdata->encapContentInfo.eContent, &asn_DEF_Manifest, (void **) result, true diff --cc src/object/manifest.h index 58be295d,fa55a6f3..2bd224f9 --- a/src/object/manifest.h +++ b/src/object/manifest.h @@@ -1,13 -1,8 +1,10 @@@ #ifndef SRC_OBJECT_MANIFEST_H_ #define SRC_OBJECT_MANIFEST_H_ - #include - #include - -#include "rpp.h" ++#include "asn1/signed_data.h" +#include "cache.h" - #include "object/certificate.h" -int handle_manifest(struct rpki_uri *, struct rpki_uri *, struct rpp **); +int manifest_traverse(struct cache_mapping const *, struct cache_cage *, + struct rpki_certificate *); #endif /* SRC_OBJECT_MANIFEST_H_ */ diff --cc src/object/roa.c index 1fcfe0f1,0b4067ab..32a46937 --- a/src/object/roa.c +++ b/src/object/roa.c @@@ -3,15 -3,14 +3,16 @@@ #include "asn1/asn1c/RouteOriginAttestation.h" #include "asn1/decode.h" #include "log.h" ++#include "object/certificate.h" #include "object/signed_object.h" #include "thread_var.h" +#include "validation_handler.h" static int -decode_roa(struct signed_object *sobj, struct RouteOriginAttestation **result) +decode_roa(struct signed_object *so, struct RouteOriginAttestation **result) { return asn1_decode_octet_string( - sobj->sdata->encapContentInfo.eContent, + so->sdata->encapContentInfo.eContent, &asn_DEF_RouteOriginAttestation, (void **) result, true diff --cc src/object/roa.h index 9642f3cc,3efb97a3..125684af --- a/src/object/roa.h +++ b/src/object/roa.h @@@ -1,8 -1,8 +1,9 @@@ #ifndef SRC_OBJECT_ROA_H_ #define SRC_OBJECT_ROA_H_ - #include "object/certificate.h" -#include "rpp.h" ++#include "asn1/signed_data.h" ++#include "types/map.h" -int roa_traverse(struct rpki_uri *, struct rpp *); +int roa_traverse(struct cache_mapping *, struct rpki_certificate *); #endif /* SRC_OBJECT_ROA_H_ */ diff --cc src/object/signed_object.h index 6d8657ce,89155dd5..98fea480 --- a/src/object/signed_object.h +++ b/src/object/signed_object.h @@@ -2,24 -2,17 +2,24 @@@ #define SRC_OBJECT_SIGNED_OBJECT_H_ #include "asn1/asn1c/ContentInfo.h" - #include "asn1/asn1c/SignedData.h" #include "asn1/oid.h" + #include "asn1/signed_data.h" +#include "types/map.h" struct signed_object { + struct cache_mapping const *map; struct ContentInfo *cinfo; struct SignedData *sdata; + OCTET_STRING_t const *sid; + SignatureValue_t const *signature; }; -int signed_object_decode(struct signed_object *, struct rpki_uri *); -int signed_object_validate(struct signed_object *, struct oid_arcs const *, - struct ee_cert *); +int signed_object_decode(struct signed_object *, struct cache_mapping const *); + +struct rpki_certificate; +int signed_object_validate(struct signed_object *, struct rpki_certificate *, + struct oid_arcs const *); + void signed_object_cleanup(struct signed_object *); #endif /* SRC_OBJECT_SIGNED_OBJECT_H_ */ diff --cc src/object/tal.c index 5e6f0bcc,33515748..819e2db2 --- a/src/object/tal.c +++ b/src/object/tal.c @@@ -1,21 -1,19 +1,15 @@@ #include "object/tal.h" -#include -#include -#include -#include +#include - #include - #include -#include "common.h" +#include "base64.h" - #include "cache.h" - #include "common.h" #include "config.h" -#include "crypto/base64.h" -#include "line_file.h" +#include "file.h" #include "log.h" -#include "stats.h" +#include "object/certificate.h" +#include "task.h" #include "thread_var.h" - -typedef int (*foreach_uri_cb)(struct tal *, struct rpki_uri *, void *); +#include "types/path.h" - #include "types/str.h" - #include "types/uri.h" struct tal { char const *file_name; @@@ -144,185 -327,231 +138,191 @@@ tal_get_spki(struct tal *tal, unsigned *len = tal->spki_len; } -struct rpki_cache * -tal_get_cache(struct tal *tal) -{ - return tal->cache; -} - -/** - * Performs the whole validation walkthrough on uri @uri, which is assumed to - * have been extracted from TAL @tal. - */ static int -handle_tal_uri(struct tal *tal, struct rpki_uri *uri, struct db_table *db) +queue_tal(char const *tal_path, void *arg) { - struct validation_handler validation_handler; - struct validation *state; - struct cert_stack *certstack; - struct deferred_cert deferred; - int error; - - pr_val_debug("TAL URI '%s' {", uri_val_get_printable(uri)); - - validation_handler.handle_roa_v4 = handle_roa_v4; - validation_handler.handle_roa_v6 = handle_roa_v6; - validation_handler.handle_router_key = handle_router_key; - validation_handler.arg = db; - - error = validation_prepare(&state, tal, &validation_handler); - if (error) - return ENSURE_NEGATIVE(error); - - if (!uri_is_certificate(uri)) { - pr_op_err("TAL URI does not point to a certificate. (Expected .cer, got '%s')", - uri_op_get_printable(uri)); - error = EINVAL; - goto end; + if (task_enqueue_tal(tal_path) < 1) { + pr_op_err("Could not enqueue task '%s'; abandoning validation.", + tal_path); + return EINVAL; } - /* Handle root certificate. */ - error = certificate_traverse(NULL, uri); - if (error) { - switch (validation_pubkey_state(state)) { - case PKS_INVALID: - error = EINVAL; - goto end; - case PKS_VALID: - case PKS_UNTESTED: - error = ENSURE_NEGATIVE(error); - goto end; - } - pr_crit("Unknown public key state: %u", - validation_pubkey_state(state)); - } + return 0; +} - /* - * From now on, the tree should be considered valid, even if subsequent - * certificates fail. - * (the root validated successfully; subtrees are isolated problems.) - */ +static validation_verdict +validate_ta(struct tal *tal, struct cache_mapping const *ta_map) +{ + struct rpki_certificate *ta; + validation_verdict vv; - /* Handle every other certificate. */ - certstack = validation_certstack(state); - if (certstack == NULL) - pr_crit("Validation state has no certificate stack"); + ta = pzalloc(sizeof(struct rpki_certificate)); + map_copy(&ta->map, ta_map); + ta->tal = tal; + atomic_init(&ta->refcount, 1); - do { - error = deferstack_pop(certstack, &deferred); - if (error == -ENOENT) { - error = 0; /* No more certificates left; we're done */ - goto end; - } else if (error) /* All other errors are critical, currently */ - pr_crit("deferstack_pop() returned illegal %d.", error); - - /* - * Ignore result code; remaining certificates are unrelated, - * so they should not be affected. - */ - certificate_traverse(deferred.pp, deferred.uri); - - uri_refput(deferred.uri); - rpp_refput(deferred.pp); - } while (true); + vv = cer_traverse(ta); -end: validation_destroy(state); - pr_val_debug("}"); - return error; + cer_free(ta); + return vv; } -static int -__handle_tal_uri(struct rpki_uri *uri, void *arg) +static validation_verdict +try_urls(struct tal *tal, bool (*url_is_protocol)(struct uri const *), + validation_verdict (*get_path)(struct uri const *, char **)) { - struct handle_tal_args *args = arg; - return handle_tal_uri(&args->tal, uri, args->db); -} + struct uri *url; + char *path; + struct cache_mapping map; + validation_verdict vv; -static void * -do_file_validation(void *arg) -{ - struct validation_thread *thread = arg; - struct handle_tal_args args; - time_t start, finish; + ARRAYLIST_FOREACH(&tal->urls, url) { + if (!url_is_protocol(url)) + continue; - start = time(NULL); + vv = get_path(url, &path); + if (vv == VV_BUSY) + return VV_BUSY; + if (vv == VV_FAIL || !path) + continue; - fnstack_init(); - fnstack_push(thread->tal_file); + map.url = *url; + map.path = path; - thread->error = tal_init(&args.tal, thread->tal_file); - if (thread->error) - goto end; + vv = validate_ta(tal, &map); + if (vv == VV_BUSY) { + free(path); + return VV_BUSY; + } + if (vv == VV_FAIL) { + free(path); + continue; + } - args.db = db_table_create(); - thread->error = cache_download_alt(args.tal.cache, &args.tal.uris, - false, __handle_tal_uri, &args); - if (thread->error) { - pr_op_err("None of the URIs of the TAL '%s' yielded a successful traversal.", - thread->tal_file); - db_table_destroy(args.db); - } else { - thread->db = args.db; + cache_commit_file(&map); + free(path); + return VV_CONTINUE; } - tal_cleanup(&args.tal); -end: fnstack_cleanup(); - - finish = time(NULL); - if (start != ((time_t) -1) && finish != ((time_t) -1)) - pr_op_debug("The %s tree took %.0lf seconds.", - args.tal.file_name, difftime(finish, start)); - return NULL; + return VV_FAIL; } -static void -thread_destroy(struct validation_thread *thread) +static validation_verdict +traverse_tal(char const *tal_path) { - free(thread->tal_file); - db_table_destroy(thread->db); - free(thread); -} + struct tal tal; + validation_verdict vv; -/* Creates a thread for the @tal_file TAL */ -static int -spawn_tal_thread(char const *tal_file, void *arg) -{ - struct threads_list *threads = arg; - struct validation_thread *thread; - int error; + fnstack_push(tal_path); - thread = pmalloc(sizeof(struct validation_thread)); + if (tal_init(&tal, tal_path) != 0) { + vv = VV_FAIL; + goto end1; + } - thread->tal_file = pstrdup(tal_file); - thread->db = NULL; - thread->error = -EINTR; - SLIST_INSERT_HEAD(threads, thread, next); + /* Online attempts */ + vv = try_urls(&tal, uri_is_https, cache_refresh_by_url); + if (vv != VV_FAIL) + goto end2; + vv = try_urls(&tal, uri_is_rsync, cache_refresh_by_url); + if (vv != VV_FAIL) + goto end2; + /* Offline fallback attempts */ + vv = try_urls(&tal, uri_is_https, cache_get_fallback); + if (vv != VV_FAIL) + goto end2; + vv = try_urls(&tal, uri_is_rsync, cache_get_fallback); + if (vv != VV_FAIL) + goto end2; + + pr_op_err("None of the TAL URIs yielded a successful traversal."); + vv = VV_FAIL; + +end2: tal_cleanup(&tal); +end1: fnstack_pop(); + return vv; +} + +static void * +pick_up_work(void *arg) +{ + struct validation_task *task = NULL; + validation_verdict vv; + + while ((task = task_dequeue(task)) != NULL) { + switch (task->type) { + case VTT_RPP: + if (cer_traverse(task->u.ca) == VV_BUSY) { + task_requeue_dormant(task); + task = NULL; + } + break; + case VTT_TAL: + vv = traverse_tal(task->u.tal); + if (vv == VV_BUSY) { + task_requeue_dormant(task); + task = NULL; + } else if (vv == VV_FAIL) { + task_stop(); + } + break; + } - error = pthread_create(&thread->pid, NULL, do_file_validation, thread); - if (error) { - pr_op_err("Could not spawn validation thread for %s: %s", - tal_file, strerror(error)); - free(thread->tal_file); - free(thread); } - return error; + return NULL; } -struct db_table * +int perform_standalone_validation(void) { - struct threads_list threads = SLIST_HEAD_INITIALIZER(threads); - struct validation_thread *thread; - struct db_table *db = NULL; - int error = 0; - int tmperr; - - cache_setup(); - - /* TODO (fine) Maybe don't spawn threads if there's only one TAL */ - if (foreach_file(config_get_tal(), ".tal", true, spawn_tal_thread, - &threads) != 0) { - while (!SLIST_EMPTY(&threads)) { - thread = SLIST_FIRST(&threads); - SLIST_REMOVE_HEAD(&threads, next); - thread_destroy(thread); - } - return NULL; - } + pthread_t threads[5]; // XXX variabilize + array_index t; + int error; - /* Wait for all */ - while (!SLIST_EMPTY(&threads)) { - thread = SLIST_FIRST(&threads); - tmperr = pthread_join(thread->pid, NULL); - if (tmperr) - pr_crit("pthread_join() threw %d (%s) on the '%s' thread.", - tmperr, strerror(tmperr), thread->tal_file); - SLIST_REMOVE_HEAD(&threads, next); - if (thread->error) { - error = thread->error; - pr_op_warn("Validation from TAL '%s' yielded error %d (%s); discarding all validation results.", - thread->tal_file, error, strerror(abs(error))); - } + error = cache_prepare(); + if (error) + return error; + fnstack_init(); + task_start(); - if (!error) { - stats_set_tal_vrps(thread->tal_file, "ipv4", - db_table_roa_count_v4(thread->db)); - stats_set_tal_vrps(thread->tal_file, "ipv6", - db_table_roa_count_v6(thread->db)); - - if (db == NULL) { - db = thread->db; - thread->db = NULL; - } else { - error = db_table_join(db, thread->db); - } - } + error = foreach_file(config_get_tal(), ".tal", true, queue_tal, NULL); + if (error) + goto end; - thread_destroy(thread); - } + /* + * From now on, the trees should be considered valid, even if subsequent + * certificates fail. + * (The roots validated successfully; subtrees are isolated problems.) + */ - cache_teardown(); + for (t = 0; t < 5; t++) { + error = pthread_create(&threads[t], NULL, pick_up_work, NULL); + if (error) + pr_crit("pthread_create(%zu) failed: %s", + t, strerror(error)); + } - /* If one thread has errors, we can't keep the resulting table. */ - if (error) { - db_table_destroy(db); - db = NULL; + for (t = 0; t < 5; t++) { + error = pthread_join(threads[t], NULL); + if (error) + pr_crit("pthread_join(%zu) failed: %s", + t, strerror(error)); } - return db; ++// // FIXME ++// stats_set_tal_vrps(thread->tal_file, "ipv4", ++// db_table_roa_count_v4(thread->db)); ++// stats_set_tal_vrps(thread->tal_file, "ipv6", ++// db_table_roa_count_v6(thread->db)); ++ +end: if (task_stop()) + error = EINVAL; /* pick_up_work(), VTT_TAL */ + fnstack_cleanup(); + /* + * Commit even on failure, as there's no reason to throw away something + * we might have recently downloaded if it managed to be marked valid. + */ + cache_commit(); + return error; } diff --cc src/object/tal.h index 70f73303,2fa1ef8f..e436b8dd --- a/src/object/tal.h +++ b/src/object/tal.h @@@ -1,9 -1,10 +1,9 @@@ -#ifndef TAL_OBJECT_H_ -#define TAL_OBJECT_H_ +#ifndef SRC_OBJECT_TAL_H_ +#define SRC_OBJECT_TAL_H_ - #include "stddef.h" -/* This is RFC 8630. */ ++#include -#include "cache/local_cache.h" -#include "rtr/db/db_table.h" +/* This is RFC 8630. */ struct tal; diff --cc src/print_file.c index 146743eb,f677818a..0b8fecc1 --- a/src/print_file.c +++ b/src/print_file.c @@@ -16,11 -9,11 +16,10 @@@ #include "asn1/asn1c/json_encoder.h" #include "common.h" #include "config.h" -#include "data_structure/path_builder.h" #include "log.h" -#include "object/tal.h" -#include "rsync/rsync.h" +#include "rsync.h" #include "types/bio_seq.h" +#include "types/path.h" - #include "types/uri.h" #define HDRSIZE 32 diff --cc src/prometheus.c index 00000000,e3f6052f..ed645898 mode 000000,100644..100644 --- a/src/prometheus.c +++ b/src/prometheus.c @@@ -1,0 -1,165 +1,164 @@@ + #include "prometheus.h" + + #include + #include + #include -#include + + #include "config.h" + #include "log.h" + #include "stats.h" + + #if MHD_VERSION > 0x00097000 + #define MHD_RESULT enum MHD_Result + #else + #define MHD_RESULT int + #endif + + #define OPENMETRICS_CT \ + "application/openmetrics-text; version=1.0.0; charset=utf-8" + #define PLAINTEXT_CT \ + "text/plain; version=0.0.4; charset=utf-8" + + static struct MHD_Daemon *prometheus_daemon; + + static MHD_RESULT + respond(struct MHD_Connection *conn, char *msg, unsigned int status) + { + struct MHD_Response *response; + MHD_RESULT result; + + response = MHD_create_response_from_buffer(strlen(msg), msg, + MHD_RESPMEM_PERSISTENT); + result = MHD_queue_response(conn, status, response); + MHD_destroy_response(response); + + return result; + } + + static float + find_q(char const *accept, char const *ct) + { + char const *value; + char const *limit; + char const *qstr; + float q; + + value = strstr(accept, ct); + if (!value) + return 0; + + limit = strchr(value, ','); + if (!limit) + limit = value + strlen(value); + + qstr = strstr(value, ";q="); + if (!qstr || qstr > limit) + return 1; + return (sscanf(qstr, ";q=%f", &q) == EOF) ? 0.5 /* Shrug */ : q; + } + + static void + set_content_type(struct MHD_Connection *conn, struct MHD_Response *res) + { + char const *accept; + float om_q, txt_q; + char const *ct; + MHD_RESULT ret; + + accept = MHD_lookup_connection_value(conn, MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT); + if (accept != NULL) { + om_q = find_q(accept, "application/openmetrics-text"); + txt_q = find_q(accept, "text/plain"); + + if (om_q < 0.001f && txt_q < 0.001f) + /* Likely a browser; these tend to prefer plaintext. */ + ct = PLAINTEXT_CT; + else + ct = (om_q >= txt_q) ? OPENMETRICS_CT : PLAINTEXT_CT; + } else { + ct = OPENMETRICS_CT; + } + + ret = MHD_add_response_header(res, "Content-Type", ct); + if (ret != MHD_YES) { + pr_op_debug("Could not set Content-Type HTTP header."); + /* Keep going; maybe the client won't care. */ + } + } + + static MHD_RESULT + send_metrics(struct MHD_Connection *conn) + { + char *stats; + struct MHD_Response *res; + MHD_RESULT ret; + + pr_op_debug("Handling Prometheus request..."); + + stats = stats_export(); + + #if MHD_VERSION > 0x00096000 + res = MHD_create_response_from_buffer_with_free_callback(strlen(stats), + stats, free); + #else + res = MHD_create_response_from_buffer(strlen(stats), stats, + MHD_RESPMEM_MUST_FREE); + #endif + + set_content_type(conn, res); + + ret = MHD_queue_response(conn, MHD_HTTP_OK, res); + MHD_destroy_response(res); + + pr_op_debug("Prometheus request handled."); + return ret; + } + + static MHD_RESULT + handle_prometheus_req(void *cls, struct MHD_Connection *conn, + const char *url, const char *method, const char *version, + const char *upload, size_t *uplen, void **state) + { + if (strcmp(method, "GET") != 0) + return respond(conn, "Invalid HTTP Method\n", MHD_HTTP_BAD_REQUEST); + + if (strcmp(url, "/") == 0) + return respond(conn, "OK\n", MHD_HTTP_OK); + if (strcmp(url, "/metrics") == 0) + return send_metrics(conn); + + return respond(conn, "Bad Request\n", MHD_HTTP_BAD_REQUEST); + } + + int + prometheus_setup(void) + { + unsigned int port; + + port = config_get_prometheus_port(); + if (config_get_mode() != SERVER || port == 0) + return 0; + + pr_op_debug("Starting Prometheus server..."); + + prometheus_daemon = MHD_start_daemon( + MHD_USE_THREAD_PER_CONNECTION, /* flags */ + port, /* port */ + NULL, NULL, /* accept policy */ + &handle_prometheus_req, NULL, /* handler */ + MHD_OPTION_END /* options */ + ); + + if (prometheus_daemon == NULL) + return pr_op_err("Could not start Prometheus server; Unknown error"); + + pr_op_debug("Prometheus server started."); + return 0; + } + + void + prometheus_teardown(void) + { + MHD_stop_daemon(prometheus_daemon); + } diff --cc src/resource.c index e7607f63,793acfc7..3cf5ca4c --- a/src/resource.c +++ b/src/resource.c @@@ -1,13 -1,12 +1,12 @@@ #include "resource.h" #include ++#include +#include "alloc.h" #include "log.h" #include "resource/ip4.h" #include "resource/ip6.h" -#include "sorted_array.h" --#include "thread_var.h" - #include "types/sorted_array.h" /* The resources we extracted from one certificate. */ struct resources { diff --cc src/resource/asn.c index 4a70512e,d9bebdc0..0eb5517e --- a/src/resource/asn.c +++ b/src/resource/asn.c @@@ -1,7 -1,7 +1,5 @@@ #include "resource/asn.h" - #include "types/sorted_array.h" -#include "sorted_array.h" -- struct asn_cb { foreach_asn_cb cb; void *arg; diff --cc src/resource/asn.h index 325816f2,2d16c28e..093c8fee --- a/src/resource/asn.h +++ b/src/resource/asn.h @@@ -1,10 -1,9 +1,8 @@@ #ifndef SRC_RESOURCE_ASN_H_ #define SRC_RESOURCE_ASN_H_ --#include -- -#include "as_number.h" +#include "types/asn.h" +#include "types/sorted_array.h" /* * Implementation note: This is just a casted struct sorted_array. diff --cc src/resource/ip4.c index 502cb31f,4bab3fa3..e29f4c8d --- a/src/resource/ip4.c +++ b/src/resource/ip4.c @@@ -1,7 -1,7 +1,5 @@@ #include "resource/ip4.h" - #include "types/sorted_array.h" -#include "sorted_array.h" -- struct r4_node { uint32_t min; /* This is an IPv4 address in host byte order */ uint32_t max; /* This is an IPv4 address in host byte order */ diff --cc src/resource/ip6.c index 28478bb9,c2d8fc8c..9536ed7f --- a/src/resource/ip6.c +++ b/src/resource/ip6.c @@@ -1,7 -1,9 +1,7 @@@ #include "resource/ip6.h" - #include "types/sorted_array.h" + #include -#include "sorted_array.h" - static int addr_cmp(struct in6_addr const *a, struct in6_addr const *b) { diff --cc src/rrdp.c index 8795364f,4d5355b6..faec0319 --- a/src/rrdp.c +++ b/src/rrdp.c @@@ -1,26 -1,17 +1,23 @@@ #include "rrdp.h" - #include -#include + #include -#include #include +#include +#include "base64.h" - #include "cache.h" +#include "cachetmp.h" #include "common.h" -#include "crypto/base64.h" -#include "crypto/hash.h" +#include "config.h" #include "file.h" +#include "hash.h" +#include "http.h" +#include "json_util.h" #include "log.h" +#include "relax_ng.h" ++#include "rtr/db/delta.h" #include "thread_var.h" - #include "types/arraylist.h" - #include "types/path.h" -#include "xml/relax_ng.h" +#include "types/str.h" - #include "types/uri.h" +#include "types/uthash.h" /* RRDP's XML namespace */ #define RRDP_NAMESPACE "http://www.ripe.net/rpki/rrdp" diff --cc src/rrdp.h index dad75f3c,f3de0a36..0b96817b --- a/src/rrdp.h +++ b/src/rrdp.h @@@ -1,27 -1,8 +1,26 @@@ #ifndef SRC_RRDP_H_ #define SRC_RRDP_H_ +#include - #include - #include ++#include ++#include + - #include "file.h" #include "types/uri.h" -int rrdp_update(struct rpki_uri *); +struct rrdp_state; + +int rrdp_update(struct uri const *, char const *, time_t, bool *, + struct rrdp_state **); +char const *rrdp_file(struct rrdp_state const *, struct uri const *); + +char const *rrdp_create_fallback(char *, struct rrdp_state **, + struct uri const *); + +json_t *rrdp_state2json(struct rrdp_state *); +int rrdp_json2state(json_t *, char *, struct rrdp_state **); + +void rrdp_state_free(struct rrdp_state *); + +void rrdp_print(struct rrdp_state *); #endif /* SRC_RRDP_H_ */ diff --cc src/rsync.c index 859e4e57,00000000..ca32ee78 mode 100644,000000..100644 --- a/src/rsync.c +++ b/src/rsync.c @@@ -1,871 -1,0 +1,868 @@@ +#include "rsync.h" + +#include +#include +#include +#include - #include ++#include +#include +#include - #include + - #include "alloc.h" ++#include "asn1/asn1c/RsyncRequest.h" ++#include "asn1/asn1c/ber_decoder.h" ++#include "asn1/asn1c/der_encoder.h" +#include "common.h" +#include "config.h" +#include "log.h" - #include "types/array.h" ++#include "stream.h" +#include "types/map.h" + - #include "asn1/asn1c/ber_decoder.h" - #include "asn1/asn1c/der_encoder.h" - #include "asn1/asn1c/RsyncRequest.h" - +#define RSP /* rsync spawner prefix */ "[rsync spawner] " +#define SRTP "[spawner response thread] " + +static char const *rsync_args[20]; /* Last must be NULL */ + +static const int RDFD = 0; +static const int WRFD = 1; + +#define STDERR_WRITE(fds) fds[0][1] +#define STDOUT_WRITE(fds) fds[1][1] +#define STDERR_READ(fds) fds[0][0] +#define STDOUT_READ(fds) fds[1][0] + +static pid_t spawner; /* The subprocess that spawns rsync runs */ + +/* + * "Parent-spawner socket." + * Used by both parent and spawner to speak with each other. + */ +struct { + struct read_stream rd; /* Read fd and extra tools */ + int wr; /* Write fd */ + + struct RsyncRequest *rr; /* Scratchpad buffer for stream read */ + pthread_mutex_t wrlock; /* To sync writes */ +} pssk; + +struct rsync_task { + int pid; + struct uri url; + char *path; + int stdoutfd; /* Child rsync's standard output */ + int stderrfd; /* Child rsync's standard error */ + struct timespec expiration; + + LIST_ENTRY(rsync_task) lh; +}; + +LIST_HEAD(rsync_task_list, rsync_task); + +struct rsync_tasks { + struct rsync_task_list active; + int a; /* total active */ + + struct rsync_task_list queued; +}; + +#ifndef LIST_FOREACH_SAFE +#define LIST_FOREACH_SAFE(var, ls, lh, tmp) \ + for ( \ + var = LIST_FIRST(ls), tmp = (var ? LIST_NEXT(var, lh) : NULL); \ + var != NULL; \ + var = tmp, tmp = (var ? LIST_NEXT(var, lh) : NULL) \ + ) +#endif + +/* Spawner response thread; The thread that listens to spawner responses. */ +static pthread_t srt; + +static void +__spsk_init(int rdfd, int wrfd) +{ + rstream_init(&pssk.rd, rdfd, 512); + pssk.wr = wrfd; + pssk.rr = NULL; + pthread_mutex_init(&pssk.wrlock, NULL); +} + +static void +spsk_init(int rdpipes[2], int wrpipes[2]) +{ + close(wrpipes[RDFD]); + close(rdpipes[WRFD]); + __spsk_init(rdpipes[RDFD], wrpipes[WRFD]); +} + +static void +spsk_cleanup(void) +{ + rstream_close(&pssk.rd, true); + if (pssk.wr != -1) { + close(pssk.wr); + pssk.wr = -1; + } + ASN_STRUCT_FREE(asn_DEF_RsyncRequest, pssk.rr); + pthread_mutex_destroy(&pssk.wrlock); +} + +static int +write_cb(const void *buffer, size_t size, void *arg) +{ + return stream_full_write(pssk.wr, buffer, size); +} + +static void +notify_parent(struct rsync_task *task) +{ + struct RsyncRequest req; + asn_enc_rval_t result; + + if (pssk.wr == -1) { + pr_op_err(RSP "Cannot message parent process: " + "The socket is closed."); + return; + } + + /* + * TODO (asn1) these error messages are too generic. + * The asn1 code needs better error reporting. + */ + + if (RsyncRequest_init(&req, &task->url, task->path) < 0) { + pr_op_err(RSP "Cannot message parent process: " + "The request object cannot be created"); + return; + } + + result = der_encode(&asn_DEF_RsyncRequest, &req, write_cb, NULL); + if (result.encoded == -1) { + pr_op_err(RSP "Cannot message parent process: Unknown error"); + /* TODO (asn1) Do this if the error was I/O: + * close(spsk.wr); + * spsk.wr = -1; + * // Can't signal finished rsyncs anymore; reject future ones. + * rstream_close(&spsk.rd, true); + */ + } + + pr_op_debug(RSP "Parent notified; sent %zd bytes.", result.encoded); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RsyncRequest, &req); +} + +static void +void_task(struct rsync_task *task) +{ + notify_parent(task); + + uri_cleanup(&task->url); + free(task->path); + free(task); +} + +static void +finish_task(struct rsync_tasks *tasks, struct rsync_task *task) +{ + LIST_REMOVE(task, lh); + tasks->a--; + void_task(task); +} + +static void +init_pfd(struct pollfd *pfd, int fd) +{ + pfd->fd = fd; + pfd->events = POLLIN; + pfd->revents = 0; +} + +static struct pollfd * +create_pfds(int request_fd, struct rsync_task_list *tasks, size_t tn) +{ + struct pollfd *pfds; + struct rsync_task *task; + size_t p; + + pfds = pmalloc((2 * tn + 1) * sizeof(struct pollfd)); + p = 0; + + init_pfd(&pfds[p++], request_fd); + LIST_FOREACH(task, tasks, lh) { + init_pfd(&pfds[p++], task->stdoutfd); + init_pfd(&pfds[p++], task->stderrfd); + } + + return pfds; +} + +static int +create_pipes(int fds[2][2]) +{ + int error; + + if (pipe(fds[0]) < 0) { + error = errno; + pr_op_err_st(RSP "Piping rsync stderr: %s", strerror(error)); + return error; + } + + if (pipe(fds[1]) < 0) { + error = errno; + pr_op_err_st(RSP "Piping rsync stdout: %s", strerror(error)); + close(fds[0][0]); + close(fds[0][1]); + return error; + } + + return 0; +} + +static void +prepare_rsync_args(char **args, struct uri const *url, char const *path) +{ + size_t i; + + /* + * execvp() is not going to tweak these strings; + * stop angsting over the const-to-raw conversion. + */ + + for (i = 0; rsync_args[i] != NULL; i++) + args[i] = (char *)rsync_args[i]; + args[i++] = (char *)uri_str(url); + args[i++] = (char *)path; + args[i++] = NULL; +} + +/* + * Duplicate parent FDs, to pipe rsync output: + * - fds[0] = stderr + * - fds[1] = stdout + */ +static void +duplicate_fds(int fds[2][2]) +{ + /* Use the loop to catch interruptions */ + while ((dup2(STDERR_WRITE(fds), STDERR_FILENO) == -1) + && (errno == EINTR)) {} + close(STDERR_WRITE(fds)); + close(STDERR_READ(fds)); + + while ((dup2(STDOUT_WRITE(fds), STDOUT_FILENO) == -1) + && (errno == EINTR)) {} + close(STDOUT_WRITE(fds)); + close(STDOUT_READ(fds)); +} + +static int +execvp_rsync(struct uri const *url, char const *path, int fds[2][2]) +{ + char *args[20]; + + prepare_rsync_args(args, url, path); + duplicate_fds(fds); + + if (execvp(args[0], args) < 0) + return errno; + + return EINVAL; /* Unreachable, but whatever */ +} + +static int +fork_rsync(struct rsync_task *task) +{ + int fork_fds[2][2]; + int error; + + error = create_pipes(fork_fds); + if (error) + return error; + + fflush(stdout); + fflush(stderr); + + task->pid = fork(); + if (task->pid < 0) { + error = errno; + pr_op_err_st(RSP "Couldn't spawn the rsync process: %s", + strerror(error)); + close(STDERR_READ(fork_fds)); + close(STDOUT_READ(fork_fds)); + close(STDERR_WRITE(fork_fds)); + close(STDOUT_WRITE(fork_fds)); + return error; + } + + if (task->pid == 0) /* Child code */ + exit(execvp_rsync(&task->url, task->path, fork_fds)); + + /* Parent code */ + + close(STDERR_WRITE(fork_fds)); + close(STDOUT_WRITE(fork_fds)); + task->stderrfd = STDERR_READ(fork_fds); + task->stdoutfd = STDOUT_READ(fork_fds); + return 0; +} + +static void +activate_task(struct rsync_tasks *tasks, struct rsync_task *task, + struct timespec *now) +{ + ts_add(&task->expiration, now, 1000 * config_rsync_timeout()); + + if (fork_rsync(task) != 0) { + void_task(task); + return; + } + + LIST_INSERT_HEAD(&tasks->active, task, lh); + tasks->a++; +} + +/* Steals ownership of @map. */ +static void +post_task(struct cache_mapping *map, struct rsync_tasks *tasks, + struct timespec *now) +{ + struct rsync_task *task; + + task = pzalloc(sizeof(struct rsync_task)); + task->url = map->url; + task->path = map->path; + + if (tasks->a >= config_rsync_max()) { + LIST_INSERT_HEAD(&tasks->queued, task, lh); + pr_op_debug(RSP "Queued task %d: %s -> %s", + task->pid, uri_str(&task->url), task->path); + } else { + activate_task(tasks, task, now); + pr_op_debug(RSP "Got new task %d: %s -> %s", + task->pid, uri_str(&task->url), task->path); + } +} + +static int +next_task(struct cache_mapping *result) +{ + asn_dec_rval_t decres; + ssize_t consumed; + int error; + +again: if (pssk.rd.len > 0) { + decres = ber_decode(&asn_DEF_RsyncRequest, (void **)&pssk.rr, + pssk.rd.buffer, pssk.rd.len); + + memmove(pssk.rd.buffer, pssk.rd.buffer + decres.consumed, + pssk.rd.len - decres.consumed); + pssk.rd.len -= decres.consumed; + + switch (decres.code) { + case RC_OK: + __uri_init(&result->url, + OCTET_STRING_toString(&pssk.rr->url), + pssk.rr->url.size); + result->path = OCTET_STRING_toString(&pssk.rr->path); + ASN_STRUCT_RESET(asn_DEF_RsyncRequest, pssk.rr); + return 0; + case RC_WMORE: + break; + case RC_FAIL: + rstream_close(&pssk.rd, true); + return EINVAL; + } + } + + if (pssk.rd.fd == -1) + return EBADF; + consumed = read(pssk.rd.fd, pssk.rd.buffer + pssk.rd.len, + pssk.rd.capacity - pssk.rd.len); + if (consumed < 0) { + error = errno; + if (error != EAGAIN && error != EWOULDBLOCK) + rstream_close(&pssk.rd, true); + return error; + } + if (consumed == 0) { /* EOS */ + rstream_close(&pssk.rd, true); + return ENOENT; + } + + pssk.rd.len += consumed; + goto again; +} + +static void +handle_parent_fd(struct pollfd *pfd, struct rsync_tasks *tasks, + struct timespec *now) +{ + struct cache_mapping map; + + if (pssk.rd.fd == -1) + return; + + if (pfd->revents & POLLNVAL) { + pr_op_err(RSP "bad parent fd: %i", pfd->fd); + rstream_close(&pssk.rd, false); + + } else if (pfd->revents & POLLERR) { + pr_op_err(RSP "Generic error during parent fd poll."); + rstream_close(&pssk.rd, true); + + } else if (pfd->revents & (POLLIN | POLLHUP)) { + while (next_task(&map) == 0) + post_task(&map, tasks, now); + } +} + +static void +log_buffer(char const *buffer, ssize_t read, bool is_error) +{ + char *cpy, *cur, *tmp; + + cpy = pmalloc(read + 1); + + strncpy(cpy, buffer, read); + cpy[read] = '\0'; + + /* Break lines to one line at log */ + cur = cpy; + while ((tmp = strchr(cur, '\n')) != NULL) { + *tmp = '\0'; + if (strlen(cur) == 0) { + cur = tmp + 1; + continue; + } + if (is_error) + pr_op_err("[RSYNC exec] %s", cur); + else + pr_op_debug("[RSYNC exec] %s", cur); + cur = tmp + 1; + } + free(cpy); +} + +/* 0 = still more to read; 1 = stream down */ +static int +log_rsync_output(struct pollfd *pfd, size_t p) +{ + char buffer[1024]; + ssize_t count; + int error; + + count = read(pfd->fd, buffer, sizeof(buffer)); + if (count == 0) + goto down; /* EOF */ + if (count == -1) { + error = errno; + if (error == EINTR) + return 0; /* Dunno; retry */ + pr_op_err(RSP "rsync buffer read error: %s", strerror(error)); + goto down; /* Error */ + } + + log_buffer(buffer, count, (p & 1) == 0); + return 0; /* Keep going */ + +down: close(pfd->fd); + pfd->fd = -1; + return 1; +} + +/* Returns 1 if the stream ended */ +static int +handle_rsync_fd(struct pollfd *pfd, size_t p) +{ + if (pfd->fd == -1) { + pr_op_debug(RSP "File descriptor already closed."); + return 1; + } + + if (pfd->revents & POLLNVAL) { + pr_op_err(RSP "rsync bad fd: %i", pfd->fd); + return 1; + } + + if (pfd->revents & POLLERR) { + pr_op_err(RSP "Generic error during rsync poll."); + close(pfd->fd); + return 1; + } + + if (pfd->revents & (POLLIN | POLLHUP)) + return log_rsync_output(pfd, p); + + return 0; +} + +static int +wait_subprocess(char const *name, pid_t pid) +{ + int status; + int error; + +again: status = 0; + if (waitpid(pid, &status, 0) < 0) { + error = errno; + pr_op_err("Could not wait for %s: %s", name, strerror(error)); + return error; + } + + if (WIFEXITED(status)) { + /* Happy path (but also sad path sometimes) */ + error = WEXITSTATUS(status); + pr_op_debug("%s ended. Result: %d", name, error); + return error ? EIO : 0; + } + + if (WIFSIGNALED(status)) { + pr_op_warn("%s interrupted by signal %d (%s).", + name, WTERMSIG(status), strsignal(WTERMSIG(status))); + return EINTR; + } + + if (WIFCONTINUED(status)) { + /* + * Testing warning: + * I can't trigger this branch. It always exits or signals; + * SIGSTOP then SIGCONT doesn't seem to wake up waitpid(). + * It's concerning because every sample code I've found assumes + * waitpid() returning always means the subprocess ended, so + * they never retry. But that contradicts all documentation, + * yet seems to be accurate to reality. + */ + pr_op_debug("%s has resumed.", name); + goto again; + } + + /* Dead code */ + return pr_op_err("Unknown waitpid() status; giving up %s.", name); +} + +static void +kill_subprocess(struct rsync_task *task) +{ + if (task->stdoutfd != -1) + close(task->stdoutfd); + if (task->stderrfd != -1) + close(task->stderrfd); + kill(task->pid, SIGTERM); +} + +static void +activate_queued(struct rsync_tasks *tasks, struct timespec *now) +{ + struct rsync_task *task; + + task = LIST_FIRST(&tasks->queued); + if (task == NULL) + return; + + pr_op_debug(RSP "Activating queued task %s -> %s.", + uri_str(&task->url), task->path); + LIST_REMOVE(task, lh); + activate_task(tasks, task, now); +} + +/* Returns true if the task died. */ +static bool +maybe_expire(struct rsync_tasks *tasks, struct rsync_task *task, + struct timespec *now) +{ + struct timespec epoch; + + ts_add(&epoch, now, 100); + if (ts_cmp(&epoch, &task->expiration) < 0) + return false; + + pr_op_debug(RSP "Task %d ran out of time.", task->pid); + kill_subprocess(task); + wait_subprocess("rsync", task->pid); + finish_task(tasks, task); + activate_queued(tasks, now); + + return true; +} + +static int +spawner_run(void) +{ + struct pollfd *pfds; /* Channels to children */ + size_t p, pfds_count; + struct timespec now, expiration; + int timeout; + + int events; + + struct rsync_tasks tasks; + struct rsync_task *task, *tmp; + + int error; + + LIST_INIT(&tasks.active); + LIST_INIT(&tasks.queued); + tasks.a = 0; + error = 0; + + ts_now(&now); + + do { + /* + * 0: request pipe + * odd: stdouts + * even > 0: stderrs + */ + pfds = create_pfds(pssk.rd.fd, &tasks.active, tasks.a); + pfds_count = 2 * tasks.a + 1; + expiration.tv_sec = now.tv_sec + 10; + expiration.tv_nsec = now.tv_nsec; + LIST_FOREACH(task, &tasks.active, lh) + if ((ts_cmp(&now, &task->expiration) < 0) && + (ts_cmp(&task->expiration, &expiration) < 0)) + expiration = task->expiration; + + timeout = ts_delta(&now, &expiration); + pr_op_debug(RSP "Timeout decided: %dms", timeout); + events = poll(pfds, pfds_count, timeout); + if (events < 0) { + error = errno; + free(pfds); + break; + } + + ts_now(&now); + + if (events == 0) { /* Timeout */ + pr_op_debug(RSP "Woke up because of timeout."); + LIST_FOREACH_SAFE(task, &tasks.active, lh, tmp) + maybe_expire(&tasks, task, &now); + goto cont; + } + + pr_op_debug(RSP "Woke up because of input."); + p = 1; + LIST_FOREACH_SAFE(task, &tasks.active, lh, tmp) { + if (maybe_expire(&tasks, task, &now)) + continue; + + if (handle_rsync_fd(&pfds[p], p)) { + pr_op_debug(RSP "Task %d: Stdout closed.", + task->pid); + task->stdoutfd = -1; + } + p++; + if (handle_rsync_fd(&pfds[p], p)) { + pr_op_debug(RSP "Task %d: Stderr closed.", + task->pid); + task->stderrfd = -1; + } + p++; + if (task->stdoutfd == -1 && task->stderrfd == -1) { + pr_op_debug(RSP "Both stdout & stderr are closed; ending task %d.", + task->pid); + wait_subprocess("rsync", task->pid); + finish_task(&tasks, task); + activate_queued(&tasks, &now); + } + } + handle_parent_fd(&pfds[0], &tasks, &now); + +cont: free(pfds); + } while ((pssk.rd.fd != -1 || tasks.a > 0)); + pr_op_debug(RSP "The parent stream is closed and there are no rsync tasks running. Cleaning up..."); + + LIST_FOREACH_SAFE(task, &tasks.active, lh, tmp) { + kill_subprocess(task); + wait_subprocess("rsync", task->pid); + finish_task(&tasks, task); + } + LIST_FOREACH_SAFE(task, &tasks.queued, lh, tmp) { + LIST_REMOVE(task, lh); + void_task(task); + } + + spsk_cleanup(); + + free_rpki_config(); + log_teardown(); + return error; +} + +static int +nonblock_pipe(int *fds) +{ + int error; + int flags; + + if (pipe(fds) < 0) { + error = errno; + pr_op_warn("Cannot create pipe: %s", strerror(error)); + return error; + } + + flags = fcntl(fds[RDFD], F_GETFL); + if (flags < 0) { + error = errno; + pr_op_warn("Cannot retrieve pipe flags: %s", strerror(error)); + goto cancel; + } + if (fcntl(fds[RDFD], F_SETFL, flags | O_NONBLOCK) < 0) { + error = errno; + pr_op_warn("Cannot enable O_NONBLOCK: %s", strerror(error)); + goto cancel; + } + + return 0; + +cancel: close(fds[RDFD]); + close(fds[WRFD]); + return error; +} + +static void * +rcv_spawner_responses(void *arg) +{ + struct cache_mapping map = { 0 }; + + while (next_task(&map) == 0) { + rsync_finished(&map.url, map.path); + map_cleanup(&map); + } + + return NULL; +} + +void +rsync_setup(char const *program, ...) +{ + int parent2spawner[2]; /* Pipe: Parent writes, spawner reads */ + int spawner2parent[2]; /* Pipe: Spawner writes, parent reads */ + + va_list args; + array_index i; + char const *arg; + int error; + + if (!config_get_rsync_enabled()) + return; + + if (program != NULL) { + rsync_args[0] = arg = program; + va_start(args, program); + for (i = 1; arg != NULL; i++) { + arg = va_arg(args, char const *); + rsync_args[i] = arg; + } + va_end(args); + } else { + /* XXX review */ + /* XXX Where is --delete? */ + i = 0; + rsync_args[i++] = config_get_rsync_program(); + rsync_args[i++] = "-rtz"; + rsync_args[i++] = "--omit-dir-times"; + rsync_args[i++] = "--contimeout"; + rsync_args[i++] = "20"; + rsync_args[i++] = "--max-size"; + rsync_args[i++] = "20MB"; + rsync_args[i++] = "--timeout"; + rsync_args[i++] = "15"; + rsync_args[i++] = "--include=*/"; + rsync_args[i++] = "--include=*.cer"; + rsync_args[i++] = "--include=*.crl"; + rsync_args[i++] = "--include=*.gbr"; + rsync_args[i++] = "--include=*.mft"; + rsync_args[i++] = "--include=*.roa"; + rsync_args[i++] = "--exclude=*"; + rsync_args[i++] = NULL; + } + + if (nonblock_pipe(parent2spawner) != 0) + goto fail1; + if (pipe(spawner2parent) < 0) { + pr_op_warn("Cannot create pipe: %s", strerror(errno)); + goto fail2; + } + + fflush(stdout); + fflush(stderr); + + spawner = fork(); + if (spawner < 0) { + pr_op_warn("Cannot fork rsync spawner: %s", strerror(errno)); + goto fail3; + } + + if (spawner == 0) { /* Client code */ + spsk_init(parent2spawner, spawner2parent); + exit(spawner_run()); + } + + /* Parent code */ + /* (Threads can now be spawned.) */ + + spsk_init(spawner2parent, parent2spawner); + + error = pthread_create(&srt, NULL, rcv_spawner_responses, NULL); + if (error) { + pr_op_warn("Cannot start rsync spawner listener thread: %s", + strerror(error)); + spsk_cleanup(); + goto fail1; + } + + return; + +fail3: close(spawner2parent[RDFD]); + close(spawner2parent[WRFD]); +fail2: close(parent2spawner[RDFD]); + close(parent2spawner[WRFD]); +fail1: pr_op_warn("rsync will not be available."); + pssk.rd.fd = pssk.wr = -1; +} + +/* + * Queues rsync; doesn't wait. + * + * Whenever at least one rsync is finished, the function rsync_finished() + * will be automatically called. + */ +int +rsync_queue(struct uri const *url, char const *path) +{ + struct RsyncRequest req; + asn_enc_rval_t result; + int error; + + if (RsyncRequest_init(&req, url, path) < 0) + return EINVAL; + + mutex_lock(&pssk.wrlock); + + if (pssk.wr == -1) { + error = EIO; + goto end; + } + + result = der_encode(&asn_DEF_RsyncRequest, &req, write_cb, NULL); + if (result.encoded == -1) { + close(pssk.wr); + pssk.wr = -1; + error = EIO; + goto end; + } + + error = 0; +end: mutex_unlock(&pssk.wrlock); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RsyncRequest, &req); + return error; +} + +void +rsync_teardown(void) +{ + if (!config_get_rsync_enabled()) + return; + + spsk_cleanup(); + wait_subprocess("rsync spawner", spawner); +} diff --cc src/rtr/db/db_table.c index 2fab2dd2,2038381d..004ec292 --- a/src/rtr/db/db_table.c +++ b/src/rtr/db/db_table.c @@@ -1,12 -1,10 +1,11 @@@ #include "rtr/db/db_table.h" #include - #include #include "alloc.h" -#include "data_structure/uthash.h" +#include "common.h" #include "log.h" +#include "types/uthash.h" struct hashable_roa { struct vrp data; @@@ -21,21 -19,15 +20,23 @@@ struct hashable_key struct db_table { struct hashable_roa *roas; struct hashable_key *router_keys; + + unsigned int total_roas_v4; + unsigned int total_roas_v6; ++ + pthread_mutex_t lock; }; struct db_table * db_table_create(void) { - return pzalloc(sizeof(struct db_table)); + struct db_table *table; + - table = pmalloc(sizeof(struct db_table)); - table->roas = NULL; - table->router_keys = NULL; ++ table = pzalloc(sizeof(struct db_table)); + panic_on_fail(pthread_mutex_init(&table->lock, NULL), + "pthread_mutex_init"); + + return table; } void @@@ -79,10 -65,17 +80,17 @@@ add_roa(struct db_table *table, struct if (error) { pr_val_err("ROA couldn't be added to hash table: %s", strerror(error)); - return -error; + return error; } - if (old != NULL) + + if (old == NULL) { + switch (new->data.addr_fam) { + case AF_INET: table->total_roas_v4++; break; + case AF_INET6: table->total_roas_v6++; break; + } + } else { free(old); + } return 0; } diff --cc src/rtr/db/delta.c index 09eb72d7,bd625748..4dd4cd01 --- a/src/rtr/db/delta.c +++ b/src/rtr/db/delta.c @@@ -1,10 -1,11 +1,11 @@@ #include "rtr/db/delta.h" #include + #include -#include "data_structure/array_list.h" #include "log.h" #include "types/address.h" +#include "types/arraylist.h" struct delta_v4 { uint32_t as; diff --cc src/rtr/db/vrps.c index 29997b01,f23107d6..8679bbc2 --- a/src/rtr/db/vrps.c +++ b/src/rtr/db/vrps.c @@@ -1,9 -1,8 +1,9 @@@ #include "rtr/db/vrps.h" --#include ++#include #include +#include "alloc.h" #include "common.h" #include "config.h" #include "log.h" diff --cc src/rtr/db/vrps.h index a31861e2,f3a65211..f6569b0f --- a/src/rtr/db/vrps.h +++ b/src/rtr/db/vrps.h @@@ -7,8 -7,8 +7,6 @@@ * This module stores VRPs and their serials. */ -#include "as_number.h" --#include "types/address.h" - #include "types/asn.h" #include "types/delta.h" #include "types/serial.h" diff --cc src/rtr/pdu_sender.c index bdad6106,2e50c23d..a3d218d2 --- a/src/rtr/pdu_sender.c +++ b/src/rtr/pdu_sender.c @@@ -2,6 -2,7 +2,9 @@@ #include #include + #include ++#include ++#include #include "alloc.h" #include "config.h" diff --cc src/rtr/rtr.c index 7b32c2c9,9757b9cb..b0b02db5 --- a/src/rtr/rtr.c +++ b/src/rtr/rtr.c @@@ -10,8 -12,8 +11,10 @@@ #include "rtr/db/vrps.h" #include "rtr/pdu_handler.h" #include "rtr/pdu_sender.h" + #include "stats.h" -#include "thread/thread_pool.h" +#include "thread_pool.h" ++#include "types/address.h" +#include "types/arraylist.h" struct rtr_server { int fd; diff --cc src/sig.c index cd59b028,00000000..f5922684 mode 100644,000000..100644 --- a/src/sig.c +++ b/src/sig.c @@@ -1,115 -1,0 +1,111 @@@ +#include "sig.h" + +#include +#ifdef BACKTRACE_ENABLED +#include +#endif +#include - #include - #include - #include - #include + +#include "cache.h" +#include "log.h" +#include "output_printer.h" + +/* + * Ensures libgcc is loaded; otherwise backtrace() might allocate + * during a signal handler (which is illegal). + */ +static void +setup_backtrace(void) +{ +#ifdef BACKTRACE_ENABLED + void *dummy; + dummy = NULL; + backtrace(&dummy, 1); +#endif +} + +void +print_stack_trace(void) +{ +#ifdef BACKTRACE_ENABLED + /* + * See https://stackoverflow.com/questions/29982643 + * I went with rationalcoder's answer, because I think not printing + * stack traces on segfaults is a nice way of ending up committing + * suicide. + */ + void *array[64]; + size_t size; + size = backtrace(array, 64); + backtrace_symbols_fd(array, size, STDERR_FILENO); +#endif +} + +/* + * THIS IS A SIGNAL HANDLER. + * Legal functions: https://pubs.opengroup.org/onlinepubs/9799919799/ + */ +static void +do_cleanup(int signum) +{ + if (signum == SIGSEGV || signum == SIGBUS) + print_stack_trace(); + + cache_atexit(); + output_atexit(); + + /* Trigger default handler */ + /* XXX unsafe on multithreaded */ + signal(signum, SIG_DFL); + kill(getpid(), signum); +} + +/* Remember to enable -rdynamic (See print_stack_trace()). */ +void +register_signal_handlers(void) +{ + /* Important: All of these need to terminate by default */ + int const cleanups[] = { + SIGFPE, SIGSEGV, SIGBUS, SIGABRT, SIGSYS, /* 24.2.1 */ + SIGTERM, SIGINT, SIGQUIT, SIGHUP, /* 24.2.2 */ + SIGUSR1, SIGUSR2, /* 24.2.7 */ + 0 + }; + struct sigaction action; + unsigned int i; + + setup_backtrace(); + + memset(&action, 0, sizeof(action)); + action.sa_handler = do_cleanup; + sigfillset(&action.sa_mask); + action.sa_flags = 0; + + for (i = 0; cleanups[i]; i++) + if (sigaction(cleanups[i], &action, NULL) < 0) + pr_op_err("'%s' signal action registration failure: %s", + strsignal(cleanups[i]), strerror(errno)); + + /* + * SIGPIPE can be triggered by any I/O function. libcurl is particularly + * tricky: + * + * > libcurl makes an effort to never cause such SIGPIPEs to trigger, + * > but some operating systems have no way to avoid them and even on + * > those that have there are some corner cases when they may still + * > happen + * (Documentation of CURLOPT_NOSIGNAL) + * + * All SIGPIPE means is "the peer closed the connection for some + * reason." + * Which is a normal I/O error, and should be handled by the normal + * error propagation logic, not by a signal handler. + * So, ignore SIGPIPE. + * + * https://github.com/NICMx/FORT-validator/issues/49 + */ + action.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &action, NULL) < 0) + pr_op_err("SIGPIPE action registration failure: %s", + strerror(errno)); +} diff --cc src/slurm/db_slurm.c index 8cb3af10,910fedd6..82406023 --- a/src/slurm/db_slurm.c +++ b/src/slurm/db_slurm.c @@@ -1,12 -1,13 +1,13 @@@ #include "slurm/db_slurm.h" #include + #include #include +#include "base64.h" #include "common.h" -#include "crypto/base64.h" -#include "data_structure/array_list.h" #include "log.h" +#include "types/arraylist.h" struct slurm_prefix_wrap { struct slurm_prefix element; diff --cc src/slurm/db_slurm.h index 7c18aece,2eefde3b..42674026 --- a/src/slurm/db_slurm.h +++ b/src/slurm/db_slurm.h @@@ -1,15 -1,8 +1,9 @@@ -#ifndef SRC_SLURM_db_slurm_H_ -#define SRC_SLURM_db_slurm_H_ +#ifndef SRC_SLURM_DB_SLURM_H_ +#define SRC_SLURM_DB_SLURM_H_ #include +#include - #include - #include - #include - #include #include - #include - #include #include "types/router_key.h" #include "types/vrp.h" diff --cc src/slurm/slurm_loader.c index d93e7619,883a57dd..6062762d --- a/src/slurm/slurm_loader.c +++ b/src/slurm/slurm_loader.c @@@ -1,12 -1,10 +1,13 @@@ #include "slurm/slurm_loader.h" #include +#include ++#include +#include "alloc.h" #include "common.h" #include "config.h" -#include "crypto/hash.h" +#include "hash.h" #include "log.h" #include "slurm/slurm_parser.h" diff --cc src/slurm/slurm_parser.c index afc54d42,44fcd38e..71decdfa --- a/src/slurm/slurm_parser.c +++ b/src/slurm/slurm_parser.c @@@ -2,10 -2,13 +2,9 @@@ #include #include -#include -#include -#include #include "algorithm.h" --#include "alloc.h" -#include "crypto/base64.h" +#include "base64.h" #include "json_util.h" #include "log.h" #include "slurm/db_slurm.h" diff --cc src/stats.c index 00000000,96dca04e..5816ff03 mode 000000,100644..100644 --- a/src/stats.c +++ b/src/stats.c @@@ -1,0 -1,187 +1,187 @@@ + #include "stats.h" + + #include + #include + + #include "alloc.h" + #include "common.h" -#include "data_structure/uthash.h" + #include "log.h" ++#include "types/uthash.h" + + struct stats_gauge { + char *name; + unsigned int value; + time_t timestamp; + + UT_hash_handle hh; + }; + + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + static struct stats_gauge *gauges; + + struct stats_gauge *stat_rtr_ready; + struct stats_gauge *stat_rtr_connections; + + /* Steals ownership of @name */ + static struct stats_gauge * + add_gauge(char *name, size_t namelen, unsigned int value) + { + struct stats_gauge *old; + struct stats_gauge *new; + struct stats_gauge *delete; + struct stats_gauge *result; + + new = pzalloc(sizeof(struct stats_gauge)); + new->name = name; + new->value = value; + new->timestamp = time(NULL); + + if (namelen == 0) + namelen = strlen(name); + + mutex_lock(&lock); + HASH_FIND(hh, gauges, name, namelen, old); + if (old != NULL) { + old->value = value; + old->timestamp = new->timestamp; + delete = new; + result = old; + } else { + HASH_ADD_KEYPTR(hh, gauges, name, namelen, new); + delete = NULL; + result = new; + } + mutex_unlock(&lock); + + if (delete) { + free(delete->name); + free(delete); + } + + return result; + } + + #define ADD_GAUGE(name) add_gauge(pstrdup(name), 0, 0) + + int + stats_setup(void) + { + stat_rtr_ready = ADD_GAUGE("fort_rtr_ready"); + stat_rtr_connections = ADD_GAUGE("fort_rtr_current_connections"); + return 0; + } + + void + stats_teardown(void) + { + struct stats_gauge *gauge, *tmp; + + HASH_ITER(hh, gauges, gauge, tmp) { + HASH_DEL(gauges, gauge); + free(gauge->name); + free(gauge); + } + } + + void + stats_gauge_set(struct stats_gauge *gauge, unsigned int value) + { + time_t now = time(NULL); + + mutex_lock(&lock); + gauge->value = value; + gauge->timestamp = now; + mutex_unlock(&lock); + } + + void + stats_set_tal_vrps(char const *tal_path, char const *proto, unsigned int value) + { + char const *ta, *dot; + size_t talen; + + size_t baselen; + size_t keylen; + char *key; + int chars; + + ta = strrchr(tal_path, '/'); + ta = (ta == NULL) ? tal_path : (ta + 1); + dot = strrchr(ta, '.'); + talen = dot ? (dot - ta) : strlen(ta); + + baselen = strlen("fort_valid_vrps_total{ta=\"\",proto=\"\"}"); + keylen = baselen + talen + strlen(proto) + 1; + + key = pmalloc(keylen); + chars = snprintf(key, keylen, + "fort_valid_vrps_total{ta=\"%.*s\",proto=\"%s\"}", + (int)talen, ta, proto); + if (chars < 0 || keylen <= chars) { + free(key); + pr_op_warn("Cannot create valid_vrps_total stat: %d", chars); + return; + } + + add_gauge(key, keylen - 1, value); + } + + struct stats_buffer { + char *str; + char *cursor; + size_t capacity; + }; + + static bool + printf_buf(struct stats_buffer *buf, char const *fmt, ...) + { + size_t available; + int written; + va_list ap; + + available = buf->capacity - (buf->cursor - buf->str); + + va_start(ap, fmt); + written = vsnprintf(buf->cursor, available, fmt, ap); + va_end(ap); + + if (written < 0 || available <= written) + return false; + + buf->cursor += written; + return true; + } + + char * + stats_export(void) + { + struct stats_buffer buf; + struct stats_gauge *gauge, *tmp; + + buf.capacity = 1024; + buf.str = buf.cursor = pmalloc(buf.capacity); + + HASH_ITER(hh, gauges, gauge, tmp) { + if (!printf_buf(&buf, "%s %u", gauge->name, gauge->value)) + goto cancel; + if (gauge->timestamp != ((time_t)-1)) + if (!printf_buf(&buf, " %jd", (intmax_t)gauge->timestamp)) + goto cancel; + if (!printf_buf(&buf, "\n")) + goto cancel; + } + + if (!printf_buf(&buf, "# EOF\n")) + goto cancel; + + if (buf.cursor >= buf.str + buf.capacity) + goto cancel; + *buf.cursor = '\0'; + + return buf.str; + + cancel: + free(buf.str); + pr_op_err("Cannot create Prometheus response: Too many stats"); + return NULL; + } diff --cc src/stream.c index 6e0ba431,00000000..a7d5d075 mode 100644,000000..100644 --- a/src/stream.c +++ b/src/stream.c @@@ -1,78 -1,0 +1,79 @@@ +#include "stream.h" + +#include - #include - #include ++#include ++#include ++#include + +#include "alloc.h" +#include "log.h" + +void +rstream_init(struct read_stream *stream, int fd, size_t initial_capacity) +{ + stream->fd = fd; + stream->buffer = pmalloc(initial_capacity); + stream->len = 0; + stream->capacity = initial_capacity; +} + +void +rstream_close(struct read_stream *stream, bool do_close) +{ + if (stream->fd == -1) + return; + + if (do_close) + close(stream->fd); + free(stream->buffer); + stream->fd = -1; +} + +/* + * Reads until exactly @len bytes (sleeping if necessary), EOS or error. + * @stream's capacity must be >= len. + * + * Returns: + * - >= 0: Number of bytes read (< @len only if EOS reached). + * - < 0: error; remove sign for proper code. + */ +int +rstream_full_read(struct read_stream *stream, size_t len) +{ + size_t offset; + ssize_t rd; + + if (stream->buffer == NULL || stream->capacity < len) + return ENOSPC; + + for (offset = 0; offset < len; offset += rd) { + rd = read(stream->fd, stream->buffer + offset, len - offset); + if (rd < 0) + return errno; + if (rd == 0) + break; + } + + return offset; +} + +/* Full write (sleeps if necessary) or error. */ +int +stream_full_write(int fd, unsigned char const *buf, size_t len) +{ + ssize_t wr; + size_t offset; + + offset = 0; + do { + wr = write(fd, buf + offset, len); + if (wr < 0) + return errno; + if (wr > len) + pr_crit("wr > len: %zd > %zu", wr, len); + len -= wr; + offset += wr; + } while (len > 0); + + return 0; +} diff --cc src/task.c index 1410a451,00000000..ad0fb8f3 mode 100644,000000..100644 --- a/src/task.c +++ b/src/task.c @@@ -1,275 -1,0 +1,274 @@@ +#include "task.h" + +#include + - #include "alloc.h" - #include "common.h" +#include "log.h" ++#include "object/certificate.h" + +STAILQ_HEAD(validation_tasks, validation_task); + +/* Queued, not yet claimed tasks */ +static struct validation_tasks waiting; +/* Queued, but not yet available for claiming */ +static struct validation_tasks dormant; +/* + * Total currently existing tasks + * (length(@waiting) + length(@dormant) + total active tasks) + */ +static int ntasks; + +static bool enabled = true; + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t awakener = PTHREAD_COND_INITIALIZER; + +static char const * +task_name(struct validation_task *task) +{ + switch (task->type) { + case VTT_RPP: + return uri_str(&task->u.ca->map.url); + case VTT_TAL: + return task->u.tal; + } + + pr_crit("Unknown task type: %u", task->type); + return NULL; +} + +static void +task_free(struct validation_task *task) +{ + switch (task->type) { + case VTT_RPP: + cer_free(task->u.ca); + break; + case VTT_TAL: + free(task->u.tal); + break; + } + + free(task); +} + +void +task_setup(void) +{ + STAILQ_INIT(&waiting); + STAILQ_INIT(&dormant); + ntasks = 0; + enabled = true; + panic_on_fail(pthread_mutex_init(&lock, NULL), "pthread_mutex_init"); + panic_on_fail(pthread_cond_init(&awakener, NULL), "pthread_cond_init"); +} + +static void +cleanup_tasks(struct validation_tasks *tasks) +{ + struct validation_task *task; + + while (!STAILQ_EMPTY(tasks)) { + task = STAILQ_FIRST(tasks); + STAILQ_REMOVE_HEAD(tasks, lh); + task_free(task); + } +} + +static void +cleanup(void) +{ + enabled = false; + ntasks = 0; + cleanup_tasks(&waiting); + cleanup_tasks(&dormant); +} + +void +task_start(void) +{ + cleanup(); + enabled = true; +} + +/* Returns true if the module had already been stopped. */ +bool +task_stop(void) +{ + bool result; + + mutex_lock(&lock); + result = !enabled; + cleanup(); + mutex_unlock(&lock); + + return result; +} + +void +task_teardown(void) +{ + pthread_mutex_destroy(&lock); + pthread_cond_destroy(&awakener); +} + +static int +enqueue_task(struct validation_task *task) +{ + mutex_lock(&lock); + if (enabled) { + STAILQ_INSERT_TAIL(&waiting, task, lh); + task = NULL; + ntasks++; + } + mutex_unlock(&lock); + + if (task) { + task_free(task); /* Couldn't queue */ + return 0; + } + + return 1; +} + +unsigned int +task_enqueue_tal(char const *tal_path) +{ + struct validation_task *task; + + task = pmalloc(sizeof(struct validation_task)); + task->type = VTT_TAL; + task->u.tal = pstrdup(tal_path); + + return enqueue_task(task); +} + +/* + * Defers a task for later. + * Call task_wakeup() once you've queued all your tasks. + * Returns number of deferred tasks. + */ +unsigned int +task_enqueue_rpp(struct cache_mapping *map, struct rpki_certificate *parent) +{ + struct validation_task *task; + struct rpki_certificate *ca; + + atomic_fetch_add(&parent->refcount, 1); + + ca = pzalloc(sizeof(struct rpki_certificate)); + uri_copy(&ca->map.url, &map->url); + ca->map.path = pstrdup(map->path); + ca->parent = parent; + atomic_init(&ca->refcount, 1); + + task = pmalloc(sizeof(struct validation_task)); + task->type = VTT_RPP; + task->u.ca = ca; + + return enqueue_task(task); +} + +/* Steals ownership of @task. */ +void +task_requeue_dormant(struct validation_task *task) +{ + mutex_lock(&lock); + if (enabled) { + STAILQ_INSERT_TAIL(&dormant, task, lh); + task = NULL; + } + mutex_unlock(&lock); + + if (task) + task_free(task); /* Couldn't queue */ +} + +/* Wakes up all sleeping task threads. */ +void +task_wakeup(void) +{ + mutex_lock(&lock); + panic_on_fail(pthread_cond_broadcast(&awakener), + "pthread_cond_broadcast"); + mutex_unlock(&lock); +} + +/* Upgrades all dormant tasks, and wakes up all sleeping task threads. */ +void +task_wakeup_dormants(void) +{ + mutex_lock(&lock); + STAILQ_CONCAT(&waiting, &dormant); + panic_on_fail(pthread_cond_broadcast(&awakener), + "pthread_cond_broadcast"); + mutex_unlock(&lock); +} + +/* + * Frees the @prev previous task, and returns the next one. + * + * If no task is available yet, will sleep until someone calls task_wakeup() or + * task_wakeup_dormants(). + * If all the tasks are done, returns NULL. + * + * Steals ownership of @prev. + * Assumes at least one task has been queued before the first dequeue. + */ +struct validation_task * +task_dequeue(struct validation_task *prev) +{ + struct validation_task *task; + struct timespec timeout; + int error; + + if (prev) + task_free(prev); + timeout.tv_nsec = 0; + + mutex_lock(&lock); + + if (!enabled) + goto end; + + if (prev) { + ntasks--; + if (ntasks < 0) + pr_crit("active < 0: %d", ntasks); + } + + while (ntasks > 0) { + pr_op_debug("task_dequeue(): %u existing tasks.", ntasks); + + task = STAILQ_FIRST(&waiting); + if (task != NULL) { + STAILQ_REMOVE_HEAD(&waiting, lh); + mutex_unlock(&lock); + pr_op_debug("task_dequeue(): Claimed task '%s'.", + task_name(task)); + return task; + } + + pr_op_debug("task_dequeue(): Sleeping..."); + timeout.tv_sec = time_fatal() + 10; + error = pthread_cond_timedwait(&awakener, &lock, &timeout); + switch (error) { + case 0: + pr_op_debug("task_dequeue(): Woke up by cond."); + break; + case ETIMEDOUT: + pr_op_debug("task_dequeue(): Woke up by timeout."); + break; + case EINTR: + pr_op_debug("task_dequeue(): Interrupted by signal."); + goto end; + default: + panic_on_fail(error, "pthread_cond_wait"); + } + } + + pr_op_debug("task_dequeue(): No more tasks; done."); + panic_on_fail(pthread_cond_broadcast(&awakener), + "pthread_cond_broadcast"); +end: mutex_unlock(&lock); + return NULL; +} diff --cc src/task.h index f47d1498,00000000..0d2ce245 mode 100644,000000..100644 --- a/src/task.h +++ b/src/task.h @@@ -1,35 -1,0 +1,36 @@@ +#ifndef SRC_TASK_H_ +#define SRC_TASK_H_ + +#include + ++#include "asn1/signed_data.h" ++#include "object/tal.h" +#include "types/map.h" - #include "object/certificate.h" + +enum validation_task_type { + VTT_RPP, + VTT_TAL, +}; + +struct validation_task { + enum validation_task_type type; + union { + char *tal; + struct rpki_certificate *ca; + } u; + STAILQ_ENTRY(validation_task) lh; +}; + +void task_setup(void); +void task_start(void); +bool task_stop(void); +void task_teardown(void); + +unsigned int task_enqueue_tal(char const *); +unsigned int task_enqueue_rpp(struct cache_mapping *, struct rpki_certificate *); +void task_requeue_dormant(struct validation_task *); +void task_wakeup(void); +void task_wakeup_dormants(void); +struct validation_task *task_dequeue(struct validation_task *); + +#endif /* SRC_TASK_H_ */ diff --cc src/thread_pool.c index a5e38bb6,e84a53e7..17eff195 --- a/src/thread_pool.c +++ b/src/thread_pool.c @@@ -1,5 -1,7 +1,7 @@@ -#include "thread/thread_pool.h" +#include "thread_pool.h" + #include + #include #include #include "alloc.h" diff --cc src/thread_var.c index 8524d7ae,1d891011..ed0b7a9b --- a/src/thread_var.c +++ b/src/thread_var.c @@@ -2,9 -2,9 +2,8 @@@ #include - #include "alloc.h" #include "log.h" -static pthread_key_t state_key; static pthread_key_t filenames_key; struct filename_stack { diff --cc src/types/address.c index dc5ffc98,911015fb..b07e0a56 --- a/src/types/address.c +++ b/src/types/address.c @@@ -1,9 -1,9 +1,10 @@@ #include "types/address.h" #include ++#include ++#include #include "log.h" --#include "thread_var.h" static void init_quadrant(struct in6_addr *addr, unsigned int slot, uint32_t value) diff --cc src/types/array.h index 2e8f68b7,00000000..29af9b1d mode 100644,000000..100644 --- a/src/types/array.h +++ b/src/types/array.h @@@ -1,15 -1,0 +1,9 @@@ +#ifndef SRC_TYPES_ARRAY_H_ +#define SRC_TYPES_ARRAY_H_ + +#include - #include - #include - #include - #include - #include - #include + +typedef size_t array_index; +#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0])) + +#endif /* SRC_TYPES_ARRAY_H_ */ diff --cc src/types/arraylist.h index 686f58ef,1bec8769..46103466 --- a/src/types/arraylist.h +++ b/src/types/arraylist.h @@@ -1,8 -1,11 +1,11 @@@ -#ifndef SRC_DATA_STRUCTURE_ARRAY_LIST_H_ -#define SRC_DATA_STRUCTURE_ARRAY_LIST_H_ +#ifndef SRC_TYPES_ARRAYLIST_H_ +#define SRC_TYPES_ARRAYLIST_H_ + #include + #include + #include "alloc.h" -#include "data_structure/common.h" +#include "types/array.h" #define DEFINE_ARRAY_LIST_STRUCT(name, elem_type) \ struct name { \ @@@ -14,11 -17,11 +17,6 @@@ size_t capacity; \ } --#define DECLARE_ARRAY_LIST_FUNCTIONS(name, elem_type) \ -- void name##_init(struct name *); \ -- void name##_cleanup(struct name *, void (*cb)(elem_type *)); \ -- void name##_add(struct name *list, elem_type *elem); -- #define DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type, modifiers) \ modifiers void \ name##_init(struct name *list) \ diff --cc src/types/map.c index 1d3b642a,00000000..4ad35992 mode 100644,000000..100644 --- a/src/types/map.c +++ b/src/types/map.c @@@ -1,48 -1,0 +1,47 @@@ +#include "types/map.h" + - #include "alloc.h" +#include "config.h" +#include "log.h" +#include "types/path.h" + +static char const * +map_get_printable(struct cache_mapping const *map, enum filename_format format) +{ + switch (format) { + case FNF_GLOBAL: + return uri_str(&map->url); + case FNF_LOCAL: + return map->path; + case FNF_NAME: + return path_filename(uri_str(&map->url)); + } + + pr_crit("Unknown file name format: %u", format); + return NULL; +} + +char const * +map_val_get_printable(struct cache_mapping const *map) +{ + return map_get_printable(map, config_get_val_log_file_format()); +} + +char const * +map_op_get_printable(struct cache_mapping const *map) +{ + return map_get_printable(map, config_get_op_log_file_format()); +} + +void +map_copy(struct cache_mapping *dst, struct cache_mapping const *src) +{ + uri_copy(&dst->url, &src->url); + dst->path = pstrdup(src->path); +} + +void +map_cleanup(struct cache_mapping *map) +{ + uri_cleanup(&map->url); + free(map->path); +} diff --cc src/types/name.c index 76214d99,e8c3d728..11419440 --- a/src/types/name.c +++ b/src/types/name.c @@@ -3,10 -3,10 +3,11 @@@ #include #include #include --#include ++#include ++#include +#include "alloc.h" #include "log.h" -#include "thread_var.h" /** * It's an RFC5280 name, but from RFC 6487's perspective. diff --cc src/types/path.c index 8456b52d,00000000..bbbdd45f mode 100644,000000..100644 --- a/src/types/path.c +++ b/src/types/path.c @@@ -1,71 -1,0 +1,72 @@@ +#include "types/path.h" + ++#include ++ +#include "alloc.h" - #include "log.h" +#include "types/str.h" + +char const * +path_filename(char const *path) +{ + char *slash = strrchr(path, '/'); + return slash ? (slash + 1) : path; +} + +static void +trim_leading_slashes(struct sized_string *str) +{ + while (str->str[0] == '/') { + str->str++; + str->len--; + } +} + +static void +trim_trailing_slashes(struct sized_string *str) +{ + while (str->len > 1 && str->str[str->len - 1] == '/') + str->len--; +} + +/* Result needs cleanup, cannot return NULL. */ +char * +path_join(char const *path1, char const *path2) +{ + struct sized_string p1; + struct sized_string p2; + size_t n; + char *result; + + if (path1) { + p1.str = path1; + p1.len = strlen(path1); + trim_trailing_slashes(&p1); + } else { + memset(&p1, 0, sizeof(p1)); + } + + if (path2) { + p2.str = path2; + p2.len = strlen(path2); + trim_leading_slashes(&p2); + } else { + memset(&p2, 0, sizeof(p2)); + } + + if (p1.len == 0 && p2.len == 0) + return pstrdup(""); + if (p1.len == 0 || p1.str[0] == '\0') + return pstrndup(p2.str, p2.len); + if (p2.len == 0 || p2.str[0] == '\0') + return pstrndup(p1.str, p1.len); + + n = p1.len + p2.len + 2; + result = pmalloc(n); + + memcpy(result, p1.str, p1.len); + result[p1.len] = '/'; + memcpy(result + p1.len + 1, p2.str, p2.len); + result[n - 1] = '\0'; + + return result; +} diff --cc src/types/rpp.c index 8f01b378,00000000..b1a6fba5 mode 100644,000000..100644 --- a/src/types/rpp.c +++ b/src/types/rpp.c @@@ -1,23 -1,0 +1,21 @@@ +#include "types/rpp.h" + - #include "types/array.h" - +void +rpp_cleanup(struct rpp *rpp) +{ + array_index i; + + for (i = 0; i < rpp->nfiles; i++) + map_cleanup(&rpp->files[i]); + free(rpp->files); + rpp->files = NULL; + rpp->nfiles = 0; + + rpp->crl.map = NULL; + if (rpp->crl.obj != NULL) { + X509_CRL_free(rpp->crl.obj); + rpp->crl.obj = NULL; + } + + mftm_cleanup(&rpp->mft); +} diff --cc src/types/rpp.h index 1f40e5b3,00000000..ea8d16ba mode 100644,000000..100644 --- a/src/types/rpp.h +++ b/src/types/rpp.h @@@ -1,31 -1,0 +1,31 @@@ +#ifndef SRC_RPP_H_ +#define SRC_RPP_H_ + +#include - #include ++#include + +#include "asn1/asn1c/INTEGER.h" +#include "types/map.h" + +struct mft_meta { + INTEGER_t num; /* Manifest's manifestNumber */ + time_t update; /* Manifest's thisUpdate */ +}; + +/* Repository Publication Point */ +struct rpp { + struct cache_mapping *files; + size_t nfiles; /* Number of maps in @files */ + + struct { + struct cache_mapping *map; /* Points to @files entry */ + X509_CRL *obj; + } crl; + + struct mft_meta mft; +}; + +#define mftm_cleanup(m) INTEGER_cleanup(&(m)->num); +void rpp_cleanup(struct rpp *); + +#endif /* SRC_RPP_H_ */ diff --cc src/types/sorted_array.c index c63ecebb,e6d10bf1..2871558b --- a/src/types/sorted_array.c +++ b/src/types/sorted_array.c @@@ -1,5 -1,9 +1,9 @@@ -#include "sorted_array.h" +#include "types/sorted_array.h" + #include + #include + #include + #include "alloc.h" #include "log.h" diff --cc src/types/str.c index 84b83bb0,05e4fcb4..91f68116 --- a/src/types/str.c +++ b/src/types/str.c @@@ -1,42 -1,11 +1,43 @@@ -#include "str_token.h" +#include "types/str.h" +#include - #include #include + #include ++#include + #include #include "alloc.h" - #include "array.h" #include "log.h" - #include "types/path.h" ++#include "types/array.h" + +/* Allocates the result; will need free(). Never returns NULL. */ +char * +str_concat(char const *s1, char const *s2) +{ + size_t n; + char *result; + int written; + + n = strlen(s1) + strlen(s2) + 1; + result = pmalloc(n); + + written = snprintf(result, n, "%s%s", s1, s2); + if (written != n - 1) + pr_crit("str_concat: %zu %d %s %s", n, written, s1, s2); + + return result; +} + +int +hex2ulong(char const *hex, unsigned long *ulong) +{ + char *endptr; + + errno = 0; + *ulong = strtoul(hex, &endptr, 16); + + return (errno || endptr[0] != 0) ? -1 : 0; +} /** * Does not assume that @string is NULL-terminated. diff --cc src/types/str.h index 33a9a9e9,016f6e79..23eb35c0 --- a/src/types/str.h +++ b/src/types/str.h @@@ -4,16 -4,8 +4,17 @@@ #include #include #include + #include +struct sized_string { + char const *str; + size_t len; +}; + +char *str_concat(char const *, char const *); + +int hex2ulong(char const *, unsigned long *); + int ia5s2string(ASN1_IA5STRING *, char **); int BN2string(BIGNUM *, char **); diff --cc src/types/uri.c index 7dfe0c1c,c6e2039d..50d02bdb --- a/src/types/uri.c +++ b/src/types/uri.c @@@ -1,120 -1,231 +1,120 @@@ #include "types/uri.h" +#include +#include ++#include + - #include "alloc.h" #include "common.h" -#include "config.h" -#include "data_structure/path_builder.h" #include "log.h" - #include "types/path.h" -#include "object/tal.h" -/** - * Design notes: - * - * Because we need to generate @local from @global, @global's allowed character - * set must be a subset of @local. Because this is Unix, @local must never - * contain NULL (except as a terminating character). Therefore, even though IA5 - * allows NULL, @global won't. - * - * Because we will simply embed @global (minus "rsync://") into @local, @local's - * encoding must be IA5-compatible. In other words, UTF-16 and UTF-32 are out of - * the question. - * - * Aside from the reference counter, instances are meant to be immutable. - */ -struct rpki_uri { - /** - * "Global URI". - * The one that always starts with "rsync://" or "https://". - * - * These things are IA5-encoded, which means you're not bound to get - * non-ASCII characters. - */ - char *global; - /** Length of @global. */ - size_t global_len; - - /** - * "Local URI". - * The file pointed by @global, but cached in the local filesystem. - * - * I can't find a standard that defines this, but lots of complaints on - * the Internet imply that Unix file paths are specifically meant to be - * C strings. - * - * So just to clarify: This is a string that permits all characters, - * printable or otherwise, except \0. (Because that's the terminating - * character.) - * - * Even though it might contain characters that are non-printable - * according to ASCII, we assume that we can just dump it into the - * output without trouble, because the input should have the same - * encoding as the output. - * - * Technically, "global" URI "https://a.b.c/d/..///./d" is not the same - * identifier as "https://a.b.c/d", but since we're supposed to download - * to a filesystem where "https/a.b.c/d" is the same file as - * "https/a.b.c/d/..///./d", @local will always be normalized. - */ - char *local; - /* "local_len" is never needed right now. */ - - enum uri_type type; - bool is_notif; /* Does it point to an RRDP Notification? */ - - unsigned int references; +#define URI_ALLOW_UNKNOWN_SCHEME (1 << 0) + +static error_msg EM_SCHEME_EMPTY = "Scheme seems empty"; +static error_msg EM_SCHEME_1ST = "First scheme character is not a letter"; +static error_msg EM_SCHEME_NTH = "Scheme character is not letter, digit, plus, period or hyphen"; +static error_msg EM_SCHEME_NOCOLON = "Scheme not terminated"; +static error_msg EM_SCHEME_UNKNOWN = "Unknown scheme"; +static error_msg EM_SCHEME_NOTREMOTE = "Missing \"://\""; +static error_msg EM_PCT_NOTHEX = "Invalid hexadecimal digit in percent encoding"; +static error_msg EM_PCT_NOT3 = "Unterminated percent-encoding"; +static error_msg EM_UTF8 = "Invalid UTF-8"; +static error_msg EM_USERINFO_BADCHR = "Illegal character in userinfo component"; +static error_msg EM_USERINFO_DISALLOWED = "Protocol disallows userinfo"; +static error_msg EM_HOST_BADCHR = "Illegal character in host component"; +static error_msg EM_HOST_EMPTY = "Protocol disallows empty host"; +static error_msg EM_HOST_LITERAL = "Unparseable IP literal in the host"; +static error_msg EM_PORT_BADCHR = "Illegal non-digit character in port component"; +static error_msg EM_PORT_RANGE = "Port value is out of range"; +static error_msg EM_PATH_BADCHR = "Illegal character in path component"; +static error_msg EM_QUERY_DISALLOWED = "Protocol disallows query"; +static error_msg EM_QF_BADCHR = "Illegal character in query or fragment"; +static error_msg EM_FRAGMENT_DISALLOWED = "Protocol disallows fragment"; + +struct sized_ustring { + unsigned char const *str; + size_t len; }; -/* - * @character is an integer because we sometimes receive signed chars, and other - * times we get unsigned chars. - * Casting a negative char into a unsigned char is undefined behavior. - */ -static int -validate_url_character(int character) -{ - /* - * RFCs 1738 and 3986 define a very specific range of allowed - * characters, but I don't think we're that concerned about URL - * correctness. Validating the URL properly is more involved than simply - * checking legal characters, anyway. - * - * What I really need this validation for is ensure that we won't get - * any trouble later, when we attempt to convert the global URI to a - * local file. - * - * Sample trouble: Getting UTF-8 characters. Why are they trouble? - * Because we don't have any guarantees that the system's file name - * encoding is UTF-8. URIs are not supposed to contain UTF-8 in the - * first place, so we have no reason to deal with encoding conversion. - * - * To be perfectly fair, we have no guarantees that the system's file - * name encoding is ASCII-compatible either, but I need to hang onto - * SOMETHING. - * - * (Asking users to use UTF-8 is fine, but asking users to use something - * ASCII-compatible is a little better.) - * - * So just make sure that the character is printable ASCII. - * - * TODO (next iteration) Consider exhaustive URL validation. - */ - return (0x20 <= character && character <= 0x7E) - ? 0 - : pr_val_err("URL has non-printable character code '%d'.", character); -} - -/** - * Initializes @uri->global* by cloning @str. - * This function does not assume that @str is null-terminated. - */ -static int -str2global(char const *str, size_t str_len, struct rpki_uri *uri) -{ - int error; - size_t i; +struct uri_buffer { + char *dst; + array_index d; + size_t capacity; +}; - for (i = 0; i < str_len; i++) { - error = validate_url_character(str[i]); - if (error) - return error; - } +struct schema_metadata { + unsigned int default_port; + bool allow_userinfo; + bool allow_empty_host; + bool allow_query; + bool allow_fragment; +}; - uri->global = pmalloc(str_len + 1); - strncpy(uri->global, str, str_len); - uri->global[str_len] = '\0'; - uri->global_len = str_len; +struct schema_metadata const HTTPS = { + .default_port = 443, + .allow_userinfo = false, + .allow_empty_host = false, + .allow_query = true, + .allow_fragment = true, +}; - return 0; -} +struct schema_metadata const RSYNC = { + .default_port = 873, + .allow_userinfo = true, + /* Seems actually allowed, but RPKI doesn't like it. */ + .allow_empty_host = false, + .allow_query = false, + .allow_fragment = false, +}; static bool -is_valid_mft_file_chara(uint8_t chara) +is_proto(struct sized_ustring *scheme, char const *proto) { - return ('a' <= chara && chara <= 'z') - || ('A' <= chara && chara <= 'Z') - || ('0' <= chara && chara <= '9') - || (chara == '-') - || (chara == '_'); -} - -/* RFC 9286, section 4.2.2 */ -static int -validate_mft_file(IA5String_t *ia5) -{ - size_t dot; - size_t i; - - if (ia5->size < 5) - return pr_val_err("File name is too short (%zu < 5).", ia5->size); - dot = ia5->size - 4; - if (ia5->buf[dot] != '.') - return pr_val_err("File name seems to lack a three-letter extension."); - - for (i = 0; i < ia5->size; i++) { - if (i != dot && !is_valid_mft_file_chara(ia5->buf[i])) { - return pr_val_err("File name contains illegal character #%u", - ia5->buf[i]); - } - } - - /* - * Actual extension doesn't matter; if there's no handler, - * we'll naturally ignore the file. - */ - return 0; + return strncasecmp((char const *)scheme->str, proto, scheme->len) == 0; } -/** - * Initializes @uri->global given manifest path @mft and its referenced file - * @ia5. - * - * ie. if @mft is "rsync://a/b/c.mft" and @ia5 is "d.cer", @uri->global will - * be "rsync://a/b/d.cer". - * - * Assumes that @mft is a "global" URL. (ie. extracted from rpki_uri.global.) - */ -static int -ia5str2global(struct rpki_uri *uri, char const *mft, IA5String_t *ia5) +static struct schema_metadata const * +get_metadata(struct sized_ustring *scheme) { - char *joined; - char const *slash_pos; - int dir_len; - int error; - - /* - * IA5String is a subset of ASCII. However, IA5String_t doesn't seem to - * be guaranteed to be NULL-terminated. - * `(char *) ia5->buf` is fair, but `strlen(ia5->buf)` is not. - */ - - error = validate_mft_file(ia5); - if (error) - return error; - - slash_pos = strrchr(mft, '/'); - if (slash_pos == NULL) - return pr_val_err("Manifest URL '%s' contains no slashes.", mft); + if (scheme->len != 5) + return NULL; - dir_len = (slash_pos + 1) - mft; - joined = pmalloc(dir_len + ia5->size + 1); + if (is_proto(scheme, "https")) + return &HTTPS; + if (is_proto(scheme, "rsync")) + return &RSYNC; - strncpy(joined, mft, dir_len); - strncpy(joined + dir_len, (char *) ia5->buf, ia5->size); - joined[dir_len + ia5->size] = '\0'; + return NULL; +} - uri->global = joined; - uri->global_len = dir_len + ia5->size; - return 0; +static bool +is_lowercase(unsigned char chr) +{ + return 'a' <= chr && chr <= 'z'; } -struct path_parser { - char const *token; - char const *slash; - size_t len; -}; +static bool +is_uppercase(unsigned char chr) +{ + return 'A' <= chr && chr <= 'Z'; +} -/* Return true if there's a new token, false if we're done. */ ++/* RFC 9286, section 4.2.2 */ static bool -path_next(struct path_parser *parser) +is_lowercase_hex(unsigned char chr) { - if (parser->slash == NULL) - return false; + return 'a' <= chr && chr <= 'f'; +} - parser->token = parser->slash + 1; - parser->slash = strchr(parser->token, '/'); - parser->len = (parser->slash != NULL) - ? (parser->slash - parser->token) - : strlen(parser->token); +static bool +is_uppercase_hex(unsigned char chr) +{ + return 'A' <= chr && chr <= 'F'; +} - return parser->token[0] != 0; +static bool +is_digit(unsigned char chr) +{ + return '0' <= chr && chr <= '9'; } static bool diff --cc src/types/uri.h index 2fa555bf,c5886ec8..ba877dd4 --- a/src/types/uri.h +++ b/src/types/uri.h @@@ -2,42 -2,70 +2,44 @@@ #define SRC_TYPES_URI_H_ #include - #include + #include -#include "asn1/asn1c/IA5String.h" -#include "data_structure/array_list.h" +#include "types/arraylist.h" -enum uri_type { - /* rsync URL */ - UT_RSYNC, - /* HTTPS URL */ - UT_HTTPS, - /* - * URI (not URL). - * In practice it's always rsync, but it doesn't matter. - */ - UT_CAGED, -}; - -struct rpki_uri; - -int __uri_create(struct rpki_uri **, char const *, enum uri_type, - bool, struct rpki_uri *, void const *, size_t); -int uri_create_mft(struct rpki_uri **, char const *, struct rpki_uri *, - struct rpki_uri *, IA5String_t *); -struct rpki_uri *uri_create_cache(char const *); +#define RPKI_SCHEMA_LEN 8 /* strlen("rsync://"), strlen("https://") */ -#define uri_create(uri, tal, type, is_notif, notif, guri) \ - __uri_create(uri, tal, type, is_notif, notif, guri, strlen(guri)) -#define uri_create_caged(uri, tal, notif, guri, guri_len) \ - __uri_create(uri, tal, UT_CAGED, false, notif, guri, guri_len) -#define uri_create_cage(uri, tal, notif) \ - uri_create_caged(uri, tal, notif, "", 0) +struct uri { + char *_str; + size_t _len; +}; -struct rpki_uri *uri_refget(struct rpki_uri *); -void uri_refput(struct rpki_uri *); +typedef char const *error_msg; -/* - * Note that, if you intend to print some URI, you're likely supposed to use - * uri_get_printable() instead. - */ -char const *uri_get_global(struct rpki_uri *); -char const *uri_get_local(struct rpki_uri *); -size_t uri_get_global_len(struct rpki_uri *); +error_msg uri_init(struct uri *, char const *); +void __uri_init(struct uri *, char const *, size_t); +#define __URI_INIT(uri, str) __uri_init(uri, str, strlen(str)) +void uri_copy(struct uri *, struct uri const *); +void uri_cleanup(struct uri *); -bool uri_equals(struct rpki_uri *, struct rpki_uri *); -bool uri_has_extension(struct rpki_uri *, char const *); -bool uri_is_certificate(struct rpki_uri *); -bool uri_is_notif(struct rpki_uri *); +#define uri_str(u) ((char const *)((u)->_str)) +#define uri_len(u) ((size_t const)((u)->_len)) -enum uri_type uri_get_type(struct rpki_uri *); -bool uri_is_rsync(struct rpki_uri *); -bool uri_is_https(struct rpki_uri *); +bool uri_is_rsync(struct uri const *); +bool uri_is_https(struct uri const *); -char const *uri_val_get_printable(struct rpki_uri *); -char const *uri_op_get_printable(struct rpki_uri *); +bool uri_equals(struct uri const *, struct uri const *); +bool uri_has_extension(struct uri const *, char const *); +bool uri_same_origin(struct uri const *, struct uri const *); -char *uri_get_rrdp_workspace(char const *, struct rpki_uri *); +int uri_parent(struct uri const *, struct uri *); +void uri_child(struct uri const *, char const *, size_t, struct uri *); +#define URI_CHILD(uri, name, child) uri_child(uri, name, strlen(name), child) /* Plural */ -DEFINE_ARRAY_LIST_STRUCT(uri_list, struct rpki_uri *); - -void uris_init(struct uri_list *); -void uris_cleanup(struct uri_list *); - -void uris_add(struct uri_list *, struct rpki_uri *); +DEFINE_ARRAY_LIST_STRUCT(uris, struct uri); - DECLARE_ARRAY_LIST_FUNCTIONS(uris, struct uri) ++void uris_init(struct uris *); ++void uris_cleanup(struct uris *, void (*cb)(struct uri *)); ++void uris_add(struct uris *list, struct uri *elem); #endif /* SRC_TYPES_URI_H_ */ diff --cc src/validation_handler.c index 34298ebb,9b02ac1e..6817c5e0 --- a/src/validation_handler.c +++ b/src/validation_handler.c @@@ -1,21 -1,21 +1,19 @@@ #include "validation_handler.h" - #include "rtr/db/db_table.h" -#include "log.h" -#include "thread_var.h" -- -/* - * Never returns NULL by contract. - */ -static struct validation_handler const * -get_current_threads_handler(void) -{ - struct validation_handler const *handler; +static struct db_table *table; - handler = validation_get_validation_handler(state_retrieve()); - if (handler == NULL) - pr_crit("This thread lacks a validation handler."); +void +vhandle_init(void) +{ + table = db_table_create(); +} - return handler; +struct db_table * +vhandle_claim(void) +{ + struct db_table *result = table; + table = NULL; + return result; } int diff --cc src/validation_handler.h index d06ae147,2e284c1f..02365dc8 --- a/src/validation_handler.h +++ b/src/validation_handler.h @@@ -1,17 -1,42 +1,18 @@@ #ifndef SRC_VALIDATION_HANDLER_H_ #define SRC_VALIDATION_HANDLER_H_ --#include "rtr/db/vrps.h" ++#include "rtr/db/db_table.h" ++#include "types/asn.h" -/** - * Functions that handle validation results. - * - * At some point, I believe we will end up separating the validator code into a - * library, so it can be used by other applications aside from Fort's RTR - * server. - * - * This structure is designed with that in mind; it's the callback collection - * that the library's user application will fill up, so it can do whatever it - * wants with the validated ROAs. - * - * Because it's intended to be used by arbitrary applications, it needs to be - * generic. Please refrain from adding callbacks that are specifically meant for - * a particular use case. - * - * All of these functions can be NULL. - */ -struct validation_handler { - /** Called every time Fort has successfully validated an IPv4 ROA. */ - int (*handle_roa_v4)(uint32_t, struct ipv4_prefix const *, uint8_t, - void *); - /** Called every time Fort has successfully validated an IPv6 ROA. */ - int (*handle_roa_v6)(uint32_t, struct ipv6_prefix const *, uint8_t, - void *); - /** Called every time Fort has successfully validated a BGPsec cert */ - int (*handle_router_key)(unsigned char const *, - struct asn_range const *, unsigned char const *, void *); - /** Generic user-defined argument for the functions above. */ - void *arg; -}; +void vhandle_init(void); +struct db_table *vhandle_claim(void); -int vhandler_handle_roa_v4(uint32_t, struct ipv4_prefix const *, uint8_t); -int vhandler_handle_roa_v6(uint32_t, struct ipv6_prefix const *, uint8_t); -int vhandler_handle_router_key(unsigned char const *, struct asn_range const *, +/* Called every time Fort has successfully validated an IPv4 ROA. */ +int vhandle_roa_v4(uint32_t, struct ipv4_prefix const *, uint8_t); +/* Called every time Fort has successfully validated an IPv6 ROA. */ +int vhandle_roa_v6(uint32_t, struct ipv6_prefix const *, uint8_t); +/* Called every time Fort has successfully validated a BGPsec cert. */ +int handle_router_key(unsigned char const *, struct asn_range const *, unsigned char const *); #endif /* SRC_VALIDATION_HANDLER_H_ */ diff --cc test/mock.c index ff776387,c5bfff43..b7bdc41c --- a/test/mock.c +++ b/test/mock.c @@@ -2,35 -2,26 +2,36 @@@ #include #include -#include "config.h" +#include +#include +#include +#include +#include +#include ++#include + +#include "common.h" #include "log.h" -#include "state.h" -#include "thread_var.h" -#include "config/filename_format.h" -#include "config/mode.h" -#include "incidence/incidence.h" - -/** - * Some core functions, as linked from unit tests. - */ - -MOCK_TRUE(log_val_enabled, unsigned int l) -MOCK_TRUE(log_op_enabled, unsigned int l) - -/* CFLAGS=-DPRINT_PRS make check */ -#ifdef PRINT_PRS -#define MOCK_PRINT \ +#include "types/map.h" + +/* Some core functions, as linked from unit tests. */ + +#if 0 + +static void +print_monotime(void) +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) + pr_crit("clock_gettime() returned '%s'", strerror(errno)); + printf("%ld.%.3ld ", now.tv_sec, now.tv_nsec / 1000000); +} + +#define MOCK_PRINT(color) \ do { \ va_list args; \ + printf(color); \ + print_monotime(); \ va_start(args, format); \ vfprintf(stdout, format, args); \ va_end(args); \