From c717043aad5bf8306a437ac0020bdfceeb8d2234 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Tue, 11 Jul 2023 17:00:46 -0600 Subject: [PATCH] Ongoing rewrite of the local cache code It's a bit smarter now. Addresses a bunch of issues at once, though it still needs several tweaks and testing: - #78: Provide a dedicated namespace for each RRDP notification, to prevent malicious RPPs from overriding files from other RPPs. - #79: RRDP session and serial are no longer cached in RAM; they're extracted from cached notification files as they are needed. This prevents all RRDP from being considered outdated during startup. - #80: rsync-strategy has been removed. - #81: The cache now retains RRDP files. The refactor has been more intrusive than intended. I've been retouching the core loop and rrdp/https code, which has yielded the following further disinfections: - #77: Refactor the HTTP code so 304 is handled as success, despite no file modifications having been made. - It seems the old code was refusing to download RPPs via RRDP when said RPP wasn't also (unrelatedly) served via rsync. This seemed to stem from an old RFC misunderstanding from the previous developer. - I've deprecated `rsync.priority` and `rrdp.priority`, mostly just to simplify the code. I haven't seen anyone using these config fields, and I think SIAs and/or randomness should be the ones to decide which protocol is preferred for a given RPP, not Fort's admin. - However, I have also decided to deprecate `shuffle_tal_uris`, because I also suspect it's completely unused, and would like to hear some complaints otherwise. - Deprecated `rsync.arguments-flat`, because non-recursive rsyncs are not needed anymore. - Since RRDP files are no longer deleted immediately after use, the `DEBUG_RRDP` compilation has lost its purpose, so I deleted it. - The code was using `HASH_ADD_STR` on strings contained outside of the node structure. This is illegal according to uthash's documentation, and might have induced some crashes in the past. --- .gitignore | 3 + deconf.sh | 4 +- src/Makefile.am | 11 +- src/abbreviations.txt | 8 +- src/cache/local_cache.c | 782 +++++++++++++++++ src/cache/local_cache.h | 20 + src/cache/tmp.c | 31 + src/cache/tmp.h | 6 + src/cert_stack.c | 3 +- src/common.c | 56 +- src/common.h | 4 +- src/config.c | 72 +- src/config.h | 3 - src/crypto/hash.c | 2 +- src/data_structure/array_list.h | 14 +- src/data_structure/path_builder.c | 280 ++++++ src/data_structure/path_builder.h | 44 + src/delete_dir_daemon.c | 293 ------- src/delete_dir_daemon.h | 8 - src/extension.c | 1 + src/file.c | 70 +- src/file.h | 19 + src/http/http.c | 284 +++--- src/http/http.h | 6 +- src/internal_pool.c | 40 - src/internal_pool.h | 10 - src/line_file.c | 1 + src/log.c | 1 + src/main.c | 21 +- src/object/certificate.c | 580 +++++-------- src/object/manifest.c | 19 +- src/object/name.c | 1 + src/object/tal.c | 111 +-- src/random.c | 38 - src/random.h | 7 - src/resource.c | 1 + src/rpp.h | 1 + src/rrdp/db/db_rrdp.c | 224 ----- src/rrdp/db/db_rrdp.h | 17 - src/rrdp/db/db_rrdp_uris.c | 241 ----- src/rrdp/db/db_rrdp_uris.h | 41 - src/rrdp/rrdp_loader.c | 323 ++----- src/rrdp/rrdp_loader.h | 4 +- src/rrdp/rrdp_objects.c | 67 +- src/rrdp/rrdp_objects.h | 45 +- src/rrdp/rrdp_parser.c | 360 +++----- src/rrdp/rrdp_parser.h | 10 +- src/rsync/rsync.c | 125 +-- src/rsync/rsync.h | 9 +- src/rtr/db/db_table.c | 34 - src/rtr/db/db_table.h | 2 - src/rtr/db/vrps.c | 88 +- src/rtr/pdu_handler.c | 3 +- src/state.c | 35 +- src/state.h | 7 +- src/thread_var.c | 1 + src/types/uri.c | 258 ++---- src/types/uri.h | 31 +- src/validation_run.c | 16 +- src/visited_uris.c | 164 ---- src/visited_uris.h | 14 - test/Makefile.am | 42 +- test/cache/local_cache_test.c | 1061 +++++++++++++++++++++++ test/data_structure/path_builder_test.c | 478 ++++++++++ test/data_structure/uthash_test.c | 78 +- test/line_file_test.c | 2 + test/mock.c | 10 +- test/mock.h | 9 +- test/rrdp_objects_test.c | 9 +- test/rsync_test.c | 96 -- test/rtr/db/rtr_db_mock.c | 2 +- test/rtr/db/vrps_test.c | 3 + test/rtr/pdu_handler_test.c | 26 +- test/tal_test.c | 73 +- test/types/uri_test.c | 71 ++ 75 files changed, 3839 insertions(+), 3095 deletions(-) create mode 100644 src/cache/local_cache.c create mode 100644 src/cache/local_cache.h create mode 100644 src/cache/tmp.c create mode 100644 src/cache/tmp.h create mode 100644 src/data_structure/path_builder.c create mode 100644 src/data_structure/path_builder.h delete mode 100644 src/delete_dir_daemon.c delete mode 100644 src/delete_dir_daemon.h delete mode 100644 src/internal_pool.c delete mode 100644 src/internal_pool.h delete mode 100644 src/random.c delete mode 100644 src/random.h delete mode 100644 src/rrdp/db/db_rrdp.c delete mode 100644 src/rrdp/db/db_rrdp.h delete mode 100644 src/rrdp/db/db_rrdp_uris.c delete mode 100644 src/rrdp/db/db_rrdp_uris.h delete mode 100644 src/visited_uris.c delete mode 100644 src/visited_uris.h create mode 100644 test/cache/local_cache_test.c create mode 100644 test/data_structure/path_builder_test.c delete mode 100644 test/rsync_test.c diff --git a/.gitignore b/.gitignore index d2f2d130..a5ff5d56 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,9 @@ config.sub *.trs test-driver +# Developer playground +sandbox/ + # Temporal files *~ tmp diff --git a/deconf.sh b/deconf.sh index cc40fc03..165a1af7 100755 --- a/deconf.sh +++ b/deconf.sh @@ -4,4 +4,6 @@ git clean -dfx \ -e .settings \ -e .metadata \ -e Debug/ \ - -e tmp + -e tmp/ \ + -e sandbox/ + diff --git a/src/Makefile.am b/src/Makefile.am index 68434f6d..d4c47bb9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,18 +14,15 @@ fort_SOURCES += cert_stack.h cert_stack.c fort_SOURCES += common.c common.h fort_SOURCES += config.h config.c fort_SOURCES += daemon.h daemon.c -fort_SOURCES += delete_dir_daemon.h delete_dir_daemon.c fort_SOURCES += extension.h extension.c fort_SOURCES += file.h file.c fort_SOURCES += init.h init.c -fort_SOURCES += internal_pool.h internal_pool.c fort_SOURCES += json_parser.c json_parser.h fort_SOURCES += line_file.h line_file.c fort_SOURCES += log.h log.c fort_SOURCES += nid.h nid.c fort_SOURCES += notify.c notify.h fort_SOURCES += output_printer.h output_printer.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 @@ -35,7 +32,6 @@ fort_SOURCES += thread_var.h thread_var.c fort_SOURCES += json_handler.h json_handler.c fort_SOURCES += validation_handler.h validation_handler.c fort_SOURCES += validation_run.h validation_run.c -fort_SOURCES += visited_uris.h visited_uris.c fort_SOURCES += asn1/content_info.h asn1/content_info.c fort_SOURCES += asn1/decode.h asn1/decode.c @@ -49,6 +45,9 @@ 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 += cache/tmp.c cache/tmp.h + fort_SOURCES += config/boolean.c config/boolean.h fort_SOURCES += config/filename_format.h config/filename_format.c fort_SOURCES += config/log_conf.h config/log_conf.c @@ -66,6 +65,7 @@ fort_SOURCES += crypto/hash.h crypto/hash.c fort_SOURCES += data_structure/array_list.h fort_SOURCES += data_structure/common.h +fort_SOURCES += data_structure/path_builder.h data_structure/path_builder.c fort_SOURCES += data_structure/uthash.h fort_SOURCES += http/http.h http/http.c @@ -91,9 +91,6 @@ fort_SOURCES += rrdp/rrdp_loader.h rrdp/rrdp_loader.c fort_SOURCES += rrdp/rrdp_objects.h rrdp/rrdp_objects.c fort_SOURCES += rrdp/rrdp_parser.h rrdp/rrdp_parser.c -fort_SOURCES += rrdp/db/db_rrdp.h rrdp/db/db_rrdp.c -fort_SOURCES += rrdp/db/db_rrdp_uris.h rrdp/db/db_rrdp_uris.c - fort_SOURCES += rsync/rsync.h rsync/rsync.c fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h diff --git a/src/abbreviations.txt b/src/abbreviations.txt index d49a33ae..4f6fe2bd 100644 --- a/src/abbreviations.txt +++ b/src/abbreviations.txt @@ -1,7 +1,5 @@ This file lists the abbreviations used through the code. (Standard C, dependency and RFC-defined abbreviations are generally excluded.) -If you find an abbreviation that is not listed here, feel free to report it as -a bug. addr: address addr4: IPv4 address @@ -10,11 +8,14 @@ be: Big Endian cert: certificate certstack: certificate stack db: database +dl: download eof: end of file err: error fd: File Descriptor (see `man 2 accept`) guri: global URI hdr: header +hh: hash (table) hook +ht: hash table id: identifier len: length max: maximum @@ -27,9 +28,10 @@ ptr: pointer refget: reference get (+1 to reference counter) refput: reference put (-1 to reference counter) rk: Router Key -rpp: Repository Publication Point +rpp: Repository Publication Point (RFC 6481) str: string tmp: temporal +ts: timestamp uint: unsigned int vrp: Validated ROA Payload (RFC 6811) vrps: Validated ROA Payloads (VRP database) diff --git a/src/cache/local_cache.c b/src/cache/local_cache.c new file mode 100644 index 00000000..80a03b85 --- /dev/null +++ b/src/cache/local_cache.c @@ -0,0 +1,782 @@ +#define _XOPEN_SOURCE 500 + +#include "cache/local_cache.h" + +#include /* opendir(), readdir(), closedir() */ +#include /* strcasecmp */ +#include /* opendir(), closedir(), stat() */ +#include /* stat() */ +#include /* STAILQ */ +#include /* stat() */ +#include +#include + +#include "alloc.h" +#include "file.h" +#include "log.h" +#include "data_structure/path_builder.h" +#include "data_structure/uthash.h" +#include "http/http.h" +#include "rsync/rsync.h" + +/* FIXME needs locking */ + +/* + * Have we ever attempted to download this directly? + * Otherwise we actually downloaded a descendant. + * + * Directly downloaded nodes need to be retained, along with their ancestors. + * If the download was successful, they should never have children (as this + * would be redundant), though their directory counterparts probably will. + */ +#define CNF_DIRECT (1 << 0) +/* Has it downloaded successfully at some point? */ +#define CNF_SUCCESS (1 << 1) +/* Has it been traversed during the current cleanup? */ +#define CNF_FOUND (1 << 2) +/* + * If enabled, node represents a file. Otherwise, node is a directory. + * Only valid on HTTPs trees; we never know what rsync downloads. + */ +#define CNF_FILE (1 << 3) + +struct cache_node { + char *basename; /* Simple file name, parents not included */ + + /* CNF_* */ + int flags; + /* + * Last successful download timestamp. + * (Only if CNF_DIRECT & CNF_SUCCESS.) + * FIXME Intended to later decide whether a file should be deleted, + * when the cache is running out of space. + */ + time_t ts_success; + /* + * Last download attempt timestamp. (Only if CNF_DIRECT.) + * Decides whether the file needs to be updated. + */ + time_t ts_attempt; + /* Last download attempt's result status. (Only if CNF_DIRECT) */ + int error; + + struct cache_node *parent; /* Simple pointer */ + struct cache_node *children; /* Hash table */ + + UT_hash_handle hh; /* Hash table hook */ +}; + +static struct cache_node *rsync; +static struct cache_node *https; + +static time_t startup_time; /* When we started the last validation */ + +static bool +is_root(struct cache_node *node) +{ + return node->parent == NULL; +} + +/* Minimizes multiple evaluation */ +static struct cache_node * +add_child(struct cache_node *parent, char const *basename) +{ + struct cache_node *child; + char *key; + size_t keylen; + + child = pzalloc(sizeof(struct cache_node)); + child->basename = pstrdup(basename); + child->parent = parent; + + key = child->basename; + keylen = strlen(key); + + HASH_ADD_KEYPTR(hh, parent->children, key, keylen, child); + + return child; +} + +static struct cache_node * +init_root(struct cache_node *root, char const *name) +{ + if (root != NULL) + return root; + + root = pzalloc(sizeof(struct cache_node)); + root->basename = pstrdup(name); + + return root; +} + +/* FIXME recursive */ +static void +delete_node(struct cache_node *node, bool force) +{ + struct cache_node *child, *tmp; + + HASH_ITER(hh, node->children, child, tmp) + delete_node(child, force); + + if (force || !is_root(node)) { + if (node->parent != NULL) + HASH_DEL(node->parent->children, node); + free(node->basename); + free(node); + } +} + +static int +json_tt_value(struct json_t const *json, time_t *result) +{ + char const *str; + struct tm tm; + time_t tmp; + + if (json == NULL) + return -1; + str = json_string_value(json); + if (str == NULL) + return -1; + str = strptime(str, "%FT%T%z", &tm); + if (str == NULL || *str != 0) + return -1; + tmp = mktime(&tm); + if (tmp == ((time_t) -1)) + return -1; + + *result = tmp; + return 0; +} + +static struct cache_node * +json2node(json_t *json, struct cache_node *parent) +{ + struct cache_node *node, *child; + char const *string; + json_t *jchild; + size_t c; + + if (json == NULL) + return NULL; + + node = pzalloc(sizeof(struct cache_node)); + + string = json_string_value(json_object_get(json, "basename")); + if (string == NULL) { + pr_op_warn("Tag 'basename' of a metadata.json's download node cannot be parsed as a string; skipping."); + goto cancel; + } + node->basename = pstrdup(string); + + jchild = json_object_get(json, "flags"); + if (!json_is_integer(jchild)) { + pr_op_warn("Tag 'flags' of metadata.json's download node '%s' cannot be parsed as an integer; skipping.", + node->basename); + goto cancel; + } + node->flags = json_integer_value(jchild); + + if (json_tt_value(json_object_get(json, "ts_success"), &node->ts_success)) { + pr_op_warn("Tag 'success' of metadata.json's download node '%s' cannot be parsed as a date; skipping.", + node->basename); + goto cancel; + } + + if (json_tt_value(json_object_get(json, "ts_attempt"), &node->ts_attempt)) { + pr_op_warn("Tag 'attempt' of metadata.json's download node '%s' cannot be parsed as a date; skipping.", + node->basename); + goto cancel; + } + + jchild = json_object_get(json, "error"); + if (!json_is_integer(jchild)) { + pr_op_warn("Tag 'error' of metadata.json's download node '%s' cannot be parsed as an integer; skipping.", + node->basename); + goto cancel; + } + node->error = json_integer_value(jchild); + + jchild = json_object_get(json, "children"); + if (jchild != NULL && !json_is_array(jchild)) { + pr_op_warn("Tag 'children' of metadata.json's download node '%s' cannot be parsed as an array; skipping.", + node->basename); + goto cancel; + } + + for (c = 0; c < json_array_size(jchild); c++) { + child = json2node(json_array_get(jchild, c), node); + if (child == NULL) + goto cancel; + HASH_ADD_KEYPTR(hh, node->children, child->basename, + strlen(child->basename), child); + } + + node->parent = parent; + pr_op_debug("Node '%s' successfully loaded from metadata.json.", + node->basename); + return node; + +cancel: + delete_node(node, true); + return NULL; +} + +static void +load_metadata_json(void) +{ + /* + * Note: Loading metadata.json is one of few things Fort can fail at + * without killing itself. It's just a cache of a cache. + */ + + struct path_builder pb; + char *filename; + json_t *root; + json_error_t jerror; + + struct cache_node *node; + size_t d; + + int error; + + path_init(&pb); + path_append(&pb, config_get_local_repository()); + path_append(&pb, "metadata.json"); + error = path_compile(&pb, &filename); + if (error) { + pr_op_err("Unable to build metadata.json's path: %s", + strerror(error)); + goto end; + } + + root = json_load_file(filename, 0, &jerror); + + free(filename); + + if (root == NULL) { + pr_op_err("Json parsing failure at metadata.json (%d:%d): %s", + jerror.line, jerror.column, jerror.text); + goto end; + } + if (json_typeof(root) != JSON_ARRAY) { + pr_op_err("The root tag of metadata.json is not an array."); + goto end; + } + + for (d = 0; d < json_array_size(root); d++) { + node = json2node(json_array_get(root, d), NULL); + if (node == NULL) + continue; + else if (strcasecmp(node->basename, "rsync") == 0) + rsync = node; + else if (strcasecmp(node->basename, "https") == 0) + https = node; + else { + pr_op_warn("Ignoring unrecognized json node '%s'.", + node->basename); + delete_node(node, true); + } + } + +end: + json_decref(root); + if (rsync == NULL) + rsync = init_root(rsync, "rsync"); + if (https == NULL) + https = init_root(https, "https"); +} + +void +cache_prepare(void) +{ + startup_time = time(NULL); + if (startup_time == ((time_t) -1)) + pr_crit("time(NULL) returned -1"); + + if (rsync == NULL) + load_metadata_json(); +} + +static int +delete_node_file(struct cache_node *node, bool is_file) +{ + struct path_builder pb; + struct cache_node *cursor; + char *path; + int error; + + path_init(&pb); + for (cursor = node; cursor != NULL; cursor = cursor->parent) + path_append(&pb, cursor->basename); + path_append(&pb, config_get_local_repository()); + path_reverse(&pb); + error = path_compile(&pb, &path); + if (error) { + pr_val_err("Cannot override '%s'; path is bogus: %s", + node->basename, strerror(error)); + return error; + } + + if (is_file) { + if (remove(path) != 0) { + error = errno; + pr_val_err("Cannot override file '%s': %s", + path, strerror(error)); + } + } else { + error = file_rm_rf(path); + pr_val_err("Cannot override directory '%s': %s", + path, strerror(error)); + } + + free(path); + return error; +} + +static bool +was_recently_downloaded(struct cache_node *node) +{ + return (node->flags & CNF_DIRECT) && (startup_time <= node->ts_attempt); +} + +static void destroy_tree(struct cache_node *); + +/* FIXME recursive */ +static void +drop_children(struct cache_node *node) +{ + struct cache_node *child, *tmp; + + HASH_ITER(hh, node->children, child, tmp) + destroy_tree(child); +} + +/** + * @changed only on HTTP. + */ +int +cache_download(struct rpki_uri *uri, bool *changed) +{ + char *luri; + char *token; + char *saveptr; + struct cache_node *node, *child; + bool recursive; + int error; + + if (changed != NULL) + *changed = false; + luri = pstrdup(uri_get_local(uri)); + token = strtok_r(luri, "/", &saveptr); + + switch (uri_get_type(uri)) { + case UT_RSYNC: + node = rsync; + recursive = true; + break; + case UT_HTTPS: + node = https; + recursive = false; + break; + default: + pr_crit("Unexpected URI type: %d", uri_get_type(uri)); + } + + while ((token = strtok_r(NULL, "/", &saveptr)) != NULL) { + if (node->flags & CNF_FILE) { + /* node used to be a file, now it's a dir. */ + delete_node_file(node, true); + node->flags = 0; + } + + HASH_FIND_STR(node->children, token, child); + + if (child == NULL) { + /* Create child */ + do { + node = add_child(node, token); + token = strtok_r(NULL, "/", &saveptr); + } while (token != NULL); + goto download; + + } else if (recursive) { + if (was_recently_downloaded(child) && !child->error) { + error = 0; + goto end; + } + + } + + node = child; + } + + if (was_recently_downloaded(node)) { + error = node->error; + goto end; + } + + if (!recursive && !(node->flags & CNF_FILE)) { + /* node used to be a dir, now it's a file. */ + delete_node_file(node, false); + } + +download: + switch (uri_get_type(uri)) { + case UT_RSYNC: + error = rsync_download(uri); + break; + case UT_HTTPS: + error = http_download(uri, changed); + break; + default: + pr_crit("Unexpected URI type: %d", uri_get_type(uri)); + } + + node->error = error; + node->flags = CNF_DIRECT; + node->ts_attempt = time(NULL); + if (node->ts_attempt == ((time_t) -1)) + pr_crit("time(NULL) returned -1"); + if (!error) { + node->flags |= CNF_SUCCESS | (recursive ? 0 : CNF_FILE); + node->ts_success = node->ts_attempt; + } + drop_children(node); + +end: + free(luri); + return error; +} + +static void +path_rm_rf(struct path_builder *pb, char const *filename) +{ + char const *path; + int error; + + error = path_peek(pb, &path); + if (error) { + pr_op_err("Path builder error code %d; cannot delete directory. (Basename is '%s')", + error, filename); + return; + } + + error = file_rm_rf(path); + if (error) + pr_op_err("Cannot delete %s: %s", path, strerror(error)); +} + +/* FIXME recursive */ +static void +cleanup_recursive(struct cache_node *node, struct path_builder *pb) +{ + char const *path; + struct stat meta; + DIR *dir; + struct dirent *file; + struct cache_node *child, *tmp; + int error; + + /* FIXME We need to recover from path too long... */ + path_append(pb, node->basename); + error = path_peek(pb, &path); + if (error) { + pr_op_err("Cannot clean up directory (basename is '%s'): %s", + node->basename, strerror(error)); + goto end; + } + + if (stat(path, &meta) != 0) { + error = errno; + if (error == ENOENT) { + /* Node exists but file doesn't: Delete node */ + delete_node(node, false); + goto end; + } + + pr_op_err("Cannot clean up '%s'; stat() returned errno %d: %s", + path, error, strerror(error)); + goto end; + } + + if (was_recently_downloaded(node) && !node->error) + goto end; /* Node is active (ie. used recently): Keep it. */ + + /* + * From now on, file exists but node is stale. + * We'll aim to delete both. + */ + + if (S_ISREG(meta.st_mode)) { + /* Both node and file exist, but inactive: Delete */ + remove(path); + delete_node(node, false); + + } else if (S_ISDIR(meta.st_mode)) { + dir = opendir(path); + if (dir == NULL) { + error = errno; + pr_op_err("Cannot clean up '%s'; S_ISDIR() but !opendir(): %s", + path, strerror(error)); + goto end; + } + + /* + * Directory exists but node is stale. + * A child might be fresh, so recurse. + */ + + FOREACH_DIR_FILE(dir, file) { + if (S_ISDOTS(file)) + continue; + + HASH_FIND_STR(node->children, file->d_name, child); + if (child != NULL) { + child->flags |= CNF_FOUND; + /* File child's node does exist: Recurse. */ + cleanup_recursive(child, pb); + } else { + /* File child's node does not exist: Delete. */ + path_append(pb, file->d_name); + path_rm_rf(pb, file->d_name); + path_pop(pb, true); + } + + } + error = errno; + closedir(dir); + if (error) { + pr_op_err("Cannot clean up directory (basename is '%s'): %s", + node->basename, strerror(error)); + goto end; + } + + HASH_ITER(hh, node->children, child, tmp) { + if (child->flags & CNF_FOUND) { + /* + * File child still exists, which means there's + * at least one active descendant. + * Clean the flag and keep the node. + */ + child->flags &= ~CNF_FOUND; + } else { + /* Node child's file does not exist: Delete. */ + delete_node(child, false); + } + } + + if (node->children == NULL && !is_root(node)) { + /* Node is inactive and we rm'd its children: Delete. */ + path_rm_rf(pb, node->basename); + delete_node(node, false); + } + + } else { + /* Outdated, not file nor directory: Delete. */ + remove(path); + delete_node(node, false); + } + +end: + path_pop(pb, true); +} + +static int +tt2json(time_t tt, json_t **result) +{ + char str[32]; + struct tm tmbuffer, *tm; + + memset(&tmbuffer, 0, sizeof(tmbuffer)); + tm = localtime_r(&tt, &tmbuffer); + if (tm == NULL) + return errno; + if (strftime(str, sizeof(str) - 1, "%FT%T%z", tm) == 0) + return ENOSPC; + + *result = json_string(str); + return 0; +} + +/* FIXME recursive */ +static json_t * +node2json(struct cache_node *node) +{ + json_t *json, *date, *children, *jchild; + struct cache_node *child, *tmp; + int error; + + json = json_object(); + if (json == NULL) { + pr_op_err("json object allocation failure."); + return NULL; + } + + if (json_object_set_new(json, "basename", json_string(node->basename))) { + pr_op_err("Cannot convert string '%s' to json; unknown cause.", + node->basename); + goto cancel; + } + + if (json_object_set_new(json, "flags", json_integer(node->flags))) { + pr_op_err("Cannot convert int '%d' to json; unknown cause.", + node->flags); + goto cancel; + } + + error = tt2json(node->ts_success, &date); + if (error) { + pr_op_err("Cannot convert timestamp %ld to json: %s", + node->ts_success, strerror(error)); + goto cancel; + } + if (json_object_set_new(json, "ts_success", date)) { + pr_op_err("Cannot convert timestamp %ld to json; unknown cause.", + node->ts_success); + goto cancel; + } + + error = tt2json(node->ts_attempt, &date); + if (error) { + pr_op_err("Cannot convert timestamp %ld to json: %s", + node->ts_attempt, strerror(error)); + goto cancel; + } + if (json_object_set_new(json, "ts_attempt", date)) { + pr_op_err("Cannot convert timestamp %ld to json; unknown cause.", + node->ts_attempt); + goto cancel; + } + + if (json_object_set_new(json, "error", json_integer(node->error))) { + pr_op_err("Cannot convert int '%d' to json; unknown cause.", + node->error); + goto cancel; + } + + if (node->children != NULL) { + children = json_array(); + if (children == NULL) { + pr_op_err("json array allocation failure."); + return NULL; + } + + if (json_object_set_new(json, "children", children)) { + pr_op_err("Cannot push children array into json node; unknown cause."); + goto cancel; + } + + HASH_ITER(hh, node->children, child, tmp) { + jchild = node2json(child); + if (jchild == NULL) + goto cancel; /* Error msg already printed */ + if (json_array_append(children, jchild)) { + pr_op_err("Cannot push child into json node; unknown cause."); + goto cancel; + } + } + } + + return json; + +cancel: + json_decref(json); + return NULL; +} + +static int +append_node(json_t *root, struct cache_node *node, char const *name) +{ + json_t *child; + + child = node2json(node); + if (child == NULL) + return -1; + if (json_array_append(root, child)) { + pr_op_err("Cannot push %s json node into json root; unknown cause.", + name); + return -1; + } + + return 0; +} + +static json_t * +build_metadata_json(void) +{ + json_t *root; + + root = json_array(); + if (root == NULL) { + pr_op_err("json root allocation failure."); + return NULL; + } + + if (append_node(root, rsync, "rsync") + || append_node(root, https, "https")) { + json_decref(root); + return NULL; + } + + return root; +} + +static void +write_metadata_json(char const *filename) +{ + struct json_t *json; + + json = build_metadata_json(); + if (json == NULL) + return; + + if (json_dump_file(json, filename, JSON_COMPACT)) + pr_op_err("Unable to write metadata.json; unknown cause."); + + json_decref(json); +} + +void +cache_cleanup(void) +{ + struct path_builder pb; + char const *json_filename; + int error; + + path_init(&pb); + path_append(&pb, config_get_local_repository()); + + cleanup_recursive(rsync, &pb); + cleanup_recursive(https, &pb); + + path_append(&pb, "metadata.json"); + error = path_peek(&pb, &json_filename); + if (error) + pr_op_err("Cannot create metadata.json: %s", strerror(error)); + else + write_metadata_json(json_filename); + + path_cancel(&pb); +} + +/* FIXME recursive */ +static void +destroy_tree(struct cache_node *node) +{ + if (node == NULL) + return; + + free(node->basename); + drop_children(node); + if (node->parent != NULL) + HASH_DEL(node->parent->children, node); + free(node); +} + +void +cache_teardown(void) +{ + destroy_tree(rsync); + destroy_tree(https); +} diff --git a/src/cache/local_cache.h b/src/cache/local_cache.h new file mode 100644 index 00000000..9bf16ef4 --- /dev/null +++ b/src/cache/local_cache.h @@ -0,0 +1,20 @@ +#ifndef SRC_CACHE_LOCAL_CACHE_H_ +#define SRC_CACHE_LOCAL_CACHE_H_ + +#include +#include "types/uri.h" + +/* Warms up cache for new validation run */ +void cache_prepare(void); + +/* Downloads @uri into the cache */ +int cache_download(struct rpki_uri *uri, bool *); + +/* Deletes old untraversed cached files, writes metadata into XML */ +/* FIXME call this */ +void cache_cleanup(void); + +/* FIXME call this */ +void cache_teardown(void); + +#endif /* SRC_CACHE_LOCAL_CACHE_H_ */ diff --git a/src/cache/tmp.c b/src/cache/tmp.c new file mode 100644 index 00000000..243de015 --- /dev/null +++ b/src/cache/tmp.c @@ -0,0 +1,31 @@ +#include "cache/tmp.h" + +#include +#include "config.h" +#include "data_structure/path_builder.h" + +static atomic_uint file_counter; + +/* + * Returns a unique temporary file name in the local cache. + * + * The file will not be automatically deleted when it is closed or the program + * terminates. + * + * The name of the function is inherited from tmpfile(3). + * + * The resulting string needs to be released. + */ +int +cache_tmpfile(char **filename) +{ + struct path_builder pb; + + path_init(&pb); + + path_append(&pb, config_get_local_repository()); + path_append(&pb, "tmp"); + path_append_uint(&pb, atomic_fetch_add(&file_counter, 1u)); + + return path_compile(&pb, filename); +} diff --git a/src/cache/tmp.h b/src/cache/tmp.h new file mode 100644 index 00000000..0b66190f --- /dev/null +++ b/src/cache/tmp.h @@ -0,0 +1,6 @@ +#ifndef SRC_CACHE_TMP_H_ +#define SRC_CACHE_TMP_H_ + +int cache_tmpfile(char **filename); + +#endif /* SRC_CACHE_TMP_H_ */ diff --git a/src/cert_stack.c b/src/cert_stack.c index 492414df..5ad5c1cd 100644 --- a/src/cert_stack.c +++ b/src/cert_stack.c @@ -297,8 +297,7 @@ x509stack_push(struct cert_stack *stack, struct rpki_uri *uri, X509 *x509, meta = pmalloc(sizeof(struct metadata_node)); - meta->uri = uri; - uri_refget(uri); + meta->uri = uri_refget(uri); serial_numbers_init(&meta->serials); error = init_resources(x509, policy, type, &meta->resources); diff --git a/src/common.c b/src/common.c index 656f40e9..0bcc7f15 100644 --- a/src/common.c +++ b/src/common.c @@ -13,6 +13,12 @@ #include "config.h" #include "log.h" +bool +str_starts_with(char const *str, char const *prefix) +{ + return strncmp(str, prefix, strlen(prefix)) == 0; +} + void panic_on_fail(int error, char const *function_name) { @@ -346,11 +352,6 @@ delete_dir_recursive_bottom_up(char const *path) size_t config_len; int error; -#ifdef DEBUG_RRDP - /* Dev will likely need this file in the next offline run. */ - return 0; -#endif - error = remove_file(path); if (error) return error; @@ -414,48 +415,3 @@ get_current_time(time_t *result) *result = now; return 0; } - -/* - * Maps an absolute @uri that begins with @uri_prefix (either 'rsync://' or - * 'https://') to a local URI. If a @workspace is set, append such location - * to the local-repository location (this workspace is used at https URIs). - * - * @result is allocated with the local URI. - * - * Returns 0 on success, otherwise an error code. - */ -char * -map_uri_to_local(char const *uri, char const *uri_prefix) -{ - char const *repository; - char *local; - size_t repository_len; - size_t uri_prefix_len; - size_t uri_len; - size_t extra_slash; - size_t offset; - - repository = config_get_local_repository(); - repository_len = strlen(repository); - uri_prefix_len = strlen(uri_prefix); - uri_len = strlen(uri); - - uri += uri_prefix_len; - uri_len -= uri_prefix_len; - extra_slash = (repository[repository_len - 1] == '/') ? 0 : 1; - - local = pmalloc(repository_len + extra_slash + uri_len + 1); - - offset = 0; - strcpy(local + offset, repository); - offset += repository_len; - if (extra_slash) { - strcpy(local + offset, "/"); - offset += extra_slash; - } - strncpy(local + offset, uri, uri_len); - offset += uri_len; - local[offset] = '\0'; - - return local; -} diff --git a/src/common.h b/src/common.h index 915f542f..7aa479d6 100644 --- a/src/common.h +++ b/src/common.h @@ -38,6 +38,8 @@ #define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0])) +bool str_starts_with(char const *, char const *); + void panic_on_fail(int, char const *); /* @@ -67,6 +69,4 @@ int delete_dir_recursive_bottom_up(char const *); int get_current_time(time_t *); -char *map_uri_to_local(char const *, char const *); - #endif /* SRC_RTR_COMMON_H_ */ diff --git a/src/config.c b/src/config.c index 54b611d0..463c3b29 100644 --- a/src/config.c +++ b/src/config.c @@ -37,11 +37,7 @@ struct rpki_config { char *tal; /** Path of our local clone of the repository */ char *local_repository; - /** - * Handle TAL URIs in random order? - * (https://tools.ietf.org/html/rfc8630#section-3, last - * paragraphs) - */ + /* Deprecated; does nothing. */ bool shuffle_tal_uris; /** * rfc6487#section-7.2, last paragraph. @@ -83,10 +79,7 @@ struct rpki_config { struct { /* Enables the protocol */ bool enabled; - /* - * Priority, this will override the order set at the CAs in - * their accessMethod extension. - */ + /* Deprecated; does nothing. */ unsigned int priority; /* Synchronization download strategy. */ char *strategy; @@ -107,12 +100,7 @@ struct rpki_config { struct { /* Enables the protocol */ bool enabled; - /* - * Priority, whenever there's an option to sync something via - * http or rsync, use this priority. When working with CAs, this - * will override the order set at the CAs in their accessMethod - * extension. - */ + /* Deprecated; does nothing. */ unsigned int priority; /* Retry conf, utilized on errors */ struct { @@ -284,7 +272,8 @@ static const struct option_field options[] = { .name = "shuffle-uris", .type = >_bool, .offset = offsetof(struct rpki_config, shuffle_tal_uris), - .doc = "Shuffle URIs in the TAL before accessing them", + .doc = "Deprecated; does nothing.", + .deprecated = true, }, { .id = 1002, .name = "maximum-certificate-depth", @@ -437,7 +426,8 @@ static const struct option_field options[] = { .name = "rsync.priority", .type = >_uint, .offset = offsetof(struct rpki_config, rsync.priority), - .doc = "Priority of execution to fetch repositories files, a higher value means higher priority", + .doc = "Deprecated; does nothing.", + .deprecated = true, .min = 0, .max = 100, }, { @@ -476,7 +466,7 @@ static const struct option_field options[] = { .name = "rsync.arguments-recursive", .type = >_string_array, .offset = offsetof(struct rpki_config, rsync.args.recursive), - .doc = "Deprecated; does nothing.", + .doc = "RSYNC program arguments", .availability = AVAILABILITY_JSON, /* Unlimited */ .max = 0, @@ -485,10 +475,11 @@ static const struct option_field options[] = { .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", + .doc = "Deprecated; does nothing.", .availability = AVAILABILITY_JSON, /* Unlimited */ .max = 0, + .deprecated = true, }, /* HTTP requests parameters */ @@ -503,7 +494,8 @@ static const struct option_field options[] = { .name = "http.priority", .type = >_uint, .offset = offsetof(struct rpki_config, http.priority), - .doc = "Priority of execution to fetch repositories files, a higher value means higher priority", + .doc = "Deprecated; does nothing.", + .deprecated = true, .min = 0, .max = 100, }, { @@ -879,16 +871,18 @@ print_config(void) static void set_default_values(void) { - static char const *recursive_rsync_args[] = { "" }; - static char const *flat_rsync_args[] = { - "--times", - "--contimeout=20", - "--timeout=15", - "--max-size=20MB", - "--dirs", - "$REMOTE", - "$LOCAL", + static char const *recursive_rsync_args[] = { + "-rtz", "--delete", + + "--contimeout=20", "--max-size=20MB", "--timeout=15", + + "--include=*/", "--include=*.cer", "--include=*.crl", + "--include=*.gbr", "--include=*.mft", "--include=*.roa", + "--exclude=*", + + "$REMOTE", "$LOCAL", }; + static char const *flat_rsync_args[] = { "" }; /* * Values that might need to be freed WILL be freed, so use heap @@ -1217,12 +1211,6 @@ config_get_local_repository(void) return rpki_config.local_repository; } -bool -config_get_shuffle_tal_uris(void) -{ - return rpki_config.shuffle_tal_uris; -} - unsigned int config_get_max_cert_depth(void) { @@ -1319,12 +1307,6 @@ config_get_rsync_enabled(void) return !rpki_config.work_offline && rpki_config.rsync.enabled; } -unsigned int -config_get_rsync_priority(void) -{ - return rpki_config.rsync.priority; -} - unsigned int config_get_rsync_retry_count(void) { @@ -1346,7 +1328,7 @@ config_get_rsync_program(void) struct string_array const * config_get_rsync_args(void) { - return &rpki_config.rsync.args.flat; + return &rpki_config.rsync.args.recursive; } bool @@ -1355,12 +1337,6 @@ config_get_http_enabled(void) return !rpki_config.work_offline && rpki_config.http.enabled; } -unsigned int -config_get_http_priority(void) -{ - return rpki_config.http.priority; -} - unsigned int config_get_http_retry_count(void) { diff --git a/src/config.h b/src/config.h index 66e34fae..806e701f 100644 --- a/src/config.h +++ b/src/config.h @@ -28,7 +28,6 @@ char const *config_get_slurm(void); char const *config_get_tal(void); char const *config_get_local_repository(void); -bool config_get_shuffle_tal_uris(void); unsigned int config_get_max_cert_depth(void); enum mode config_get_mode(void); char const *config_get_http_user_agent(void); @@ -39,13 +38,11 @@ long config_get_http_low_speed_time(void); long config_get_http_max_file_size(void); char const *config_get_http_ca_path(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); char *config_get_rsync_program(void); struct string_array const *config_get_rsync_args(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_output_roa(void); diff --git a/src/crypto/hash.c b/src/crypto/hash.c index 0537b4bc..33cc64aa 100644 --- a/src/crypto/hash.c +++ b/src/crypto/hash.c @@ -175,7 +175,7 @@ hash_buffer(char const *algorithm, { EVP_MD const *md; EVP_MD_CTX *ctx; - int error = 0; + int error; error = get_md(algorithm, &md); if (error) diff --git a/src/data_structure/array_list.h b/src/data_structure/array_list.h index 86248f3d..6a6ea5ac 100644 --- a/src/data_structure/array_list.h +++ b/src/data_structure/array_list.h @@ -45,25 +45,17 @@ modifiers void \ name##_add(struct name *list, elem_type *elem) \ { \ - elem_type *tmp; \ - \ if (list->array == NULL) { \ list->capacity = 8; \ - list->array = malloc(list->capacity \ + list->array = pmalloc(list->capacity \ * sizeof(elem_type)); \ - if (list->array == NULL) \ - enomem_panic(); \ } \ \ list->len++; \ while (list->len >= list->capacity) { \ list->capacity *= 2; \ - \ - tmp = realloc(list->array, list->capacity \ - * sizeof(elem_type)); \ - if (tmp == NULL) \ - enomem_panic(); \ - list->array = tmp; \ + list->array = prealloc(list->array, \ + list->capacity * sizeof(elem_type)); \ } \ \ list->array[list->len - 1] = *elem; \ diff --git a/src/data_structure/path_builder.c b/src/data_structure/path_builder.c new file mode 100644 index 00000000..bc49ee53 --- /dev/null +++ b/src/data_structure/path_builder.c @@ -0,0 +1,280 @@ +#include "data_structure/path_builder.h" + +#include + +#include "alloc.h" +#include "log.h" +#include "crypto/hash.h" + +#define SHA256_LEN (256 >> 3) /* 256 / 8, bits -> bytes */ + +/* These are arbitrary; feel free to change them. */ +#ifndef INITIAL_CAPACITY /* Unit tests want to override this */ +#define INITIAL_CAPACITY 128 +#endif +#define MAX_CAPACITY 4096 + +void +path_init(struct path_builder *pb) +{ + pb->string = pmalloc(INITIAL_CAPACITY); + pb->len = 0; + pb->capacity = INITIAL_CAPACITY; + pb->error = 0; +} + +/* + * Returns true on success, false on failure. + */ +static bool +path_grow(struct path_builder *pb, size_t total_len) +{ + if (total_len > MAX_CAPACITY) { + free(pb->string); + pr_val_err("Path too long: %zu > %u characters.", total_len, + MAX_CAPACITY); + pb->error = ENOSPC; + return false; + } + + do { + pb->capacity *= 2; + } while (total_len > pb->capacity); + + pb->string = prealloc(pb->string, pb->capacity); + return true; +} + +static char const * +find_slash(char const *str, size_t len) +{ + char const *wall; + + for (wall = str + len; str < wall; str++) + if (str[0] == '/') + return str; + + return str; +} + +/* + * Do NOT include the null character in @addlen. + * Assumes @addend needs no slashes. + */ +static void +add(struct path_builder *pb, char const *addend, size_t addlen) +{ + size_t total_len; + + total_len = pb->len + addlen; + if (total_len > pb->capacity && !path_grow(pb, total_len)) + return; + + memcpy(pb->string + pb->len, addend, addlen); + pb->len += addlen; +} + +void +path_append(struct path_builder *pb, char const *addend) +{ + path_append_limited(pb, addend, strlen(addend)); +} + +static void +add_slashed(struct path_builder *pb, char const *addend, size_t addlen) +{ + /* Normalize first */ + switch (addlen) { + case 1: + if (addend[0] == '.') + return; + break; + case 2: + if (addend[0] == '.' && addend[1] == '.') { + path_pop(pb, false); + return; + } + break; + } + + /* Ok, do */ + if (pb->len > 0) + add(pb, "/", 1); + add(pb, addend, addlen); +} + +/* Do NOT include the null character in @addlen. */ +void +path_append_limited(struct path_builder *pb, char const *addend, size_t addlen) +{ + char const *wall; + char const *next_slash; + + if (pb->error) + return; + + do { + for (wall = addend + addlen; addend < wall; addend++, addlen--) + if (addend[0] != '/') + break; + next_slash = find_slash(addend, addlen); + if (addend == next_slash) + return; + add_slashed(pb, addend, next_slash - addend); + addlen -= next_slash - addend; + addend = next_slash; + } while (addlen > 0); +} + +void +path_append_guri(struct path_builder *pb, struct rpki_uri *uri) +{ + char const *guri; + char const *colon; + size_t schema_len; + + if (pb->error) + return; + + guri = uri_get_global(uri); + + colon = strstr(guri, ":"); + schema_len = colon - guri; + path_append_limited(pb, guri, schema_len); + + path_append_limited(pb, colon + 3, + uri_get_global_len(uri) - schema_len - 3); +} + +void +path_append_uint(struct path_builder *pb, unsigned int num) +{ + size_t room; + int num_len; + + if (pb->error) + return; + + if (pb->len != 0 && pb->string[pb->len - 1] != '/') + add(pb, "/", 1); + + room = pb->capacity - pb->len; + num_len = snprintf(pb->string + pb->len, room, "%X", num); + if (num_len < 0) + goto bad_print; + if (num_len >= room) { + if (!path_grow(pb, pb->len + num_len + 1)) + return; + + room = pb->capacity - pb->len; + num_len = snprintf(pb->string + pb->len, room, "%X", num); + if (num_len < 0) + goto bad_print; + if (num_len >= room) + pr_crit("pb: %d %zu", num_len, room); + } + + pb->len += num_len; + return; + +bad_print: + free(pb->string); + pb->error = EIO; /* num_len is not necessarily an error code */ +} + +/* Removes the last component added. */ +void +path_pop(struct path_builder *pb, bool fatal) +{ + size_t i; + + if (pb->error) + return; + if (pb->len == 0) { + if (fatal) + pr_crit("Programming error: Attempting to pop empty path builder"); + free(pb->string); + pb->error = -pr_val_err("Path cannot '..' over the root."); + return; + } + + for (i = pb->len - 1; i >= 1; i--) { + if (pb->string[i] == '/') { + pb->len = i; + return; + } + } + + pb->len = (pb->string[0] == '/') && (pb->len > 1); +} + +static void +reverse_string(char *str, size_t len) +{ + char *b, *e; /* beginning, end */ + char tmp; + + for (b = str, e = str + len - 1; b < e; b++, e--) { + tmp = *b; + *b = *e; + *e = tmp; + } +} + +/* Turns ab/cd/ef/gh into gh/ef/cd/ab. */ +void +path_reverse(struct path_builder *pb) +{ + size_t min; + size_t max; + + if (pb->error) + return; + + reverse_string(pb->string, pb->len); + + min = 0; + for (max = 1; max < pb->len; max++) { + if (pb->string[max] == '/') { + reverse_string(&pb->string[min], max - min); + max++; + min = max; + } + } + reverse_string(&pb->string[min], pb->len - min); +} + +/* + * Returns @pb's current accumulated path. Do not free it. + * Result is a temporary pointer; it becomes junk if you call any other pb + * functions on @pb afterwards. + */ +int +path_peek(struct path_builder *pb, char const **result) +{ + add(pb, "\0", 1); + if (pb->error) + return pb->error; + + *result = pb->string; + pb->len--; + return 0; +} + +/* Should not be called more than once. */ +int +path_compile(struct path_builder *pb, char **result) +{ + add(pb, "\0", 1); + if (pb->error) + return pb->error; + + *result = pb->string; + return 0; +} + +void +path_cancel(struct path_builder *pb) +{ + free(pb->string); +} diff --git a/src/data_structure/path_builder.h b/src/data_structure/path_builder.h new file mode 100644 index 00000000..73c0e1be --- /dev/null +++ b/src/data_structure/path_builder.h @@ -0,0 +1,44 @@ +#ifndef SRC_DATA_STRUCTURE_PATH_BUILDER_H_ +#define SRC_DATA_STRUCTURE_PATH_BUILDER_H_ + +/* FIXME add support for absolute paths */ + +#include +#include +#include "types/uri.h" + +struct path_builder { + char *string; + size_t len; + size_t capacity; + int error; +}; + +void path_init(struct path_builder *); + +/* + * Note, the append()s merge slashes: + * + * a + b = a/b + * a/ + b = a/b + * a + /b = a/b + * a/ + /b = a/b + * a// + ///b = a/b + * a///b + c//d = a/b/c/d + */ + +void path_append(struct path_builder *, char const *); +void path_append_limited(struct path_builder *, char const *, size_t); +void path_append_guri(struct path_builder *, struct rpki_uri *); +void path_append_uint(struct path_builder *, unsigned int); + +void path_pop(struct path_builder *, bool); + +void path_reverse(struct path_builder *); + +int path_peek(struct path_builder *, char const **); +int path_compile(struct path_builder *, char **); + +void path_cancel(struct path_builder *); + +#endif /* SRC_DATA_STRUCTURE_PATH_BUILDER_H_ */ diff --git a/src/delete_dir_daemon.c b/src/delete_dir_daemon.c deleted file mode 100644 index f86ff6c7..00000000 --- a/src/delete_dir_daemon.c +++ /dev/null @@ -1,293 +0,0 @@ -#define _XOPEN_SOURCE 500 - -#include "delete_dir_daemon.h" - -#include -#include -#include -#include -#include -#include -#include "alloc.h" -#include "common.h" -#include "internal_pool.h" -#include "log.h" -#include "random.h" - -#define MAX_FD_ALLOWED 20 - -struct rem_dirs { - char **arr; - size_t arr_len; - size_t arr_set; -}; - -static int -remove_file(char const *location) -{ - int error; - - pr_op_debug("Trying to remove file '%s'.", location); - -#ifdef DEBUG_RRDP - /* Dev will likely need this file in the next offline run. */ - return 0; -#endif - - if (remove(location) != 0) { - error = errno; - pr_op_err("Couldn't delete file '%s': %s", location, - strerror(error)); - return error; - } - - return 0; -} - -static int -remove_dir(char const *location) -{ - int error; - - pr_op_debug("Trying to remove dir '%s'.", location); - -#ifdef DEBUG_RRDP - /* Dev will likely need this directory in the next offline run. */ - return 0; -#endif - - if (remove(location) != 0) { - error = errno; - pr_op_err("Couldn't delete directory '%s': %s", location, - strerror(error)); - return error; - } - - return 0; -} - -static int -traverse(char const *path, struct stat const *sb, int flag, struct FTW *ftwbuf) -{ - /* - * FTW_SLN: - * Will never be present since FTW_PHYS flag is utilized - */ - switch (flag) { - case FTW_DP: - return remove_dir(path); - case FTW_F: - return remove_file(path); - case FTW_DNR: - return pr_op_err("Can't access '%s', stop deletion.", path); - case FTW_NS: - return pr_op_err("Can't get information of '%s', stop deletion.", - path); - case FTW_SL: - return pr_op_err("Can't delete '%s' since is a symbolic link, stop deletion.", - path); - case FTW_D: - return pr_op_err("Can't delete '%s' dir before deleting its content.", - path); - default: - return pr_op_warn("Unknown path flag %d, doing nothing to '%s'.", - flag, path); - } -} - -static void -remove_from_root(void *arg) -{ - struct rem_dirs *root_arg = arg; - char **dirs_arr; - size_t len, i; - int error; - - dirs_arr = root_arg->arr; - len = root_arg->arr_set; - - /* Release received arg */ - free(root_arg); - - for (i = 0; i < len; i++) { - error = nftw(dirs_arr[i], traverse, MAX_FD_ALLOWED, - FTW_DEPTH|FTW_MOUNT|FTW_PHYS); - if (error) { - if (errno) - pr_op_debug("Error deleting directory '%s', please delete it manually: %s", - dirs_arr[i], strerror(errno)); - else - pr_op_debug("Couldn't delete directory '%s', please delete it manually", - dirs_arr[i]); - } - /* Release at once, won't be needed anymore */ - free(dirs_arr[i]); - } - - pr_op_debug("Done removing dirs."); - free(dirs_arr); -} - -/* - * Soft/hard error logic utilized, beware to prepare caller: - * - '> 0' is a soft error - * - '< 0' is a hard error - * - '= 0' no error - */ -static int -get_local_path(char const *rcvd, char **result) -{ - struct stat attr; - char *tmp, *local_path; - size_t tmp_size; - int error; - - /* Currently, only rsync URIs are utilized */ - local_path = map_uri_to_local(rcvd, "rsync://"); - - error = stat(local_path, &attr); - if (error) { - /* Soft error */ - pr_op_debug("Error reading path '%s' (discarding): %s", - local_path, strerror(errno)); - error = errno; - goto release_local; - } - - if (!S_ISDIR(attr.st_mode)) { - /* Soft error */ - pr_op_debug("Path '%s' exists but is not a directory (discarding).", - local_path); - error = ENOTDIR; - goto release_local; - } - - /* Assure that root dir ends without '/' */ - tmp_size = strlen(local_path); - if (strrchr(local_path, '/') == local_path + strlen(local_path) - 1) - tmp_size--; - - tmp = pmalloc(tmp_size + 1); - strncpy(tmp, local_path, tmp_size); - tmp[tmp_size] = '\0'; - - free(local_path); - - *result = tmp; - return 0; -release_local: - free(local_path); - return error; -} - -static int -rename_local_path(char const *rcvd, char **result) -{ - char *tmp; - long random_sfx; - size_t rcvd_size, tmp_size; - int error; - - rcvd_size = strlen(rcvd); - /* original size + one underscore + hex random val (8 chars) */ - tmp_size = rcvd_size + 1 + (sizeof(RAND_MAX) * 2); - tmp = pmalloc(tmp_size + 1); - - /* Rename the path with a random suffix */ - random_init(); - random_sfx = random_at_most(RAND_MAX); - - snprintf(tmp, tmp_size + 1, "%s_%08lX", rcvd, random_sfx); - - if (rename(rcvd, tmp) != 0) { - error = errno; - free(tmp); - pr_op_debug("Couldn't rename '%s' to delete it (discarding): %s", - rcvd, strerror(error)); - return error; - } - - *result = tmp; - return 0; -} - -static int -rename_all_roots(struct rem_dirs *rem_dirs, char **src) -{ - char *local_path, *delete_path; - size_t i; - int error; - - for (i = 0; i < rem_dirs->arr_len; i++) { - local_path = NULL; - error = get_local_path(src[(rem_dirs->arr_len - 1) - i], - &local_path); - if (error < 0) - return error; - if (error > 0) - continue; - - delete_path = NULL; - error = rename_local_path(local_path, &delete_path); - free(local_path); - if (error) - continue; - rem_dirs->arr[rem_dirs->arr_set++] = delete_path; - } - - return 0; -} - -static struct rem_dirs * -rem_dirs_create(size_t arr_len) -{ - struct rem_dirs *tmp; - - tmp = pmalloc(sizeof(struct rem_dirs)); - - tmp->arr = pcalloc(arr_len, sizeof(char *)); - tmp->arr_len = arr_len; - tmp->arr_set = 0; - - return tmp; -} - -static void -rem_dirs_destroy(struct rem_dirs *rem_dirs) -{ - size_t i; - - for (i = 0; i < rem_dirs->arr_set; i++) - free(rem_dirs->arr[i]); - free(rem_dirs->arr); - free(rem_dirs); -} - -/* - * Remove the files listed at @roots array of @roots_len size. The local files - * will be searched at the specified HTTP local @workspace. - * - * The daemon will be as quiet as possible, since most of its job is done - * asynchronously. Also, it works on the best possible effort; some errors are - * treated as "soft" errors, since the directory deletion still doesn't - * considers the relations (parent-child) at dirs. - */ -int -delete_dir_daemon_start(char **roots, size_t roots_len) -{ - struct rem_dirs *arg; - int error; - - arg = rem_dirs_create(roots_len); - - error = rename_all_roots(arg, roots); - if (error) { - rem_dirs_destroy(arg); - return error; - } - - /* Thread arg is released at thread */ - internal_pool_push("Directory deleter", remove_from_root, arg); - - return 0; -} diff --git a/src/delete_dir_daemon.h b/src/delete_dir_daemon.h deleted file mode 100644 index 992d9234..00000000 --- a/src/delete_dir_daemon.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef SRC_DELETE_DIR_DAEMON_H_ -#define SRC_DELETE_DIR_DAEMON_H_ - -#include - -int delete_dir_daemon_start(char **, size_t); - -#endif /* SRC_DELETE_DIR_DAEMON_H_ */ diff --git a/src/extension.c b/src/extension.c index 023a3fe1..e4bb06b2 100644 --- a/src/extension.c +++ b/src/extension.c @@ -1,6 +1,7 @@ #include "extension.h" #include +#include "cert_stack.h" #include "common.h" #include "log.h" #include "nid.h" diff --git a/src/file.c b/src/file.c index 8548bc91..c2656650 100644 --- a/src/file.c +++ b/src/file.c @@ -1,19 +1,25 @@ +#define _XOPEN_SOURCE 500 /* nftw() */ + #include "file.h" #include #include +#include /* nftw() */ +#include /* opendir(), closedir() */ +#include /* opendir(), readdir(), closedir() */ + #include "alloc.h" #include "log.h" +#include "data_structure/uthash.h" -static int -file_get(char const *file_name, FILE **result, struct stat *stat, - char const *mode) +int +file_open(char const *file_name, FILE **result, struct stat *stat) { FILE *file; int error; - file = fopen(file_name, mode); + file = fopen(file_name, "rb"); if (file == NULL) { error = errno; pr_val_err("Could not open file '%s': %s", file_name, @@ -39,17 +45,23 @@ file_get(char const *file_name, FILE **result, struct stat *stat, return error; } -int -file_open(char const *file_name, FILE **result, struct stat *stat) -{ - return file_get(file_name, result, stat, "rb"); -} - int file_write(char const *file_name, FILE **result) { - struct stat stat; - return file_get(file_name, result, &stat, "wb"); + FILE *file; + int error; + + file = fopen(file_name, "wb"); + if (file == NULL) { + error = errno; + pr_val_err("Could not open file '%s': %s", file_name, + strerror(error)); + *result = NULL; + return error; + } + + *result = file; + return 0; } void @@ -83,7 +95,7 @@ file_load(char const *file_name, struct file_contents *fc) * code. It literally doesn't say how to get an error * code. */ - pr_val_err("File reading error. The error message is (apparently) '%s'", + pr_val_err("File reading error. The error message is (possibly) '%s'", strerror(error)); free(fc->buffer); goto end; @@ -134,3 +146,35 @@ file_valid(char const *file_name) file_close(tmp); return true; } + +static int +rm(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) +{ + return (remove(fpath) != 0) ? errno : 0; +} + +/* Same as `system("rm -rf ")`, but more portable and maaaaybe faster. */ +int +file_rm_rf(char const *path) +{ + /* FIXME optimize that 32 */ + return nftw(path, rm, 32, FTW_DEPTH | FTW_PHYS); +} + +static int +lsR(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) +{ + unsigned int i; + + for (i = 0; i < ftwbuf->level; i++) + printf("\t"); + printf("%s\n", &fpath[ftwbuf->base]); + + return 0; +} + +void +file_ls_R(char const *path) +{ + nftw(path, lsR, 32, FTW_PHYS); +} diff --git a/src/file.h b/src/file.h index 0ce44ba5..497f35fb 100644 --- a/src/file.h +++ b/src/file.h @@ -25,4 +25,23 @@ void file_free(struct file_contents *); bool file_valid(char const *); +int file_rm_rf(char const *); +void file_ls_R(char const *); + +/* + * Remember that this API is awkward: + * + * 1. Check errno after the loop. + * 2. Probably also check S_ISDOTS() during the loop. + * 3. Do closedir() even on error. + */ +#define FOREACH_DIR_FILE(dir, file) for ( \ + errno = 0, file = readdir(dir); \ + file != NULL; \ + errno = 0, file = readdir(dir) \ + ) + +#define S_ISDOTS(file) \ + (strcmp((file)->d_name, ".") == 0 || strcmp((file)->d_name, "..") == 0) + #endif /* SRC_FILE_H_ */ diff --git a/src/http/http.c b/src/http/http.c index 121e5409..09d1e933 100644 --- a/src/http/http.c +++ b/src/http/http.c @@ -1,4 +1,4 @@ -#include "http.h" +#include "http/http.h" #include #include @@ -10,6 +10,8 @@ #include "config.h" #include "file.h" #include "log.h" +#include "cache/tmp.h" +#include "data_structure/uthash.h" struct http_handler { CURL *curl; @@ -34,6 +36,22 @@ http_cleanup(void) curl_global_cleanup(); } +static int +get_ims(char const *file, time_t *ims) +{ + struct stat meta; + int error; + + if (stat(file, &meta) != 0) { + error = errno; + *ims = 0; + return (error == ENOENT) ? 0 : error; + } + + *ims = meta.st_mtim.tv_sec; + return 0; +} + static void setopt_str(CURL *curl, CURLoption opt, char const *value) { @@ -64,7 +82,9 @@ setopt_long(CURL *curl, CURLoption opt, long value) struct write_callback_arg { size_t total_bytes; int error; - FILE *dst; + + char const *file_name; + FILE *file; /* Initialized lazily */ }; static size_t @@ -86,7 +106,13 @@ write_callback(void *data, size_t size, size_t nmemb, void *userp) return 0; /* Ugh. See fwrite(3) */ } - return fwrite(data, size, nmemb, arg->dst); + if (arg->file == NULL) { + arg->error = file_write(arg->file_name, &arg->file); + if (arg->error) + return 0; + } + + return fwrite(data, size, nmemb, arg->file); } static void @@ -114,7 +140,7 @@ setopt_writedata(CURL *curl, struct write_callback_arg *arg) } static void -http_easy_init(struct http_handler *handler, long ims) +http_easy_init(struct http_handler *handler, curl_off_t ims) { CURL *result; @@ -164,7 +190,7 @@ http_easy_init(struct http_handler *handler, long ims) setopt_long(result, CURLOPT_NOSIGNAL, 1L); if (ims > 0) { - setopt_long(result, CURLOPT_TIMEVALUE, ims); + setopt_long(result, CURLOPT_TIMEVALUE_LARGE, ims); setopt_long(result, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); } @@ -172,6 +198,12 @@ http_easy_init(struct http_handler *handler, long ims) handler->curl = result; } +static void +http_easy_cleanup(struct http_handler *handler) +{ + curl_easy_cleanup(handler->curl); +} + static char const * curl_err_string(struct http_handler *handler, CURLcode res) { @@ -228,61 +260,76 @@ handle_http_response_code(long http_code) } /* - * Fetch data from @uri and write result using @cb (which will receive @arg). + * Fetch data from @src and write result on @dst. */ static int -http_fetch(struct http_handler *handler, char const *uri, long *response_code, - FILE *file) +http_fetch(char const *src, char const *dst, curl_off_t ims, bool *changed) { + struct http_handler handler; struct write_callback_arg args; CURLcode res; long http_code; + int error; - handler->errbuf[0] = 0; - setopt_str(handler->curl, CURLOPT_URL, uri); + http_easy_init(&handler, ims); + + handler.errbuf[0] = 0; + setopt_str(handler.curl, CURLOPT_URL, src); args.total_bytes = 0; args.error = 0; - args.dst = file; - setopt_writedata(handler->curl, &args); - - pr_val_info("HTTP GET: %s", uri); - res = curl_easy_perform(handler->curl); + args.file_name = dst; + args.file = NULL; + setopt_writedata(handler.curl, &args); + + pr_val_info("HTTP GET: %s", src); + res = curl_easy_perform(handler.curl); + if (args.file != NULL) + file_close(args.file); pr_val_debug("Done. Total bytes transferred: %zu", args.total_bytes); - args.error = validate_file_size(uri, &args); - if (args.error) - return args.error; + args.error = validate_file_size(src, &args); + if (args.error) { + error = args.error; + goto end; + } - args.error = get_http_response_code(handler, &http_code, uri); - if (args.error) - return args.error; - *response_code = http_code; + args.error = get_http_response_code(&handler, &http_code, src); + if (args.error) { + error = args.error; + goto end; + } if (res != CURLE_OK) { pr_val_err("Error requesting URL: %s. (HTTP code: %ld)", - curl_err_string(handler, res), http_code); + curl_err_string(&handler, res), http_code); switch (res) { case CURLE_FILESIZE_EXCEEDED: - return -EFBIG; /* Do not retry */ + error = -EFBIG; /* Do not retry */ + goto end; case CURLE_OPERATION_TIMEDOUT: case CURLE_COULDNT_RESOLVE_HOST: case CURLE_COULDNT_RESOLVE_PROXY: case CURLE_FTP_ACCEPT_TIMEOUT: - return EREQFAILED; /* Retry */ + error = EREQFAILED; /* Retry */ + goto end; default: - return handle_http_response_code(http_code); + error = handle_http_response_code(http_code); + goto end; } } if (http_code >= 400) { pr_val_err("HTTP result code: %ld", http_code); - return handle_http_response_code(http_code); + error = handle_http_response_code(http_code); + goto end; } if (http_code == 304) { + /* Write callback not called, no file to remove. */ pr_val_debug("Not modified."); - return 0; + error = 0; + goto end; } if (http_code >= 300) { /* @@ -292,36 +339,17 @@ http_fetch(struct http_handler *handler, char const *uri, long *response_code, */ pr_val_err("HTTP result code: %ld. I don't follow redirects; discarding file.", http_code); - return -EINVAL; /* Do not retry. */ + error = -EINVAL; /* Do not retry. */ + goto end; } pr_val_debug("HTTP result code: %ld", http_code); - return 0; -} - -static void -http_easy_cleanup(struct http_handler *handler) -{ - curl_easy_cleanup(handler->curl); -} - -static long -download(char const *src, char const *dst, long ims, long *response_code) -{ - FILE *file; - struct http_handler handler; - long error; + error = 0; + *changed = true; - error = file_write(dst, &file); +end: http_easy_cleanup(&handler); if (error) - return error; - http_easy_init(&handler, ims); - - error = http_fetch(&handler, src, response_code, file); - - http_easy_cleanup(&handler); - file_close(file); - + remove(dst); return error; } @@ -329,16 +357,18 @@ download(char const *src, char const *dst, long ims, long *response_code) * Assumes @dst's parent directory has already been created. */ static int -do_retries(char const *src, char const *dst, long ims, long *response_code) +do_retries(char const *src, char const *dst, curl_off_t ims, bool *changed) { unsigned int r; int error; + pr_val_info("Downloading '%s'.", src); + r = 0; do { pr_val_debug("Download attempt #%u...", r); - error = download(src, dst, ims, response_code); + error = http_fetch(src, dst, ims, changed); switch (error) { case 0: pr_val_debug("Download successful."); @@ -360,7 +390,7 @@ do_retries(char const *src, char const *dst, long ims, long *response_code) pr_val_warn("Download failed; retrying in %u seconds.", config_get_http_retry_interval()); /* - * TODO (#78) Wrong. This is slowing the entire tree traversal + * TODO (fine) Wrong. This is slowing the entire tree traversal * down; use a thread pool. */ sleep(config_get_http_retry_interval()); @@ -368,100 +398,60 @@ do_retries(char const *src, char const *dst, long ims, long *response_code) } while (true); } -static int -__http_download_file(struct rpki_uri *uri, long *response_code, long ims_value) +/* + * Download @uri->global into @uri->local; HTTP assumed. + * + * If @changed returns true, the file was downloaded normally. + * If @changed returns false, the file already existed and is already its latest + * version. + * @changed can be NULL. + */ +int +http_download(struct rpki_uri *uri, bool *changed) { - char const *tmp_suffix = "_tmp"; - char const *original_file; - char *tmp_file; + char *tmp_file_name; + char const *final_file_name; + time_t ims; + bool __changed; int error; - *response_code = 0; + if (changed == NULL) + changed = &__changed; + *changed = false; - if (!config_get_http_enabled()) - return 0; + if (!config_get_rsync_enabled()) + return 0; /* Skip; caller will work with existing cache. */ - original_file = uri_get_local(uri); - - tmp_file = pmalloc(strlen(original_file) + strlen(tmp_suffix) + 1); - strcpy(tmp_file, original_file); - strcat(tmp_file, tmp_suffix); - - error = create_dir_recursive(tmp_file); + error = cache_tmpfile(&tmp_file_name); + if (error) + return error; + final_file_name = uri_get_local(uri); + error = get_ims(final_file_name, &ims); if (error) - goto release_tmp; + goto end; - error = do_retries(uri_get_global(uri), tmp_file, ims_value, - response_code); - /* TODO (#78) Looks like the temporal file isn't being rm'd. */ - if ((*response_code) == 304) + error = do_retries(uri_get_global(uri), tmp_file_name, (curl_off_t)ims, + changed); + if (error || !(*changed)) goto end; - if (error) - goto delete_dir; - /* Overwrite the original file */ - error = rename(tmp_file, original_file); + error = create_dir_recursive(final_file_name); + if (error) { + remove(tmp_file_name); + goto end; + } + + error = rename(tmp_file_name, final_file_name); if (error) { error = errno; pr_val_err("Renaming temporal file from '%s' to '%s': %s", - tmp_file, original_file, strerror(error)); - goto delete_dir; + tmp_file_name, final_file_name, strerror(error)); + remove(tmp_file_name); + goto end; } -end: - free(tmp_file); - return 0; -delete_dir: - delete_dir_recursive_bottom_up(tmp_file); -release_tmp: - free(tmp_file); - return ENSURE_NEGATIVE(error); -} - -/* - * Try to download from global @uri into a local directory structure created - * from local @uri. - * - * Return values: 0 on success, negative value on error, EREQFAILED if the - * request to the server failed. - */ -int -http_download_file(struct rpki_uri *uri) -{ - long response; - return __http_download_file(uri, &response, 0); -} - -/* - * Fetch the file from @uri. - * - * The HTTP request is made using the header 'If-Modified-Since' with a value - * of @value (if @value is 0, the header isn't set). - * - * Returns: - * EREQFAILED the request to the server has failed. - * > 0 file was requested but wasn't downloaded since the server didn't sent - * a response due to its policy using the header 'If-Modified-Since'. - * = 0 file successfully downloaded. - * < 0 an actual error happened. - */ -int -http_download_file_with_ims(struct rpki_uri *uri, long value) -{ - long response; - int error; - - error = __http_download_file(uri, &response, value); - if (error) - return error; - - /* rfc7232#section-3.3: - * "the origin server SHOULD generate a 304 (Not Modified) response" - */ - if (response == 304) - return 1; - - return 0; +end: free(tmp_file_name); + return error; } /* @@ -471,26 +461,15 @@ http_download_file_with_ims(struct rpki_uri *uri, long value) int http_direct_download(char const *remote, char const *dest) { - char const *tmp_suffix = "_tmp"; - struct http_handler handler; - FILE *out; - long response_code; + bool changed; char *tmp_file; int error; - tmp_file = pstrdup(dest); - tmp_file = prealloc(tmp_file, strlen(tmp_file) + strlen(tmp_suffix) + 1); - strcat(tmp_file, tmp_suffix); - - error = file_write(tmp_file, &out); + error = cache_tmpfile(&tmp_file); if (error) - goto end; - - http_easy_init(&handler, 0); - error = http_fetch(&handler, remote, &response_code, out); - http_easy_cleanup(&handler); + return error; - file_close(out); + error = http_fetch(remote, tmp_file, 0, &changed); if (error) goto end; @@ -503,7 +482,6 @@ http_direct_download(char const *remote, char const *dest) goto end; } -end: - free(tmp_file); +end: free(tmp_file); return error; } diff --git a/src/http/http.h b/src/http/http.h index 120127bc..41cb6692 100644 --- a/src/http/http.h +++ b/src/http/http.h @@ -1,16 +1,12 @@ #ifndef SRC_HTTP_HTTP_H_ #define SRC_HTTP_HTTP_H_ -#include #include "types/uri.h" -/* Init on the main process */ int http_init(void); void http_cleanup(void); -int http_download_file(struct rpki_uri *); -int http_download_file_with_ims(struct rpki_uri *, long); - +int http_download(struct rpki_uri *, bool *); int http_direct_download(char const *, char const *); #endif /* SRC_HTTP_HTTP_H_ */ diff --git a/src/internal_pool.c b/src/internal_pool.c deleted file mode 100644 index 09e36f27..00000000 --- a/src/internal_pool.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "internal_pool.h" - -#include - -/* - * This is a basic wrapper for thread_pool functions, but the allocated pool - * lives at the main thread. - * - * Additional threads that must be spawned during execution (those that aren't - * related to the validation or server thread pool tasks) can be pushed here. - */ - -#define INTERNAL_POOL_MAX 10 - -static struct thread_pool *pool; - -int -internal_pool_init(void) -{ - int error; - - pool = NULL; - error = thread_pool_create("Internal", INTERNAL_POOL_MAX, &pool); - if (error) - return error; - - return 0; -} - -void -internal_pool_push(char const *task_name, thread_pool_task_cb cb, void *arg) -{ - thread_pool_push(pool, task_name, cb, arg); -} - -void -internal_pool_cleanup(void) -{ - thread_pool_destroy(pool); -} diff --git a/src/internal_pool.h b/src/internal_pool.h deleted file mode 100644 index 1eeacad6..00000000 --- a/src/internal_pool.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef SRC_INTERNAL_POOL_H_ -#define SRC_INTERNAL_POOL_H_ - -#include "thread/thread_pool.h" - -int internal_pool_init(void); -void internal_pool_push(char const *, thread_pool_task_cb, void *); -void internal_pool_cleanup(void); - -#endif /* SRC_INTERNAL_POOL_H_ */ diff --git a/src/line_file.c b/src/line_file.c index ec511422..abde7d34 100644 --- a/src/line_file.c +++ b/src/line_file.c @@ -2,6 +2,7 @@ #include #include +#include #include #include "alloc.h" diff --git a/src/log.c b/src/log.c index 264b27a9..9d69774f 100644 --- a/src/log.c +++ b/src/log.c @@ -633,6 +633,7 @@ val_crypto_err(const char *format, ...) return crypto_err(&val_config, pr_val_err); } +/* FIXME open call hierarchy */ __dead void enomem_panic(void) { diff --git a/src/main.c b/src/main.c index 6a03a9e0..f60cacb4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,14 +1,14 @@ #include "config.h" #include "extension.h" -#include "internal_pool.h" +#include "log.h" #include "nid.h" #include "thread_var.h" #include "validation_run.h" #include "http/http.h" +#include "incidence/incidence.h" #include "rtr/rtr.h" #include "rtr/db/vrps.h" #include "xml/relax_ng.h" -#include "rrdp/db/db_rrdp.h" static int run_rtr_server(void) @@ -90,23 +90,12 @@ main(int argc, char **argv) if (error) goto revert_nid; - /* - * TODO (performance) this looks like a lot of overhead. Is it really - * necessary when mode is STANDALONE? - */ - error = internal_pool_init(); - if (error) - goto revert_http; - error = relax_ng_init(); if (error) - goto revert_pool; + goto revert_http; error = vrps_init(); if (error) goto revert_relax_ng; - error = db_rrdp_init(); - if (error) - goto vrps_cleanup; /* Do stuff */ switch (config_get_mode()) { @@ -120,13 +109,9 @@ main(int argc, char **argv) /* End */ - db_rrdp_cleanup(); -vrps_cleanup: vrps_destroy(); revert_relax_ng: relax_ng_cleanup(); -revert_pool: - internal_pool_cleanup(); revert_http: http_cleanup(); revert_nid: diff --git a/src/object/certificate.c b/src/object/certificate.c index 7af43972..3c44c3b1 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -15,6 +15,7 @@ #include "algorithm.h" #include "alloc.h" +#include "cert_stack.h" #include "config.h" #include "extension.h" #include "log.h" @@ -25,13 +26,14 @@ #include "asn1/oid.h" #include "asn1/asn1c/IPAddrBlocks.h" #include "crypto/hash.h" +#include "data_structure/array_list.h" #include "incidence/incidence.h" #include "object/bgpsec.h" #include "object/name.h" #include "object/manifest.h" #include "object/signed_object.h" #include "rrdp/rrdp_loader.h" -#include "rsync/rsync.h" +#include "cache/local_cache.h" /* Just to prevent some line breaking. */ #define GN_URI uniformResourceIdentifier @@ -48,15 +50,11 @@ struct ski_arguments { OCTET_STRING_t *sid; }; -struct sia_uri { - uint8_t position; - struct rpki_uri *uri; -}; +STATIC_ARRAY_LIST(sia_rpp_uris, struct rpki_uri *) -struct sia_ca_uris { - struct sia_uri caRepository; - struct sia_uri rpkiNotify; - struct sia_uri mft; +struct sia_uris { + struct sia_rpp_uris rpp; + struct rpki_uri *mft; }; struct bgpsec_ski { @@ -65,25 +63,74 @@ struct bgpsec_ski { }; /* Callback method to fetch repository objects */ -typedef int (access_method_exec)(struct sia_ca_uris *); +typedef int (access_method_exec)(struct sia_uris *); + +struct ad_metadata { + char const *name; + char const *ia_name; + enum uri_type type; + char const *type_str; + bool required; +}; + +static const struct ad_metadata CA_ISSUERS = { + .name = "caIssuers", + .ia_name = "AIA", + .type = UT_RSYNC, + .type_str = "rsync", + .required = true, +}; + +static const struct ad_metadata SIGNED_OBJECT = { + .name = "signedObject", + .ia_name = "SIA", + .type = UT_RSYNC, + .type_str = "rsync", + .required = true, +}; + +static const struct ad_metadata CA_REPOSITORY = { + .name = "caRepository", + .ia_name = "SIA", + .type = UT_RSYNC, + .type_str = "rsync", + .required = false, +}; + +static const struct ad_metadata RPKI_NOTIFY = { + .name = "rpkiNotify", + .ia_name = "SIA", + .type = UT_HTTPS, + .type_str = "HTTPS", + .required = false, +}; + +static const struct ad_metadata RPKI_MANIFEST = { + .name = "rpkiManifest", + .ia_name = "SIA", + .type = UT_RSYNC, + .type_str = "rsync", + .required = true, +}; + +static void +sia_uris_init(struct sia_uris *uris) +{ + sia_rpp_uris_init(&uris->rpp); + uris->mft = NULL; +} static void -sia_ca_uris_init(struct sia_ca_uris *sia_uris) +cleanup_uri(struct rpki_uri **uri) { - sia_uris->caRepository.uri = NULL; - sia_uris->rpkiNotify.uri = NULL; - sia_uris->mft.uri = NULL; + uri_refput(*uri); } static void -sia_ca_uris_cleanup(struct sia_ca_uris *sia_uris) +sia_uris_cleanup(struct sia_uris *uris) { - if (sia_uris->caRepository.uri != NULL) - uri_refput(sia_uris->caRepository.uri); - if (sia_uris->rpkiNotify.uri != NULL) - uri_refput(sia_uris->rpkiNotify.uri); - if (sia_uris->mft.uri != NULL) - uri_refput(sia_uris->mft.uri); + sia_rpp_uris_cleanup(&uris->rpp, cleanup_uri); + uri_refput(uris->mft); } static void @@ -764,6 +811,8 @@ certificate_load(struct rpki_uri *uri, X509 **result) BIO *bio; int error; + *result = NULL; + bio = BIO_new(BIO_s_file()); if (bio == NULL) return val_crypto_err("BIO_new(BIO_s_file()) returned NULL"); @@ -1154,44 +1203,39 @@ is_rsync_uri(GENERAL_NAME *name) } static int -handle_rpkiManifest(struct rpki_uri *uri, uint8_t pos, void *arg) +handle_rpkiManifest(struct rpki_uri *uri, void *arg) { - struct sia_uri *mft = arg; - mft->position = pos; - mft->uri = uri; - uri_refget(uri); + struct sia_uris *uris = arg; + uris->mft = uri_refget(uri); return 0; } static int -handle_caRepository(struct rpki_uri *uri, uint8_t pos, void *arg) +handle_caRepository(struct rpki_uri *uri, void *arg) { - struct sia_uri *repo = arg; + struct sia_uris *uris = arg; pr_val_debug("caRepository: %s", uri_val_get_printable(uri)); - repo->position = pos; - repo->uri = uri; + sia_rpp_uris_add(&uris->rpp, &uri); uri_refget(uri); return 0; } static int -handle_rpkiNotify(struct rpki_uri *uri, uint8_t pos, void *arg) +handle_rpkiNotify(struct rpki_uri *uri, void *arg) { - struct sia_uri *notify = arg; + struct sia_uris *uris = arg; pr_val_debug("rpkiNotify: %s", uri_val_get_printable(uri)); - notify->position = pos; - notify->uri = uri; + sia_rpp_uris_add(&uris->rpp, &uri); uri_refget(uri); return 0; } static int -handle_signedObject(struct rpki_uri *uri, uint8_t pos, void *arg) +handle_signedObject(struct rpki_uri *uri, void *arg) { struct certificate_refs *refs = arg; pr_val_debug("signedObject: %s", uri_val_get_printable(uri)); - refs->signedObject = uri; - uri_refget(uri); + refs->signedObject = uri_refget(uri); return 0; } @@ -1442,53 +1486,94 @@ handle_cdp(X509_EXTENSION *ext, void *arg) return error; } +/* + * Create @uri from the @ad + */ +static int +uri_create_ad(struct rpki_uri **uri, ACCESS_DESCRIPTION *ad, enum uri_type type) +{ + ASN1_STRING *asn1_string; + int ptype; + + asn1_string = GENERAL_NAME_get0_value(ad->location, &ptype); + + /* + * RFC 6487: "This extension MUST have an instance of an + * AccessDescription with an accessMethod of id-ad-rpkiManifest, (...) + * with an rsync URI [RFC5781] form of accessLocation." + * + * Ehhhhhh. It's a little annoying in that it seems to be stucking more + * than one requirement in a single sentence, which I think is rather + * rare for an RFC. Normally they tend to hammer things more. + * + * Does it imply that the GeneralName CHOICE is constrained to type + * "uniformResourceIdentifier"? I guess so, though I don't see anything + * stopping a few of the other types from also being capable of storing + * URIs. + * + * Also, nobody seems to be using the other types, and handling them + * would be a titanic pain in the ass. So this is what I'm committing + * to. + */ + if (ptype != GEN_URI) { + pr_val_err("Unknown GENERAL_NAME type: %d", ptype); + return ENOTSUPPORTED; + } + + /* + * GEN_URI signals an IA5String. + * IA5String is a subset of ASCII, so this cast is safe. + * No guarantees of a NULL chara, though. + * + * TODO (testers) According to RFC 5280, accessLocation can be an IRI + * somehow converted into URI form. I don't think that's an issue + * because the RSYNC clone operation should not have performed the + * conversion, so we should be looking at precisely the IA5String + * directory our g2l version of @asn1_string should contain. + * But ask the testers to keep an eye on it anyway. + */ + return __uri_create(uri, type, + ASN1_STRING_get0_data(asn1_string), + ASN1_STRING_length(asn1_string)); +} + /** * The RFC does not explain AD validation very well. This is personal * interpretation, influenced by Tim Bruijnzeels's response * (https://mailarchive.ietf.org/arch/msg/sidr/4ycmff9jEU4VU9gGK5RyhZ7JYsQ) * (I'm being a bit more lax than he suggested.) * - * 1. Only one NID needs to be searched at a time. (This is currently somewhat - * of a coincidence, and will probably be superseded at some point. But I'm - * not going to complicate this until it's necessary.) - * 2. The NID MUST be found, otherwise the certificate is invalid. - * 3. The NID can be found more than once. - * 4. All access descriptions that match the NID must be URLs. - * 5. Precisely one of those matches will be an RSYNC URL, and it's the only one - * we are required to support. - * (I would have gone with "at least one of those matches", but I don't know + * 1. The NID (@nid) can be found more than once. + * 2. All access descriptions that match the NID must be URLs. + * 3. Depending on meta->required, zero or one of those matches will be an URL + * of the meta->type we're expecting. + * (I would have gone with "at least zero of those matches", but I don't know * what to do with the other ones.) - * 6. Other access descriptions that do not match the NID are allowed and + * 4. Other access descriptions that do not match the NID are allowed and * supposed to be ignored. - * 7. Other access descriptions that match the NID but do not have RSYNC URIs - * are also allowed, and also supposed to be ignored. + * 5. Other access descriptions that match the NID but do not have recognized + * URLs are also allowed, and also supposed to be ignored. */ static int -handle_ad(char const *ia_name, SIGNATURE_INFO_ACCESS *ia, - char const *ad_name, int ad_nid, int uri_flags, bool required, - int (*cb)(struct rpki_uri *, uint8_t, void *), void *arg) +handle_ad(int nid, struct ad_metadata const *meta, SIGNATURE_INFO_ACCESS *ia, + int (*cb)(struct rpki_uri *, void *), void *arg) { -# define AD_METHOD ((uri_flags & URI_VALID_RSYNC) == URI_VALID_RSYNC ? \ - "RSYNC" : \ - (((uri_flags & URI_VALID_HTTPS) == URI_VALID_HTTPS) ? \ - "HTTPS" : "RSYNC/HTTPS")) ACCESS_DESCRIPTION *ad; struct rpki_uri *uri; - bool found = false; + bool found; unsigned int i; int error; + found = false; for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ia); i++) { ad = sk_ACCESS_DESCRIPTION_value(ia, i); - if (OBJ_obj2nid(ad->method) == ad_nid) { - error = uri_create_ad(&uri, ad, uri_flags); + if (OBJ_obj2nid(ad->method) == nid) { + error = uri_create_ad(&uri, ad, meta->type); switch (error) { case 0: break; case ENOTRSYNC: - continue; case ENOTHTTPS: - continue; case ENOTSUPPORTED: continue; default: @@ -1498,10 +1583,10 @@ handle_ad(char const *ia_name, SIGNATURE_INFO_ACCESS *ia, if (found) { uri_refput(uri); return pr_val_err("Extension '%s' has multiple '%s' %s URIs.", - ia_name, ad_name, AD_METHOD); + meta->ia_name, meta->name, meta->type_str); } - error = cb(uri, i, arg); + error = cb(uri, arg); if (error) { uri_refput(uri); return error; @@ -1512,9 +1597,9 @@ handle_ad(char const *ia_name, SIGNATURE_INFO_ACCESS *ia, } } - if (required && !found) { - pr_val_err("Extension '%s' lacks a '%s' valid %s URI.", ia_name, - ad_name, AD_METHOD); + if (meta->required && !found) { + pr_val_err("Extension '%s' lacks a '%s' valid %s URI.", + meta->ia_name, meta->name, meta->type_str); return -ESRCH; } @@ -1522,7 +1607,7 @@ handle_ad(char const *ia_name, SIGNATURE_INFO_ACCESS *ia, } static int -handle_caIssuers(struct rpki_uri *uri, uint8_t pos, void *arg) +handle_caIssuers(struct rpki_uri *uri, void *arg) { struct certificate_refs *refs = arg; /* @@ -1530,8 +1615,7 @@ handle_caIssuers(struct rpki_uri *uri, uint8_t pos, void *arg) * over here is too much trouble, so do the handle_cdp() * hack. */ - refs->caIssuers = uri; - uri_refget(uri); + refs->caIssuers = uri_refget(uri); return 0; } @@ -1545,8 +1629,8 @@ handle_aia(X509_EXTENSION *ext, void *arg) if (aia == NULL) return cannot_decode(ext_aia()); - error = handle_ad("AIA", aia, "caIssuers", NID_ad_ca_issuers, - URI_VALID_RSYNC, true, handle_caIssuers, arg); + error = handle_ad(NID_ad_ca_issuers, &CA_ISSUERS, aia, handle_caIssuers, + arg); AUTHORITY_INFO_ACCESS_free(aia); return error; @@ -1556,32 +1640,28 @@ static int handle_sia_ca(X509_EXTENSION *ext, void *arg) { SIGNATURE_INFO_ACCESS *sia; - struct sia_ca_uris *uris = arg; + struct sia_uris *uris = arg; int error; sia = X509V3_EXT_d2i(ext); if (sia == NULL) return cannot_decode(ext_sia()); - /* rsync, still the preferred and required */ - error = handle_ad("SIA", sia, "caRepository", NID_caRepository, - URI_VALID_RSYNC, true, handle_caRepository, &uris->caRepository); + /* rsync */ + error = handle_ad(NID_caRepository, &CA_REPOSITORY, sia, + handle_caRepository, uris); if (error) goto end; - /* HTTPS RRDP */ - error = handle_ad("SIA", sia, "rpkiNotify", nid_rpkiNotify(), - URI_VALID_HTTPS, false, handle_rpkiNotify, &uris->rpkiNotify); + /* RRDP */ + error = handle_ad(nid_rpkiNotify(), &RPKI_NOTIFY, sia, + handle_rpkiNotify, uris); if (error) goto end; - /* - * Store the manifest URI in @mft. - * (We won't actually touch the manifest until we know the certificate - * is fully valid.) - */ - error = handle_ad("SIA", sia, "rpkiManifest", nid_rpkiManifest(), - URI_VALID_RSYNC, true, handle_rpkiManifest, &uris->mft); + /* Manifest */ + error = handle_ad(nid_rpkiManifest(), &RPKI_MANIFEST, sia, + handle_rpkiManifest, uris); end: AUTHORITY_INFO_ACCESS_free(sia); @@ -1598,8 +1678,8 @@ handle_sia_ee(X509_EXTENSION *ext, void *arg) if (sia == NULL) return cannot_decode(ext_sia()); - error = handle_ad("SIA", sia, "signedObject", nid_signedObject(), - URI_VALID_RSYNC, true, handle_signedObject, arg); + error = handle_ad(nid_signedObject(), &SIGNED_OBJECT, sia, + handle_signedObject, arg); AUTHORITY_INFO_ACCESS_free(sia); return error; @@ -1683,7 +1763,7 @@ handle_ar(X509_EXTENSION *ext, void *arg) * @sia_uris will be allocated. */ static int -certificate_validate_extensions_ta(X509 *cert, struct sia_ca_uris *sia_uris, +certificate_validate_extensions_ta(X509 *cert, struct sia_uris *sia_uris, enum rpki_policy *policy) { struct extension_handler handlers[] = { @@ -1714,7 +1794,7 @@ certificate_validate_extensions_ta(X509 *cert, struct sia_ca_uris *sia_uris, * extensions. */ static int -certificate_validate_extensions_ca(X509 *cert, struct sia_ca_uris *sia_uris, +certificate_validate_extensions_ca(X509 *cert, struct sia_uris *sia_uris, enum rpki_policy *policy, struct rpp *rpp_parent) { struct certificate_refs refs = { 0 }; @@ -1841,247 +1921,39 @@ int certificate_validate_aia(struct rpki_uri *caIssuers, X509 *cert) { /* - * TODO (#78) Compare the AIA to the parent's URI. + * FIXME Compare the AIA to the parent's URI. * We're currently not recording the URI, so this can't be solved until * the #78 refactor. */ return 0; } -/* - * Verify that the manifest file actually exists at the local repository, if it - * doesn't exist then discard the repository (which can result in a attempt - * to fetch data from another repository). - */ -static int -verify_mft_loc(struct rpki_uri *mft_uri) -{ - if (!valid_file_or_dir(uri_get_local(mft_uri), true, false, pr_val_err)) - return -EINVAL; /* Error already logged */ - - return 0; -} - -/* - * Verify the manifest location at the local RRDP workspace. - * - * Don't log in case the @mft_uri doesn't exist at the RRDP workspace. - */ -static int -verify_rrdp_mft_loc(struct rpki_uri *mft_uri) -{ - struct rpki_uri *tmp; - int error; - - tmp = NULL; - error = uri_create_rsync_str_rrdp(&tmp, uri_get_global(mft_uri), - uri_get_global_len(mft_uri)); - if (error) - return error; - - if (!valid_file_or_dir(uri_get_local(tmp), true, false, NULL)) { - uri_refput(tmp); - return -ENOENT; - } - - uri_refput(tmp); - return 0; -} - static int -replace_rrdp_mft_uri(struct sia_uri *sia_mft) +download_rpp(struct sia_uris *uris) { - struct rpki_uri *tmp; - int error; - - tmp = NULL; - error = uri_create_rsync_str_rrdp(&tmp, - uri_get_global(sia_mft->uri), - uri_get_global_len(sia_mft->uri)); - if (error) - return error; - - uri_refput(sia_mft->uri); - sia_mft->uri = tmp; - - return 0; -} - -static int -exec_rrdp_method(struct sia_ca_uris *sia_uris) -{ - bool data_updated; - int error; - - data_updated = false; - error = rrdp_load(sia_uris->rpkiNotify.uri, &data_updated); - if (error) - return error; - - error = verify_rrdp_mft_loc(sia_uris->mft.uri); - switch(error) { - case 0: - /* MFT exists, great! We're good to go. */ - break; - case -ENOENT: - /* Doesn't exist and the RRDP data was updated: error */ - if (data_updated) - return error; - - /* Otherwise, force the snapshot processing and check again */ - error = rrdp_reload_snapshot(sia_uris->rpkiNotify.uri); - if (error) - return error; - error = verify_rrdp_mft_loc(sia_uris->mft.uri); - if (error) - return error; - break; - default: - return error; - } - - /* Successfully loaded (or no updates yet), update MFT local URI */ - return replace_rrdp_mft_uri(&sia_uris->mft); -} - -static int -exec_rsync_method(struct sia_ca_uris *sia_uris) -{ - int error; - - error = rsync_download_files(sia_uris->caRepository.uri, false); - if (error) - return error; - - return verify_mft_loc(sia_uris->mft.uri); -} - -/* - * Currently only two access methods are supported, just consider those two: - * rsync and RRDP. If a new access method is supported, this function must - * change (and probably the sia_ca_uris struct as well). - * - * Both access method callbacks must verify the manifest existence. - */ -static int -use_access_method(struct sia_ca_uris *sia_uris, access_method_exec rsync_cb, - access_method_exec rrdp_cb, bool *retry_repo_sync) -{ - access_method_exec *cb_primary; - access_method_exec *cb_secondary; - rrdp_req_status_t rrdp_req_status; - bool primary_rrdp; - int upd_error; - int error; - - /* - * By default, RRDP has a greater priority than rsync. - * See "http.priority" default value. - */ - primary_rrdp = true; - (*retry_repo_sync) = true; - - /* - * RSYNC will always be present (at least for now, see - * rfc6487#section-4.8.8.1). If rsync is disabled, the cb will take - * care of that. - */ - if (sia_uris->rpkiNotify.uri == NULL) { - primary_rrdp = false; - error = rsync_cb(sia_uris); - if (!error) - return 0; - goto verify_mft; - } - - /* - * There isn't any restriction about the preferred access method of - * children CAs being the same as the parent CA. - * - * Two possible scenarios arise: - * 1) CA Parent didn't utilized (or didn't had) an RRDP update - * notification URI. - * 2) CA Parent successfully utilized an RRDP update notification URI. - * - * Step (1) is simple, do the check of the preferred access method. - * Step (2) must do something different. - * - If RRDP URI was already successfully visited, don't care - * preference, don't execute access method. - */ - error = db_rrdp_uris_get_request_status( - uri_get_global(sia_uris->rpkiNotify.uri), &rrdp_req_status); - if (error == 0 && rrdp_req_status == RRDP_URI_REQ_VISITED) { - (*retry_repo_sync) = false; - return replace_rrdp_mft_uri(&sia_uris->mft); - } - - /* Use CA's or configured priority? */ - if (config_get_rsync_priority() == config_get_http_priority()) - primary_rrdp = sia_uris->caRepository.position - > sia_uris->rpkiNotify.position; - else - primary_rrdp = config_get_rsync_priority() - < config_get_http_priority(); - - cb_primary = primary_rrdp ? rrdp_cb : rsync_cb; - cb_secondary = primary_rrdp ? rsync_cb : rrdp_cb; - - /* Try with the preferred; in case of error, try with the next one */ - error = cb_primary(sia_uris); - if (!error) { - (*retry_repo_sync) = !primary_rrdp; - return 0; - } - - if (primary_rrdp) { - if (error != -EPERM) - pr_val_info("Couldn't fetch data from RRDP repository '%s', trying to fetch data now from '%s'.", - uri_get_global(sia_uris->rpkiNotify.uri), - uri_get_global(sia_uris->caRepository.uri)); - else - pr_val_info("RRDP repository '%s' download/processing returned error previously, now I will try to fetch data from '%s'.", - uri_get_global(sia_uris->rpkiNotify.uri), - uri_get_global(sia_uris->caRepository.uri)); - } else { - pr_val_info("Couldn't fetch data from repository '%s', trying to fetch data now from RRDP '%s'.", - uri_get_global(sia_uris->caRepository.uri), - uri_get_global(sia_uris->rpkiNotify.uri)); - } - - /* Retry if rrdp was the first option but failed */ - (*retry_repo_sync) = primary_rrdp; - error = cb_secondary(sia_uris); - -verify_mft: - /* Reach here on error or when both access methods were utilized */ - switch (error) { - case 0: - break; - case EREQFAILED: - /* Log that we'll try to work with a local copy */ - pr_val_warn("Trying to work with the local cache files."); - (*retry_repo_sync) = false; - break; - case -EPERM: - /* - * Specific RRPD error: the URI error'd on the first try, so - * we'll keep trying with the local files - */ - (*retry_repo_sync) = false; - break; - default: - return error; - } - - /* Error and the primary access method was RRDP? Use its workspace */ - if (error && primary_rrdp) { - upd_error = replace_rrdp_mft_uri(&sia_uris->mft); - if (upd_error) - return upd_error; + struct rpki_uri **node, *uri; + array_index index; + + if (uris->rpp.len == 0) + return pr_val_err("SIA lacks both caRepository and rpkiNotify."); + + ARRAYLIST_FOREACH(&uris->rpp, node, index) { + uri = *node; + switch (uri_get_type(uri)) { + case UT_RSYNC: + if (cache_download(uri, NULL) == 0) + return 0; + break; + case UT_HTTPS: + if (rrdp_update(uri) == 0) + return 0; + break; + default: + pr_crit("Unknown URI type: %u", uri_get_type(uri)); + } } - /* Look for the manifest */ - return verify_mft_loc(sia_uris->mft.uri); + return pr_val_err("The RPP could not be downloaded."); } /** Boilerplate code for CA certificate validation and recursive traversal. */ @@ -2092,11 +1964,10 @@ certificate_traverse(struct rpp *rpp_parent, struct rpki_uri *cert_uri) int total_parents; STACK_OF(X509_CRL) *rpp_parent_crl; X509 *cert; - struct sia_ca_uris sia_uris; + struct sia_uris sia_uris; enum rpki_policy policy; enum cert_type certype; struct rpp *pp; - bool repo_retry; int error; state = state_retrieve(); @@ -2152,76 +2023,29 @@ certificate_traverse(struct rpp *rpp_parent, struct rpki_uri *cert_uri) if (error) goto revert_cert; - sia_ca_uris_init(&sia_uris); + sia_uris_init(&sia_uris); error = (certype == CERTYPE_TA) ? certificate_validate_extensions_ta(cert, &sia_uris, &policy) - : certificate_validate_extensions_ca(cert, &sia_uris, &policy, rpp_parent); + : certificate_validate_extensions_ca(cert, &sia_uris, &policy, + rpp_parent); if (error) goto revert_uris; - /* - * RFC 6481 section 5: "when the repository publication point contents - * are updated, a repository operator cannot assure RPs that the - * manifest contents and the repository contents will be precisely - * aligned at all times" - * - * Trying to avoid this issue, download the CA repository and validate - * manifest (and its content) again. - * - * Avoid to re-download the repo if the mft was fetched with RRDP. - */ - repo_retry = true; - error = use_access_method(&sia_uris, exec_rsync_method, - exec_rrdp_method, &repo_retry); + error = download_rpp(&sia_uris); if (error) goto revert_uris; - do { - /* Validate the manifest (@mft) pointed by the certificate */ - error = x509stack_push(validation_certstack(state), cert_uri, - cert, policy, certype); - if (error) - goto revert_uris; - - cert = NULL; /* Ownership stolen */ - - error = handle_manifest(sia_uris.mft.uri, &pp); - if (error == 0 || !repo_retry) - break; - - /* - * Don't reach here if: - * - Manifest is valid. - * - Working with local files due to a download error. - * - RRDP was utilized to fetch the manifest. - * - There was a previous attempt to re-fetch the repository. - */ - pr_val_info("Retrying repository download to discard 'transient inconsistency' manifest issue (see RFC 6481 section 5) '%s'", - uri_val_get_printable(sia_uris.caRepository.uri)); - error = rsync_download_files(sia_uris.caRepository.uri, true); - if (error) - break; - - /* Cancel stack, reload certificate (no need to revalidate) */ - x509stack_cancel(validation_certstack(state)); - error = certificate_load(cert_uri, &cert); - if (error) - goto revert_uris; - - repo_retry = false; - } while (true); - - if (error) { - x509stack_cancel(validation_certstack(state)); + error = handle_manifest(sia_uris.mft, &pp); + if (error) goto revert_uris; - } /* -- Validate & traverse the RPP (@pp) described by the manifest -- */ rpp_traverse(pp); - rpp_refput(pp); + revert_uris: - sia_ca_uris_cleanup(&sia_uris); + validation_set_notification_uri(state, NULL); + sia_uris_cleanup(&sia_uris); revert_cert: if (cert != NULL) X509_free(cert); diff --git a/src/object/manifest.c b/src/object/manifest.c index b95ff6da..f1c5037a 100644 --- a/src/object/manifest.c +++ b/src/object/manifest.c @@ -16,6 +16,19 @@ #include "object/roa.h" #include "object/signed_object.h" +static int +cage(struct rpki_uri **uri) +{ + if (validation_get_notification_uri(state_retrieve()) == NULL) { + /* No need to cage */ + uri_refget(*uri); + return 0; + } + + return __uri_create(uri, UT_CAGED, uri_get_global(*uri), + uri_get_global_len(*uri)); +} + static int decode_manifest(struct signed_object *sobj, struct Manifest **result) { @@ -167,8 +180,6 @@ build_rpp(struct Manifest *mft, struct rpki_uri *mft_uri, struct rpp **pp) fah = mft->fileList.list.array[i]; error = uri_create_mft(&uri, mft_uri, &fah->file); - if (error == ESKIP) - continue; /* * Not handling ENOTRSYNC is fine because the manifest URL * should have been RSYNC. Something went wrong if an RSYNC URL @@ -243,6 +254,9 @@ handle_manifest(struct rpki_uri *uri, struct rpp **pp) int error; /* Prepare */ + error = cage(&uri); /* ref++ */ + if (error) + return error; pr_val_debug("Manifest '%s' {", uri_val_get_printable(uri)); fnstack_push_uri(uri); @@ -291,5 +305,6 @@ handle_manifest(struct rpki_uri *uri, struct rpp **pp) revert_log: pr_val_debug("}"); fnstack_pop(); + uri_refput(uri); /* ref-- */ return error; } diff --git a/src/object/name.c b/src/object/name.c index 5c679062..b86cf45d 100644 --- a/src/object/name.c +++ b/src/object/name.c @@ -6,6 +6,7 @@ #include #include "alloc.h" +#include "cert_stack.h" #include "log.h" #include "thread_var.h" diff --git a/src/object/tal.c b/src/object/tal.c index 6fbcd2f5..28781c22 100644 --- a/src/object/tal.c +++ b/src/object/tal.c @@ -1,5 +1,3 @@ -#define _GNU_SOURCE - #include "tal.h" #include @@ -18,16 +16,13 @@ #include "config.h" #include "line_file.h" #include "log.h" -#include "random.h" #include "state.h" #include "thread_var.h" #include "validation_handler.h" #include "crypto/base64.h" -#include "http/http.h" #include "object/certificate.h" -#include "rsync/rsync.h" #include "rtr/db/vrps.h" -#include "rrdp/db/db_rrdp.h" +#include "cache/local_cache.h" #define TAL_FILE_EXTENSION ".tal" typedef int (*foreach_uri_cb)(struct tal *, struct rpki_uri *, void *); @@ -97,9 +92,12 @@ uris_add(struct uris *uris, char *uri) struct rpki_uri *new; int error; - error = uri_create_mixed_str(&new, uri, strlen(uri)); - if (error == ENOTSUPPORTED) - return pr_op_err("TAL has non-RSYNC/HTTPS URI."); + if (str_starts_with(uri, "rsync://")) + error = uri_create(&new, UT_RSYNC, uri); + else if (str_starts_with(uri, "https://")) + error = uri_create(&new, UT_HTTPS, uri); + else + error = pr_op_err("TAL has non-RSYNC/HTTPS URI: %s", uri); if (error) return error; @@ -384,63 +382,6 @@ foreach_uri(struct tal *tal, foreach_uri_cb cb, void *arg) return 0; } -static void -tal_shuffle_uris(struct tal *tal) -{ - struct rpki_uri **array = tal->uris.array; - struct rpki_uri *tmp; - unsigned int count = tal->uris.count; - 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; - } -} - -static void -tal_order_uris(struct tal *tal) -{ - struct rpki_uri **ordered; - struct rpki_uri **tmp; - bool http_first; - unsigned int i; - unsigned int last_rsync; - unsigned int last_https; - - /* First do the shuffle */ - if (config_get_shuffle_tal_uris()) - tal_shuffle_uris(tal); - - if (config_get_rsync_priority() == config_get_http_priority()) - return; - - /* Now order according to the priority */ - http_first = (config_get_http_priority() > config_get_rsync_priority()); - - ordered = pmalloc(tal->uris.size * sizeof(struct rpki_uri *)); - - last_rsync = (http_first ? tal->uris.https_count : 0); - last_https = (http_first ? 0 : tal->uris.rsync_count); - - for (i = 0; i < tal->uris.count; i++) { - if (uri_is_rsync(tal->uris.array[i])) - ordered[last_rsync++] = tal->uris.array[i]; - else - ordered[last_https++] = tal->uris.array[i]; - } - - /* Everything is ok, point to the ordered array */ - tmp = tal->uris.array; - tal->uris.array = ordered; - free(tmp); -} - char const * tal_get_file_name(struct tal *tal) { @@ -494,20 +435,7 @@ handle_tal_uri(struct tal *tal, struct rpki_uri *uri, void *arg) return ENSURE_NEGATIVE(error); if (thread->sync_files) { - if (uri_is_rsync(uri)) { - if (!config_get_rsync_enabled()) { - validation_destroy(state); - return 0; /* Try some other TAL URI */ - } - error = rsync_download_files(uri, false); - } else /* HTTPS */ { - if (!config_get_http_enabled()) { - validation_destroy(state); - return 0; /* Try some other TAL URI */ - } - error = http_download_file(uri); - } - + error = cache_download(uri, NULL); /* Reminder: there's a positive error: EREQFAILED */ if (error) { validation_destroy(state); @@ -535,12 +463,6 @@ handle_tal_uri(struct tal *tal, struct rpki_uri *uri, void *arg) goto fail; } - /* - * Set all RRDPs URIs to non-requested, this way we will force the - * request on every cycle (to check if there are updates). - */ - db_rrdp_uris_set_all_unvisited(); - /* Handle root certificate. */ error = certificate_traverse(NULL, uri); if (error) { @@ -607,8 +529,6 @@ do_file_validation(void *thread_arg) if (error) goto end; - tal_order_uris(tal); - error = foreach_uri(tal, handle_tal_uri, thread); if (error > 0) { error = 0; @@ -654,8 +574,6 @@ __do_file_validation(char const *tal_file, void *arg) struct tal_param *t_param = arg; struct validation_thread *thread; - db_rrdp_add_tal(tal_file); - thread = pmalloc(sizeof(struct validation_thread)); thread->tal_file = pstrdup(tal_file); @@ -678,9 +596,6 @@ perform_standalone_validation(struct thread_pool *pool, struct db_table *table) struct validation_thread *thread; int error; - /* Set existent tal RRDP info to non visited */ - db_rrdp_reset_visited_tals(); - param.pool = pool; param.db = table; SLIST_INIT(¶m.threads); @@ -711,12 +626,6 @@ perform_standalone_validation(struct thread_pool *pool, struct db_table *table) thread_destroy(thread); } - /* One thread has errors, validation can't keep the resulting table */ - if (error) - return error; - - /* Remove non-visited rrdps URIS by tal */ - db_rrdp_rem_nonvisited_tals(); - - return 0; + /* If one thread has errors, we can't keep the resulting table. */ + return error; } diff --git a/src/random.c b/src/random.c deleted file mode 100644 index 291ee37c..00000000 --- a/src/random.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "random.h" -#include -#include - -void -random_init(void) -{ - /* - * time() has second precision, which is fine. - * I don't think that anyone will legitimately need to run this program - * more than once a second. - */ - srandom(time(NULL)); -} - -/** - * Assumes 0 <= max <= RAND_MAX - * Returns in the closed interval [0, max] - * - * Source: https://stackoverflow.com/questions/2509679 - */ -long random_at_most(long max) -{ - /* max <= RAND_MAX < ULONG_MAX, so this is okay. */ - unsigned long num_bins = (unsigned long) max + 1; - unsigned long num_rand = (unsigned long) RAND_MAX + 1; - unsigned long bin_size = num_rand / num_bins; - unsigned long defect = num_rand % num_bins; - long x; - - do { - x = random(); - /* This is carefully written not to overflow */ - } while (num_rand - defect <= (unsigned long) x); - - /* Truncated division is intentional */ - return x / bin_size; -} diff --git a/src/random.h b/src/random.h deleted file mode 100644 index 1e837ce2..00000000 --- a/src/random.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef SRC_RANDOM_H_ -#define SRC_RANDOM_H_ - -void random_init(void); -long random_at_most(long max); - -#endif /* SRC_RANDOM_H_ */ diff --git a/src/resource.c b/src/resource.c index e1818a45..3a034d77 100644 --- a/src/resource.c +++ b/src/resource.c @@ -4,6 +4,7 @@ #include /* UINT32_MAX */ #include "alloc.h" +#include "cert_stack.h" #include "log.h" #include "sorted_array.h" #include "thread_var.h" diff --git a/src/rpp.h b/src/rpp.h index 72ddf7a9..50f3313e 100644 --- a/src/rpp.h +++ b/src/rpp.h @@ -1,6 +1,7 @@ #ifndef SRC_RPP_H_ #define SRC_RPP_H_ +#include #include "types/uri.h" struct rpp; diff --git a/src/rrdp/db/db_rrdp.c b/src/rrdp/db/db_rrdp.c deleted file mode 100644 index dbfcc22e..00000000 --- a/src/rrdp/db/db_rrdp.c +++ /dev/null @@ -1,224 +0,0 @@ -#include "rrdp/db/db_rrdp.h" - -#include -#include -#include -#include -#include -#include -#include "alloc.h" -#include "crypto/hash.h" -#include "common.h" -#include "log.h" - -struct tal_elem { - char *file_name; - struct db_rrdp_uri *uris; - bool visited; - SLIST_ENTRY(tal_elem) next; -}; - -SLIST_HEAD(tal_list, tal_elem); - -struct db_rrdp { - struct tal_list tals; -}; - -static struct db_rrdp db; - -/** Read/write lock, which protects @db. */ -static pthread_rwlock_t lock; - -/* - * Creates an ID for the RRDP local workspace. - * - * The ID is generated using the hash (sha-1) of @base. The first 4 bytes of - * the hash are "stringified" (8 chars) and a '/' is added at the end to - * (later) facilitate the concatenation of the ID at --local-repository. - * - * The ID is allocated at @result. - * - * TODO (#78) Improve and use this. - */ -static int -get_workspace_path(char const *base, char **result) -{ -/* SHA1 produces 20 bytes */ -#define HASH_LEN 20 -/* We'll use the first 4 bytes (8 chars) */ -#define OUT_LEN 8 - unsigned char *hash; - unsigned int hash_len; - unsigned int i; - char *tmp; - char *ptr; - int error; - - hash = pmalloc(HASH_LEN * sizeof(unsigned char)); - - hash_len = 0; - error = hash_str("sha1", base, hash, &hash_len); - if (error) { - free(hash); - return error; - } - - /* Get the first bytes + one slash + NUL char */ - tmp = pmalloc(OUT_LEN + 2); - - ptr = tmp; - for (i = 0; i < OUT_LEN / 2; i++) { - sprintf(ptr, "%02X", hash[i]); - ptr += 2; - } - tmp[OUT_LEN] = '/'; - tmp[OUT_LEN + 1] = '\0'; - - free(hash); - *result = tmp; - return 0; -} - -static struct tal_elem * -tal_elem_create(char const *name) -{ - struct tal_elem *result; - - result = pmalloc(sizeof(struct tal_elem)); - - result->uris = db_rrdp_uris_create(); - result->visited = true; - result->file_name = pstrdup(name); - - return result; -} - -static void -tal_elem_destroy(struct tal_elem *elem, bool remove_local) -{ - db_rrdp_uris_destroy(elem->uris); - free(elem->file_name); - free(elem); -} - -int -db_rrdp_init(void) -{ - int error; - - error = pthread_rwlock_init(&lock, NULL); - if (error) { - pr_op_err("DB RRDP pthread_rwlock_init() errored: %s", - strerror(error)); - return error; - } - - SLIST_INIT(&db.tals); - return 0; -} - -void -db_rrdp_cleanup(void) -{ - struct tal_elem *elem; - - while (!SLIST_EMPTY(&db.tals)) { - elem = db.tals.slh_first; - SLIST_REMOVE_HEAD(&db.tals, next); - tal_elem_destroy(elem, false); - } - pthread_rwlock_destroy(&lock); -} - -static struct tal_elem * -db_rrdp_find_tal(char const *tal_name) -{ - struct tal_elem *found; - - rwlock_read_lock(&lock); - SLIST_FOREACH(found, &db.tals, next) { - if (strcmp(tal_name, found->file_name) == 0) { - rwlock_unlock(&lock); - return found; - } - } - rwlock_unlock(&lock); - - return NULL; -} - -void -db_rrdp_add_tal(char const *tal_name) -{ - struct tal_elem *elem, *found; - - /* Element exists, no need to create it again */ - found = db_rrdp_find_tal(tal_name); - if (found != NULL) { - found->visited = true; - return; - } - - elem = tal_elem_create(tal_name); - - rwlock_write_lock(&lock); - SLIST_INSERT_HEAD(&db.tals, elem, next); - rwlock_unlock(&lock); -} - -void -db_rrdp_rem_tal(char const *tal_name) -{ - struct tal_elem *found; - - found = db_rrdp_find_tal(tal_name); - if (found == NULL) - return; - - rwlock_write_lock(&lock); - SLIST_REMOVE(&db.tals, found, tal_elem, next); - rwlock_unlock(&lock); - - tal_elem_destroy(found, true); -} - -/* Returns the reference to RRDP URIs of a TAL */ -struct db_rrdp_uri * -db_rrdp_get_uris(char const *tal_name) -{ - struct tal_elem *found; - - found = db_rrdp_find_tal(tal_name); - if (found == NULL) - pr_crit("db_rrdp_find_tal() returned NULL, means it hasn't been initialized"); - - return found->uris; -} - -/* Set all tals to non-visited */ -void -db_rrdp_reset_visited_tals(void) -{ - struct tal_elem *found; - - rwlock_write_lock(&lock); - SLIST_FOREACH(found, &db.tals, next) - found->visited = false; - rwlock_unlock(&lock); -} - -/* Remove non-visited tals */ -void -db_rrdp_rem_nonvisited_tals(void) -{ - struct tal_elem *found; - - rwlock_write_lock(&lock); - SLIST_FOREACH(found, &db.tals, next) { - if (!found->visited) { - SLIST_REMOVE(&db.tals, found, tal_elem, next); - tal_elem_destroy(found, true); - } - } - rwlock_unlock(&lock); -} diff --git a/src/rrdp/db/db_rrdp.h b/src/rrdp/db/db_rrdp.h deleted file mode 100644 index 84161dce..00000000 --- a/src/rrdp/db/db_rrdp.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef SRC_RRDP_DB_DB_RRDP_H_ -#define SRC_RRDP_DB_DB_RRDP_H_ - -#include "rrdp/db/db_rrdp_uris.h" - -int db_rrdp_init(void); -void db_rrdp_cleanup(void); - -void db_rrdp_add_tal(char const *); -void db_rrdp_rem_tal(char const *); - -struct db_rrdp_uri *db_rrdp_get_uris(char const *); - -void db_rrdp_reset_visited_tals(void); -void db_rrdp_rem_nonvisited_tals(void); - -#endif /* SRC_RRDP_DB_DB_RRDP_H_ */ diff --git a/src/rrdp/db/db_rrdp_uris.c b/src/rrdp/db/db_rrdp_uris.c deleted file mode 100644 index 427799ee..00000000 --- a/src/rrdp/db/db_rrdp_uris.c +++ /dev/null @@ -1,241 +0,0 @@ -#include "rrdp/db/db_rrdp_uris.h" - -#include -#include -#include -#include "data_structure/uthash.h" -#include "alloc.h" -#include "common.h" -#include "log.h" -#include "thread_var.h" - -struct uris_table { - /* Key */ - char *uri; - /* Last session ID and serial from the URI */ - struct global_data data; - /* Last local update of the URI (after a successful processing) */ - long last_update; - /* The URI has been requested (HTTPS) at this cycle? */ - rrdp_req_status_t request_status; - /* MFT URIs loaded from the @uri */ - struct visited_uris *visited_uris; - UT_hash_handle hh; -}; - -struct db_rrdp_uri { - struct uris_table *table; -}; - -static struct uris_table * -uris_table_create(char const *uri, char const *session_id, - unsigned long serial, rrdp_req_status_t req_status) -{ - struct uris_table *tmp; - - tmp = pzalloc(sizeof(struct uris_table)); /* Zero needed by uthash */ - - tmp->uri = pstrdup(uri); - tmp->data.session_id = pstrdup(session_id); - tmp->data.serial = serial; - tmp->last_update = 0; - tmp->request_status = req_status; - tmp->visited_uris = NULL; - - return tmp; -} - -static void -uris_table_destroy(struct uris_table *uri) -{ - visited_uris_refput(uri->visited_uris); - free(uri->data.session_id); - free(uri->uri); - free(uri); -} - -static struct uris_table * -find_rrdp_uri(struct db_rrdp_uri *uris, const char *search) -{ - struct uris_table *found; - HASH_FIND_STR(uris->table, search, found); - return found; -} - -static void -add_rrdp_uri(struct db_rrdp_uri *uris, struct uris_table *new_uri) -{ - struct uris_table *old_uri; - - HASH_REPLACE_STR(uris->table, uri, new_uri, old_uri); - if (old_uri != NULL) - uris_table_destroy(old_uri); -} - -static struct db_rrdp_uri * -get_thread_rrdp_uris(void) -{ - return validation_get_rrdp_uris(state_retrieve()); -} - -struct db_rrdp_uri * -db_rrdp_uris_create(void) -{ - struct db_rrdp_uri *tmp; - - tmp = pmalloc(sizeof(struct db_rrdp_uri)); - tmp->table = NULL; - - return tmp; -} - -void -db_rrdp_uris_destroy(struct db_rrdp_uri *uris) -{ - struct uris_table *uri_node, *uri_tmp; - - HASH_ITER(hh, uris->table, uri_node, uri_tmp) { - HASH_DEL(uris->table, uri_node); - uris_table_destroy(uri_node); - } - free(uris); -} - -rrdp_uri_cmp_result_t -db_rrdp_uris_cmp(char const *uri, char const *session_id, unsigned long serial) -{ - struct uris_table *found; - - found = find_rrdp_uri(get_thread_rrdp_uris(), uri); - if (found == NULL) { - pr_val_debug("I don't have state for this Update Notification; downloading snapshot..."); - return RRDP_URI_NOTFOUND; - } - - if (strcmp(session_id, found->data.session_id) != 0) { - pr_val_debug("session_id changed from '%s' to '%s'.", - found->data.session_id, session_id); - return RRDP_URI_DIFF_SESSION; - } - - if (serial != found->data.serial) { - pr_val_debug("The serial changed from %lu to %lu.", - found->data.serial, serial); - return RRDP_URI_DIFF_SERIAL; - } - - pr_val_debug("The new Update Notification has the same session_id (%s) and serial (%lu) as the old one.", - session_id, serial); - return RRDP_URI_EQUAL; -} - -void -db_rrdp_uris_update(char const *uri, char const *session_id, - unsigned long serial, rrdp_req_status_t req_status, - struct visited_uris *visited_uris) -{ - struct uris_table *db_uri; - - db_uri = uris_table_create(uri, session_id, serial, req_status); - db_uri->visited_uris = visited_uris; /* Ownership transfered */ - add_rrdp_uri(get_thread_rrdp_uris(), db_uri); -} - -int -db_rrdp_uris_get_serial(char const *uri, unsigned long *serial) -{ - struct uris_table *found; - - found = find_rrdp_uri(get_thread_rrdp_uris(), uri); - if (found == NULL) - return -ENOENT; - - *serial = found->data.serial; - return 0; -} - -int -db_rrdp_uris_get_last_update(char const *uri, long *date) -{ - struct uris_table *found; - - found = find_rrdp_uri(get_thread_rrdp_uris(), uri); - if (found == NULL) - return -ENOENT; - - *date = found->last_update; - return 0; -} - -/* Set the last update to now */ -int -db_rrdp_uris_set_last_update(char const *uri) -{ - struct uris_table *found; - time_t now; - int error; - - found = find_rrdp_uri(get_thread_rrdp_uris(), uri); - if (found == NULL) - return -ENOENT; - - now = 0; - error = get_current_time(&now); - if (error) - return error; - - found->last_update = (long)now; - return 0; -} - -int -db_rrdp_uris_get_request_status(char const *uri, rrdp_req_status_t *result) -{ - struct uris_table *found; - - found = find_rrdp_uri(get_thread_rrdp_uris(), uri); - if (found == NULL) - return -ENOENT; - - *result = found->request_status; - return 0; -} - -int -db_rrdp_uris_set_request_status(char const *uri, rrdp_req_status_t value) -{ - struct uris_table *found; - - found = find_rrdp_uri(get_thread_rrdp_uris(), uri); - if (found == NULL) - return -ENOENT; - - found->request_status = value; - return 0; -} - -void -db_rrdp_uris_set_all_unvisited(void) -{ - struct db_rrdp_uri *uris; - struct uris_table *uri_node, *uri_tmp; - - uris = get_thread_rrdp_uris(); - HASH_ITER(hh, uris->table, uri_node, uri_tmp) - uri_node->request_status = RRDP_URI_REQ_UNVISITED; -} - -/* - * Returns a pointer to the visited_uris of the current thread. - */ -struct visited_uris * -db_rrdp_uris_get_visited_uris(char const *uri) -{ - struct uris_table *found; - - found = find_rrdp_uri(get_thread_rrdp_uris(), uri); - if (found == NULL) - return NULL; - - return found->visited_uris; -} diff --git a/src/rrdp/db/db_rrdp_uris.h b/src/rrdp/db/db_rrdp_uris.h deleted file mode 100644 index ecda622e..00000000 --- a/src/rrdp/db/db_rrdp_uris.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef SRC_RRDP_DB_DB_RRDP_URIS_H_ -#define SRC_RRDP_DB_DB_RRDP_URIS_H_ - -#include -#include "rrdp/rrdp_objects.h" -#include "visited_uris.h" - -typedef enum { - RRDP_URI_REQ_ERROR, - RRDP_URI_REQ_UNVISITED, - RRDP_URI_REQ_VISITED, -} rrdp_req_status_t; - -/* - * RRDP URI fetched from 'rpkiNotify' OID at a CA certificate, each TAL thread - * may have a reference to one of these (it holds information such as update - * notification URI, session ID, serial, visited mft uris). - */ -struct db_rrdp_uri; - -struct db_rrdp_uri *db_rrdp_uris_create(void); -void db_rrdp_uris_destroy(struct db_rrdp_uri *); - -rrdp_uri_cmp_result_t db_rrdp_uris_cmp(char const *, char const *, - unsigned long); -void db_rrdp_uris_update(char const *, char const *session_id, unsigned long, - rrdp_req_status_t, struct visited_uris *); -int db_rrdp_uris_get_serial(char const *, unsigned long *); - -int db_rrdp_uris_get_last_update(char const *, long *); -int db_rrdp_uris_set_last_update(char const *); - -int db_rrdp_uris_get_request_status(char const *, rrdp_req_status_t *); -int db_rrdp_uris_set_request_status(char const *, rrdp_req_status_t); -void db_rrdp_uris_set_all_unvisited(void); - -struct visited_uris *db_rrdp_uris_get_visited_uris(char const *); - -char const *db_rrdp_uris_workspace_get(void); - -#endif /* SRC_RRDP_DB_DB_RRDP_URIS_H_ */ diff --git a/src/rrdp/rrdp_loader.c b/src/rrdp/rrdp_loader.c index e73a2d5e..0b03fa90 100644 --- a/src/rrdp/rrdp_loader.c +++ b/src/rrdp/rrdp_loader.c @@ -1,90 +1,42 @@ #include "rrdp_loader.h" -#include "rrdp/db/db_rrdp_uris.h" -#include "rrdp/rrdp_objects.h" -#include "rrdp/rrdp_parser.h" -#include "rsync/rsync.h" -#include "common.h" -#include "config.h" +#include +#include +#include + #include "log.h" #include "thread_var.h" -#include "visited_uris.h" - -/* Fetch and process the deltas from the @notification */ -static int -process_diff_serial(struct update_notification *notification, - struct visited_uris **visited) -{ - unsigned long serial; - int error; - - error = db_rrdp_uris_get_serial(notification->uri, &serial); - if (error) - return error; - - /* Work with the existent visited uris */ - *visited = db_rrdp_uris_get_visited_uris(notification->uri); - if ((*visited) == NULL) - return -ENOENT; - - return rrdp_process_deltas(notification, serial, *visited); -} +#include "rrdp/rrdp_objects.h" +#include "rrdp/rrdp_parser.h" +#include "cache/local_cache.h" -/* Fetch and process the snapshot from the @notification */ static int -process_snapshot(struct update_notification *notification, - struct visited_uris **visited) +get_metadata(struct rpki_uri *uri, struct notification_metadata *result) { - struct visited_uris *tmp; + struct stat st; + struct update_notification notification; int error; - /* Use a new allocated visited_uris struct */ - tmp = visited_uris_create(); + result->session_id = NULL; + result->serial = 0; - error = rrdp_parse_snapshot(notification, tmp); - if (error) { - visited_uris_refput(tmp); - return error; + if (stat(uri_get_local(uri), &st) != 0) { + error = errno; + return (error == ENOENT) ? 0 : error; } - *visited = tmp; - return 0; -} - -static int -remove_rrdp_uri_files(char const *notification_uri) -{ - struct visited_uris *tmp; - - /* Work with the existent visited uris */ - tmp = db_rrdp_uris_get_visited_uris(notification_uri); - if (tmp == NULL) - return -ENOENT; - - return visited_uris_delete_local(tmp); -} - -/* Mark the URI as errored with dummy data, so it won't be requested again */ -static void -mark_rrdp_uri_request_err(char const *notification_uri) -{ - pr_val_debug("RRDP data of '%s' won't be requested again during this cycle due to previous error.", - notification_uri); - db_rrdp_uris_update(notification_uri, "", 0, RRDP_URI_REQ_ERROR, - visited_uris_create()); -} - -static int -process_diff_session(struct update_notification *notification, - struct visited_uris **visited) -{ - int error; - - error = remove_rrdp_uri_files(notification->uri); + /* + * TODO (fine) optimize by not reading everything, + * or maybe keep it if it doesn't change. + */ + error = rrdp_parse_notification(uri, ¬ification); if (error) return error; - return process_snapshot(notification, visited); + *result = notification.meta; + + update_notification_destroy(¬ification); + return 0; } /* @@ -96,205 +48,68 @@ process_diff_session(struct update_notification *notification, * Calling code can then access the files, just as if they had been downloaded * via rsync. */ -static int -__rrdp_load(struct rpki_uri *uri, bool force_snapshot, bool *data_updated) +int +rrdp_update(struct rpki_uri *uri) { - struct update_notification *upd_notification; - struct visited_uris *visited; - rrdp_req_status_t requested; - rrdp_uri_cmp_result_t res; + struct notification_metadata old; + struct update_notification new; + bool changed; int error; - (*data_updated) = false; + if (uri == NULL || !uri_is_https(uri)) + pr_crit("Wrong call, trying to parse a non HTTPS URI"); -#ifndef DEBUG_RRDP - /* - * In normal mode (DEBUG_RRDP disabled), RRDP files (notifications, - * snapshots and deltas) are not cached. - * I think it was implemented this way to prevent the cache from growing - * indefinitely. (Because otherwise Fort would lose track of RRDP files - * from disappearing CAs. RRDP files are designed to be relevant on - * single validation runs anyway.) - * Note that __rrdp_load() includes the RRDP file explosion. Exploded - * files (manifests, certificates, ROAs and ghostbusters) are cached as - * usual. - * - * Therefore, in normal offline mode, the entirety of __rrdp_load() - * needs to be skipped because it would otherwise error out while - * attempting to access the nonexistent RRDP files. - * - * But if you need to debug RRDP files specifically, their persistent - * deletions will force you to debug them in online mode. - * - * That's why DEBUG_RRDP exists. When it's enabled, RRDP files will not - * be deleted, and config_get_http_enabled() will kick off during - * __http_download_file(). This will allow you to reach the RRDP file - * parsing code in offline mode. - * - * I know this is somewhat convoluted, but I haven't found a more - * elegant way to do it. - * - * Simple enable example: `make FORT_FLAGS=-DDEBUG_RRDP` - */ - if (!config_get_http_enabled()) { - (*data_updated) = true; - return 0; - } -#endif + fnstack_push_uri(uri); + pr_val_debug("Processing notification."); - /* Avoid multiple requests on the same run */ - requested = RRDP_URI_REQ_UNVISITED; - error = db_rrdp_uris_get_request_status(uri_get_global(uri), - &requested); - if (error && error != -ENOENT) - return error; + error = get_metadata(uri, &old); + if (error) + goto end; + pr_val_debug("Old session/serial: %s/%lu", old.session_id, old.serial); - if (!force_snapshot) { - switch(requested) { - case RRDP_URI_REQ_VISITED: - (*data_updated) = true; - return 0; - case RRDP_URI_REQ_UNVISITED: - break; - case RRDP_URI_REQ_ERROR: - /* Log has been done before this call */ - return -EPERM; - } - } else { - if (requested != RRDP_URI_REQ_VISITED) { - pr_val_info("Skipping RRDP snapshot reload"); - return -EINVAL; - } + error = cache_download(uri, &changed); + if (error) + goto end; + if (!changed) { + pr_val_debug("The Notification has not changed."); + goto end; } - pr_val_debug("Downloading RRDP Update Notification..."); - error = rrdp_parse_notification(uri, force_snapshot, &upd_notification); + error = rrdp_parse_notification(uri, &new); if (error) - goto upd_end; - - if (upd_notification == NULL) { - pr_val_debug("The Update Notification has not changed."); - return 0; + goto end; /* FIXME fall back to previous? */ + pr_val_debug("New session/serial: %s/%lu", new.meta.session_id, + new.meta.serial); + + if (old.session_id == NULL) { + pr_val_debug("This is a new Notification."); + error = rrdp_parse_snapshot(&new); + goto revert_notification; } - pr_val_debug("The Update Notification changed."); + if (strcmp(old.session_id, new.meta.session_id) != 0) { + pr_val_debug("The Notification's session ID changed."); + error = rrdp_parse_snapshot(&new); + goto revert_notification; + } - do { - /* Same flow as a session update */ - if (force_snapshot) { - error = process_diff_session(upd_notification, - &visited); - if (error) - goto upd_destroy; - (*data_updated) = true; - break; - } + if (old.serial != new.meta.serial) { + pr_val_debug("The Notification' serial changed."); + error = rrdp_process_deltas(&new, old.serial); + goto revert_notification; + } - res = db_rrdp_uris_cmp(uri_get_global(uri), - upd_notification->global_data.session_id, - upd_notification->global_data.serial); + pr_val_debug("The Notification changed, but the session ID and serial didn't."); - switch (res) { - case RRDP_URI_EQUAL: - goto set_update; - case RRDP_URI_DIFF_SESSION: - /* Delete the old session files */ - error = process_diff_session(upd_notification, - &visited); - if (error) - goto upd_destroy; - (*data_updated) = true; - break; - case RRDP_URI_DIFF_SERIAL: - error = process_diff_serial(upd_notification, &visited); - if (!error) { - visited_uris_refget(visited); - (*data_updated) = true; - break; - } - /* Something went wrong, use snapshot */ - pr_val_info("There was an error processing RRDP deltas, using the snapshot instead."); - case RRDP_URI_NOTFOUND: - error = process_snapshot(upd_notification, &visited); - if (error) - goto upd_destroy; - (*data_updated) = true; - break; - default: - pr_crit("Unexpected RRDP URI comparison result"); - } - } while (0); +revert_notification: + update_notification_destroy(&new); - /* Any update, and no error during the process, update db as well */ - pr_val_debug("Updating local RRDP data of '%s' to:", uri_get_global(uri)); - pr_val_debug("- Session ID: %s", upd_notification->global_data.session_id); - pr_val_debug("- Serial: %lu", upd_notification->global_data.serial); - db_rrdp_uris_update(uri_get_global(uri), - upd_notification->global_data.session_id, - upd_notification->global_data.serial, - RRDP_URI_REQ_VISITED, - visited); +end: notification_metadata_cleanup(&old); -set_update: - /* Set the last update to now */ - pr_val_debug("Set last update of RRDP data of '%s' to now.", - uri_get_global(uri)); - db_rrdp_uris_set_last_update(uri_get_global(uri)); -upd_destroy: - if (upd_notification != NULL) - update_notification_destroy(upd_notification); -upd_end: - /* Just return on success */ + /* TODO (fine) hideous function side effect; find a better way. */ if (!error) - return 0; - - /* Request failed, store the repository URI */ - if (error != EREQFAILED) { - /* Reset RSYNC visited URIs, this may force the update */ - /* TODO um, what? */ - reset_downloaded(); - } - - mark_rrdp_uri_request_err(uri_get_global(uri)); + validation_set_notification_uri(state_retrieve(), uri); + fnstack_pop(); return error; } - -/* - * Try to get RRDP Update Notification file and process it accordingly. - * - * If there's an error that could lead to an inconsistent local repository - * state, marks the @uri as error'd so that it won't be requested again during - * the same validation cycle. - * - * If there are no errors, updates the local DB and marks the @uri as visited. - * - * If the @uri is being visited again, verify its previous visit state. If there - * were no errors, just return success; otherwise, return error code -EPERM. - * - * @data_updated will be true if: - * - Delta files were processed, - * - Snapshot file was processed, - * - or @uri was already visited at this cycle - */ -int -rrdp_load(struct rpki_uri *uri, bool *data_updated) -{ - return __rrdp_load(uri, false, data_updated); -} - -/* - * Force the processing of the snapshot. The update notification is requested - * again, omitting the 'If-Modified-Since' header at the HTTP request. - * - * Shouldn't be called if @uri had a previous error or hasn't been requested, - * still the check is done. - */ -int -rrdp_reload_snapshot(struct rpki_uri *uri) -{ - bool tmp; - - tmp = false; - return __rrdp_load(uri, true, &tmp); -} diff --git a/src/rrdp/rrdp_loader.h b/src/rrdp/rrdp_loader.h index 838e4fab..9afd9f6b 100644 --- a/src/rrdp/rrdp_loader.h +++ b/src/rrdp/rrdp_loader.h @@ -1,10 +1,8 @@ #ifndef SRC_RRDP_RRDP_LOADER_H_ #define SRC_RRDP_RRDP_LOADER_H_ -#include #include "types/uri.h" -int rrdp_load(struct rpki_uri *, bool *); -int rrdp_reload_snapshot(struct rpki_uri *); +int rrdp_update(struct rpki_uri *); #endif /* SRC_RRDP_RRDP_LOADER_H_ */ diff --git a/src/rrdp/rrdp_objects.c b/src/rrdp/rrdp_objects.c index 0c7ee998..b34f1a29 100644 --- a/src/rrdp/rrdp_objects.c +++ b/src/rrdp/rrdp_objects.c @@ -5,34 +5,35 @@ #include #include "alloc.h" #include "log.h" +#include "types/uri.h" DEFINE_ARRAY_LIST_FUNCTIONS(deltas_head, struct delta_head, ) void -global_data_init(struct global_data *data) +notification_metadata_init(struct notification_metadata *meta) { - data->session_id = NULL; + meta->session_id = NULL; } void -global_data_cleanup(struct global_data *data) +notification_metadata_cleanup(struct notification_metadata *meta) { - free(data->session_id); + free(meta->session_id); } void -doc_data_init(struct doc_data *data) +metadata_init(struct file_metadata *meta) { - data->uri = NULL; - data->hash = NULL; - data->hash_len = 0; + meta->uri = NULL; + meta->hash = NULL; + meta->hash_len = 0; } void -doc_data_cleanup(struct doc_data *data) +metadata_cleanup(struct file_metadata *meta) { - free(data->hash); - free(data->uri); + free(meta->hash); + free(meta->uri); } /* Do the @cb to the delta head elements from @from_serial to @max_serial */ @@ -114,35 +115,29 @@ deltas_head_sort(struct deltas_head *deltas, unsigned long max_serial) return 0; } -struct update_notification * -update_notification_create(char const *uri) +void +update_notification_init(struct update_notification *notification, + struct rpki_uri *uri) { - struct update_notification *result; - - result = pmalloc(sizeof(struct update_notification)); - - global_data_init(&result->global_data); - doc_data_init(&result->snapshot); - deltas_head_init(&result->deltas_list); - result->uri = pstrdup(uri); - - return result; + notification_metadata_init(¬ification->meta); + metadata_init(¬ification->snapshot); + deltas_head_init(¬ification->deltas_list); + notification->uri = uri_refget(uri); } static void delta_head_destroy(struct delta_head *delta) { - doc_data_cleanup(&delta->doc_data); + metadata_cleanup(&delta->meta); } void update_notification_destroy(struct update_notification *file) { - doc_data_cleanup(&file->snapshot); - global_data_cleanup(&file->global_data); + metadata_cleanup(&file->snapshot); + notification_metadata_cleanup(&file->meta); deltas_head_cleanup(&file->deltas_list, delta_head_destroy); - free(file->uri); - free(file); + uri_refput(file->uri); } struct snapshot * @@ -151,7 +146,7 @@ snapshot_create(void) struct snapshot *tmp; tmp = pmalloc(sizeof(struct snapshot)); - global_data_init(&tmp->global_data); + notification_metadata_init(&tmp->meta); return tmp; } @@ -159,7 +154,7 @@ snapshot_create(void) void snapshot_destroy(struct snapshot *file) { - global_data_cleanup(&file->global_data); + notification_metadata_cleanup(&file->meta); free(file); } @@ -169,7 +164,7 @@ delta_create(void) struct delta *tmp; tmp = pmalloc(sizeof(struct delta)); - global_data_init(&tmp->global_data); + notification_metadata_init(&tmp->meta); return tmp; } @@ -177,7 +172,7 @@ delta_create(void) void delta_destroy(struct delta *file) { - global_data_cleanup(&file->global_data); + notification_metadata_cleanup(&file->meta); free(file); } @@ -187,7 +182,7 @@ publish_create(void) struct publish *tmp; tmp = pmalloc(sizeof(struct publish)); - doc_data_init(&tmp->doc_data); + metadata_init(&tmp->meta); tmp->content = NULL; tmp->content_len = 0; @@ -197,7 +192,7 @@ publish_create(void) void publish_destroy(struct publish *file) { - doc_data_cleanup(&file->doc_data); + metadata_cleanup(&file->meta); free(file->content); free(file); } @@ -208,7 +203,7 @@ withdraw_create(void) struct withdraw *tmp; tmp = pmalloc(sizeof(struct withdraw)); - doc_data_init(&tmp->doc_data); + metadata_init(&tmp->meta); return tmp; } @@ -216,6 +211,6 @@ withdraw_create(void) void withdraw_destroy(struct withdraw *file) { - doc_data_cleanup(&file->doc_data); + metadata_cleanup(&file->meta); free(file); } diff --git a/src/rrdp/rrdp_objects.h b/src/rrdp/rrdp_objects.h index f9a691db..e2c4b46e 100644 --- a/src/rrdp/rrdp_objects.h +++ b/src/rrdp/rrdp_objects.h @@ -5,29 +5,14 @@ #include #include "data_structure/array_list.h" -/* Possible results for an RRDP URI comparison */ -typedef enum { - /* The URI exists and has the same session ID and serial */ - RRDP_URI_EQUAL, - - /* The URI exists but has distinct serial */ - RRDP_URI_DIFF_SERIAL, - - /* The URI exists but has distinct session ID */ - RRDP_URI_DIFF_SESSION, - - /* The URI doesn't exists */ - RRDP_URI_NOTFOUND, -} rrdp_uri_cmp_result_t; - /* Global RRDP files data */ -struct global_data { +struct notification_metadata { char *session_id; unsigned long serial; }; /* Specific RRDP files data, in some cases the hash can be omitted */ -struct doc_data { +struct file_metadata { char *uri; unsigned char *hash; size_t hash_len; @@ -35,14 +20,14 @@ struct doc_data { /* Represents a element */ struct publish { - struct doc_data doc_data; + struct file_metadata meta; unsigned char *content; size_t content_len; }; /* Represents a element */ struct withdraw { - struct doc_data doc_data; + struct file_metadata meta; }; /* @@ -50,7 +35,7 @@ struct withdraw { * Publish/withdraw list aren't remember, they are processed ASAP. */ struct delta { - struct global_data global_data; + struct notification_metadata meta; }; /* @@ -58,7 +43,7 @@ struct delta { * Publish list isn't remember, is processed ASAP. */ struct snapshot { - struct global_data global_data; + struct notification_metadata meta; }; /* Delta element located at an update notification file */ @@ -68,7 +53,7 @@ struct delta_head { * so we should probably handle it as a string. */ unsigned long serial; - struct doc_data doc_data; + struct file_metadata meta; }; /* List of deltas inside an update notification file */ @@ -77,19 +62,19 @@ DECLARE_ARRAY_LIST_FUNCTIONS(deltas_head, struct delta_head) /* Update notification file content and location URI */ struct update_notification { - struct global_data global_data; - struct doc_data snapshot; + struct notification_metadata meta; + struct file_metadata snapshot; struct deltas_head deltas_list; - char *uri; + struct rpki_uri *uri; }; -void global_data_init(struct global_data *); -void global_data_cleanup(struct global_data *); +void notification_metadata_init(struct notification_metadata *); +void notification_metadata_cleanup(struct notification_metadata *); -void doc_data_init(struct doc_data *); -void doc_data_cleanup(struct doc_data *); +void metadata_init(struct file_metadata *); +void metadata_cleanup(struct file_metadata *); -struct update_notification *update_notification_create(char const *); +void update_notification_init(struct update_notification *, struct rpki_uri *); void update_notification_destroy(struct update_notification *); typedef int (*delta_head_cb)(struct delta_head *, void *); diff --git a/src/rrdp/rrdp_parser.c b/src/rrdp/rrdp_parser.c index 702837ae..06c1b1ce 100644 --- a/src/rrdp/rrdp_parser.c +++ b/src/rrdp/rrdp_parser.c @@ -9,16 +9,15 @@ #include #include -#include "rrdp/db/db_rrdp_uris.h" #include "crypto/base64.h" #include "crypto/hash.h" -#include "http/http.h" #include "xml/relax_ng.h" #include "alloc.h" #include "common.h" #include "file.h" #include "log.h" #include "thread_var.h" +#include "cache/local_cache.h" /* XML Common Namespace of files */ #define RRDP_NAMESPACE "http://www.ripe.net/rpki/rrdp" @@ -42,9 +41,7 @@ struct rdr_snapshot_ctx { /* Data being parsed */ struct snapshot *snapshot; /* Parent data to validate session ID and serial */ - struct update_notification *parent; - /* Visited URIs related to this thread */ - struct visited_uris *visited_uris; + struct update_notification *notification; }; /* Context while reading a delta */ @@ -52,56 +49,16 @@ struct rdr_delta_ctx { /* Data being parsed */ struct delta *delta; /* Parent data to validate session ID */ - struct update_notification *parent; + struct update_notification *notification; /* Current serial loaded from update notification deltas list */ unsigned long expected_serial; - /* Visited URIs related to this thread */ - struct visited_uris *visited_uris; }; -/* Args to send on update (snapshot/delta) files parsing */ -struct proc_upd_args { - struct update_notification *parent; - struct visited_uris *visited_uris; -}; - -static void -add_mft_to_list(struct visited_uris *visited_uris, char const *uri) -{ - if (strcmp(".mft", strrchr(uri, '.')) == 0) - visited_uris_add(visited_uris, uri); -} - -static int -rem_mft_from_list(struct visited_uris *visited_uris, char const *uri) -{ - if (strcmp(".mft", strrchr(uri, '.')) != 0) - return 0; - - return visited_uris_remove(visited_uris, uri); -} - -static int -download_file(struct rpki_uri *uri, long last_update) -{ - int error; - - if (last_update > 0) - error = http_download_file_with_ims(uri, last_update); - else - error = http_download_file(uri); - - /* - * Since distinct files can be downloaded (notification, snapshot, - * delta) just return the error and let the caller to add only the - * update notification URI to the request errors DB. - */ - if (error == -EREQFAILED) - return EREQFAILED; - - /* Remember: positive values are expected */ - return error; -} +typedef enum { + HR_MANDATORY, + HR_OPTIONAL, + HR_IGNORE, +} hash_requirement; /* Left trim @from, setting the result at @result pointer */ static int @@ -275,7 +232,7 @@ parse_long(xmlTextReaderPtr reader, char const *attr, unsigned long *result) } static int -parse_hex_string(xmlTextReaderPtr reader, bool required, char const *attr, +parse_hex_string(xmlTextReaderPtr reader, hash_requirement hr, char const *attr, unsigned char **result, size_t *result_len) { xmlChar *xml_value; @@ -286,8 +243,8 @@ parse_hex_string(xmlTextReaderPtr reader, bool required, char const *attr, xml_value = xmlTextReaderGetAttribute(reader, BAD_CAST attr); if (xml_value == NULL) - return required ? - pr_val_err("RRDP file: Couldn't find xml attribute '%s'", attr) + return (hr == HR_MANDATORY) + ? pr_val_err("RRDP file: Couldn't find xml attribute '%s'", attr) : 0; /* The rest of the checks are done at the schema */ @@ -334,7 +291,7 @@ validate_version(xmlTextReaderPtr reader, unsigned long expected) /* @gdata elements are allocated */ static int -parse_global_data(xmlTextReaderPtr reader, struct global_data *gdata, +parse_metadata(xmlTextReaderPtr reader, struct notification_metadata *meta, char const *expected_session, unsigned long expected_serial) { char *session_id; @@ -388,15 +345,20 @@ parse_global_data(xmlTextReaderPtr reader, struct global_data *gdata, } return_val: - gdata->session_id = session_id; - gdata->serial = serial; + meta->session_id = session_id; + meta->serial = serial; return error; } -/* @data elements are allocated */ +/* + * Extracts the following two attributes from @reader's current tag: + * + * 1. "uri" + * 2. "hash" (optional, depending on @hr) + */ static int -parse_doc_data(xmlTextReaderPtr reader, bool parse_hash, bool hash_req, - struct doc_data *data) +parse_doc_data(xmlTextReaderPtr reader, hash_requirement hr, + struct file_metadata *data) { char *uri; unsigned char *hash; @@ -411,22 +373,15 @@ parse_doc_data(xmlTextReaderPtr reader, bool parse_hash, bool hash_req, if (error) return error; - if (!parse_hash) + if (hr == HR_IGNORE) goto end; - error = parse_hex_string(reader, hash_req, RRDP_ATTR_HASH, &hash, - &hash_len); + error = parse_hex_string(reader, hr, RRDP_ATTR_HASH, &hash, &hash_len); if (error) { free(uri); return error; } end: - /* Function called just to do the validation */ - if (data == NULL) { - free(hash); - free(uri); - return 0; - } data->uri = uri; data->hash = hash; data->hash_len = hash_len; @@ -434,25 +389,24 @@ parse_doc_data(xmlTextReaderPtr reader, bool parse_hash, bool hash_req, } static int -parse_publish(xmlTextReaderPtr reader, bool parse_hash, bool hash_required, +parse_publish(xmlTextReaderPtr reader, hash_requirement hr, struct publish **publish) { - struct publish *tmp; + struct publish *result; struct rpki_uri *uri; char *base64_str; int error; - tmp = publish_create(); + result = publish_create(); - error = parse_doc_data(reader, parse_hash, hash_required, - &tmp->doc_data); + error = parse_doc_data(reader, hr, &result->meta); if (error) goto release_tmp; /* Read the text */ if (xmlTextReaderRead(reader) != 1) { error = pr_val_err("Couldn't read publish content of element '%s'", - tmp->doc_data.uri); + result->meta.uri); goto release_tmp; } @@ -460,37 +414,36 @@ parse_publish(xmlTextReaderPtr reader, bool parse_hash, bool hash_required, if (error) goto release_tmp; - error = base64_read(base64_str, &tmp->content, &tmp->content_len); + error = base64_read(base64_str, &result->content, &result->content_len); if (error) goto release_base64; /* rfc8181#section-2.2 but considering optional hash */ uri = NULL; - if (tmp->doc_data.hash_len > 0) { + if (result->meta.hash_len > 0) { /* Get the current file from the uri */ - error = uri_create_rsync_str_rrdp(&uri, tmp->doc_data.uri, - strlen(tmp->doc_data.uri)); + error = uri_create(&uri, UT_CAGED, result->meta.uri); if (error) goto release_base64; - error = hash_validate_file(uri, tmp->doc_data.hash, - tmp->doc_data.hash_len); + error = hash_validate_file(uri, result->meta.hash, + result->meta.hash_len); uri_refput(uri); if (error != 0) { pr_val_info("Hash of base64 decoded element from URI '%s' doesn't match element hash", - tmp->doc_data.uri); + result->meta.uri); error = EINVAL; goto release_base64; } } free(base64_str); - *publish = tmp; + *publish = result; return 0; release_base64: free(base64_str); release_tmp: - publish_destroy(tmp); + publish_destroy(result); return error; } @@ -503,18 +456,17 @@ parse_withdraw(xmlTextReaderPtr reader, struct withdraw **withdraw) tmp = withdraw_create(); - error = parse_doc_data(reader, true, true, &tmp->doc_data); + error = parse_doc_data(reader, HR_MANDATORY, &tmp->meta); if (error) goto release_tmp; /* rfc8181#section-2.2, get the file from the uri */ - error = uri_create_rsync_str_rrdp(&uri, tmp->doc_data.uri, - strlen(tmp->doc_data.uri)); + error = uri_create(&uri, UT_CAGED, tmp->meta.uri); if (error) goto release_tmp; - error = hash_validate_file(uri, tmp->doc_data.hash, - tmp->doc_data.hash_len); + error = hash_validate_file(uri, tmp->meta.hash, + tmp->meta.hash_len); if (error) goto release_uri; @@ -529,8 +481,7 @@ parse_withdraw(xmlTextReaderPtr reader, struct withdraw **withdraw) } static int -write_from_uri(char const *location, unsigned char *content, size_t content_len, - struct visited_uris *visited_uris) +write_from_uri(char const *location, unsigned char *content, size_t content_len) { struct rpki_uri *uri; FILE *out; @@ -538,7 +489,7 @@ write_from_uri(char const *location, unsigned char *content, size_t content_len, int error; /* rfc8181#section-2.2 must be an rsync URI */ - error = uri_create_rsync_str_rrdp(&uri, location, strlen(location)); + error = uri_create(&uri, UT_CAGED, location); if (error) return error; @@ -562,8 +513,6 @@ write_from_uri(char const *location, unsigned char *content, size_t content_len, uri_get_local(uri)); } - add_mft_to_list(visited_uris, uri_get_global(uri)); - uri_refput(uri); file_close(out); return 0; @@ -571,32 +520,24 @@ write_from_uri(char const *location, unsigned char *content, size_t content_len, /* Remove a local file and its directory tree (if empty) */ static int -delete_from_uri(struct rpki_uri *uri, struct visited_uris *visited_uris) +delete_from_uri(struct rpki_uri *uri) { - int error; - - if (visited_uris) { - error = rem_mft_from_list(visited_uris, uri_get_global(uri)); - if (error) - return error; - } - /* Delete parent dirs only if empty. */ return delete_dir_recursive_bottom_up(uri_get_local(uri)); } static int -__delete_from_uri(char const *location, struct visited_uris *visited_uris) +__delete_from_uri(char const *location) { struct rpki_uri *uri; int error; /* rfc8181#section-2.2 must be an rsync URI */ - error = uri_create_rsync_str_rrdp(&uri, location, strlen(location)); + error = uri_create(&uri, UT_CAGED, location); if (error) return error; - error = delete_from_uri(uri, visited_uris); + error = delete_from_uri(uri); /* Error 0 is ok */ uri_refput(uri); @@ -608,24 +549,20 @@ __delete_from_uri(char const *location, struct visited_uris *visited_uris) * other type at the caller. */ static int -parse_publish_elem(xmlTextReaderPtr reader, bool parse_hash, bool hash_required, - struct visited_uris *visited_uris) +parse_publish_elem(xmlTextReaderPtr reader, hash_requirement hr) { struct publish *tmp; int error; tmp = NULL; - error = parse_publish(reader, parse_hash, hash_required, &tmp); + error = parse_publish(reader, hr, &tmp); if (error) return error; - error = write_from_uri(tmp->doc_data.uri, tmp->content, - tmp->content_len, visited_uris); - publish_destroy(tmp); - if (error) - return error; + error = write_from_uri(tmp->meta.uri, tmp->content, tmp->content_len); - return 0; + publish_destroy(tmp); + return error; } /* @@ -633,7 +570,7 @@ parse_publish_elem(xmlTextReaderPtr reader, bool parse_hash, bool hash_required, * other type at the caller. */ static int -parse_withdraw_elem(xmlTextReaderPtr reader, struct visited_uris *visited_uris) +parse_withdraw_elem(xmlTextReaderPtr reader) { struct withdraw *tmp; int error; @@ -642,17 +579,15 @@ parse_withdraw_elem(xmlTextReaderPtr reader, struct visited_uris *visited_uris) if (error) return error; - error = __delete_from_uri(tmp->doc_data.uri, visited_uris); - withdraw_destroy(tmp); - if (error) - return error; + error = __delete_from_uri(tmp->meta.uri); - return 0; + withdraw_destroy(tmp); + return error; } static int parse_notification_delta(xmlTextReaderPtr reader, - struct update_notification *update) + struct update_notification *notification) { struct delta_head delta; int error; @@ -660,11 +595,11 @@ parse_notification_delta(xmlTextReaderPtr reader, error = parse_long(reader, RRDP_ATTR_SERIAL, &delta.serial); if (error) return error; - error = parse_doc_data(reader, true, true, &delta.doc_data); + error = parse_doc_data(reader, HR_MANDATORY, &delta.meta); if (error) return error; - deltas_head_add(&update->deltas_list, &delta); + deltas_head_add(¬ification->deltas_list, &delta); return 0; } @@ -680,12 +615,11 @@ xml_read_notification(xmlTextReaderPtr reader, void *arg) if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_DELTA)) { return parse_notification_delta(reader, update); } else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_SNAPSHOT)) { - return parse_doc_data(reader, true, true, + return parse_doc_data(reader, HR_MANDATORY, &update->snapshot); } else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_NOTIFICATION)) { /* No need to validate session ID and serial */ - return parse_global_data(reader, - &update->global_data, NULL, 0); + return parse_metadata(reader, &update->meta, NULL, 0); } return pr_val_err("Unexpected '%s' element", name); @@ -693,32 +627,26 @@ xml_read_notification(xmlTextReaderPtr reader, void *arg) case XML_READER_TYPE_END_ELEMENT: if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_NOTIFICATION)) return deltas_head_sort(&update->deltas_list, - update->global_data.serial); + update->meta.serial); break; } return 0; } -static int -parse_notification(struct rpki_uri *uri, struct update_notification **file) +int +rrdp_parse_notification(struct rpki_uri *uri, + struct update_notification *result) { - struct update_notification *result; int error; - result = update_notification_create(uri_get_global(uri)); - if (result == NULL) - enomem_panic(); + update_notification_init(result, uri); - error = relax_ng_parse(uri_get_local(uri), xml_read_notification, - result); - if (error) { + error = relax_ng_parse(uri_get_local(uri), xml_read_notification, result); + if (error) update_notification_destroy(result); - return error; - } - *file = result; - return 0; + return error; } static int @@ -734,13 +662,12 @@ xml_read_snapshot(xmlTextReaderPtr reader, void *arg) switch (type) { case XML_READER_TYPE_ELEMENT: if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_PUBLISH)) - error = parse_publish_elem(reader, false, false, - ctx->visited_uris); + error = parse_publish_elem(reader, HR_IGNORE); else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_SNAPSHOT)) - error = parse_global_data(reader, - &ctx->snapshot->global_data, - ctx->parent->global_data.session_id, - ctx->parent->global_data.serial); + error = parse_metadata(reader, + &ctx->snapshot->meta, + ctx->notification->meta.session_id, + ctx->notification->meta.serial); else return pr_val_err("Unexpected '%s' element", name); @@ -755,21 +682,20 @@ xml_read_snapshot(xmlTextReaderPtr reader, void *arg) } static int -parse_snapshot(struct rpki_uri *uri, struct proc_upd_args *args) +parse_snapshot(struct rpki_uri *uri, struct update_notification *notification) { struct rdr_snapshot_ctx ctx; int error; fnstack_push_uri(uri); /* Hash validation */ - error = hash_validate_file(uri, args->parent->snapshot.hash, - args->parent->snapshot.hash_len); + error = hash_validate_file(uri, notification->snapshot.hash, + notification->snapshot.hash_len); if (error) goto pop; ctx.snapshot = snapshot_create(); - ctx.parent = args->parent; - ctx.visited_uris = args->visited_uris; + ctx.notification = notification; error = relax_ng_parse(uri_get_local(uri), xml_read_snapshot, &ctx); @@ -791,14 +717,13 @@ xml_read_delta(xmlTextReaderPtr reader, void *arg) switch (type) { case XML_READER_TYPE_ELEMENT: if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_PUBLISH)) - error = parse_publish_elem(reader, true, false, - ctx->visited_uris); + error = parse_publish_elem(reader, HR_OPTIONAL); else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_WITHDRAW)) - error = parse_withdraw_elem(reader, ctx->visited_uris); + error = parse_withdraw_elem(reader); else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_DELTA)) - error = parse_global_data(reader, - &ctx->delta->global_data, - ctx->parent->global_data.session_id, + error = parse_metadata(reader, + &ctx->delta->meta, + ctx->notification->meta.session_id, ctx->expected_serial); else return pr_val_err("Unexpected '%s' element", name); @@ -815,13 +740,13 @@ xml_read_delta(xmlTextReaderPtr reader, void *arg) static int parse_delta(struct rpki_uri *uri, struct delta_head *parents_data, - struct proc_upd_args *args) + struct update_notification *notification) { struct rdr_delta_ctx ctx; - struct doc_data *expected_data; + struct file_metadata *expected_data; int error; - expected_data = &parents_data->doc_data; + expected_data = &parents_data->meta; fnstack_push_uri(uri); error = hash_validate_file(uri, expected_data->hash, @@ -830,8 +755,7 @@ parse_delta(struct rpki_uri *uri, struct delta_head *parents_data, goto pop_fnstack; ctx.delta = delta_create(); - ctx.parent = args->parent; - ctx.visited_uris = args->visited_uris; + ctx.notification = notification; ctx.expected_serial = parents_data->serial; error = relax_ng_parse(uri_get_local(uri), xml_read_delta, &ctx); @@ -845,28 +769,26 @@ parse_delta(struct rpki_uri *uri, struct delta_head *parents_data, static int process_delta(struct delta_head *delta_head, void *arg) { - struct proc_upd_args *args = arg; struct rpki_uri *uri; - struct doc_data *head_data; + struct file_metadata *head_data; int error; - head_data = &delta_head->doc_data; + head_data = &delta_head->meta; pr_val_debug("Processing delta '%s'.", head_data->uri); - error = uri_create_https_str_rrdp(&uri, head_data->uri, - strlen(head_data->uri)); + error = uri_create(&uri, UT_HTTPS, head_data->uri); if (error) return error; fnstack_push_uri(uri); - error = download_file(uri, 0); + error = cache_download(uri, NULL); if (error) goto release_uri; - error = parse_delta(uri, delta_head, args); + error = parse_delta(uri, delta_head, arg); - delete_from_uri(uri, NULL); + delete_from_uri(uri); /* Error 0 its ok */ release_uri: fnstack_pop(); @@ -874,97 +796,26 @@ process_delta(struct delta_head *delta_head, void *arg) return error; } -/* - * Download from @uri and set result file contents to @result, the file name - * is pushed into fnstack, so don't forget to do the pop when done working - * with the file. - * - * If the server didn't sent the file, due to the validation of - * 'If-Modified-Since' header, return 0 and set @result to NULL. - * - * Set @force to true to omit 'If-Modified-Since' header. - */ int -rrdp_parse_notification(struct rpki_uri *uri, bool force, - struct update_notification **result) +rrdp_parse_snapshot(struct update_notification *notification) { - long last_update; - int error, vis_err; - - if (uri == NULL || uri_is_rsync(uri)) - pr_crit("Wrong call, trying to parse a non HTTPS URI"); - - fnstack_push_uri(uri); - pr_val_debug("Processing notification."); - - last_update = 0; - if (!force) { - error = db_rrdp_uris_get_last_update(uri_get_global(uri), &last_update); - if (error && error != -ENOENT) - goto end; - } - - error = download_file(uri, last_update); - if (error < 0) - goto end; - - /* Request error, stop processing to handle as such */ - if (error == EREQFAILED) - goto end; - - /* - * Mark as visited, if it doesn't exists yet, there's no problem since - * this is probably the first time is visited (first run), so it will - * be marked as visited when the URI is stored at DB. - */ - vis_err = db_rrdp_uris_set_request_status(uri_get_global(uri), - RRDP_URI_REQ_VISITED); - if (vis_err && vis_err != -ENOENT) { - error = pr_val_err("Couldn't mark file as visited."); - goto end; - } - - /* No updates yet */ - if (error > 0) { - delete_from_uri(uri, NULL); - *result = NULL; - error = 0; - goto end; - } - - error = parse_notification(uri, result); - - delete_from_uri(uri, NULL); -end: fnstack_pop(); - return error; -} - -int -rrdp_parse_snapshot(struct update_notification *parent, - struct visited_uris *visited_uris) -{ - struct proc_upd_args args; struct rpki_uri *uri; int error; - args.parent = parent; - args.visited_uris = visited_uris; - - pr_val_debug("Processing snapshot '%s'.", parent->snapshot.uri); - error = uri_create_https_str_rrdp(&uri, parent->snapshot.uri, - strlen(parent->snapshot.uri)); + pr_val_debug("Processing snapshot '%s'.", notification->snapshot.uri); + error = uri_create(&uri, UT_HTTPS, notification->snapshot.uri); if (error) return error; fnstack_push_uri(uri); - error = download_file(uri, 0); + error = cache_download(uri, NULL); if (error) goto release_uri; - error = parse_snapshot(uri, &args); + error = parse_snapshot(uri, notification); - delete_from_uri(uri, NULL); + delete_from_uri(uri); /* Error 0 is ok */ release_uri: fnstack_pop(); @@ -973,14 +824,9 @@ rrdp_parse_snapshot(struct update_notification *parent, } int -rrdp_process_deltas(struct update_notification *parent, - unsigned long cur_serial, struct visited_uris *visited_uris) +rrdp_process_deltas(struct update_notification *notification, + unsigned long cur_serial) { - struct proc_upd_args args; - - args.parent = parent; - args.visited_uris = visited_uris; - - return deltas_head_for_each(&parent->deltas_list, - parent->global_data.serial, cur_serial, process_delta, &args); + return deltas_head_for_each(¬ification->deltas_list, + notification->meta.serial, cur_serial, process_delta, notification); } diff --git a/src/rrdp/rrdp_parser.h b/src/rrdp/rrdp_parser.h index 7546eaec..9215b70b 100644 --- a/src/rrdp/rrdp_parser.h +++ b/src/rrdp/rrdp_parser.h @@ -4,13 +4,9 @@ #include #include "types/uri.h" #include "rrdp/rrdp_objects.h" -#include "visited_uris.h" -int rrdp_parse_notification(struct rpki_uri *, bool, - struct update_notification **); -int rrdp_parse_snapshot(struct update_notification *, struct visited_uris *); - -int rrdp_process_deltas(struct update_notification *, unsigned long, - struct visited_uris *); +int rrdp_parse_notification(struct rpki_uri *, struct update_notification *); +int rrdp_parse_snapshot(struct update_notification *); +int rrdp_process_deltas(struct update_notification *, unsigned long); #endif /* SRC_RRDP_RRDP_PARSER_H_ */ diff --git a/src/rsync/rsync.c b/src/rsync/rsync.c index 0cfdcaa5..a9a07c15 100644 --- a/src/rsync/rsync.c +++ b/src/rsync/rsync.c @@ -1,4 +1,4 @@ -#include "rsync.h" +#include "rsync/rsync.h" #include #include @@ -15,70 +15,6 @@ #include "log.h" #include "thread_var.h" -struct uri { - struct rpki_uri *uri; - SLIST_ENTRY(uri) next; -}; - -/** URIs that we have already downloaded. */ -SLIST_HEAD(uri_list, uri); - -/* static char const *const RSYNC_PREFIX = "rsync://"; */ - -struct uri_list * -rsync_create(void) -{ - struct uri_list *visited_uris; - - visited_uris = pmalloc(sizeof(struct uri_list)); - SLIST_INIT(visited_uris); - - return visited_uris; -} - -void -rsync_destroy(struct uri_list *list) -{ - struct uri *uri; - - while (!SLIST_EMPTY(list)) { - uri = SLIST_FIRST(list); - SLIST_REMOVE_HEAD(list, next); - uri_refput(uri->uri); - free(uri); - } - free(list); -} - -/* - * Returns whether @uri has already been rsync'd during the current validation - * run. - */ -static bool -is_already_downloaded(struct rpki_uri *uri, struct uri_list *visited_uris) -{ - struct uri *cursor; - - /* TODO (next iteration) this is begging for a hash set. */ - SLIST_FOREACH(cursor, visited_uris, next) - if (uri_equals(cursor->uri, uri)) - return true; - - return false; -} - -static void -mark_as_downloaded(struct rpki_uri *uri, struct uri_list *visited_uris) -{ - struct uri *node; - - node = pmalloc(sizeof(struct uri)); - node->uri = uri; - uri_refget(uri); - - SLIST_INSERT_HEAD(visited_uris, node, next); -} - /* * Duplicate parent FDs, to pipe rsync output: * - fds[0] = stderr @@ -273,8 +209,8 @@ read_pipes(int fds[2][2]) /* * Downloads the @uri->global file into the @uri->local path. */ -static int -do_rsync(struct rpki_uri *uri) +int +rsync_download(struct rpki_uri *uri) { /* Descriptors to pipe stderr (first element) and stdout (second) */ char **args; @@ -286,6 +222,9 @@ do_rsync(struct rpki_uri *uri) int child_status; int error; + if (!config_get_rsync_enabled()) + return 0; /* Skip; caller will work with existing cache. */ + /* Prepare everything for the child exec */ args = NULL; args_len = 0; @@ -410,55 +349,3 @@ do_rsync(struct rpki_uri *uri) release_args(args, args_len); return error; } - -int -rsync_download_files(struct rpki_uri *uri, bool force) -{ - struct validation *state; - struct uri_list *visited_uris; - int error; - - if (!config_get_rsync_enabled()) - return 0; - - state = state_retrieve(); - visited_uris = validation_rsync_visited_uris(state); - - if (!force && is_already_downloaded(uri, visited_uris)) { - pr_val_debug("No need to redownload '%s'.", - uri_val_get_printable(uri)); - return 0; - } - - pr_val_debug("Going to RSYNC '%s'.", uri_val_get_printable(uri)); - - error = do_rsync(uri); - switch (error) { - case 0: - /* Don't store when "force" and if its already downloaded */ - if (!(force && is_already_downloaded(uri, visited_uris))) - mark_as_downloaded(uri, visited_uris); - break; - case EREQFAILED: - mark_as_downloaded(uri, visited_uris); - break; - } - - return error; -} - -void -reset_downloaded(void) -{ - struct uri_list *list; - struct uri *uri; - - list = validation_rsync_visited_uris(state_retrieve()); - - while (!SLIST_EMPTY(list)) { - uri = SLIST_FIRST(list); - SLIST_REMOVE_HEAD(list, next); - uri_refput(uri->uri); - free(uri); - } -} diff --git a/src/rsync/rsync.h b/src/rsync/rsync.h index 8f1cbcd3..a80ea5cb 100644 --- a/src/rsync/rsync.h +++ b/src/rsync/rsync.h @@ -1,15 +1,8 @@ #ifndef SRC_RSYNC_RSYNC_H_ #define SRC_RSYNC_RSYNC_H_ -#include #include "types/uri.h" -struct uri_list; - -int rsync_download_files(struct rpki_uri *, bool); -struct uri_list *rsync_create(void); -void rsync_destroy(struct uri_list *); - -void reset_downloaded(void); +int rsync_download(struct rpki_uri *); #endif /* SRC_RSYNC_RSYNC_H_ */ diff --git a/src/rtr/db/db_table.c b/src/rtr/db/db_table.c index 4adb232f..77ff11f4 100644 --- a/src/rtr/db/db_table.c +++ b/src/rtr/db/db_table.c @@ -247,40 +247,6 @@ add_roa_deltas(struct hashable_roa *roas1, struct hashable_roa *roas2, return 0; } -void -find_bad_vrp(char const *prefix, struct db_table *table) -{ - struct hashable_roa *node; - struct hashable_roa *tmp; - struct vrp const *vrp; - unsigned int roa_counter; - unsigned int roa_count; - char buffer[INET6_ADDRSTRLEN]; - - if (table == NULL) - return; - - roa_counter = 0; - roa_count = HASH_COUNT(table->roas); - - HASH_ITER(hh, table->roas, node, tmp) { - vrp = &node->data; - if (vrp->addr_fam != AF_INET && vrp->addr_fam != AF_INET6) { - pr_crit("%s: VRP corrupted! [%u %s/%u-%u %u] %u/%u " - "(Please report this output to https://github.com/NICMx/FORT-validator/issues/89)", - prefix, - vrp->asn, - addr2str6(&vrp->prefix.v6, buffer), - vrp->prefix_length, - vrp->max_prefix_length, - vrp->addr_fam, - roa_counter, - roa_count); - } - roa_counter++; - } -} - static int add_router_key_delta(struct deltas *deltas, struct hashable_key *key, int op) { diff --git a/src/rtr/db/db_table.h b/src/rtr/db/db_table.h index 8ad9acc9..8562567a 100644 --- a/src/rtr/db/db_table.h +++ b/src/rtr/db/db_table.h @@ -28,6 +28,4 @@ int rtrhandler_handle_router_key(struct db_table *, unsigned char const *, uint32_t, unsigned char const *); int compute_deltas(struct db_table *, struct db_table *, struct deltas **); -void find_bad_vrp(char const *, struct db_table *); - #endif /* SRC_RTR_DB_DB_TABLE_H_ */ diff --git a/src/rtr/db/vrps.c b/src/rtr/db/vrps.c index dd1c9091..cb51ad5c 100644 --- a/src/rtr/db/vrps.c +++ b/src/rtr/db/vrps.c @@ -17,6 +17,7 @@ #include "rtr/db/db_table.h" #include "slurm/slurm_loader.h" #include "thread/thread_pool.h" +#include "cache/local_cache.h" struct vrp_node { struct delta_vrp delta; @@ -220,9 +221,9 @@ __perform_standalone_validation(struct db_table **result) struct db_table *db; int error; + cache_prepare(); + db = db_table_create(); - if (db == NULL) - enomem_panic(); error = perform_standalone_validation(pool, db); if (error) { @@ -262,6 +263,13 @@ __compute_deltas(struct db_table *old_base, struct db_table *new_base, return 0; } +/* + * High level validator function. + * + * - Downloads tree + * - Validates tree + * - Updates RTR state + */ static int __vrps_update(bool *notify_clients) { @@ -280,24 +288,17 @@ __vrps_update(bool *notify_clients) *notify_clients = false; old_base = state.base; new_base = NULL; - find_bad_vrp("Old base", old_base); error = __perform_standalone_validation(&new_base); if (error) return error; - find_bad_vrp("After standalone (old)", old_base); - find_bad_vrp("After standalone (new)", new_base); - error = slurm_apply(new_base, &state.slurm); if (error) { db_table_destroy(new_base); return error; } - find_bad_vrp("After SLURM (old)", old_base); - find_bad_vrp("After SLURM (new)", new_base); - /* * At this point, new_base is completely valid. Even if we error out * later, report the ROAs. @@ -307,9 +308,6 @@ __vrps_update(bool *notify_clients) */ output_print_data(new_base); - find_bad_vrp("After CSV (old)", old_base); - find_bad_vrp("After CSV (new)", new_base); - error = __compute_deltas(old_base, new_base, notify_clients, &new_deltas); if (error) { @@ -346,53 +344,47 @@ __vrps_update(bool *notify_clients) return 0; } +/* + * Highest level validator function. + * + * - Downloads tree + * - Validates tree + * - Updates RTR state + * - Logs status + * + * TODO (#50) remove this wrapper once Prometheus is implemented + */ int vrps_update(bool *changed) { time_t start, finish; - long int exec_time; + unsigned int roas, rks; serial_t serial; int error; - /* - * This wrapper is mainly intended to log informational data, so if - * there's no need, don't do unnecessary calls. - */ - if (!log_op_enabled(LOG_INFO)) - return __vrps_update(changed); - - pr_op_info("Starting validation."); - if (config_get_mode() == SERVER) { - error = get_last_serial_number(&serial); - if (!error) - pr_op_info("- Serial before validation: %u", serial); - } - - time(&start); + start = time(NULL); error = __vrps_update(changed); - time(&finish); - exec_time = finish - start; + finish = time(NULL); - pr_op_info("Validation finished:"); rwlock_read_lock(&state_lock); - do { - if (state.base == NULL) { - rwlock_unlock(&state_lock); - pr_op_info("- Valid ROAs: 0"); - pr_op_info("- Valid Router Keys: 0"); - if (config_get_mode() == SERVER) - pr_op_info("- No serial number."); - break; - } + if (state.base == NULL) { + roas = 0; + rks = 0; + serial = 0; + } else { + roas = db_table_roa_count(state.base); + rks = db_table_router_key_count(state.base); + serial = state.serial; + } + rwlock_unlock(&state_lock); - pr_op_info("- Valid ROAs: %u", db_table_roa_count(state.base)); - pr_op_info("- Valid Router Keys: %u", - db_table_router_key_count(state.base)); - if (config_get_mode() == SERVER) - pr_op_info("- Serial: %u", state.serial); - rwlock_unlock(&state_lock); - } while(0); - pr_op_info("- Real execution time: %ld secs.", exec_time); + pr_op_info("Validation finished:"); + pr_op_info("- Valid ROAs: %u", roas); + pr_op_info("- Valid Router Keys: %u", rks); + if (config_get_mode() == SERVER) + pr_op_info("- Serial: %u", serial); + if (start != ((time_t) -1) && finish != ((time_t) -1)) + pr_op_info("- Real execution time: %ld secs.", finish - start); return error; } diff --git a/src/rtr/pdu_handler.c b/src/rtr/pdu_handler.c index f822b557..c6182515 100644 --- a/src/rtr/pdu_handler.c +++ b/src/rtr/pdu_handler.c @@ -121,8 +121,7 @@ handle_serial_query_pdu(int fd, struct rtr_request const *request) } return send_end_of_data_pdu(fd, args.rtr_version, final_serial); case -EAGAIN: /* Database still under construction */ - error = err_pdu_send_no_data_available(fd, args.rtr_version); - return error; + return err_pdu_send_no_data_available(fd, args.rtr_version); case -ESRCH: /* Invalid serial */ /* https://tools.ietf.org/html/rfc6810#section-6.3 */ return send_cache_reset_pdu(fd, args.rtr_version); diff --git a/src/state.c b/src/state.c index 9ec4887c..2ec37c6f 100644 --- a/src/state.c +++ b/src/state.c @@ -1,8 +1,8 @@ #include "state.h" #include -#include "rrdp/db/db_rrdp.h" #include "alloc.h" +#include "cert_stack.h" #include "log.h" #include "thread_var.h" @@ -24,11 +24,6 @@ struct validation { struct cert_stack *certstack; - struct uri_list *rsync_visited_uris; - - /* Shallow copy of RRDP URIs and its corresponding visited uris */ - struct db_rrdp_uri *rrdp_uris; - /* Did the TAL's public key match the root certificate's public key? */ enum pubkey_state pubkey_state; @@ -43,6 +38,9 @@ struct validation { char addr_buffer2[INET6_ADDRSTRLEN]; struct validation_handler validation_handler; + + /* URL of the Notification file currently being traversed. */ + struct rpki_uri *notification_uri; }; /* @@ -118,8 +116,6 @@ validation_prepare(struct validation **out, struct tal *tal, if (error) goto abort3; - result->rsync_visited_uris = rsync_create(); - result->rrdp_uris = db_rrdp_get_uris(tal_get_file_name(tal)); result->pubkey_state = PKS_UNTESTED; result->validation_handler = *validation_handler; result->x509_data.params = params; /* Ownership transfered */ @@ -140,7 +136,6 @@ validation_destroy(struct validation *state) X509_VERIFY_PARAM_free(state->x509_data.params); X509_STORE_free(state->x509_data.store); certstack_destroy(state->certstack); - rsync_destroy(state->rsync_visited_uris); free(state); } @@ -162,12 +157,6 @@ validation_certstack(struct validation *state) return state->certstack; } -struct uri_list * -validation_rsync_visited_uris(struct validation *state) -{ - return state->rsync_visited_uris; -} - void validation_pubkey_valid(struct validation *state) { @@ -204,8 +193,18 @@ validation_get_validation_handler(struct validation *state) return &state->validation_handler; } -struct db_rrdp_uri * -validation_get_rrdp_uris(struct validation *state) +struct rpki_uri * +validation_get_notification_uri(struct validation *state) +{ + return state->notification_uri; +} + +void +validation_set_notification_uri(struct validation *state, struct rpki_uri *uri) { - return state->rrdp_uris; + if (state->notification_uri != NULL) + uri_refput(state->notification_uri); + state->notification_uri = uri; + if (uri != NULL) + uri_refget(uri); } diff --git a/src/state.h b/src/state.h index 190a5719..88eaf65d 100644 --- a/src/state.h +++ b/src/state.h @@ -2,11 +2,8 @@ #define SRC_STATE_H_ #include -#include "cert_stack.h" #include "validation_handler.h" #include "object/tal.h" -#include "rsync/rsync.h" -#include "rrdp/db/db_rrdp_uris.h" struct validation; @@ -17,7 +14,6 @@ void validation_destroy(struct validation *); struct tal *validation_tal(struct validation *); X509_STORE *validation_store(struct validation *); struct cert_stack *validation_certstack(struct validation *); -struct uri_list *validation_rsync_visited_uris(struct validation *); enum pubkey_state { PKS_VALID, @@ -37,4 +33,7 @@ validation_get_validation_handler(struct validation *); struct db_rrdp_uri *validation_get_rrdp_uris(struct validation *); +struct rpki_uri *validation_get_notification_uri(struct validation *); +void validation_set_notification_uri(struct validation *, struct rpki_uri *); + #endif /* SRC_STATE_H_ */ diff --git a/src/thread_var.c b/src/thread_var.c index 8fa3961c..ba4dccdd 100644 --- a/src/thread_var.c +++ b/src/thread_var.c @@ -10,6 +10,7 @@ #include "alloc.h" #include "config.h" +#include "log.h" static pthread_key_t state_key; static pthread_key_t filenames_key; diff --git a/src/types/uri.c b/src/types/uri.c index 57f2d820..de616b75 100644 --- a/src/types/uri.c +++ b/src/types/uri.c @@ -2,21 +2,13 @@ #include #include -#include "rrdp/db/db_rrdp_uris.h" #include "alloc.h" #include "common.h" -#include "config.h" #include "log.h" +#include "state.h" #include "str_token.h" - -/* Expected URI types */ -enum rpki_uri_type { - URI_RSYNC, - URI_HTTPS, -}; - -static char const *const PFX_RSYNC = "rsync://"; -static char const *const PFX_HTTPS = "https://"; +#include "thread_var.h" +#include "data_structure/path_builder.h" /** * Design notes: @@ -60,12 +52,16 @@ struct rpki_uri { * 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. */ - /* Type, currently rysnc and https are valid */ - enum rpki_uri_type type; + enum uri_type type; unsigned int references; }; @@ -213,99 +209,82 @@ ia5str2global(struct rpki_uri *uri, char const *mft, IA5String_t *ia5) return 0; } +/* + * Maps "rsync://a.b.c/d/e.cer" into "/rsync/a.b.c/d/e.cer". + */ static int -validate_uri_begin(char const *uri_pfx, const size_t uri_pfx_len, - char const *global, size_t global_len, int error) -{ - if (global_len < uri_pfx_len - || strncasecmp(uri_pfx, global, uri_pfx_len) != 0) { - if (!error) - return -EINVAL; - pr_val_err("Global URI '%s' does not begin with '%s'.", - global, uri_pfx); - return error; - } - - return 0; -} - -static int -validate_gprefix(char const *global, size_t global_len, uint8_t flags, - enum rpki_uri_type *type) +map_simple(struct rpki_uri *uri, char const *gprefix, char const *lprefix, + int err) { - size_t const PFX_RSYNC_LEN = strlen(PFX_RSYNC); - size_t const PFX_HTTPS_LEN = strlen(PFX_HTTPS); + struct path_builder pb; int error; - if (flags == URI_VALID_RSYNC) { - (*type) = URI_RSYNC; - return validate_uri_begin(PFX_RSYNC, PFX_RSYNC_LEN, global, - global_len, ENOTRSYNC); + if (!str_starts_with(uri->global, gprefix)) { + pr_val_err("URI '%s' does not begin with '%s'.", + uri->global, lprefix); + return err; } - if (flags == URI_VALID_HTTPS) { - (*type) = URI_HTTPS; - return validate_uri_begin(PFX_HTTPS, PFX_HTTPS_LEN, global, - global_len, ENOTHTTPS); - } - if (flags != (URI_VALID_RSYNC | URI_VALID_HTTPS)) - pr_crit("Unknown URI flag"); - - /* It has both flags */ - error = validate_uri_begin(PFX_RSYNC, PFX_RSYNC_LEN, global, global_len, - 0); - if (!error) { - (*type) = URI_RSYNC; - return 0; - } - error = validate_uri_begin(PFX_HTTPS, PFX_HTTPS_LEN, global, global_len, - 0); - if (error) { - pr_val_warn("URI '%s' does not begin with '%s' nor '%s'.", - global, PFX_RSYNC, PFX_HTTPS); - return ENOTSUPPORTED; + + path_init(&pb); + path_append_guri(&pb, uri); + error = path_compile(&pb, &uri->local); + if (error) + return error; + + if (!str_starts_with(uri->local, lprefix)) { + pr_val_err("URI '%s' seems to be dot-dotting to its scheme.", + uri->global); + free(uri->local); + return -EINVAL; } - /* @size was already set */ - (*type) = URI_HTTPS; return 0; } -/** - * Initializes @uri->local by converting @uri->global. - * - * For example, given local cache repository "/tmp/rpki" and global uri - * "rsync://rpki.ripe.net/repo/manifest.mft", initializes @uri->local as - * "/tmp/rpki/rpki.ripe.net/repo/manifest.mft". - * - * By contract, if @guri is not RSYNC nor HTTPS, this will return ENOTRSYNC. - * This often should not be treated as an error; please handle gracefully. +/* + * Maps "rsync://a.b.c/d/e.cer" into + * "/rrdp//a.b.c/d/e.cer". */ static int -g2l(char const *global, size_t global_len, uint8_t flags, char **result, - enum rpki_uri_type *result_type) +map_caged(struct rpki_uri *uri) { - enum rpki_uri_type type; - int error; + struct path_builder builder; + struct rpki_uri *notification; - error = validate_gprefix(global, global_len, flags, &type); - if (error) - return error; + notification = validation_get_notification_uri(state_retrieve()); + if (notification == NULL) + pr_crit("Programming error: Notification not recorded."); - *result = map_uri_to_local(global, - type == URI_RSYNC ? PFX_RSYNC : PFX_HTTPS); - (*result_type) = type; - return 0; + path_init(&builder); + + path_append(&builder, "rrdp"); + path_append_guri(&builder, notification); + path_append_guri(&builder, uri); + + return path_compile(&builder, &uri->local); } static int -autocomplete_local(struct rpki_uri *uri, uint8_t flags) +autocomplete_local(struct rpki_uri *uri) { - return g2l(uri->global, uri->global_len, flags, &uri->local, - &uri->type); + switch (uri->type) { + case UT_RSYNC: + return map_simple(uri, "rsync://", "rsync/", ENOTRSYNC); + case UT_HTTPS: + return map_simple(uri, "https://", "https/", ENOTHTTPS); + case UT_CAGED: + return map_caged(uri); + } + + pr_crit("Unknown URI type: %u", uri->type); } -static int -uri_create(struct rpki_uri **result, uint8_t flags, void const *guri, +/* + * I think the reason why @guri is not a char * is to convey that it doesn't + * need to be NULL terminated, but I'm not sure. + */ +int +__uri_create(struct rpki_uri **result, enum uri_type type, void const *guri, size_t guri_len) { struct rpki_uri *uri; @@ -319,7 +298,9 @@ uri_create(struct rpki_uri **result, uint8_t flags, void const *guri, return error; } - error = autocomplete_local(uri, flags); + uri->type = type; + + error = autocomplete_local(uri); if (error) { free(uri->global); free(uri); @@ -327,40 +308,15 @@ uri_create(struct rpki_uri **result, uint8_t flags, void const *guri, } uri->references = 1; + *result = uri; return 0; } int -uri_create_rsync_str_rrdp(struct rpki_uri **uri, char const *guri, - size_t guri_len) +uri_create(struct rpki_uri **result, enum uri_type type, char const *guri) { - return uri_create(uri, URI_VALID_RSYNC, guri, guri_len); -} - -int -uri_create_https_str_rrdp(struct rpki_uri **uri, char const *guri, - size_t guri_len) -{ - return uri_create(uri, URI_VALID_HTTPS, guri, guri_len); -} - -int -uri_create_rsync_str(struct rpki_uri **uri, char const *guri, size_t guri_len) -{ - return uri_create(uri, URI_VALID_RSYNC, guri, guri_len); -} - -/* - * A URI that can be rsync or https. - * - * Return ENOTSUPPORTED if not an rsync or https URI. - */ -int -uri_create_mixed_str(struct rpki_uri **uri, char const *guri, size_t guri_len) -{ - return uri_create(uri, URI_VALID_RSYNC | URI_VALID_HTTPS, guri, - guri_len); + return __uri_create(result, type, guri, strlen(guri)); } /* @@ -381,7 +337,9 @@ uri_create_mft(struct rpki_uri **result, struct rpki_uri *mft, IA5String_t *ia5) return error; } - error = autocomplete_local(uri, URI_VALID_RSYNC); + uri->type = UT_RSYNC; + + error = autocomplete_local(uri); if (error) { free(uri->global); free(uri); @@ -389,66 +347,16 @@ uri_create_mft(struct rpki_uri **result, struct rpki_uri *mft, IA5String_t *ia5) } uri->references = 1; + *result = uri; return 0; } -/* - * Create @uri from the @ad, validating that the uri is of type(s) indicated - * at @flags (can be URI_VALID_RSYNC and/or URI_VALID_HTTPS) - */ -int -uri_create_ad(struct rpki_uri **uri, ACCESS_DESCRIPTION *ad, int flags) -{ - ASN1_STRING *asn1_string; - int type; - - asn1_string = GENERAL_NAME_get0_value(ad->location, &type); - - /* - * RFC 6487: "This extension MUST have an instance of an - * AccessDescription with an accessMethod of id-ad-rpkiManifest, (...) - * with an rsync URI [RFC5781] form of accessLocation." - * - * Ehhhhhh. It's a little annoying in that it seems to be stucking more - * than one requirement in a single sentence, which I think is rather - * rare for an RFC. Normally they tend to hammer things more. - * - * Does it imply that the GeneralName CHOICE is constrained to type - * "uniformResourceIdentifier"? I guess so, though I don't see anything - * stopping a few of the other types from also being capable of storing - * URIs. - * - * Also, nobody seems to be using the other types, and handling them - * would be a titanic pain in the ass. So this is what I'm committing - * to. - */ - if (type != GEN_URI) { - pr_val_err("Unknown GENERAL_NAME type: %d", type); - return ENOTSUPPORTED; - } - - /* - * GEN_URI signals an IA5String. - * IA5String is a subset of ASCII, so this cast is safe. - * No guarantees of a NULL chara, though. - * - * TODO (testers) According to RFC 5280, accessLocation can be an IRI - * somehow converted into URI form. I don't think that's an issue - * because the RSYNC clone operation should not have performed the - * conversion, so we should be looking at precisely the IA5String - * directory our g2l version of @asn1_string should contain. - * But ask the testers to keep an eye on it anyway. - */ - return uri_create(uri, flags, - ASN1_STRING_get0_data(asn1_string), - ASN1_STRING_length(asn1_string)); -} - -void +struct rpki_uri * uri_refget(struct rpki_uri *uri) { uri->references++; + return uri; } void @@ -507,10 +415,22 @@ uri_is_certificate(struct rpki_uri *uri) return uri_has_extension(uri, ".cer"); } +enum uri_type +uri_get_type(struct rpki_uri *uri) +{ + return uri->type; +} + bool uri_is_rsync(struct rpki_uri *uri) { - return uri->type == URI_RSYNC; + return uri->type == UT_RSYNC; +} + +bool +uri_is_https(struct rpki_uri *uri) +{ + return uri->type == UT_HTTPS; } static char const * diff --git a/src/types/uri.h b/src/types/uri.h index 83563bb2..a0d60700 100644 --- a/src/types/uri.h +++ b/src/types/uri.h @@ -2,27 +2,27 @@ #define SRC_TYPES_URI_H_ #include -#include #include "asn1/asn1c/IA5String.h" -/* Flags to indicate expected uri type */ -#define URI_VALID_RSYNC 0x01 -#define URI_VALID_HTTPS 0x02 - -#define ESKIP 85830 +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; -/* Maps RSYNC URIs of RRDP to a local workspace */ -int uri_create_rsync_str_rrdp(struct rpki_uri **, char const *, size_t); -int uri_create_https_str_rrdp(struct rpki_uri **, char const *, size_t); - -int uri_create_rsync_str(struct rpki_uri **, char const *, size_t); -int uri_create_mixed_str(struct rpki_uri **, char const *, size_t); +int __uri_create(struct rpki_uri **, enum uri_type, void const *, size_t); +int uri_create(struct rpki_uri **, enum uri_type, char const *); int uri_create_mft(struct rpki_uri **, struct rpki_uri *, IA5String_t *); -int uri_create_ad(struct rpki_uri **, ACCESS_DESCRIPTION *, int); -void uri_refget(struct rpki_uri *); +struct rpki_uri *uri_refget(struct rpki_uri *); void uri_refput(struct rpki_uri *); /* @@ -36,7 +36,10 @@ size_t uri_get_global_len(struct rpki_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 *); + +enum uri_type uri_get_type(struct rpki_uri *); bool uri_is_rsync(struct rpki_uri *); +bool uri_is_https(struct rpki_uri *); char const *uri_val_get_printable(struct rpki_uri *); char const *uri_op_get_printable(struct rpki_uri *); diff --git a/src/validation_run.c b/src/validation_run.c index b527da04..9e9b98ca 100644 --- a/src/validation_run.c +++ b/src/validation_run.c @@ -13,21 +13,15 @@ int validation_run_first(void) { - int error; - - if (config_get_mode() == SERVER) - pr_op_warn("First validation cycle has begun, wait until the next notification to connect your router(s)"); - else - pr_op_warn("The validation has begun."); + pr_op_info("Please wait. Validating..."); - error = vrps_update(NULL); - if (error) - return pr_op_err("First validation wasn't successful."); + if (vrps_update(NULL) != 0) + return pr_op_err("Validation unsuccessful; results unusable."); if (config_get_mode() == SERVER) - pr_op_warn("First validation cycle successfully ended, now you can connect your router(s)"); + pr_op_info("Validation complete; waiting for routers."); else - pr_op_warn("The validation has successfully ended."); + pr_op_info("Validation complete."); return 0; } diff --git a/src/visited_uris.c b/src/visited_uris.c deleted file mode 100644 index fb9ca0dc..00000000 --- a/src/visited_uris.c +++ /dev/null @@ -1,164 +0,0 @@ -#include "visited_uris.h" - -#include -#include -#include -#include "alloc.h" -#include "log.h" -#include "delete_dir_daemon.h" -#include "data_structure/array_list.h" -#include "data_structure/uthash.h" - -struct visited_elem { - /* key */ - char *uri; - UT_hash_handle hh; -}; - -struct visited_uris { - struct visited_elem *table; - unsigned int refs; -}; - -DEFINE_ARRAY_LIST_STRUCT(uris_roots, char *); -DEFINE_ARRAY_LIST_FUNCTIONS(uris_roots, char *, static) - -static struct visited_elem * -visited_elem_create(char const *uri) -{ - struct visited_elem *tmp; - - tmp = pzalloc(sizeof(struct visited_elem)); /* Zero needed by uthash */ - tmp->uri = pstrdup(uri); - - return tmp; -} - -static void -visited_elem_destroy(struct visited_elem *elem) -{ - free(elem->uri); - free(elem); -} - -struct visited_uris * -visited_uris_create(void) -{ - struct visited_uris *tmp; - - tmp = pmalloc(sizeof(struct visited_uris)); - - tmp->table = NULL; - tmp->refs = 1; - - return tmp; -} - -static void -visited_uris_destroy(struct visited_uris *uris) -{ - struct visited_elem *elm_node, *elm_tmp; - - HASH_ITER(hh, uris->table, elm_node, elm_tmp) { - HASH_DEL(uris->table, elm_node); - visited_elem_destroy(elm_node); - } - free(uris); -} - -void -visited_uris_refget(struct visited_uris *uris) -{ - uris->refs++; -} - -void -visited_uris_refput(struct visited_uris *uris) -{ - uris->refs--; - if (uris->refs == 0) - visited_uris_destroy(uris); -} - -static struct visited_elem * -elem_find(struct visited_uris *list, char const *uri) -{ - struct visited_elem *found; - HASH_FIND_STR(list->table, uri, found); - return found; -} - -void -visited_uris_add(struct visited_uris *uris, char const *uri) -{ - struct visited_elem *node; - - if (elem_find(uris, uri) == NULL) { - /* Do not inline; HASH_ADD_STR expands "add" multiple times. */ - node = visited_elem_create(uri); - HASH_ADD_STR(uris->table, uri, node); - } -} - -int -visited_uris_remove(struct visited_uris *uris, char const *uri) -{ - struct visited_elem *elem; - - elem = elem_find(uris, uri); - if (elem == NULL) - return pr_val_err("Trying to remove a nonexistent URI '%s'", uri); - - HASH_DEL(uris->table, elem); - visited_elem_destroy(elem); - - return 0; -} - -static void -visited_uris_to_arr(struct visited_uris *uris, struct uris_roots *roots) -{ - struct visited_elem *elem; - char *tmp, *last_slash; - size_t size; - - for (elem = uris->table; elem != NULL; elem = elem->hh.next) { - last_slash = strrchr(elem->uri, '/'); - size = last_slash - elem->uri; - tmp = pmalloc(size + 1); - strncpy(tmp, elem->uri, size); - tmp[size] = '\0'; - uris_roots_add(roots, &tmp); - } -} - -static void -uris_root_destroy(char **elem) -{ - free(*elem); -} - -/* - * Delete all the corresponding local files of @uris located at @workspace - */ -int -visited_uris_delete_local(struct visited_uris *uris) -{ - struct uris_roots roots; - int error; - - uris_roots_init(&roots); - visited_uris_to_arr(uris, &roots); - if (roots.len == 0) - goto success; - - error = delete_dir_daemon_start(roots.array, roots.len); - if (error) { - uris_roots_cleanup(&roots, uris_root_destroy); - return error; - } - -success: - uris_roots_cleanup(&roots, uris_root_destroy); - return 0; -} diff --git a/src/visited_uris.h b/src/visited_uris.h deleted file mode 100644 index b0fc91f6..00000000 --- a/src/visited_uris.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SRC_VISITED_URIS_H_ -#define SRC_VISITED_URIS_H_ - -struct visited_uris; - -struct visited_uris *visited_uris_create(void); -void visited_uris_refput(struct visited_uris *); -void visited_uris_refget(struct visited_uris *); - -void visited_uris_add(struct visited_uris *, char const *); -int visited_uris_remove(struct visited_uris *, char const *); -int visited_uris_delete_local(struct visited_uris *); - -#endif /* SRC_VISITED_URIS_H_ */ diff --git a/test/Makefile.am b/test/Makefile.am index 7ae96fe8..c6e29399 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -20,45 +20,55 @@ AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS} $ MY_LDADD = ${CHECK_LIBS} check_PROGRAMS = address.test -check_PROGRAMS += deltas_array.test +check_PROGRAMS += cache.test check_PROGRAMS += db_table.test +check_PROGRAMS += deltas_array.test check_PROGRAMS += line_file.test +check_PROGRAMS += pb.test +check_PROGRAMS += pdu.test check_PROGRAMS += pdu_handler.test +check_PROGRAMS += primitive_reader.test check_PROGRAMS += rrdp_objects.test -check_PROGRAMS += rsync.test check_PROGRAMS += serial.test check_PROGRAMS += tal.test check_PROGRAMS += thread_pool.test check_PROGRAMS += uri.test +check_PROGRAMS += uthash.test check_PROGRAMS += vcard.test check_PROGRAMS += vrps.test check_PROGRAMS += xml.test -check_PROGRAMS += rtr/pdu.test -check_PROGRAMS += rtr/primitive_reader.test -check_PROGRAMS += data_structure/uthash.test TESTS = ${check_PROGRAMS} address_test_SOURCES = types/address_test.c address_test_LDADD = ${MY_LDADD} -deltas_array_test_SOURCES = rtr/db/deltas_array_test.c -deltas_array_test_LDADD = ${MY_LDADD} +cache_test_SOURCES = cache/local_cache_test.c +cache_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} db_table_test_SOURCES = rtr/db/db_table_test.c db_table_test_LDADD = ${MY_LDADD} +deltas_array_test_SOURCES = rtr/db/deltas_array_test.c +deltas_array_test_LDADD = ${MY_LDADD} + line_file_test_SOURCES = line_file_test.c line_file_test_LDADD = ${MY_LDADD} +pb_test_SOURCES = data_structure/path_builder_test.c +pb_test_LDADD = ${MY_LDADD} + +pdu_test_SOURCES = rtr/pdu_test.c +pdu_test_LDADD = ${MY_LDADD} + pdu_handler_test_SOURCES = rtr/pdu_handler_test.c pdu_handler_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} +primitive_reader_test_SOURCES = rtr/primitive_reader_test.c +primitive_reader_test_LDADD = ${MY_LDADD} + rrdp_objects_test_SOURCES = rrdp_objects_test.c rrdp_objects_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} ${XML2_LIBS} -rsync_test_SOURCES = rsync_test.c -rsync_test_LDADD = ${MY_LDADD} - serial_test_SOURCES = types/serial_test.c serial_test_LDADD = ${MY_LDADD} @@ -71,6 +81,9 @@ thread_pool_test_LDADD = ${MY_LDADD} uri_test_SOURCES = types/uri_test.c uri_test_LDADD = ${MY_LDADD} +uthash_test_SOURCES = data_structure/uthash_test.c +uthash_test_LDADD = ${MY_LDADD} + vcard_test_SOURCES = vcard_test.c vcard_test_LDADD = ${MY_LDADD} @@ -80,15 +93,6 @@ vrps_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} xml_test_SOURCES = xml_test.c xml_test_LDADD = ${MY_LDADD} ${XML2_LIBS} -rtr_pdu_test_SOURCES = rtr/pdu_test.c -rtr_pdu_test_LDADD = ${MY_LDADD} - -rtr_primitive_reader_test_SOURCES = rtr/primitive_reader_test.c -rtr_primitive_reader_test_LDADD = ${MY_LDADD} - -data_structure_uthash_test_SOURCES = data_structure/uthash_test.c -data_structure_uthash_test_LDADD = ${MY_LDADD} - EXTRA_DIST = mock.c EXTRA_DIST += line_file/core.txt EXTRA_DIST += line_file/empty.txt diff --git a/test/cache/local_cache_test.c b/test/cache/local_cache_test.c new file mode 100644 index 00000000..a2fa9ffe --- /dev/null +++ b/test/cache/local_cache_test.c @@ -0,0 +1,1061 @@ +/* This test will create temporal directory "tmp/". Needs permissions. */ + +#include "cache/local_cache.c" + +#include +#include + +#include "alloc.c" +#include "common.c" +#include "file.c" +#include "mock.c" +#include "data_structure/path_builder.c" +#include "types/uri.c" + +/* Mocks */ + +MOCK_ABORT_PTR(state_retrieve, validation, void) + +static unsigned int dl_count; /* Times the download function was called */ +static bool dl_error; /* Download should return error? */ + +int +rsync_download(struct rpki_uri *uri) +{ + char *cmd; + int printed; + + dl_count++; + if (dl_error) + return -EINVAL; + + cmd = pmalloc(128); + printed = snprintf(cmd, 128, "mkdir -p tmp/%s", uri_get_local(uri)); + ck_assert(printed < 128); + + ck_assert_int_eq(0, system(cmd)); + + free(cmd); + return 0; +} + +int +http_download(struct rpki_uri *uri, bool *changed) +{ + char *cmd; + int printed; + int error; + + dl_count++; + if (dl_error) + return -EINVAL; + + cmd = pmalloc(128); + printed = snprintf(cmd, 128, + /* "create file, but only if it's not already a directory" */ + "test ! -d tmp/%s && install -D /dev/null tmp/%s", + uri_get_local(uri), uri_get_local(uri)); + ck_assert(printed < 128); + + error = system(cmd); + + free(cmd); + return error; +} + +/* Helpers */ + +static const int SUCCESS = CNF_DIRECT | CNF_SUCCESS; +static const int HTTP_SUCCESS = SUCCESS | CNF_FILE; + +static bool +is_rsync(struct cache_node *node) +{ + while (node->parent != NULL) + node = node->parent; + return strcmp(node->basename, "rsync") == 0; +} + +static bool +is_https(struct cache_node *node) +{ + while (node->parent != NULL) + node = node->parent; + return strcmp(node->basename, "https") == 0; +} + +static void +__download(char const *url, enum uri_type uritype, int expected_error, + unsigned int expected_cb_count) +{ + struct rpki_uri *uri; + + ck_assert_int_eq(0, uri_create(&uri, uritype, url)); + dl_count = 0; + + ck_assert_int_eq(expected_error, cache_download(uri, NULL)); + ck_assert_uint_eq(expected_cb_count, dl_count); + + uri_refput(uri); +} + +#define download_rsync(url, err, ecc) __download(url, UT_RSYNC, err, ecc) +#define download_https(url, err, ecc) __download(url, UT_HTTPS, err, ecc) + +static struct cache_node * +__NODE(char const *basename, int flags, time_t success, time_t attempt, + int error, ...) +{ + struct cache_node *result; + struct cache_node *child; + va_list args; + + result = pzalloc(sizeof(struct cache_node)); + result->basename = pstrdup(basename); + result->flags = flags; + result->ts_success = success; + result->ts_attempt = attempt; + result->error = error; + + va_start(args, error); + while ((child = va_arg(args, struct cache_node *)) != NULL) { + HASH_ADD_KEYPTR(hh, result->children, child->basename, + strlen(child->basename), child); + child->parent = result; + } + va_end(args); + + return result; +} + +#define NODE(bs, f, ...) __NODE(bs, f, 0, 0, __VA_ARGS__, NULL) +/* "Timed" node */ +#define TNODE(bs, f, s, a, ...) __NODE(bs, f, s, a, __VA_ARGS__, NULL) + +static void +actual_not_found(struct cache_node *expected, char *parent_basename) +{ + ck_abort_msg("Parent '%s' is missing child '%s'", parent_basename, + expected->basename); +} + +static void +expected_not_found(struct cache_node *actual) +{ + ck_abort_msg("Parent '%s' has unexpected node '%s'", + (actual->parent == NULL) ? "root" : actual->parent->basename, + actual->basename); +} + +static void +print_tree(struct cache_node *root, unsigned int tabs) +{ + struct cache_node *cursor, *tmp; + unsigned int t; + + if (root == NULL) + return; + + for (t = 0; t < tabs; t++) + printf("\t"); + printf("%s\n", root->basename); + + HASH_ITER(hh, root->children, cursor, tmp) + print_tree(cursor, tabs + 1); +} + +static void +validate_node(struct cache_node *expected, struct cache_node *expected_parent, + struct cache_node *actual, struct path_builder *pb) +{ + struct cache_node *expected_child, *actual_child, *tmp; + + ck_assert_str_eq(expected->basename, actual->basename); + ck_assert_int_eq(expected->flags, actual->flags); + if (expected->flags & CNF_DIRECT) { + /* ck_assert_int_ne(0, actual->ts_attempt); */ + /* ck_assert_int_eq(actual->ts_attempt, actual->ts_success); */ + if (expected->error) + ck_assert_int_ne(0, actual->error); + else + ck_assert_int_eq(0, actual->error); + } else { + /* ck_assert_int_eq(0, actual->ts_attempt); */ + /* ck_assert_int_eq(0, actual->ts_success); */ + ck_assert_int_eq(0, actual->error); + } + ck_assert_ptr_eq(expected_parent, actual->parent); + + path_append(pb, expected->basename); + + HASH_ITER(hh, expected->children, expected_child, tmp) { + HASH_FIND_STR(actual->children, expected_child->basename, + actual_child); + if (actual_child == NULL) + actual_not_found(expected_child, actual->basename); + validate_node(expected_child, actual, actual_child, pb); + } + + HASH_ITER(hh, actual->children, actual_child, tmp) { + HASH_FIND_STR(expected->children, actual_child->basename, + expected_child); + if (expected_child == NULL) + expected_not_found(actual_child); + } + + path_pop(pb, true); +} + +static void +search_dir(DIR *parent, char const *path, char const *name) +{ + struct dirent *file; + int error; + + rewinddir(parent); + FOREACH_DIR_FILE(parent, file) { + if (S_ISDOTS(file)) + continue; + + if (strcmp(name, file->d_name) == 0) + return; + } + + error = errno; + ck_assert_int_eq(0, error); + + ck_abort_msg("File %s/%s doesn't exist", path, name); +} + +static void +validate_file(struct cache_node *expected, struct path_builder *pb) +{ + char const *path; + struct stat meta; + DIR *dir; + struct dirent *file; + struct cache_node *child, *tmp; + int error; + + if (expected == NULL) + return; + + path_append(pb, expected->basename); + ck_assert_int_eq(0, path_peek(pb, &path)); + + if (is_rsync(expected)) { + /* Currently, the unit tests do not fake rsync files */ + goto must_be_dir; + + } else if (is_https(expected)) { + if (expected->flags & CNF_DIRECT) { + if (expected->error == 0) + goto must_be_file; /* Because HTTP */ + else + goto end; + } else { + goto must_be_dir; /* Because HTTP */ + } + } else { + ck_abort_msg("Not rsync nor httpd"); + } + +must_be_file: + ck_assert_int_eq(0, stat(path, &meta)); + ck_assert_int_eq(1, S_ISREG(meta.st_mode)); + goto end; + +must_be_dir: + errno = 0; + dir = opendir(path); + error = errno; + ck_assert_int_eq(0, error); + ck_assert_ptr_nonnull(dir); + + FOREACH_DIR_FILE(dir, file) { + if (S_ISDOTS(file)) + continue; + + HASH_FIND_STR(expected->children, file->d_name, child); + if (child == NULL) { + ck_abort_msg("file %s/%s is not supposed to exist.", + path, file->d_name); + } + + validate_file(child, pb); + } + error = errno; + ck_assert_int_eq(0, error); + + HASH_ITER(hh, expected->children, child, tmp) + search_dir(dir, path, child->basename); + + closedir(dir); +end: + path_pop(pb, true); +} + +static void +validate_trees(struct cache_node *nodes, struct cache_node *files) +{ + struct cache_node *actual; + struct path_builder pb; + + if (is_rsync(nodes)) + actual = rsync; + else if (is_https(nodes)) + actual = https; + else + ck_abort_msg("unknown root node: %s", nodes->basename); + + printf("------------------------------\n"); + printf("Expected nodes:\n"); + print_tree(nodes, 1); + printf("Actual nodes:\n"); + print_tree(actual, 1); + if (files != NULL) { + if (nodes != files) { + printf("Expected files:\n"); + print_tree(files, 0); + } + printf("Actual files:\n"); + file_ls_R("tmp"); + } + + path_init(&pb); + path_append(&pb, "tmp"); + + validate_node(nodes, NULL, actual, &pb); + validate_file(files, &pb); + + path_cancel(&pb); + + destroy_tree(nodes); + if (nodes != files) + destroy_tree(files); +} + +static void +validate_tree(struct cache_node *expected) +{ + validate_trees(expected, expected); +} + +static void +backtrack_times(struct cache_node *node) +{ + struct cache_node *child, *tmp; + node->ts_success -= 1000; + node->ts_attempt -= 1000; + HASH_ITER(hh, node->children, child, tmp) + backtrack_times(child); +} + +static void +__cache_prepare(void) +{ + cache_prepare(); + /* Ensure the old ts_successes and ts_attempts are outdated */ + backtrack_times(rsync); + backtrack_times(https); +} + +/* Tests */ + +START_TEST(test_cache_download_rsync) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + dl_error = false; + + cache_prepare(); + + download_rsync("rsync://a.b.c/d/e", 0, 1); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", 0, 0, + NODE("e", SUCCESS, 0))))); + + /* Redownload same file, nothing should happen */ + download_rsync("rsync://a.b.c/d/e", 0, 0); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", 0, 0, + NODE("e", SUCCESS, 0))))); + + /* + * For better *and* worse, rsyncs are recursive, which means if we've + * been recently asked to download e, we needn't bother redownloading + * e/f. + */ + download_rsync("rsync://a.b.c/d/e/f", 0, 0); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", 0, 0, + NODE("e", SUCCESS, 0))))); + + /* + * The trees will *look* different, because the tree will get trimmed, + * while the filesystem will not. + */ + download_rsync("rsync://a.b.c/d", 0, 1); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", 0, 0, + NODE("e", 0, 0)))) + ); + + download_rsync("rsync://a.b.c/e", 0, 1); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", SUCCESS, 0))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", 0, 0, + NODE("e", 0, 0)), + NODE("e", 0, 0))) + ); + + download_rsync("rsync://x.y.z/e", 0, 1); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", SUCCESS, 0)), + NODE("x.y.z", 0, 0, + NODE("e", SUCCESS, 0))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", 0, 0, + NODE("e", 0, 0)), + NODE("e", 0, 0)), + NODE("x.y.z", 0, 0, + NODE("e", 0, 0)))); + + cache_teardown(); +} +END_TEST + +START_TEST(test_cache_download_rsync_error) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + + cache_prepare(); + + dl_error = false; + download_rsync("rsync://a.b.c/d", 0, 1); + dl_error = true; + download_rsync("rsync://a.b.c/e", -EINVAL, 1); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", CNF_DIRECT, -EINVAL))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0)))); + + /* Regardless of error, not reattempted because same iteration */ + dl_error = true; + download_rsync("rsync://a.b.c/e", -EINVAL, 0); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", CNF_DIRECT, -EINVAL))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0)))); + + dl_error = false; + download_rsync("rsync://a.b.c/e", -EINVAL, 0); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", CNF_DIRECT, -EINVAL))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0)))); + + cache_teardown(); +} +END_TEST + +START_TEST(test_cache_cleanup_rsync) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + dl_error = false; + + /* + * First iteration: Tree is created. No prunes, because nothing's + * outdated. + */ + __cache_prepare(); + download_rsync("rsync://a.b.c/d", 0, 1); + download_rsync("rsync://a.b.c/e", 0, 1); + cache_cleanup(); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", SUCCESS, 0)))); + + /* One iteration with no changes, for paranoia */ + __cache_prepare(); + download_rsync("rsync://a.b.c/d", 0, 1); + download_rsync("rsync://a.b.c/e", 0, 1); + cache_cleanup(); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", SUCCESS, 0)))); + + /* Add one sibling */ + __cache_prepare(); + download_rsync("rsync://a.b.c/d", 0, 1); + download_rsync("rsync://a.b.c/e", 0, 1); + download_rsync("rsync://a.b.c/f", 0, 1); + cache_cleanup(); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", SUCCESS, 0), + NODE("f", SUCCESS, 0)))); + + /* Remove some branches */ + __cache_prepare(); + download_rsync("rsync://a.b.c/d", 0, 1); + cache_cleanup(); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0)))); + + /* Remove old branch and add sibling at the same time */ + __cache_prepare(); + download_rsync("rsync://a.b.c/e", 0, 1); + cache_cleanup(); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", SUCCESS, 0)))); + + /* Add a child to the same branch, do not update the old one */ + __cache_prepare(); + download_rsync("rsync://a.b.c/e/f/g", 0, 1); + cache_cleanup(); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", SUCCESS, 0, + NODE("f", 0, 0, + NODE("g", SUCCESS, 0)))))); + + /* + * Download parent, do not update child. + * Child's node should be deleted (because we don't need it anymore), + * but its file should persist (because it should be retained as its + * parent's descendant). + */ + __cache_prepare(); + download_rsync("rsync://a.b.c/e/f", 0, 1); + cache_cleanup(); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", SUCCESS, 0, + NODE("f", SUCCESS, 0)))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", SUCCESS, 0, + NODE("f", 0, 0, + NODE("g", SUCCESS, 0)))))); + + /* Do it again. Node should die, all descendant files should persist. */ + __cache_prepare(); + download_rsync("rsync://a.b.c/e", 0, 1); + cache_cleanup(); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", SUCCESS, 0))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", SUCCESS, 0, + NODE("f", 0, 0, + NODE("g", SUCCESS, 0)))))); + + /* Empty the tree */ + __cache_prepare(); + cache_cleanup(); + validate_tree(NODE("rsync", 0, 0)); + + /* Node exists, but file doesn't */ + __cache_prepare(); + download_rsync("rsync://a.b.c/e", 0, 1); + download_rsync("rsync://a.b.c/f/g/h", 0, 1); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", SUCCESS, 0), + NODE("f", 0, 0, + NODE("g", 0, 0, + NODE("h", SUCCESS, 0)))))); + ck_assert_int_eq(0, system("rm -rf tmp/rsync/a.b.c/f/g")); + cache_cleanup(); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", SUCCESS, 0)))); + + cache_teardown(); +} +END_TEST + +START_TEST(test_cache_cleanup_rsync_error) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + + cache_prepare(); + + /* Set up */ + dl_error = false; + download_rsync("rsync://a.b.c/d", 0, 1); + dl_error = true; + download_rsync("rsync://a.b.c/e", -EINVAL, 1); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0), + NODE("e", CNF_DIRECT, -EINVAL))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0)))); + + /* + * I originally intended this test to delete e because of the error, + * but it actually gets deleted because the file doesn't exist. + * Which is fine; we should test that too. We'll try d next, which + * does have a file. + */ + cache_cleanup(); + validate_tree( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0)))); + + /* Fail d */ + __cache_prepare(); + dl_error = true; + download_rsync("rsync://a.b.c/d", -EINVAL, 1); + validate_trees( + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", CNF_DIRECT, -EINVAL))), + NODE("rsync", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", SUCCESS, 0)))); + + /* Clean up d because of error */ + cache_cleanup(); + validate_tree(NODE("rsync", 0, 0)); + + cache_teardown(); +} +END_TEST + +START_TEST(test_cache_download_https) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + dl_error = false; + + cache_prepare(); + + /* Download *file* e. */ + download_https("https://a.b.c/d/e", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", 0, 0, + NODE("e", HTTP_SUCCESS, 0))))); + + /* e is now a dir; need to replace it. */ + download_https("https://a.b.c/d/e/f", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", 0, 0, + NODE("e", 0, 0, + NODE("f", HTTP_SUCCESS, 0)))))); + + /* d is now a file; need to replace it. */ + download_https("https://a.b.c/d", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + /* Download something else 1 */ + download_https("https://a.b.c/e", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0), + NODE("e", HTTP_SUCCESS, 0)))); + + /* Download something else 2 */ + download_https("https://x.y.z/e", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0), + NODE("e", HTTP_SUCCESS, 0)), + NODE("x.y.z", 0, 0, + NODE("e", HTTP_SUCCESS, 0)))); + + cache_teardown(); +} +END_TEST + +START_TEST(test_cache_download_https_error) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + + cache_prepare(); + + dl_error = false; + download_https("https://a.b.c/d", 0, 1); + dl_error = true; + download_https("https://a.b.c/e", -EINVAL, 1); + validate_trees( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0), + NODE("e", CNF_DIRECT, -EINVAL))), + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + /* Regardless of error, not reattempted because same iteration */ + dl_error = true; + download_https("https://a.b.c/e", -EINVAL, 0); + validate_trees( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0), + NODE("e", CNF_DIRECT, -EINVAL))), + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + dl_error = false; + download_https("https://a.b.c/e", -EINVAL, 0); + validate_trees( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0), + NODE("e", CNF_DIRECT, -EINVAL))), + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + cache_teardown(); +} +END_TEST + +START_TEST(test_cache_cleanup_https) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + dl_error = false; + + /* First iteration; make a tree and clean it */ + __cache_prepare(); + download_https("https://a.b.c/d", 0, 1); + download_https("https://a.b.c/e", 0, 1); + cache_cleanup(); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0), + NODE("e", HTTP_SUCCESS, 0)))); + + /* Remove one branch */ + __cache_prepare(); + download_https("https://a.b.c/d", 0, 1); + cache_cleanup(); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + /* Change the one branch */ + __cache_prepare(); + download_https("https://a.b.c/e", 0, 1); + cache_cleanup(); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", HTTP_SUCCESS, 0)))); + + /* + * Add a child to the same branch, do not update the old one + */ + __cache_prepare(); + download_https("https://a.b.c/e/f/g", 0, 1); + cache_cleanup(); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", 0, 0, + NODE("f", 0, 0, + NODE("g", HTTP_SUCCESS, 0)))))); + + /* + * Download parent, do not update child. + * Children need to die, because parent is now a file. + */ + __cache_prepare(); + download_https("https://a.b.c/e/f", 0, 1); + cache_cleanup(); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", 0, 0, + NODE("f", HTTP_SUCCESS, 0))))); + + /* Do it again. */ + __cache_prepare(); + download_https("https://a.b.c/e", 0, 1); + cache_cleanup(); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", HTTP_SUCCESS, 0)))); + + /* Empty the tree */ + __cache_prepare(); + cache_cleanup(); + validate_tree(NODE("https", 0, 0)); + + /* Node exists, but file doesn't */ + __cache_prepare(); + download_https("https://a.b.c/e", 0, 1); + download_https("https://a.b.c/f/g/h", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", HTTP_SUCCESS, 0), + NODE("f", 0, 0, + NODE("g", 0, 0, + NODE("h", HTTP_SUCCESS, 0)))))); + ck_assert_int_eq(0, system("rm -rf tmp/https/a.b.c/f/g")); + cache_cleanup(); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", HTTP_SUCCESS, 0)))); + + cache_teardown(); +} +END_TEST + +START_TEST(test_cache_cleanup_https_error) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + + cache_prepare(); + + /* Set up */ + dl_error = false; + download_https("https://a.b.c/d", 0, 1); + dl_error = true; + download_https("https://a.b.c/e", -EINVAL, 1); + validate_trees( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0), + NODE("e", CNF_DIRECT, -EINVAL))), + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + /* Deleted because file ENOENT. */ + cache_cleanup(); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + /* Fail d */ + __cache_prepare(); + dl_error = true; + download_https("https://a.b.c/d", -EINVAL, 1); + validate_trees( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", CNF_DIRECT, -EINVAL))), + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + /* Clean up d because of error */ + cache_cleanup(); + validate_tree(NODE("https", 0, 0)); + + cache_teardown(); +} +END_TEST + +START_TEST(test_dots) +{ + ck_assert_int_eq(0, system("rm -rf tmp/")); + dl_error = false; + + cache_prepare(); + + download_https("https://a.b.c/d", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + download_https("https://a.b.c/d/.", 0, 0); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("d", HTTP_SUCCESS, 0)))); + + download_https("https://a.b.c/d/..", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", HTTP_SUCCESS, 0))); + + download_https("https://a.b.c/./d/../e", 0, 1); + validate_tree( + NODE("https", 0, 0, + NODE("a.b.c", 0, 0, + NODE("e", HTTP_SUCCESS, 0)))); + + cache_teardown(); +} +END_TEST + +START_TEST(test_metadata_json) +{ + const time_t NOW = 1693952610; + json_t *json; + char *str; + + rsync = TNODE("rsync", 0, NOW + 0, NOW + 1, 0, + TNODE("a.b.c", 0, NOW + 2, NOW + 3, 0, + TNODE("d", SUCCESS, NOW + 4, NOW + 5, 0), + TNODE("e", SUCCESS, NOW + 6, NOW + 7, 0)), + TNODE("x.y.z", 0, NOW + 8, NOW + 9, 0, + TNODE("w", SUCCESS, NOW + 0, NOW + 1, 0))); + https = TNODE("https", 0, NOW + 2, NOW + 3, 0, + TNODE("a", 0, NOW + 4, NOW + 5, 0, + TNODE("b", HTTP_SUCCESS, NOW + 6, NOW + 7, 0), + TNODE("c", HTTP_SUCCESS, NOW + 8, NOW + 9, 0))); + + json = build_metadata_json(); + ck_assert_int_eq(0, json_dump_file(json, "tmp/metadata.json", JSON_COMPACT)); + + str = json_dumps(json, /* JSON_INDENT(4) */ JSON_COMPACT); + /* printf("%s\n", str); */ + json_decref(json); + + ck_assert_str_eq( + "[{\"basename\":\"rsync\",\"flags\":0,\"ts_success\":\"2023-09-05T16:23:30-0600\",\"ts_attempt\":\"2023-09-05T16:23:31-0600\",\"error\":0,\"children\":[" + "{\"basename\":\"a.b.c\",\"flags\":0,\"ts_success\":\"2023-09-05T16:23:32-0600\",\"ts_attempt\":\"2023-09-05T16:23:33-0600\",\"error\":0,\"children\":[" + "{\"basename\":\"d\",\"flags\":3,\"ts_success\":\"2023-09-05T16:23:34-0600\",\"ts_attempt\":\"2023-09-05T16:23:35-0600\",\"error\":0}," + "{\"basename\":\"e\",\"flags\":3,\"ts_success\":\"2023-09-05T16:23:36-0600\",\"ts_attempt\":\"2023-09-05T16:23:37-0600\",\"error\":0}]}," + "{\"basename\":\"x.y.z\",\"flags\":0,\"ts_success\":\"2023-09-05T16:23:38-0600\",\"ts_attempt\":\"2023-09-05T16:23:39-0600\",\"error\":0,\"children\":[" + "{\"basename\":\"w\",\"flags\":3,\"ts_success\":\"2023-09-05T16:23:30-0600\",\"ts_attempt\":\"2023-09-05T16:23:31-0600\",\"error\":0}]}]}," + "{\"basename\":\"https\",\"flags\":0,\"ts_success\":\"2023-09-05T16:23:32-0600\",\"ts_attempt\":\"2023-09-05T16:23:33-0600\",\"error\":0,\"children\":[" + "{\"basename\":\"a\",\"flags\":0,\"ts_success\":\"2023-09-05T16:23:34-0600\",\"ts_attempt\":\"2023-09-05T16:23:35-0600\",\"error\":0,\"children\":[" + "{\"basename\":\"b\",\"flags\":11,\"ts_success\":\"2023-09-05T16:23:36-0600\",\"ts_attempt\":\"2023-09-05T16:23:37-0600\",\"error\":0}," + "{\"basename\":\"c\",\"flags\":11,\"ts_success\":\"2023-09-05T16:23:38-0600\",\"ts_attempt\":\"2023-09-05T16:23:39-0600\",\"error\":0}]}]}]", + str); + free(str); + + cache_teardown(); + rsync = https = NULL; + + load_metadata_json(); + validate_trees( + TNODE("rsync", 0, NOW + 0, NOW + 1, 0, + TNODE("a.b.c", 0, NOW + 2, NOW + 3, 0, + TNODE("d", SUCCESS, NOW + 4, NOW + 5, 0), + TNODE("e", SUCCESS, NOW + 6, NOW + 7, 0)), + TNODE("x.y.z", 0, NOW + 8, NOW + 9, 0, + TNODE("w", SUCCESS, NOW + 0, NOW + 1, 0))), + NULL); + validate_trees( + TNODE("https", 0, NOW + 2, NOW + 3, 0, + TNODE("a", 0, NOW + 4, NOW + 5, 0, + TNODE("b", HTTP_SUCCESS, NOW + 6, NOW + 7, 0), + TNODE("c", HTTP_SUCCESS, NOW + 8, NOW + 9, 0))), + NULL); +} +END_TEST + +/* Boilerplate */ + +Suite *thread_pool_suite(void) +{ + Suite *suite; + TCase *rsync , *https, *dot, *meta; + + rsync = tcase_create("rsync"); + tcase_add_test(rsync, test_cache_download_rsync); + tcase_add_test(rsync, test_cache_download_rsync_error); + tcase_add_test(rsync, test_cache_cleanup_rsync); + tcase_add_test(rsync, test_cache_cleanup_rsync_error); + + https = tcase_create("https"); + tcase_add_test(https, test_cache_download_https); + tcase_add_test(https, test_cache_download_https_error); + tcase_add_test(https, test_cache_cleanup_https); + tcase_add_test(https, test_cache_cleanup_https_error); + + dot = tcase_create("dot"); + tcase_add_test(https, test_dots); + + meta = tcase_create("metadata.json"); + tcase_add_test(https, test_metadata_json); + + suite = suite_create("local-cache"); + suite_add_tcase(suite, rsync); + suite_add_tcase(suite, https); + suite_add_tcase(suite, dot); + suite_add_tcase(suite, meta); + + return suite; +} + +int main(void) +{ + Suite *suite; + SRunner *runner; + int tests_failed; + + suite = thread_pool_suite(); + + runner = srunner_create(suite); + srunner_run_all(runner, CK_NORMAL); + tests_failed = srunner_ntests_failed(runner); + srunner_free(runner); + + return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/data_structure/path_builder_test.c b/test/data_structure/path_builder_test.c new file mode 100644 index 00000000..8b0f339c --- /dev/null +++ b/test/data_structure/path_builder_test.c @@ -0,0 +1,478 @@ +#include +#include + +#define INITIAL_CAPACITY 8 + +#include "alloc.c" +#include "mock.c" +#include "data_structure/path_builder.c" + +/* Mocks */ + +__MOCK_ABORT(uri_get_global, char const *, NULL, struct rpki_uri *uri) +__MOCK_ABORT(uri_get_global_len, size_t, 0, struct rpki_uri *uri) + +/* Tests */ + +#define CHECK_PB(_len, _capacity, _error) \ + ck_assert_uint_eq(_len, pb.len); \ + ck_assert_uint_eq(_capacity, pb.capacity); \ + ck_assert_int_eq(_error, pb.error) + +#define CHECK_RESULTS(expected) \ + ck_assert_uint_eq(0, path_peek(&pb, &peek_result)); \ + ck_assert_str_eq(expected, peek_result); \ + ck_assert_uint_eq(0, path_compile(&pb, &compile_result)); \ + ck_assert_str_eq(expected, compile_result); \ + free(compile_result); + +#define CHECK_ERROR \ + ck_assert_uint_eq(EINVAL, path_peek(&pb, &peek_result)); \ + ck_assert_uint_eq(EINVAL, path_compile(&pb, &compile_result)); + +START_TEST(test_append) +{ + struct path_builder pb; + char const *peek_result; + char *compile_result; + + path_init(&pb); + path_append(&pb, ""); + CHECK_PB(0, 8, 0); + CHECK_RESULTS(""); + + path_init(&pb); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + CHECK_RESULTS("a"); + + path_init(&pb); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + path_append(&pb, "b"); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("a/b"); + + path_init(&pb); + path_append(&pb, "a/b"); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("a/b"); + + path_init(&pb); + path_append(&pb, "a/"); + CHECK_PB(1, 8, 0); + path_append(&pb, "b/"); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("a/b"); + + /* notes from .h */ + path_init(&pb); + path_append(&pb, "a/"); + CHECK_PB(1, 8, 0); + path_append(&pb, "b"); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("a/b"); + + path_init(&pb); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + path_append(&pb, "/b"); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("a/b"); + + path_init(&pb); + path_append(&pb, "a/"); + CHECK_PB(1, 8, 0); + path_append(&pb, "/b"); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("a/b"); + + path_init(&pb); + path_append(&pb, "//a"); + CHECK_PB(1, 8, 0); + path_append(&pb, "///"); + CHECK_PB(1, 8, 0); + path_append(&pb, "b////"); + CHECK_PB(3, 8, 0); + path_append(&pb, "/////c//////"); + CHECK_PB(5, 8, 0); + CHECK_RESULTS("a/b/c"); + + path_init(&pb); + path_append(&pb, "//a///b//c//"); + CHECK_PB(5, 8, 0); + CHECK_RESULTS("a/b/c"); +} +END_TEST + +/* Actually mainly designed to manhandle capacity expansion */ +START_TEST(test_uint) +{ + struct path_builder pb; + char const *peek_result; + char *compile_result; + + path_init(&pb); + path_append_uint(&pb, 291); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("123"); /* hex */ + + path_init(&pb); + path_append_uint(&pb, 19088743); + CHECK_PB(7, 8, 0); + CHECK_RESULTS("1234567"); + + path_init(&pb); + path_append_uint(&pb, 305419896); + CHECK_PB(8, 16, 0); + CHECK_RESULTS("12345678"); + + path_init(&pb); + path_append_uint(&pb, 74565); + CHECK_PB(5, 8, 0); + path_append_uint(&pb, 7); + CHECK_PB(7, 8, 0); + CHECK_RESULTS("12345/7"); + + path_init(&pb); + path_append_uint(&pb, 74565); + CHECK_PB(5, 8, 0); + path_append_uint(&pb, 120); + CHECK_PB(8, 16, 0); + CHECK_RESULTS("12345/78"); + + path_init(&pb); + path_append_uint(&pb, 74565); + CHECK_PB(5, 8, 0); + path_append_uint(&pb, 1929); + CHECK_PB(9, 16, 0); + CHECK_RESULTS("12345/789"); +} +END_TEST + +START_TEST(test_pop) +{ + struct path_builder pb; + char const *peek_result; + char *compile_result; + + path_init(&pb); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + path_append(&pb, "b"); + CHECK_PB(3, 8, 0); + path_pop(&pb, false); + CHECK_PB(1, 8, 0); + CHECK_RESULTS("a"); + + path_init(&pb); + path_append(&pb, "abc"); + CHECK_PB(3, 8, 0); + path_append(&pb, "def"); + CHECK_PB(7, 8, 0); + path_pop(&pb, false); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("abc"); + + path_init(&pb); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + path_pop(&pb, false); + CHECK_PB(0, 8, 0); + CHECK_RESULTS(""); + + path_init(&pb); + path_append(&pb, "/a"); + CHECK_PB(1, 8, 0); + path_pop(&pb, false); + CHECK_PB(0, 8, 0); + CHECK_RESULTS(""); + + path_init(&pb); + path_pop(&pb, false); + CHECK_PB(0, 8, EINVAL); + CHECK_ERROR; + + path_init(&pb); + path_append(&pb, "a"); + path_pop(&pb, false); + CHECK_PB(0, 8, 0); + path_pop(&pb, false); + CHECK_PB(0, 8, EINVAL); + CHECK_ERROR; + +// path_init(&pb); +// path_append(&pb, "/"); +// CHECK_PB(1, 8, 0); +// path_pop(&pb); +// CHECK_PB(0, 8, 0); +// CHECK_RESULTS(""); +// +// path_init(&pb); +// path_append(&pb, "///"); +// CHECK_PB(3, 8, 0); +// path_pop(&pb); +// CHECK_PB(2, 8, 0); +// path_pop(&pb); +// CHECK_PB(1, 8, 0); +// path_pop(&pb); +// CHECK_PB(0, 8, 0); +// CHECK_RESULTS(""); +} +END_TEST + +START_TEST(test_peek) +{ + struct path_builder pb; + char const *peek_result; + + /* + * Most of path_peek() has already been tested above, + * just check it leaves the pb in a stable state. + */ + + path_init(&pb); + + path_peek(&pb, &peek_result); + ck_assert_str_eq("", peek_result); + + path_append(&pb, "a"); + path_peek(&pb, &peek_result); + ck_assert_str_eq("a", peek_result); + + path_append(&pb, "b"); + path_peek(&pb, &peek_result); + ck_assert_str_eq("a/b", peek_result); + + path_pop(&pb, true); + path_peek(&pb, &peek_result); + ck_assert_str_eq("a", peek_result); + + path_pop(&pb, true); + path_peek(&pb, &peek_result); + ck_assert_str_eq("", peek_result); + + free(pb.string); +} +END_TEST + +START_TEST(test_reverse) +{ + struct path_builder pb; + char const *peek_result; + char *compile_result; + + /* 0 components */ + path_init(&pb); + path_reverse(&pb); + CHECK_PB(0, 8, 0); + CHECK_RESULTS(""); + + /* 1 component */ + path_init(&pb); + path_append(&pb, "a"); + path_reverse(&pb); + CHECK_PB(1, 8, 0); + CHECK_RESULTS("a"); + + /* 2 components */ + path_init(&pb); + path_append(&pb, "a"); + path_append(&pb, "b"); + path_reverse(&pb); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("b/a"); + + path_init(&pb); + path_append(&pb, "abc"); + path_append(&pb, "def"); + path_reverse(&pb); + CHECK_PB(7, 8, 0); + CHECK_RESULTS("def/abc"); + + path_init(&pb); + path_append(&pb, "abcd"); + path_append(&pb, "efgh"); + path_reverse(&pb); + CHECK_PB(9, 16, 0); + CHECK_RESULTS("efgh/abcd"); + + path_init(&pb); + path_append(&pb, "abc"); + path_append(&pb, "efgh"); + path_reverse(&pb); + CHECK_PB(8, 8, 0); + CHECK_RESULTS("efgh/abc"); + + path_init(&pb); + path_append(&pb, "abcd"); + path_append(&pb, "fgh"); + path_reverse(&pb); + CHECK_PB(8, 8, 0); + CHECK_RESULTS("fgh/abcd"); + + /* 3 components */ + path_init(&pb); + path_append(&pb, "abc"); + path_append(&pb, "def"); + path_append(&pb, "ghi"); + path_reverse(&pb); + CHECK_PB(11, 16, 0); + CHECK_RESULTS("ghi/def/abc"); + + path_init(&pb); + path_append(&pb, "ab"); + path_append(&pb, "cde"); + path_append(&pb, "fghi"); + path_reverse(&pb); + CHECK_PB(11, 16, 0); + CHECK_RESULTS("fghi/cde/ab"); + + /* 4 components */ + path_init(&pb); + path_append(&pb, "a"); + path_append(&pb, "b"); + path_append(&pb, "c"); + path_append(&pb, "d"); + path_reverse(&pb); + CHECK_PB(7, 8, 0); + CHECK_RESULTS("d/c/b/a"); + + path_init(&pb); + path_append(&pb, "ab"); + path_append(&pb, "cd"); + path_append(&pb, "ef"); + path_append(&pb, "gh"); + path_reverse(&pb); + CHECK_PB(11, 16, 0); + CHECK_RESULTS("gh/ef/cd/ab"); + + path_init(&pb); + path_append(&pb, "a"); + path_append(&pb, "bcd"); + path_append(&pb, "efgh"); + path_append(&pb, "ijklm"); + path_reverse(&pb); + CHECK_PB(16, 16, 0); + CHECK_RESULTS("ijklm/efgh/bcd/a"); + + path_init(&pb); + path_append(&pb, "abcdefghijklmnopq"); + path_append(&pb, "r"); + path_append(&pb, "stu"); + path_append(&pb, "vx"); + path_reverse(&pb); + CHECK_PB(26, 32, 0); + CHECK_RESULTS("vx/stu/r/abcdefghijklmnopq"); +} +END_TEST + +START_TEST(test_normalization) +{ + struct path_builder pb; + char const *peek_result; + char *compile_result; + + path_init(&pb); + path_append(&pb, "."); + CHECK_PB(0, 8, 0); + CHECK_RESULTS(""); + + path_init(&pb); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + path_append(&pb, "."); + CHECK_PB(1, 8, 0); + path_append(&pb, "b"); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("a/b"); + + path_init(&pb); + path_append(&pb, "."); + CHECK_PB(0, 8, 0); + path_append(&pb, "."); + CHECK_PB(0, 8, 0); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + path_append(&pb, "."); + CHECK_PB(1, 8, 0); + path_append(&pb, "."); + CHECK_PB(1, 8, 0); + path_append(&pb, "b"); + CHECK_PB(3, 8, 0); + path_append(&pb, "."); + CHECK_PB(3, 8, 0); + path_append(&pb, "."); + CHECK_PB(3, 8, 0); + CHECK_RESULTS("a/b"); + + path_init(&pb); + path_append(&pb, ".."); + CHECK_PB(0, 8, EINVAL); + CHECK_ERROR; + + path_init(&pb); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + path_append(&pb, "b"); + CHECK_PB(3, 8, 0); + path_append(&pb, ".."); + CHECK_PB(1, 8, 0); + CHECK_RESULTS("a"); + + path_init(&pb); + path_append(&pb, "a"); + CHECK_PB(1, 8, 0); + path_append(&pb, "b"); + CHECK_PB(3, 8, 0); + path_append(&pb, ".."); + CHECK_PB(1, 8, 0); + path_append(&pb, "."); + CHECK_PB(1, 8, 0); + path_append(&pb, ".."); + CHECK_PB(0, 8, 0); + CHECK_RESULTS(""); + + /* dot dot injection */ + path_init(&pb); + path_append(&pb, "a/../b"); + CHECK_PB(1, 8, 0); + CHECK_RESULTS("b"); +} +END_TEST + +Suite * +pdu_suite(void) +{ + Suite *suite; + TCase *core; + + core = tcase_create("functions"); + tcase_add_test(core, test_append); + tcase_add_test(core, test_uint); + tcase_add_test(core, test_pop); + tcase_add_test(core, test_peek); + tcase_add_test(core, test_reverse); + tcase_add_test(core, test_normalization); + + suite = suite_create("path_builder"); + suite_add_tcase(suite, core); + return suite; +} + +int +main(int argc, char **argv) +{ + Suite *suite; + SRunner *runner; + int tests_failed; + + suite = pdu_suite(); + + runner = srunner_create(suite); + srunner_run_all(runner, CK_NORMAL); + tests_failed = srunner_ntests_failed(runner); + srunner_free(runner); + + return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/data_structure/uthash_test.c b/test/data_structure/uthash_test.c index 4505f965..90ee0bad 100644 --- a/test/data_structure/uthash_test.c +++ b/test/data_structure/uthash_test.c @@ -118,16 +118,92 @@ START_TEST(test_replace) } END_TEST +/* + * To assure myself I can hash nodes using an rpki_uri's global field as key. + * (Given that they're private.) + * + * ie. Neither the node nor the key contains the key, but the key points to it + * somewhere else. + */ +START_TEST(test_uri) +{ + struct test2_key { + char *outer_string; + int something_else; + }; + + struct test2_node { + struct test2_key *key; + int value; + UT_hash_handle hh; + }; + + struct test2_node *table = NULL; + + char *keystr; + unsigned int keystrlen; + struct test2_node *node, *node2; + + /* Try finding a nonexistent node, because paranoia */ + keystr = strdup("potato"); + keystrlen = strlen(keystr); + + HASH_FIND(hh, table, keystr, keystrlen, node); + ck_assert_ptr_null(node); + + /* Add a node */ + node = malloc(sizeof(struct test2_node)); + node->key = malloc(sizeof(struct test2_key)); + memset(node->key, 0, sizeof(struct test2_key)); + node->key->outer_string = keystr; + node->key->something_else = 1; + + HASH_ADD_KEYPTR(hh, table, keystr, keystrlen, node); + + /* Try finding the node using the same string */ + node2 = NULL; + HASH_FIND(hh, table, keystr, keystrlen, node2); + ck_assert_ptr_eq(node, node2); + + /* Try finding the node using a different (but equal) string */ + keystr = strdup("potato"); + node2 = NULL; + HASH_FIND(hh, table, keystr, keystrlen, node2); + ck_assert_ptr_eq(node, node2); + free(keystr); + + /* Try finding some other string */ + keystr = strdup("potato2"); + keystrlen = strlen(keystr); + node2 = NULL; + HASH_FIND(hh, table, keystr, keystrlen, node2); + ck_assert_ptr_null(node2); + free(keystr); + + /* free the hash table contents */ + HASH_ITER(hh, table, node, node2) { + HASH_DEL(table, node); + free(node->key->outer_string); + free(node->key); + free(node); + } +} +END_TEST + Suite *pdu_suite(void) { Suite *suite; - TCase *core; + TCase *core, *uri; core = tcase_create("simple"); tcase_add_test(core, test_replace); + uri = tcase_create("uri"); + tcase_add_test(uri, test_uri); + suite = suite_create("uthash"); suite_add_tcase(suite, core); + suite_add_tcase(suite, uri); return suite; } diff --git a/test/line_file_test.c b/test/line_file_test.c index 9a374da9..01a4ab5c 100644 --- a/test/line_file_test.c +++ b/test/line_file_test.c @@ -1,3 +1,5 @@ +#define _XOPEN_SOURCE 500 + #include #include #include diff --git a/test/mock.c b/test/mock.c index 73e5e8b7..eb83f51a 100644 --- a/test/mock.c +++ b/test/mock.c @@ -1,7 +1,9 @@ #include "mock.h" +#include #include #include "state.h" +#include "incidence/incidence.h" /** * Some core functions, as linked from unit tests. @@ -16,6 +18,7 @@ MOCK_TRUE(log_op_enabled, unsigned int l) va_start(args, format); \ vfprintf(stdout, format, args); \ va_end(args); \ + printf("\n"); \ } while (0) #define MOCK_VOID_PRINT(name) \ @@ -63,9 +66,11 @@ void pr_crit(const char *format, ...) { va_list args; + fprintf(stderr, "pr_crit() called!\n"); va_start(args, format); - vfprintf(stdout, format, args); + vfprintf(stderr, format, args); va_end(args); + fprintf(stderr, "\n"); ck_abort(); } @@ -98,8 +103,7 @@ v6addr2str2(struct in6_addr const *addr) MOCK_NULL(config_get_slurm, char const *, void) MOCK(config_get_tal, char const *, "tal/", void) -MOCK(config_get_local_repository, char const *, "repository/", void) -MOCK_FALSE(config_get_shuffle_tal_uris, void) +MOCK(config_get_local_repository, char const *, "tmp", void) MOCK(config_get_mode, enum mode, STANDALONE, void) MOCK_TRUE(config_get_rsync_enabled, void) MOCK_NULL(config_get_output_roa, char const *, void) diff --git a/test/mock.h b/test/mock.h index 906b6bdc..d699cb64 100644 --- a/test/mock.h +++ b/test/mock.h @@ -16,15 +16,20 @@ #define MOCK_VOID(name, ...) \ void name(__VA_ARGS__) {} +#define __MOCK_ABORT_MSG ck_abort_msg("%s() called.", __func__) + #define __MOCK_ABORT(name, type, result, ...) \ - type name(__VA_ARGS__) { ck_abort(); return result; } + type name(__VA_ARGS__) { __MOCK_ABORT_MSG; return result; } #define MOCK_ABORT_INT(name, ...) \ __MOCK_ABORT(name, int, 0, __VA_ARGS__) +/* FIXME delete? */ +#define MOCK_ABORT_BOOL(name, ...) \ + __MOCK_ABORT(name, bool, false, __VA_ARGS__) #define MOCK_ABORT_ENUM(name, type, ...) \ __MOCK_ABORT(name, enum type, 0, __VA_ARGS__) #define MOCK_ABORT_PTR(name, type, ...) \ __MOCK_ABORT(name, struct type *, NULL, __VA_ARGS__) #define MOCK_ABORT_VOID(name, ...) \ - void name(__VA_ARGS__) { ck_abort(); } + void name(__VA_ARGS__) { __MOCK_ABORT_MSG; } #endif /* TEST_MOCK_H_ */ diff --git a/test/rrdp_objects_test.c b/test/rrdp_objects_test.c index a22df416..324e8a3d 100644 --- a/test/rrdp_objects_test.c +++ b/test/rrdp_objects_test.c @@ -6,6 +6,13 @@ #include "mock.c" #include "rrdp/rrdp_objects.c" +/* Mocks */ + +MOCK_ABORT_PTR(uri_refget, rpki_uri, struct rpki_uri *uri) +MOCK_ABORT_VOID(uri_refput, struct rpki_uri *uri) + +/* Mocks end */ + #define END 0xFFFF static void @@ -14,7 +21,7 @@ add_serials(struct deltas_head *deltas, ...) struct delta_head delta; va_list vl; - doc_data_init(&delta.doc_data); + metadata_init(&delta.meta); va_start(vl, deltas); while ((delta.serial = va_arg(vl, unsigned long)) != END) diff --git a/test/rsync_test.c b/test/rsync_test.c deleted file mode 100644 index dba230f2..00000000 --- a/test/rsync_test.c +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include - -#include "alloc.c" -#include "common.c" -#include "mock.c" -#include "str_token.c" -#include "types/uri.c" -#include "rsync/rsync.c" - -/* Mocks */ - -MOCK_NULL(state_retrieve, struct validation *, void) -MOCK_ABORT_PTR(validation_rsync_visited_uris, uri_list, struct validation *s) - -/* Tests */ - -static void -__mark_as_downloaded(char *uri_str, struct uri_list *visited_uris) -{ - struct rpki_uri *uri; - ck_assert_int_eq(0, uri_create_rsync_str(&uri, uri_str, strlen(uri_str))); - mark_as_downloaded(uri, visited_uris); - uri_refput(uri); -} - -static void -assert_downloaded(char *uri_str, struct uri_list *visited_uris, bool expected) -{ - struct rpki_uri *uri; - ck_assert_int_eq(0, uri_create_rsync_str(&uri, uri_str, strlen(uri_str))); - ck_assert_int_eq(is_already_downloaded(uri, visited_uris), expected); - uri_refput(uri); -} - -START_TEST(rsync_test_list) -{ - struct uri_list *visited_uris; - - visited_uris = rsync_create(); - ck_assert_ptr_nonnull(visited_uris); - - __mark_as_downloaded("rsync://example.foo/repository/", visited_uris); - __mark_as_downloaded("rsync://example.foo/member_repository/", - visited_uris); - __mark_as_downloaded("rsync://example.foz/repository/", visited_uris); - __mark_as_downloaded("rsync://example.boo/repo/", visited_uris); - __mark_as_downloaded("rsync://example.potato/rpki/", visited_uris); - - assert_downloaded("rsync://example.foo/repository/", visited_uris, - true); - assert_downloaded("rsync://example.foo/repository/abc/cdfg", - visited_uris, false); - assert_downloaded("rsync://example.foo/member_repository/bca", - visited_uris, false); - assert_downloaded("rsync://example.boo/repository/", visited_uris, - false); - assert_downloaded("rsync://example.potato/repository/", visited_uris, - false); - assert_downloaded("rsync://example.potato/rpki/abc/", visited_uris, - false); - - rsync_destroy(visited_uris); -} -END_TEST - -Suite *rsync_load_suite(void) -{ - Suite *suite; - TCase *uri_list; - - uri_list = tcase_create("uriList"); - tcase_add_test(uri_list, rsync_test_list); - - suite = suite_create("rsync_test()"); - suite_add_tcase(suite, uri_list); - - return suite; -} - -int main(void) -{ - Suite *suite; - SRunner *runner; - int tests_failed; - - suite = rsync_load_suite(); - - runner = srunner_create(suite); - srunner_run_all(runner, CK_NORMAL); - tests_failed = srunner_ntests_failed(runner); - srunner_free(runner); - - return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/test/rtr/db/rtr_db_mock.c b/test/rtr/db/rtr_db_mock.c index 58e1a6d4..b621bf1d 100644 --- a/test/rtr/db/rtr_db_mock.c +++ b/test/rtr/db/rtr_db_mock.c @@ -68,7 +68,7 @@ __handle_router_key(unsigned char const *ski, struct asn_range const *range, unsigned char const *spk, void *arg) { uint64_t as; - int error; + int error = 0; for (as = range->min; as <= range->max; as++) { error = rtrhandler_handle_router_key(arg, ski, as, spk); diff --git a/test/rtr/db/vrps_test.c b/test/rtr/db/vrps_test.c index 5a7dbca8..e0bbc5b9 100644 --- a/test/rtr/db/vrps_test.c +++ b/test/rtr/db/vrps_test.c @@ -1,3 +1,5 @@ +#define _XOPEN_SOURCE 500 /* nftw() */ + #include #include #include @@ -69,6 +71,7 @@ static const bool deltas_4to4[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; unsigned int deltas_lifetime = 5; MOCK_UINT(config_get_deltas_lifetime, deltas_lifetime, void) +MOCK_VOID(cache_prepare, void) /* Test functions */ diff --git a/test/rtr/pdu_handler_test.c b/test/rtr/pdu_handler_test.c index 0bebc923..181483a2 100644 --- a/test/rtr/pdu_handler_test.c +++ b/test/rtr/pdu_handler_test.c @@ -1,23 +1,18 @@ +#define _XOPEN_SOURCE 500 + #include #include #include #include -#include "algorithm.c" #include "alloc.c" #include "common.c" -#include "file.c" -#include "json_parser.c" #include "mock.c" -#include "output_printer.c" -#include "crypto/base64.c" #include "types/delta.c" #include "types/router_key.c" #include "types/serial.c" #include "types/vrp.c" -#include "rtr/pdu.c" #include "rtr/pdu_handler.c" -#include "rtr/primitive_reader.c" #include "rtr/primitive_writer.c" #include "rtr/err_pdu.c" #include "rtr/db/delta.c" @@ -25,11 +20,22 @@ #include "rtr/db/db_table.c" #include "rtr/db/rtr_db_mock.c" #include "rtr/db/vrps.c" -#include "slurm/db_slurm.c" -#include "slurm/slurm_loader.c" -#include "slurm/slurm_parser.c" #include "thread/thread_pool.c" +/* Mocks */ + +MOCK_ABORT_INT(read_int32, struct pdu_reader *reader, uint32_t *result) +MOCK_ABORT_INT(read_int8, struct pdu_reader *reader, uint8_t *result) + +MOCK_VOID(cache_prepare, void) + +MOCK_INT(slurm_apply, 0, struct db_table *base, struct db_slurm **slurm) +MOCK_ABORT_VOID(db_slurm_destroy, struct db_slurm *db) + +MOCK_VOID(output_print_data, struct db_table const *db) + +/* Mocks end */ + struct expected_pdu { uint8_t pdu_type; STAILQ_ENTRY(expected_pdu) list_hook; diff --git a/test/tal_test.c b/test/tal_test.c index f7cc82a9..04614c5e 100644 --- a/test/tal_test.c +++ b/test/tal_test.c @@ -1,24 +1,38 @@ +#define _XOPEN_SOURCE 500 /* nftw() */ + #include "object/tal.c" #include #include "alloc.c" +#include "common.c" #include "file.c" #include "line_file.c" #include "mock.c" +#include "data_structure/path_builder.c" #include "types/uri.c" #include "crypto/base64.c" /* Mocks */ -static unsigned int rsync_priority; -static unsigned int http_priority; +MOCK_ABORT_VOID(thread_pool_push, struct thread_pool *pool, + char const *task_name, thread_pool_task_cb cb, void *arg) +MOCK_ABORT_VOID(thread_pool_wait, struct thread_pool *pool) + +MOCK_ABORT_INT(handle_roa_v4, uint32_t as, struct ipv4_prefix const *prefix, + uint8_t max_length, void *arg) +MOCK_ABORT_INT(handle_roa_v6, uint32_t as, struct ipv6_prefix const *prefix, + uint8_t max_length, void *arg) +MOCK_ABORT_INT(handle_router_key, unsigned char const *ski, + struct asn_range const *asns, unsigned char const *spk, void *arg) -MOCK_UINT(config_get_rsync_priority, rsync_priority, void) -MOCK_UINT(config_get_http_priority, http_priority, void) -/* These tests focus on global URIs, so set a dummy value */ -MOCK(map_uri_to_local, char *, pstrdup("dummy"), char const *u, char const *p) -MOCK_ABORT_VOID(db_rrdp_reset_visited_tals, void) +MOCK_ABORT_PTR(state_retrieve, validation, void) +MOCK_ABORT_PTR(validation_get_notification_uri, rpki_uri, + struct validation *state) + +MOCK_ABORT_VOID(fnstack_init, void) +MOCK_ABORT_VOID(fnstack_cleanup, void) +MOCK_ABORT_VOID(fnstack_push, char const *f) /* Tests */ @@ -73,59 +87,16 @@ START_TEST(tal_load_normal) } END_TEST -START_TEST(tal_order_http_first) -{ - struct tal *tal; - - ck_assert_int_eq(tal_load("tal/lacnic.tal", &tal), 0); - - http_priority = 60; - rsync_priority = 50; - tal_order_uris(tal); - - ck_assert_str_eq(tal->uris.array[0]->global, "https://potato"); - ck_assert_str_eq(tal->uris.array[1]->global, - "rsync://repository.lacnic.net/rpki/lacnic/rta-lacnic-rpki.cer"); - ck_assert_str_eq(tal->uris.array[2]->global, "rsync://potato"); - - tal_destroy(tal); -} -END_TEST - -START_TEST(tal_order_http_last) -{ - struct tal *tal; - - ck_assert_int_eq(tal_load("tal/lacnic.tal", &tal), 0); - - http_priority = 50; - rsync_priority = 60; - tal_order_uris(tal); - - ck_assert_str_eq(tal->uris.array[0]->global, - "rsync://repository.lacnic.net/rpki/lacnic/rta-lacnic-rpki.cer"); - ck_assert_str_eq(tal->uris.array[1]->global, "rsync://potato"); - ck_assert_str_eq(tal->uris.array[2]->global, "https://potato"); - - tal_destroy(tal); -} -END_TEST - Suite *tal_load_suite(void) { Suite *suite; - TCase *core, *order; + TCase *core; core = tcase_create("Core"); tcase_add_test(core, tal_load_normal); - order = tcase_create("Order"); - tcase_add_test(order, tal_order_http_first); - tcase_add_test(order, tal_order_http_last); - suite = suite_create("tal_load()"); suite_add_tcase(suite, core); - suite_add_tcase(suite, order); return suite; } diff --git a/test/types/uri_test.c b/test/types/uri_test.c index 27c978e4..87d538b3 100644 --- a/test/types/uri_test.c +++ b/test/types/uri_test.c @@ -6,6 +6,57 @@ #include "common.c" #include "mock.c" #include "types/uri.c" +#include "data_structure/path_builder.c" + +/* Mocks */ + +struct rpki_uri *notification; + +MOCK(state_retrieve, struct validation *, NULL, void) +MOCK(validation_get_notification_uri, struct rpki_uri *, notification, + struct validation *state) + +/* Tests */ + +START_TEST(test_constructor) +{ + struct rpki_uri *uri; + + ck_assert_int_eq(0, uri_create(&uri, UT_HTTPS, "https://a.b.c/d/e")); + ck_assert_str_eq("https://a.b.c/d/e", uri_get_global(uri)); + ck_assert_str_eq("https/a.b.c/d/e", uri_get_local(uri)); + uri_refput(uri); + + ck_assert_int_eq(0, uri_create(&uri, UT_HTTPS, "https://a.b.c")); + ck_assert_str_eq("https://a.b.c", uri_get_global(uri)); + ck_assert_str_eq("https/a.b.c", uri_get_local(uri)); + uri_refput(uri); + + ck_assert_int_eq(-EINVAL, uri_create(&uri, UT_HTTPS, "https://")); + + ck_assert_int_eq(0, uri_create(&uri, UT_HTTPS, "https://a.b.c/d/..")); + ck_assert_str_eq("https://a.b.c/d/..", uri_get_global(uri)); + ck_assert_str_eq("https/a.b.c", uri_get_local(uri)); + uri_refput(uri); + + ck_assert_int_eq(-EINVAL, uri_create(&uri, UT_HTTPS, "https://a.b.c/..")); + ck_assert_int_eq(-EINVAL, uri_create(&uri, UT_HTTPS, "https://a.b.c/d/../..")); + + ck_assert_int_eq(0, uri_create(&uri, UT_HTTPS, "https://a.b.c/.")); + ck_assert_str_eq("https://a.b.c/.", uri_get_global(uri)); + ck_assert_str_eq("https/a.b.c", uri_get_local(uri)); + uri_refput(uri); + + ck_assert_int_eq(ENOTHTTPS, uri_create(&uri, UT_HTTPS, "rsync://a.b.c/d")); + ck_assert_int_eq(ENOTHTTPS, uri_create(&uri, UT_HTTPS, "")); + ck_assert_int_eq(-EINVAL, uri_create(&uri, UT_HTTPS, "https://..")); + ck_assert_int_eq(-EINVAL, uri_create(&uri, UT_HTTPS, "https://a.b.c/../..")); + + ck_assert_int_eq(ENOTHTTPS, uri_create(&uri, UT_HTTPS, "rsync://a.b.c/d")); + ck_assert_int_eq(ENOTHTTPS, uri_create(&uri, UT_HTTPS, "http://a.b.c/d")); + ck_assert_int_eq(ENOTRSYNC, uri_create(&uri, UT_RSYNC, "https://a.b.c/d")); +} +END_TEST #define BUFFER_LEN 128 static uint8_t buffer[BUFFER_LEN]; @@ -59,13 +110,33 @@ START_TEST(check_validate_current_directory) } END_TEST +START_TEST(check_caged) +{ + struct rpki_uri *uri; + + ck_assert_int_eq(0, uri_create(¬ification, UT_HTTPS, "https://a.b.c/d/e.xml")); + ck_assert_int_eq(0, uri_create(&uri, UT_CAGED, "rsync://x.y.z/v/w.cer")); + ck_assert_str_eq("rrdp/https/a.b.c/d/e.xml/rsync/x.y.z/v/w.cer", uri_get_local(uri)); + uri_refput(uri); + uri_refput(notification); + + ck_assert_int_eq(0, uri_create(¬ification, UT_RSYNC, "rsync://a.b.c")); + ck_assert_int_eq(0, uri_create(&uri, UT_CAGED, "https://w")); + ck_assert_str_eq("rrdp/rsync/a.b.c/https/w", uri_get_local(uri)); + uri_refput(uri); + uri_refput(notification); +} +END_TEST + Suite *address_load_suite(void) { Suite *suite; TCase *core; core = tcase_create("Core"); + tcase_add_test(core, test_constructor); tcase_add_test(core, check_validate_current_directory); + tcase_add_test(core, check_caged); suite = suite_create("Encoding checking"); suite_add_tcase(suite, core);