Checkpoint: console validation is stable, server is not.
*.i*86
*.x86_64
*.hex
- rpki_validator
-rtr_server
++src/fort
# Debug files
*.dSYM/
+ # GNU wants us to include some files that we really don't want; therefore
+ # "foreign". The files are
+ #
+ # - AUTHORS: This should be inferred from the (automatic) git history, not some
+ # error prone, manually-maintained file!
+ # - ChangeLog: This is included in the main page of the site, which can be found
+ # in the gh-pages branch. Don't want to (nor should I) repeat myself.
+ # - NEWS: Same as ChangeLog.
+ # - README: We prefer the much gayer "README.md" version, so no thanks.
+ #
+ # Man, GNU conventions need a 21 century overhaul badly.
AUTOMAKE_OPTIONS = foreign
- SUBDIRS = src $(MAYBE_TESTS)
- DIST_SUBDIRS = src
-SUBDIRS = src man test
++SUBDIRS = src man $(MAYBE_TESTS)
++DIST_SUBDIRS = src man
-# FORT RTR server
+# FORT
- An RPKI Validator.
-An RTR server compliant to [RFC 6810](https://tools.ietf.org/html/rfc6810) (at least for now).
-
-More documentation about FORT at FORT's site [https://nicmx.github.io/FORT-validator/](https://nicmx.github.io/FORT-validator/).
++An RPKI Validator and RTR Server.
**Still under development!**
Dependencies:
- 2. [tomlc99](https://github.com/cktan/tomlc99)
+1. libcrypto ([LibreSSL](http://www.libressl.org/) or [OpenSSL](https://www.openssl.org/))
+ 1. [jansson](https://github.com/akheron/jansson)
+3. [libcmscodec](https://github.com/ydahhrk/libcmscodec)
+4. [rsync](http://rsync.samba.org/)
+ After all the dependencies are installed, run:
++
```
./autogen.sh
./configure
make install
```
-## Configuration
+More documentation at [https://nicmx.github.io/FORT-validator/](https://nicmx.github.io/FORT-validator/).
++
++## RTR Configuration
++
++> TODO Move this
+
+ The RTR server reads the configuration from a JSON file, learn about it at FORT's site [RTR Server arguments](https://nicmx.github.io/FORT-validator/doc/rtr-server.html).
+
+ Here's an example of a valid configuration file (assuming that the CSV file returned by FORT's validator is located at `/tmp/fort/roas.csv`):
+
+ ```javascript
+ {
+ "listen": {
+ "address": "127.0.0.1",
+ "port": "8323",
+ "queue": 10
+ },
+ "vrps": {
+ "location": "/tmp/fort/roas.csv",
+ "checkInterval": 60
+ }
+ }
+ ```
+
+ ## Execution
+
+ The executable needs only one argument: the location of the configuration file. So, assuming that the configuration file is located at `/home/fort/rtr.conf`, use the flag `-f` to indicate such location and run the server:
+
+ ```
+ $ rtr_server -f /home/fort/rtr.conf
+ ```
+
-That's it! The server will be listening on the configured port for any RTR client that wishes to establish a connection and exchange for validated ROA payloads.
++That's it! The server will be listening on the configured port for any RTR client that wishes to establish a connection and exchange for validated ROA payloads.
AC_PREREQ([2.69])
# TODO change the bug report address
- AC_INIT([rpki-validator], [0.0.1], [ydahhrk@gmail.com])
-AC_INIT([rtr-server], [0.0.1], [ydahhrk@gmail.com])
++AC_INIT([fort], [0.0.1], [ydahhrk@gmail.com])
AC_CONFIG_SRCDIR([src/main.c])
AM_INIT_AUTOMAKE([subdir-objects])
# Checks for library functions.
AC_FUNC_MALLOC
AC_CHECK_FUNCS([memset socket])
-AC_SEARCH_LIBS([pthread_create], [pthread])
+AC_SEARCH_LIBS([pthread_create], [pthread], [],
+ [AC_MSG_ERROR([unable to find the pthread() function])]
+)
+AC_SEARCH_LIBS([ber_decode], [cmscodec], [],
+ [AC_MSG_ERROR([unable to find the ber_decode() function])]
+)
+AC_SEARCH_LIBS([d2i_X509_bio], [crypto], [],
+ [AC_MSG_ERROR([unable to find the d2i_X509_bio() function])]
+)
- AC_SEARCH_LIBS([toml_parse], [toml], [],
- [AC_MSG_ERROR([unable to find the toml_parse() function])]
- )
+AC_SEARCH_LIBS([backtrace],[execinfo],[],
+ [AC_MSG_ERROR([unable to find backtrace() function])]
+)
+
+# Dependencies managed by pkg-config
-# Uhhh... this one starts with "PKG_" so it's probably different.
-# No idea.
-PKG_CHECK_MODULES([CHECK], [check])
+ PKG_CHECK_MODULES([JANSSON], [jansson])
+
+# By the way: Apparently PKG_CHECK_MODULES is poor practice now. I can't tell;
+# it's always the same guy complaining about it in Stack Overflow.
+# (Main one: https://stackoverflow.com/questions/10220946)
+# But I couldn't make check work with AC_SEARCH_LIBS, and (probably due to
+# typical obscure bullshit autotools reasoning) I have no idea why.
+
+AC_ARG_WITH(
+ [unit-tests],
+ AS_HELP_STRING(
+ [--with-unit-tests],
- [Generate unit tests. (Requires pkg-config and check)]
++ [Generate unit tests. (Requires check)]
+ )
+)
+
+# https://www.gnu.org/software/automake/manual/html_node/Conditional-Subdirectories.html
+# "Subdirectories with AM_CONDITIONAL" never worked for me. The problem might
+# be that it puts `MAYBE_TESTS` in `.PRECIOUS`, which maybe is a bug in the
+# autotools. (I honestly can't tell. They are so incredibly poorly designed.)
+# The code below is implemented as "Subdirectories with AC_SUBST."
- #
- # https://autotools.io/pkgconfig/pkg_check_modules.html#pkgconfig.pkg_check_modules.optional
- # Note: Since Check is the only current user of pkg-config, I don't want to
- # force pkg-config as a dependency; it should only be needed by people who want
- # to unit test. That's why I chose to use an `if` (which skips the
- # `PKG_CHECK_MODULES` on false) instead of an `AS_IF` (which evaluates all paths
- # apparently). In other words, if you ever want to add another pkg-config
- # dependency, you will need the `AS_IF` version.
+if test "x$with_unit_tests" = "xyes"; then
+ PKG_CHECK_MODULES([CHECK], [check])
+ MAYBE_TESTS=test
+else
+ MAYBE_TESTS=
+fi
+AC_SUBST([MAYBE_TESTS])
+
# Spit out the makefiles.
- AC_OUTPUT(Makefile src/Makefile test/Makefile)
+ AC_OUTPUT(Makefile src/Makefile man/Makefile test/Makefile)
--- /dev/null
- Path to a TOML file from which additional configuration will be read.
+.TH rpki-validator 8 2019-03-5 v0.0.1-beta "RPKI certificate path validator"
+
+.SH NAME
+rpki-validator - Actually still unnamed officially.
+
+.SH OPTIONS
+
+--help
+.RS 4
+Print long usage message.
+.RE
+.P
+
+--usage
+.RS 4
+Print short usage message.
+.RE
+.P
+
+--version
+.RS 4
+Print program version.
+.RE
+.P
+
+--configuration-file=<file>
+.RS 4
++Path to a JSON file from which additional configuration will be read.
+.RE
+.P
+
+--local-repository=<directory>
+.RS 4
+Path to a directory where the local cache of the repository will be stored
+and/or read.
+.RE
+.P
+
+--sync-strategy=(off|strict|root|root-except-ta)
+.RS 4
+RSYNC download strategy.
+.P
+off
+.RS 4
+Skip all RSYNCs. (Validate the existing cache repository pointed by --local-repository.)
+.RE
+.P
+strict
+.RS 4
+RSYNC every repository publication point separately. Only skip publication
+points that have already been downloaded during the current validation cycle.
+(Assuming each synchronization is recursive.)
+.P
+For example, suppose the validator gets certificates whose caRepository access
+methods (in their Subject Information Access extensions) point to the following
+publication points:
+.P
+1. rsync://rpki.example.com/foo/bar/
+.br
+2. rsync://rpki.example.com/foo/qux/
+.br
+3. rsync://rpki.example.com/foo/bar/
+.br
+4. rsync://rpki.example.com/foo/corge/grault/
+.br
+5. rsync://rpki.example.com/foo/corge/
+.br
+6. rsync://rpki.example.com/foo/corge/waldo/
+.P
+A validator following the `strict` strategy would download `bar`, download
+`qux`, skip `bar`, download `corge/grault`, download `corge` and skip
+`corge/waldo`.
+.P
+This is the slowest, but also the strictly correct sync strategy.
+.RE
+.P
+root
+.RS 4
+For each publication point found, guess the root of its repository and RSYNC
+that instead. Then skip any subsequent children of said root.
+.P
+(To guess the root of a repository, the validator counts four slashes, and
+prunes the rest of the URL.)
+.P
+Reusing the caRepository URLs from the `strict` strategy (above) as example, a
+validator following the `root` strategy would download
+`rsync://rpki.example.com/foo`, and then skip everything else.
+.P
+Assuming that the repository is specifically structured to be found within as
+few roots as possible, and they contain minimal RPKI-unrelated noise files, this
+is the fastest synchronization strategy. At time of writing, this is true for
+all the current official repositories.
+.RE
+.P
+root-except-ta
+.RS 4
+Synchronizes the root certificate (the one pointed by the TAL) in 'strict' mode,
+and once it's validated, synchronizes the rest of the repository in 'root' mode.
+.P
+Useful if you want 'root', but the root certificate is separated from the rest
+of the repository. Also useful if you don't want the validator to download the
+entire repository without first confirming the integrity and legitimacy of the
+root certificate.
+.RE
+.RE
+.P
+
+--maximum-certificate-depth=<unsigned integer>
+.RS 4
+Maximum allowable certificate chain length.
+.P
+(Required to prevent loops and "other degenerate forms of the logical RPKI
+hierarchy." (RFC 6481))
+.RE
+.P
+
+--tal=<file>
+.RS 4
+Path to the TAL file the validation will sprawl from.
+.P
+The TAL ("Trust Anchor Locator") is a text file that lists a few URLs which can
+be used to access the "Trust Anchor" (the root of a particular RPKI tree) and
+its public key. (See RFC 7730.)
+.RE
+.P
+
+--shuffle-uris
+.RS 4
+Shuffle URIs in the TAL before accessing them.
+.RE
+.P
+
+--color-output
+.RS 4
+Print ANSI color codes.
+.RE
+.P
+
+--output-file-name-format=(global-url|local-path|file-name)
+.RS 4
+Decides which version of file names should be printed during most debug/error
+messages.
+.P
+Suppose a certificate was downloaded from `rsync://rpki.example.com/foo/bar/baz.cer` into the local cache `repository/`:
+.P
+global-url
+.RS 4
+will print the certificate's name as `rsync://rpki.example.com/foo/bar/baz.cer`.
+.RE
+.P
+local-path
+.RS 4
+will print the certificate's name as `repository/rpki.example.com/foo/bar/baz.cer`.
+.RE
+.P
+file-name
+.RS 4
+will print the certificate's name as `baz.cer`.
+.RE
+.P
+.RE
-AM_CFLAGS = -pedantic -Wall -std=gnu11 -O3
-AM_LDFLAGS =
-
-bin_PROGRAMS = rtr_server
-
-rtr_server_SOURCES = main.c
-rtr_server_SOURCES += address.c address.h
-rtr_server_SOURCES += array_list.h
-rtr_server_SOURCES += clients.c clients.h
-rtr_server_SOURCES += common.c common.h
-rtr_server_SOURCES += configuration.c configuration.h
-rtr_server_SOURCES += csv.c csv.h
-rtr_server_SOURCES += line_file.c line_file.h
-rtr_server_SOURCES += notify.c notify.h
-rtr_server_SOURCES += updates_daemon.c updates_daemon.h
-rtr_server_SOURCES += vrps.c vrps.h
-
-rtr_server_SOURCES += rtr/err_pdu.c rtr/err_pdu.h
-rtr_server_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h
-rtr_server_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h
-rtr_server_SOURCES += rtr/pdu_serializer.c rtr/pdu_serializer.h
-rtr_server_SOURCES += rtr/pdu.c rtr/pdu.h
-rtr_server_SOURCES += rtr/primitive_reader.c rtr/primitive_reader.h
-rtr_server_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h
-rtr_server_SOURCES += rtr/rtr.c rtr/rtr.h
-
-rtr_server_LDADD = ${JANSSON_LIBS}
+# Comment these out during releases.
+# Also increase optimization I guess (-O0 -> -On)
+CFLAGS_DEBUG = -DDEBUG
+LDFLAGS_DEBUG = -rdynamic
+
- bin_PROGRAMS = rpki_validator
-
- rpki_validator_SOURCES = main.c
-
- rpki_validator_SOURCES += address.h address.c
- rpki_validator_SOURCES += algorithm.h algorithm.c
- rpki_validator_SOURCES += array_list.h
- rpki_validator_SOURCES += certificate_refs.h certificate_refs.c
- rpki_validator_SOURCES += common.h
- rpki_validator_SOURCES += config.h config.c
- rpki_validator_SOURCES += debug.h debug.c
- rpki_validator_SOURCES += extension.h extension.c
- rpki_validator_SOURCES += file.h file.c
- rpki_validator_SOURCES += line_file.h line_file.c
- rpki_validator_SOURCES += log.h log.c
- rpki_validator_SOURCES += nid.h nid.c
- rpki_validator_SOURCES += random.h random.c
- rpki_validator_SOURCES += resource.h resource.c
- rpki_validator_SOURCES += rpp.h rpp.c
- rpki_validator_SOURCES += sorted_array.h sorted_array.c
- rpki_validator_SOURCES += state.h state.c
- rpki_validator_SOURCES += str.h str.c
- rpki_validator_SOURCES += thread_var.h thread_var.c
- rpki_validator_SOURCES += uri.h uri.c
- rpki_validator_SOURCES += toml_handler.h toml_handler.c
-
- rpki_validator_SOURCES += rsync/rsync.h rsync/rsync.c
-
- rpki_validator_SOURCES += asn1/content_info.h asn1/content_info.c
- rpki_validator_SOURCES += asn1/decode.h asn1/decode.c
- rpki_validator_SOURCES += asn1/oid.h asn1/oid.c
- rpki_validator_SOURCES += asn1/signed_data.h asn1/signed_data.c
-
- rpki_validator_SOURCES += config/boolean.c config/boolean.h
- rpki_validator_SOURCES += config/filename_format.h config/filename_format.c
- rpki_validator_SOURCES += config/out_file.h config/out_file.c
- rpki_validator_SOURCES += config/str.c config/str.h
- rpki_validator_SOURCES += config/string_array.h config/string_array.c
- rpki_validator_SOURCES += config/sync_strategy.h config/sync_strategy.c
- rpki_validator_SOURCES += config/types.h
- rpki_validator_SOURCES += config/uint.c config/uint.h
-
- rpki_validator_SOURCES += crypto/base64.h crypto/base64.c
- rpki_validator_SOURCES += crypto/hash.h crypto/hash.c
-
- rpki_validator_SOURCES += object/certificate.h object/certificate.c
- rpki_validator_SOURCES += object/crl.h object/crl.c
- rpki_validator_SOURCES += object/ghostbusters.h object/ghostbusters.c
- rpki_validator_SOURCES += object/manifest.h object/manifest.c
- rpki_validator_SOURCES += object/name.h object/name.c
- rpki_validator_SOURCES += object/roa.h object/roa.c
- rpki_validator_SOURCES += object/signed_object.h object/signed_object.c
- rpki_validator_SOURCES += object/tal.h object/tal.c
- rpki_validator_SOURCES += object/vcard.h object/vcard.c
-
- rpki_validator_SOURCES += resource/ip4.h resource/ip4.c
- rpki_validator_SOURCES += resource/ip6.h resource/ip6.c
- rpki_validator_SOURCES += resource/asn.h resource/asn.c
-
- rpki_validator_CFLAGS = -Wall
++bin_PROGRAMS = fort
++
++fort_SOURCES = main.c
++
++fort_SOURCES += address.h address.c
++fort_SOURCES += algorithm.h algorithm.c
++fort_SOURCES += array_list.h
++fort_SOURCES += certificate_refs.h certificate_refs.c
++fort_SOURCES += common.h
++fort_SOURCES += config.h config.c
++fort_SOURCES += debug.h debug.c
++fort_SOURCES += extension.h extension.c
++fort_SOURCES += file.h file.c
++fort_SOURCES += line_file.h line_file.c
++fort_SOURCES += log.h log.c
++fort_SOURCES += nid.h nid.c
++fort_SOURCES += random.h random.c
++fort_SOURCES += resource.h resource.c
++fort_SOURCES += rpp.h rpp.c
++fort_SOURCES += sorted_array.h sorted_array.c
++fort_SOURCES += state.h state.c
++fort_SOURCES += str.h str.c
++fort_SOURCES += thread_var.h thread_var.c
++fort_SOURCES += uri.h uri.c
++fort_SOURCES += json_handler.h json_handler.c
++
++fort_SOURCES += rsync/rsync.h rsync/rsync.c
++
++fort_SOURCES += asn1/content_info.h asn1/content_info.c
++fort_SOURCES += asn1/decode.h asn1/decode.c
++fort_SOURCES += asn1/oid.h asn1/oid.c
++fort_SOURCES += asn1/signed_data.h asn1/signed_data.c
++
++fort_SOURCES += config/boolean.c config/boolean.h
++fort_SOURCES += config/filename_format.h config/filename_format.c
++fort_SOURCES += config/str.c config/str.h
++fort_SOURCES += config/string_array.h config/string_array.c
++fort_SOURCES += config/sync_strategy.h config/sync_strategy.c
++fort_SOURCES += config/types.h
++fort_SOURCES += config/uint.c config/uint.h
++fort_SOURCES += config/uint32.c config/uint32.h
++
++fort_SOURCES += crypto/base64.h crypto/base64.c
++fort_SOURCES += crypto/hash.h crypto/hash.c
++
++fort_SOURCES += object/certificate.h object/certificate.c
++fort_SOURCES += object/crl.h object/crl.c
++fort_SOURCES += object/ghostbusters.h object/ghostbusters.c
++fort_SOURCES += object/manifest.h object/manifest.c
++fort_SOURCES += object/name.h object/name.c
++fort_SOURCES += object/roa.h object/roa.c
++fort_SOURCES += object/signed_object.h object/signed_object.c
++fort_SOURCES += object/tal.h object/tal.c
++fort_SOURCES += object/vcard.h object/vcard.c
++
++fort_SOURCES += resource/ip4.h resource/ip4.c
++fort_SOURCES += resource/ip6.h resource/ip6.c
++fort_SOURCES += resource/asn.h resource/asn.c
++
++fort_SOURCES += array_list.h
++fort_SOURCES += clients.c clients.h
++fort_SOURCES += common.c common.h
++fort_SOURCES += notify.c notify.h
++fort_SOURCES += updates_daemon.c updates_daemon.h
++fort_SOURCES += vrps.c vrps.h
++
++fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h
++fort_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h
++fort_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h
++fort_SOURCES += rtr/pdu_serializer.c rtr/pdu_serializer.h
++fort_SOURCES += rtr/pdu.c rtr/pdu.h
++fort_SOURCES += rtr/primitive_reader.c rtr/primitive_reader.h
++fort_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h
++fort_SOURCES += rtr/rtr.c rtr/rtr.h
++
++fort_CFLAGS = -Wall
+# Feel free to temporarily remove this one if you're not using gcc 7.3.0.
- rpki_validator_CFLAGS += $(GCC_WARNS)
- rpki_validator_CFLAGS += -std=gnu99 -O0 -g $(CFLAGS_DEBUG)
- rpki_validator_LDFLAGS = $(LDFLAGS_DEBUG)
++fort_CFLAGS += $(GCC_WARNS)
++fort_CFLAGS += -std=gnu99 -O0 -g $(CFLAGS_DEBUG)
++fort_LDFLAGS = $(LDFLAGS_DEBUG)
++fort_LDADD = ${JANSSON_LIBS}
# I'm tired of scrolling up, but feel free to comment this out.
--GCC_WARNS = -fmax-errors=1
++#GCC_WARNS = -fmax-errors=1
# Please ready some arguments if you want to permanently remove some of these
# flags. I gave a quick read to the documentation of all of these warnings, and
# generally liked each of them.
--GCC_WARNS += -Wpedantic -pedantic-errors -Waddress -Walloc-zero -Walloca
++GCC_WARNS = -Wpedantic -pedantic-errors -Waddress -Walloc-zero -Walloca
GCC_WARNS += -Wno-aggressive-loop-optimizations -Warray-bounds=2 -Wbool-compare
GCC_WARNS += -Wbool-operation -Wno-builtin-declaration-mismatch -Wcast-align
GCC_WARNS += -Wcast-qual -Wchar-subscripts -Wchkp -Wclobbered -Wcomment
# Can't use because of dependencies: -Waggregate-return
# Want to add, but needs work: -Wconversion, -Wsign-compare, -Wsign-conversion
# Seem to require other compiler features: -Wchkp, -Wstack-protector,
--# -Wstrict-aliasing, -Wstrict-overflow
++# -Wstrict-aliasing
#ifndef SRC_ARRAY_LIST_H_
#define SRC_ARRAY_LIST_H_
-#include <err.h>
#include <errno.h>
+ #include <stdlib.h>
++#include "log.h"
#define ARRAY_LIST(name, elem_type) \
struct name { \
list->len = 0; \
list->array = malloc(list->capacity \
* sizeof(elem_type)); \
-- return (list->array != NULL) ? 0 : -ENOMEM; \
++ return (list->array != NULL) ? 0 : pr_enomem(); \
} \
\
static void \
--- /dev/null
- warnx( "Clients DB couldn't be initialized");
+ #include "clients.h"
+
+ #include "array_list.h"
+ #include "common.h"
+
+ #define SADDR_IN(addr) ((struct sockaddr_in *)addr)
+ #define SADDR_IN6(addr) ((struct sockaddr_in6 *)addr)
+
+ ARRAY_LIST(clientsdb, struct client)
+
+ struct clientsdb clients_db;
+
+ /* Read and Write locks */
+ sem_t rlock, wlock;
+
+ /* Readers counter */
+ unsigned int rcounter;
+
+ int
+ clients_db_init(void)
+ {
+ int error;
+
+ error = clientsdb_init(&clients_db);
+ if (error)
- if (ptr->sin_addr.s_addr ==
++ return error;
+
+ sem_init(&rlock, 0, 1);
+ sem_init(&wlock, 0, 1);
+ rcounter = 0;
+
+ return error;
+ }
+
+ static struct client *
+ get_client(struct sockaddr_storage *addr)
+ {
+ struct client *ptr;
+
+ read_lock(&rlock, &wlock, &rcounter);
+ ARRAYLIST_FOREACH(&clients_db, ptr)
+ if (ptr->sin_family == addr->ss_family) {
+ if (ptr->sin_family == AF_INET) {
- ptr->sin6_addr.s6_addr32,
++ if (ptr->addr.sin.s_addr ==
+ SADDR_IN(addr)->sin_addr.s_addr &&
+ ptr->sin_port ==
+ SADDR_IN(addr)->sin_port) {
+ read_unlock(&rlock, &wlock, &rcounter);
+ return ptr;
+ }
+ } else if (ptr->sin_family == AF_INET6)
+ if (IN6_ARE_ADDR_EQUAL(
- client.sin_addr = SADDR_IN(addr)->sin_addr;
++ ptr->addr.sin6.s6_addr32,
+ SADDR_IN6(addr)->sin6_addr.s6_addr32) &&
+ ptr->sin_port ==
+ SADDR_IN6(addr)->sin6_port) {
+ read_unlock(&rlock, &wlock, &rcounter);
+ return ptr;
+ }
+ }
+ read_unlock(&rlock, &wlock, &rcounter);
+ return NULL;
+ }
+
+ static int
+ create_client(int fd, struct sockaddr_storage *addr, u_int8_t rtr_version)
+ {
+ struct client client;
+ int error;
+
+ client.fd = fd;
+ client.sin_family = addr->ss_family;
+ if (addr->ss_family == AF_INET) {
- client.sin6_addr = SADDR_IN6(addr)->sin6_addr;
++ client.addr.sin = SADDR_IN(addr)->sin_addr;
+ client.sin_port = SADDR_IN(addr)->sin_port;
+ } else if (addr->ss_family == AF_INET6) {
- warnx("Couldn't allocate new clients DB");
- return;
++ client.addr.sin6 = SADDR_IN6(addr)->sin6_addr;
+ client.sin_port = SADDR_IN6(addr)->sin6_port;
+ }
+ client.rtr_version = rtr_version;
+
+ sem_wait(&wlock);
+ error = clientsdb_add(&clients_db, &client);
+ sem_post(&wlock);
+
+ return error;
+ }
+
+ /*
+ * If the ADDR isn't already stored, store it; otherwise update its file
+ * descriptor.
+ *
+ * Return the creation/update result.
+ *
+ * Code error -EINVAL will be returned when a client exists but its RTR version
+ * isn't the same as in the DB.
+ */
+ int
+ update_client(int fd, struct sockaddr_storage *addr, u_int8_t rtr_version)
+ {
+ struct client *client;
+ client = get_client(addr);
+
+ if (client == NULL)
+ return create_client(fd, addr, rtr_version);
+
+ /*
+ * Isn't ready to handle distinct version on clients reconnection, but
+ * for now there's no problem since only RTR v0 is supported.
+ */
+ if (client->rtr_version != rtr_version)
+ return -EINVAL;
+
+ client->fd = fd;
+ return 0;
+ }
+
+ size_t
+ client_list(struct client **clients)
+ {
+ size_t len;
+
+ read_lock(&rlock, &wlock, &rcounter);
+ *clients = clients_db.array;
+ len = clients_db.len;
+ read_unlock(&rlock, &wlock, &rcounter);
+
+ return len;
+ }
+
+ static void
+ client_destroy(struct client *client)
+ {
+ /* Didn't allocate something, so do nothing */
+ }
+
+ void
+ clients_forget(int fd)
+ {
+ struct clientsdb *new_db;
+ struct client *ptr;
+
+ new_db = malloc(sizeof(struct clientsdb));
+ if (new_db == NULL) {
++ pr_err("Couldn't allocate new clients DB");
++ return; /* TODO This is not acceptable... */
+ }
+ clientsdb_init(new_db);
+ read_lock(&rlock, &wlock, &rcounter);
+ ARRAYLIST_FOREACH(&clients_db, ptr)
+ if (ptr->fd != fd)
+ clientsdb_add(new_db, ptr);
+ read_unlock(&rlock, &wlock, &rcounter);
+
+ sem_wait(&wlock);
+ clientsdb_cleanup(&clients_db, client_destroy);
+ clients_db = *new_db;
+ sem_post(&wlock);
+ free(new_db);
+ }
+
+ void
+ clients_db_destroy(void)
+ {
+ sem_wait(&wlock);
+ clientsdb_cleanup(&clients_db, client_destroy);
+ sem_post(&wlock);
+
+ sem_destroy(&wlock);
+ sem_destroy(&rlock);
+ }
--- /dev/null
- struct in_addr sin_addr;
- struct in6_addr sin6_addr;
- };
+ #ifndef SRC_CLIENTS_H_
+ #define SRC_CLIENTS_H_
+
+ #include <arpa/inet.h>
+
+ struct client {
+ int fd;
+ sa_family_t sin_family;
+ union {
++ struct in_addr sin;
++ struct in6_addr sin6;
++ } addr;
+ in_port_t sin_port;
+ u_int8_t rtr_version;
+ };
+
+ int clients_db_init(void);
+ int update_client(int, struct sockaddr_storage *, u_int8_t);
+ size_t client_list(struct client **);
+
+ void clients_forget(int);
+ void clients_db_destroy(void);
+
+ #endif /* SRC_CLIENTS_H_ */
-#ifndef _SRC_COMMON_H_
-#define _SRC_COMMON_H_
+#ifndef SRC_RTR_COMMON_H_
+#define SRC_RTR_COMMON_H_
-#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
-
-#define EUNIMPLEMENTED 566456
+ #include <semaphore.h>
+
+/* "I think that this is not supposed to be implemented." */
+#define ENOTSUPPORTED 3172
+/* "I haven't implemented this yet." */
+#define ENOTIMPLEMENTED 3173
+/*
+ * "URI was not RSYNC; ignore it."
+ * Not really an error. The RFCs usually declare URI lists; usually only one of
+ * them is required to be RSYNC and the others should be skipped (until we
+ * start supporting them.)
+ */
+#define ENOTRSYNC 3174
- #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
+ /*
- * FYI: The error functions are warn() and warnx().
- * warn() automatically appends the errno string message (strerror()), warnx()
- * does not.
++ * If you're wondering why I'm not using -abs(error), it's because abs(INT_MIN)
++ * overflows, so gcc complains sometimes.
+ */
++#define ENSURE_NEGATIVE(error) (((error) < 0) ? (error) : -(error))
++
++#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0]))
+
+ void read_lock(sem_t *, sem_t *, unsigned int *);
+ void read_unlock(sem_t *, sem_t *, unsigned int *);
-#endif /* _SRC_COMMON_H_ */
+#endif /* SRC_RTR_COMMON_H_ */
--- /dev/null
- #include "toml_handler.h"
+#include "config.h"
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
++#include <sys/socket.h>
+
+#include "common.h"
++#include "json_handler.h"
+#include "log.h"
- #include "config/out_file.h"
+#include "config/boolean.h"
-
- /**
- * Please note that this is actually two `for`s stacked together, so don't use
- * `break` nor `continue` to get out.
- */
- #define FOREACH_OPTION(groups, grp, opt, type) \
- for (grp = groups; grp->name != NULL; grp++) \
- for (opt = grp->options; opt->id != 0; opt++) \
- if ((opt->availability == 0) || \
- (opt->availability & type))
+#include "config/str.h"
+#include "config/uint.h"
- /** TAL file name/location. */
++#include "config/uint32.h"
+
+/**
+ * To add a member to this structure,
+ *
+ * 1. Add it.
+ * 2. Add its metadata somewhere in @groups.
+ * 3. Add default value to set_default_values().
+ * 4. Create the getter.
+ *
+ * Assuming you don't need to create a data type, that should be all.
+ */
+struct rpki_config {
- * Shuffle uris in tal?
- * (https://tools.ietf.org/html/rfc7730#section-3, last paragraphs)
++ /** TAL file name or directory. TODO support directories */
+ char *tal;
+ /** Path of our local clone of the repository */
+ char *local_repository;
+ /** Synchronization (currently only RSYNC) download strategy. */
+ enum sync_strategy sync_strategy;
+ /**
- bool shuffle_uris;
++ * Handle TAL URIs in random order?
++ * (https://tools.ietf.org/html/rfc7730#section-3, last
++ * paragraphs)
+ */
- /** Output stream where the valid ROAs will be dumped. */
- struct config_out_file roa_output;
- } output;
++ bool shuffle_tal_uris;
+ /**
+ * rfc6487#section-7.2, last paragraph.
+ * Prevents arbitrarily long paths and loops.
+ */
+ unsigned int maximum_certificate_depth;
+
++ struct {
++ /** The listener address of the RTR server. */
++ char *address;
++ /** TODO document */
++ char *port;
++ /** Maximum accepted client connections */
++ unsigned int queue;
++
++ /** Interval used to look for updates at VRPs location */
++ /* TODO rename */
++ unsigned int vrps_check_interval;
++
++ /** Intervals use at RTR v1 End of data PDU **/
++ u_int32_t refresh_interval;
++ u_int32_t retry_interval;
++ u_int32_t expire_interval;
++ } server;
++
+ struct {
+ char *program;
+ struct {
+ struct string_array flat;
+ struct string_array recursive;
+ } args;
+ } rsync;
+
+ struct {
+ /** Print ANSI color codes? */
+ bool color;
+ /** Format in which file names will be printed. */
+ enum filename_format filename_format;
- DECLARE_HANDLE_FN(handle_toml);
++ } log;
+};
+
+static void print_usage(FILE *, bool);
+
+#define DECLARE_HANDLE_FN(name) \
+ static int name( \
+ struct option_field const *, \
+ char * \
+ )
+DECLARE_HANDLE_FN(handle_help);
+DECLARE_HANDLE_FN(handle_usage);
+DECLARE_HANDLE_FN(handle_version);
- static const struct option_field global_fields[] = {
++DECLARE_HANDLE_FN(handle_json);
+
+static char const *program_name;
+static struct rpki_config rpki_config;
+
+/**
+ * An option that takes no arguments, is not correlated to any rpki_config
+ * fields, and is entirely managed by its handler function.
+ */
+static const struct global_type gt_callback = {
+ .has_arg = no_argument,
+};
+
- .handler = handle_toml,
- .doc = "TOML file additional configuration will be read from",
++static const struct option_field options[] = {
++
++ /* ARGP-only, non-fields */
+ {
+ .id = 'h',
+ .name = "help",
+ .type = >_callback,
+ .handler = handle_help,
+ .doc = "Give this help list",
+ .availability = AVAILABILITY_GETOPT,
+ }, {
+ .id = 1000,
+ .name = "usage",
+ .type = >_callback,
+ .handler = handle_usage,
+ .doc = "Give a short usage message",
+ .availability = AVAILABILITY_GETOPT,
+ }, {
+ .id = 'V',
+ .name = "version",
+ .type = >_callback,
+ .handler = handle_version,
+ .doc = "Print program version",
+ .availability = AVAILABILITY_GETOPT,
+ }, {
+ .id = 'f',
+ .name = "configuration-file",
+ .type = >_string,
- { 0 },
- };
++ .handler = handle_json,
++ .doc = "JSON file additional configuration will be read from",
+ .arg_doc = "<file>",
+ .availability = AVAILABILITY_GETOPT,
++ },
++
++ /* Root fields */
++ {
++ .id = 't',
++ .name = "tal",
++ .type = >_string,
++ .offset = offsetof(struct rpki_config, tal),
++ .doc = "Path to the TAL file",
++ .arg_doc = "<file>",
+ }, {
+ .id = 'r',
+ .name = "local-repository",
+ .type = >_string,
+ .offset = offsetof(struct rpki_config, local_repository),
+ .doc = "Directory where the repository local cache will be stored/read",
+ .arg_doc = "<directory>",
+ }, {
+ .id = 1001,
+ .name = "sync-strategy",
+ .type = >_sync_strategy,
+ .offset = offsetof(struct rpki_config, sync_strategy),
+ .doc = "RSYNC download strategy",
++ }, {
++ .id = 2000,
++ .name = "shuffle-uris",
++ .type = >_bool,
++ .offset = offsetof(struct rpki_config, shuffle_tal_uris),
++ .doc = "Shuffle URIs in the TAL before accessing them",
+ }, {
+ .id = 1002,
+ .name = "maximum-certificate-depth",
+ .type = >_u_int,
+ .offset = offsetof(struct rpki_config,
+ maximum_certificate_depth),
+ .doc = "Maximum allowable certificate chain length",
+ .min = 1,
+ /**
+ * It cannot be UINT_MAX, because then the actual number will
+ * overflow and will never be bigger than this.
+ */
+ .max = UINT_MAX - 1,
+ },
- static const struct option_field tal_fields[] = {
+
- .id = 't',
- .name = "tal",
++ /* Server fields */
+ {
- .offset = offsetof(struct rpki_config, tal),
- .doc = "Path to the TAL file",
- .arg_doc = "<file>",
++ .id = 5000,
++ .name = "server.address",
+ .type = >_string,
- .id = 2000,
- .name = "shuffle-uris",
- .type = >_bool,
- .offset = offsetof(struct rpki_config, shuffle_uris),
- .doc = "Shuffle URIs in the TAL before accessing them",
++ .offset = offsetof(struct rpki_config, server.address),
++ .doc = "The listener address of the RTR server.",
+ }, {
- { 0 },
- };
++ .id = 5001,
++ .name = "server.port",
++ .type = >_string,
++ .offset = offsetof(struct rpki_config, server.port),
++ .doc = "", /* TODO */
++ }, {
++ .id = 5003,
++ .name = "server.queue",
++ .type = >_u_int,
++ .offset = offsetof(struct rpki_config, server.queue),
++ .doc = "Maximum accepted client connections",
++ .min = 1,
++ .max = SOMAXCONN,
++ }, {
++ .id = 5004,
++ .name = "server.vrps-check-interval",
++ .type = >_u_int,
++ .offset = offsetof(struct rpki_config, server.vrps_check_interval),
++ .doc = "Interval used to look for updates at VRPs location",
++ /*
++ * RFC 6810 and 8210:
++ * The cache MUST rate-limit Serial Notifies to no more frequently than
++ * one per minute.
++ */
++ .min = 60,
++ .max = 7200,
++ }, {
++ .id = 5005,
++ .name = "server.rtr-interval.refresh",
++ .type = >_u_int32,
++ .offset = offsetof(struct rpki_config, server.refresh_interval),
++ .doc = "Intervals use at RTR v1 End of data PDU",
++ .min = 1,
++ .max = 86400,
++ }, {
++ .id = 5006,
++ .name = "server.rtr-interval.retry",
++ .type = >_u_int32,
++ .offset = offsetof(struct rpki_config, server.retry_interval),
++ .doc = "",
++ .min = 1,
++ .max = 7200,
++ }, {
++ .id = 5007,
++ .name = "server.rtr-interval.expire",
++ .type = >_u_int32,
++ .offset = offsetof(struct rpki_config, server.expire_interval),
++ .doc = "",
++ .min = 600,
++ .max = 172800,
+ },
- static const struct option_field rsync_fields[] = {
+
- .name = "program",
++ /* RSYNC fields */
+ {
+ .id = 3000,
- .availability = AVAILABILITY_TOML,
++ .name = "rsync.program",
+ .type = >_string,
+ .offset = offsetof(struct rpki_config, rsync.program),
+ .doc = "Name of the program needed to execute an RSYNC",
+ .arg_doc = "<path to program>",
- .name = "arguments-recursive",
++ .availability = AVAILABILITY_JSON,
+ }, {
+ .id = 3001,
- .availability = AVAILABILITY_TOML,
++ .name = "rsync.arguments-recursive",
+ .type = >_string_array,
+ .offset = offsetof(struct rpki_config, rsync.args.recursive),
+ .doc = "RSYNC program arguments that will trigger a recursive RSYNC",
- .name = "arguments-flat",
++ .availability = AVAILABILITY_JSON,
+ }, {
+ .id = 3002,
- .availability = AVAILABILITY_TOML,
++ .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",
- { 0 },
- };
++ .availability = AVAILABILITY_JSON,
+ },
- static const struct option_field output_fields[] = {
+
- .name = "color-output",
++ /* Logging fields */
+ {
+ .id = 'c',
- .offset = offsetof(struct rpki_config, output.color),
++ .name = "log.color-output",
+ .type = >_bool,
- .name = "output-file-name-format",
++ .offset = offsetof(struct rpki_config, log.color),
+ .doc = "Print ANSI color codes.",
+ }, {
+ .id = 4000,
- .offset = offsetof(struct rpki_config, output.filename_format),
++ .name = "log.file-name-format",
+ .type = >_filename_format,
- }, {
- .id = 'o',
- .name = "roa-output-file",
- .type = >_out_file,
- .offset = offsetof(struct rpki_config, output.roa_output),
- .doc = "File where the valid ROAs will be dumped.",
++ .offset = offsetof(struct rpki_config, log.filename_format),
+ .doc = "File name variant to print during debug/error messages",
- { 0 },
- };
+ },
- static const struct group_fields groups[] = {
- {
- .name = "root",
- .options = global_fields,
- }, {
- .name = "tal",
- .options = tal_fields,
- }, {
- .name = "rsync",
- .options = rsync_fields,
- }, {
- .name = "output",
- .options = output_fields,
- },
- { NULL },
+
- handle_toml(struct option_field const *field, char *file_name)
++ { 0 },
+};
+
+/**
+ * Returns true if @field is the descriptor of one of the members of the
+ * struct rpki_config structure, false otherwise.
+ * (Alternatively: Returns true if @field->offset is valid, false otherwise.)
+ */
+static bool
+is_rpki_config_field(struct option_field const *field)
+{
+ return field->handler == NULL;
+}
+
+void *
+get_rpki_config_field(struct option_field const *field)
+{
+ return ((unsigned char *) &rpki_config) + field->offset;
+}
+
+static int
+handle_help(struct option_field const *field, char *arg)
+{
+ print_usage(stdout, true);
+ exit(0);
+}
+
+static int
+handle_usage(struct option_field const *field, char *arg)
+{
+ print_usage(stdout, false);
+ exit(0);
+}
+
+static int
+handle_version(struct option_field const *field, char *arg)
+{
+ printf("0.0.1\n");
+ exit(0);
+}
+
+static int
- struct group_fields const *group;
++handle_json(struct option_field const *field, char *file_name)
+{
+ return set_config_from_file(file_name);
+}
+
+static bool
+is_alphanumeric(int chara)
+{
+ return ('a' <= chara && chara <= 'z')
+ || ('A' <= chara && chara <= 'Z')
+ || ('0' <= chara && chara <= '9');
+}
+
+/**
+ * "struct option" is the array that getopt expects.
+ * "struct args_flag" is our option metadata.
+ */
+static int
+construct_getopt_options(struct option **_long_opts, char **_short_opts)
+{
- FOREACH_OPTION(groups, group, opt, AVAILABILITY_GETOPT) {
+ struct option_field const *opt;
+ struct option *long_opts;
+ char *short_opts;
+ unsigned int total_long_options;
+ unsigned int total_short_options;
+
+ total_long_options = 0;
+ total_short_options = 0;
- FOREACH_OPTION(groups, group, opt, AVAILABILITY_GETOPT) {
++ FOREACH_OPTION(options, opt, AVAILABILITY_GETOPT) {
+ total_long_options++;
+ if (is_alphanumeric(opt->id)) {
+ total_short_options++;
+ if (opt->type->has_arg != no_argument)
+ total_short_options++; /* ":" */
+ }
+ }
+
+ /* +1 NULL end, means end of array. */
+ long_opts = calloc(total_long_options + 1, sizeof(struct option));
+ if (long_opts == NULL)
+ return pr_enomem();
+ short_opts = malloc(total_short_options + 1);
+ if (short_opts == NULL) {
+ free(long_opts);
+ return pr_enomem();
+ }
+
+ *_long_opts = long_opts;
+ *_short_opts = short_opts;
+
- struct group_fields const *grp;
++ FOREACH_OPTION(options, opt, AVAILABILITY_GETOPT) {
+ long_opts->name = opt->name;
+ long_opts->has_arg = opt->type->has_arg;
+ long_opts->flag = NULL;
+ long_opts->val = opt->id;
+ long_opts++;
+
+ if (is_alphanumeric(opt->id)) {
+ *short_opts = opt->id;
+ short_opts++;
+ if (opt->type->has_arg != no_argument) {
+ *short_opts = ':';
+ short_opts++;
+ }
+ }
+ }
+
+ *short_opts = '\0';
+ return 0;
+}
+
+static void
+print_config(void)
+{
- FOREACH_OPTION(groups, grp, opt, 0xFFFF)
+ struct option_field const *opt;
+
+ pr_info("Configuration {");
+ pr_indent_add();
+
- opt->type->print(grp, opt, get_rpki_config_field(opt));
++ FOREACH_OPTION(options, opt, 0xFFFF)
+ if (is_rpki_config_field(opt) && opt->type->print != NULL)
- if (rpki_config.local_repository == NULL)
- return pr_enomem();
++ opt->type->print(opt, get_rpki_config_field(opt));
+
+ pr_indent_rm();
+ pr_info("}");
+}
+
+static int
+set_default_values(void)
+{
+ static char const *default_rsync_args[] = {
+ "--recursive",
+ "--delete",
+ "--times",
+ "--contimeout=20",
+ "$REMOTE",
+ "$LOCAL",
+ };
+
+ int error;
+
+ /*
+ * Values that might need to be freed WILL be freed, so use heap
+ * duplicates.
+ */
+
++ rpki_config.server.address = NULL;
++ rpki_config.server.port = strdup("323");
++ if (rpki_config.server.port == NULL)
++ return pr_enomem();
++
++ rpki_config.server.queue = 10;
++ rpki_config.server.vrps_check_interval = 60;
++ rpki_config.server.refresh_interval = 3600;
++ rpki_config.server.retry_interval = 600;
++ rpki_config.server.expire_interval = 7200;
++
+ rpki_config.tal = NULL;
+
+ rpki_config.local_repository = strdup("repository/");
- rpki_config.shuffle_uris = false;
++ if (rpki_config.local_repository == NULL) {
++ error = pr_enomem();
++ goto revert_port;
++ }
+
+ rpki_config.sync_strategy = SYNC_ROOT;
- rpki_config.output.color = false;
- rpki_config.output.filename_format = FNF_GLOBAL;
- rpki_config.output.roa_output.fd = NULL;
- rpki_config.output.roa_output.file_name = NULL;
++ rpki_config.shuffle_tal_uris = false;
+ rpki_config.maximum_certificate_depth = 32;
+
+ rpki_config.rsync.program = strdup("rsync");
+ if (rpki_config.rsync.program == NULL) {
+ error = pr_enomem();
+ goto revert_repository;
+ }
+
+ error = string_array_init(&rpki_config.rsync.args.recursive,
+ default_rsync_args, ARRAY_LEN(default_rsync_args));
+ if (error)
+ goto revert_rsync_program;
+ /* Simply remove --recursive and --delete. */
+ error = string_array_init(&rpki_config.rsync.args.flat,
+ default_rsync_args + 2, ARRAY_LEN(default_rsync_args) - 2);
+ if (error)
+ goto revert_recursive_array;
+
- struct group_fields const *group;
++ rpki_config.log.color = false;
++ rpki_config.log.filename_format = FNF_GLOBAL;
+
+ return 0;
+
+revert_recursive_array:
+ string_array_cleanup(&rpki_config.rsync.args.recursive);
+revert_rsync_program:
+ free(rpki_config.rsync.program);
+revert_repository:
+ free(rpki_config.local_repository);
++revert_port:
++ free(rpki_config.server.port);
+ return error;
+}
+
+static int
+validate_config(void)
+{
+ return (rpki_config.tal != NULL)
+ ? 0
+ : pr_err("The TAL file (--tal) is mandatory.");
+}
+
+static void
+print_usage(FILE *stream, bool print_doc)
+{
- FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) {
+ struct option_field const *option;
+ char const *arg_doc;
+
+ fprintf(stream, "Usage: %s\n", program_name);
- struct group_fields const *group;
++ FOREACH_OPTION(options, option, AVAILABILITY_GETOPT) {
+ fprintf(stream, "\t[");
+ fprintf(stream, "--%s", option->name);
+
+ if (option->arg_doc != NULL)
+ arg_doc = option->arg_doc;
+ else if (option->type->arg_doc != NULL)
+ arg_doc = option->type->arg_doc;
+ else
+ arg_doc = NULL;
+
+ switch (option->type->has_arg) {
+ case no_argument:
+ break;
+ case optional_argument:
+ case required_argument:
+ if (arg_doc != NULL)
+ fprintf(stream, "=%s", arg_doc);
+ break;
+ }
+
+ fprintf(stream, "]\n");
+
+ if (print_doc)
+ fprintf(stream, "\t (%s)\n", option->doc);
+ }
+}
+
+
+static int
+handle_opt(int opt)
+{
- FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) {
+ struct option_field const *option;
+
- void
- get_group_fields(struct group_fields const **group_fields)
++ FOREACH_OPTION(options, option, AVAILABILITY_GETOPT) {
+ if (option->id == opt) {
+ return is_rpki_config_field(option)
+ ? option->type->parse.argv(option, optarg,
+ get_rpki_config_field(option))
+ : option->handler(option, optarg);
+ }
+ }
+
+ pr_err("Unrecognized option: %d", opt);
+ return -ESRCH;
+}
+
+int
+handle_flags_config(int argc, char **argv)
+{
+ struct option *long_opts;
+ char *short_opts;
+ int opt;
+ int error;
+
+ program_name = argv[0];
+ error = set_default_values();
+ if (error)
+ return error;
+
+ long_opts = NULL;
+ short_opts = NULL;
+ error = construct_getopt_options(&long_opts, &short_opts);
+ if (error)
+ goto end; /* Error msg already printed. */
+
+ while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL))
+ != -1) {
+ error = handle_opt(opt);
+ if (error)
+ goto end;
+ }
+
+ /*
+ * This triggers when the user runs something like
+ * `rpki-validator disable-rsync` instead of
+ * `rpki-validator --disable-rsync`.
+ * This program does not have unflagged payload.
+ */
+ if (optind < argc) {
+ error = pr_err("I don't know what '%s' is.", argv[optind]);
+ goto end;
+ }
+
+ error = validate_config();
+
+end:
+ if (error)
+ free_rpki_config();
+ else
+ print_config();
+
+ free(long_opts);
+ free(short_opts);
+ return error;
+
+}
+
- *group_fields = groups;
++struct option_field const *
++get_option_metadatas(void)
++{
++ return options;
++}
++
++char const *
++config_get_server_address(void)
+{
- config_get_shuffle_uris(void)
++ return rpki_config.server.address;
++}
++
++char const *
++config_get_server_port(void)
++{
++ return rpki_config.server.port;
++}
++
++int
++config_get_server_queue(void)
++{
++ /*
++ * The range of this is 1-<small number>, so adding signedness is safe.
++ */
++ return rpki_config.server.queue;
++}
++
++unsigned int
++config_get_vrps_check_interval(void)
++{
++ return rpki_config.server.vrps_check_interval;
++}
++
++u_int32_t
++config_get_refresh_interval(void)
++{
++ return rpki_config.server.refresh_interval;
++}
++
++u_int32_t
++config_get_retry_interval(void)
++{
++ return rpki_config.server.retry_interval;
++}
++
++u_int32_t
++config_get_expire_interval(void)
++{
++ return rpki_config.server.expire_interval;
+}
+
+char const *
+config_get_tal(void)
+{
+ return rpki_config.tal;
+}
+
+char const *
+config_get_local_repository(void)
+{
+ return rpki_config.local_repository;
+}
+
+enum sync_strategy
+config_get_sync_strategy(void)
+{
+ return rpki_config.sync_strategy;
+}
+
+bool
- return rpki_config.shuffle_uris;
++config_get_shuffle_tal_uris(void)
+{
- return rpki_config.output.color;
++ return rpki_config.shuffle_tal_uris;
+}
+
+unsigned int
+config_get_max_cert_depth(void)
+{
+ return rpki_config.maximum_certificate_depth;
+}
+
+bool
+config_get_color_output(void)
+{
- return rpki_config.output.filename_format;
- }
-
- FILE *
- config_get_roa_output(void)
- {
- return (rpki_config.output.roa_output.fd != NULL)
- ? rpki_config.output.roa_output.fd
- : stdout;
++ return rpki_config.log.color;
+}
+
+enum filename_format
+config_get_filename_format(void)
+{
- pr_crit("Invalid sync strategy: '%u'", rpki_config.sync_strategy);
++ return rpki_config.log.filename_format;
+}
+
+char *
+config_get_rsync_program(void)
+{
+ return rpki_config.rsync.program;
+}
+
+struct string_array const *
+config_get_rsync_args(bool is_ta)
+{
+ switch (rpki_config.sync_strategy) {
+ case SYNC_ROOT:
+ return &rpki_config.rsync.args.recursive;
+ case SYNC_ROOT_EXCEPT_TA:
+ return is_ta
+ ? &rpki_config.rsync.args.flat
+ : &rpki_config.rsync.args.recursive;
+ case SYNC_STRICT:
+ return &rpki_config.rsync.args.flat;
+ case SYNC_OFF:
+ break;
+ }
+
- struct group_fields const *group;
++ pr_crit("Invalid sync strategy: '%u'",
++ rpki_config.sync_strategy);
+ /*
+ * Return something usable anyway; don't want to check NULL.
+ * This is supposed to be unreachable code anyway.
+ */
+ return &rpki_config.rsync.args.recursive;
+}
+
+void
+free_rpki_config(void)
+{
- FOREACH_OPTION(groups, group, option, 0xFFFF)
+ struct option_field const *option;
+
++ FOREACH_OPTION(options, option, 0xFFFF)
+ if (is_rpki_config_field(option) && option->type->free != NULL)
+ option->type->free(get_rpki_config_field(option));
+}
--- /dev/null
- bool config_get_shuffle_uris(void);
+#ifndef SRC_CONFIG_H_
+#define SRC_CONFIG_H_
+
+#include <stdbool.h>
++#include <stdint.h>
+
+#include "config/filename_format.h"
+#include "config/sync_strategy.h"
+#include "config/string_array.h"
+#include "config/types.h"
+
+/* Init/destroy */
+int handle_flags_config(int , char **);
+void free_rpki_config(void);
+
+/* Getters */
++char const *config_get_server_address(void);
++char const *config_get_server_port(void);
++int config_get_server_queue(void);
++unsigned int config_get_vrps_check_interval(void);
++uint32_t config_get_refresh_interval(void);
++uint32_t config_get_retry_interval(void);
++uint32_t config_get_expire_interval(void);
++
+char const *config_get_tal(void);
+char const *config_get_local_repository(void);
+enum sync_strategy config_get_sync_strategy(void);
- FILE *config_get_roa_output(void);
++bool config_get_shuffle_tal_uris(void);
+unsigned int config_get_max_cert_depth(void);
+bool config_get_color_output(void);
+enum filename_format config_get_filename_format(void);
- /* Needed public by the TOML module */
+char *config_get_rsync_program(void);
+struct string_array const *config_get_rsync_args(bool);
+
- void get_group_fields(struct group_fields const **);
++/* Needed public by the JSON module */
+void *get_rpki_config_field(struct option_field const *);
++struct option_field const *get_option_metadatas(void);
+
+#endif /* SRC_CONFIG_H_ */
--- /dev/null
- print_bool(struct group_fields const *group, struct option_field const *field,
- void *_value)
+#include "config/boolean.h"
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <string.h>
+#include "log.h"
+
++#define DEREFERENCE(void_value) (*((bool *) void_value))
++
+static void
- bool *value = _value;
- pr_info("%s.%s: %s", group->name, field->name,
- (*value) ? "true" : "false");
++print_bool(struct option_field const *field, void *value)
+{
- bool *value = result;
-
++ pr_info("%s: %s", field->name, DEREFERENCE(value) ? "true" : "false");
+}
+
+static int
+parse_argv_bool(struct option_field const *field, char const *str, void *result)
+{
- *value = true;
+ if (str == NULL) {
- *value = true;
++ DEREFERENCE(result) = true;
+ return 0;
+ }
+
+ if (strcmp(str, "true") == 0) {
- *value = false;
++ DEREFERENCE(result) = true;
+ return 0;
+ }
+
+ if (strcmp(str, "false") == 0) {
- parse_toml_bool(struct option_field const *opt, struct toml_table_t *toml,
- void *_result)
++ DEREFERENCE(result) = false;
+ return 0;
+ }
+
+ return pr_err("Cannot parse '%s' as a bool (true|false).", str);
+}
+
+static int
- const char *raw;
- int value;
- bool *result;
-
- raw = toml_raw_in(toml, opt->name);
- if (raw == NULL)
- return 0;
- if (toml_rtob(raw, &value) == -1)
- return pr_err("Cannot parse '%s' as a boolean.", raw);
++parse_json_bool(struct option_field const *opt, struct json_t *json,
++ void *result)
+{
- result = _result;
- *result = value;
++ if (!json_is_boolean(json)) {
++ return pr_err("The '%s' element is not a JSON boolean.",
++ opt->name);
++ }
+
- .parse.toml = parse_toml_bool,
++ DEREFERENCE(result) = json_boolean_value(json);
+ return 0;
+}
+
+const struct global_type gt_bool = {
+ .has_arg = no_argument,
+ .size = sizeof(bool),
+ .print = print_bool,
+ .parse.argv = parse_argv_bool,
++ .parse.json = parse_json_bool,
+ .arg_doc = "true|false",
+};
--- /dev/null
- print_filename_format(struct group_fields const *group,
- struct option_field const *field, void *value)
+#include "config/filename_format.h"
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "config/str.h"
+
+#define FNF_VALUE_GLOBAL "global-url"
+#define FNF_VALUE_LOCAL "local-path"
+#define FNF_VALUE_NAME "file-name"
+
++#define DEREFERENCE(void_value) (*((enum filename_format *) void_value))
++
+static void
- enum filename_format *format = value;
++print_filename_format(struct option_field const *field, void *value)
+{
- switch (*format) {
+ char const *str = "<unknown>";
+
- pr_info("%s.%s: %s", group->name, field->name, str);
++ switch (DEREFERENCE(value)) {
+ case FNF_GLOBAL:
+ str = FNF_VALUE_GLOBAL;
+ break;
+ case FNF_LOCAL:
+ str = FNF_VALUE_LOCAL;
+ break;
+ case FNF_NAME:
+ str = FNF_VALUE_NAME;
+ break;
+ }
+
- void *_result)
++ pr_info("%s: %s", field->name, str);
+}
+
+static int
+parse_argv_filename_format(struct option_field const *field, char const *str,
- enum filename_format *result = _result;
-
++ void *result)
+{
- *result = FNF_GLOBAL;
+ if (strcmp(str, FNF_VALUE_GLOBAL) == 0)
- *result = FNF_LOCAL;
++ DEREFERENCE(result) = FNF_GLOBAL;
+ else if (strcmp(str, FNF_VALUE_LOCAL) == 0)
- *result = FNF_NAME;
++ DEREFERENCE(result) = FNF_LOCAL;
+ else if (strcmp(str, FNF_VALUE_NAME) == 0)
- parse_toml_filename_format(struct option_field const *opt,
- struct toml_table_t *toml, void *_result)
++ DEREFERENCE(result) = FNF_NAME;
+ else
+ return pr_err("Unknown file name format: '%s'", str);
+
+ return 0;
+}
+
+static int
- char *string;
++parse_json_filename_format(struct option_field const *opt, json_t *json,
++ void *result)
+{
- error = parse_toml_string(toml, opt->name, &string);
- if (error)
- return error;
- if (string == NULL)
- return 0;
-
- error = parse_argv_filename_format(opt, string, _result);
-
- free(string);
- return error;
++ char const *string;
+ int error;
+
- .parse.toml = parse_toml_filename_format,
++ error = parse_json_string(json, opt->name, &string);
++ return error ? error : parse_argv_filename_format(opt, string, result);
+}
+
+const struct global_type gt_filename_format = {
+ .has_arg = required_argument,
+ .size = sizeof(enum filename_format),
+ .print = print_filename_format,
+ .parse.argv = parse_argv_filename_format,
++ .parse.json = parse_json_filename_format,
+ .arg_doc = FNF_VALUE_GLOBAL "|" FNF_VALUE_LOCAL "|" FNF_VALUE_NAME,
+};
--- /dev/null
- string_print(struct group_fields const *group, struct option_field const *field,
- void *value)
+#include "config/str.h"
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include "log.h"
+
++#define DEREFERENCE(void_value) (*((char **) void_value))
++
+static void
+__string_free(char **string)
+{
+ free(*string);
+ *string = NULL;
+}
+
+static void
- pr_info("%s.%s: %s", group->name, field->name, *((char **) value));
++string_print(struct option_field const *field, void *value)
+{
- void *_result)
++ pr_info("%s: %s", field->name, DEREFERENCE(value));
+}
+
+static int
+string_parse_argv(struct option_field const *field, char const *str,
- char **result = _result;
-
++ void *result)
+{
- /* tomlc99 frees @str early, so work with a copy. */
- *result = strdup(str);
- return ((*result) != NULL) ? 0 : pr_enomem();
+ if (field->type->has_arg != required_argument || str == NULL) {
+ return pr_err("String options ('%s' in this case) require an argument.",
+ field->name);
+ }
+
+ /* Remove the previous value (usually the default). */
+ __string_free(result);
+
- string_parse_toml(struct option_field const *opt, struct toml_table_t *toml,
- void *_result)
++ DEREFERENCE(result) = strdup(str);
++ return (DEREFERENCE(result) != NULL) ? 0 : pr_enomem();
+}
+
+static int
- char *tmp;
- char **result;
++string_parse_json(struct option_field const *opt, json_t *json, void *result)
+{
- error = parse_toml_string(toml, opt->name, &tmp);
- if (error)
- return error;
- if (tmp == NULL)
- return 0;
-
- result = _result;
- __string_free(result);
- *result = tmp;
- return 0;
++ char const *string;
+ int error;
+
- .parse.toml = string_parse_toml,
++ error = parse_json_string(json, opt->name, &string);
++ return error ? error : string_parse_argv(opt, string, result);
+}
+
+static void
+string_free(void *string)
+{
+ __string_free(string);
+}
+
+const struct global_type gt_string = {
+ .has_arg = required_argument,
+ .size = sizeof(char *),
+ .print = string_print,
+ .parse.argv = string_parse_argv,
- parse_toml_string(struct toml_table_t *toml, char const *name, char **result)
++ .parse.json = string_parse_json,
+ .free = string_free,
+ .arg_doc = "<string>",
+};
+
++/**
++ * *result must not be freed nor long-term stored.
++ */
+int
- const char *raw;
- char *value;
-
- raw = toml_raw_in(toml, name);
- if (raw == NULL) {
- *result = NULL;
- return 0;
- }
- if (toml_rtos(raw, &value) == -1)
- return pr_err("Cannot parse '%s' as a string.", raw);
++parse_json_string(json_t *json, char const *name, char const **result)
+{
- *result = value;
++ if (!json_is_string(json))
++ return pr_err("The '%s' element is not a JSON string.", name);
+
++ *result = json_string_value(json);
+ return 0;
+}
--- /dev/null
- int parse_toml_string(struct toml_table_t *, char const *, char **);
+#ifndef SRC_CONFIG_STR_H_
+#define SRC_CONFIG_STR_H_
+
+#include "config/types.h"
+
+extern const struct global_type gt_string;
+
++int parse_json_string(json_t *, char const *, char const **);
+
+#endif /* SRC_CONFIG_STR_H_ */
--- /dev/null
- string_array_print(struct group_fields const *group,
- struct option_field const *field, void *_value)
+#include "config/string_array.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
++
+#include "log.h"
++#include "config/str.h"
+
+int
+string_array_init(struct string_array *array, char const *const *values,
+ size_t len)
+{
+ size_t i;
+
+ array->length = len;
+
+ array->array = calloc(len, sizeof(char *));
+ if (array->array == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < len; i++) {
+ array->array[i] = strdup(values[i]);
+ if (array->array[i] == NULL) {
+ string_array_cleanup(array);
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+void
+string_array_cleanup(struct string_array *array)
+{
+ size_t i;
+ for (i = 0; i < array->length; i++)
+ free(array->array[i]);
+ free(array->array);
+}
+
+static void
+__string_array_free(struct string_array *array)
+{
+ string_array_cleanup(array);
+ array->array = NULL;
+ array->length = 0;
+}
+
+static void
- pr_info("%s.%s:", group->name, field->name);
++string_array_print(struct option_field const *field, void *_value)
+{
+ struct string_array *value = _value;
+ size_t i;
+
- string_array_parse_toml(struct option_field const *opt,
- struct toml_table_t *toml, void *_result)
++ pr_info("%s:", field->name);
+ pr_indent_add();
+
+ if (value->length == 0)
+ pr_info("<Nothing>");
+ else for (i = 0; i < value->length; i++)
+ pr_info("%s", value->array[i]);
+
+ pr_indent_rm();
+}
+
+static int
- toml_array_t *array;
- int array_len;
- int i;
- const char *raw;
- struct string_array *result = _result;
++string_array_parse_json(struct option_field const *opt, json_t *json,
++ void *_result)
+{
- array = toml_array_in(toml, opt->name);
- if (array == NULL)
++ struct string_array *result;
++ json_t *child;
++ size_t i, len;
++ char const *tmp;
+ int error;
+
- array_len = toml_array_nelem(array);
++ if (!json_is_array(json)) {
++ return pr_err("The '%s' element is not a JSON array.",
++ opt->name);
++ }
++
++ len = json_array_size(json);
++ if (len == 0) {
++ __string_array_free(_result);
+ return 0;
- result->array = malloc(array_len * sizeof(char *));
++ }
++
++ for (i = 0; i < len; i++) {
++ child = json_array_get(json, i);
++ if (!json_is_string(child)) {
++ return pr_err("'%s' array element #%zu is not a string.",
++ opt->name, i);
++ }
++ }
++
++ result = _result;
+
+ /* Remove the previous value (usually the default). */
+ __string_array_free(result);
+
- result->length = array_len;
++ result->array = calloc(len, sizeof(char *));
+ if (result->array == NULL)
+ return pr_enomem();
- for (i = 0; i < array_len; i++) {
- raw = toml_raw_at(array, i);
- if (raw == NULL) {
- error = pr_crit("Array index %d is NULL.", i);
++ result->length = len;
+
- }
- if (toml_rtos(raw, &result->array[i]) == -1) {
- error = pr_err("Cannot parse '%s' as a string.", raw);
++ for (i = 0; i < len; i++) {
++ error = parse_json_string(json_array_get(json, i),
++ "array element", &tmp);
++ if (error)
+ goto fail;
- free(result->array);
- result->length = 0;
++
++ result->array[i] = strdup(tmp);
++ if (result->array[i] == NULL) {
++ error = pr_enomem();
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
- .parse.toml = string_array_parse_toml,
++ __string_array_free(result);
+ return error;
+}
+
+static void
+string_array_free(void *array)
+{
+ __string_array_free(array);
+}
+
+const struct global_type gt_string_array = {
+ .has_arg = required_argument,
+ .size = sizeof(char *const *),
+ .print = string_array_print,
++ .parse.json = string_array_parse_json,
+ .free = string_array_free,
+ .arg_doc = "<sequence of strings>",
+};
--- /dev/null
+#ifndef SRC_CONFIG_STRING_ARRAY_H_
+#define SRC_CONFIG_STRING_ARRAY_H_
+
+#include <stddef.h>
+#include "config/types.h"
+
+struct string_array {
++ /*
++ * BTW: The array size can be zero, in which case this will be NULL.
++ * TODO Remember to handle properly.
++ */
+ char **array;
+ size_t length;
+};
+
+extern const struct global_type gt_string_array;
+
+int string_array_init(struct string_array *, char const *const *, size_t);
+void string_array_cleanup(struct string_array *);
+
+#endif /* SRC_CONFIG_STRING_ARRAY_H_ */
--- /dev/null
- print_sync_strategy(struct group_fields const *group,
- struct option_field const *field, void *value)
+#include "config/sync_strategy.h"
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "config/str.h"
+
+#define SYNC_VALUE_OFF "off"
+#define SYNC_VALUE_STRICT "strict"
+#define SYNC_VALUE_ROOT "root"
+#define SYNC_VALUE_ROOT_EXCEPT_TA "root-except-ta"
+
++#define DEREFERENCE(void_value) (*((enum sync_strategy *) void_value))
++
+static void
- enum sync_strategy *strategy = value;
++print_sync_strategy(struct option_field const *field, void *value)
+{
- switch (*strategy) {
+ char const *str = "<unknown>";
+
- pr_info("%s.%s: %s", group->name, field->name, str);
++ switch (DEREFERENCE(value)) {
+ case SYNC_OFF:
+ str = SYNC_VALUE_OFF;
+ break;
+ case SYNC_STRICT:
+ str = SYNC_VALUE_STRICT;
+ break;
+ case SYNC_ROOT:
+ str = SYNC_VALUE_ROOT;
+ break;
+ case SYNC_ROOT_EXCEPT_TA:
+ str = SYNC_VALUE_ROOT_EXCEPT_TA;
+ break;
+ }
+
- void *_result)
++ pr_info("%s: %s", field->name, str);
+}
+
+static int
+parse_argv_sync_strategy(struct option_field const *field, char const *str,
- enum sync_strategy *result = _result;
-
++ void *result)
+{
- *result = SYNC_OFF;
+ if (strcmp(str, SYNC_VALUE_OFF) == 0)
- *result = SYNC_STRICT;
++ DEREFERENCE(result) = SYNC_OFF;
+ else if (strcmp(str, SYNC_VALUE_STRICT) == 0)
- *result = SYNC_ROOT;
++ DEREFERENCE(result) = SYNC_STRICT;
+ else if (strcmp(str, SYNC_VALUE_ROOT) == 0)
- *result = SYNC_ROOT_EXCEPT_TA;
++ DEREFERENCE(result) = SYNC_ROOT;
+ else if (strcmp(str, SYNC_VALUE_ROOT_EXCEPT_TA) == 0)
- parse_toml_sync_strategy(struct option_field const *opt,
- struct toml_table_t *toml, void *_result)
++ DEREFERENCE(result) = SYNC_ROOT_EXCEPT_TA;
+ else
+ return pr_err("Unknown synchronization strategy: '%s'", str);
+
+ return 0;
+}
+
+static int
- char *string;
-
- error = parse_toml_string(toml, opt->name, &string);
- if (error)
- return error;
- if (string == NULL)
- return 0;
-
- error = parse_argv_sync_strategy(opt, string, _result);
++parse_json_sync_strategy(struct option_field const *opt, struct json_t *json,
++ void *result)
+{
++ char const *string;
+ int error;
- free(string);
- return error;
+
- .parse.toml = parse_toml_sync_strategy,
++ error = parse_json_string(json, opt->name, &string);
++ return error ? error : parse_argv_sync_strategy(opt, string, result);
+}
+
+const struct global_type gt_sync_strategy = {
+ .has_arg = required_argument,
+ .size = sizeof(enum sync_strategy),
+ .print = print_sync_strategy,
+ .parse.argv = parse_argv_sync_strategy,
++ .parse.json = parse_json_sync_strategy,
+ .arg_doc = SYNC_VALUE_OFF
+ "|" SYNC_VALUE_STRICT
+ "|" SYNC_VALUE_ROOT
+ "|" SYNC_VALUE_ROOT_EXCEPT_TA,
+};
--- /dev/null
- #include <toml.h>
+#ifndef SRC_CONFIG_TYPES_H_
+#define SRC_CONFIG_TYPES_H_
+
++#include <jansson.h>
+#include <stdint.h>
+#include <stdio.h>
- /** This option can be set from the TOML file. */
- #define AVAILABILITY_TOML (1 << 1)
+
+struct option_field;
+struct group_fields;
+
+/** This option can be set from the command line. */
+#define AVAILABILITY_GETOPT (1 << 0)
- struct group_fields const *,
++/** This option can be set from the JSON file. */
++#define AVAILABILITY_JSON (1 << 1)
+
+typedef void (*print_function)(
- typedef int (*toml_parse_function)(
+ struct option_field const *,
+ void *
+);
+typedef int (*argv_parse_function)(
+ struct option_field const *,
+ char const *,
+ void *
+);
- struct toml_table_t *,
++typedef int (*json_parse_function)(
+ struct option_field const *,
- struct group_fields {
- char const *name;
- struct option_field const *options;
- };
++ struct json_t *,
+ void *
+);
+typedef int (*handler_function)(
+ struct option_field const *,
+ char *
+);
+
+struct option_field {
+ /*
+ * Must be zero, alphanumeric or >= 1000.
+ * If zero, signals the end of the array.
+ * If alphanumeric, it's the short option name character.
+ * Otherwise it's just a non-printable identifier.
+ * Must be unique across all option fields.
+ * Mandatory.
+ */
+ int id;
+ /**
+ * For example, if the option name is '--potato', then @name is
+ * "potato".
+ * Mandatory.
+ */
+ char const *name;
+
+ /** Data type. Mandatory. */
+ struct global_type const *type;
+ /**
+ * Number of bytes between the beginning of the struct rpki_config
+ * and the position where this option is stored.
+ * Only relevant when @handler == NULL.
+ */
+ size_t offset;
+ /** Overrides @type->parser and @offset. Optional. */
+ handler_function handler;
+
+ /**
+ * Explanation of the field, for user consumption during --help.
+ * Meant to be short; the bulk of it should be found in the manpage.
+ * Probably should not include punctuation at the end.
+ * Mandatory.
+ */
+ const char *doc;
+ /** Overrides type->arg_doc. Optional. */
+ char const *arg_doc;
+ /**
+ * AVAILABILITY_* flags above.
+ * Default availability is everywhere.
+ * Optional.
+ */
+ int availability;
+ unsigned int min;
+ unsigned int max;
+};
+
- * Converts from a TOML node to this data type.
- * If the node is not present in the file, this function should
- * do nothing.
++#define FOREACH_OPTION(opts, opt, type) \
++ for (opt = opts; opt->id != 0; opt++) \
++ if ((opt->availability == 0) || (opt->availability & type))
+
+struct global_type {
+ /** Same as struct option.has_arg. Mandatory. */
+ int has_arg;
+ /**
+ * Number of bytes this data type uses in the rpki_config structure.
+ * Optional. Defaults to zero, obviously.
+ */
+ size_t size;
+
+ /**
+ * Prints this data type during the print_config() function.
+ * Optional.
+ */
+ print_function print;
+
+ /** If the option's handler is not NULL, this is optional. */
+ struct {
+ /**
+ * Convers from string to this data type.
+ * Optional if there are no fields of this type that are read
+ * from argv.
+ */
+ argv_parse_function argv;
+ /**
- * from TOML files.
++ * Converts from a JSON node to this data type.
+ * Optional if there are no fields of this type that are read
- toml_parse_function toml;
++ * from JSON files.
+ */
++ json_parse_function json;
+ } parse;
+
+ /**
+ * Function that will release this data type.
+ * If the option's handler is not NULL, this is optional.
+ *
+ * IMPORTANT: This function might be called twice in succession.
+ * Therefore, make sure that it nullifies the value, and reacts properly
+ * when the input is NULL.
+ */
+ void (*free)(void *);
+
+ /**
+ * Descriptor of this type's payload. Printed in usage documentation.
+ * For example, in `--tal=<file>`, @arg_doc is "<file>".
+ * The type might have no payload, so this is optional.
+ */
+ char const *arg_doc;
+};
+
+#endif /* SRC_CONFIG_TYPES_H_ */
--- /dev/null
- print_u_int(struct group_fields const *group, struct option_field const *field,
- void *value)
+#include "config/uint.h"
+
+#include <getopt.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "log.h"
+
+static void
- pr_info("%s.%s: %u", group->name, field->name,
- *((unsigned int *) value));
++print_u_int(struct option_field const *field, void *value)
+{
- static int
++ pr_info("%s: %u", field->name, *((unsigned int *) value));
+}
+
- void *_result)
++int
+parse_argv_u_int(struct option_field const *field, char const *str,
- int *result;
++ void *result)
+{
+ unsigned long parsed;
- result = _result;
- *result = parsed;
+
+ if (field->type->has_arg != required_argument || str == NULL) {
+ return pr_err("Integer options ('%s' in this case) require an argument.",
+ field->name);
+ }
+
+ errno = 0;
+ parsed = strtoul(str, NULL, 10);
+ if (errno)
+ return pr_errno(errno, "'%s' is not an unsigned integer", str);
+
+ if (parsed < field->min || field->max < parsed) {
+ return pr_err("'%lu' is out of bounds (%u-%u).", parsed,
+ field->min, field->max);
+ }
+
- static int
- parse_toml_u_int(struct option_field const *opt, struct toml_table_t *toml,
- void *_result)
++ *((unsigned int *) result) = parsed;
+ return 0;
+}
+
- const char *raw;
- int64_t value;
- unsigned int *result;
++int
++parse_json_u_int(struct option_field const *opt, json_t *json, void *result)
+{
- raw = toml_raw_in(toml, opt->name);
- if (raw == NULL)
- return 0;
- if (toml_rtoi(raw, &value) == -1)
- return pr_err("Cannot parse '%s' as an integer.", raw);
++ json_int_t value;
+
- result = _result;
- *result = value;
++ if (!json_is_integer(json)) {
++ return pr_err("The '%s' element is not a JSON integer.",
++ opt->name);
++ }
++
++ value = json_integer_value(json);
+
+ if (value < opt->min || opt->max < value) {
+ return pr_err("Integer '%s' is out of range (%u-%u).",
+ opt->name, opt->min, opt->max);
+ }
+
- .parse.toml = parse_toml_u_int,
++ *((unsigned int *) result) = value;
+ return 0;
+}
+
+const struct global_type gt_u_int = {
+ .has_arg = required_argument,
+ .size = sizeof(unsigned int),
+ .print = print_u_int,
+ .parse.argv = parse_argv_u_int,
++ .parse.json = parse_json_u_int,
+ .arg_doc = "<unsigned integer>",
+};
--- /dev/null
+#ifndef SRC_CONFIG_UINT_H_
+#define SRC_CONFIG_UINT_H_
+
+#include "config/types.h"
+
+extern const struct global_type gt_u_int;
+
++int parse_argv_u_int(struct option_field const *, char const *, void *);
++int parse_json_u_int(struct option_field const *, struct json_t *, void *);
++
+#endif /* SRC_CONFIG_UINT_H_ */
--- /dev/null
--- /dev/null
++#include "config/uint.h"
++
++#include <getopt.h>
++#include <errno.h>
++#include <stdlib.h>
++
++#include "log.h"
++#include "config/uint.h"
++
++static void
++print_u_int32(struct option_field const *field, void *value)
++{
++ pr_info("%s: %u", field->name, *((u_int32_t *) value));
++}
++
++static int
++parse_argv_u_int32(struct option_field const *field, char const *str,
++ void *result)
++{
++ unsigned int tmp;
++ int error;
++
++ error = parse_argv_u_int(field, str, &tmp);
++ if (error)
++ return error;
++
++ /* Range already validated (from field->min and field->max). */
++ *((u_int32_t *) result) = tmp;
++ return 0;
++}
++
++static int
++parse_json_u_int32(struct option_field const *opt, json_t *json, void *result)
++{
++ unsigned int tmp;
++ int error;
++
++ error = parse_json_u_int(opt, json, &tmp);
++ if (error)
++ return error;
++
++ /* Range already validated (from opt->min and opt->max). */
++ *((u_int32_t *) result) = tmp;
++ return 0;
++}
++
++const struct global_type gt_u_int32 = {
++ .has_arg = required_argument,
++ .size = sizeof(u_int32_t),
++ .print = print_u_int32,
++ .parse.argv = parse_argv_u_int32,
++ .parse.json = parse_json_u_int32,
++ .arg_doc = "<32-bit unsigned integer>",
++};
--- /dev/null
--- /dev/null
++#ifndef SRC_CONFIG_UINT32_H_
++#define SRC_CONFIG_UINT32_H_
++
++#include "config/types.h"
++
++extern const struct global_type gt_u_int32;
++
++#endif /* SRC_CONFIG_UINT32_H_ */
--- /dev/null
--- /dev/null
++#include "json_handler.h"
++
++#include <errno.h>
++#include <string.h>
++
++#include "config.h"
++#include "log.h"
++#include "config/types.h"
++
++int
++find_json(struct json_t *root, char const *full_name, json_t **result)
++{
++ struct {
++ char *opt_name; /* full token sequence string */
++ char *token; /* current token */
++ char *saveptr; /* state needed by strtok_r */
++ } strtok;
++ struct json_t *node;
++
++ /* strtok_r() needs a non-const string */
++ strtok.opt_name = strdup(full_name);
++ if (strtok.opt_name == NULL)
++ return pr_enomem();
++
++ node = root;
++ strtok.token = strtok_r(strtok.opt_name, ".", &strtok.saveptr);
++
++ while (node != NULL && strtok.token != NULL) {
++ node = json_object_get(node, strtok.token);
++ strtok.token = strtok_r(NULL, ".", &strtok.saveptr);
++ }
++
++ free(strtok.opt_name);
++ *result = node;
++ return 0;
++}
++
++static int
++json_to_config(struct json_t *root)
++{
++ struct option_field const *opt;
++ struct json_t *child;
++ int error;
++
++ FOREACH_OPTION(get_option_metadatas(), opt, AVAILABILITY_JSON) {
++ error = find_json(root, opt->name, &child);
++ if (error)
++ return error;
++ if (child == NULL)
++ continue;
++
++ error = opt->type->parse.json(opt, child,
++ get_rpki_config_field(opt));
++ if (error)
++ return error;
++ }
++
++ return 0;
++}
++
++int
++set_config_from_file(char *file)
++{
++ json_t *root;
++ json_error_t json_error;
++ int error;
++
++ root = json_load_file(file, JSON_REJECT_DUPLICATES, &json_error);
++ if (root == NULL) {
++ pr_err("JSON error on line %d, column %d: %s",
++ json_error.line, json_error.column, json_error.text);
++ return -ENOENT;
++ }
++
++ error = json_to_config(root);
++
++ json_decref(root);
++ return error;
++}
--- /dev/null
--- /dev/null
++#ifndef SRC_JSON_HANDLER_H_
++#define SRC_JSON_HANDLER_H_
++
++int set_config_from_file(char *);
++
++#endif /* SRC_JSON_HANDLER_H_ */
- #include <err.h>
--#include <errno.h>
- #include <getopt.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
--
- #include "common.h"
-#include "rtr/rtr.h"
+ #include "clients.h"
-#include "configuration.h"
-#include "csv.h"
+#include "config.h"
+#include "debug.h"
+#include "extension.h"
- #include "log.h"
+#include "nid.h"
- #include "rpp.h"
+#include "thread_var.h"
- #include "object/certificate.h"
- #include "object/manifest.h"
- #include "object/tal.h"
+ #include "vrps.h"
+#include "rsync/rsync.h"
++#include "rtr/rtr.h"
- /**
- * Performs the whole validation walkthrough on uri @uri, which is assumed to
- * have been extracted from a TAL.
-/*
- * This program is an RTR server.
- *
- * RTR ("RPKI-to-Router") is a protocol (defined in RFCs 6810 and 8210) that
- * reports the work of an RPKI validator (cryptographcally-verified
- * attestations that define the ASN that owns a given routing prefix). It is
- * normally served to routers who wish to verify BGP claims.
-- */
-int
-main(int argc, char *argv[])
+static int
- handle_tal_uri(struct tal *tal, struct rpki_uri const *uri)
++start_rtr_server(void)
{
- /*
- * Because of the way the foreach iterates, this function must return
- *
- * - 0 on soft errors.
- * - `> 0` on URI handled successfully.
- * - `< 0` on hard errors.
- *
- * A "soft error" is "the connection to the preferred URI fails, or the
- * retrieved CA certificate public key does not match the TAL public
- * key." (RFC 7730)
- *
- * A "hard error" is any other error.
- */
-
- struct validation *state;
- int err;
- char *json_file = NULL;
- int c;
+ int error;
- error = download_files(uri, true);
- if (error) {
- return pr_warn("TAL URI '%s' could not be RSYNC'd.",
- uri->global);
- while ((c = getopt(argc, argv, "f:")) != -1) {
- switch (c) {
- case 'f':
- json_file = optarg;
- break;
- case '?':
- fprintf(stdout, "usage: %s -f <file name>\n", argv[0]);
- return 0;
- }
-- }
-
- error = validation_prepare(&state, tal);
++ error = deltas_db_init();
+ if (error)
- return -abs(error);
-
- pr_debug_add("TAL URI '%s' {", uri_get_printable(uri));
++ goto end1;
- if (!uri_is_certificate(uri)) {
- pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')",
- uri_get_printable(uri));
- error = -EINVAL;
- goto end;
- if (json_file == NULL) {
- fprintf(stderr, "Missing flag '-f <file name>'\n");
- return -EINVAL;
-- }
++ error = clients_db_init();
++ if (error)
++ goto end2;
- error = certificate_traverse(NULL, uri, NULL, true);
- if (error) {
- switch (validation_pubkey_state(state)) {
- case PKS_INVALID:
- error = 0;
- break;
- case PKS_VALID:
- case PKS_UNTESTED:
- error = -abs(error);
- break;
- }
- } else {
- error = 1;
- err = config_init(json_file);
- if (err) {
- /*
- * TODO Special scenario, if the VRPs location doesn't exists
- * just send a warning (logged by the config_init function).
- *
- * This should be fixed later.
- */
- err = (err == -ENOENT ? 0 : err);
- goto end1;
-- }
++ error = rtr_listen();
++ rtr_cleanup(); /* TODO shouldn't this only happen on !error? */
- end:
- validation_destroy(state);
- pr_debug_rm("}");
- return error;
- err = deltas_db_init();
- if (err)
- goto end1;
++ clients_db_destroy();
++end2: deltas_db_destroy();
++end1: return error;
+}
- err = clients_db_init();
- if (err)
- goto end2;
+int
+main(int argc, char **argv)
+{
- struct tal *tal;
+ int error;
- err = csv_parse_vrps_file();
- if (err)
- goto end3;
+ print_stack_trace_on_segfault();
- thvar_init();
- fnstack_init();
- err = rtr_listen();
++ error = thvar_init();
++ if (error)
++ return error;
- rtr_cleanup();
-end3:
- clients_db_destroy();
-end2:
- deltas_db_destroy();
-end1:
- config_cleanup();
- return err;
+ error = handle_flags_config(argc, argv);
+ if (error)
+ return error;
+
+ error = rsync_init();
+ if (error)
- goto end1;
-
++ goto revert_config;
+ error = nid_init();
+ if (error)
- goto end2;
++ goto revert_rsync;
+ error = extension_init();
+ if (error)
- goto end2;
- fnstack_push(config_get_tal());
++ goto revert_rsync;
+
- error = tal_load(config_get_tal(), &tal);
- if (!error) {
- if (config_get_shuffle_uris())
- tal_shuffle_uris(tal);
-
- error = foreach_uri(tal, handle_tal_uri);
- if (error > 0)
- error = 0;
- else if (error == 0)
- error = pr_err("None of the URIs of the TAL yielded a successful traversal.");
++ error = perform_standalone_validation(NULL);
++ if (error)
++ goto revert_rsync;
+
- tal_destroy(tal);
- }
++ if (config_get_server_address() != NULL)
++ error = start_rtr_server();
++ /* Otherwise, no server requested. */
+
- end2:
++revert_rsync:
+ rsync_destroy();
- end1:
++revert_config:
+ free_rpki_config();
- fnstack_cleanup();
+ return error;
}
--- /dev/null
- handle_ghostbusters(struct rpki_uri const *uri, struct rpp *pp,
+#include "object/ghostbusters.h"
+
+#include "log.h"
+#include "thread_var.h"
+#include "asn1/oid.h"
+#include "asn1/signed_data.h"
+#include "object/signed_object.h"
+#include "vcard.h"
+
+static int
+handle_vcard(OCTET_STRING_t *vcard, void *arg)
+{
+ return handle_ghostbusters_vcard(vcard);
+}
+
+int
++ghostbusters_traverse(struct rpki_uri const *uri, struct rpp *pp,
+ STACK_OF(X509_CRL) *crls)
+{
+ static OID oid = OID_GHOSTBUSTERS;
+ struct oid_arcs arcs = OID2ARCS("ghostbusters", oid);
+ struct signed_object_args sobj_args;
+ int error;
+
+ pr_debug_add("Ghostbusters '%s' {", uri->global);
+ fnstack_push_uri(uri);
+
+ error = signed_object_args_init(&sobj_args, uri, crls, true);
+ if (error)
+ goto end1;
+
+ error = signed_object_decode(&sobj_args, &arcs, handle_vcard, NULL);
+ if (error)
+ goto end2;
+
+ error = refs_validate_ee(&sobj_args.refs, pp, sobj_args.uri);
+
+end2:
+ signed_object_args_cleanup(&sobj_args);
+end1:
+ pr_debug_rm("}");
+ fnstack_pop();
+ return error;
+}
--- /dev/null
- int handle_ghostbusters(struct rpki_uri const *, struct rpp *,
+#ifndef SRC_OBJECT_GHOSTBUSTERS_H_
+#define SRC_OBJECT_GHOSTBUSTERS_H_
+
+#include <openssl/x509.h>
+#include "uri.h"
+#include "rpp.h"
+
++int ghostbusters_traverse(struct rpki_uri const *, struct rpp *,
+ STACK_OF(X509_CRL) *);
+
+#endif /* SRC_OBJECT_GHOSTBUSTERS_H_ */
--- /dev/null
- char str[INET_ADDRSTRLEN];
- const char *str2;
+#include "object/roa.h"
+
+#include <errno.h>
+#include <arpa/inet.h>
+#include <libcmscodec/RouteOriginAttestation.h>
+
+#include "config.h"
+#include "log.h"
+#include "thread_var.h"
+#include "asn1/decode.h"
+#include "asn1/oid.h"
+#include "object/signed_object.h"
+
+#include <sys/socket.h>
+
+static int
+roa_decode(OCTET_STRING_t *string, void *arg)
+{
+ return asn1_decode_octet_string(string, &asn_DEF_RouteOriginAttestation,
+ arg);
+}
+
+static int
+print_addr4(struct resources *parent, unsigned long asn,
+ struct ROAIPAddress *roa_addr)
+{
+ struct ipv4_prefix prefix;
+ unsigned long max_length;
- str2 = inet_ntop(AF_INET, &prefix.addr, str, sizeof(str));
- if (str2 == NULL)
- return pr_err("inet_ntop() returned NULL.");
-
+ int error;
+
+ error = prefix4_decode(&roa_addr->address, &prefix);
+ if (error)
+ return error;
+
+ if (roa_addr->maxLength != NULL) {
+ error = asn_INTEGER2ulong(roa_addr->maxLength, &max_length);
+ if (error) {
+ if (errno)
+ pr_errno(errno, "Error casting ROA's IPv4 maxLength");
+ return pr_err("The ROA's IPv4 maxLength isn't a valid unsigned long");
+ }
+
+ if (max_length > 32) {
+ return pr_err("maxLength (%lu) is out of bounds (0-32).",
+ max_length);
+ }
+
+ if (prefix.len > max_length) {
+ return pr_err("Prefix length (%u) > maxLength (%lu)",
+ prefix.len, max_length);
+ }
+
+ } else {
+ max_length = prefix.len;
+ }
+
- return pr_err("ROA is not allowed to advertise %s/%u.", str2,
- prefix.len);
+ if (!resources_contains_ipv4(parent, &prefix)) {
- fprintf(config_get_roa_output(), "AS%lu,%s/%u,%lu\n", asn, str2,
- prefix.len, max_length);
- return 0;
++ return pr_err("ROA is not allowed to advertise %s/%u.",
++ v4addr2str(&prefix.addr), prefix.len);
+ }
+
- char str[INET6_ADDRSTRLEN];
- const char *str2;
++ /* TODO I think we're not validating asn boundaries */
++ return roa_handle_v4(asn, &prefix, max_length);
++}
++
++int
++roa_handle_v4(u_int32_t asn, struct ipv4_prefix *prefix, u_int8_t max_length)
++{
++ return -ENOTIMPLEMENTED;
+}
+
+static int
+print_addr6(struct resources *parent, unsigned long asn,
+ struct ROAIPAddress *roa_addr)
+{
+ struct ipv6_prefix prefix;
+ unsigned long max_length;
- str2 = inet_ntop(AF_INET6, &prefix.addr, str, sizeof(str));
- if (str2 == NULL)
- return pr_err("inet_ntop() returned NULL.");
-
+ int error;
+
+ error = prefix6_decode(&roa_addr->address, &prefix);
+ if (error)
+ return error;
+
+ if (roa_addr->maxLength != NULL) {
+ error = asn_INTEGER2ulong(roa_addr->maxLength, &max_length);
+ if (error) {
+ if (errno)
+ pr_errno(errno, "Error casting ROA's IPv6 maxLength");
+ return pr_err("The ROA's IPv6 maxLength isn't a valid unsigned long");
+ }
+
+ if (max_length > 128) {
+ return pr_err("maxLength (%lu) is out of bounds (0-128).",
+ max_length);
+ }
+
+ if (prefix.len > max_length) {
+ return pr_err("Prefix length (%u) > maxLength (%lu)",
+ prefix.len, max_length);
+ }
+
+ } else {
+ max_length = prefix.len;
+ }
+
- return pr_err("ROA is not allowed to advertise %s/%u.", str2,
- prefix.len);
+ if (!resources_contains_ipv6(parent, &prefix)) {
- fprintf(config_get_roa_output(), "AS%lu,%s/%u,%lu\n", asn, str2,
- prefix.len, max_length);
- return 0;
++ return pr_err("ROA is not allowed to advertise %s/%u.",
++ v6addr2str(&prefix.addr), prefix.len);
+ }
+
- handle_roa(struct rpki_uri const *uri, struct rpp *pp,
++ return roa_handle_v6(asn, &prefix, max_length);
++}
++
++int
++roa_handle_v6(u_int32_t asn, struct ipv6_prefix *prefix, u_int8_t max_length)
++{
++ return -ENOTIMPLEMENTED;
+}
+
+static int
+print_addr(struct resources *parent, ASID_t *as_id, uint8_t family,
+ struct ROAIPAddress *roa_addr)
+{
+ unsigned long asn;
+
+ if (asn_INTEGER2ulong(as_id, &asn) != 0) {
+ if (errno)
+ pr_errno(errno, "Error casting ROA's AS ID value");
+ return pr_err("ROA's AS ID couldn't be parsed as unsigned long");
+ }
+
+ switch (family) {
+ case 1: /* IPv4 */
+ return print_addr4(parent, asn, roa_addr);
+ case 2: /* IPv6 */
+ return print_addr6(parent, asn, roa_addr);
+ }
+
+ return pr_err("Unknown family value: %u", family);
+}
+
+static int
+__handle_roa(struct RouteOriginAttestation *roa, struct resources *parent)
+{
+ struct ROAIPAddressFamily *block;
+ unsigned long version;
+ int b;
+ int a;
+ int error;
+
+ if (roa->version != NULL) {
+ error = asn_INTEGER2ulong(roa->version, &version);
+ if (error) {
+ if (errno)
+ pr_errno(errno, "Error casting ROA's version");
+ return pr_err("The ROA's version isn't a valid long");
+ }
+ /* rfc6482#section-3.1 */
+ if (version != 0)
+ return pr_err("ROA's version (%lu) is nonzero.", version);
+ }
+
+ /* rfc6482#section-3.3 */
+
+ if (roa->ipAddrBlocks.list.array == NULL)
+ return pr_crit("ipAddrBlocks array is NULL.");
+
+ for (b = 0; b < roa->ipAddrBlocks.list.count; b++) {
+ block = roa->ipAddrBlocks.list.array[b];
+ if (block == NULL)
+ return pr_err("Address block array element is NULL.");
+
+ if (block->addressFamily.size != 2)
+ goto family_error;
+ if (block->addressFamily.buf[0] != 0)
+ goto family_error;
+ if (block->addressFamily.buf[1] != 1
+ && block->addressFamily.buf[1] != 2)
+ goto family_error;
+
+ if (block->addresses.list.array == NULL)
+ return pr_err("ROA's address list array is NULL.");
+ for (a = 0; a < block->addresses.list.count; a++) {
+ error = print_addr(parent, &roa->asID,
+ block->addressFamily.buf[1],
+ block->addresses.list.array[a]);
+ if (error)
+ return error;
+ }
+ }
+
+ return 0;
+
+family_error:
+ return pr_err("ROA's IP family is not v4 or v6.");
+}
+
+int
++roa_traverse(struct rpki_uri const *uri, struct rpp *pp,
+ STACK_OF(X509_CRL) *crls)
+{
+ static OID oid = OID_ROA;
+ struct oid_arcs arcs = OID2ARCS("roa", oid);
+ struct signed_object_args sobj_args;
+ struct RouteOriginAttestation *roa;
+ int error;
+
+ pr_debug_add("ROA '%s' {", uri_get_printable(uri));
+ fnstack_push_uri(uri);
+
+ error = signed_object_args_init(&sobj_args, uri, crls, false);
+ if (error)
+ goto end1;
+
+ error = signed_object_decode(&sobj_args, &arcs, roa_decode, &roa);
+ if (error)
+ goto end2;
+
+ error = __handle_roa(roa, sobj_args.res);
+ if (error)
+ goto end3;
+
+ error = refs_validate_ee(&sobj_args.refs, pp, sobj_args.uri);
+
+end3:
+ ASN_STRUCT_FREE(asn_DEF_RouteOriginAttestation, roa);
+end2:
+ signed_object_args_cleanup(&sobj_args);
+end1:
+ pr_debug_rm("}");
+ fnstack_pop();
+ return error;
+}
--- /dev/null
- int handle_roa(struct rpki_uri const *, struct rpp *, STACK_OF(X509_CRL) *);
+#ifndef SRC_OBJECT_ROA_H_
+#define SRC_OBJECT_ROA_H_
+
+#include <openssl/x509.h>
++
++#include "address.h"
+#include "rpp.h"
+#include "uri.h"
+
++int roa_traverse(struct rpki_uri const *, struct rpp *, STACK_OF(X509_CRL) *);
++
++int roa_handle_v4(u_int32_t, struct ipv4_prefix *, u_int8_t);
++int roa_handle_v6(u_int32_t, struct ipv6_prefix *, u_int8_t);
+
+#endif /* SRC_OBJECT_ROA_H_ */
--- /dev/null
+#define _GNU_SOURCE
+
+#include "tal.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <openssl/evp.h>
+
+#include "common.h"
++#include "config.h"
+#include "line_file.h"
+#include "log.h"
+#include "random.h"
++#include "state.h"
++#include "thread_var.h"
+#include "crypto/base64.h"
++#include "object/certificate.h"
++#include "rsync/rsync.h"
+
+struct uris {
+ char **array; /* This is an array of string pointers. */
+ unsigned int count;
+ unsigned int size;
+};
+
+struct tal {
+ char const *file_name;
+ struct uris uris;
+ unsigned char *spki; /* Decoded; not base64. */
+ size_t spki_len;
+};
+
+static int
+uris_init(struct uris *uris)
+{
+ uris->count = 0;
+ uris->size = 4; /* Most TALs only define one. */
+ uris->array = malloc(uris->size * sizeof(char *));
+ return (uris->array != NULL) ? 0 : -ENOMEM;
+}
+
+static void
+uris_destroy(struct uris *uris)
+{
+ unsigned int i;
+ for (i = 0; i < uris->count; i++)
+ free(uris->array[i]);
+ free(uris->array);
+}
+
+static int
+uris_add(struct uris *uris, char *uri)
+{
+ char **tmp;
+
+ if (uris->count + 1 >= uris->size) {
+ uris->size *= 2;
+ tmp = realloc(uris->array, uris->size * sizeof(char *));
+ if (tmp == NULL)
+ return pr_enomem();
+ uris->array = tmp;
+ }
+
+ uris->array[uris->count++] = uri;
+ return 0;
+}
+
+static int
+read_uris(struct line_file *lfile, struct uris *uris)
+{
+ char *uri;
+ int error;
+
+ error = lfile_read(lfile, &uri);
+ if (error)
+ return error;
+
+ if (uri == NULL)
+ return pr_err("TAL file is empty.");
+ if (strcmp(uri, "") == 0) {
+ free(uri);
+ return pr_err("There's no URI in the first line of the TAL.");
+ }
+
+ error = uris_add(uris, uri);
+ if (error)
+ return error;
+
+ do {
+ error = lfile_read(lfile, &uri);
+ if (error)
+ return error;
+
+ if (uri == NULL)
+ return pr_err("TAL file ended prematurely. (Expected URI list, blank line and public key.)");
+ if (strcmp(uri, "") == 0) {
+ free(uri);
+ return 0; /* Happy path */
+ }
+
+ error = uris_add(uris, uri);
+ if (error)
+ return error;
+ } while (true);
+}
+
+/*
+ * Will usually allocate slightly more because of the newlines, but I'm fine
+ * with it.
+ */
+static size_t
+get_spki_alloc_size(struct line_file *lfile)
+{
+ struct stat st;
+ size_t result;
+
+ stat(lfile_name(lfile), &st);
+ result = st.st_size - lfile_offset(lfile);
+
+ return EVP_DECODE_LENGTH(result);
+}
+
+static int
+read_spki(struct line_file *lfile, struct tal *tal)
+{
+ BIO *encoded; /* base64 encoded. */
+ size_t alloc_size;
+ int error;
+
+ alloc_size = get_spki_alloc_size(lfile);
+ tal->spki = malloc(alloc_size);
+ if (tal->spki == NULL)
+ return -ENOMEM;
+
+ encoded = BIO_new_fp(lfile_fd(lfile), BIO_NOCLOSE);
+ if (encoded == NULL) {
+ free(tal->spki);
+ return crypto_err("BIO_new_fp() returned NULL");
+ }
+
+ error = base64_decode(encoded, tal->spki, alloc_size, &tal->spki_len);
+ if (error)
+ free(tal->spki);
+
+ BIO_free(encoded);
+ return error;
+}
+
+/**
+ * @file_name is expected to outlive @result.
+ */
+int
+tal_load(char const *file_name, struct tal **result)
+{
+ struct line_file *lfile;
+ struct tal *tal;
+ int error;
+
+ error = lfile_open(file_name, &lfile);
+ if (error) {
+ pr_errno(error, "Error opening file '%s'", file_name);
+ goto fail4;
+ }
+
+ tal = malloc(sizeof(struct tal));
+ if (tal == NULL) {
+ error = -ENOMEM;
+ goto fail3;
+ }
+
+ tal->file_name = file_name;
+
+ error = uris_init(&tal->uris);
+ if (error)
+ goto fail2;
+
+ error = read_uris(lfile, &tal->uris);
+ if (error)
+ goto fail1;
+
+ error = read_spki(lfile, tal);
+ if (error)
+ goto fail1;
+
+ lfile_close(lfile);
+ *result = tal;
+ return 0;
+
+fail1:
+ uris_destroy(&tal->uris);
+fail2:
+ free(tal);
+fail3:
+ lfile_close(lfile);
+fail4:
+ return error;
+}
+
+void tal_destroy(struct tal *tal)
+{
+ if (tal == NULL)
+ return;
+
+ uris_destroy(&tal->uris);
+ free(tal->spki);
+ free(tal);
+}
+
+int
+foreach_uri(struct tal *tal, foreach_uri_cb cb)
+{
+ struct rpki_uri uri;
+ unsigned int i;
+ int error;
+
+ for (i = 0; i < tal->uris.count; i++) {
+ error = uri_init_str(&uri, tal->uris.array[i],
+ strlen(tal->uris.array[i]));
+ if (error == ENOTRSYNC) {
+ /* Log level should probably be INFO. */
+ pr_debug("TAL has non-RSYNC URI; ignoring.");
+ continue;
+ }
+ if (error)
+ return error;
+
+ error = cb(tal, &uri);
+ uri_cleanup(&uri);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+void
+tal_shuffle_uris(struct tal *tal)
+{
+ char **array = tal->uris.array;
+ unsigned int count = tal->uris.count;
+ char *tmp;
+ long random_index;
+ unsigned int i;
+
+ random_init();
+
+ for (i = 0; i < count; i++) {
+ tmp = array[i];
+ random_index = random_at_most(count - 1 - i) + i;
+ array[i] = array[random_index];
+ array[random_index] = tmp;
+ }
+}
+
+char const *
+tal_get_file_name(struct tal *tal)
+{
+ return tal->file_name;
+}
+
+void
+tal_get_spki(struct tal *tal, unsigned char const **buffer, size_t *len)
+{
+ *buffer = tal->spki;
+ *len = tal->spki_len;
+}
++
++/**
++ * Performs the whole validation walkthrough on uri @uri, which is assumed to
++ * have been extracted from a TAL.
++ */
++static int
++handle_tal_uri(struct tal *tal, struct rpki_uri const *uri)
++{
++ /*
++ * Because of the way the foreach iterates, this function must return
++ *
++ * - 0 on soft errors.
++ * - `> 0` on URI handled successfully.
++ * - `< 0` on hard errors.
++ *
++ * A "soft error" is "the connection to the preferred URI fails, or the
++ * retrieved CA certificate public key does not match the TAL public
++ * key." (RFC 7730)
++ *
++ * A "hard error" is any other error.
++ */
++
++ struct validation *state;
++ int error;
++
++ error = download_files(uri, true);
++ if (error) {
++ return pr_warn("TAL URI '%s' could not be RSYNC'd.",
++ uri->global);
++ }
++
++ error = validation_prepare(&state, tal);
++ if (error)
++ return ENSURE_NEGATIVE(error);
++
++ pr_debug_add("TAL URI '%s' {", uri_get_printable(uri));
++
++ if (!uri_is_certificate(uri)) {
++ pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')",
++ uri_get_printable(uri));
++ error = -EINVAL;
++ goto end;
++ }
++
++ error = certificate_traverse(NULL, uri, NULL, true);
++ if (error) {
++ switch (validation_pubkey_state(state)) {
++ case PKS_INVALID:
++ error = 0;
++ break;
++ case PKS_VALID:
++ case PKS_UNTESTED:
++ error = ENSURE_NEGATIVE(error);
++ break;
++ }
++ } else {
++ error = 1;
++ }
++
++end: validation_destroy(state);
++ pr_debug_rm("}");
++ return error;
++}
++
++/* TODO set @updated */
++int
++perform_standalone_validation(bool *updated)
++{
++ struct tal *tal;
++ int error;
++
++ fnstack_init();
++ fnstack_push(config_get_tal());
++
++ error = tal_load(config_get_tal(), &tal);
++ if (error)
++ goto end;
++
++ if (config_get_shuffle_tal_uris())
++ tal_shuffle_uris(tal);
++ error = foreach_uri(tal, handle_tal_uri);
++ if (error > 0)
++ error = 0;
++ else if (error == 0)
++ error = pr_err("None of the URIs of the TAL yielded a successful traversal.");
++
++end: tal_destroy(tal);
++ fnstack_pop();
++ fnstack_cleanup();
++ return error;
++}
--- /dev/null
+#ifndef TAL_OBJECT_H_
+#define TAL_OBJECT_H_
+
+/* This is RFC 7730. */
+
+#include <stddef.h>
+#include "uri.h"
+
+struct tal;
+
+int tal_load(char const *, struct tal **);
+void tal_destroy(struct tal *);
+
+typedef int (*foreach_uri_cb)(struct tal *, struct rpki_uri const *);
+int foreach_uri(struct tal *, foreach_uri_cb);
+void tal_shuffle_uris(struct tal *);
+
+char const *tal_get_file_name(struct tal *);
+void tal_get_spki(struct tal *, unsigned char const **, size_t *);
+
++int perform_standalone_validation(bool *);
++
+#endif /* TAL_OBJECT_H_ */
--- /dev/null
- handle_roa(uri, pp, crls);
+#include "rpp.h"
+
+#include <stdlib.h>
+#include "array_list.h"
+#include "log.h"
+#include "thread_var.h"
+#include "uri.h"
+#include "object/certificate.h"
+#include "object/crl.h"
+#include "object/ghostbusters.h"
+#include "object/roa.h"
+
+ARRAY_LIST(uris, struct rpki_uri)
+
+/** A Repository Publication Point (RFC 6481), as described by some manifest. */
+struct rpp {
+ struct uris certs; /* Certificates */
+
+ struct rpki_uri crl; /* Certificate Revocation List */
+ bool crl_set;
+
+ /* The Manifest is not needed for now. */
+
+ struct uris roas; /* Route Origin Attestations */
+
+ struct uris ghostbusters;
+};
+
+struct rpp *
+rpp_create(void)
+{
+ struct rpp *result;
+
+ result = malloc(sizeof(struct rpp));
+ if (result == NULL)
+ goto fail1;
+
+ if (uris_init(&result->certs) != 0)
+ goto fail2;
+ result->crl_set = false;
+ if (uris_init(&result->roas) != 0)
+ goto fail3;
+ if (uris_init(&result->ghostbusters) != 0)
+ goto fail4;
+
+ return result;
+
+fail4:
+ uris_cleanup(&result->roas, uri_cleanup);
+fail3:
+ uris_cleanup(&result->certs, uri_cleanup);
+fail2:
+ free(result);
+fail1:
+ return NULL;
+}
+
+void
+rpp_destroy(struct rpp *pp)
+{
+ uris_cleanup(&pp->certs, uri_cleanup);
+ uri_cleanup(&pp->crl);
+ uris_cleanup(&pp->roas, uri_cleanup);
+ uris_cleanup(&pp->ghostbusters, uri_cleanup);
+ free(pp);
+}
+
+int
+rpp_add_cert(struct rpp *pp, struct rpki_uri *uri)
+{
+ return uris_add(&pp->certs, uri);
+}
+
+int
+rpp_add_roa(struct rpp *pp, struct rpki_uri *uri)
+{
+ return uris_add(&pp->roas, uri);
+}
+
+int
+rpp_add_ghostbusters(struct rpp *pp, struct rpki_uri *uri)
+{
+ return uris_add(&pp->ghostbusters, uri);
+}
+
+int
+rpp_add_crl(struct rpp *pp, struct rpki_uri *uri)
+{
+ /* rfc6481#section-2.2 */
+ if (pp->crl_set)
+ return pr_err("Repository Publication Point has more than one CRL.");
+
+ pp->crl = *uri;
+ pp->crl_set = true;
+ return 0;
+}
+
+static int
+add_crl_to_stack(struct rpp *pp, STACK_OF(X509_CRL) *crls)
+{
+ X509_CRL *crl;
+ int error;
+ int idx;
+
+ fnstack_push_uri(&pp->crl);
+
+ error = crl_load(&pp->crl, &crl);
+ if (error)
+ goto end;
+
+ idx = sk_X509_CRL_push(crls, crl);
+ if (idx <= 0) {
+ error = crypto_err("Could not add CRL to a CRL stack");
+ X509_CRL_free(crl);
+ goto end;
+ }
+
+end:
+ fnstack_pop();
+ return error;
+}
+
+struct rpki_uri const *
+rpp_get_crl(struct rpp const *pp)
+{
+ return pp->crl_set ? &pp->crl : NULL;
+}
+
+int
+rpp_traverse(struct rpp *pp)
+{
+ /*
+ * TODO is the stack supposed to have only the CRLs of this layer,
+ * or all of them?
+ */
+ STACK_OF(X509_CRL) *crls;
+ struct rpki_uri *uri;
+ int error;
+
+ crls = sk_X509_CRL_new_null();
+ if (crls == NULL)
+ return pr_enomem();
+ error = add_crl_to_stack(pp, crls);
+ if (error)
+ goto end;
+
+ /* Use CRL stack to validate certificates, and also traverse them. */
+ ARRAYLIST_FOREACH(&pp->certs, uri)
+ certificate_traverse(pp, uri, crls, false);
+
+ /* Use valid address ranges to print ROAs that match them. */
+ ARRAYLIST_FOREACH(&pp->roas, uri)
- handle_ghostbusters(uri, pp, crls);
++ roa_traverse(uri, pp, crls);
+
+ ARRAYLIST_FOREACH(&pp->ghostbusters, uri)
++ ghostbusters_traverse(uri, pp, crls);
+
+end:
+ sk_X509_CRL_pop_free(crls, X509_CRL_free);
+ return error;
+}
--- /dev/null
- char *message)
+ #include "err_pdu.h"
+
+ #include <err.h>
+ #include <unistd.h>
+ #include "pdu_sender.h"
++#include "log.h"
+
+ int
+ err_pdu_send(int fd, u_int8_t version, u_int16_t code, void *err_pdu_header,
- char *code_title;
++ char const *message)
+ {
+ int error;
+
+ error = send_error_report_pdu(fd, version, code, err_pdu_header,
+ message);
+ if (err_pdu_is_fatal(code)) {
+ warnx("Fatal error report PDU sent [code %u], closing socket.",
+ code);
+ close(fd);
+ }
+
+ return error;
+ }
+
+ bool
+ err_pdu_is_fatal(u_int16_t code)
+ {
+ /* Only NO_DATA_AVAILABLE error isn't fatal */
+ return code != ERR_PDU_NO_DATA_AVAILABLE;
+ }
+
+ void
+ err_pdu_log(u_int16_t code, char *message)
+ {
- warnx("Error report PDU info: '%s', message '%s'.",
++ char const *code_title;
+
+ switch (code) {
+ case ERR_PDU_CORRUPT_DATA:
+ code_title = "Corrupt Data";
+ break;
+ case ERR_PDU_INTERNAL_ERROR:
+ code_title = "Internal Error";
+ break;
+ case ERR_PDU_NO_DATA_AVAILABLE:
+ code_title = "No Data Available";
+ break;
+ case ERR_PDU_INVALID_REQUEST:
+ code_title = "Invalid Request";
+ break;
+ case ERR_PDU_UNSUP_PROTO_VERSION:
+ code_title = "Unsupported Protocol Version";
+ break;
+ case ERR_PDU_UNSUP_PDU_TYPE:
+ code_title = "Unsupported PDU Type";
+ break;
+ case ERR_PDU_WITHDRAWAL_UNKNOWN:
+ code_title = "Withdrawal of Unknown Record";
+ break;
+ case ERR_PDU_DUPLICATE_ANNOUNCE:
+ code_title = "Duplicate Announcement Received";
+ break;
+ case ERR_PDU_UNEXPECTED_PROTO_VERSION:
+ code_title = "Unexpected Protocol Version";
+ break;
+ default:
+ code_title = "Unknown error code";
+ break;
+ }
+
++ pr_err("Error report PDU info: '%s', message '%s'.",
+ code_title, message == NULL ? "[empty]" : message);
+ }
--- /dev/null
-int err_pdu_send(int, u_int8_t, u_int16_t, void *, char *);
+ #ifndef SRC_RTR_ERR_PDU_H_
+ #define SRC_RTR_ERR_PDU_H_
+
+ #include <stdbool.h>
+ #include <sys/types.h>
+
+ #define ERR_PDU_CORRUPT_DATA 0
+ #define ERR_PDU_INTERNAL_ERROR 1
+ #define ERR_PDU_NO_DATA_AVAILABLE 2
+ #define ERR_PDU_INVALID_REQUEST 3
+ #define ERR_PDU_UNSUP_PROTO_VERSION 4
+ #define ERR_PDU_UNSUP_PDU_TYPE 5
+ #define ERR_PDU_WITHDRAWAL_UNKNOWN 6
+ #define ERR_PDU_DUPLICATE_ANNOUNCE 7
+ #define ERR_PDU_UNEXPECTED_PROTO_VERSION 8
+
+
++int err_pdu_send(int, u_int8_t, u_int16_t, void *, char const *);
+ bool err_pdu_is_fatal(u_int16_t);
+ void err_pdu_log(u_int16_t, char *);
+
+ #endif /* SRC_RTR_ERR_PDU_H_ */
--- /dev/null
- || read_int16(fd, &header->session_id)
+ #include "pdu.h"
+
+ #include <err.h>
+ #include <errno.h>
+ #include <stdlib.h>
+ #include <string.h>
+
+ #include "../common.h"
+ #include "pdu_handler.h"
+
+ static int pdu_header_from_stream(int, struct pdu_header *);
+ static int serial_notify_from_stream(struct pdu_header *, int, void *);
+ static int serial_query_from_stream(struct pdu_header *, int, void *);
+ static int reset_query_from_stream(struct pdu_header *, int, void *);
+ static int cache_response_from_stream(struct pdu_header *, int, void *);
+ static int ipv4_prefix_from_stream(struct pdu_header *, int, void *);
+ static int ipv6_prefix_from_stream(struct pdu_header *, int, void *);
+ static int end_of_data_from_stream(struct pdu_header *, int, void *);
+ static int cache_reset_from_stream(struct pdu_header *, int, void *);
+ static int error_report_from_stream(struct pdu_header *, int, void *);
+ static void error_report_destroy(void *);
+
+ int
+ pdu_load(int fd, void **pdu, struct pdu_metadata const **metadata,
+ u_int8_t *rtr_version)
+ {
+ struct pdu_header header;
+ struct pdu_metadata const *meta;
+ int err;
+
+ err = pdu_header_from_stream(fd, &header);
+ if (err)
+ return err;
+
+ meta = pdu_get_metadata(header.pdu_type);
+ if (!meta)
+ return -ENOENT; /* TODO try to skip it anyway? */
+
+ *pdu = malloc(meta->length);
+ if (*pdu == NULL)
+ return -ENOMEM;
+
+ err = meta->from_stream(&header, fd, *pdu);
+ if (err) {
+ free(*pdu);
+ return err;
+ }
+ *rtr_version = header.protocol_version;
+
+ if (metadata)
+ *metadata = meta;
+ return 0;
+ }
+
+ static int
+ pdu_header_from_stream(int fd, struct pdu_header *header)
+ {
+ /* If the first read yields no bytes, the connection was terminated. */
+ return read_int8(fd, &header->protocol_version)
+ || read_int8(fd, &header->pdu_type)
- return (type < 0 || ARRAY_SIZE(pdu_metadatas) <= type)
- ? NULL
- : pdu_metadatas[type];
++ || read_int16(fd, &header->m.session_id)
+ || read_int32(fd, &header->length);
+ }
+
+ static int
+ serial_notify_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct serial_notify_pdu *pdu = pdu_void;
+ memcpy(&pdu->header, header, sizeof(*header));
+ return read_int32(fd, &pdu->serial_number);
+ }
+
+ static int
+ serial_query_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct serial_query_pdu *pdu = pdu_void;
+ memcpy(&pdu->header, header, sizeof(*header));
+ return read_int32(fd, &pdu->serial_number);
+ }
+
+ static int
+ reset_query_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct reset_query_pdu *pdu = pdu_void;
+ memcpy(&pdu->header, header, sizeof(*header));
+ return 0;
+ }
+
+ static int
+ cache_response_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct cache_response_pdu *pdu = pdu_void;
+ memcpy(&pdu->header, header, sizeof(*header));
+ return 0;
+ }
+
+ static int
+ ipv4_prefix_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct ipv4_prefix_pdu *pdu = pdu_void;
+ memcpy(&pdu->header, header, sizeof(*header));
+ return read_int8(fd, &pdu->flags)
+ || read_int8(fd, &pdu->prefix_length)
+ || read_int8(fd, &pdu->max_length)
+ || read_int8(fd, &pdu->zero)
+ || read_in_addr(fd, &pdu->ipv4_prefix)
+ || read_int32(fd, &pdu->asn);
+ }
+
+ static int
+ ipv6_prefix_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct ipv6_prefix_pdu *pdu = pdu_void;
+ memcpy(&pdu->header, header, sizeof(*header));
+ return read_int8(fd, &pdu->flags)
+ || read_int8(fd, &pdu->prefix_length)
+ || read_int8(fd, &pdu->max_length)
+ || read_int8(fd, &pdu->zero)
+ || read_in6_addr(fd, &pdu->ipv6_prefix)
+ || read_int32(fd, &pdu->asn);
+ }
+
+ static int
+ end_of_data_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct end_of_data_pdu *pdu = pdu_void;
+ memcpy(&pdu->header, header, sizeof(*header));
+ return read_int32(fd, &pdu->serial_number);
+ }
+
+ static int
+ cache_reset_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct cache_reset_pdu *pdu = pdu_void;
+ memcpy(&pdu->header, header, sizeof(*header));
+ return 0;
+ }
+
+ static int
+ error_report_from_stream(struct pdu_header *header, int fd, void *pdu_void)
+ {
+ struct error_report_pdu *pdu = pdu_void;
+ u_int32_t sub_pdu_len; /* TODO use this for something */
+ u_int8_t rtr_version;
+ int error;
+
+ memcpy(&pdu->header, header, sizeof(*header));
+
+ error = read_int32(fd, &sub_pdu_len);
+ if (error)
+ return error;
+
+ error = pdu_load(fd, &pdu->erroneous_pdu, NULL, &rtr_version);
+ if (error)
+ return -EINVAL;
+
+ error = read_string(fd, &pdu->error_message);
+ if (error) {
+ free(pdu->erroneous_pdu);
+ return error;
+ }
+
+ return 0;
+ }
+
+ static void
+ error_report_destroy(void *pdu_void)
+ {
+ struct error_report_pdu *pdu = pdu_void;
+ struct pdu_header *sub_hdr;
+ struct pdu_metadata const *sub_meta;
+
+ sub_hdr = pdu_get_header(pdu->erroneous_pdu);
+ sub_meta = pdu_get_metadata(sub_hdr->pdu_type);
+ if (sub_meta)
+ sub_meta->destructor(pdu->erroneous_pdu);
+ else
+ warnx("Unknown PDU type (%u).", sub_hdr->pdu_type);
+
+ free(pdu->error_message);
+ free(pdu_void);
+ }
+
+ #define DEFINE_METADATA(name, dtor) \
+ static struct pdu_metadata const name ## _meta = { \
+ .length = sizeof(struct name ## _pdu), \
+ .from_stream = name ## _from_stream, \
+ .handle = handle_ ## name ## _pdu, \
+ .destructor = dtor, \
+ }
+
+ DEFINE_METADATA(serial_notify, free);
+ DEFINE_METADATA(serial_query, free);
+ DEFINE_METADATA(reset_query, free);
+ DEFINE_METADATA(cache_response, free);
+ DEFINE_METADATA(ipv4_prefix, free);
+ DEFINE_METADATA(ipv6_prefix, free);
+ DEFINE_METADATA(end_of_data, free);
+ DEFINE_METADATA(cache_reset, free);
+ DEFINE_METADATA(error_report, error_report_destroy);
+
+ struct pdu_metadata const *const pdu_metadatas[] = {
+ /* 0 */ &serial_notify_meta,
+ /* 1 */ &serial_query_meta,
+ /* 2 */ &reset_query_meta,
+ /* 3 */ &cache_response_meta,
+ /* 4 */ &ipv4_prefix_meta,
+ /* 5 */ NULL,
+ /* 6 */ &ipv6_prefix_meta,
+ /* 7 */ &end_of_data_meta,
+ /* 8 */ &cache_reset_meta,
+ /* 9 */ NULL,
+ /* 10 */ &error_report_meta,
+ };
+
+ struct pdu_metadata const *
+ pdu_get_metadata(u_int8_t type)
+ {
++ return (ARRAY_LEN(pdu_metadatas) <= type) ? NULL : pdu_metadatas[type];
+ }
+
+ struct pdu_header *
+ pdu_get_header(void *pdu)
+ {
+ /* The header is by definition the first field of every PDU. */
+ return pdu;
+ }
--- /dev/null
- };
+ #ifndef RTR_PDU_H_
+ #define RTR_PDU_H_
+
+ #include <netinet/in.h>
+
+ #include "../common.h"
+ #include "primitive_reader.h"
+
+ #define RTR_V0 0
+ #define RTR_V1 1
+
+ #define PDU_TYPE_SERIAL_NOTIFY 0
+ #define PDU_TYPE_CACHE_RESPONSE 3
+ #define PDU_TYPE_IPV4_PREFIX 4
+ #define PDU_TYPE_IPV6_PREFIX 6
+ #define PDU_TYPE_END_OF_DATA 7
+ #define PDU_TYPE_CACHE_RESET 8
+ #define PDU_TYPE_ERROR_REPORT 10
+
+ struct pdu_header {
+ u_int8_t protocol_version;
+ u_int8_t pdu_type;
+ union {
+ u_int16_t session_id;
+ u_int16_t reserved;
+ u_int16_t error_code;
++ } m; /* Note: "m" stands for "meh." I have no idea what to call this. */
+ u_int32_t length;
+ };
+
+ struct serial_notify_pdu {
+ struct pdu_header header;
+ u_int32_t serial_number;
+ };
+
+ struct serial_query_pdu {
+ struct pdu_header header;
+ u_int32_t serial_number;
+ };
+
+ struct reset_query_pdu {
+ struct pdu_header header;
+ };
+
+ struct cache_response_pdu {
+ struct pdu_header header;
+ };
+
+ struct ipv4_prefix_pdu {
+ struct pdu_header header;
+ u_int8_t flags;
+ u_int8_t prefix_length;
+ u_int8_t max_length;
+ u_int8_t zero;
+ struct in_addr ipv4_prefix;
+ u_int32_t asn;
+ };
+
+ struct ipv6_prefix_pdu {
+ struct pdu_header header;
+ u_int8_t flags;
+ u_int8_t prefix_length;
+ u_int8_t max_length;
+ u_int8_t zero;
+ struct in6_addr ipv6_prefix;
+ u_int32_t asn;
+ };
+
+ struct end_of_data_pdu {
+ struct pdu_header header;
+ u_int32_t serial_number;
+ u_int32_t refresh_interval;
+ u_int32_t retry_interval;
+ u_int32_t expire_interval;
+ };
+
+ struct cache_reset_pdu {
+ struct pdu_header header;
+ };
+
+ struct error_report_pdu {
+ struct pdu_header header;
+ u_int32_t error_pdu_length;
+ void *erroneous_pdu;
+ u_int32_t error_message_length;
+ rtr_char *error_message;
+ };
+
+ struct pdu_metadata {
+ size_t length;
+ int (*from_stream)(struct pdu_header *, int, void *);
+ int (*handle)(int, void *);
+ void (*destructor)(void *);
+ };
+
+ __BEGIN_DECLS
+ int pdu_load(int, void **, struct pdu_metadata const **, u_int8_t *);
+ struct pdu_metadata const *pdu_get_metadata(u_int8_t);
+ struct pdu_header *pdu_get_header(void *);
+ __END_DECLS
+
+ #endif /* RTR_PDU_H_ */
--- /dev/null
-warn_unexpected_pdu(int fd, void *pdu, char *pdu_name)
+ #include "pdu_handler.h"
+
+ #include <err.h>
+ #include <errno.h>
+ #include <stddef.h>
+ #include <unistd.h>
+
+ #include "err_pdu.h"
+ #include "pdu.h"
+ #include "pdu_sender.h"
+ #include "vrps.h"
+
+ static int
- if (received->header.session_id != session_id)
++warn_unexpected_pdu(int fd, void *pdu, char const *pdu_name)
+ {
+ struct pdu_header *pdu_header = pdu;
+ warnx("Unexpected %s PDU received", pdu_name);
+ err_pdu_send(fd, pdu_header->protocol_version, ERR_PDU_UNSUP_PDU_TYPE,
+ pdu_header, "Unexpected PDU received");
+ return -EINVAL;
+ }
+
+ int
+ handle_serial_notify_pdu(int fd, void *pdu)
+ {
+ return warn_unexpected_pdu(fd, pdu, "Serial Notify");
+ }
+
+ static int
+ send_commmon_exchange(struct sender_common *common)
+ {
+ int error;
+
+ /* Send Cache response PDU */
+ error = send_cache_response_pdu(common);
+ if (error)
+ return error;
+
+ /* Send Payload PDUs */
+ error = send_payload_pdus(common);
+ if (error)
+ return error;
+
+ /* Send End of data PDU */
+ return send_end_of_data_pdu(common);
+ }
+
+ int
+ handle_serial_query_pdu(int fd, void *pdu)
+ {
+ struct serial_query_pdu *received = pdu;
+ struct sender_common common;
+ int error, updates;
+ u_int32_t current_serial;
+ u_int16_t session_id;
+ u_int8_t version;
+
+ /*
+ * RFC 6810 and 8210:
+ * "If [...] either the router or the cache finds that the value of the
+ * Session ID is not the same as the other's, the party which detects
+ * the mismatch MUST immediately terminate the session with an Error
+ * Report PDU with code 0 ("Corrupt Data")"
+ */
+ version = received->header.protocol_version;
+ session_id = get_current_session_id(version);
- if (err_pdu_is_fatal(received->header.error_code)) {
++ if (received->header.m.session_id != session_id)
+ return err_pdu_send(fd, version, ERR_PDU_CORRUPT_DATA,
+ &received->header, NULL);
+
+ current_serial = get_last_serial_number();
+ init_sender_common(&common, fd, version, &session_id,
+ &received->serial_number, ¤t_serial);
+
+ updates = deltas_db_status(common.start_serial);
+ switch (updates) {
+ case NO_DATA_AVAILABLE:
+ /* https://tools.ietf.org/html/rfc8210#section-8.4 */
+ return err_pdu_send(fd, version, ERR_PDU_NO_DATA_AVAILABLE,
+ NULL, NULL);
+ case DIFF_UNDETERMINED:
+ /* https://tools.ietf.org/html/rfc8210#section-8.3 */
+ return send_cache_reset_pdu(&common);
+ case DIFF_AVAILABLE:
+ /* https://tools.ietf.org/html/rfc8210#section-8.2 */
+ return send_commmon_exchange(&common);
+ case NO_DIFF:
+ /* Typical exchange with no Payloads */
+ error = send_cache_response_pdu(&common);
+ if (error)
+ return error;
+ return send_end_of_data_pdu(&common);
+ default:
+ warnx("Reached 'unreachable' code");
+ return -EINVAL;
+ }
+ }
+
+ int
+ handle_reset_query_pdu(int fd, void *pdu)
+ {
+ struct reset_query_pdu *received = pdu;
+ struct sender_common common;
+ u_int32_t current_serial;
+ u_int16_t session_id;
+ u_int8_t version;
+ int updates;
+
+ version = received->header.protocol_version;
+ session_id = get_current_session_id(version);
+ current_serial = get_last_serial_number();
+ init_sender_common(&common, fd, version, &session_id, NULL,
+ ¤t_serial);
+
+ updates = deltas_db_status(NULL);
+ switch (updates) {
+ case NO_DATA_AVAILABLE:
+ /* https://tools.ietf.org/html/rfc8210#section-8.4 */
+ return err_pdu_send(fd, version, ERR_PDU_NO_DATA_AVAILABLE,
+ NULL, NULL);
+ case DIFF_AVAILABLE:
+ /* https://tools.ietf.org/html/rfc8210#section-8.1 */
+ return send_commmon_exchange(&common);
+ default:
+ warnx("Reached 'unreachable' code");
+ return -EINVAL;
+ }
+ }
+
+ int
+ handle_cache_response_pdu(int fd, void *pdu)
+ {
+ return warn_unexpected_pdu(fd, pdu, "Cache Response");
+ }
+
+ int
+ handle_ipv4_prefix_pdu(int fd, void *pdu)
+ {
+ return warn_unexpected_pdu(fd, pdu, "IPv4 Prefix");
+ }
+
+ int
+ handle_ipv6_prefix_pdu(int fd, void *pdu)
+ {
+ return warn_unexpected_pdu(fd, pdu, "IPv6 Prefix");
+ }
+
+ int
+ handle_end_of_data_pdu(int fd, void *pdu)
+ {
+ return warn_unexpected_pdu(fd, pdu, "End of Data");
+ }
+
+ int
+ handle_cache_reset_pdu(int fd, void *pdu)
+ {
+ return warn_unexpected_pdu(fd, pdu, "Cache Reset");
+ }
+
+ int
+ handle_error_report_pdu(int fd, void *pdu)
+ {
+ struct error_report_pdu *received = pdu;
+
- received->header.error_code);
++ if (err_pdu_is_fatal(received->header.m.error_code)) {
+ warnx("Fatal error report PDU received [code %u], closing socket.",
- err_pdu_log(received->header.error_code, received->error_message);
++ received->header.m.error_code);
+ close(fd);
+ }
++ err_pdu_log(received->header.m.error_code, received->error_message);
+
+ return 0;
+ }
--- /dev/null
-#include "../configuration.h"
-#include "../vrps.h"
-#include "pdu_serializer.h"
+ #include "pdu_sender.h"
+
+ #include <err.h>
+ #include <errno.h>
+ #include <stdbool.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
+
- header->reserved = reserved;
++#include "config.h"
++#include "vrps.h"
++#include "rtr/pdu_serializer.h"
+
+ /* Header length field is always 64 bits long */
+ #define HEADER_LENGTH 8
+ /* IPvN PDUs length without header */
+ #define IPV4_PREFIX_LENGTH 12
+ #define IPV6_PREFIX_LENGTH 24
+
+ void
+ init_sender_common(struct sender_common *common, int fd, u_int8_t version,
+ u_int16_t *session_id, u_int32_t *start_serial, u_int32_t *end_serial)
+ {
+ common->fd = fd;
+ common->version = version;
+ common->session_id = session_id == NULL ? 0 : session_id;
+ common->start_serial = start_serial;
+ common->end_serial = end_serial;
+ }
+ /*
+ * Set all the header values, EXCEPT length field.
+ */
+ static void
+ set_header_values(struct pdu_header *header, u_int8_t version, u_int8_t type,
+ u_int16_t reserved)
+ {
+ header->protocol_version = version;
+ header->pdu_type = type;
- pdu.ipv4_prefix = vrp->ipv4_prefix;
++ header->m.reserved = reserved;
+ }
+
+ static u_int32_t
+ length_serial_notify_pdu(struct serial_notify_pdu *pdu)
+ {
+ return HEADER_LENGTH + sizeof(pdu->serial_number);
+ }
+
+ static u_int32_t
+ length_ipvx_prefix_pdu(bool isv4)
+ {
+ return HEADER_LENGTH +
+ (isv4 ? IPV4_PREFIX_LENGTH : IPV6_PREFIX_LENGTH);
+ }
+
+ static u_int32_t
+ length_end_of_data_pdu(struct end_of_data_pdu *pdu)
+ {
+ u_int32_t len;
+
+ len = HEADER_LENGTH;
+ len += sizeof(pdu->serial_number);
+ if (pdu->header.protocol_version == RTR_V1) {
+ len += sizeof(pdu->refresh_interval);
+ len += sizeof(pdu->retry_interval);
+ len += sizeof(pdu->expire_interval);
+ }
+
+ return len;
+ }
+
+ static u_int32_t
+ length_error_report_pdu(struct error_report_pdu *pdu)
+ {
+ return HEADER_LENGTH +
+ pdu->error_pdu_length + sizeof(pdu->error_pdu_length) +
+ pdu->error_message_length + sizeof(pdu->error_message_length);
+ }
+
+ static int
+ send_response(int fd, char *data, size_t data_len)
+ {
+ struct data_buffer buffer;
+ int error;
+
+ init_buffer(&buffer);
+ /* Check for buffer overflow */
+ if (data_len > buffer.capacity) {
+ warnx("Response buffer out of capacity");
+ return -EINVAL;
+ }
+ memcpy(buffer.data, data, data_len);
+ buffer.len = data_len;
+
+ error = write(fd, buffer.data, buffer.len);
+ free_buffer(&buffer);
+ if (error < 0) {
+ warnx("Error sending response");
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ int
+ send_serial_notify_pdu(struct sender_common *common)
+ {
+ struct serial_notify_pdu pdu;
+ char data[BUFFER_SIZE];
+ size_t len;
+
+ set_header_values(&pdu.header, common->version, PDU_TYPE_SERIAL_NOTIFY,
+ *common->session_id);
+
+ pdu.serial_number = *common->start_serial;
+ pdu.header.length = length_serial_notify_pdu(&pdu);
+
+ len = serialize_serial_notify_pdu(&pdu, data);
+
+ return send_response(common->fd, data, len);
+ }
+
+ int
+ send_cache_reset_pdu(struct sender_common *common)
+ {
+ struct cache_reset_pdu pdu;
+ char data[BUFFER_SIZE];
+ size_t len;
+
+ /* This PDU has only the header */
+ set_header_values(&pdu.header, common->version, PDU_TYPE_CACHE_RESET,
+ 0);
+ pdu.header.length = HEADER_LENGTH;
+
+ len = serialize_cache_reset_pdu(&pdu, data);
+ return send_response(common->fd, data, len);
+ }
+
+ int
+ send_cache_response_pdu(struct sender_common *common)
+ {
+ struct cache_response_pdu pdu;
+ char data[BUFFER_SIZE];
+ size_t len;
+
+ /* This PDU has only the header */
+ set_header_values(&pdu.header, common->version,
+ PDU_TYPE_CACHE_RESPONSE, *common->session_id);
+ pdu.header.length = HEADER_LENGTH;
+
+ len = serialize_cache_response_pdu(&pdu, data);
+
+ return send_response(common->fd, data, len);
+ }
+
+ static int
+ send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp)
+ {
+ struct ipv4_prefix_pdu pdu;
+ char data[BUFFER_SIZE];
+ size_t len;
+
+ set_header_values(&pdu.header, common->version, PDU_TYPE_IPV4_PREFIX,
+ 0);
+
+ pdu.flags = vrp->flags;
+ pdu.prefix_length = vrp->prefix_length;
+ pdu.max_length = vrp->max_prefix_length;
+ pdu.zero = 0;
- pdu.ipv6_prefix = vrp->ipv6_prefix;
++ pdu.ipv4_prefix = vrp->prefix.ipv4;
+ pdu.asn = vrp->asn;
+ pdu.header.length = length_ipvx_prefix_pdu(true);
+
+ len = serialize_ipv4_prefix_pdu(&pdu, data);
+
+ return send_response(common->fd, data, len);
+ }
+
+ static int
+ send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp)
+ {
+ struct ipv6_prefix_pdu pdu;
+ char data[BUFFER_SIZE];
+ size_t len;
+
+ set_header_values(&pdu.header, common->version, PDU_TYPE_IPV6_PREFIX,
+ 0);
+
+ pdu.flags = vrp->flags;
+ pdu.prefix_length = vrp->prefix_length;
+ pdu.max_length = vrp->max_prefix_length;
+ pdu.zero = 0;
-struct pdu_header *err_pdu_header, char *message)
++ pdu.ipv6_prefix = vrp->prefix.ipv6;
+ pdu.asn = vrp->asn;
+ pdu.header.length = length_ipvx_prefix_pdu(false);
+
+ len = serialize_ipv6_prefix_pdu(&pdu, data);
+
+ return send_response(common->fd, data, len);
+ }
+
+ int
+ send_payload_pdus(struct sender_common *common)
+ {
+ struct vrp *vrps, *ptr;
+ unsigned int len;
+ int error;
+
+ vrps = malloc(sizeof(struct vrp));
+ if (vrps == NULL) {
+ warn("Couldn't allocate VRPs to send PDUs");
+ return -ENOMEM;
+ }
+ len = get_vrps_delta(common->start_serial, common->end_serial, &vrps);
+ if (len == 0)
+ goto end;
+
+ for (ptr = vrps; (ptr - vrps) < len; ptr++) {
+ if (ptr->addr_fam == AF_INET)
+ error = send_ipv4_prefix_pdu(common, ptr);
+ else if (ptr->addr_fam == AF_INET6)
+ error = send_ipv6_prefix_pdu(common, ptr);
+ else
+ error = -EINVAL;
+
+ if (error) {
+ free(vrps);
+ return error;
+ }
+ }
+ end:
+ free(vrps);
+ return 0;
+ }
+
+ int
+ send_end_of_data_pdu(struct sender_common *common)
+ {
+ struct end_of_data_pdu pdu;
+ char data[BUFFER_SIZE];
+ size_t len;
+
+ set_header_values(&pdu.header, common->version, PDU_TYPE_END_OF_DATA,
+ *common->session_id);
+ pdu.serial_number = *common->end_serial;
+ if (common->version == RTR_V1) {
+ pdu.refresh_interval = config_get_refresh_interval();
+ pdu.retry_interval = config_get_retry_interval();
+ pdu.expire_interval = config_get_expire_interval();
+ }
+ pdu.header.length = length_end_of_data_pdu(&pdu);
+
+ len = serialize_end_of_data_pdu(&pdu, data);
+
+ return send_response(common->fd, data, len);
+ }
+
+ int
+ send_error_report_pdu(int fd, u_int8_t version, u_int16_t code,
++struct pdu_header *err_pdu_header, char const *message)
+ {
+ struct error_report_pdu pdu;
+ char data[BUFFER_SIZE];
+ size_t len;
+
+ set_header_values(&pdu.header, version, PDU_TYPE_ERROR_REPORT,
+ code);
+
+ pdu.error_pdu_length = 0;
+ pdu.erroneous_pdu = (void *)err_pdu_header;
+ if (err_pdu_header != NULL)
+ pdu.error_pdu_length = sizeof(err_pdu_header);
+
+ pdu.error_message_length = 0;
+ pdu.error_message = NULL;
+ if (message != NULL) {
+ pdu.error_message = malloc(strlen(message) + 1);
+ if (pdu.error_message == NULL)
+ warn("Error message couldn't be allocated, removed from PDU");
+ else {
+ pdu.error_message_length = strlen(message) + 1;
+ strcpy(pdu.error_message, message);
+ }
+ }
+
+ /* Calculate lengths */
+ pdu.header.length = length_error_report_pdu(&pdu);
+
+ len = serialize_error_report_pdu(&pdu, data);
+ free(pdu.error_message);
+ return send_response(fd, data, len);
+ }
--- /dev/null
- char *);
+ #ifndef SRC_RTR_PDU_SENDER_H_
+ #define SRC_RTR_PDU_SENDER_H_
+
+ #include <sys/types.h>
+ #include "pdu.h"
+
+ struct sender_common {
+ int fd;
+ u_int8_t version;
+ u_int16_t *session_id;
+ u_int32_t *start_serial;
+ u_int32_t *end_serial;
+ };
+
+ void init_sender_common(struct sender_common *, int, u_int8_t, u_int16_t *,
+ u_int32_t *, u_int32_t *);
+
+ int send_serial_notify_pdu(struct sender_common *);
+ int send_cache_reset_pdu(struct sender_common *);
+ int send_cache_response_pdu(struct sender_common *);
+ int send_payload_pdus(struct sender_common *);
+ int send_end_of_data_pdu(struct sender_common *);
+ int send_error_report_pdu(int, u_int8_t, u_int16_t, struct pdu_header *,
++ char const *);
+
+
+ #endif /* SRC_RTR_PDU_SENDER_H_ */
--- /dev/null
- head_size = serialize_pdu_header(&pdu->header, pdu->header.session_id,
+ #include "pdu_serializer.h"
+
+ #include <stdlib.h>
+ #include <string.h>
+ #include "primitive_writer.h"
+
+ void
+ init_buffer(struct data_buffer *buffer)
+ {
+ buffer->capacity = BUFFER_SIZE;
+ buffer->data = malloc(BUFFER_SIZE);
+ }
+
+ void
+ free_buffer(struct data_buffer *buffer)
+ {
+ free(buffer->data);
+ }
+
+ static size_t
+ serialize_pdu_header(struct pdu_header *header, u_int16_t union_value,
+ char *buf)
+ {
+ char *ptr;
+
+ ptr = buf;
+ ptr = write_int8(ptr, header->protocol_version);
+ ptr = write_int8(ptr, header->pdu_type);
+ ptr = write_int16(ptr, union_value);
+ ptr = write_int32(ptr, header->length);
+
+ return ptr - buf;
+ }
+
+ size_t
+ serialize_serial_notify_pdu(struct serial_notify_pdu *pdu, char *buf)
+ {
+ size_t head_size;
+ char *ptr;
+
- return serialize_pdu_header(&pdu->header, pdu->header.session_id, buf);
++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.session_id,
+ buf);
+
+ ptr = buf + head_size;
+ ptr = write_int32(ptr, pdu->serial_number);
+
+ return ptr - buf;
+ }
+
+ size_t
+ serialize_cache_response_pdu(struct cache_response_pdu *pdu, char *buf)
+ {
+ /* No payload to serialize */
- head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved,
++ return serialize_pdu_header(&pdu->header, pdu->header.m.session_id,
++ buf);
+ }
+
+ size_t
+ serialize_ipv4_prefix_pdu(struct ipv4_prefix_pdu *pdu, char *buf)
+ {
+ size_t head_size;
+ char *ptr;
+
- head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved,
++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved,
+ buf);
+
+ ptr = buf + head_size;
+ ptr = write_int8(ptr, pdu->flags);
+ ptr = write_int8(ptr, pdu->prefix_length);
+ ptr = write_int8(ptr, pdu->max_length);
+ ptr = write_int8(ptr, pdu->zero);
+ ptr = write_in_addr(ptr, pdu->ipv4_prefix);
+ ptr = write_int32(ptr, pdu->asn);
+
+ return ptr - buf;
+ }
+
+ size_t
+ serialize_ipv6_prefix_pdu(struct ipv6_prefix_pdu *pdu, char *buf)
+ {
+ size_t head_size;
+ char *ptr;
+
- head_size = serialize_pdu_header(&pdu->header, pdu->header.session_id,
++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved,
+ buf);
+
+ ptr = buf + head_size;
+ ptr = write_int8(ptr, pdu->flags);
+ ptr = write_int8(ptr, pdu->prefix_length);
+ ptr = write_int8(ptr, pdu->max_length);
+ ptr = write_int8(ptr, pdu->zero);
+ ptr = write_in6_addr(ptr, pdu->ipv6_prefix);
+ ptr = write_int32(ptr, pdu->asn);
+
+ return ptr - buf;
+ }
+
+ size_t
+ serialize_end_of_data_pdu(struct end_of_data_pdu *pdu, char *buf)
+ {
+ size_t head_size;
+ char *ptr;
+
- return serialize_pdu_header(&pdu->header, pdu->header.reserved, buf);
++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.session_id,
+ buf);
+
+ ptr = buf + head_size;
+ ptr = write_int32(ptr, pdu->serial_number);
+ if (pdu->header.protocol_version == RTR_V1) {
+ ptr = write_int32(ptr, pdu->refresh_interval);
+ ptr = write_int32(ptr, pdu->retry_interval);
+ ptr = write_int32(ptr, pdu->expire_interval);
+ }
+
+ return ptr - buf;
+ }
+
+ size_t
+ serialize_cache_reset_pdu(struct cache_reset_pdu *pdu, char *buf)
+ {
+ /* No payload to serialize */
- head_size = serialize_pdu_header(&pdu->header, pdu->header.error_code,
++ return serialize_pdu_header(&pdu->header, pdu->header.m.reserved, buf);
+ }
+
+ size_t
+ serialize_error_report_pdu(struct error_report_pdu *pdu, char *buf)
+ {
+ struct pdu_header *err_pdu_header;
+ size_t head_size;
+ char *ptr, *tmp_ptr;
+ int i;
+
- err_pdu_header->reserved, ptr);
++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.error_code,
+ buf);
+
+ ptr = buf + head_size;
+
+ ptr = write_int32(ptr, pdu->error_pdu_length);
+ if (pdu->error_pdu_length > 0) {
+ /* Set only the header of err PDU */
+ err_pdu_header = (struct pdu_header *)pdu->erroneous_pdu;
+ head_size = serialize_pdu_header(err_pdu_header,
++ err_pdu_header->m.reserved, ptr);
+ ptr = ptr + head_size;
+ }
+
+ ptr = write_int32(ptr, pdu->error_message_length);
+ tmp_ptr = pdu->error_message;
+ for (i = 0; i < pdu->error_message_length; i++)
+ ptr = write_int8(ptr, tmp_ptr[i]);
+
+ return ptr - buf;
+ }
--- /dev/null
- unsigned char trash[TLEN];
+ #include "primitive_reader.h"
+
+ #include <err.h>
+ #include <errno.h>
+ #include <stdlib.h>
++#include <stdint.h>
+ #include <unistd.h>
+ #include <netinet/in.h>
+
++#include "log.h"
++
+ static int read_exact(int, unsigned char *, size_t);
+ static int read_and_waste(int, unsigned char *, size_t, u_int32_t);
+ static int get_octets(unsigned char);
+ static void place_null_character(rtr_char *, size_t);
+
+ static int
+ read_exact(int fd, unsigned char *buffer, size_t buffer_len)
+ {
+ ssize_t read_result;
+ size_t offset;
+
+ for (offset = 0; offset < buffer_len; offset += read_result) {
+ read_result = read(fd, &buffer[offset], buffer_len - offset);
+ if (read_result == -1) {
+ warn("Client socket read interrupted");
+ return -errno;
+ }
+ if (read_result == 0) {
+ warnx("Stream ended mid-PDU.");
+ return -EPIPE;
+ }
+ }
+
+ return 0;
+ }
+
+ int
+ read_int8(int fd, u_int8_t *result)
+ {
+ return read_exact(fd, result, sizeof(u_int8_t));
+ }
+
+ /** Big Endian. */
+ int
+ read_int16(int fd, u_int16_t *result)
+ {
+ unsigned char buffer[2];
+ int err;
+
+ err = read_exact(fd, buffer, sizeof(buffer));
+ if (err)
+ return err;
+
+ *result = (((u_int16_t)buffer[0]) << 8) | ((u_int16_t)buffer[1]);
+ return 0;
+ }
+
+ /** Big Endian. */
+ int
+ read_int32(int fd, u_int32_t *result)
+ {
+ unsigned char buffer[4];
+ int err;
+
+ err = read_exact(fd, buffer, sizeof(buffer));
+ if (err)
+ return err;
+
+ *result = (((u_int32_t)buffer[0]) << 24)
+ | (((u_int32_t)buffer[1]) << 16)
+ | (((u_int32_t)buffer[2]) << 8)
+ | (((u_int32_t)buffer[3]) );
+ return 0;
+ }
+
+ int
+ read_in_addr(int fd, struct in_addr *result)
+ {
+ return read_int32(fd, &result->s_addr);
+ }
+
+ int
+ read_in6_addr(int fd, struct in6_addr *result)
+ {
+ return read_int32(fd, &result->s6_addr32[0])
+ || read_int32(fd, &result->s6_addr32[1])
+ || read_int32(fd, &result->s6_addr32[2])
+ || read_int32(fd, &result->s6_addr32[3]);
+ }
+
+ /*
+ * Consumes precisely @total_len bytes from @fd.
+ * The first @str_len bytes are stored in @str.
+ *
+ * It is required that @str_len <= @total_len.
+ */
+ static int
+ read_and_waste(int fd, unsigned char *str, size_t str_len, u_int32_t total_len)
+ {
+ #define TLEN 1024 /* "Trash length" */
- for (offset = str_len; (offset + TLEN) < total_len; offset += TLEN) {
- err = read_exact(fd, trash, TLEN);
++ unsigned char *trash;
+ size_t offset;
+ int err;
+
+ err = read_exact(fd, str, str_len);
+ if (err)
+ return err;
+
- return err;
++ if (str_len == total_len)
++ return 0;
++
++ trash = malloc(TLEN);
++ if (trash == NULL)
++ return pr_enomem();
++
++ for (offset = str_len; offset < total_len; offset += TLEN) {
++ err = read_exact(fd, trash,
++ (total_len - offset >= TLEN) ? TLEN : (total_len - offset));
+ if (err)
- return read_exact(fd, trash, total_len - offset);
++ break;
+ }
+
++ free(trash);
++ return err;
+ #undef TLEN
+ }
+
+ #define EINVALID_UTF8 -0xFFFF
+
+ /*
+ * Returns the length (in octets) of the UTF-8 code point that starts with
+ * octet @first_octet.
+ */
+ static int
+ get_octets(unsigned char first_octet)
+ {
+ if ((first_octet & 0x80) == 0)
+ return 1;
+ if ((first_octet >> 5) == 6) /* 0b110 */
+ return 2;
+ if ((first_octet >> 4) == 14) /* 0b1110 */
+ return 3;
+ if ((first_octet >> 3) == 30) /* 0b11110 */
+ return 4;
+ return EINVALID_UTF8;
+ }
+
+ /* This is just a cast. The barebones version is too cluttered. */
+ #define UCHAR(c) ((unsigned char *)c)
+
+ /*
+ * This also sanitizes the string, BTW.
+ * (Because it overrides the first invalid character with the null chara.
+ * The rest is silently ignored.)
+ */
+ static void
+ place_null_character(rtr_char *str, size_t len)
+ {
+ rtr_char *null_chara_pos;
+ rtr_char *cursor;
+ int octet;
+ int octets;
+
+ /*
+ * This could be optimized by noticing that all byte continuations in
+ * UTF-8 start with 0b10. This means that we could start from the end
+ * of the string and move left until we find a valid character.
+ * But if we do that, we'd lose the sanitization. So this is better
+ * methinks.
+ */
+
+ null_chara_pos = str;
+ cursor = str;
+
+ while (cursor < str + len - 1) {
+ octets = get_octets(*UCHAR(cursor));
+ if (octets == EINVALID_UTF8)
+ break;
+ cursor++;
+
+ for (octet = 1; octet < octets; octet++) {
+ /* Memory ends in the middle of this code point? */
+ if (cursor >= str + len - 1)
+ goto end;
+ /* All continuation octets must begin with 0b10. */
+ if ((*(UCHAR(cursor)) >> 6) != 2 /* 0b10 */)
+ goto end;
+ cursor++;
+ }
+
+ null_chara_pos = cursor;
+ }
+
+ end:
+ *null_chara_pos = '\0';
+ }
+
+ /*
+ * Reads an RTR string from the file descriptor @fd. Returns the string as a
+ * normal UTF-8 C string (NULL-terminated).
+ *
+ * Will consume the entire string from the stream, but @result can be
+ * truncated. This is because RTR strings are technically allowed to be 4 GBs
+ * long.
+ *
+ * The result is allocated in the heap. It will length 4096 characters at most.
+ * (Including the NULL chara.)
+ */
+ int
+ read_string(int fd, rtr_char **result)
+ {
+ /* Actual string length claimed by the PDU, in octets. */
+ u_int32_t full_length32; /* Excludes the null chara */
+ u_int64_t full_length64; /* Includes the null chara */
+ /*
+ * Actual length that we allocate. Octets.
+ * This exists because there might be value in truncating the string;
+ * full_length is a fucking 32-bit integer for some reason.
+ * Note that, because this is UTF-8 we're dealing with, this might not
+ * necessarily end up being the actual octet length of the final
+ * string; since our truncation can land in the middle of a code point,
+ * the null character might need to be shifted left slightly.
+ */
+ size_t alloc_length; /* Includes the null chara */
+ rtr_char *str;
+ int err;
+
+ err = read_int32(fd, &full_length32);
+ if (err)
+ return err;
+
+ full_length64 = ((u_int64_t) full_length32) + 1;
+
+ alloc_length = (full_length64 > 4096) ? 4096 : full_length64;
+ str = malloc(alloc_length);
+ if (!str)
+ return -ENOMEM;
+
+ err = read_and_waste(fd, UCHAR(str), alloc_length - 1, full_length32);
+ if (err) {
+ free(str);
+ return err;
+ }
+
+ place_null_character(str, alloc_length);
+
+ *result = str;
+ return 0;
+ }
--- /dev/null
-#include "../updates_daemon.h"
+ #include "rtr.h"
+
+ #include <err.h>
+ #include <errno.h>
+ #include <netdb.h>
+ #include <pthread.h>
+ #include <signal.h>
+ #include <stdbool.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
+ #include <arpa/inet.h>
+ #include <sys/queue.h>
+
-#include "configuration.h"
-#include "err_pdu.h"
-#include "pdu.h"
++#include "config.h"
+ #include "clients.h"
-create_server_socket(void)
++#include "updates_daemon.h"
++#include "rtr/err_pdu.h"
++#include "rtr/pdu.h"
+
+ /* TODO Support both RTR v0 an v1 */
+ #define RTR_VERSION_SUPPORTED RTR_V0
+
+ volatile bool loop;
+
+ struct thread_node {
+ pthread_t tid;
+ SLIST_ENTRY(thread_node) next;
+ };
+
+ SLIST_HEAD(thread_list, thread_node) threads;
+
++static int
++init_addrinfo(struct addrinfo **result)
++{
++ char const *hostname;
++ char const *service;
++ struct addrinfo hints;
++ int error;
++
++ memset(&hints, 0 , sizeof(hints));
++ hints.ai_family = AF_UNSPEC;
++ /* hints.ai_socktype = SOCK_DGRAM; */
++ hints.ai_flags |= AI_PASSIVE;
++
++ hostname = config_get_server_address();
++ service = config_get_server_port();
++
++ error = getaddrinfo(hostname, service, &hints, result);
++ if (error) {
++ warnx("Could not infer a bindable address out of address '%s' and port '%s': %s",
++ (hostname != NULL) ? hostname : "any", service,
++ gai_strerror(error));
++ return error;
++ }
++
++ return 0;
++}
++
++
+ /*
+ * Creates the socket that will stay put and wait for new connections started
+ * from the clients.
+ */
+ static int
- struct addrinfo const *addr;
++create_server_socket(int *result)
+ {
- addr = config_get_server_addrinfo();
- for (; addr != NULL; addr = addr->ai_next) {
++ struct addrinfo *addrs;
++ struct addrinfo *addr;
+ int fd; /* "file descriptor" */
++ int error;
+
- return fd; /* Happy path */
++ error = init_addrinfo(&addrs);
++ if (error)
++ return error;
++
++ for (addr = addrs; addr != NULL; addr = addr->ai_next) {
+ printf(
+ "Attempting to bind socket to address '%s', port '%s'.\n",
+ (addr->ai_canonname != NULL) ? addr->ai_canonname : "any",
+ config_get_server_port());
+
+ fd = socket(addr->ai_family, SOCK_STREAM, 0);
+ if (fd < 0) {
+ warn("socket() failed");
+ continue;
+ }
+
+ if (bind(fd, addr->ai_addr, addr->ai_addrlen) < 0) {
+ warn("bind() failed");
+ continue;
+ }
+
+ printf("Success.\n");
- server_fd = create_server_socket();
- if (server_fd < 0)
- return server_fd;
++ freeaddrinfo(addrs);
++ *result = fd;
++ return 0; /* Happy path */
+ }
+
+ warnx("None of the addrinfo candidates could be bound.");
++ freeaddrinfo(addrs);
+ return -EINVAL;
+ }
+
+ /*
+ * Arguments that the server socket thread will send to the client socket
+ * threads whenever it creates them.
+ */
+ struct thread_param {
+ int client_fd;
+ struct sockaddr_storage client_addr;
+ };
+
+ enum verdict {
+ /* No errors; continue happily. */
+ VERDICT_SUCCESS,
+ /* A temporal error just happened. Try again. */
+ VERDICT_RETRY,
+ /* "Stop whatever you're doing and return." */
+ VERDICT_EXIT,
+ };
+
+ /*
+ * Converts an error code to a verdict.
+ * The error code is assumed to have been spewed by the `accept()` function.
+ */
+ static enum verdict
+ handle_accept_result(int client_fd, int err)
+ {
+ if (client_fd >= 0)
+ return VERDICT_SUCCESS;
+
+ /*
+ * Note: I can't just use a single nice switch because EAGAIN and
+ * EWOULDBLOCK are the same value in at least one supported system
+ * (Linux).
+ */
+
+ /*
+ * TODO this `if` is a Linux quirk and should probably not exist in the
+ * BSDs. See `man 2 accept`.
+ */
+ if (err == ENETDOWN || err == EPROTO || err == ENOPROTOOPT
+ || err == EHOSTDOWN || err == ENONET || err == EHOSTUNREACH
+ || err == EOPNOTSUPP || err == ENETUNREACH)
+ return VERDICT_RETRY;
+
++#pragma GCC diagnostic push
++#pragma GCC diagnostic ignored "-Wlogical-op"
+ if (err == EAGAIN || err == EWOULDBLOCK)
+ return VERDICT_RETRY;
++#pragma GCC diagnostic pop
+
+ errno = err;
+ warn("Connection acceptor thread interrupted");
+ return VERDICT_EXIT;
+ }
+
+ static void *
+ end_client(int client_fd, const struct pdu_metadata *meta, void *pdu)
+ {
+ if (meta != NULL && pdu != NULL)
+ meta->destructor(pdu);
+ clients_forget(client_fd);
+ return NULL;
+ }
+
+ /*
+ * The client socket threads' entry routine.
+ *
+ * Please remember that this function needs to always release @param_void
+ * before returning.
+ */
+ static void *
+ client_thread_cb(void *param_void)
+ {
+ struct thread_param param;
+ struct pdu_metadata const *meta;
+ void *pdu;
+ int err;
+ u_int8_t rtr_version;
+
+ memcpy(¶m, param_void, sizeof(param));
+
+ while (loop) { /* For each PDU... */
+ err = pdu_load(param.client_fd, &pdu, &meta, &rtr_version);
+ if (err)
+ return end_client(param.client_fd, NULL, NULL);
+
+ /* Protocol Version Negotiation */
+ if (rtr_version != RTR_VERSION_SUPPORTED) {
+ err_pdu_send(param.client_fd, RTR_VERSION_SUPPORTED,
+ ERR_PDU_UNSUP_PROTO_VERSION,
+ (struct pdu_header *) pdu, NULL);
+ return end_client(param.client_fd, meta, pdu);
+ }
+ /* RTR Version ready, now update client */
+ err = update_client(param.client_fd, ¶m.client_addr,
+ rtr_version);
+ if (err) {
+ if (err == -EINVAL) {
+ err_pdu_send(param.client_fd, rtr_version,
+ (rtr_version == RTR_V0
+ ? ERR_PDU_UNSUP_PROTO_VERSION
+ : ERR_PDU_UNEXPECTED_PROTO_VERSION),
+ (struct pdu_header *) pdu, NULL);
+ }
+ return end_client(param.client_fd, meta, pdu);
+ }
+
+ err = meta->handle(param.client_fd, pdu);
+ meta->destructor(pdu);
+ if (err)
+ return end_client(param.client_fd, NULL, NULL);
+ }
+
+ return NULL; /* Unreachable. */
+ }
+
+ /*
+ * Waits for client connections and spawns threads to handle them.
+ */
+ static int
+ handle_client_connections(int server_fd)
+ {
+ struct sockaddr_storage client_addr;
+ struct thread_param arg;
+ struct thread_node *new_thread;
+ socklen_t sizeof_client_addr;
+ pthread_attr_t attr;
+ int client_fd;
+
+ listen(server_fd, config_get_server_queue());
+
+ sizeof_client_addr = sizeof(client_addr);
+
+ do {
+ client_fd = accept(server_fd, (struct sockaddr *)&client_addr,
+ &sizeof_client_addr);
+ switch (handle_accept_result(client_fd, errno)) {
+ case VERDICT_SUCCESS:
+ break;
+ case VERDICT_RETRY:
+ continue;
+ case VERDICT_EXIT:
+ return 0;
+ }
+
+ /*
+ * Note: My gut says that errors from now on (even the unknown
+ * ones) should be treated as temporary; maybe the next
+ * accept() will work.
+ * So don't interrupt the thread when this happens.
+ */
+
+ new_thread = malloc(sizeof(struct thread_node));
+ if (new_thread == NULL) {
+ warnx("Couldn't create thread struct");
+ close(client_fd);
+ continue;
+ }
+
+ arg.client_fd = client_fd;
+ arg.client_addr = client_addr;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ errno = pthread_create(&new_thread->tid, &attr,
+ client_thread_cb, &arg);
+ pthread_attr_destroy(&attr);
+ if (errno) {
+ warn("Could not spawn the client's thread");
+ free(new_thread);
+ close(client_fd);
+ continue;
+ }
+
+ SLIST_INSERT_HEAD(&threads, new_thread, next);
+
+ } while (true);
+
+ return 0; /* Unreachable. */
+ }
+
+ static void
+ signal_handler(int signal, siginfo_t *info, void *param)
+ {
+ /* Empty handler */
+ }
+
+ static int
+ init_signal_handler(void)
+ {
+ struct sigaction act;
+ int error;
+
+ memset(&act, 0, sizeof act);
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_SIGINFO;
+ act.sa_sigaction = signal_handler;
+
+ error = sigaction(SIGINT, &act, NULL);
+ if (error) {
+ warn("Error initializing signal handler");
+ error = -errno;
+ }
+ return error;
+ }
+
+ /*
+ * Starts the server, using the current thread to listen for RTR client
+ * requests.
+ *
+ * This function blocks.
+ */
+ int
+ rtr_listen(void)
+ {
+ int server_fd; /* "file descriptor" */
+ int error;
+
++ error = create_server_socket(&server_fd);
++ if (error)
++ return error;
+
+ /* Server ready, start updates thread */
+ error = updates_daemon_start();
+ if (error)
+ return error;
+
+ /* Init global vars */
+ loop = true;
+ SLIST_INIT(&threads);
+
+ error = init_signal_handler();
+ if (error)
+ return error;
+
+ return handle_client_connections(server_fd);
+ }
+
+ void
+ rtr_cleanup(void)
+ {
+ struct thread_node *ptr;
+
+ updates_daemon_destroy();
+
+ /* Wait for threads to end gracefully */
+ loop = false;
+ while (!SLIST_EMPTY(&threads)) {
+ ptr = SLIST_FIRST(&threads);
+ SLIST_REMOVE_HEAD(&threads, next);
+ pthread_kill(ptr->tid, SIGINT);
+ pthread_join(ptr->tid, NULL);
+ free(ptr);
+ }
+ }
--- /dev/null
+#ifndef SRC_STR_H_
+#define SRC_STR_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <openssl/asn1.h>
+#include <openssl/x509.h>
+
+int ia5s2string(ASN1_IA5STRING *, char **);
+int BN2string(BIGNUM *, char **);
+
+/* This file is named "str.h" because "string.h" collides with <string.h>. */
+
+/**
+ * Do not modify fields directly; this should be private.
++ *
++ * This is more or less like strtok(), except it doesn't modify the string at
++ * any point.
+ */
+struct string_tokenizer {
+ /** String we're tokenizing. */
+ char const *str;
+ size_t str_len;
+ /** Token delimiter. */
+ unsigned char separator;
+ /** Offset of the first character of the current token. */
+ size_t start;
+ /** Offset of the last character of the current token + 1. */
+ size_t end;
+};
+
+void string_tokenizer_init(struct string_tokenizer *, char const *, size_t,
+ unsigned char);
+bool string_tokenizer_next(struct string_tokenizer *);
+bool token_equals(struct string_tokenizer *, struct string_tokenizer *);
+
+#endif /* SRC_STR_H_ */
--- /dev/null
- void
+#include "thread_var.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include "config.h"
+
+static pthread_key_t state_key;
+static pthread_key_t filenames_key;
+
+struct filename_stack {
+ /* This can be NULL. Abort all operations if this is the case. */
+ char const **filenames;
+ unsigned int len;
+ unsigned int size;
+};
+
+static void
+fnstack_discard(void *arg)
+{
+ struct filename_stack *files = arg;
+ free(files->filenames);
+ free(files);
+}
+
+/** Initializes this entire module. Call once per runtime lifetime. */
- exit(error);
++int
+thvar_init(void)
+{
+ int error;
+
+ error = pthread_key_create(&state_key, NULL);
+ if (error) {
+ fprintf(stderr,
+ "Fatal: Errcode %d while initializing the validation state thread variable.\n",
+ error);
- exit(error);
++ return error;
+ }
+
+ /*
+ * Hm. It's a little odd.
+ * fnstack_discard() is not being called on program termination.
+ * Not sure if this is an implementation quirk.
+ * We'll just have to delete it manually.
+ */
+ error = pthread_key_create(&filenames_key, fnstack_discard);
+ if (error) {
+ fprintf(stderr,
+ "Fatal: Errcode %d while initializing the file name stack thread variable.\n",
+ error);
++ return error;
+ }
++
++ return 0;
+}
+
+/* Puts @state in the current thread's variable pool. Call once per thread. */
+int
+state_store(struct validation *state)
+{
+ int error;
+
+ error = pthread_setspecific(state_key, state);
+ if (error)
+ fprintf(stderr, "pthread_setspecific() returned %d.", error);
+
+ return error;
+}
+
+/* Returns the current thread's validation state. */
+struct validation *
+state_retrieve(void)
+{
+ struct validation *state;
+
+ state = pthread_getspecific(state_key);
+ if (state == NULL)
+ fprintf(stderr, "Programming error: This thread lacks a validation state.\n");
+
+ return state;
+}
+
+/** Initializes the current thread's fnstack. Call once per thread. */
+void
+fnstack_init(void)
+{
+ struct filename_stack *files;
+ int error;
+
+ files = malloc(sizeof(struct filename_stack));
+ if (files == NULL)
+ return;
+
+ files->filenames = malloc(32 * sizeof(char *));
+ if (files->filenames == NULL) {
+ free(files);
+ return;
+ }
+
+ files->len = 0;
+ files->size = 32;
+
+ error = pthread_setspecific(filenames_key, files);
+ if (error)
+ fprintf(stderr, "pthread_setspecific() returned %d.", error);
+}
+
+void
+fnstack_cleanup(void)
+{
+ struct filename_stack *files;
+ int error;
+
+ files = pthread_getspecific(filenames_key);
+ if (files == NULL)
+ return;
+
+ fnstack_discard(files);
+
+ error = pthread_setspecific(filenames_key, NULL);
+ if (error)
+ fprintf(stderr, "pthread_setspecific() returned %d.", error);
+}
+
+static struct filename_stack *
+get_file_stack(void)
+{
+ struct filename_stack *files;
+
+ files = pthread_getspecific(filenames_key);
+ if (files == NULL)
+ fprintf(stderr, "This thread lacks a files stack.\n");
+
+ return files;
+}
+
+/**
+ * Call this function every time you're about to start processing a new file.
+ * Any pr_err()s and friends will now include the new file name.
+ * Use fnstack_pop() to revert back to the previously stacked file name.
+ * @file is not cloned; it's expected to outlive the push/pop operation.
+ */
+void
+fnstack_push(char const *file)
+{
+ struct filename_stack *files;
+ char const **tmp;
+
+ files = get_file_stack();
+ if (files == NULL || files->filenames == NULL)
+ return;
+
+ if (files->len >= files->size) {
+ tmp = realloc(files->filenames, 2 * files->size * sizeof(char *));
+ if (tmp == NULL) {
+ /* Oh noes */
+ free(files->filenames);
+ files->filenames = NULL;
+ return;
+ }
+
+ files->filenames = tmp;
+ files->size *= 2;
+ }
+
+ files->filenames[files->len++] = file;
+}
+
+/** See fnstack_push(). */
+void
+fnstack_push_uri(struct rpki_uri const *uri)
+{
+ fnstack_push(uri_get_printable(uri));
+}
+
+/* Returns the file name on the top of the file name stack. */
+char const *
+fnstack_peek(void)
+{
+ struct filename_stack *files;
+
+ files = get_file_stack();
+ if (files == NULL || files->filenames == NULL || files->len == 0)
+ return NULL;
+
+ return files->filenames[files->len - 1];
+}
+
+/* Reverts the last fnstack_push(). */
+void
+fnstack_pop(void)
+{
+ struct filename_stack *files;
+
+ files = get_file_stack();
+ if (files == NULL || files->filenames == NULL || files->len == 0)
+ return;
+
+ files->len--;
+}
+
+static char const *
+addr2str(int af, void *addr, char *(*buffer_cb)(struct validation *))
+{
+ struct validation *state;
+
+ state = state_retrieve();
+ if (!state)
+ return NULL;
+
+ return inet_ntop(af, addr, buffer_cb(state), INET6_ADDRSTRLEN);
+}
+
+/**
+ * Returns @addr, converted to a printable string. Intended for minimal clutter
+ * address printing.
+ *
+ * The buffer the string is stored in was allocated in a thread variable, so it
+ * will be overridden the next time you call this function. Also, you should not
+ * free it.
+ *
+ * The buffer is the same as v6addr2str()'s, so don't mix them either.
+ */
+char const *
+v4addr2str(struct in_addr *addr)
+{
+ return addr2str(AF_INET, addr, validation_get_ip_buffer1);
+}
+
+/**
+ * Same as v4addr2str(), except a different buffer is used.
+ */
+char const *
+v4addr2str2(struct in_addr *addr)
+{
+ return addr2str(AF_INET, addr, validation_get_ip_buffer2);
+}
+
+/**
+ * See v4addr2str().
+ */
+char const *
+v6addr2str(struct in6_addr *addr)
+{
+ return addr2str(AF_INET6, addr, validation_get_ip_buffer1);
+}
+
+/**
+ * See v4addr2str2().
+ */
+char const *
+v6addr2str2(struct in6_addr *addr)
+{
+ return addr2str(AF_INET6, addr, validation_get_ip_buffer2);
+}
--- /dev/null
- void thvar_init(void);
+#ifndef SRC_THREAD_VAR_H_
+#define SRC_THREAD_VAR_H_
+
+#include "state.h"
+
++int thvar_init(void); /* This function does not need cleanup. */
+
+int state_store(struct validation *);
+struct validation *state_retrieve(void);
+
+void fnstack_init(void);
+void fnstack_cleanup(void);
+
+void fnstack_push(char const *);
+void fnstack_push_uri(struct rpki_uri const *);
+char const *fnstack_peek(void);
+void fnstack_pop(void);
+
+char const *v4addr2str(struct in_addr *addr);
+char const *v4addr2str2(struct in_addr *addr);
+char const *v6addr2str(struct in6_addr *addr);
+char const *v6addr2str2(struct in6_addr *addr);
+
+#endif /* SRC_THREAD_VAR_H_ */
--- /dev/null
-#include "csv.h"
-#include "configuration.h"
+ #include "updates_daemon.h"
+
+ #include <err.h>
+ #include <errno.h>
+ #include <pthread.h>
+ #include <stdbool.h>
+ #include <unistd.h>
+
-pthread_t thread;
++#include "config.h"
+ #include "notify.h"
++#include "object/tal.h"
+
-check_vrps_updates(void *param_void) {
++static pthread_t thread;
+
+ static void *
- error = csv_check_vrps_file(&updated);
++check_vrps_updates(void *param_void)
++{
+ int error;
+ bool updated;
++
+ do {
+ updated = false;
- warnx("Error while searching CSV updates, sleeping..");
++ error = perform_standalone_validation(&updated);
+ if (error) {
++ warnx("Error '%d' while searching CSV updates, sleeping...",
++ error);
+ goto sleep;
+ }
+ if (updated)
+ notify_clients();
+ sleep:
+ sleep(config_get_vrps_check_interval());
+ } while (true);
+
+ return NULL;
+ }
+
+ int
+ updates_daemon_start(void)
+ {
+ pthread_attr_t attr;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ errno = pthread_create(&thread, NULL, check_vrps_updates, NULL);
+ pthread_attr_destroy(&attr);
+ if (errno) {
+ warn("Could not spawn the update daemon thread");
+ return -errno;
+ }
+
+ return 0;
+ }
+
+ void
+ updates_daemon_destroy(void)
+ {
+ void *ptr = NULL;
+ pthread_cancel(thread);
+ pthread_join(thread, &ptr);
+ }
--- /dev/null
- if (error){
- warnx("Delta base DB couldn't be initialized");
+ #include "vrps.h"
+
+ #include <stdbool.h>
+ #include <string.h>
+ #include "array_list.h"
+ #include "common.h"
+
+ /*
+ * Storage of VRPs (term taken from RFC 6811 "Validated ROA Payload") and
+ * Serials that contain such VRPs
+ */
+
+ #define FLAG_WITHDRAWAL 0
+ #define FLAG_ANNOUNCEMENT 1
+ #define START_SERIAL 0
+
+ ARRAY_LIST(vrps, struct vrp)
+
+ struct delta {
+ u_int32_t serial;
+ struct vrps vrps;
+ };
+
+ ARRAY_LIST(deltasdb, struct delta)
+
+ struct state {
+ /** The current valid ROAs, freshly loaded from the file */
+ struct delta base_db;
+ /** ROA changes over time */
+ struct deltasdb deltas_db;
+ u_int32_t current_serial;
+ u_int16_t v0_session_id;
+ u_int16_t v1_session_id;
+ time_t last_modified_date;
+ } state;
+
+ /* Read and Write locks */
+ sem_t rlock, wlock;
+
+ /* Readers counter */
+ unsigned int rcounter;
+
+ static int
+ delta_init(struct delta *delta)
+ {
+ return vrps_init(&delta->vrps);
+ }
+
+ static void
+ vrp_destroy(struct vrp *vrp)
+ {
+ /* Didn't allocate something, so do nothing */
+ }
+
+ static void
+ delta_destroy(struct delta *delta)
+ {
+ vrps_cleanup(&delta->vrps, vrp_destroy);
+ }
+
+ int
+ deltas_db_init(void)
+ {
+ int error, shift;
+
+ error = delta_init(&state.base_db);
- }
++ if (error)
+ return error;
- warnx("Deltas DB couldn't be initialized");
+
+ error = deltasdb_init(&state.deltas_db);
+ if (error) {
- result.ipv4_prefix = ipv4_prefix;
+ delta_destroy(&state.base_db);
+ return error;
+ }
+
+ /*
+ * Use the same start serial, the session ID will avoid
+ * "desynchronization" (more at RFC 6810 'Glossary' and
+ * 'Fields of a PDU')
+ */
+ state.current_serial = START_SERIAL;
+
+ /* Get the bits that'll fit in session_id */
+ shift = sizeof(time_t) - sizeof(state.v0_session_id);
+ state.v0_session_id = (u_int16_t)((time(NULL) << shift) >> shift);
+ /* Minus 1 to prevent same ID */
+ state.v1_session_id = state.v0_session_id - 1;
+
+ sem_init(&rlock, 0, 1);
+ sem_init(&wlock, 0, 1);
+ rcounter = 0;
+
+ return 0;
+ }
+
+ static void
+ init_vrp (struct vrp *vrp, u_int32_t asn, u_int8_t prefix_length,
+ u_int8_t max_prefix_length)
+ {
+ vrp->asn = asn;
+ vrp->prefix_length = prefix_length;
+ vrp->max_prefix_length = max_prefix_length;
+ /* Set as ANNOUNCEMENT by default */
+ vrp->flags = FLAG_ANNOUNCEMENT;
+ }
+
+ struct vrp
+ create_vrp4(u_int32_t asn, struct in_addr ipv4_prefix, u_int8_t prefix_length,
+ u_int8_t max_prefix_length)
+ {
+ struct vrp result;
+
+ init_vrp(&result, asn, prefix_length, max_prefix_length);
- result.ipv6_prefix = ipv6_prefix;
++ result.prefix.ipv4 = ipv4_prefix;
+ result.addr_fam = AF_INET;
+
+ return result;
+ }
+
+ struct vrp
+ create_vrp6(u_int32_t asn, struct in6_addr ipv6_prefix, u_int8_t prefix_length,
+ u_int8_t max_prefix_length)
+ {
+ struct vrp result;
+
+ init_vrp(&result, asn, prefix_length, max_prefix_length);
- && left->ipv4_prefix.s_addr == right->ipv4_prefix.s_addr)
++ result.prefix.ipv6 = ipv6_prefix;
+ result.addr_fam = AF_INET6;
+
+ return result;
+ }
+
+ static bool
+ vrp_equal(struct vrp *left, struct vrp *right)
+ {
+ return left->asn == right->asn
+ && left->addr_fam == right->addr_fam
+ && left->prefix_length == right->prefix_length
+ && left->max_prefix_length == right->max_prefix_length
+ && ((left->addr_fam == AF_INET
- && IN6_ARE_ADDR_EQUAL(left->ipv6_prefix.s6_addr32,
- right->ipv6_prefix.s6_addr32)));
++ && left->prefix.ipv4.s_addr == right->prefix.ipv4.s_addr)
+ || (left->addr_fam == AF_INET6
- warnx("Couldn't add announcement to summary");
++ && IN6_ARE_ADDR_EQUAL(left->prefix.ipv6.s6_addr32,
++ right->prefix.ipv6.s6_addr32)));
+ }
+
+ static struct vrp *
+ vrp_locate(struct vrps *base, struct vrp *vrp)
+ {
+ struct vrp *cursor;
+
+ ARRAYLIST_FOREACH(base, cursor)
+ if (vrp_equal(cursor, vrp))
+ return cursor;
+
+ return NULL;
+ }
+
+ static bool
+ vrp_is_new(struct vrps *base, struct vrp *vrp)
+ {
+ return vrp_locate(base, vrp) == NULL;
+ }
+
+ static int
+ delta_add_vrp(struct delta *delta, struct vrp *vrp)
+ {
+ return vrps_add(&delta->vrps, vrp);
+ }
+
+ static int
+ delta_summary(struct delta *base_delta, struct delta *result)
+ {
+ struct vrps *base, *search_list;
+ struct vrp *cursor;
+ int error;
+
+ /*
+ * Note: Don't fix this function yet.
+ * I realize why you implemented it this way, and I'm trying to come up
+ * with a more efficient algorithm.
+ */
+
+ error = delta_init(result);
+ if (error)
+ return error;
+
+ result->serial = base_delta->serial;
+ read_lock(&rlock, &wlock, &rcounter);
+ /* First check for announcements */
+ base = &base_delta->vrps;
+ search_list = &state.base_db.vrps;
+ ARRAYLIST_FOREACH(base, cursor)
+ if (vrp_is_new(search_list, cursor)) {
+ cursor->flags = FLAG_ANNOUNCEMENT;
+ error = delta_add_vrp(result, cursor);
+ if (error) {
- warnx("Couldn't add withdrawal to summary");
+ read_unlock(&rlock, &wlock, &rcounter);
+ return error;
+ }
+ }
+
+ /* Now for withdrawals */
+ base = &state.base_db.vrps;
+ search_list = &base_delta->vrps;
+ ARRAYLIST_FOREACH(base, cursor)
+ if (vrp_is_new(search_list, cursor)) {
+ cursor->flags = FLAG_WITHDRAWAL;
+ error = delta_add_vrp(result, cursor);
+ if (error) {
- warnx("Error summarizing new delta");
+ read_unlock(&rlock, &wlock, &rcounter);
+ return error;
+ }
+ }
+
+ read_unlock(&rlock, &wlock, &rcounter);
+ return 0;
+ }
+
+ static int
+ deltas_db_add_delta(struct delta delta)
+ {
+ struct delta summary;
+ int result;
+
+ result = 0;
+ read_lock(&rlock, &wlock, &rcounter);
+ delta.serial = state.current_serial;
+ read_unlock(&rlock, &wlock, &rcounter);
+ /* Store only updates */
+ if (delta.serial != START_SERIAL) {
+ result = delta_summary(&delta, &summary);
+ if (result != 0) {
- warnx("Error persisting new delta");
++ pr_err("Error summarizing new delta");
+ return result;
+ }
+ sem_wait(&wlock);
+ result = deltasdb_add(&state.deltas_db, &summary);
+ sem_post(&wlock);
+ }
+ /* Don't set the base in case of error */
+ if (result != 0) {
- warn("Couldn't copy VRPs");
++ pr_err("Error persisting new delta");
+ return result;
+ }
+
+ sem_wait(&wlock);
+ free(state.base_db.vrps.array);
+ state.base_db = delta;
+ state.current_serial++;
+ sem_post(&wlock);
+ return result;
+ }
+
+ static void
+ copy_vrps(struct vrp **dst, struct vrp *src, unsigned int len)
+ {
+ struct vrp *tmp;
+ tmp = realloc(*dst, len * sizeof(struct vrp));
+ if (tmp == NULL) {
- warnx("New Delta couldn't be initialized");
++ pr_enomem();
+ return;
+ }
+ *dst = tmp;
+ memcpy(*dst, src, len * sizeof(struct vrp));
+ }
+
+ int
+ deltas_db_create_delta(struct vrp *array, unsigned int len)
+ {
+ struct delta new_delta;
+ int error;
+
+ error = delta_init(&new_delta);
+ if (error) {
++ pr_err("New Delta couldn't be initialized");
+ return error;
+ }
+
+ copy_vrps(&new_delta.vrps.array, array, len);
+ new_delta.vrps.len = len;
+ new_delta.vrps.capacity = len * sizeof(struct vrp);
+
+ error = deltas_db_add_delta(new_delta);
+ if (error)
+ return error;
+
+ return 0;
+ }
+
+ void
+ deltas_db_destroy(void)
+ {
+ sem_wait(&wlock);
+ delta_destroy(&state.base_db);
+ deltasdb_cleanup(&state.deltas_db, delta_destroy);
+ sem_post(&wlock);
+
+ sem_destroy(&wlock);
+ sem_destroy(&rlock);
+ }
+
+ /*
+ * Get a status to know the difference between the delta with serial SERIAL and
+ * the last delta at DB.
+ *
+ * If SERIAL is received as NULL, and there's data at DB then the status will
+ * be DIFF_AVAILABLE.
+ *
+ * The possible return values are:
+ * NO_DATA_AVAILABLE -> There's no data at the DB
+ * DIFF_UNDETERMINED -> The diff can't be determined
+ * NO_DIFF -> There's no difference
+ * DIFF_AVAILABLE -> There are diffs between SERIAL and the last DB serial
+ */
+ int
+ deltas_db_status(u_int32_t *serial)
+ {
+ struct delta *delta;
+ int result;
+
+ read_lock(&rlock, &wlock, &rcounter);
+ if (state.base_db.vrps.len == 0) {
+ result = NO_DATA_AVAILABLE;
+ goto end;
+ }
+
+ /* No serial to match, and there's data at DB */
+ if (serial == NULL) {
+ result = DIFF_AVAILABLE;
+ goto end;
+ }
+
+ /* Is the last version? */
+ if (*serial == state.base_db.serial) {
+ result = NO_DIFF;
+ goto end;
+ }
+
+ /* Get the delta corresponding to the serial */
+ ARRAYLIST_FOREACH(&state.deltas_db, delta)
+ if (delta->serial == *serial) {
+ result = DIFF_AVAILABLE;
+ goto end;
+ }
+
+ /* No match yet, release lock */
+ read_unlock(&rlock, &wlock, &rcounter);
+
+ /* The first serial isn't at deltas */
+ if (*serial == START_SERIAL)
+ return DIFF_AVAILABLE;
+
+ /* Reached end, diff can't be determined */
+ return DIFF_UNDETERMINED;
+ end:
+ read_unlock(&rlock, &wlock, &rcounter);
+ return result;
+ }
+
+ static void
+ add_vrps_filtered(struct vrps *dst, struct vrps *src)
+ {
+ struct vrp *ptr;
+ for (ptr = src->array; (ptr - src->array) < src->len; ptr++)
+ if (vrp_is_new(dst, ptr))
+ vrps_add(dst, ptr);
+ }
+
+ /*
+ * Get the number of updates from serial START_SERIAL to END_SERIAL, set them
+ * at RESULT.
+ *
+ * Return 0 if no updates are available or couldn't be calculated with the
+ * received values.
+ */
+ unsigned int
+ get_vrps_delta(u_int32_t *start_serial, u_int32_t *end_serial,
+ struct vrp **result)
+ {
+ struct delta *delta1;
+ struct vrps summary;
+ unsigned int vrps_len;
+
+ read_lock(&rlock, &wlock, &rcounter);
+ /* No data */
+ if (state.base_db.vrps.len == 0) {
+ read_unlock(&rlock, &wlock, &rcounter);
+ return 0;
+ }
+
+ /* NULL start? Send the last version, there's no need to iterate DB */
+ if (start_serial == NULL) {
+ copy_vrps(result, state.base_db.vrps.array,
+ state.base_db.vrps.len);
+ vrps_len = state.base_db.vrps.len;
+ read_unlock(&rlock, &wlock, &rcounter);
+ return vrps_len;
+ }
+
+ /* Apparently nothing to return */
+ if (*start_serial >= *end_serial) {
+ read_unlock(&rlock, &wlock, &rcounter);
+ return 0;
+ }
+
+ /* Get the delta corresponding to the serials */
+ vrps_init(&summary);
+ ARRAYLIST_FOREACH(&state.deltas_db, delta1) {
+ if (delta1->serial > *start_serial)
+ add_vrps_filtered(&summary, &delta1->vrps);
+ if (delta1->serial == *end_serial)
+ break;
+ }
+ read_unlock(&rlock, &wlock, &rcounter);
+
+ copy_vrps(result, summary.array, summary.len);
+ vrps_cleanup(&summary, vrp_destroy);
+ return summary.len;
+ }
+
+ void
+ set_vrps_last_modified_date(time_t new_date)
+ {
+ sem_wait(&wlock);
+ state.last_modified_date = new_date;
+ sem_post(&wlock);
+ }
+
+ u_int32_t
+ get_last_serial_number(void)
+ {
+ u_int32_t serial;
+
+ read_lock(&rlock, &wlock, &rcounter);
+ serial = state.current_serial - 1;
+ read_unlock(&rlock, &wlock, &rcounter);
+
+ return serial;
+ }
+
+ u_int16_t
+ get_current_session_id(u_int8_t rtr_version)
+ {
+ /* Semaphore isn't needed since this value is set at initialization */
+ if (rtr_version == 1)
+ return state.v1_session_id;
+ return state.v0_session_id;
+ }
+
+ time_t
+ get_vrps_last_modified_date(void)
+ {
+ time_t date;
+
+ read_lock(&rlock, &wlock, &rcounter);
+ date = state.last_modified_date;
+ read_unlock(&rlock, &wlock, &rcounter);
+
+ return date;
+ }
--- /dev/null
- struct in_addr ipv4_prefix;
- struct in6_addr ipv6_prefix;
- };
+ #ifndef SRC_VRPS_H_
+ #define SRC_VRPS_H_
+
+ #include <time.h>
+ #include <netinet/ip.h>
+
+ #define NO_DATA_AVAILABLE -2
+ #define DIFF_UNDETERMINED -1
+ #define NO_DIFF 0
+ #define DIFF_AVAILABLE 1
+
+ struct vrp {
+ u_int32_t asn;
+ union {
++ struct in_addr ipv4;
++ struct in6_addr ipv6;
++ } prefix;
+ u_int8_t prefix_length;
+ u_int8_t max_prefix_length;
+ u_int8_t addr_fam;
+ u_int8_t flags;
+ };
+
+ int deltas_db_init(void);
+
+ struct vrp create_vrp4(u_int32_t, struct in_addr, u_int8_t, u_int8_t);
+ struct vrp create_vrp6(u_int32_t, struct in6_addr, u_int8_t, u_int8_t);
+
+ int deltas_db_create_delta(struct vrp *, unsigned int);
+ int deltas_db_status(u_int32_t *);
+
+ unsigned int get_vrps_delta(u_int32_t *, u_int32_t *, struct vrp **);
+
+ void deltas_db_destroy(void);
+ void set_vrps_last_modified_date(time_t);
+
+ u_int32_t get_last_serial_number(void);
+ u_int16_t get_current_session_id(u_int8_t);
+ time_t get_vrps_last_modified_date(void);
+
+ #endif /* SRC_VRPS_H_ */
-# mumble_mumble_CFLAGS = $(AM_CFLAGS) flag1 flag2 flag3 ...
-AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src @CHECK_CFLAGS@
-
+# Reminder: `make check`
+
+# If you want to only run one test, do `make check TESTS=<test>`.
+# Example: `make check TESTS=address`
+
+# Once compiled, you can also just run the test directly: `./address.test`
+# You can easily see standard output and error this way.
+
+ # Reminder: Automake will automatically add this to any targets where
+ # <mumble>_CFLAGS is not defined.
+ # Otherwise it must be included manually:
-MY_LDADD = $(CHECK_LIBS)
++# mumble_mumble_CFLAGS = ${AM_CFLAGS} flag1 flag2 flag3 ...
+AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS}
+ # Reminder: As opposed to AM_CFLAGS, "AM_LDADD" is not idiomatic automake, and
+ # autotools will even reprehend us if we declare it. Therefore, I came up with
+ # "my" own "ldadd". Unlike AM_CFLAGS, it needs to be manually added to every
+ # target.
+MY_LDADD = ${CHECK_LIBS}
+BASIC_MODULES = ../src/log.c ../src/log.h
+BASIC_MODULES += impersonator.c
-check_PROGRAMS = rtr/primitive_reader.test rtr/pdu.test address.test
-TESTS = $(check_PROGRAMS)
+check_PROGRAMS = address.test
+check_PROGRAMS += vcard.test
+check_PROGRAMS += line_file.test
+check_PROGRAMS += rsync.test
+check_PROGRAMS += tal.test
++check_PROGRAMS += rtr/primitive_reader.test
++check_PROGRAMS += rtr/pdu.test
+TESTS = ${check_PROGRAMS}
-rtr_primitive_reader_test_SOURCES = \
- rtr/primitive_reader_test.c \
- rtr/stream.c
-rtr_primitive_reader_test_LDADD = $(MY_LDADD)
+address_test_SOURCES = ${BASIC_MODULES}
+address_test_SOURCES += ../src/address.h
+address_test_SOURCES += address_test.c
+address_test_LDADD = ${MY_LDADD}
- vcard_test_SOURCES = ${BASIC_MODULES}
- vcard_test_SOURCES += vcard_test.c
- vcard_test_LDADD = ${MY_LDADD}
-
-rtr_pdu_test_SOURCES = \
- rtr/pdu_test.c \
- rtr/stream.c \
- $(top_builddir)/src/rtr/primitive_reader.c \
- $(top_builddir)/src/rtr/pdu_handler.c
-rtr_pdu_test_LDADD = $(MY_LDADD)
+line_file_test_SOURCES = ${BASIC_MODULES}
+line_file_test_SOURCES += ../src/file.c ../src/file.h
+line_file_test_SOURCES += ../src/line_file.c ../src/line_file.h
+line_file_test_SOURCES += line_file_test.c
+line_file_test_LDADD = ${MY_LDADD}
+
+rsync_test_SOURCES = ${BASIC_MODULES}
+rsync_test_SOURCES += ../src/str.c ../src/str.h
+rsync_test_SOURCES += ../src/uri.c ../src/uri.h
+rsync_test_SOURCES += rsync_test.c
+rsync_test_LDADD = ${MY_LDADD}
+
++rtr_primitive_reader_test_SOURCES = ${BASIC_MODULES}
++rtr_primitive_reader_test_SOURCES += rtr/primitive_reader_test.c
++rtr_primitive_reader_test_SOURCES += rtr/stream.c
++rtr_primitive_reader_test_LDADD = ${MY_LDADD}
++
++rtr_pdu_test_SOURCES = ${BASIC_MODULES}
++rtr_pdu_test_SOURCES += rtr/pdu_test.c
++rtr_pdu_test_SOURCES += rtr/stream.c
++rtr_pdu_test_SOURCES += $(top_builddir)/src/rtr/primitive_reader.c
++rtr_pdu_test_SOURCES += $(top_builddir)/src/rtr/pdu_handler.c
++rtr_pdu_test_LDADD = ${MY_LDADD}
++
+tal_test_SOURCES = ${BASIC_MODULES}
+tal_test_SOURCES += ../src/file.c ../src/file.h
+tal_test_SOURCES += ../src/crypto/base64.c ../src/crypto/base64.h
+tal_test_SOURCES += ../src/random.c ../src/random.h
+tal_test_SOURCES += ../src/str.c ../src/str.h
+tal_test_SOURCES += ../src/uri.c ../src/uri.h
+tal_test_SOURCES += ../src/line_file.c ../src/line_file.h
+tal_test_SOURCES += tal_test.c
- tal_test_CFLAGS = ${AM_CFLAGS}
+tal_test_LDADD = ${MY_LDADD}
++
++vcard_test_SOURCES = ${BASIC_MODULES}
++vcard_test_SOURCES += vcard_test.c
++vcard_test_LDADD = ${MY_LDADD}
+
-address_test_SOURCES = ../src/address.h
-address_test_SOURCES += address_test.c
-address_test_LDADD = $(MY_LDADD)
}
END_TEST
+static void
+test_range6(uint32_t a1a, uint32_t a1b, uint32_t a1c, uint32_t a1d,
+ uint32_t a2a, uint32_t a2b, uint32_t a2c, uint32_t a2d,
+ bool valid)
+{
+ struct ipv6_range range;
+
+ addr6_set_quadrant(&range.min, 0, a1a);
+ addr6_set_quadrant(&range.min, 1, a1b);
+ addr6_set_quadrant(&range.min, 2, a1c);
+ addr6_set_quadrant(&range.min, 3, a1d);
+ addr6_set_quadrant(&range.max, 0, a2a);
+ addr6_set_quadrant(&range.max, 1, a2b);
+ addr6_set_quadrant(&range.max, 2, a2c);
+ addr6_set_quadrant(&range.max, 3, a2d);
+
+ ck_assert_int_eq(valid ? 0 : -EINVAL, check_encoding6(&range));
+}
+
+START_TEST(check_encoding6_test)
+{
+ test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ false);
+ test_range6(0x12345678, 0x12345678, 0x12345678, 0x12345678,
+ 0x12345678, 0x12345678, 0x12345678, 0x12345678,
+ false);
+ test_range6(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ false);
+
+ test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ false);
+ test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000001,
+ false);
+
+ /* Matching most significant bits stop on the first quadrant */
+ test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ false);
+
+ test_range6(0x00001010, 0x00000000, 0x00000000, 0x00000000,
+ 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
+ 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00000000, 0x00001000, 0x00000000,
+ 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00000000, 0x00000000, 0x00001000,
+ 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+
+ test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00001F0F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00001FFF, 0xFF0FFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00001FFF, 0xFFFFFFFF, 0xFFFFF0FF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF0FF,
+ true);
+
+ /* Matching most significant bits stop on the second quadrant */
+ test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
+ 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ false);
+
+ test_range6(0x00001000, 0x00001010, 0x00000000, 0x00000000,
+ 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000,
+ 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00001000, 0x00000000, 0x00001000,
+ 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+
+ test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
+ 0x00001000, 0x00001F0F, 0xFFFFFFFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
+ 0x00001000, 0x00001FFF, 0xFFFFF0FF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000,
+ 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFF0FF,
+ true);
+
+ /* Matching most significant bits stop on the third quadrant */
+ test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000,
+ 0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF,
+ false);
+
+ test_range6(0x00001000, 0x00001000, 0x00001010, 0x00000000,
+ 0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000,
+ 0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF,
+ true);
+
+ test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000,
+ 0x00001000, 0x00001000, 0x00001F0F, 0xFFFFFFFF,
+ true);
+ test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000,
+ 0x00001000, 0x00001000, 0x00001FFF, 0xFFFFF0FF,
+ true);
+
+ /* Matching most significant bits stop on the fourth quadrant */
+ test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000,
+ 0x00001000, 0x00001000, 0x00001000, 0x00001FFF,
+ false);
+
+ test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001010,
+ 0x00001000, 0x00001000, 0x00001000, 0x00001FFF,
+ true);
+
+ test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000,
+ 0x00001000, 0x00001000, 0x00001000, 0x00001F0F,
+ true);
+}
+END_TEST
+
+ static void
+ test_get_address_from_string(char *text_prefix)
+ {
+ struct ipv4_prefix prefix;
+ const char *result;
+ int error;
+ char buffer[INET_ADDRSTRLEN];
+
+ error = prefix4_decode(text_prefix, &prefix);
+ if (error)
+ return;
+
+ result = addr2str4(&prefix.addr, buffer);
+
+ ck_assert_str_eq(text_prefix, result);
+ }
+
+ START_TEST(address_test_get_addr)
+ {
+ char *text;
+ text = "198.248.146.0";
+
+ test_get_address_from_string(text);
+
+ }
+ END_TEST
+
Suite *address_load_suite(void)
{
Suite *suite;
- TCase *core, *test_get_address;
+ TCase *core;
++ TCase *string;
core = tcase_create("Core");
- tcase_add_test(core, load_normal);
+ tcase_add_test(core, check_encoding4_test);
+ tcase_add_test(core, check_encoding6_test);
- test_get_address = tcase_create("test_get_address");
- tcase_add_test(test_get_address, address_test_get_addr);
++ string = tcase_create("Strings");
++ tcase_add_test(string, address_test_get_addr);
+
- suite = suite_create("address_test()");
+ suite = suite_create("Encoding checking");
suite_add_tcase(suite, core);
- suite_add_tcase(suite, test_get_address);
-
return suite;
}
--- /dev/null
- return -abs(err);
+ #include "stream.h"
+
+ #include <err.h>
+ #include <errno.h>
+ #include <stdlib.h>
+ #include <unistd.h>
+
+ #include "common.h"
+
+ /*
+ * Writes exactly @length bytes from @buffer to the file descriptor @fd.
+ * All or nothing.
+ *
+ * The result is zero on success, nonzero on failure.
+ */
+ int
+ write_exact(int fd, unsigned char *buffer, size_t length)
+ {
+ size_t written;
+ int written_now;
+
+ for (written = 0; written < length; written += written_now) {
+ written_now = write(fd, buffer + written, length - written);
+ if (written_now == -1)
+ return errno;
+ }
+
+ return 0;
+ }
+
+ /*
+ * "Converts" the buffer @buffer (sized @size) to a file descriptor (FD).
+ * You will get @buffer if you `read()` the FD.
+ *
+ * If the result is not negative, then you're receiving the resulting FD.
+ * If the result is negative, it's an error code.
+ *
+ * Note that you need to close the FD when you're done reading it.
+ */
+ int
+ buffer2fd(unsigned char *buffer, size_t size)
+ {
+ int fd[2];
+ int err;
+
+ if (pipe(fd) == -1) {
+ err = errno;
+ warn("Pipe creation failed");
- return -abs(err);
++ return ENSURE_NEGATIVE(err);
+ }
+
+ err = write_exact(fd[1], buffer, size);
+ close(fd[1]);
+ if (err) {
+ errno = err;
+ warn("Pipe write failed");
+ close(fd[0]);
++ return ENSURE_NEGATIVE(err);
+ }
+
+ return fd[0];
+ }