From: pcarana Date: Tue, 23 Apr 2019 23:27:35 +0000 (-0500) Subject: Merge remote-tracking branch 'rtrserver/master' X-Git-Tag: v0.0.2~49 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=733f61ac6122b51d1e82676287571776a56ba419;p=thirdparty%2FFORT-validator.git Merge remote-tracking branch 'rtrserver/master' Include SLURM functions and some adequations to run the validator. --- 733f61ac6122b51d1e82676287571776a56ba419 diff --cc src/Makefile.am index fd901afc,003cf0ff..b4a95d4b --- a/src/Makefile.am +++ b/src/Makefile.am @@@ -1,99 -1,36 +1,103 @@@ -AM_CFLAGS = -pedantic -Wall -std=gnu11 -O3 -AM_LDFLAGS = - -bin_PROGRAMS = rtr_server - -rtr_server_SOURCES = main.c -rtr_server_SOURCES += address.c address.h -rtr_server_SOURCES += array_list.h -rtr_server_SOURCES += clients.c clients.h -rtr_server_SOURCES += common.c common.h -rtr_server_SOURCES += configuration.c configuration.h -rtr_server_SOURCES += csv.c csv.h -rtr_server_SOURCES += json_parser.c json_parser.h -rtr_server_SOURCES += line_file.c line_file.h -rtr_server_SOURCES += notify.c notify.h -rtr_server_SOURCES += slurm_db.c slurm_db.h -rtr_server_SOURCES += slurm_loader.c slurm_loader.h -rtr_server_SOURCES += slurm_parser.c slurm_parser.h -rtr_server_SOURCES += updates_daemon.c updates_daemon.h -rtr_server_SOURCES += vrps.c vrps.h - -rtr_server_SOURCES += rtr/err_pdu.c rtr/err_pdu.h -rtr_server_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h -rtr_server_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h -rtr_server_SOURCES += rtr/pdu_serializer.c rtr/pdu_serializer.h -rtr_server_SOURCES += rtr/pdu.c rtr/pdu.h -rtr_server_SOURCES += rtr/primitive_reader.c rtr/primitive_reader.h -rtr_server_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h -rtr_server_SOURCES += rtr/rtr.c rtr/rtr.h - -rtr_server_SOURCES += crypto/base64.c crypto/base64.h - -rtr_server_LDADD = ${JANSSON_LIBS} +# Comment these out during releases. +# Also increase optimization I guess (-O0 -> -On) +#CFLAGS_DEBUG = -DDEBUG +LDFLAGS_DEBUG = -rdynamic + +bin_PROGRAMS = fort + +fort_SOURCES = main.c + +fort_SOURCES += address.h address.c +fort_SOURCES += algorithm.h algorithm.c +fort_SOURCES += certificate_refs.h certificate_refs.c +fort_SOURCES += console_handler.h console_handler.c +fort_SOURCES += common.h +fort_SOURCES += config.h config.c +fort_SOURCES += debug.h debug.c +fort_SOURCES += extension.h extension.c +fort_SOURCES += file.h file.c +fort_SOURCES += line_file.h line_file.c +fort_SOURCES += log.h log.c +fort_SOURCES += nid.h nid.c +fort_SOURCES += random.h random.c +fort_SOURCES += resource.h resource.c +fort_SOURCES += rpp.h rpp.c +fort_SOURCES += sorted_array.h sorted_array.c +fort_SOURCES += state.h state.c +fort_SOURCES += str.h str.c +fort_SOURCES += thread_var.h thread_var.c +fort_SOURCES += uri.h uri.c +fort_SOURCES += json_handler.h json_handler.c +fort_SOURCES += validation_handler.h validation_handler.c + +fort_SOURCES += rsync/rsync.h rsync/rsync.c + +fort_SOURCES += asn1/content_info.h asn1/content_info.c +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 += config/boolean.c config/boolean.h +fort_SOURCES += config/filename_format.h config/filename_format.c +fort_SOURCES += config/str.c config/str.h +fort_SOURCES += config/string_array.h config/string_array.c +fort_SOURCES += config/sync_strategy.h config/sync_strategy.c +fort_SOURCES += config/types.h +fort_SOURCES += config/uint.c config/uint.h +fort_SOURCES += config/uint32.c config/uint32.h + +fort_SOURCES += data_structure/array_list.h + +fort_SOURCES += crypto/base64.h crypto/base64.c +fort_SOURCES += crypto/hash.h crypto/hash.c + +fort_SOURCES += data_structure/circular_indexer.h +fort_SOURCES += data_structure/circular_indexer.c + +fort_SOURCES += object/certificate.h object/certificate.c +fort_SOURCES += object/crl.h object/crl.c +fort_SOURCES += object/ghostbusters.h object/ghostbusters.c +fort_SOURCES += object/manifest.h object/manifest.c +fort_SOURCES += object/name.h object/name.c +fort_SOURCES += object/roa.h object/roa.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 += resource/ip4.h resource/ip4.c +fort_SOURCES += resource/ip6.h resource/ip6.c +fort_SOURCES += resource/asn.h resource/asn.c + +fort_SOURCES += array_list.h +fort_SOURCES += clients.c clients.h +fort_SOURCES += common.c common.h ++fort_SOURCES += json_parser.c json_parser.h +fort_SOURCES += notify.c notify.h ++fort_SOURCES += slurm_db.c slurm_db.h ++fort_SOURCES += slurm_loader.c slurm_loader.h ++fort_SOURCES += slurm_parser.c slurm_parser.h +fort_SOURCES += updates_daemon.c updates_daemon.h + +fort_SOURCES += rtr/err_pdu.c rtr/err_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_serializer.c rtr/pdu_serializer.h +fort_SOURCES += rtr/pdu.c rtr/pdu.h +fort_SOURCES += rtr/primitive_reader.c rtr/primitive_reader.h +fort_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h +fort_SOURCES += rtr/rtr.c rtr/rtr.h + +fort_SOURCES += rtr/db/delta.c rtr/db/delta.h +fort_SOURCES += rtr/db/roa_tree.c rtr/db/roa_tree.h +fort_SOURCES += rtr/db/roa.c rtr/db/roa.h +fort_SOURCES += rtr/db/vrps.c rtr/db/vrps.h + +fort_CFLAGS = -Wall +# Feel free to temporarily remove this one if you're not using gcc 7.3.0. +#fort_CFLAGS += $(GCC_WARNS) +fort_CFLAGS += -std=gnu99 -O0 -g $(CFLAGS_DEBUG) +fort_LDFLAGS = $(LDFLAGS_DEBUG) +fort_LDADD = ${JANSSON_LIBS} # I'm tired of scrolling up, but feel free to comment this out. GCC_WARNS = -fmax-errors=1 diff --cc src/address.c index cdde1ec6,de8b9fdf..67663021 --- a/src/address.c +++ b/src/address.c @@@ -171,196 -98,79 +171,307 @@@ prefix4_decode(IPAddress_t const *str, return 0; } +/** + * Translates an `IPAddress_t` to its equivalent `struct ipv6_prefix`. + */ int -prefix6_decode(const char *str, struct ipv6_prefix *result) +prefix6_decode(IPAddress_t const *str, struct ipv6_prefix *result) { - int error; + struct in6_addr suffix; + int len; - if (str == NULL) { - warnx("Null string received, can't decode IPv6 prefix"); - return -EINVAL; + if (str->size > 16) { + return pr_err("IPv6 address has too many octets. (%zu)", + str->size); + } + if (str->bits_unused < 0 || 7 < str->bits_unused) { + return pr_err("Bit string IPv6 address's unused bits count (%d) is out of range (0-7).", + str->bits_unused); } - error = str2addr6(str, &result->addr); - if (error) { - warnx("Invalid IPv6 prefix '%s'", str); + memset(&result->addr, 0, sizeof(result->addr)); + memcpy(&result->addr, str->buf, str->size); + len = 8 * str->size - str->bits_unused; + + if (len < 0 || 128 < len) { + return pr_err("IPv6 prefix length (%d) is out of bounds (0-128).", + len); + } + + result->len = len; + + memset(&suffix, 0, sizeof(suffix)); + ipv6_suffix_mask(result->len, &suffix); + if (addr6_bitwise_and(&result->addr, &suffix)) { + return pr_err("IPv6 prefix '%s/%u' has enabled suffix bits.", + v6addr2str(&result->addr), result->len); + } + + return 0; +} + +static int +check_order4(struct ipv4_range *result) +{ + if (ntohl(result->min.s_addr) > ntohl(result->max.s_addr)) { + return pr_err("The IPv4 range '%s-%s' is inverted.", + v4addr2str(&result->min), v4addr2str2(&result->max)); + } + + return 0; +} + +/** + * If @range could have been encoded as a prefix, this function errors. + * + * rfc3779#section-2.2.3.7 + */ +static int +check_encoding4(struct ipv4_range *range) +{ + const uint32_t MIN = ntohl(range->min.s_addr); + const uint32_t MAX = ntohl(range->max.s_addr); + uint32_t mask; + + for (mask = 0x80000000u; mask != 0; mask >>= 1) + if ((MIN & mask) != (MAX & mask)) + break; + + for (; mask != 0; mask >>= 1) + if (((MIN & mask) != 0) || ((MAX & mask) == 0)) + return 0; + + return pr_err("IPAddressRange '%s-%s' is a range, but should have been encoded as a prefix.", + v4addr2str(&range->min), v4addr2str2(&range->max)); +} + +/** + * Translates an `IPAddressRange_t` to its equivalent `struct ipv4_range`. + */ +int +range4_decode(IPAddressRange_t const *input, struct ipv4_range *result) +{ + struct ipv4_prefix prefix; + int error; + + error = prefix4_decode(&input->min, &prefix); + if (error) + return error; + result->min = prefix.addr; + + error = prefix4_decode(&input->max, &prefix); + if (error) + return error; + result->max.s_addr = prefix.addr.s_addr | be32_suffix_mask(prefix.len); + + error = check_order4(result); + if (error) return error; + + return check_encoding4(result); +} + +static int +check_order6(struct ipv6_range *result) +{ + uint32_t min; + uint32_t max; + unsigned int quadrant; + + for (quadrant = 0; quadrant < 4; quadrant++) { + min = addr6_get_quadrant(&result->min, quadrant); + max = addr6_get_quadrant(&result->max, quadrant); + if (min > max) { + return pr_err("The IPv6 range '%s-%s' is inverted.", + v6addr2str(&result->min), + v6addr2str2(&result->max)); + } else if (min < max) { + return 0; /* result->min < result->max */ + } + } + + return 0; /* result->min == result->max */ +} + +static int +pr_bad_encoding(struct ipv6_range *range) +{ + return pr_err("IPAddressRange %s-%s is a range, but should have been encoded as a prefix.", + v6addr2str(&range->min), v6addr2str2(&range->max)); +} + +static int +__check_encoding6(struct ipv6_range *range, unsigned int quadrant, + uint32_t mask) +{ + uint32_t min; + uint32_t max; + + for (; quadrant < 4; quadrant++) { + min = addr6_get_quadrant(&range->min, quadrant); + max = addr6_get_quadrant(&range->max, quadrant); + for (; mask != 0; mask >>= 1) + if (((min & mask) != 0) || ((max & mask) == 0)) + return 0; + mask = 0x80000000u; + } + + return pr_bad_encoding(range); +} + +static int +check_encoding6(struct ipv6_range *range) +{ + uint32_t min; + uint32_t max; + unsigned int quadrant; + uint32_t mask; + + for (quadrant = 0; quadrant < 4; quadrant++) { + min = addr6_get_quadrant(&range->min, quadrant); + max = addr6_get_quadrant(&range->max, quadrant); + for (mask = 0x80000000u; mask != 0; mask >>= 1) + if ((min & mask) != (max & mask)) + return __check_encoding6(range, quadrant, mask); } + return pr_bad_encoding(range); +} + +/** + * Translates an `IPAddressRange_t` to its equivalent `struct ipv6_range`. + */ +int +range6_decode(IPAddressRange_t const *input, struct ipv6_range *result) +{ + struct ipv6_prefix prefix; + int error; + + error = prefix6_decode(&input->min, &prefix); + if (error) + return error; + result->min = prefix.addr; + + error = prefix6_decode(&input->max, &prefix); + if (error) + return error; + result->max = prefix.addr; + ipv6_suffix_mask(prefix.len, &result->max); + + error = check_order6(result); + if (error) + return error; + + return check_encoding6(result); +} ++ ++static char const * ++addr2str4(struct in_addr *addr, char *buffer) ++{ ++ return inet_ntop(AF_INET, addr, buffer, INET_ADDRSTRLEN); ++} ++ ++static char const * ++addr2str6(struct in6_addr *addr, char *buffer) ++{ ++ return inet_ntop(AF_INET6, addr, buffer, INET6_ADDRSTRLEN); ++} ++ ++static int const ++str2addr4(const char *addr, struct in_addr *dst) ++{ ++ if (!inet_pton(AF_INET, addr, dst)) ++ return -EINVAL; + return 0; - } ++} ++ ++static int const ++str2addr6(const char *addr, struct in6_addr *dst) ++{ ++ if (!inet_pton(AF_INET6, addr, dst)) ++ return -EINVAL; ++ return 0; ++} + + int -prefix_length_decode (const char *text, unsigned int *dst, int max_value) ++str_to_prefix4(const char *str, struct ipv4_prefix *result) ++{ ++ int error; ++ ++ if (str == NULL) ++ return pr_err("Can't parse NULL IPv4 prefix"); ++ ++ error = str2addr4(str, &result->addr); ++ if (error) ++ return pr_err("Invalid IPv4 prefix '%s'", str); ++ ++ return 0; ++} ++ ++int ++str_to_prefix6(const char *str, struct ipv6_prefix *result) ++{ ++ int error; ++ ++ if (str == NULL) ++ return pr_err("Can't parse NULL IPv6 prefix"); ++ ++ error = str2addr6(str, &result->addr); ++ if (error) ++ return pr_err("Invalid IPv6 prefix '%s'", str); ++ ++ return 0; ++} ++ ++int ++str_to_prefix_length(const char *text, uint8_t *dst, uint8_t max_value) + { + unsigned long len; + - if (text == NULL) { - warnx("Null string received, can't decode prefix length"); - return -EINVAL; - } ++ if (text == NULL) ++ return pr_err("Can't decode NULL prefix length"); + + errno = 0; + len = strtoul(text, NULL, 10); + if (errno) { - warn("Invalid prefix length '%s'", text); ++ pr_errno(errno, "Invalid prefix length '%s'", text); + return -EINVAL; + } + /* An underflow or overflow will be considered here */ - if (len < 0 || max_value < len) { - warnx("Prefix length (%ld) is out of range (0-%d).", ++ if (len < 0 || max_value < len) ++ return pr_err("Prefix length (%ld) is out of range (0-%d).", + len, max_value); - return -EINVAL; - } - *dst = (unsigned int) len; ++ ++ *dst = (uint8_t) len; + return 0; + } + + int -prefix4_validate (struct ipv4_prefix *prefix) ++ipv4_prefix_validate(struct ipv4_prefix *prefix) + { + char buffer[INET_ADDRSTRLEN]; + - if ((prefix->addr.s_addr & be32_suffix_mask(prefix->len)) != 0) { - warnx("IPv4 prefix %s/%u has enabled suffix bits.", ++ if ((prefix->addr.s_addr & be32_suffix_mask(prefix->len)) != 0) ++ return pr_err("IPv4 prefix %s/%u has enabled suffix bits.", + addr2str4(&prefix->addr, buffer), prefix->len); - return -EINVAL; - } ++ + return 0; + } + + int -prefix6_validate (struct ipv6_prefix *prefix) ++ipv6_prefix_validate(struct ipv6_prefix *prefix) + { + struct in6_addr suffix; + char buffer[INET6_ADDRSTRLEN]; + + memset(&suffix, 0, sizeof(suffix)); + ipv6_suffix_mask(prefix->len, &suffix); + if ((prefix->addr.s6_addr32[0] & suffix.s6_addr32[0]) + || (prefix->addr.s6_addr32[1] & suffix.s6_addr32[1]) + || (prefix->addr.s6_addr32[2] & suffix.s6_addr32[2]) - || (prefix->addr.s6_addr32[3] & suffix.s6_addr32[3])) { - warnx("IPv6 prefix %s/%u has enabled suffix bits.", ++ || (prefix->addr.s6_addr32[3] & suffix.s6_addr32[3])) ++ return pr_err("IPv6 prefix %s/%u has enabled suffix bits.", + addr2str6(&prefix->addr, buffer), prefix->len); - return -EINVAL; - } ++ + return 0; + } diff --cc src/address.h index 74dba9c3,1969f625..f7869937 --- a/src/address.h +++ b/src/address.h @@@ -13,29 -10,15 +13,36 @@@ struct ipv4_prefix struct ipv6_prefix { struct in6_addr addr; - unsigned int len; + uint8_t len; }; -int prefix4_decode(const char *, struct ipv4_prefix *); -int prefix6_decode(const char *, struct ipv6_prefix *); +struct ipv4_range { + struct in_addr min; + struct in_addr max; +}; + +struct ipv6_range { + struct in6_addr min; + struct in6_addr max; +}; + +uint32_t u32_suffix_mask(unsigned int); +uint32_t be32_suffix_mask(unsigned int); +void ipv6_suffix_mask(unsigned int, struct in6_addr *); + +bool prefix4_equals(struct ipv4_prefix const *, struct ipv4_prefix const *); +bool prefix6_equals(struct ipv6_prefix const *, struct ipv6_prefix const *); + +int prefix4_decode(IPAddress_t const *, struct ipv4_prefix *); +int prefix6_decode(IPAddress_t const *, struct ipv6_prefix *); +int range4_decode(IPAddressRange_t const *, struct ipv4_range *); +int range6_decode(IPAddressRange_t const *, struct ipv6_range *); -int prefix_length_decode(const char *, unsigned int *, int); ++int str_to_prefix4(const char *, struct ipv4_prefix *); ++int str_to_prefix6(const char *, struct ipv6_prefix *); ++int str_to_prefix_length(const char *, uint8_t *, uint8_t); + -int prefix4_validate (struct ipv4_prefix *); -int prefix6_validate (struct ipv6_prefix *); ++int ipv4_prefix_validate(struct ipv4_prefix *); ++int ipv6_prefix_validate(struct ipv6_prefix *); + #endif /* SRC_ADDRESS_H_ */ diff --cc src/config.c index bae3f2b2,00000000..de9354da mode 100644,000000..100644 --- a/src/config.c +++ b/src/config.c @@@ -1,728 -1,0 +1,743 @@@ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "json_handler.h" +#include "log.h" +#include "config/boolean.h" +#include "config/str.h" +#include "config/uint.h" +#include "config/uint32.h" + +/** + * To add a member to this structure, + * + * 1. Add it. + * 2. Add its metadata somewhere in @groups. + * 3. Add default value to set_default_values(). + * 4. Create the getter. + * + * Assuming you don't need to create a data type, that should be all. + */ +struct rpki_config { + /** TAL file name or directory. TODO support directories */ + char *tal; + /** Path of our local clone of the repository */ + char *local_repository; + /** Synchronization (currently only RSYNC) download strategy. */ + enum sync_strategy sync_strategy; + /** + * Handle TAL URIs in random order? + * (https://tools.ietf.org/html/rfc7730#section-3, last + * paragraphs) + */ + bool shuffle_tal_uris; + /** + * rfc6487#section-7.2, last paragraph. + * Prevents arbitrarily long paths and loops. + */ + unsigned int maximum_certificate_depth; + + struct { + /** The listener address of the RTR server. */ + char *address; + /** TODO document */ + char *port; + /** Maximum accepted client connections */ + unsigned int queue; + + /** Interval used to look for updates at VRPs location */ + /* TODO rename */ + unsigned int vrps_check_interval; + + /** Intervals use at RTR v1 End of data PDU **/ + uint32_t refresh_interval; + uint32_t retry_interval; + uint32_t expire_interval; ++ ++ /** Directory where the .slurm files are located */ ++ char *slurm_location; + } server; + + struct { + char *program; + struct { + struct string_array flat; + struct string_array recursive; + } args; + } rsync; + + struct { + /** Print ANSI color codes? */ + bool color; + /** Format in which file names will be printed. */ + enum filename_format filename_format; + } log; +}; + +static void print_usage(FILE *, bool); + +#define DECLARE_HANDLE_FN(name) \ + static int name( \ + struct option_field const *, \ + char * \ + ) +DECLARE_HANDLE_FN(handle_help); +DECLARE_HANDLE_FN(handle_usage); +DECLARE_HANDLE_FN(handle_version); +DECLARE_HANDLE_FN(handle_json); + +static char const *program_name; +static struct rpki_config rpki_config; + +/** + * An option that takes no arguments, is not correlated to any rpki_config + * fields, and is entirely managed by its handler function. + */ +static const struct global_type gt_callback = { + .has_arg = no_argument, +}; + +static const struct option_field options[] = { + + /* ARGP-only, non-fields */ + { + .id = 'h', + .name = "help", + .type = >_callback, + .handler = handle_help, + .doc = "Give this help list", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 1000, + .name = "usage", + .type = >_callback, + .handler = handle_usage, + .doc = "Give a short usage message", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 'V', + .name = "version", + .type = >_callback, + .handler = handle_version, + .doc = "Print program version", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 'f', + .name = "configuration-file", + .type = >_string, + .handler = handle_json, + .doc = "JSON file additional configuration will be read from", + .arg_doc = "", + .availability = AVAILABILITY_GETOPT, + }, + + /* Root fields */ + { + .id = 't', + .name = "tal", + .type = >_string, + .offset = offsetof(struct rpki_config, tal), + .doc = "Path to the TAL file", + .arg_doc = "", + }, { + .id = 'r', + .name = "local-repository", + .type = >_string, + .offset = offsetof(struct rpki_config, local_repository), + .doc = "Directory where the repository local cache will be stored/read", + .arg_doc = "", + }, { + .id = 1001, + .name = "sync-strategy", + .type = >_sync_strategy, + .offset = offsetof(struct rpki_config, sync_strategy), + .doc = "RSYNC download strategy", + }, { + .id = 2000, + .name = "shuffle-uris", + .type = >_bool, + .offset = offsetof(struct rpki_config, shuffle_tal_uris), + .doc = "Shuffle URIs in the TAL before accessing them", + }, { + .id = 1002, + .name = "maximum-certificate-depth", + .type = >_uint, + .offset = offsetof(struct rpki_config, + maximum_certificate_depth), + .doc = "Maximum allowable certificate chain length", + .min = 1, + /** + * It cannot be UINT_MAX, because then the actual number will + * overflow and will never be bigger than this. + */ + .max = UINT_MAX - 1, + }, + + /* Server fields */ + { + .id = 5000, + .name = "server.address", + .type = >_string, + .offset = offsetof(struct rpki_config, server.address), + .doc = "The listener address of the RTR server.", + }, { + .id = 5001, + .name = "server.port", + .type = >_string, + .offset = offsetof(struct rpki_config, server.port), + .doc = "", /* TODO */ + }, { + .id = 5003, + .name = "server.queue", + .type = >_uint, + .offset = offsetof(struct rpki_config, server.queue), + .doc = "Maximum accepted client connections", + .min = 1, + .max = SOMAXCONN, + }, { + .id = 5004, + .name = "server.vrps-check-interval", + .type = >_uint, + .offset = offsetof(struct rpki_config, server.vrps_check_interval), + .doc = "Interval used to look for updates at VRPs location", + /* + * RFC 6810 and 8210: + * The cache MUST rate-limit Serial Notifies to no more frequently than + * one per minute. + */ + .min = 60, + .max = 7200, + }, { + .id = 5005, + .name = "server.rtr-interval.refresh", + .type = >_uint32, + .offset = offsetof(struct rpki_config, server.refresh_interval), + .doc = "Intervals use at RTR v1 End of data PDU", + .min = 1, + .max = 86400, + }, { + .id = 5006, + .name = "server.rtr-interval.retry", + .type = >_uint32, + .offset = offsetof(struct rpki_config, server.retry_interval), + .doc = "", + .min = 1, + .max = 7200, + }, { + .id = 5007, + .name = "server.rtr-interval.expire", + .type = >_uint32, + .offset = offsetof(struct rpki_config, server.expire_interval), + .doc = "", + .min = 600, + .max = 172800, - }, ++ }, { ++ .id = 5008, ++ .name = "server.slurm.location", ++ .type = >_string, ++ .offset = offsetof(struct rpki_config, server.slurm_location), ++ .doc = "Directory where the .slurm files are located", ++ }, + + /* RSYNC fields */ + { + .id = 3000, + .name = "rsync.program", + .type = >_string, + .offset = offsetof(struct rpki_config, rsync.program), + .doc = "Name of the program needed to execute an RSYNC", + .arg_doc = "", + .availability = AVAILABILITY_JSON, + }, { + .id = 3001, + .name = "rsync.arguments-recursive", + .type = >_string_array, + .offset = offsetof(struct rpki_config, rsync.args.recursive), + .doc = "RSYNC program arguments that will trigger a recursive RSYNC", + .availability = AVAILABILITY_JSON, + }, { + .id = 3002, + .name = "rsync.arguments-flat", + .type = >_string_array, + .offset = offsetof(struct rpki_config, rsync.args.flat), + .doc = "RSYNC program arguments that will trigger a non-recursive RSYNC", + .availability = AVAILABILITY_JSON, + }, + + /* Logging fields */ + { + .id = 'c', + .name = "log.color-output", + .type = >_bool, + .offset = offsetof(struct rpki_config, log.color), + .doc = "Print ANSI color codes.", + }, { + .id = 4000, + .name = "log.file-name-format", + .type = >_filename_format, + .offset = offsetof(struct rpki_config, log.filename_format), + .doc = "File name variant to print during debug/error messages", + }, + + { 0 }, +}; + +/** + * Returns true if @field is the descriptor of one of the members of the + * struct rpki_config structure, false otherwise. + * (Alternatively: Returns true if @field->offset is valid, false otherwise.) + */ +static bool +is_rpki_config_field(struct option_field const *field) +{ + return field->handler == NULL; +} + +void * +get_rpki_config_field(struct option_field const *field) +{ + return ((unsigned char *) &rpki_config) + field->offset; +} + +static int +handle_help(struct option_field const *field, char *arg) +{ + print_usage(stdout, true); + exit(0); +} + +static int +handle_usage(struct option_field const *field, char *arg) +{ + print_usage(stdout, false); + exit(0); +} + +static int +handle_version(struct option_field const *field, char *arg) +{ + printf("0.0.1\n"); + exit(0); +} + +static int +handle_json(struct option_field const *field, char *file_name) +{ + return set_config_from_file(file_name); +} + +static bool +is_alphanumeric(int chara) +{ + return ('a' <= chara && chara <= 'z') + || ('A' <= chara && chara <= 'Z') + || ('0' <= chara && chara <= '9'); +} + +/** + * "struct option" is the array that getopt expects. + * "struct args_flag" is our option metadata. + */ +static int +construct_getopt_options(struct option **_long_opts, char **_short_opts) +{ + struct option_field const *opt; + struct option *long_opts; + char *short_opts; + unsigned int total_long_options; + unsigned int total_short_options; + + total_long_options = 0; + total_short_options = 0; + FOREACH_OPTION(options, opt, AVAILABILITY_GETOPT) { + total_long_options++; + if (is_alphanumeric(opt->id)) { + total_short_options++; + if (opt->type->has_arg != no_argument) + total_short_options++; /* ":" */ + } + } + + /* +1 NULL end, means end of array. */ + long_opts = calloc(total_long_options + 1, sizeof(struct option)); + if (long_opts == NULL) + return pr_enomem(); + short_opts = malloc(total_short_options + 1); + if (short_opts == NULL) { + free(long_opts); + return pr_enomem(); + } + + *_long_opts = long_opts; + *_short_opts = short_opts; + + FOREACH_OPTION(options, opt, AVAILABILITY_GETOPT) { + long_opts->name = opt->name; + long_opts->has_arg = opt->type->has_arg; + long_opts->flag = NULL; + long_opts->val = opt->id; + long_opts++; + + if (is_alphanumeric(opt->id)) { + *short_opts = opt->id; + short_opts++; + if (opt->type->has_arg != no_argument) { + *short_opts = ':'; + short_opts++; + } + } + } + + *short_opts = '\0'; + return 0; +} + +static void +print_config(void) +{ + struct option_field const *opt; + + pr_info("Configuration {"); + pr_indent_add(); + + FOREACH_OPTION(options, opt, 0xFFFF) + if (is_rpki_config_field(opt) && opt->type->print != NULL) + opt->type->print(opt, get_rpki_config_field(opt)); + + pr_indent_rm(); + pr_info("}"); +} + +static int +set_default_values(void) +{ + static char const *default_rsync_args[] = { + "--recursive", + "--delete", + "--times", + "--contimeout=20", + "$REMOTE", + "$LOCAL", + }; + + int error; + + /* + * Values that might need to be freed WILL be freed, so use heap + * duplicates. + */ + + rpki_config.server.address = NULL; + rpki_config.server.port = strdup("323"); + if (rpki_config.server.port == NULL) + return pr_enomem(); + + rpki_config.server.queue = 10; + rpki_config.server.vrps_check_interval = 60; + rpki_config.server.refresh_interval = 3600; + rpki_config.server.retry_interval = 600; + rpki_config.server.expire_interval = 7200; ++ rpki_config.server.slurm_location = NULL; + + rpki_config.tal = NULL; + + rpki_config.local_repository = strdup("repository/"); + if (rpki_config.local_repository == NULL) { + error = pr_enomem(); + goto revert_port; + } + + rpki_config.sync_strategy = SYNC_ROOT; + rpki_config.shuffle_tal_uris = false; + rpki_config.maximum_certificate_depth = 32; + + rpki_config.rsync.program = strdup("rsync"); + if (rpki_config.rsync.program == NULL) { + error = pr_enomem(); + goto revert_repository; + } + + error = string_array_init(&rpki_config.rsync.args.recursive, + default_rsync_args, ARRAY_LEN(default_rsync_args)); + if (error) + goto revert_rsync_program; + /* Simply remove --recursive and --delete. */ + error = string_array_init(&rpki_config.rsync.args.flat, + default_rsync_args + 2, ARRAY_LEN(default_rsync_args) - 2); + if (error) + goto revert_recursive_array; + + rpki_config.log.color = false; + rpki_config.log.filename_format = FNF_GLOBAL; + + return 0; + +revert_recursive_array: + string_array_cleanup(&rpki_config.rsync.args.recursive); +revert_rsync_program: + free(rpki_config.rsync.program); +revert_repository: + free(rpki_config.local_repository); +revert_port: + free(rpki_config.server.port); + return error; +} + +static int +validate_config(void) +{ + return (rpki_config.tal != NULL) + ? 0 + : pr_err("The TAL file (--tal) is mandatory."); +} + +static void +print_usage(FILE *stream, bool print_doc) +{ + struct option_field const *option; + char const *arg_doc; + + fprintf(stream, "Usage: %s\n", program_name); + FOREACH_OPTION(options, option, AVAILABILITY_GETOPT) { + fprintf(stream, "\t["); + fprintf(stream, "--%s", option->name); + + if (option->arg_doc != NULL) + arg_doc = option->arg_doc; + else if (option->type->arg_doc != NULL) + arg_doc = option->type->arg_doc; + else + arg_doc = NULL; + + switch (option->type->has_arg) { + case no_argument: + break; + case optional_argument: + case required_argument: + if (arg_doc != NULL) + fprintf(stream, "=%s", arg_doc); + break; + } + + fprintf(stream, "]\n"); + + if (print_doc) + fprintf(stream, "\t (%s)\n", option->doc); + } +} + + +static int +handle_opt(int opt) +{ + struct option_field const *option; + + FOREACH_OPTION(options, option, AVAILABILITY_GETOPT) { + if (option->id == opt) { + return is_rpki_config_field(option) + ? option->type->parse.argv(option, optarg, + get_rpki_config_field(option)) + : option->handler(option, optarg); + } + } + + pr_err("Unrecognized option: %d", opt); + return -ESRCH; +} + +int +handle_flags_config(int argc, char **argv) +{ + struct option *long_opts; + char *short_opts; + int opt; + int error; + + program_name = argv[0]; + error = set_default_values(); + if (error) + return error; + + long_opts = NULL; + short_opts = NULL; + error = construct_getopt_options(&long_opts, &short_opts); + if (error) + goto end; /* Error msg already printed. */ + + while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) + != -1) { + error = handle_opt(opt); + if (error) + goto end; + } + + /* + * This triggers when the user runs something like + * `rpki-validator disable-rsync` instead of + * `rpki-validator --disable-rsync`. + * This program does not have unflagged payload. + */ + if (optind < argc) { + error = pr_err("I don't know what '%s' is.", argv[optind]); + goto end; + } + + error = validate_config(); + +end: + if (error) + free_rpki_config(); + else + print_config(); + + free(long_opts); + free(short_opts); + return error; + +} + +struct option_field const * +get_option_metadatas(void) +{ + return options; +} + +char const * +config_get_server_address(void) +{ + return rpki_config.server.address; +} + +char const * +config_get_server_port(void) +{ + return rpki_config.server.port; +} + +int +config_get_server_queue(void) +{ + /* + * The range of this is 1-, so adding signedness is safe. + */ + return rpki_config.server.queue; +} + +unsigned int +config_get_vrps_check_interval(void) +{ + return rpki_config.server.vrps_check_interval; +} + +uint32_t +config_get_refresh_interval(void) +{ + return rpki_config.server.refresh_interval; +} + +uint32_t +config_get_retry_interval(void) +{ + return rpki_config.server.retry_interval; +} + +uint32_t +config_get_expire_interval(void) +{ + return rpki_config.server.expire_interval; +} + ++char const * ++config_get_slurm_location(void) ++{ ++ return rpki_config.server.slurm_location; ++} +char const * +config_get_tal(void) +{ + return rpki_config.tal; +} + +char const * +config_get_local_repository(void) +{ + return rpki_config.local_repository; +} + +enum sync_strategy +config_get_sync_strategy(void) +{ + return rpki_config.sync_strategy; +} + +bool +config_get_shuffle_tal_uris(void) +{ + return rpki_config.shuffle_tal_uris; +} + +unsigned int +config_get_max_cert_depth(void) +{ + return rpki_config.maximum_certificate_depth; +} + +bool +config_get_color_output(void) +{ + return rpki_config.log.color; +} + +enum filename_format +config_get_filename_format(void) +{ + return rpki_config.log.filename_format; +} + +char * +config_get_rsync_program(void) +{ + return rpki_config.rsync.program; +} + +struct string_array const * +config_get_rsync_args(bool is_ta) +{ + switch (rpki_config.sync_strategy) { + case SYNC_ROOT: + return &rpki_config.rsync.args.recursive; + case SYNC_ROOT_EXCEPT_TA: + return is_ta + ? &rpki_config.rsync.args.flat + : &rpki_config.rsync.args.recursive; + case SYNC_STRICT: + return &rpki_config.rsync.args.flat; + case SYNC_OFF: + break; + } + + pr_crit("Invalid sync strategy: '%u'", + rpki_config.sync_strategy); + /* + * Return something usable anyway; don't want to check NULL. + * This is supposed to be unreachable code anyway. + */ + return &rpki_config.rsync.args.recursive; +} + +void +free_rpki_config(void) +{ + struct option_field const *option; + + FOREACH_OPTION(options, option, 0xFFFF) + if (is_rpki_config_field(option) && option->type->free != NULL) + option->type->free(get_rpki_config_field(option)); +} diff --cc src/config.h index 1eca1e09,00000000..8730ba15 mode 100644,000000..100644 --- a/src/config.h +++ b/src/config.h @@@ -1,39 -1,0 +1,40 @@@ +#ifndef SRC_CONFIG_H_ +#define SRC_CONFIG_H_ + +#include +#include + +#include "config/filename_format.h" +#include "config/sync_strategy.h" +#include "config/string_array.h" +#include "config/types.h" + +/* Init/destroy */ +int handle_flags_config(int , char **); +void free_rpki_config(void); + +/* Getters */ +char const *config_get_server_address(void); +char const *config_get_server_port(void); +int config_get_server_queue(void); +unsigned int config_get_vrps_check_interval(void); +uint32_t config_get_refresh_interval(void); +uint32_t config_get_retry_interval(void); +uint32_t config_get_expire_interval(void); ++char const *config_get_slurm_location(void); + +char const *config_get_tal(void); +char const *config_get_local_repository(void); +enum sync_strategy config_get_sync_strategy(void); +bool config_get_shuffle_tal_uris(void); +unsigned int config_get_max_cert_depth(void); +bool config_get_color_output(void); +enum filename_format config_get_filename_format(void); +char *config_get_rsync_program(void); +struct string_array const *config_get_rsync_args(bool); + +/* Needed public by the JSON module */ +void *get_rpki_config_field(struct option_field const *); +struct option_field const *get_option_metadatas(void); + +#endif /* SRC_CONFIG_H_ */ diff --cc src/crypto/base64.c index efb5ecc6,0ce85f23..f5969880 --- a/src/crypto/base64.c +++ b/src/crypto/base64.c @@@ -2,7 -2,18 +2,10 @@@ #include #include + #include + #include + #include -/* - * TODO This is a copy of fort-validator/src/crypto/base64.c with a few - * modifications: - * - At 'base64_decode': new parameter 'has_nl' to indicate if the encoded - * string has newlines in it. - * - New function 'base64url_decode'. - */ - /** * Converts error from libcrypto representation to this project's * representation. @@@ -32,7 -44,8 +36,8 @@@ error_ul2i(unsigned long error * register a libcrypto stack error. */ int - base64_decode(BIO *in, unsigned char *out, size_t out_len, size_t *out_written) -base64_decode(BIO *in, unsigned char **out, bool has_nl, size_t out_len, ++base64_decode(BIO *in, unsigned char *out, bool has_nl, size_t out_len, + size_t *out_written) { BIO *b64; size_t offset = 0; @@@ -108,3 -124,88 +116,88 @@@ end return error ? error_ul2i(error) : 0; } + + /* + * 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. + */ + int + base64url_decode(char const *str_encoded, unsigned char **result, + size_t *result_len) + { + BIO *encoded; /* base64 encoded. */ + char *str_copy; + size_t encoded_len, alloc_size, dec_len; + int error, pad, i; + + /* + * 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 = malloc(encoded_len + pad + 1); + if (str_copy == NULL) + return -ENOMEM; + /* 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 */ + encoded = BIO_new_mem_buf(str_copy, -1); + if (encoded == NULL) { + warnx("BIO_new() returned NULL"); + error = -EINVAL; + goto free_copy; + } + + alloc_size = EVP_DECODE_LENGTH(strlen(str_copy)); + *result = malloc(alloc_size + 1); + if (*result == NULL) { + error = -ENOMEM; + goto free_enc; + } + memset(*result, 0, alloc_size); + (*result)[alloc_size] = '\0'; + - error = base64_decode(encoded, result, false, alloc_size, &dec_len); ++ error = base64_decode(encoded, *result, false, alloc_size, &dec_len); + if (error) + goto free_all; + + if (dec_len == 0) { + warnx("'%s' couldn't be decoded", str_encoded); + error = -EINVAL; + goto free_all; + } + *result_len = dec_len; + + free(str_copy); + BIO_free(encoded); + return 0; + free_all: + free(*result); + free_enc: + BIO_free(encoded); + free_copy: + free(str_copy); + return error; + } diff --cc src/crypto/base64.h index c56b3e6f,fb8210d7..a3d83ddd --- a/src/crypto/base64.h +++ b/src/crypto/base64.h @@@ -4,6 -5,7 +5,7 @@@ #include #include - int base64_decode(BIO *, unsigned char *, size_t, size_t *); -int base64_decode(BIO *, unsigned char **, bool, size_t, size_t *); ++int base64_decode(BIO *, unsigned char *, bool, size_t, size_t *); + int base64url_decode(char const *, unsigned char **, size_t *); #endif /* SRC_BASE64_H_ */ diff --cc src/main.c index 909eead7,85d9d1bf..cc673259 --- a/src/main.c +++ b/src/main.c @@@ -1,67 -1,86 +1,74 @@@ -#include -#include -#include -#include - -#include "rtr/rtr.h" -#include "slurm_loader.h" #include "clients.h" -#include "configuration.h" -#include "csv.h" -#include "vrps.h" +#include "config.h" +#include "console_handler.h" +#include "debug.h" +#include "extension.h" +#include "nid.h" ++#include "slurm_loader.h" +#include "thread_var.h" +#include "rsync/rsync.h" +#include "rtr/rtr.h" +#include "rtr/db/vrps.h" -/* - * This program is an RTR server. - * - * RTR ("RPKI-to-Router") is a protocol (defined in RFCs 6810 and 8210) that - * reports the work of an RPKI validator (cryptographcally-verified - * attestations that define the ASN that owns a given routing prefix). It is - * normally served to routers who wish to verify BGP claims. - */ -int -main(int argc, char *argv[]) +static int +start_rtr_server(void) { - int err; - char *json_file = NULL; - int c; + int error; - while ((c = getopt(argc, argv, "f:")) != -1) { - switch (c) { - case 'f': - json_file = optarg; - break; - case '?': - fprintf(stdout, "usage: %s -f \n", argv[0]); - return 0; - } - } - - if (json_file == NULL) { - fprintf(stderr, "Missing flag '-f '\n"); - return -EINVAL; - } - - err = config_init(json_file); - if (err) { - /* - * TODO Special scenario, if the VRPs location doesn't exists - * just send a warning (logged by the config_init function). - * - * This should be fixed later. - */ - err = (err == -ENOENT ? 0 : err); - goto end1; - } - - err = deltas_db_init(); - if (err) + error = vrps_init(); + if (error) goto end1; - err = clients_db_init(); - if (err) + error = clients_db_init(); + if (error) goto end2; - err = csv_parse_vrps_file(); - if (err) ++ error = slurm_load(); ++ if (error) + goto end3; + - err = slurm_load(); - if (err) - goto end4; - - err = rtr_listen(); - if (err) - goto end4; + error = rtr_listen(); + rtr_cleanup(); /* TODO shouldn't this only happen on !error? */ - rtr_cleanup(); -end4: - slurm_cleanup(); + end3: clients_db_destroy(); -end2: - deltas_db_destroy(); -end1: - config_cleanup(); - return err; ++ slurm_cleanup(); +end2: vrps_destroy(); +end1: return error; +} + +int +main(int argc, char **argv) +{ + int error; + + print_stack_trace_on_segfault(); + + error = thvar_init(); + if (error) + return error; + + error = handle_flags_config(argc, argv); + if (error) + return error; + + error = rsync_init(); + if (error) + goto revert_config; + error = nid_init(); + if (error) + goto revert_rsync; + error = extension_init(); + if (error) + goto revert_rsync; + + error = (config_get_server_address() != NULL) + ? start_rtr_server() + : validate_into_console(); + +revert_rsync: + rsync_destroy(); +revert_config: + free_rpki_config(); + return error; } diff --cc src/object/tal.c index 16a69aa8,00000000..d9557e0b mode 100644,000000..100644 --- a/src/object/tal.c +++ b/src/object/tal.c @@@ -1,363 -1,0 +1,364 @@@ +#define _GNU_SOURCE + +#include "tal.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "config.h" +#include "line_file.h" +#include "log.h" +#include "random.h" +#include "state.h" +#include "thread_var.h" +#include "crypto/base64.h" +#include "object/certificate.h" +#include "rsync/rsync.h" + +struct uris { + char **array; /* This is an array of string pointers. */ + unsigned int count; + unsigned int size; +}; + +struct tal { + char const *file_name; + struct uris uris; + unsigned char *spki; /* Decoded; not base64. */ + size_t spki_len; +}; + +static int +uris_init(struct uris *uris) +{ + uris->count = 0; + uris->size = 4; /* Most TALs only define one. */ + uris->array = malloc(uris->size * sizeof(char *)); + return (uris->array != NULL) ? 0 : -ENOMEM; +} + +static void +uris_destroy(struct uris *uris) +{ + unsigned int i; + for (i = 0; i < uris->count; i++) + free(uris->array[i]); + free(uris->array); +} + +static int +uris_add(struct uris *uris, char *uri) +{ + char **tmp; + + if (uris->count + 1 >= uris->size) { + uris->size *= 2; + tmp = realloc(uris->array, uris->size * sizeof(char *)); + if (tmp == NULL) + return pr_enomem(); + uris->array = tmp; + } + + uris->array[uris->count++] = uri; + return 0; +} + +static int +read_uris(struct line_file *lfile, struct uris *uris) +{ + char *uri; + int error; + + error = lfile_read(lfile, &uri); + if (error) + return error; + + if (uri == NULL) + return pr_err("TAL file is empty."); + if (strcmp(uri, "") == 0) { + free(uri); + return pr_err("There's no URI in the first line of the TAL."); + } + + error = uris_add(uris, uri); + if (error) + return error; + + do { + error = lfile_read(lfile, &uri); + if (error) + return error; + + if (uri == NULL) + return pr_err("TAL file ended prematurely. (Expected URI list, blank line and public key.)"); + if (strcmp(uri, "") == 0) { + free(uri); + return 0; /* Happy path */ + } + + error = uris_add(uris, uri); + if (error) + return error; + } while (true); +} + +/* + * Will usually allocate slightly more because of the newlines, but I'm fine + * with it. + */ +static size_t +get_spki_alloc_size(struct line_file *lfile) +{ + struct stat st; + size_t result; + + stat(lfile_name(lfile), &st); + result = st.st_size - lfile_offset(lfile); + + return EVP_DECODE_LENGTH(result); +} + +static int +read_spki(struct line_file *lfile, struct tal *tal) +{ + BIO *encoded; /* base64 encoded. */ + size_t alloc_size; + int error; + + alloc_size = get_spki_alloc_size(lfile); + tal->spki = malloc(alloc_size); + if (tal->spki == NULL) + return -ENOMEM; + + encoded = BIO_new_fp(lfile_fd(lfile), BIO_NOCLOSE); + if (encoded == NULL) { + free(tal->spki); + return crypto_err("BIO_new_fp() returned NULL"); + } + - error = base64_decode(encoded, tal->spki, alloc_size, &tal->spki_len); ++ error = base64_decode(encoded, tal->spki, true, alloc_size, ++ &tal->spki_len); + if (error) + free(tal->spki); + + BIO_free(encoded); + return error; +} + +/** + * @file_name is expected to outlive @result. + */ +int +tal_load(char const *file_name, struct tal **result) +{ + struct line_file *lfile; + struct tal *tal; + int error; + + error = lfile_open(file_name, &lfile); + if (error) { + pr_errno(error, "Error opening file '%s'", file_name); + goto fail4; + } + + tal = malloc(sizeof(struct tal)); + if (tal == NULL) { + error = -ENOMEM; + goto fail3; + } + + tal->file_name = file_name; + + error = uris_init(&tal->uris); + if (error) + goto fail2; + + error = read_uris(lfile, &tal->uris); + if (error) + goto fail1; + + error = read_spki(lfile, tal); + if (error) + goto fail1; + + lfile_close(lfile); + *result = tal; + return 0; + +fail1: + uris_destroy(&tal->uris); +fail2: + free(tal); +fail3: + lfile_close(lfile); +fail4: + return error; +} + +void tal_destroy(struct tal *tal) +{ + if (tal == NULL) + return; + + uris_destroy(&tal->uris); + free(tal->spki); + free(tal); +} + +int +foreach_uri(struct tal *tal, foreach_uri_cb cb, void *arg) +{ + struct rpki_uri uri; + unsigned int i; + int error; + + for (i = 0; i < tal->uris.count; i++) { + error = uri_init_str(&uri, tal->uris.array[i], + strlen(tal->uris.array[i])); + if (error == ENOTRSYNC) { + /* Log level should probably be INFO. */ + pr_debug("TAL has non-RSYNC URI; ignoring."); + continue; + } + if (error) + return error; + + error = cb(tal, &uri, arg); + uri_cleanup(&uri); + if (error) + return error; + } + + return 0; +} + +void +tal_shuffle_uris(struct tal *tal) +{ + char **array = tal->uris.array; + unsigned int count = tal->uris.count; + char *tmp; + long random_index; + unsigned int i; + + random_init(); + + for (i = 0; i < count; i++) { + tmp = array[i]; + random_index = random_at_most(count - 1 - i) + i; + array[i] = array[random_index]; + array[random_index] = tmp; + } +} + +char const * +tal_get_file_name(struct tal *tal) +{ + return tal->file_name; +} + +void +tal_get_spki(struct tal *tal, unsigned char const **buffer, size_t *len) +{ + *buffer = tal->spki; + *len = tal->spki_len; +} + +/** + * Performs the whole validation walkthrough on uri @uri, which is assumed to + * have been extracted from a TAL. + */ +static int +handle_tal_uri(struct tal *tal, struct rpki_uri const *uri, void *arg) +{ + /* + * Because of the way the foreach iterates, this function must return + * + * - 0 on soft errors. + * - `> 0` on URI handled successfully. + * - `< 0` on hard errors. + * + * A "soft error" is "the connection to the preferred URI fails, or the + * retrieved CA certificate public key does not match the TAL public + * key." (RFC 7730) + * + * A "hard error" is any other error. + */ + + struct validation *state; + int error; + + error = download_files(uri, true); + if (error) { + return pr_warn("TAL URI '%s' could not be RSYNC'd.", + uri->global); + } + + error = validation_prepare(&state, tal, arg); + if (error) + return ENSURE_NEGATIVE(error); + + error = vhandler_reset(arg); + if (error) + return ENSURE_NEGATIVE(error); + + pr_debug_add("TAL URI '%s' {", uri_get_printable(uri)); + + if (!uri_is_certificate(uri)) { + pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')", + uri_get_printable(uri)); + error = -EINVAL; + goto end; + } + + error = certificate_traverse(NULL, uri, NULL, true); + if (error) { + switch (validation_pubkey_state(state)) { + case PKS_INVALID: + error = 0; + break; + case PKS_VALID: + case PKS_UNTESTED: + error = ENSURE_NEGATIVE(error); + break; + } + } else { + error = 1; + } + +end: validation_destroy(state); + pr_debug_rm("}"); + return error; +} + +int +perform_standalone_validation(struct validation_handler *handler) +{ + struct tal *tal; + int error; + + fnstack_init(); + fnstack_push(config_get_tal()); + + error = tal_load(config_get_tal(), &tal); + if (error) + goto end; + + if (config_get_shuffle_tal_uris()) + tal_shuffle_uris(tal); + error = foreach_uri(tal, handle_tal_uri, handler); + if (error > 0) + error = 0; + else if (error == 0) + error = pr_err("None of the URIs of the TAL yielded a successful traversal."); + +end: tal_destroy(tal); + fnstack_pop(); + fnstack_cleanup(); + return error; +} diff --cc src/rtr/pdu.h index a0f1252d,8e616463..c34cc4c4 --- a/src/rtr/pdu.h +++ b/src/rtr/pdu.h @@@ -78,11 -79,20 +79,20 @@@ struct cache_reset_pdu struct pdu_header header; }; + struct router_key_pdu { + struct pdu_header header; + unsigned char *ski; + size_t ski_len; + u_int32_t asn; + unsigned char *spki; + size_t spki_len; + }; + struct error_report_pdu { struct pdu_header header; - u_int32_t error_pdu_length; + uint32_t error_pdu_length; void *erroneous_pdu; - u_int32_t error_message_length; + uint32_t error_message_length; rtr_char *error_message; }; diff --cc src/rtr/pdu_sender.c index 87baf586,b700545e..7f8571f1 --- a/src/rtr/pdu_sender.c +++ b/src/rtr/pdu_sender.c @@@ -68,7 -68,14 +68,17 @@@ length_end_of_data_pdu(struct end_of_da return len; } -static u_int32_t -length_router_key_pdu(struct router_key_pdu *pdu) -{ - return HEADER_LENGTH + - pdu->ski_len + sizeof(pdu->asn) + pdu->spki_len; -} ++/* TODO Include Router Key PDU serials */ ++/** ++ * static uint32_t ++ * length_router_key_pdu(struct router_key_pdu *pdu) ++ * { ++ * return HEADER_LENGTH + ++ * pdu->ski_len + sizeof(pdu->asn) + pdu->spki_len; ++ * } ++ */ + -static u_int32_t +static uint32_t length_error_report_pdu(struct error_report_pdu *pdu) { return HEADER_LENGTH + @@@ -247,11 -377,11 +297,11 @@@ send_end_of_data_pdu(struct sender_comm } int -send_error_report_pdu(int fd, u_int8_t version, u_int16_t code, -struct pdu_header *err_pdu_header, char *message) +send_error_report_pdu(int fd, uint8_t version, uint16_t code, +struct pdu_header *err_pdu_header, char const *message) { struct error_report_pdu pdu; - char data[BUFFER_SIZE]; + unsigned char data[BUFFER_SIZE]; size_t len; set_header_values(&pdu.header, version, PDU_TYPE_ERROR_REPORT, diff --cc src/rtr/pdu_serializer.c index f3ef6ecb,5de4ba82..fbb74b20 --- a/src/rtr/pdu_serializer.c +++ b/src/rtr/pdu_serializer.c @@@ -18,9 -18,10 +18,10 @@@ free_buffer(struct data_buffer *buffer } static size_t - serialize_pdu_header(struct pdu_header *header, uint16_t union_value, char *buf) -serialize_pdu_header(struct pdu_header *header, u_int16_t union_value, ++serialize_pdu_header(struct pdu_header *header, uint16_t union_value, + unsigned char *buf) { - char *ptr; + unsigned char *ptr; ptr = buf; ptr = write_int8(ptr, header->protocol_version); @@@ -32,12 -33,12 +33,12 @@@ } size_t - serialize_serial_notify_pdu(struct serial_notify_pdu *pdu, char *buf) + serialize_serial_notify_pdu(struct serial_notify_pdu *pdu, unsigned char *buf) { size_t head_size; - char *ptr; + unsigned char *ptr; - head_size = serialize_pdu_header(&pdu->header, pdu->header.session_id, + head_size = serialize_pdu_header(&pdu->header, pdu->header.m.session_id, buf); ptr = buf + head_size; @@@ -47,20 -48,20 +48,21 @@@ } size_t - serialize_cache_response_pdu(struct cache_response_pdu *pdu, char *buf) + serialize_cache_response_pdu(struct cache_response_pdu *pdu, + unsigned char *buf) { /* No payload to serialize */ - return serialize_pdu_header(&pdu->header, pdu->header.session_id, buf); + return serialize_pdu_header(&pdu->header, pdu->header.m.session_id, + buf); } size_t - serialize_ipv4_prefix_pdu(struct ipv4_prefix_pdu *pdu, char *buf) + serialize_ipv4_prefix_pdu(struct ipv4_prefix_pdu *pdu, unsigned char *buf) { size_t head_size; - char *ptr; + unsigned char *ptr; - head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved, + head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved, buf); ptr = buf + head_size; @@@ -75,12 -76,12 +77,12 @@@ } size_t - serialize_ipv6_prefix_pdu(struct ipv6_prefix_pdu *pdu, char *buf) + serialize_ipv6_prefix_pdu(struct ipv6_prefix_pdu *pdu, unsigned char *buf) { size_t head_size; - char *ptr; + unsigned char *ptr; - head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved, + head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved, buf); ptr = buf + head_size; @@@ -95,12 -96,12 +97,12 @@@ } size_t - serialize_end_of_data_pdu(struct end_of_data_pdu *pdu, char *buf) + serialize_end_of_data_pdu(struct end_of_data_pdu *pdu, unsigned char *buf) { size_t head_size; - char *ptr; + unsigned char *ptr; - head_size = serialize_pdu_header(&pdu->header, pdu->header.session_id, + head_size = serialize_pdu_header(&pdu->header, pdu->header.m.session_id, buf); ptr = buf + head_size; @@@ -115,21 -116,51 +117,51 @@@ } size_t - serialize_cache_reset_pdu(struct cache_reset_pdu *pdu, char *buf) + serialize_cache_reset_pdu(struct cache_reset_pdu *pdu, unsigned char *buf) { /* No payload to serialize */ - return serialize_pdu_header(&pdu->header, pdu->header.reserved, buf); + return serialize_pdu_header(&pdu->header, pdu->header.m.reserved, buf); } + /* + * Don't forget to use 'header->reserved' to set flags + */ size_t - serialize_error_report_pdu(struct error_report_pdu *pdu, char *buf) + serialize_router_key_pdu(struct router_key_pdu *pdu, unsigned char *buf) + { + size_t head_size; + unsigned char *ptr; + int i; + + if (pdu->header.protocol_version == RTR_V0) + return 0; + - head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved, ++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved, + buf); + + ptr = buf + head_size; + + for (i = 0; i < pdu->ski_len; i++) + ptr = write_int8(ptr, pdu->ski[i]); + + ptr = write_int32(ptr, pdu->asn); + + for (i = 0; i < pdu->spki_len; i++) + ptr = write_int8(ptr, pdu->spki[i]); + + return ptr - buf; + } + + size_t + serialize_error_report_pdu(struct error_report_pdu *pdu, unsigned char *buf) { struct pdu_header *err_pdu_header; size_t head_size; - char *ptr, *tmp_ptr; + unsigned char *ptr; + char *tmp_ptr; int i; - head_size = serialize_pdu_header(&pdu->header, pdu->header.error_code, + head_size = serialize_pdu_header(&pdu->header, pdu->header.m.error_code, buf); ptr = buf + head_size; diff --cc src/rtr/pdu_serializer.h index 9c23dce2,2c490be5..4889f7f2 --- a/src/rtr/pdu_serializer.h +++ b/src/rtr/pdu_serializer.h @@@ -8,18 -8,23 +8,21 @@@ struct data_buffer { size_t len; size_t capacity; - char *data; + unsigned char *data; }; -__BEGIN_DECLS void init_buffer(struct data_buffer *); void free_buffer(struct data_buffer *); - size_t serialize_serial_notify_pdu(struct serial_notify_pdu *, char *); - size_t serialize_cache_response_pdu(struct cache_response_pdu *, char *); - size_t serialize_ipv4_prefix_pdu(struct ipv4_prefix_pdu *, char *); - size_t serialize_ipv6_prefix_pdu(struct ipv6_prefix_pdu *, char *); - size_t serialize_end_of_data_pdu(struct end_of_data_pdu *, char *); - size_t serialize_cache_reset_pdu(struct cache_reset_pdu *, char *); - size_t serialize_error_report_pdu(struct error_report_pdu *, char *); + size_t serialize_serial_notify_pdu(struct serial_notify_pdu *, + unsigned char *); + size_t serialize_cache_response_pdu(struct cache_response_pdu *, + unsigned char *); + size_t serialize_ipv4_prefix_pdu(struct ipv4_prefix_pdu *, unsigned char *); + size_t serialize_ipv6_prefix_pdu(struct ipv6_prefix_pdu *, unsigned char *); + size_t serialize_end_of_data_pdu(struct end_of_data_pdu *, unsigned char *); + size_t serialize_cache_reset_pdu(struct cache_reset_pdu *, unsigned char *); + size_t serialize_router_key_pdu(struct router_key_pdu *, unsigned char *); + size_t serialize_error_report_pdu(struct error_report_pdu *, unsigned char *); -__END_DECLS #endif /* SRC_RTR_PDU_SERIALIZER_H_ */ diff --cc src/rtr/primitive_writer.c index f117b32d,bb1a9be4..599d267d --- a/src/rtr/primitive_writer.c +++ b/src/rtr/primitive_writer.c @@@ -1,15 -1,15 +1,15 @@@ #include "primitive_writer.h" - char * - write_int8(char *buf, uint8_t value) + unsigned char * -write_int8(unsigned char *buf, u_int8_t value) ++write_int8(unsigned char *buf, uint8_t value) { buf[0] = value; return buf + 1; } /** Big Endian. */ - char * - write_int16(char *buf, uint16_t value) + unsigned char * -write_int16(unsigned char *buf, u_int16_t value) ++write_int16(unsigned char *buf, uint16_t value) { buf[0] = value >> 8; buf[1] = value; @@@ -17,8 -17,8 +17,8 @@@ } /** Big Endian. */ - char * - write_int32(char *buf, uint32_t value) + unsigned char * -write_int32(unsigned char *buf, u_int32_t value) ++write_int32(unsigned char *buf, uint32_t value) { buf[0] = value >> 24; buf[1] = value >> 16; diff --cc src/rtr/primitive_writer.h index 8a34ce7d,7bed5a71..badd1019 --- a/src/rtr/primitive_writer.h +++ b/src/rtr/primitive_writer.h @@@ -3,10 -3,12 +3,10 @@@ #include - char *write_int8(char *, uint8_t); - char *write_int16(char *, uint16_t); - char *write_int32(char *, uint32_t); - char *write_in_addr(char *, struct in_addr); - char *write_in6_addr(char *, struct in6_addr); -__BEGIN_DECLS -unsigned char *write_int8(unsigned char *, u_int8_t); -unsigned char *write_int16(unsigned char *, u_int16_t); -unsigned char *write_int32(unsigned char *, u_int32_t); ++unsigned char *write_int8(unsigned char *, uint8_t); ++unsigned char *write_int16(unsigned char *, uint16_t); ++unsigned char *write_int32(unsigned char *, uint32_t); + unsigned char *write_in_addr(unsigned char *, struct in_addr); + unsigned char *write_in6_addr(unsigned char *, struct in6_addr); -__END_DECLS #endif /* RTR_PRIMITIVE_WRITER_H_ */ diff --cc src/slurm_db.c index 00000000,1279732c..ea7749a2 mode 000000,100644..100644 --- a/src/slurm_db.c +++ b/src/slurm_db.c @@@ -1,0 -1,247 +1,248 @@@ + #include "slurm_db.h" + + #include + #include -#include "array_list.h" ++ ++#include "data_structure/array_list.h" + + ARRAY_LIST(al_filter_prefix, struct slurm_prefix) + ARRAY_LIST(al_assertion_prefix, struct slurm_prefix) + ARRAY_LIST(al_filter_bgpsec, struct slurm_bgpsec) + ARRAY_LIST(al_assertion_bgpsec, struct slurm_bgpsec) + + struct arraylist_db { + struct al_filter_prefix filter_pfx_al; + struct al_assertion_prefix assertion_pfx_al; + struct al_filter_bgpsec filter_bgps_al; + struct al_assertion_bgpsec assertion_bgps_al; + } array_lists_db; + + #define LOCATE_FUNCS(name, type, array_list, equal_cb, filter) \ + static type * \ + name##_locate(array_list *base, type *obj) \ + { \ + type *cursor; \ + \ + ARRAYLIST_FOREACH(base, cursor) \ + if (equal_cb(cursor, obj, filter)) \ + return cursor; \ + \ + return NULL; \ + } \ + \ + static bool \ + name##_exists(array_list *base, type *obj) \ + { \ + return name##_locate(base, obj) != NULL; \ + } + + int + slurm_db_init(void) + { + al_filter_prefix_init(&array_lists_db.filter_pfx_al); + al_assertion_prefix_init(&array_lists_db.assertion_pfx_al); + al_filter_bgpsec_init(&array_lists_db.filter_bgps_al); + al_assertion_bgpsec_init(&array_lists_db.assertion_bgps_al); + + return 0; + } + + static bool + prefix_filtered_by(struct slurm_prefix *prefix, struct slurm_prefix *filter) + { + /* Both have ASN */ + if ((prefix->data_flag & SLURM_COM_FLAG_ASN) > 0 && + (filter->data_flag & SLURM_COM_FLAG_ASN) > 0) + return prefix->asn == filter->asn; + + /* Both have a prefix of the same type */ + if ((prefix->data_flag & SLURM_PFX_FLAG_PREFIX) > 0 && + (filter->data_flag & SLURM_PFX_FLAG_PREFIX) > 0 && + prefix->addr_fam == filter->addr_fam && + prefix->prefix_length == filter->prefix_length) + return ((prefix->addr_fam == AF_INET && + prefix->ipv4_prefix.s_addr == filter->ipv4_prefix.s_addr) || + (prefix->addr_fam == AF_INET6 && + IN6_ARE_ADDR_EQUAL(prefix->ipv6_prefix.s6_addr32, + filter->ipv6_prefix.s6_addr32))); + + return false; + } + + static bool + prefix_equal(struct slurm_prefix *left, struct slurm_prefix *right, + bool filter) + { + bool equal; + + /* Ignore the comments */ + if ((left->data_flag & ~SLURM_COM_FLAG_COMMENT) != + (right->data_flag & ~SLURM_COM_FLAG_COMMENT)) + return filter && prefix_filtered_by(left, right); + + /* It has the same data, compare it */ + equal = true; + if ((left->data_flag & SLURM_COM_FLAG_ASN) > 0) + equal = equal && left->asn == right->asn; + + if ((left->data_flag & SLURM_PFX_FLAG_PREFIX) > 0) + equal = equal && left->prefix_length == right->prefix_length + && left->addr_fam == right->addr_fam + && ((left->addr_fam == AF_INET + && left->ipv4_prefix.s_addr == right->ipv4_prefix.s_addr) + || (left->addr_fam == AF_INET6 + && IN6_ARE_ADDR_EQUAL(left->ipv6_prefix.s6_addr32, + right->ipv6_prefix.s6_addr32))); + + if ((left->data_flag & SLURM_PFX_FLAG_MAX_LENGTH) > 0) + equal = equal && + ((left->data_flag & SLURM_PFX_FLAG_MAX_LENGTH) > 0) && + left->max_prefix_length == right->max_prefix_length; + + return equal; + } + + static bool + bgpsec_filtered_by(struct slurm_bgpsec *bgpsec, struct slurm_bgpsec *filter) + { + /* Both have ASN */ + if ((bgpsec->data_flag & SLURM_COM_FLAG_ASN) > 0 && + (filter->data_flag & SLURM_COM_FLAG_ASN) > 0) + return bgpsec->asn == filter->asn; + + /* Both have a SKI */ + if ((bgpsec->data_flag & SLURM_BGPS_FLAG_SKI) > 0 && + (filter->data_flag & SLURM_BGPS_FLAG_SKI) > 0 && + bgpsec->ski_len == filter->ski_len) + return memcmp(bgpsec->ski, filter->ski, bgpsec->ski_len) == 0; + + return false; + } + + static bool + bgpsec_equal(struct slurm_bgpsec *left, struct slurm_bgpsec *right, + bool filter) + { + bool equal; + + /* Ignore the comments */ + if ((left->data_flag & ~SLURM_COM_FLAG_COMMENT) != + (right->data_flag & ~SLURM_COM_FLAG_COMMENT)) + return filter && bgpsec_filtered_by(left, right); + + /* It has the same data, compare it */ + equal = true; + if ((left->data_flag & SLURM_COM_FLAG_ASN) > 0) + equal = equal && left->asn == right->asn; + + if ((left->data_flag & SLURM_BGPS_FLAG_SKI) > 0) + equal = equal && left->ski_len == right->ski_len && + memcmp(left->ski, right->ski, left->ski_len) == 0; + + if ((left->data_flag & SLURM_BGPS_FLAG_ROUTER_KEY) > 0) + equal = equal && + left->router_public_key_len == + right->router_public_key_len && + memcmp(left->router_public_key, right->router_public_key, + left->router_public_key_len) == 0; + + return equal; + } + + LOCATE_FUNCS(prefix_filter, struct slurm_prefix, struct al_filter_prefix, + prefix_equal, true) + LOCATE_FUNCS(bgpsec_filter, struct slurm_bgpsec, struct al_filter_bgpsec, + bgpsec_equal, true) + LOCATE_FUNCS(prefix_assertion, struct slurm_prefix, struct al_assertion_prefix, + prefix_equal, false) + LOCATE_FUNCS(bgpsec_assertion, struct slurm_bgpsec, struct al_assertion_bgpsec, + bgpsec_equal, false) + + /* + * Try to persist the @prefix filter, if it already exists or is covered + * by another filter, then the error -EEXIST is returned; otherwise, returns + * the result of persisting the @prefix. + */ + int + slurm_db_add_prefix_filter(struct slurm_prefix *prefix) + { + if (prefix_filter_exists(&array_lists_db.filter_pfx_al, prefix)) + return -EEXIST; + + return al_filter_prefix_add(&array_lists_db.filter_pfx_al, prefix); + } + + /* + * Try to persist the @prefix assertion, if it already exists, then the error + * -EEXIST is returned; otherwise, returns the result of persisting the + * @prefix. + */ + int + slurm_db_add_prefix_assertion(struct slurm_prefix *prefix) + { + if (prefix_assertion_exists(&array_lists_db.assertion_pfx_al, prefix)) + return -EEXIST; + + return al_assertion_prefix_add(&array_lists_db.assertion_pfx_al, + prefix); + } + + /* + * Try to persist the @bgpsec filter, if it already exists or is covered + * by another filter, then the error -EEXIST is returned; otherwise, returns + * the result of persisting the @bgpsec. + */ + int + slurm_db_add_bgpsec_filter(struct slurm_bgpsec *bgpsec) + { + if (bgpsec_filter_exists(&array_lists_db.filter_bgps_al, bgpsec)) + return -EEXIST; + + return al_filter_bgpsec_add(&array_lists_db.filter_bgps_al, bgpsec); + } + + /* + * Try to persist the @bgpsec assertion, if it already exists, then the error + * -EEXIST is returned; otherwise, returns the result of persisting the + * @bgpsec. + */ + int + slurm_db_add_bgpsec_assertion(struct slurm_bgpsec *bgpsec) + { + if (bgpsec_assertion_exists(&array_lists_db.assertion_bgps_al, bgpsec)) + return -EEXIST; + + return al_assertion_bgpsec_add(&array_lists_db.assertion_bgps_al, + bgpsec); + } + + static void + clean_slurm_prefix(struct slurm_prefix *prefix) + { + if ((prefix->data_flag & SLURM_COM_FLAG_COMMENT) > 0) + free((void *)prefix->comment); + } + + static void + clean_slurm_bgpsec(struct slurm_bgpsec *bgpsec) + { + if ((bgpsec->data_flag & SLURM_BGPS_FLAG_SKI) > 0) + free(bgpsec->ski); + if ((bgpsec->data_flag & SLURM_BGPS_FLAG_ROUTER_KEY) > 0) + free(bgpsec->router_public_key); + if ((bgpsec->data_flag & SLURM_COM_FLAG_COMMENT) > 0) + free((void *)bgpsec->comment); + } + + void + slurm_db_cleanup(void) + { + al_filter_prefix_cleanup(&array_lists_db.filter_pfx_al, + clean_slurm_prefix); + al_filter_bgpsec_cleanup(&array_lists_db.filter_bgps_al, + clean_slurm_bgpsec); + al_assertion_prefix_cleanup(&array_lists_db.assertion_pfx_al, + clean_slurm_prefix); + al_assertion_bgpsec_cleanup(&array_lists_db.assertion_bgps_al, + clean_slurm_bgpsec); + } diff --cc src/slurm_loader.c index 00000000,bae0987f..df249a5c mode 000000,100644..100644 --- a/src/slurm_loader.c +++ b/src/slurm_loader.c @@@ -1,0 -1,102 +1,102 @@@ + #include "slurm_loader.h" + + #include + #include + #include + #include + #include + -#include "configuration.h" ++#include "config.h" + #include "slurm_db.h" + #include "slurm_parser.h" + + #define SLURM_FILE_EXTENSION ".slurm" + + static int + single_slurm_load(const char *dir_name, const char *file_name) + { + char *ext, *fullpath, *tmp; + int error; + + ext = strrchr(file_name, '.'); + /* Ignore file if extension isn't the expected */ + if (ext == NULL || strcmp(ext, SLURM_FILE_EXTENSION) != 0) + return 0; + + /* Get the full file path */ + tmp = strdup(dir_name); + if (tmp == NULL) { + warn("Couldn't create temporal char for SLURM"); + return -errno; + } + tmp = realloc(tmp, strlen(tmp) + 1 + strlen(file_name) + 1); + if (tmp == NULL) { + warn("Couldn't reallocate temporal char for SLURM"); + return -errno; + } + + strcat(tmp, "/"); + strcat(tmp, file_name); + fullpath = realpath(tmp, NULL); + if (fullpath == NULL) { + warn("Error getting real path for file '%s' at dir '%s'", + dir_name, file_name); + free(tmp); + return -errno; + } + + error = slurm_parse(fullpath); + free(tmp); + free(fullpath); + return error; + } + + int + slurm_load(void) + { + DIR *dir_loc; + struct dirent *dir_ent; + char const *slurm_dir; + int error; + + /* Optional configuration */ + slurm_dir = config_get_slurm_location(); + if (slurm_dir == NULL) + return 0; + + error = slurm_db_init(); + if (error) + return error; + + dir_loc = opendir(slurm_dir); + if (dir_loc == NULL) { + warn("Couldn't open dir %s", slurm_dir); + return -errno; + } + + errno = 0; + while ((dir_ent = readdir(dir_loc)) != NULL) { + error = single_slurm_load(slurm_dir, dir_ent->d_name); + if (error) { + warnx("The error was at SLURM file %s", + dir_ent->d_name); + goto end; + } + errno = 0; + } + if (errno) { + warn("Error reading dir %s", slurm_dir); + error = -errno; + } + end: + closedir(dir_loc); + return error; + } + + void + slurm_cleanup(void) + { + /* Only if the SLURM was configured */ + if (config_get_slurm_location() != NULL) + slurm_db_cleanup(); + } diff --cc src/slurm_parser.c index 00000000,af285c37..fed75463 mode 000000,100644..100644 --- a/src/slurm_parser.c +++ b/src/slurm_parser.c @@@ -1,0 -1,763 +1,763 @@@ + #include "slurm_parser.h" + + #include + #include + #include + #include + #include + #include + + #include "crypto/base64.h" + #include "address.h" + #include "json_parser.h" + #include "slurm_db.h" + + /* JSON members */ + #define SLURM_VERSION "slurmVersion" + #define VALIDATION_OUTPUT_FILTERS "validationOutputFilters" + #define PREFIX_FILTERS "prefixFilters" + #define BGPSEC_FILTERS "bgpsecFilters" + #define LOCALLY_ADDED_ASSERTIONS "locallyAddedAssertions" + #define PREFIX_ASSERTIONS "prefixAssertions" + #define BGPSEC_ASSERTIONS "bgpsecAssertions" + + /* Prefix and BGPsec properties */ + #define PREFIX "prefix" + #define ASN "asn" + #define MAX_PREFIX_LENGTH "maxPrefixLength" + #define SKI "SKI" + #define ROUTER_PUBLIC_KEY "routerPublicKey" + #define COMMENT "comment" + + #define CHECK_REQUIRED(element, name) \ + if (element == NULL) { \ + warnx("SLURM member '%s' is required", name); \ + return -EINVAL; \ + } + + static int handle_json(json_t *); + + int + slurm_parse(char const *location) + { + json_t *json_root; + json_error_t json_error; + int error; + + json_root = json_load_file(location, JSON_REJECT_DUPLICATES, + &json_error); + if (json_root == NULL) { + warnx("SLURM JSON error on line %d, column %d: %s", + json_error.line, json_error.column, json_error.text); + return -ENOENT; + } + + error = handle_json(json_root); + + json_decref(json_root); + return error; + } + + /* + * TODO Maybe some of the parsing functions can be on a common place, since + * csv.c also does a similar parsing + */ + + static int + parse_prefix4(char *text, struct ipv4_prefix *prefixv4) + { + if (text == NULL) + return -EINVAL; - return prefix4_decode(text, prefixv4); ++ return str_to_prefix4(text, prefixv4); + } + + static int + parse_prefix6(char *text, struct ipv6_prefix *prefixv6) + { + if (text == NULL) + return -EINVAL; - return prefix6_decode(text, prefixv6); ++ return str_to_prefix6(text, prefixv6); + } + + static int -parse_prefix_length(char *text, unsigned int *value, int max_value) ++parse_prefix_length(char *text, uint8_t *value, uint8_t max_value) + { + if (text == NULL) + return -EINVAL; - return prefix_length_decode(text, value, max_value); ++ return str_to_prefix_length(text, value, max_value); + } + /* + * Any unknown members should be treated as errors, RFC8416 3.1: + * "JSON members that are not defined here MUST NOT be used in SLURM + * files. An RP MUST consider any deviations from the specifications to + * be errors." + */ + static bool + valid_members_count(json_t *object, size_t expected_size) + { + return json_object_size(object) == expected_size; + } + + static int + set_asn(json_t *object, bool is_assertion, u_int32_t *result, + u_int8_t *flag, size_t *members_loaded) + { + json_int_t int_tmp; + int error; + + error = json_get_int(object, ASN, &int_tmp); + if (error == -ENOENT) { + if (is_assertion) { + warnx("ASN is required"); + return -EINVAL; + } else + return 0; /* Optional for filters */ + } else if (error) + return error; + + /* An underflow or overflow will be considered here */ + if (int_tmp < 0 || UINT32_MAX < int_tmp) { + warnx("ASN (%lld) is out of range [0 - %u].", int_tmp, + UINT32_MAX); + return -EINVAL; + } + *flag = *flag | SLURM_COM_FLAG_ASN; + *result = (u_int32_t) int_tmp; + (*members_loaded)++; + return 0; + } + + static int + set_comment(json_t *object, char const **comment, u_int8_t *flag, + size_t *members_loaded) + { + char const *tmp; + int error; + + error = json_get_string(object, COMMENT, &tmp); + if (error && error == -ENOENT) + return 0; /* Optional member */ + else if (error) + return error; + + *comment = strdup(tmp); + *flag = *flag | SLURM_COM_FLAG_COMMENT; + (*members_loaded)++; + + return 0; + } + + static int + set_prefix(json_t *object, bool is_assertion, struct slurm_prefix *result, + size_t *members_loaded) + { + struct ipv4_prefix prefixv4; + struct ipv6_prefix prefixv6; + char const *str_prefix; + char *clone, *token; + bool isv4; + int error; + + /* First part: Prefix in string format */ + error = json_get_string(object, PREFIX, &str_prefix); + if (error && error == -ENOENT) { + if (is_assertion) { + warnx("SLURM assertion prefix is required"); + return -EINVAL; + } else + return 0; /* Optional for filters */ + } else if (error) + return error; + + clone = strdup(str_prefix); + if (clone == NULL) { + warn("Couldn't allocate string to parse prefix"); + return -errno; + } + + token = strtok(clone, "/"); + isv4 = strchr(token, ':') == NULL; + if (isv4) + error = parse_prefix4(token, &prefixv4); + else + error = parse_prefix6(token, &prefixv6); + + if (error) { + free(clone); + return error; + } + + /* Second part: Prefix length in numeric format */ + token = strtok(NULL, "/"); + error = parse_prefix_length(token, - isv4 ? &prefixv4.len : &prefixv6.len, - isv4 ? 32 : 128); ++ (isv4 ? &prefixv4.len : &prefixv6.len), ++ (isv4 ? 32 : 128)); + free(clone); + if (error) + return error; + + if (isv4) { - error = prefix4_validate(&prefixv4); ++ error = ipv4_prefix_validate(&prefixv4); + if (error) + return error; + result->addr_fam = AF_INET; + result->ipv4_prefix = prefixv4.addr; + result->prefix_length = prefixv4.len; + } else { - error = prefix6_validate(&prefixv6); ++ error = ipv6_prefix_validate(&prefixv6); + if (error) + return error; + result->addr_fam = AF_INET6; + result->ipv6_prefix = prefixv6.addr; + result->prefix_length = prefixv6.len; + } + result->data_flag |= SLURM_PFX_FLAG_PREFIX; + (*members_loaded)++; + return 0; + } + + static int + set_max_prefix_length(json_t *object, bool is_assertion, u_int8_t addr_fam, + u_int8_t *result, u_int8_t *flag, size_t *members_loaded) + { + json_int_t int_tmp; + int error; + + error = json_get_int(object, MAX_PREFIX_LENGTH, &int_tmp); + if (error == -ENOENT) + return 0; /* Optional for assertions, unsupported by filters */ + + if (error && is_assertion) + return error; + + /* Unsupported by filters */ + if (!is_assertion) { + warnx("Prefix filter can't have a max prefix length"); + return -EINVAL; + } + + /* An underflow or overflow will be considered here */ + if (int_tmp <= 0 || (addr_fam == AF_INET ? 32 : 128) < int_tmp) { + warnx("Max prefix length (%lld) is out of range [1 - %d].", + int_tmp, (addr_fam == AF_INET ? 32 : 128)); + return -EINVAL; + } + *flag = *flag | SLURM_PFX_FLAG_MAX_LENGTH; + *result = (u_int8_t) int_tmp; + (*members_loaded)++; + return 0; + + } + + int + validate_base64url_encoded(const char *encoded) + { + /* + * RFC 8416, sections 3.3.2 (SKI member), and 3.4.2 (SKI and + * routerPublicKey members): "{..} whose value is the Base64 encoding + * without trailing '=' (Section 5 of [RFC4648])" + */ + if (strrchr(encoded, '=') != NULL) { + warnx("The base64 encoded value has trailing '='"); + return -EINVAL; + } + + /* + * IMHO there's an error at RFC 8416 regarding the use of base64 + * encoding. The RFC cites "RFC 4648 section 5" to justify the + * removal of trailing pad char '=', a section that refers to base64url + * encoding. So, at the same RFC 4648 section, there's this paragraph: + * "This encoding may be referred to as "base64url". This encoding + * should not be regarded as the same as the "base64" encoding and + * should not be referred to as only "base64". Unless clarified + * otherwise, "base64" refers to the base 64 in the previous section." + * + * Well, I believe that the RFC 8416 must say something like: + * "{..} whose value is the Base64url encoding without trailing '=' + * (Section 5 of [RFC4648])" + */ + return 0; + } + + static int + set_ski(json_t *object, bool is_assertion, struct slurm_bgpsec *result, + size_t *members_loaded) + { + char const *str_encoded; + int error; + + error = json_get_string(object, SKI, &str_encoded); + if (error && error == -ENOENT) { + if (is_assertion) { + warnx("SLURM assertion %s is required", SKI); + return -EINVAL; + } else + return 0; /* Optional for filters */ + } else if (error) + return error; + + error = validate_base64url_encoded(str_encoded); + if (error) + return error; + + error = base64url_decode(str_encoded, &result->ski, &result->ski_len); + if (error) + return error; + + /* Validate that's at least 20 octects long */ + if (result->ski_len != 20) { + warnx("The decoded SKI must be 20 octets long"); + free(result->ski); + return -EINVAL; + } + + result->data_flag = result->data_flag | SLURM_BGPS_FLAG_SKI; + (*members_loaded)++; + return 0; + } + + static int + set_router_pub_key(json_t *object, bool is_assertion, + struct slurm_bgpsec *result, size_t *members_loaded) + { + char const *str_encoded; + int error; + + error = json_get_string(object, ROUTER_PUBLIC_KEY, &str_encoded); + if (error == -ENOENT && !is_assertion) + return 0; /* OK for filters */ + + /* Required by assertions */ + if (error && is_assertion) { + if (error == -ENOENT) { + warnx("SLURM assertion %s is required", ROUTER_PUBLIC_KEY); + return -EINVAL; + } + return error; + } + + /* Unsupported by filters */ + if (!is_assertion) { + warnx("BGPsec filter can't have a router public key"); + return -EINVAL; + } + + error = validate_base64url_encoded(str_encoded); + if (error) + return error; + + error = base64url_decode(str_encoded, &result->router_public_key, + &result->router_public_key_len); + if (error) + return error; + + /* + * TODO Validate that 'routerPublicKey' is: "the equivalent to the + * subjectPublicKeyInfo value of the router certificate's public key, + * as described in [RFC8208]. This is the full ASN.1 DER encoding of + * the subjectPublicKeyInfo, including the ASN.1 tag and length values + * of the subjectPublicKeyInfo SEQUENCE. + */ + /* + * TODO When the merge is done, reuse the functions at fort-validator + * + * #include + * #include "asn1/decode.h" + * struct SubjectPublicKeyInfo *router_pki; + * error = asn1_decode(result->router_public_key, + * result->router_public_key_len, &asn_DEF_SubjectPublicKeyInfo, + * (void **) &router_pki); + */ + + result->data_flag = result->data_flag | SLURM_BGPS_FLAG_ROUTER_KEY; + (*members_loaded)++; + return 0; + } + + static void + init_slurm_prefix(struct slurm_prefix *slurm_prefix) + { + slurm_prefix->data_flag = SLURM_COM_FLAG_NONE; + slurm_prefix->asn = 0; + slurm_prefix->ipv6_prefix = in6addr_any; + slurm_prefix->prefix_length = 0; + slurm_prefix->max_prefix_length = 0; + slurm_prefix->addr_fam = 0; + slurm_prefix->comment = NULL; + } + + static int + load_single_prefix(json_t *object, bool is_assertion) + { + struct slurm_prefix result; + size_t member_count; + int error; + + if (!json_is_object(object)) { + warnx("Not a valid JSON object"); + return -EINVAL; + } + + init_slurm_prefix(&result); + member_count = 0; + + error = set_asn(object, is_assertion, &result.asn, &result.data_flag, + &member_count); + if (error) + return error; + + error = set_prefix(object, is_assertion, &result, &member_count); + if (error) + return error; + + error = set_max_prefix_length(object, is_assertion, result.addr_fam, + &result.max_prefix_length, &result.data_flag, &member_count); + if (error) + return error; + + error = set_comment(object, &result.comment, &result.data_flag, + &member_count); + if (error) + return error; + + /* A single comment isn't valid */ + if (result.data_flag == SLURM_COM_FLAG_COMMENT) { + warnx("Single comments aren't valid"); + error = -EINVAL; + goto release_comment; + } + + /* A filter must have ASN and/or prefix */ + if (!is_assertion) { + if ((result.data_flag & + (SLURM_COM_FLAG_ASN | SLURM_PFX_FLAG_PREFIX)) == 0) { + warnx("Prefix filter must have an asn and/or prefix"); + error = -EINVAL; + goto release_comment; + } + + /* Validate expected members */ + if (!valid_members_count(object, member_count)) { + warnx("Prefix filter has unknown members (see RFC 8416 section 3.3.1)"); + error = -EINVAL; + goto release_comment; + } + + error = slurm_db_add_prefix_filter(&result); + if (error) + goto release_comment; + + return 0; + } + + /* + * An assertion must have ASN and prefix, the validation is done at + * set_asn and set_prefix + */ + + if ((result.data_flag & SLURM_PFX_FLAG_MAX_LENGTH) > 0) + if (result.prefix_length > result.max_prefix_length) { + warnx( + "Prefix length is greater than max prefix length"); + error = -EINVAL; + goto release_comment; + } + + /* Validate expected members */ + if (!valid_members_count(object, member_count)) { + warnx("Prefix assertion has unknown members (see RFC 8416 section 3.4.1)"); + error = -EINVAL; + goto release_comment; + } + + error = slurm_db_add_prefix_assertion(&result); + if (error) + goto release_comment; + + return 0; + + release_comment: + free((void *)result.comment); + return error; + } + + static int + load_prefix_array(json_t *array, bool is_assertion) + { + json_t *element; + int index, error; + + json_array_foreach(array, index, element) { + error = load_single_prefix(element, is_assertion); + if (error) { + if (error == -EEXIST) + warnx( + "The prefix %s element #%d, is duplicated or covered by another %s; SLURM loading will be stopped", + (is_assertion ? "assertion" : "filter"), + index + 1, + (is_assertion ? "assertion" : "filter")); + else + warnx( + "Error at prefix %s, element #%d, SLURM loading will be stopped", + (is_assertion ? "assertions" : "filters"), + index + 1); + + return error; + } + } + + return 0; + } + + static void + init_slurm_bgpsec(struct slurm_bgpsec *slurm_bgpsec) + { + slurm_bgpsec->data_flag = SLURM_COM_FLAG_NONE; + slurm_bgpsec->asn = 0; + slurm_bgpsec->ski = NULL; + slurm_bgpsec->ski_len = 0; + slurm_bgpsec->router_public_key = NULL; + slurm_bgpsec->router_public_key_len = 0; + slurm_bgpsec->comment = NULL; + } + + static int + load_single_bgpsec(json_t *object, bool is_assertion) + { + struct slurm_bgpsec result; + size_t member_count; + int error; + + if (!json_is_object(object)) { + warnx("Not a valid JSON object"); + return -EINVAL; + } + + init_slurm_bgpsec(&result); + member_count = 0; + + error = set_asn(object, is_assertion, &result.asn, &result.data_flag, + &member_count); + if (error) + return error; + + error = set_ski(object, is_assertion, &result, &member_count); + if (error) + return error; + + error = set_router_pub_key(object, is_assertion, &result, + &member_count); + if (error) + goto release_ski; + + error = set_comment(object, &result.comment, &result.data_flag, + &member_count); + if (error) + goto release_router_key; + + /* A single comment isn't valid */ + if (result.data_flag == SLURM_COM_FLAG_COMMENT) { + warnx("Single comments aren't valid"); + error = -EINVAL; + goto release_comment; + } + + /* A filter must have ASN and/or SKI */ + if (!is_assertion) { + if ((result.data_flag & + (SLURM_COM_FLAG_ASN | SLURM_BGPS_FLAG_SKI)) == 0) { + warnx("BGPsec filter must have an asn and/or SKI"); + error = -EINVAL; + goto release_comment; + } + + /* Validate expected members */ + if (!valid_members_count(object, member_count)) { + warnx("BGPsec filter has unknown members (see RFC 8416 section 3.3.2)"); + error = -EINVAL; + goto release_comment; + } + + error = slurm_db_add_bgpsec_filter(&result); + if (error) + goto release_comment; + + return 0; + } + + /* Validate expected members */ + if (!valid_members_count(object, member_count)) { + warnx("BGPsec assertion has unknown members (see RFC 8416 section 3.4.2)"); + error = -EINVAL; + goto release_comment; + } + + error = slurm_db_add_bgpsec_assertion(&result); + if (error) + goto release_comment; + + return 0; + + release_comment: + free((void *)result.comment); + release_router_key: + free(result.router_public_key); + release_ski: + free(result.ski); + return error; + } + + static int + load_bgpsec_array(json_t *array, bool is_assertion) + { + json_t *element; + int index, error; + + json_array_foreach(array, index, element) { + error = load_single_bgpsec(element, is_assertion); + if (error) { + if (error == -EEXIST) + warnx( + "The bgpsec %s element #%d, is duplicated or covered by another %s; SLURM loading will be stopped", + (is_assertion ? "assertion" : "filter"), + index + 1, + (is_assertion ? "assertion" : "filter")); + else + warnx( + "Error at bgpsec %s, element #%d, SLURM loading will be stopped", + (is_assertion ? "assertions" : "filters"), + index + 1); + + return error; + } + } + + return 0; + } + + static int + load_version(json_t *root) + { + json_int_t version; + int error; + + version = -1; + error = json_get_int(root, SLURM_VERSION, &version); + if (error) + return error; + + /* Validate data */ + if (version != 1) { + warnx("'%s' must be 1", SLURM_VERSION); + return -EINVAL; + } + + return 0; + } + + static int + load_filters(json_t *root) + { + json_t *filters, *prefix, *bgpsec; + size_t expected_members; + int error; + + filters = json_get_object(root, VALIDATION_OUTPUT_FILTERS); + CHECK_REQUIRED(filters, VALIDATION_OUTPUT_FILTERS) + + prefix = json_get_array(filters, PREFIX_FILTERS); + CHECK_REQUIRED(prefix, PREFIX_FILTERS) + + bgpsec = json_get_array(filters, BGPSEC_FILTERS); + CHECK_REQUIRED(bgpsec, BGPSEC_FILTERS) + + expected_members = 2; + if (!valid_members_count(filters, expected_members)) { + warnx( + "SLURM '%s' must contain only %lu members (RFC 8416 section 3.2)", + VALIDATION_OUTPUT_FILTERS, + expected_members); + return -EINVAL; + } + + /* Arrays loaded, now iterate */ + error = load_prefix_array(prefix, false); + if (error) + return error; + + error = load_bgpsec_array(bgpsec, false); + if (error) + return error; + + return 0; + } + + static int + load_assertions(json_t *root) + { + json_t *assertions, *prefix, *bgpsec; + size_t expected_members; + int error; + + assertions = json_get_object(root, LOCALLY_ADDED_ASSERTIONS); + CHECK_REQUIRED(assertions, LOCALLY_ADDED_ASSERTIONS) + + prefix = json_get_array(assertions, PREFIX_ASSERTIONS); + CHECK_REQUIRED(prefix, PREFIX_ASSERTIONS) + + bgpsec = json_get_array(assertions, BGPSEC_ASSERTIONS); + CHECK_REQUIRED(bgpsec, BGPSEC_ASSERTIONS) + + expected_members = 2; + if (!valid_members_count(assertions, expected_members)) { + warnx( + "SLURM '%s' must contain only %lu members (RFC 8416 section 3.2)", + LOCALLY_ADDED_ASSERTIONS, + expected_members); + return -EINVAL; + } + + error = load_prefix_array(prefix, true); + if (error) + return error; + + error = load_bgpsec_array(bgpsec, true); + if (error) + return error; + + return 0; + } + + static int + handle_json(json_t *root) + { + size_t expected_members; + int error; + + if (!json_is_object(root)) { + warnx("The root of the SLURM is not a JSON object."); + return -EINVAL; + } + + error = load_version(root); + if (error) + return error; + + error = load_filters(root); + if (error) + return error; + + error = load_assertions(root); + if (error) + return error; + + expected_members = 3; + if (!valid_members_count(root, expected_members)) { + warnx( + "SLURM root must have only %lu members (RFC 8416 section 3.2)", + expected_members); + return -EINVAL; + } + + return 0; + }