From: pcarana Date: Wed, 27 Nov 2019 21:49:39 +0000 (-0600) Subject: Parse and process snapshot, remember RRDP URIs (session ID and serial). X-Git-Tag: v1.2.0~49 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3139916fc249990440e8c6b60c47dd1d216150ad;p=thirdparty%2FFORT-validator.git Parse and process snapshot, remember RRDP URIs (session ID and serial). +Create struct and method to store RRDP URIs data. +Create handler so that multiple threads can access RRDP URIs data. +Rename 'gdata' property to 'global_data' at update_notification struct. +Use prefered access method according to the order specified at the CAs. +Implement RRDP URIs comparison, considers: URI, session ID and serial so that the caller can determine what to do (process snapshot, deltas, etc.) +Document rrdp_objects.h structs. +Add content length to 'publish' structure. +Add functions to parse 'publish' elements. +Validate that a new RRDP object parsed matches session ID and serial of the parent. +Whenever a snapshot file is parsed (and validated), all of its 'publish' elements are parsed as well and created at the local repository. +Use 'fnstack' to log whenever an RRDP file is being processed. +Update 'uri.h' to explicitly create either rsync or https URIs. +Use rrdp_handler at the validation state of each thread (or each TAL, it's the same thing). +Fix wrong return value on error at __do_file_validation, it should return a 'no memory' error instead of 'invalid value'. +Fix macro ARRAYLIST_FOREACH, one argument wasn't being utilized. +Update unit tests, add reference to new header 'db_rrdp.h'. --- diff --git a/src/Makefile.am b/src/Makefile.am index 9bf88d94..bca8af9f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -79,6 +79,8 @@ fort_SOURCES += resource/ip4.h resource/ip4.c fort_SOURCES += resource/ip6.h resource/ip6.c fort_SOURCES += resource/asn.h resource/asn.c +fort_SOURCES += rrdp/db_rrdp.h rrdp/db_rrdp.c +fort_SOURCES += rrdp/rrdp_handler.h rrdp/rrdp_handler.c fort_SOURCES += rrdp/rrdp_objects.h rrdp/rrdp_objects.c fort_SOURCES += rrdp/rrdp_parser.h rrdp/rrdp_parser.c diff --git a/src/data_structure/array_list.h b/src/data_structure/array_list.h index 7ca927cc..b20cb9d8 100644 --- a/src/data_structure/array_list.h +++ b/src/data_structure/array_list.h @@ -74,9 +74,9 @@ DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type, ) #define ARRAYLIST_FOREACH(list, node, index) for ( \ - (i) = 0, (node) = (list)->array; \ - (i) < (list)->len; \ - (i)++, (node)++ \ + (index) = 0, (node) = (list)->array; \ + (index) < (list)->len; \ + (index)++, (node)++ \ ) #endif /* SRC_DATA_STRUCTURE_ARRAY_LIST_H_ */ diff --git a/src/object/certificate.c b/src/object/certificate.c index 45707133..ca5d98dc 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -19,6 +19,7 @@ #include "object/name.h" #include "object/manifest.h" #include "object/signed_object.h" +#include "rrdp/rrdp_objects.h" #include "rrdp/rrdp_parser.h" #include "rsync/rsync.h" @@ -53,6 +54,9 @@ struct bgpsec_ski { unsigned char **ski_data; }; +/* Callback method to fetch repository objects */ +typedef int (access_method_exec)(struct sia_ca_uris *); + static void sia_ca_uris_init(struct sia_ca_uris *sia_uris) { @@ -237,7 +241,7 @@ check_dup_public_key(bool *duplicated, char const *file, void *arg) rcvd_cert = NULL; tmp_size = 0; - error = uri_create_str(&uri, file, strlen(file)); + error = uri_create_rsync_str(&uri, file, strlen(file)); if (error) return error; @@ -1873,6 +1877,110 @@ certificate_validate_aia(struct rpki_uri *caIssuers, X509 *cert) return force_aia_validation(caIssuers, cert); } +static int +exec_rrdp_method(struct sia_ca_uris *sia_uris) +{ + struct update_notification *upd_notification; + struct snapshot *snapshot; + enum rrdp_uri_cmp_result res; + int error; + + error = rrdp_parse_notification(sia_uris->rpkiNotify.uri, + &upd_notification); + if (error) + return error; + + res = rhandler_uri_cmp(uri_get_global(sia_uris->rpkiNotify.uri), + upd_notification->global_data.session_id, + upd_notification->global_data.serial); + switch(res) { + case RRDP_URI_EQUAL: + update_notification_destroy(upd_notification); + fnstack_pop(); + return 0; + case RRDP_URI_DIFF_SERIAL: + /* FIXME (now) Fetch and process the deltas */ + /* Store the new value */ + error = rhandler_uri_update( + uri_get_global(sia_uris->rpkiNotify.uri), + upd_notification->global_data.session_id, + upd_notification->global_data.serial); + break; + case RRDP_URI_DIFF_SESSION: + /* FIXME (now) delete the old session files */ + case RRDP_URI_NOTFOUND: + /* Process the snapshot */ + error = rrdp_parse_snapshot(upd_notification, &snapshot); + if (error) { + break; + } + snapshot_destroy(snapshot); + fnstack_pop(); /* FIXME (now) Snapshot pop, shouldn't be here */ + /* Store the new value */ + error = rhandler_uri_update( + uri_get_global(sia_uris->rpkiNotify.uri), + upd_notification->global_data.session_id, + upd_notification->global_data.serial); + + break; + default: + pr_crit("Unknown RRDP URI comparison result"); + } + + /* + * FIXME (now) Now do the validation, start by the root manifest + */ + update_notification_destroy(upd_notification); + fnstack_pop(); + if (error) { + return error; + } + return 0; +} + +static int +exec_rsync_method(struct sia_ca_uris *sia_uris) +{ + return download_files(sia_uris->caRepository.uri, false, false); +} + +/* + * 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). + */ +static int +use_access_method(struct sia_ca_uris *sia_uris, + access_method_exec rsync_cb, access_method_exec rrdp_cb) +{ + access_method_exec *cb_primary; + access_method_exec *cb_secondary; + int error; + + /* + * RSYNC will always be present (at least for now, see + * rfc6487#section-4.8.8.1) + */ + if (sia_uris->rpkiNotify.uri == NULL) + return rsync_cb(sia_uris); + + /* Get the preferred */ + if (sia_uris->caRepository.position > sia_uris->rpkiNotify.position) { + cb_primary = rrdp_cb; + cb_secondary = rsync_cb; + } else { + cb_primary = rsync_cb; + cb_secondary = rrdp_cb; + } + + /* Try with the preferred; in case of error, try with the next one */ + error = cb_primary(sia_uris); + if (!error) + return 0; + + return cb_secondary(sia_uris); +} + /** Boilerplate code for CA certificate validation and recursive traversal. */ int certificate_traverse(struct rpp *rpp_parent, struct rpki_uri *cert_uri) @@ -1983,18 +2091,31 @@ certificate_traverse(struct rpp *rpp_parent, struct rpki_uri *cert_uri) goto revert_refs; } - /* Currently there's support for 2 access methods */ - /* FIXME (now) Download files according to the preferred method */ - if (sia_uris.rpkiNotify.uri != NULL && - sia_uris.caRepository.position > sia_uris.rpkiNotify.position) { - pr_info("Preferred Access Method: RRDP, but doing rsync anyways"); - rrdp_parse_notification(sia_uris.rpkiNotify.uri); - } else - pr_info("Preferred Access Method: RSYNC [found RRDP? %s]", - (sia_uris.rpkiNotify.uri == NULL ? "NO" : "YES")); - - /* Currently downloading RSYNC (will always be present) */ - error = download_files(sia_uris.caRepository.uri, false, false); + /* + * FIXME (now) Beware: what if RRDP is already being utilized? + * + * 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) and 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. + * - The manifest should exist at the snapshot file (aka, directory + * structure), so it should be processed from there on. + * - Verify that the manifest file exists locally (must be the same + * session_id and serial, load those at start, the serial can + * be written to a local file, the session_id can be loaded from the + * directory structure /rrdp/).: + * + Exists: Read and process normally (no need to do rsync) + * + Doesn't exists: check for preferred access method, keep the flow + * from there on. + */ + error = use_access_method(&sia_uris, exec_rsync_method, + exec_rrdp_method); if (error) return error; diff --git a/src/object/tal.c b/src/object/tal.c index 69eec5be..3512a411 100644 --- a/src/object/tal.c +++ b/src/object/tal.c @@ -469,6 +469,7 @@ handle_tal_uri(struct tal *tal, struct rpki_uri *uri, void *arg) */ struct validation_handler validation_handler; + struct rrdp_handler rrdp_handler; struct validation *state; struct cert_stack *certstack; struct deferred_cert deferred; @@ -479,7 +480,11 @@ handle_tal_uri(struct tal *tal, struct rpki_uri *uri, void *arg) validation_handler.handle_router_key = handle_router_key; validation_handler.arg = arg; - error = validation_prepare(&state, tal, &validation_handler); + rrdp_handler.uri_cmp = rrdp_uri_cmp; + rrdp_handler.uri_update = rrdp_uri_update; + + error = validation_prepare(&state, tal, &validation_handler, + &rrdp_handler); if (error) return ENSURE_NEGATIVE(error); @@ -519,6 +524,8 @@ handle_tal_uri(struct tal *tal, struct rpki_uri *uri, void *arg) validation_pubkey_state(state)); } + /* FIXME (now) Consider RRDP found scenario */ + /* * From now on, the tree should be considered valid, even if subsequent * certificates fail. @@ -621,7 +628,7 @@ __do_file_validation(char const *tal_file, void *arg) thread = malloc(sizeof(struct thread)); if (thread == NULL) { close_thread(pid, tal_file); - error = -EINVAL; + error = pr_enomem(); goto free_param; } diff --git a/src/rrdp/db_rrdp.c b/src/rrdp/db_rrdp.c new file mode 100644 index 00000000..862d1065 --- /dev/null +++ b/src/rrdp/db_rrdp.c @@ -0,0 +1,151 @@ +#include "db_rrdp.h" + +#include +#include +#include "data_structure/uthash_nonfatal.h" +#include "rrdp/rrdp_objects.h" +#include "log.h" + +struct db_rrdp_uri { + /* Key */ + char *uri; + struct global_data data; + UT_hash_handle hh; +}; + +struct db_rrdp { + struct db_rrdp_uri *uris; +}; + +static int +db_rrdp_uri_create(char const *uri, char const *session_id, + unsigned long serial, struct db_rrdp_uri **result) +{ + struct db_rrdp_uri *tmp; + int error; + + tmp = malloc(sizeof(struct db_rrdp_uri)); + if (tmp == NULL) + return pr_enomem(); + /* Needed by uthash */ + memset(tmp, 0, sizeof(struct db_rrdp_uri)); + + tmp->uri = strdup(uri); + if (tmp->uri == NULL) { + error = pr_enomem(); + goto release_tmp; + } + + tmp->data.session_id = strdup(session_id); + if (tmp->data.session_id == NULL) { + error = pr_enomem(); + goto release_uri; + } + + tmp->data.serial = serial; + + *result = tmp; + return 0; +release_uri: + free(tmp->uri); +release_tmp: + free(tmp); + return error; +} + +static void +db_rrdp_uri_destroy(struct db_rrdp_uri *uri) +{ + free(uri->data.session_id); + free(uri->uri); + free(uri); +} + +static int +add_rrdp_uri(struct db_rrdp *db, struct db_rrdp_uri *new_uri) +{ + struct db_rrdp_uri *old_uri; + + errno = 0; + HASH_FIND_STR(db->uris, new_uri->uri, old_uri); + if (errno) + return pr_errno(errno, + "RRDP URI couldn't be added to hash table"); + + if (old_uri != NULL) { + HASH_DELETE(hh, db->uris, old_uri); + db_rrdp_uri_destroy(old_uri); + } + HASH_ADD_KEYPTR(hh, db->uris, new_uri->uri, strlen(new_uri->uri), + new_uri); + + return 0; +} + +enum rrdp_uri_cmp_result +db_rrdp_cmp_uri(struct db_rrdp *db, char const *uri, char const *session_id, + unsigned long serial) +{ + struct db_rrdp_uri *found; + + HASH_FIND_STR(db->uris, uri, found); + if (found == NULL) + return RRDP_URI_NOTFOUND; + + if (strcmp(session_id, found->data.session_id) != 0) + return RRDP_URI_DIFF_SESSION; + + if (serial != found->data.serial) + return RRDP_URI_DIFF_SERIAL; + + return RRDP_URI_EQUAL; +} + +int +db_rrdp_add_uri(struct db_rrdp *db, char const *uri, char const *session_id, + unsigned long serial) +{ + struct db_rrdp_uri *db_uri; + int error; + + db_uri = NULL; + error = db_rrdp_uri_create(uri, session_id, serial, &db_uri); + if (error) + return error; + + error = add_rrdp_uri(db, db_uri); + if (error) { + db_rrdp_uri_destroy(db_uri); + return error; + } + + return 0; +} + +int +db_rrdp_create(struct db_rrdp **result) +{ + struct db_rrdp *tmp; + + tmp = malloc(sizeof(struct db_rrdp)); + if (tmp == NULL) + return pr_enomem(); + + tmp->uris = NULL; + + *result = tmp; + return 0; +} + +void +db_rddp_destroy(struct db_rrdp *db) +{ + struct db_rrdp_uri *uri_node, *uri_tmp; + + HASH_ITER(hh, db->uris, uri_node, uri_tmp) { + HASH_DEL(db->uris, uri_node); + db_rrdp_uri_destroy(uri_node); + } + + free(db); +} diff --git a/src/rrdp/db_rrdp.h b/src/rrdp/db_rrdp.h new file mode 100644 index 00000000..48ca94d7 --- /dev/null +++ b/src/rrdp/db_rrdp.h @@ -0,0 +1,20 @@ +#ifndef SRC_RRDP_DB_RRDP_H_ +#define SRC_RRDP_DB_RRDP_H_ + +#include "rrdp/rrdp_objects.h" + +/* + * Struct and methods to persist RRDP information, such as URIs, session ID per + * URI and current serial number. + */ +struct db_rrdp; + +int db_rrdp_create(struct db_rrdp **); +void db_rddp_destroy(struct db_rrdp *); + +enum rrdp_uri_cmp_result db_rrdp_cmp_uri(struct db_rrdp *, char const *, + char const *, unsigned long); +int db_rrdp_add_uri(struct db_rrdp *, char const *, char const *, + unsigned long); + +#endif /* SRC_RRDP_DB_RRDP_H_ */ diff --git a/src/rrdp/rrdp_handler.c b/src/rrdp/rrdp_handler.c new file mode 100644 index 00000000..a09765ba --- /dev/null +++ b/src/rrdp/rrdp_handler.c @@ -0,0 +1,52 @@ +#include "rrdp_handler.h" + +#include "thread_var.h" + +static int +get_current_threads_handler(struct rrdp_handler const **result) +{ + struct validation *state; + struct rrdp_handler const *handler; + + state = state_retrieve(); + if (state == NULL) + return -EINVAL; + + handler = validation_get_rrdp_handler(state); + if (handler == NULL) + pr_crit("This thread lacks an RRDP handler."); + + *result = handler; + return 0; +} + +enum rrdp_uri_cmp_result +rhandler_uri_cmp(char const *uri, char const *session_id, unsigned long serial) +{ + struct rrdp_handler const *handler; + int error; + + error = get_current_threads_handler(&handler); + if (error) + return error; + + return (handler->uri_cmp != NULL) + ? handler->uri_cmp(uri, session_id, serial) + : RRDP_URI_NOTFOUND; +} + +int +rhandler_uri_update(char const *uri, char const *session_id, + unsigned long serial) +{ + struct rrdp_handler const *handler; + int error; + + error = get_current_threads_handler(&handler); + if (error) + return error; + + return (handler->uri_update != NULL) + ? handler->uri_update(uri, session_id, serial) + : 0; +} diff --git a/src/rrdp/rrdp_handler.h b/src/rrdp/rrdp_handler.h new file mode 100644 index 00000000..bcf5da74 --- /dev/null +++ b/src/rrdp/rrdp_handler.h @@ -0,0 +1,30 @@ +#ifndef SRC_RRDP_RRDP_HANDLER_H_ +#define SRC_RRDP_RRDP_HANDLER_H_ + +#include "rrdp/rrdp_objects.h" + +/* + * Almost the same idea as 'validation_handler.h', only that the main focus is + * a multithreaded environment. + * + * The RRDP URIs are expected to live at the main thread, the other threads can + * access such URIs. The handler must assure that the data is safe + * (handle r/w locks), that's the reason why there isn't any reference to a + * 'db_rrdp' struct. + */ + +struct rrdp_handler { + /* + * Search the RRDP URI, returns the corresponding enum to indicate + * the comparison result. + */ + enum rrdp_uri_cmp_result (*uri_cmp)(char const *, char const *, + unsigned long); + /* Add or update an RRDP URI */ + int (*uri_update)(char const *, char const *, unsigned long); +}; + +enum rrdp_uri_cmp_result rhandler_uri_cmp(char const *, char const *, unsigned long); +int rhandler_uri_update(char const *, char const *, unsigned long); + +#endif /* SRC_RRDP_RRDP_HANDLER_H_ */ diff --git a/src/rrdp/rrdp_objects.c b/src/rrdp/rrdp_objects.c index 05ae9805..e5146dc5 100644 --- a/src/rrdp/rrdp_objects.c +++ b/src/rrdp/rrdp_objects.c @@ -23,6 +23,7 @@ int doc_data_init(struct doc_data *data) { data->hash = NULL; + data->hash_len = 0; data->uri = NULL; return 0; } @@ -55,7 +56,6 @@ xml_source_destroy(struct xml_source *src){ } } - int xml_source_set(struct xml_source *src, xmlDoc *orig) { @@ -78,7 +78,7 @@ update_notification_create(struct update_notification **file) if (tmp == NULL) return pr_enomem(); - global_data_init(&tmp->gdata); + global_data_init(&tmp->global_data); doc_data_init(&tmp->snapshot); SLIST_INIT(&tmp->deltas_list); @@ -101,7 +101,7 @@ update_notification_destroy(struct update_notification *file) free(head); } doc_data_cleanup(&file->snapshot); - global_data_cleanup(&file->gdata); + global_data_cleanup(&file->global_data); free(file); } @@ -125,3 +125,72 @@ update_notification_deltas_add(struct deltas_head *deltas, unsigned long serial, return 0; } + +int +snapshot_create(struct snapshot **file) +{ + struct snapshot *tmp; + + tmp = malloc(sizeof(struct snapshot)); + if (tmp == NULL) + return pr_enomem(); + + global_data_init(&tmp->global_data); + SLIST_INIT(&tmp->publish_list); + tmp->source = NULL; + + *file = tmp; + return 0; +} + +void +snapshot_destroy(struct snapshot *file) +{ + struct publish_list *list; + struct publish *head; + + list = &file->publish_list; + while (!SLIST_EMPTY(list)) { + head = list->slh_first; + SLIST_REMOVE_HEAD(list, next); + doc_data_cleanup(&head->doc_data); + free(head->content); + free(head); + } + global_data_cleanup(&file->global_data); + xml_source_destroy(file->source); + + free(file); +} + +int +publish_create(struct publish **file) +{ + struct publish *tmp; + + tmp = malloc(sizeof(struct publish)); + if (tmp == NULL) + return pr_enomem(); + + doc_data_init(&tmp->doc_data); + tmp->content = NULL; + tmp->content_len = 0; + + *file = tmp; + return 0; +} + +void +publish_destroy(struct publish *file) +{ + doc_data_cleanup(&file->doc_data); + free(file->content); + free(file); +} + +int +publish_list_add(struct publish_list *list, struct publish *elem) +{ + SLIST_INSERT_HEAD(list, elem, next); + return 0; +} diff --git a/src/rrdp/rrdp_objects.h b/src/rrdp/rrdp_objects.h index 2090a7ac..a9fb90a7 100644 --- a/src/rrdp/rrdp_objects.h +++ b/src/rrdp/rrdp_objects.h @@ -5,35 +5,57 @@ #include #include -/* Common structures */ +/* Possible results for an RRDP URI comparison */ +enum rrdp_uri_cmp_result { + /* The URI doesn't exists */ + RRDP_URI_NOTFOUND, + + /* 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, +}; + +/* Structure to remember the XML source file (useful for hash validations) */ struct xml_source; +/* Global RRDP files data */ struct global_data { char *session_id; unsigned long serial; }; +/* Specific RRDP files data, in some cases the hash can be omitted */ struct doc_data { char *uri; unsigned char *hash; size_t hash_len; }; +/* Represents a element to be utilized inside a list */ struct publish { struct doc_data doc_data; unsigned char *content; + size_t content_len; SLIST_ENTRY(publish) next; }; +/* Represents a element to be utilized inside a list */ struct withdraw { struct doc_data doc_data; SLIST_ENTRY(withdraw) next; }; -/* Delta file structs */ +/* List of elements (either in a delta or a snapshot file) */ SLIST_HEAD(publish_list, publish); +/* List of elements */ SLIST_HEAD(withdrawn_list, withdraw); +/* Delta file content */ struct delta { struct global_data global_data; struct publish_list publish_list; @@ -41,24 +63,25 @@ struct delta { struct xml_source *source; }; -/* Snapshot file structs */ +/* Snapshot file content */ struct snapshot { struct global_data global_data; struct publish_list publish_list; struct xml_source *source; }; -/* Update notification file structs */ +/* Delta element located at an update notification file */ struct delta_head { unsigned long serial; struct doc_data doc_data; SLIST_ENTRY(delta_head) next; }; +/* List of deltas inside an update notification file */ SLIST_HEAD(deltas_head, delta_head); struct update_notification { - struct global_data gdata; + struct global_data global_data; struct doc_data snapshot; struct deltas_head deltas_list; }; @@ -79,4 +102,13 @@ void update_notification_destroy(struct update_notification *); int update_notification_deltas_add(struct deltas_head *, unsigned long, char **, unsigned char **, size_t); +int snapshot_create(struct snapshot **); +void snapshot_destroy(struct snapshot *); + +int publish_create(struct publish **); +void publish_destroy(struct publish *); + +int publish_list_add(struct publish_list *, struct publish *); + + #endif /* SRC_RRDP_RRDP_OBJECTS_H_ */ diff --git a/src/rrdp/rrdp_parser.c b/src/rrdp/rrdp_parser.c index ec01475c..9610cdc9 100644 --- a/src/rrdp/rrdp_parser.c +++ b/src/rrdp/rrdp_parser.c @@ -2,17 +2,27 @@ #include #include +#include +#include +#include #include #include +#include +#include "crypto/base64.h" #include "http/http.h" #include "xml/relax_ng.h" +#include "common.h" +#include "file.h" #include "log.h" +#include "thread_var.h" /* XML Elements */ #define RRDP_ELEM_NOTIFICATION "notification" #define RRDP_ELEM_SNAPSHOT "snapshot" #define RRDP_ELEM_DELTA "delta" +#define RRDP_ELEM_PUBLISH "publish" +#define RRDP_ELEM_WITHDRAW "withdraw" /* XML Attributes */ #define RRDP_ATTR_VERSION "version" @@ -35,16 +45,181 @@ get_root_element(xmlDoc *doc, xmlNode **result) return 0; } +static size_t +write_local(unsigned char *content, size_t size, size_t nmemb, void *arg) +{ + FILE *fd = arg; + size_t read = size * nmemb; + size_t written; + + written = fwrite(content, size, nmemb, fd); + if (written != nmemb) + return -EINVAL; + + return read; +} + +/* Trim @from, setting the result at @result pointer */ +static int +trim(char *from, char **result, size_t *result_size) +{ + char *start, *end; + size_t tmp_size; + + start = from; + tmp_size = strlen(from); + while (isspace(*start)) { + start++; + tmp_size--; + } + if (*start == '\0') + return pr_err("Invalid base64 encoded string (seems to be empty or full of spaces)."); + + end = start; + while (*end != '\0') { + if (!isspace(*end)) { + end++; + continue; + } + /* No middle spaces, newlines, etc. allowed */ + *end = '\0'; + tmp_size = end - start; + break; + } + + *result = start; + *result_size = tmp_size; + return 0; +} + +/* + * Get the base64 chars from @content and allocate to @out with lines no greater + * than 65 chars (including line feed). + * + * Why? LibreSSL doesn't like lines greater than 80 chars, so use a common + * length per line. + */ +static int +base64_sanitize(char *content, char **out) +{ +#define BUF_SIZE 65 + char *result; + char *tmp; + size_t original_size, new_size; + size_t offset, buf_len; + int error; + + original_size = 0; + error = trim(content, &tmp, &original_size); + if (error) + return error; + + if (original_size <= BUF_SIZE) { + result = malloc(original_size + 1); + if (result == NULL) + return pr_enomem(); + result[original_size] = '\0'; + *out = result; + return 0; + } + + new_size = original_size + (original_size / BUF_SIZE); + result = malloc(new_size + 1); + if (result == NULL) + return pr_enomem(); + + offset = 0; + while (original_size > 0){ + buf_len = original_size > BUF_SIZE ? BUF_SIZE : original_size; + memcpy(&result[offset], tmp, buf_len); + tmp += buf_len; + offset += buf_len; + original_size -= buf_len; + + if (original_size <= 0) + break; + result[offset] = '\n'; + offset++; + } + + /* Reallocate to exact size and add nul char */ + if (offset != new_size + 1) { + tmp = realloc(result, offset + 1); + if (tmp == NULL) { + free(result); + return pr_enomem(); + } + result = tmp; + } + + result[offset] = '\0'; + *out = result; + return 0; +#undef BUF_SIZE +} + +static int +base64_read(char *content, unsigned char **out, size_t *out_len) +{ + BIO *encoded; /* base64 encoded. */ + unsigned char *result; + char *sanitized; + size_t alloc_size; + size_t result_len; + int error; + + sanitized = NULL; + error = base64_sanitize(content, &sanitized); + if (error) + return error; + + encoded = BIO_new_mem_buf(sanitized, -1); + if (encoded == NULL) { + error = crypto_err("BIO_new_mem_buf() returned NULL"); + goto release_sanitized; + } + + alloc_size = EVP_DECODE_LENGTH(strlen(content)); + result = malloc(alloc_size); + if (result == NULL) { + error = pr_enomem(); + goto release_bio; + } + + error = base64_decode(encoded, result, true, alloc_size, &result_len); + if (error) + goto release_result; + + free(sanitized); + BIO_free(encoded); + + *out = result; + (*out_len) = result_len; + return 0; +release_result: + free(result); +release_bio: + BIO_free(encoded); +release_sanitized: + free(sanitized); + return error; +} + static int parse_string(xmlNode *root, char const *attr, char **result) { xmlChar *xml_value; char *tmp; - xml_value = xmlGetProp(root, BAD_CAST attr); + if (attr == NULL) + xml_value = xmlNodeGetContent(root); + else + xml_value = xmlGetProp(root, BAD_CAST attr); + if (xml_value == NULL) - return pr_err("RRDP file: Couldn't find xml attribute %s", - attr); + return pr_err("RRDP file: Couldn't find %s from '%s'", + (attr == NULL ? "string content" : "xml attribute"), + root->name); tmp = malloc(xmlStrlen(xml_value) + 1); if (tmp == NULL) { @@ -132,7 +307,8 @@ parse_hex_string(xmlNode *root, char const *attr, unsigned char **result, /* @gdata elements are allocated */ static int -parse_global_data(xmlNode *root, struct global_data *gdata) +parse_global_data(xmlNode *root, struct global_data *gdata, + struct global_data *parent_data) { int error; @@ -146,6 +322,24 @@ parse_global_data(xmlNode *root, struct global_data *gdata) return error; } + if (parent_data == NULL) + return 0; + + /* + * FIXME (now) Prepare the callers to receive positive error values, + * which means the file was successfully parsed but is has a logic error + * (in this case, session ID or serial don't match parent's). + */ + if (strcmp(parent_data->session_id, gdata->session_id) != 0) { + pr_info("Object session id doesn't match parent's session id"); + return EINVAL; + } + + if (parent_data->serial != gdata->serial) { + pr_info("Object serial doesn't match parent's serial"); + return EINVAL; + } + return 0; } @@ -218,8 +412,104 @@ parse_notification_data(xmlNode *root, struct update_notification *file) } static int -parse_notification(const char *path, - struct update_notification **file) +parse_publish(xmlNode *root, bool parse_hash, struct publish **publish) +{ + struct publish *tmp; + char *base64_str; + int error; + + error = publish_create(&tmp); + if (error) + return error; + + error = parse_doc_data(root, parse_hash, &tmp->doc_data); + if (error) + goto release_tmp; + + error = parse_string(root, NULL, &base64_str); + if (error) + goto release_tmp; + + error = base64_read(base64_str, &tmp->content, &tmp->content_len); + if (error) + goto release_base64; + + free(base64_str); + *publish = tmp; + return 0; +release_base64: + free(base64_str); +release_tmp: + publish_destroy(tmp); + return error; +} + +static int +write_from_uri(char const *location, unsigned char *content, size_t content_len) +{ + struct rpki_uri *uri; + struct stat stat; + FILE *out; + size_t written; + int error; + + error = uri_create_mixed_str(&uri, location, strlen(location)); + if (error) + return error; + + error = create_dir_recursive(uri_get_local(uri)); + if (error) { + uri_refput(uri); + return error; + } + + error = file_write(uri_get_local(uri), &out, &stat); + if (error) { + uri_refput(uri); + return error; + } + + written = fwrite(content, sizeof(unsigned char), content_len, out); + if (written != content_len) { + uri_refput(uri); + file_close(out); + return pr_err("Coudln't write bytes to file %s", + uri_get_local(uri)); + } + + uri_refput(uri); + file_close(out); + return 0; +} + +static int +parse_snapshot_publish_list(xmlNode *root, struct snapshot *file) +{ + struct publish *tmp; + xmlNode *cur_node; + int error; + + /* Only publish elements are expected, already validated by syntax */ + for (cur_node = root->children; cur_node; cur_node = cur_node->next) { + if (xmlStrEqual(cur_node->name, BAD_CAST RRDP_ELEM_PUBLISH)) { + tmp = NULL; + error = parse_publish(cur_node, false, &tmp); + if (error) + return error; + + error = write_from_uri(tmp->doc_data.uri, tmp->content, + tmp->content_len); + publish_destroy(tmp); + if (error) + return error; + } + } + + return 0; +} + +static int +parse_notification(const char *path, struct update_notification **file) { xmlDoc *doc; xmlNode *root; @@ -238,10 +528,10 @@ parse_notification(const char *path, error = get_root_element(doc, &root); if (error) - return error; + goto release_update; /* FIXME (now) validate version, namespace, etc. */ - error = parse_global_data(root, &tmp->gdata); + error = parse_global_data(root, &tmp->global_data, NULL); if (error) goto release_update; @@ -260,41 +550,113 @@ release_doc: return error; } -static size_t -write_local(unsigned char *content, size_t size, size_t nmemb, void *arg) +static int +parse_snapshot(const char *path, struct update_notification *parent, + struct snapshot **file) { - FILE *fd = arg; - size_t read = size * nmemb; - size_t written; + xmlDoc *doc; + xmlNode *root; + struct snapshot *tmp; + struct xml_source *source; + int error; - written = fwrite(content, size, nmemb, fd); - if (written != nmemb) - return -EINVAL; + root = NULL; - return read; + error = relax_ng_validate(path, &doc); + if (error) + return error; + + error = snapshot_create(&tmp); + if (error) + goto release_doc; + + error = get_root_element(doc, &root); + if (error) + goto release_snapshot; + + /* FIXME (now) validate hash, version, namespace, etc. */ + error = xml_source_create(&source); + if (error) + goto release_snapshot; + + tmp->source = source; + error = xml_source_set(source, doc); + if (error) + goto release_snapshot; + + error = parse_global_data(root, &tmp->global_data, + &parent->global_data); + if (error) + goto release_snapshot; + + error = parse_snapshot_publish_list(root, tmp); + if (error) + goto release_snapshot; + + *file = tmp; + /* Error 0 is ok */ + goto release_doc; +release_snapshot: + snapshot_destroy(tmp); +release_doc: + xmlFreeDoc(doc); + return error; } -/* FIXME (now) Receive an **update_notification? */ int -rrdp_parse_notification(struct rpki_uri *uri) +rrdp_parse_notification(struct rpki_uri *uri, + struct update_notification **result) { - struct update_notification *tmp; int error; if (uri == NULL || uri_is_rsync(uri)) pr_crit("Wrong call, trying to parse a non HTTPS URI"); + /* + * FIXME (now) Add "If-Modified-Since" header (see rfc8182#section-4.2) + */ error = http_download_file(uri, write_local); if (error) return error; - error = parse_notification(uri_get_local(uri), &tmp); + fnstack_push_uri(uri); + error = parse_notification(uri_get_local(uri), result); + if (error) { + fnstack_pop(); + return error; + } + + return 0; +} + +int +rrdp_parse_snapshot(struct update_notification *parent, + struct snapshot **result) +{ + struct rpki_uri *uri; + struct snapshot *tmp; + int error; + + error = uri_create_https_str(&uri, parent->snapshot.uri, + strlen(parent->snapshot.uri)); if (error) return error; - /* FIXME (now) This is just a test, must be removed */ - update_notification_destroy(tmp); + error = http_download_file(uri, write_local); + if (error) + goto release_uri; + + fnstack_push_uri(uri); + error = parse_snapshot(uri_get_local(uri), parent, &tmp); + if (error) + goto release_uri; - /* result = tmp; */ + uri_refput(uri); + *result = tmp; + fnstack_pop(); return 0; +release_uri: + fnstack_pop(); + uri_refput(uri); + return error; } diff --git a/src/rrdp/rrdp_parser.h b/src/rrdp/rrdp_parser.h index edfd591b..3cd2f3a2 100644 --- a/src/rrdp/rrdp_parser.h +++ b/src/rrdp/rrdp_parser.h @@ -4,6 +4,7 @@ #include "rrdp/rrdp_objects.h" #include "uri.h" -int rrdp_parse_notification(struct rpki_uri *); +int rrdp_parse_notification(struct rpki_uri *, struct update_notification **); +int rrdp_parse_snapshot(struct update_notification *, struct snapshot **); #endif /* SRC_RRDP_RRDP_PARSER_H_ */ diff --git a/src/rsync/rsync.c b/src/rsync/rsync.c index 62a3ac16..652a7780 100644 --- a/src/rsync/rsync.c +++ b/src/rsync/rsync.c @@ -141,7 +141,7 @@ handle_root_strategy(struct rpki_uri *src, struct rpki_uri **dst) if (global[i] == '/') { slashes++; if (slashes == 4) - return uri_create_str(dst, global, i); + return uri_create_rsync_str(dst, global, i); } } diff --git a/src/rtr/db/vrps.c b/src/rtr/db/vrps.c index 46233733..877cb60d 100644 --- a/src/rtr/db/vrps.c +++ b/src/rtr/db/vrps.c @@ -11,6 +11,7 @@ #include "data_structure/array_list.h" #include "object/router_key.h" #include "object/tal.h" +#include "rrdp/db_rrdp.h" #include "rtr/db/db_table.h" #include "slurm/slurm_loader.h" @@ -60,6 +61,10 @@ struct state { serial_t next_serial; uint16_t v0_session_id; uint16_t v1_session_id; + + /* RRDP URIs known from certificates */ + struct db_rrdp *rrdp_uris; + } state; /** Read/write lock, which protects @state and its inhabitants. */ @@ -98,21 +103,30 @@ vrps_init(void) : (0xFFFFu); state.slurm = NULL; + error = db_rrdp_create(&state.rrdp_uris); + if (error) + goto release_deltas; error = pthread_rwlock_init(&state_lock, NULL); if (error) { - deltas_db_cleanup(&state.deltas, deltagroup_cleanup); - return pr_errno(error, "state pthread_rwlock_init() errored"); + error = pr_errno(error, "state pthread_rwlock_init() errored"); + goto release_db_rrdp; } error = pthread_rwlock_init(&table_lock, NULL); if (error) { - pthread_rwlock_destroy(&state_lock); - deltas_db_cleanup(&state.deltas, deltagroup_cleanup); - return pr_errno(error, "table pthread_rwlock_init() errored"); + error = pr_errno(error, "table pthread_rwlock_init() errored"); + goto release_state_lock; } return 0; +release_state_lock: + pthread_rwlock_destroy(&state_lock); +release_db_rrdp: + db_rddp_destroy(state.rrdp_uris); +release_deltas: + deltas_db_cleanup(&state.deltas, deltagroup_cleanup); + return error; } void @@ -123,6 +137,7 @@ vrps_destroy(void) if (state.slurm != NULL) db_slurm_destroy(state.slurm); deltas_db_cleanup(&state.deltas, deltagroup_cleanup); + db_rddp_destroy(state.rrdp_uris); /* Nothing to do with error codes from now on */ pthread_rwlock_destroy(&state_lock); pthread_rwlock_destroy(&table_lock); @@ -135,6 +150,13 @@ vrps_destroy(void) rwlock_unlock(lock); \ return error; +#define RLOCK_HANDLER(lock, cb) \ + int error; \ + rwlock_read_lock(lock); \ + error = cb; \ + rwlock_unlock(lock); \ + return error; + int handle_roa_v4(uint32_t as, struct ipv4_prefix const *prefix, uint8_t max_length, void *arg) @@ -159,6 +181,20 @@ handle_router_key(unsigned char const *ski, uint32_t as, rtrhandler_handle_router_key(arg, ski, as, spk)) } +enum rrdp_uri_cmp_result +rrdp_uri_cmp(char const *uri, char const *session_id, unsigned long serial) +{ + RLOCK_HANDLER(&state_lock, + db_rrdp_cmp_uri(state.rrdp_uris, uri, session_id, serial)) +} + +int +rrdp_uri_update(char const *uri, char const *session_id, unsigned long serial) +{ + WLOCK_HANDLER(&state_lock, + db_rrdp_add_uri(state.rrdp_uris, uri, session_id, serial)) +} + static int __perform_standalone_validation(struct db_table **result) { @@ -274,6 +310,7 @@ __vrps_update(bool *changed) old_base = NULL; new_base = NULL; + /* FIXME (now) identify if RRDP is being utilized? */ error = __perform_standalone_validation(&new_base); if (error) return error; diff --git a/src/rtr/db/vrps.h b/src/rtr/db/vrps.h index 78770849..0ed662d6 100644 --- a/src/rtr/db/vrps.h +++ b/src/rtr/db/vrps.h @@ -41,6 +41,10 @@ int handle_roa_v6(uint32_t, struct ipv6_prefix const *, uint8_t, void *); int handle_router_key(unsigned char const *, uint32_t, unsigned char const *, void *); +enum rrdp_uri_cmp_result rrdp_uri_cmp(char const *, char const *, + unsigned long); +int rrdp_uri_update(char const *, char const *, unsigned long); + uint16_t get_current_session_id(uint8_t); #endif /* SRC_VRPS_H_ */ diff --git a/src/state.c b/src/state.c index 343f8a24..40ee2d62 100644 --- a/src/state.c +++ b/src/state.c @@ -38,6 +38,12 @@ struct validation { char addr_buffer2[INET6_ADDRSTRLEN]; struct validation_handler validation_handler; + + /* + * Handler of RRDP functions, this handler can access data from the + * main thread, so that multiple threads can use it. + */ + struct rrdp_handler rrdp_handler; }; /* @@ -81,7 +87,8 @@ cb(int ok, X509_STORE_CTX *ctx) */ int validation_prepare(struct validation **out, struct tal *tal, - struct validation_handler *validation_handler) + struct validation_handler *validation_handler, + struct rrdp_handler *rrdp_handler) { struct validation *result; X509_VERIFY_PARAM *params; @@ -123,6 +130,7 @@ validation_prepare(struct validation **out, struct tal *tal, result->pubkey_state = PKS_UNTESTED; result->validation_handler = *validation_handler; + result->rrdp_handler = *rrdp_handler; result->x509_data.params = params; /* Ownership transfered */ *out = result; @@ -207,3 +215,9 @@ validation_get_validation_handler(struct validation *state) { return &state->validation_handler; } + +struct rrdp_handler const * +validation_get_rrdp_handler(struct validation *state) +{ + return &state->rrdp_handler; +} diff --git a/src/state.h b/src/state.h index 495ae873..8c21a0f0 100644 --- a/src/state.h +++ b/src/state.h @@ -5,12 +5,13 @@ #include "cert_stack.h" #include "validation_handler.h" #include "object/tal.h" +#include "rrdp/rrdp_handler.h" #include "rsync/rsync.h" struct validation; int validation_prepare(struct validation **, struct tal *, - struct validation_handler *); + struct validation_handler *, struct rrdp_handler *); void validation_destroy(struct validation *); struct tal *validation_tal(struct validation *); @@ -34,4 +35,7 @@ char *validation_get_ip_buffer2(struct validation *); struct validation_handler const * validation_get_validation_handler(struct validation *); +struct rrdp_handler const * +validation_get_rrdp_handler(struct validation *); + #endif /* SRC_STATE_H_ */ diff --git a/src/uri.c b/src/uri.c index 291fe270..605da499 100644 --- a/src/uri.c +++ b/src/uri.c @@ -334,11 +334,17 @@ uri_create(struct rpki_uri **result, uint8_t flags, void const *guri, } int -uri_create_str(struct rpki_uri **uri, char const *guri, size_t guri_len) +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); } +int +uri_create_https_str(struct rpki_uri **uri, char const *guri, size_t guri_len) +{ + return uri_create(uri, URI_VALID_HTTPS, guri, guri_len); +} + /* A URI that can be rsync or https */ int uri_create_mixed_str(struct rpki_uri **uri, char const *guri, size_t guri_len) diff --git a/src/uri.h b/src/uri.h index c1fce5dc..693c8a4f 100644 --- a/src/uri.h +++ b/src/uri.h @@ -11,7 +11,8 @@ struct rpki_uri; -int uri_create_str(struct rpki_uri **, char const *, size_t); +int uri_create_rsync_str(struct rpki_uri **, char const *, size_t); +int uri_create_https_str(struct rpki_uri **, char const *, size_t); int uri_create_mixed_str(struct rpki_uri **, char const *, size_t); int uri_create_mft(struct rpki_uri **, struct rpki_uri *, IA5String_t *); int uri_create_ad(struct rpki_uri **, ACCESS_DESCRIPTION *, int); diff --git a/test/rsync_test.c b/test/rsync_test.c index 3fcb1f36..943c9069 100644 --- a/test/rsync_test.c +++ b/test/rsync_test.c @@ -27,9 +27,9 @@ assert_descendant(bool expected, char *ancestor, char *descendant) struct rpki_uri *ancestor_uri; struct rpki_uri *descendant_uri; - ck_assert_int_eq(0, uri_create_str(&ancestor_uri, ancestor, + ck_assert_int_eq(0, uri_create_rsync_str(&ancestor_uri, ancestor, strlen(ancestor))); - ck_assert_int_eq(0, uri_create_str(&descendant_uri, descendant, + ck_assert_int_eq(0, uri_create_rsync_str(&descendant_uri, descendant, strlen(descendant))); ck_assert_int_eq(is_descendant(ancestor_uri, descendant_uri), expected); @@ -62,7 +62,7 @@ static void __mark_as_downloaded(char *uri_str, struct uri_list *visited_uris) { struct rpki_uri *uri; - ck_assert_int_eq(0, uri_create_str(&uri, uri_str, strlen(uri_str))); + ck_assert_int_eq(0, uri_create_rsync_str(&uri, uri_str, strlen(uri_str))); ck_assert_int_eq(mark_as_downloaded(uri, visited_uris), 0); uri_refput(uri); } @@ -71,7 +71,7 @@ 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_str(&uri, uri_str, strlen(uri_str))); + 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); } @@ -112,7 +112,7 @@ test_root_strategy(char *test, char *expected) struct rpki_uri *src; struct rpki_uri *dst; - ck_assert_int_eq(0, uri_create_str(&src, test, strlen(test))); + ck_assert_int_eq(0, uri_create_rsync_str(&src, test, strlen(test))); ck_assert_int_eq(handle_root_strategy(src, &dst), 0); ck_assert_str_eq(uri_get_global(dst), expected); diff --git a/test/rtr/db/vrps_test.c b/test/rtr/db/vrps_test.c index 962cc447..0802761b 100644 --- a/test/rtr/db/vrps_test.c +++ b/test/rtr/db/vrps_test.c @@ -11,6 +11,7 @@ #include "log.c" #include "output_printer.c" #include "object/router_key.c" +#include "rrdp/db_rrdp.c" #include "rtr/db/delta.c" #include "rtr/db/db_table.c" #include "rtr/db/rtr_db_impersonator.c" diff --git a/test/rtr/pdu_handler_test.c b/test/rtr/pdu_handler_test.c index efe1e358..67d28207 100644 --- a/test/rtr/pdu_handler_test.c +++ b/test/rtr/pdu_handler_test.c @@ -12,6 +12,7 @@ #include "output_printer.c" #include "crypto/base64.c" #include "object/router_key.c" +#include "rrdp/db_rrdp.c" #include "rtr/pdu.c" #include "rtr/pdu_handler.c" #include "rtr/primitive_reader.c" diff --git a/test/tal_test.c b/test/tal_test.c index aedfabc6..ee762f25 100644 --- a/test/tal_test.c +++ b/test/tal_test.c @@ -20,7 +20,8 @@ int validation_prepare(struct validation **out, struct tal *tal, - struct validation_handler *validation_handler) + struct validation_handler *validation_handler, + struct rrdp_handler *rrdp_handler) { return 0; }