[--server.interval.retry=<unsigned integer>]
[--server.interval.expire=<unsigned integer>]
[--server.deltas.lifetime=<unsigned integer>]
+ [--prometheus.port=<unsigned integer>]
[--rsync.enabled=true|false]
[--rsync.priority=<unsigned integer>]
- [--rsync.retry.count=<unsigned integer>]
- [--rsync.retry.interval=<unsigned integer>]
[--rsync.transfer-timeout=<unsigned integer>]
[--http.enabled=true|false]
[--http.priority=<unsigned integer>]
.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:<port>\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.
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/file_type.h config/file_type.c
+ fort_SOURCES += config/curl_offset.c config/curl_offset.h
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
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)
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
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
#include <assert.h>
#include <errno.h>
-#include <string.h>
- #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"
/*
#include <openssl/asn1.h>
#include <openssl/obj_mac.h>
#include <openssl/objects.h>
-#include <string.h>
--#include "asn1/asn1c/INTEGER.h"
#include "asn1/asn1c/asn_internal.h"
#include "json_util.h"
#include <assert.h>
#include <errno.h>
-#include <string.h>
--#include "alloc.h"
#include "asn1/asn1c/BIT_STRING.h"
#include "asn1/asn1c/asn_internal.h"
#include "asn1/asn1c/ber_decoder.h"
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
- #include "types/uri.h"
+/*
+ * 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 "asn1/asn1c/constr_SEQUENCE.h"
-
- #ifdef __cplusplus
- extern "C" {
- #endif
+#include "asn1/asn1c/OCTET_STRING.h"
- #ifdef __cplusplus
- }
- #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 *);
+
- #include "asn1/asn1c/asn_internal.h"
+#endif /* _RsyncRequest_H_ */
#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"
#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"
#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;
--- /dev/null
+#include "base64.h"
+
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
++#include <stdlib.h>
++#include <string.h>
+
+#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;
+}
#ifndef SRC_BASE64_H_
#define SRC_BASE64_H_
-#include <openssl/bio.h>
#include <stdbool.h>
#include <stddef.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
- #include <sys/types.h>
- #include <unistd.h>
-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_ */
--- /dev/null
- #include <ftw.h>
+#include "cache.h"
+
+#include <fcntl.h>
- #include <stdbool.h>
- #include <sys/queue.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <unistd.h>
+#include <signal.h>
- #include "alloc.h"
+
- #include "common.h"
+#include "cachetmp.h"
- #include "types/array.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/uri.h"
+#include "types/path.h"
+#include "types/str.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);
+}
--- /dev/null
- #include <stdbool.h>
+#ifndef SRC_CACHE_LOCAL_CACHE_H_
+#define SRC_CACHE_LOCAL_CACHE_H_
+
- #include "types/map.h"
+#include "common.h"
- #include "types/uri.h"
+#include "types/rpp.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_ */
--- /dev/null
- #include <stdio.h>
+#include "cachetmp.h"
+
+#include <stdatomic.h>
- #include "types/path.h"
+
+#include "log.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);
+}
#include <dirent.h>
#include <errno.h>
#include <limits.h>
++#include <stdlib.h>
+ #include <string.h>
#include <time.h>
+ #include <unistd.h>
#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)
{
#ifndef SRC_RTR_COMMON_H_
#define SRC_RTR_COMMON_H_
++#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
++#include <stddef.h>
+#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "config.h"
- #include <curl/curl.h>
--#include <errno.h>
#include <getopt.h>
#include <libxml/xmlreader.h>
+#include <limits.h>
+ #include <microhttpd.h>
#include <openssl/opensslv.h>
- #include <stdlib.h>
++#include <sys/socket.h>
#include <syslog.h>
-#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"
#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,
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;
.offset = offsetof(struct rpki_config, http.ca_path),
.doc = "Directory where CA certificates are found, used to verify the peer",
.arg_doc = "<directory>",
- .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 = "<URI>",
+ .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,
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("<deprecated>");
- 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;
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;
}
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)
{
#include <netdb.h>
#include <netinet/in.h>
#include <stdint.h>
+#include <sys/stat.h>
++#include <sys/types.h>
#include "config/file_type.h"
#include "config/filename_format.h"
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);
#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,
--- /dev/null
- #include <errno.h>
+#include "config/time.h"
+
- #include <time.h>
+#include <getopt.h>
++#include <string.h>
+
+#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,
+};
#include <openssl/asn1t.h>
#include <openssl/obj_mac.h>
#include <openssl/objects.h>
++#include <sys/socket.h>
-#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"
#include <ftw.h>
#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)
#include <dirent.h>
#include <errno.h>
+#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
- #include <stdlib.h>
#include <string.h>
- #include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
--- /dev/null
+#include "hash.h"
+
+#include <openssl/evp.h>
++#include <stdlib.h>
+
+#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;
+}
--- /dev/null
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
- #include <sys/types.h>
- #include <unistd.h>
+#ifndef SRC_HASH_H_
+#define SRC_HASH_H_
+
+#include <stddef.h>
+
+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_ */
-#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"
--- /dev/null
- #include <stdbool.h>
+#ifndef SRC_HTTP_H_
+#define SRC_HTTP_H_
+
+#include <curl/curl.h>
+
+#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_ */
#include "init.h"
+#include <errno.h>
++#include <stdio.h>
++
#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)
#include "json_util.h"
#include <errno.h>
--#include <limits.h>
-#include <string.h>
--#include <time.h>
- #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)
{
#ifndef SRC_JSON_UTIL_H_
#define SRC_JSON_UTIL_H_
--#include <arpa/inet.h>
--#include <jansson.h>
#include <netdb.h>
--#include <netinet/in.h>
--#include <stdbool.h>
--#include <stddef.h>
--#include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
#include <sys/stat.h>
--#include <sys/types.h>
- #include <unistd.h>
+
+#include "asn1/asn1c/INTEGER.h"
- #include "file.h"
+#include "types/uri.h"
/*
* Contract of get functions:
#include <openssl/pem.h>
#include <time.h>
--#include "alloc.h"
#include "asn1/asn1c/OBJECT_IDENTIFIER.h"
-#include "extension.h"
+#include "ext.h"
#include "json_util.h"
#include "log.h"
#endif
#include <openssl/err.h>
#include <pthread.h>
-#include <signal.h>
#include <stdarg.h>
-#include <sys/stat.h>
#include <syslog.h>
#include <time.h>
++#include <unistd.h>
#include "config.h"
#include "thread_var.h"
#include <errno.h>
+#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)
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)
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;
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:
#include "log.h"
#include "object/certificate.h"
--#include "validation_handler.h"
++#include "types/router_key.h"
struct resource_params {
unsigned char const *ski;
#include <openssl/obj_mac.h>
#include <openssl/objects.h>
#include <openssl/rsa.h>
--#include <syslog.h>
--#include <time.h>
#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/path.h"
+#include "types/name.h"
- #include "types/uri.h"
+#include "types/str.h"
/*
* The X509V3_EXT_METHOD that references NID_sinfo_access uses the AIA item.
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;
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;
#ifndef SRC_OBJECT_CERTIFICATE_H_
#define SRC_OBJECT_CERTIFICATE_H_
- #include <openssl/x509.h>
-#include "asn1/asn1c/ANY.h"
-#include "asn1/asn1c/SignatureValue.h"
-#include "certificate_refs.h"
+#include <stdatomic.h>
+#include <sys/queue.h>
+
- #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 {
#include <openssl/bio.h>
#include <openssl/bn.h>
--#include <syslog.h>
#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;
#define SRC_OBJECT_CRL_H_
#include <openssl/x509.h>
-#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_ */
#include "object/ghostbusters.h"
- #include <errno.h>
-
--#include "log.h"
++#include "object/certificate.h"
#include "object/signed_object.h"
#include "object/vcard.h"
#include "thread_var.h"
#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_ */
#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
#ifndef SRC_OBJECT_MANIFEST_H_
#define SRC_OBJECT_MANIFEST_H_
- #include <openssl/sha.h>
- #include <openssl/x509.h>
-
-#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_ */
#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
#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_ */
#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_ */
#include "object/tal.h"
-#include <errno.h>
-#include <openssl/evp.h>
-#include <sys/queue.h>
-#include <time.h>
+#include <ctype.h>
- #include <sys/queue.h>
- #include <time.h>
-#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;
*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;
}
-#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 <stddef.h>
-#include "cache/local_cache.h"
-#include "rtr/db/db_table.h"
+/* This is RFC 8630. */
struct tal;
#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/uri.h"
+#include "types/path.h"
#define HDRSIZE 32
--- /dev/null
-#include <sys/types.h>
+ #include "prometheus.h"
+
+ #include <microhttpd.h>
+ #include <string.h>
+ #include <sys/socket.h>
+
+ #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);
+ }
#include "resource.h"
#include <errno.h>
++#include <string.h>
+#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 {
#include "resource/asn.h"
- #include "types/sorted_array.h"
-#include "sorted_array.h"
--
struct asn_cb {
foreach_asn_cb cb;
void *arg;
#ifndef SRC_RESOURCE_ASN_H_
#define SRC_RESOURCE_ASN_H_
--#include <stdbool.h>
--
-#include "as_number.h"
+#include "types/asn.h"
+#include "types/sorted_array.h"
/*
* Implementation note: This is just a casted struct sorted_array.
#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 */
#include "resource/ip6.h"
- #include "types/sorted_array.h"
+ #include <string.h>
-#include "sorted_array.h"
-
static int
addr_cmp(struct in6_addr const *a, struct in6_addr const *b)
{
#include "rrdp.h"
- #include <openssl/bn.h>
-#include <ctype.h>
+ #include <libxml/globals.h>
-#include <openssl/evp.h>
#include <openssl/sha.h>
+#include <sys/queue.h>
- #include "cache.h"
+#include "base64.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"
#ifndef SRC_RRDP_H_
#define SRC_RRDP_H_
- #include <stdbool.h>
- #include <time.h>
+#include <jansson.h>
- #include "file.h"
++#include <sys/stat.h>
++#include <sys/types.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_ */
--- /dev/null
- #include <stream.h>
+#include "rsync.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
- #include <syslog.h>
++#include <stdarg.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
- #include "alloc.h"
+
- #include "types/array.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 "asn1/asn1c/ber_decoder.h"
- #include "asn1/asn1c/der_encoder.h"
- #include "asn1/asn1c/RsyncRequest.h"
-
++#include "stream.h"
+#include "types/map.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);
+}
#include "rtr/db/db_table.h"
#include <errno.h>
- #include <pthread.h>
#include "alloc.h"
-#include "data_structure/uthash.h"
+#include "common.h"
#include "log.h"
+#include "types/uthash.h"
struct hashable_roa {
struct vrp data;
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
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;
}
#include "rtr/db/delta.h"
#include <stdatomic.h>
+ #include <string.h>
-#include "data_structure/array_list.h"
#include "log.h"
#include "types/address.h"
+#include "types/arraylist.h"
struct delta_v4 {
uint32_t as;
#include "rtr/db/vrps.h"
--#include <errno.h>
++#include <string.h>
#include <time.h>
+#include "alloc.h"
#include "common.h"
#include "config.h"
#include "log.h"
* 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"
#include <errno.h>
#include <poll.h>
+ #include <string.h>
++#include <sys/socket.h>
++#include <unistd.h>
#include "alloc.h"
#include "config.h"
#include "rtr/db/vrps.h"
#include "rtr/pdu_handler.h"
#include "rtr/pdu_sender.h"
-#include "thread/thread_pool.h"
+ #include "stats.h"
+#include "thread_pool.h"
++#include "types/address.h"
+#include "types/arraylist.h"
struct rtr_server {
int fd;
--- /dev/null
- #include <stddef.h>
- #include <string.h>
- #include <sys/types.h>
- #include <unistd.h>
+#include "sig.h"
+
+#include <errno.h>
+#ifdef BACKTRACE_ENABLED
+#include <execinfo.h>
+#endif
+#include <signal.h>
+
+#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));
+}
#include "slurm/db_slurm.h"
#include <errno.h>
+ #include <string.h>
#include <time.h>
+#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;
-#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 <openssl/evp.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
+#include <stddef.h>
#include <sys/queue.h>
- #include <sys/types.h>
- #include <unistd.h>
#include "types/router_key.h"
#include "types/vrp.h"
#include "slurm/slurm_loader.h"
#include <errno.h>
+#include <openssl/sha.h>
++#include <string.h>
+#include "alloc.h"
#include "common.h"
#include "config.h"
-#include "crypto/hash.h"
+#include "hash.h"
#include "log.h"
#include "slurm/slurm_parser.h"
#include <errno.h>
#include <openssl/asn1.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
#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"
--- /dev/null
-#include "data_structure/uthash.h"
+ #include "stats.h"
+
+ #include <stdarg.h>
+ #include <time.h>
+
+ #include "alloc.h"
+ #include "common.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;
+ }
--- /dev/null
- #include <stdbool.h>
- #include <stddef.h>
+#include "stream.h"
+
+#include <errno.h>
++#include <stdlib.h>
++#include <sys/types.h>
++#include <unistd.h>
+
+#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;
+}
--- /dev/null
- #include "alloc.h"
- #include "common.h"
+#include "task.h"
+
+#include <errno.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;
+}
--- /dev/null
- #include "object/certificate.h"
+#ifndef SRC_TASK_H_
+#define SRC_TASK_H_
+
+#include <sys/queue.h>
+
++#include "asn1/signed_data.h"
++#include "object/tal.h"
+#include "types/map.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_ */
-#include "thread/thread_pool.h"
+#include "thread_pool.h"
+ #include <stdlib.h>
+ #include <string.h>
#include <sys/queue.h>
#include "alloc.h"
#include <pthread.h>
- #include "alloc.h"
#include "log.h"
-static pthread_key_t state_key;
static pthread_key_t filenames_key;
struct filename_stack {
#include "types/address.h"
#include <errno.h>
++#include <stdlib.h>
++#include <string.h>
#include "log.h"
--#include "thread_var.h"
static void
init_quadrant(struct in6_addr *addr, unsigned int slot, uint32_t value)
--- /dev/null
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
- #include <sys/types.h>
- #include <unistd.h>
+#ifndef SRC_TYPES_ARRAY_H_
+#define SRC_TYPES_ARRAY_H_
+
+#include <stddef.h>
+
+typedef size_t array_index;
+#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0]))
+
+#endif /* SRC_TYPES_ARRAY_H_ */
-#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 <stdlib.h>
+ #include <strings.h>
+
#include "alloc.h"
-#include "data_structure/common.h"
+#include "types/array.h"
#define DEFINE_ARRAY_LIST_STRUCT(name, elem_type) \
struct name { \
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) \
--- /dev/null
- #include "alloc.h"
+#include "types/map.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);
+}
#include <openssl/asn1.h>
#include <openssl/obj_mac.h>
#include <openssl/objects.h>
--#include <syslog.h>
++#include <stdlib.h>
++#include <string.h>
+#include "alloc.h"
#include "log.h"
-#include "thread_var.h"
/**
* It's an RFC5280 name, but from RFC 6487's perspective.
--- /dev/null
- #include "log.h"
+#include "types/path.h"
+
++#include <string.h>
++
+#include "alloc.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;
+}
--- /dev/null
- #include "types/array.h"
-
+#include "types/rpp.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);
+}
--- /dev/null
- #include <time.h>
+#ifndef SRC_RPP_H_
+#define SRC_RPP_H_
+
+#include <openssl/x509.h>
++#include <sys/stat.h>
+
+#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_ */
-#include "sorted_array.h"
+#include "types/sorted_array.h"
+ #include <stdlib.h>
+ #include <string.h>
+ #include <strings.h>
+
#include "alloc.h"
#include "log.h"
-#include "str_token.h"
+#include "types/str.h"
- #include <string.h>
+#include <errno.h>
#include <openssl/bio.h>
+ #include <stdint.h>
++#include <stdlib.h>
+ #include <string.h>
#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.
#include <openssl/asn1.h>
#include <openssl/bn.h>
#include <stdbool.h>
+ #include <stddef.h>
+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 **);
#include "types/uri.h"
- #include "alloc.h"
+#include <arpa/inet.h>
+#include <errno.h>
++#include <sys/socket.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
#define SRC_TYPES_URI_H_
#include <stdbool.h>
- #include <stddef.h>
+ #include <string.h>
-#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_ */
#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
#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_ */
#include <errno.h>
#include <arpa/inet.h>
-#include "config.h"
+#include <fcntl.h>
+#include <openssl/err.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
++#include <unistd.h>
+
+#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); \