summaryrefslogtreecommitdiffstats
path: root/tools/rtrclient.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/rtrclient.c1013
1 files changed, 1013 insertions, 0 deletions
diff --git a/tools/rtrclient.c b/tools/rtrclient.c
new file mode 100644
index 0000000..6282fac
--- /dev/null
+++ b/tools/rtrclient.c
@@ -0,0 +1,1013 @@
+/*
+ * This file is part of RTRlib.
+ *
+ * This file is subject to the terms and conditions of the MIT license.
+ * See the file LICENSE in the top level directory for more details.
+ *
+ * Website: http://rtrlib.realmv6.org/
+ */
+
+#include "templates.h"
+
+#include "rtrlib/rtrlib.h"
+
+#include "third-party/mustach.h"
+#include "third-party/tommyds/tommyarray.h"
+#include "third-party/tommyds/tommyhashlin.h"
+
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifdef RTRLIB_HAVE_LIBSSH
+// constants for utf8 validation
+static const uint8_t UTF8_ONE_BYTE_PREFIX = 0b00000000;
+static const uint8_t UTF8_ONE_BYTE_MASK = 0b10000000;
+static const uint8_t UTF8_TWO_BYTE_PREFIX = 0b11000000;
+static const uint8_t UTF8_TWO_BYTE_MASK = 0b11100000;
+static const uint8_t UTF8_THREE_BYTE_PREFIX = 0b11100000;
+static const uint8_t UTF8_THREE_BYTE_MASK = 0b11110000;
+static const uint8_t UTF8_FOUR_BYTE_PREFIX = 0b11110000;
+static const uint8_t UTF8_FOUR_BYTE_MASK = 0b11111000;
+static const uint8_t UTF8_SUBSEQUENT_BYTE_PREFIX = 0b10000000;
+static const uint8_t UTF8_SUBSEQUENT_BYTE_MASK = 0b11000000;
+#endif
+
+__attribute__((format(printf, 1, 2), noreturn)) static void print_error_exit(const char *fmt, ...);
+
+static bool is_readable_file(const char *str);
+
+enum socket_type {
+ SOCKET_TYPE_TCP,
+#ifdef RTRLIB_HAVE_LIBSSH
+ SOCKET_TYPE_SSH,
+#endif
+};
+
+struct socket_config {
+ struct rtr_socket socket;
+ struct tr_socket tr_socket;
+ enum socket_type type;
+ bool print_pfx_updates;
+ bool print_spki_updates;
+ char *bindaddr;
+ char *host;
+ char *port;
+#ifdef RTRLIB_HAVE_LIBSSH
+ char *ssh_username;
+ char *ssh_private_key;
+ char *ssh_host_key;
+ char *ssh_password;
+ bool force_password;
+ bool force_key;
+#endif
+};
+
+/* activate update callback */
+bool activate_pfx_update_cb = false;
+bool activate_spki_update_cb = false;
+
+/* print all updates regardless of socket configuration */
+bool print_all_pfx_updates = false;
+bool print_all_spki_updates = false;
+
+bool print_status_updates = false;
+
+bool export_pfx = false;
+char *export_file_path = NULL;
+const char *template_name = NULL;
+
+struct socket_config **socket_config = NULL;
+size_t socket_count = 0;
+
+pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/**
+ * @brief Check if argument is NULL and exit if it is
+ */
+static void *check_alloc_ret(void *ptr)
+{
+ if (ptr)
+ return ptr;
+
+ fprintf(stderr, "Memory allocation error\n");
+ exit(-1);
+}
+
+/**
+ * @brief Return value checking malloc wrapper. Exits program on error
+ */
+static inline void *checked_malloc(size_t size)
+{
+ return check_alloc_ret(malloc(size));
+}
+
+/**
+ * @brief Return value checking realloc wrapper. Exits program on error.
+ */
+static inline void *checked_realloc(void *ptr, size_t size)
+{
+ return check_alloc_ret(realloc(ptr, size));
+}
+
+static struct socket_config *extend_socket_config()
+{
+ socket_config = checked_realloc(socket_config, sizeof(struct socket_config *) * (socket_count + 1));
+ struct socket_config *config = socket_config[socket_count] = checked_malloc(sizeof(struct socket_config));
+
+ memset(config, 0, sizeof(*config));
+ ++socket_count;
+
+ return config;
+}
+
+static const char *get_template(const char *name)
+{
+ const struct pfx_output_template *current = templates;
+
+ while (current->name) {
+ if (strcmp(name, current->name) == 0)
+ return current->template;
+
+ ++current;
+ }
+
+ if (is_readable_file(name)) {
+ FILE *template_file = fopen(name, "r");
+
+ if (fseek(template_file, 0, SEEK_END) == -1)
+ goto read_error;
+
+ long file_size = ftell(template_file);
+
+ if (file_size == -1)
+ goto read_error;
+
+ rewind(template_file);
+
+ char *template = checked_malloc(file_size);
+
+ size_t bytes_read = fread(template, 1, file_size, template_file);
+
+ if (bytes_read != (unsigned long)file_size)
+ goto read_error;
+
+ fclose(template_file);
+ return template;
+
+read_error:
+ fclose(template_file);
+ print_error_exit("Could not read template");
+ }
+
+ print_error_exit("Template \"%s\" not found", name);
+}
+
+static void print_templates(void)
+{
+ const struct pfx_output_template *current = templates;
+
+ while (current->name) {
+ if (template_name && strcmp(current->name, template_name) == 0)
+ printf("%s", current->template);
+ else if (!template_name)
+ printf("%s\n", current->name);
+
+ ++current;
+ }
+}
+
+static bool is_numeric(const char *str)
+{
+ for (size_t i = 0; i < strlen(str); ++i) {
+ if (!isdigit(str[i]))
+ return false;
+ }
+ return true;
+}
+
+static bool is_valid_port_number(const char *str)
+{
+ if (!is_numeric(str))
+ return false;
+
+ int port = atoi(str);
+
+ if (port < 1 || port > 65535)
+ return false;
+
+ return true;
+}
+
+static bool is_resolveable_host(const char *str)
+{
+ struct addrinfo hints;
+ struct addrinfo *result = NULL;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0;
+
+ int ret = getaddrinfo(str, NULL, &hints, &result);
+
+ freeaddrinfo(result);
+
+ if (ret != 0)
+ return false;
+ return true;
+}
+
+static bool is_file(const char *str)
+{
+ struct stat stat_buf;
+
+ if (stat(str, &stat_buf) == -1)
+ return false;
+
+ return S_ISREG(stat_buf.st_mode);
+}
+
+static bool is_readable_file(const char *str)
+{
+ if (access(str, R_OK) != 0)
+ return false;
+
+ return is_file(str);
+}
+
+#ifdef RTRLIB_HAVE_LIBSSH
+static bool is_utf8(const char *str)
+{
+ size_t len = strlen(str);
+ size_t i = 0;
+
+ while (i < len) {
+ // check if current byte is a single utf8 char
+ if ((str[i] & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_PREFIX) {
+ i += 1;
+
+ // check if current byte is the start of a two byte utf8 char and validate subsequent bytes
+ } else if ((str[i] & UTF8_TWO_BYTE_MASK) == UTF8_TWO_BYTE_PREFIX && i + 1 < len &&
+ (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) {
+ i += 2;
+
+ // check if current byte is the start of a three byte utf8 char and validate subsequent bytes
+ } else if ((str[i] & UTF8_THREE_BYTE_MASK) == UTF8_THREE_BYTE_PREFIX && i + 2 < len &&
+ (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX &&
+ (str[i + 2] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) {
+ i += 3;
+
+ // check if current byte is the start of a four byte utf8 char and validate subsequent bytes
+ } else if ((str[i] & UTF8_FOUR_BYTE_MASK) == UTF8_FOUR_BYTE_PREFIX && i + 3 < len &&
+ (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX &&
+ (str[i + 2] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX &&
+ (str[i + 3] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) {
+ i += 4;
+
+ // if none of the conditions matched. The string contains at least one byte that is not valid utf8
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+struct exporter_state {
+ bool roa_section;
+ tommy_count_t current_roa;
+ tommy_array *roas;
+};
+
+struct pfx_export_cb_arg {
+ tommy_array *array;
+ tommy_hashlin *hashtable;
+};
+
+static void pfx_export_cb(const struct pfx_record *pfx_record, void *data);
+
+static tommy_uint32_t hash_pfx_record(const struct pfx_record *record)
+{
+ struct pfx_record_packed {
+ uint8_t ip[16];
+ uint32_t asn;
+ uint8_t min_len;
+ uint8_t max_len;
+ } __attribute__((packed));
+
+ struct pfx_record_packed packed_record;
+
+ memset(&packed_record, 0, sizeof(packed_record));
+
+ if (record->prefix.ver == LRTR_IPV4)
+ memcpy(&packed_record.ip, &record->prefix.u.addr4, sizeof(struct lrtr_ipv4_addr));
+ else
+ memcpy(&packed_record.ip, &record->prefix.u.addr6, sizeof(struct lrtr_ipv6_addr));
+
+ packed_record.asn = record->asn;
+ packed_record.min_len = record->min_len;
+ packed_record.max_len = record->max_len;
+
+ return tommy_hash_u32(0, &packed_record, sizeof(packed_record));
+}
+
+static int pfx_record_cmp(const void *data1, const void *data2)
+{
+ const struct pfx_record *arg1 = data1;
+ const struct pfx_record *arg2 = data2;
+
+ if ((arg1->asn == arg2->asn) && (arg1->max_len == arg2->max_len) && (arg1->min_len == arg2->min_len) &&
+ lrtr_ip_addr_equal(arg1->prefix, arg2->prefix))
+ return 0;
+
+ return 1;
+}
+
+static int template_enter(void *closure, const char *name)
+{
+ struct exporter_state *state = closure;
+
+ if (strncmp("roas", name, strlen(name)) == 0) {
+ state->roa_section = true;
+ state->current_roa = 0;
+
+ return 1;
+
+ } else if (state->current_roa && strcmp("last", name) == 0 &&
+ state->current_roa == tommy_array_size(state->roas) - 1) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int template_leave(void *closure)
+{
+ struct exporter_state *state = closure;
+
+ state->roa_section = false;
+ return 0;
+}
+
+static int template_next(void *closure)
+{
+ struct exporter_state *state = closure;
+
+ if (++state->current_roa >= tommy_array_size(state->roas))
+ return 0;
+
+ return 1;
+}
+
+static int template_put(void *closure, const char *name, __attribute__((unused)) int escape, FILE *file)
+{
+ struct exporter_state *state = closure;
+
+ size_t namelen = strlen(name);
+
+ if (!state->roa_section || namelen == 0)
+ return MUSTACH_ERROR_SYSTEM;
+
+ struct pfx_record *pfx_record = tommy_array_get(state->roas, state->current_roa);
+
+ if (strncmp("prefix", name, namelen) == 0) {
+ char prefix_str[INET6_ADDRSTRLEN];
+
+ lrtr_ip_addr_to_str(&pfx_record->prefix, prefix_str, sizeof(prefix_str));
+ fputs(prefix_str, file);
+
+ } else if (strncmp("length", name, namelen) == 0) {
+ fprintf(file, "%d", pfx_record->min_len);
+
+ } else if (strncmp("maxlen", name, namelen) == 0) {
+ fprintf(file, "%d", pfx_record->max_len);
+
+ } else if (strncmp("origin", name, namelen) == 0) {
+ fprintf(file, "%d", pfx_record->asn);
+ }
+
+ return MUSTACH_OK;
+}
+
+struct mustach_itf template_itf = {.start = NULL,
+ .put = template_put,
+ .enter = template_enter,
+ .next = template_next,
+ .leave = template_leave};
+
+static void print_usage(char **argv)
+{
+ printf("Usage:\n");
+ printf(" %s [-hpkels] [-o file] [-t template] <socket>...\n", argv[0]);
+ printf("\nSocket:\n");
+ printf(" tcp [-hpkb bindaddr] <host> <port>\n");
+#ifdef RTRLIB_HAVE_LIBSSH
+ printf(" ssh [-hpkb bindaddr] <host> <port> <username> (<private_key> | <password>) [<host_key>]\n");
+#endif
+ printf("\nOptions:\n");
+ printf("-b bindaddr Hostnamne or IP address to connect from\n\n");
+
+#ifdef RTRLIB_HAVE_LIBSSH
+ printf("-w force ssh authentication information to be interpreted as a password\n");
+ printf("-r force ssh authentication information to be interpreted as a private key\n\n");
+#endif
+
+ printf("-k Print information about SPKI updates.\n");
+ printf("-p Print information about PFX updates.\n");
+ printf("-s Print information about connection status updates.\n\n");
+
+ printf("-e export pfx table and exit\n");
+ printf("-o output file for export\n");
+ printf("-t template used for export\n");
+ printf("-l list available templates\n\n");
+
+ printf("-h Print this help message.\n");
+
+ printf("\nExamples:\n");
+ printf(" %s tcp rpki-validator.realmv6.org 8283\n", argv[0]);
+ printf(" %s tcp -k -p rpki-validator.realmv6.org 8283\n", argv[0]);
+ printf(" %s tcp -k rpki-validator.realmv6.org 8283 tcp -s example.com 323\n", argv[0]);
+ printf(" %s -kp tcp rpki-validator.realmv6.org 8283 tcp example.com 323\n", argv[0]);
+#ifdef RTRLIB_HAVE_LIBSSH
+ printf(" %s ssh rpki-validator.realmv6.org 22 rtr-ssh", argv[0]);
+ printf(" ~/.ssh/id_rsa ~/.ssh/known_hosts\n");
+ printf(" %s ssh -k -p rpki-validator.realmv6.org 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts\n", argv[0]);
+ printf(" %s ssh -k -p rpki-validator.realmv6.org 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts", argv[0]);
+ printf(" ssh -k -p example.com 22 rtr-ssh ~/.ssh/id_rsa_example\n");
+ printf(" %s ssh -k -p rpki-validator.realmv6.org 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts", argv[0]);
+ printf(" tcp -k -p example.com 323\n");
+#endif
+
+ printf(" %s -e tcp rpki-validator.realmv6.org 8283\n", argv[0]);
+ printf(" %s -e -t csv -o roa.csv tcp rpki-validator.realmv6.org 8283\n", argv[0]);
+}
+
+static void status_fp(const struct rtr_mgr_group *group __attribute__((unused)), enum rtr_mgr_status mgr_status,
+ const struct rtr_socket *rtr_sock, void *data __attribute__((unused)))
+{
+ if (print_status_updates) {
+ pthread_mutex_lock(&stdout_mutex);
+ printf("RTR-Socket changed connection status to: %s, Mgr Status: %s\n",
+ rtr_state_to_str(rtr_sock->state), rtr_mgr_status_to_str(mgr_status));
+ pthread_mutex_unlock(&stdout_mutex);
+ }
+}
+
+static void update_cb(struct pfx_table *p __attribute__((unused)), const struct pfx_record rec, const bool added)
+{
+ char ip[INET6_ADDRSTRLEN];
+
+ const struct socket_config *config = (const struct socket_config *)rec.socket;
+
+ if (!print_all_pfx_updates && !config->print_pfx_updates)
+ return;
+
+ pthread_mutex_lock(&stdout_mutex);
+ if (added)
+ printf("+ ");
+ else
+ printf("- ");
+ lrtr_ip_addr_to_str(&rec.prefix, ip, sizeof(ip));
+ if (socket_count > 1)
+ printf("%s:%s %-40s %3u - %3u %10u\n", config->host, config->port, ip, rec.min_len, rec.max_len,
+ rec.asn);
+ else
+ printf("%-40s %3u - %3u %10u\n", ip, rec.min_len, rec.max_len, rec.asn);
+
+ pthread_mutex_unlock(&stdout_mutex);
+}
+
+static void update_spki(struct spki_table *s __attribute__((unused)), const struct spki_record record, const bool added)
+{
+ const struct socket_config *config = (const struct socket_config *)record.socket;
+
+ if (!print_all_spki_updates && !config->print_spki_updates)
+ return;
+
+ pthread_mutex_lock(&stdout_mutex);
+
+ char c;
+
+ if (added)
+ c = '+';
+ else
+ c = '-';
+
+ printf("%c ", c);
+ printf("HOST: %s:%s\n", config->host, config->port);
+ printf("ASN: %u\n ", record.asn);
+
+ int i;
+ int size = sizeof(record.ski);
+
+ printf("SKI: ");
+ for (i = 0; i < size; i++) {
+ printf("%02x", record.ski[i]);
+ if (i < size - 1)
+ printf(":");
+ }
+ printf("\n ");
+
+ i = 0;
+ size = sizeof(record.spki);
+ printf("SPKI: ");
+ for (i = 0; i < size; i++) {
+ if ((i % 40 == 0) && (i != 0))
+ printf("\n ");
+
+ printf("%02x", record.spki[i]);
+ if (i < size - 1)
+ printf(":");
+ }
+ printf("\n");
+
+ pthread_mutex_unlock(&stdout_mutex);
+}
+
+static void parse_global_opts(int argc, char **argv)
+{
+ int opt;
+
+ bool print_template = false;
+
+ while ((opt = getopt(argc, argv, "+kphelo:t:s")) != -1) {
+ switch (opt) {
+ case 'k':
+ activate_spki_update_cb = true;
+ print_all_spki_updates = true;
+ break;
+
+ case 'p':
+ activate_pfx_update_cb = true;
+ print_all_pfx_updates = true;
+ break;
+
+ case 'e':
+ export_pfx = true;
+ break;
+
+ case 'o':
+ if (export_file_path)
+ print_error_exit("output file can not be specified more than once");
+
+ export_file_path = optarg;
+ break;
+
+ case 't':
+ template_name = optarg;
+ break;
+
+ case 'l':
+ print_template = true;
+ break;
+
+ case 's':
+ print_status_updates = true;
+ break;
+
+ default:
+ print_usage(argv);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (print_template) {
+ print_templates();
+ exit(EXIT_SUCCESS);
+ }
+
+ if ((export_file_path || template_name) && !export_pfx)
+ print_error_exit("Specifying -o or -t without -e does not make sense");
+}
+
+static void parse_socket_opts(int argc, char **argv, struct socket_config *config)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "+kphwrb:")) != -1) {
+ switch (opt) {
+ case 'k':
+ activate_spki_update_cb = true;
+ config->print_spki_updates = true;
+ break;
+
+ case 'p':
+ activate_pfx_update_cb = true;
+ config->print_pfx_updates = true;
+ break;
+
+ case 'b':
+ config->bindaddr = optarg;
+ break;
+
+#ifdef RTRLIB_HAVE_LIBSSH
+ case 'w':
+ if (config->force_key)
+ print_error_exit("-w and -r are mutually exclusive");
+
+ config->force_password = true;
+ break;
+
+ case 'r':
+ if (config->force_password)
+ print_error_exit("-w and -r are mutually exclusive");
+
+ config->force_key = true;
+ break;
+#endif
+
+ default:
+ print_usage(argv);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+static void print_error_exit(const char *fmt, ...)
+{
+ va_list argptr;
+
+ va_start(argptr, fmt);
+
+ fprintf(stderr, "error: ");
+
+ vfprintf(stderr, fmt, argptr);
+
+ fprintf(stderr, "\n");
+
+ va_end(argptr);
+
+ exit(EXIT_FAILURE);
+}
+
+static int parse_cli(int argc, char **argv)
+{
+ enum cli_parse_state {
+ CLI_PARSE_STATE_BEGIN,
+ CLI_PARSE_STATE_GLOBAL_OPTS,
+ CLI_PARSE_STATE_SOCKET_BEGIN,
+ CLI_PARSE_STATE_SOCKET_TCP_OPTIONS,
+ CLI_PARSE_STATE_SOCKET_TCP_HOST,
+ CLI_PARSE_STATE_SOCKET_TCP_PORT,
+#ifdef RTRLIB_HAVE_LIBSSH
+ CLI_PARSE_STATE_SOCKET_SSH_OPTIONS,
+ CLI_PARSE_STATE_SOCKET_SSH_HOST,
+ CLI_PARSE_STATE_SOCKET_SSH_PORT,
+ CLI_PARSE_STATE_SOCKET_SSH_USERNAME,
+ CLI_PARSE_STATE_SOCKET_SSH_PRIVATE_KEY_PASSWORD,
+ CLI_PARSE_STATE_SOCKET_SSH_HOST_KEY,
+#endif
+ CLI_PARSE_STATE_END,
+ };
+
+ enum cli_parse_state state = CLI_PARSE_STATE_BEGIN;
+
+ struct socket_config *current_config;
+
+ while (true) {
+ switch (state) {
+ case CLI_PARSE_STATE_BEGIN:
+ optind = 1;
+ state = CLI_PARSE_STATE_GLOBAL_OPTS;
+ break;
+
+ case CLI_PARSE_STATE_GLOBAL_OPTS:
+ parse_global_opts(argc, argv);
+
+ state = CLI_PARSE_STATE_SOCKET_BEGIN;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_BEGIN:
+ current_config = extend_socket_config();
+
+ if (strncasecmp(argv[optind], "tcp", strlen(argv[optind])) == 0) {
+ state = CLI_PARSE_STATE_SOCKET_TCP_OPTIONS;
+ current_config->type = SOCKET_TYPE_TCP;
+
+ /* Check if enough arguments are available
+ * to configure this socket.
+ * This does not account for options.
+ */
+ if ((argc - optind) < 2)
+ print_error_exit("Not enough arguments for tcp socket");
+
+ } else if (strncasecmp(argv[optind], "ssh", strlen(argv[optind])) == 0) {
+#ifdef RTRLIB_HAVE_LIBSSH
+ state = CLI_PARSE_STATE_SOCKET_SSH_OPTIONS;
+ current_config->type = SOCKET_TYPE_SSH;
+
+ /* Check if enough arguments are available
+ * to configure this socket.
+ * This does not account for options.
+ */
+ if ((argc - optind) < 4)
+ print_error_exit("Not enough arguments for ssh socket");
+#else
+ print_error_exit("ssh support disabled at compile time\n");
+#endif
+
+ } else {
+ print_error_exit("\"%s\" is not a valid socket type\n", argv[optind]);
+ break;
+ }
+
+ ++optind;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_TCP_OPTIONS:
+ parse_socket_opts(argc, argv, current_config);
+
+ /* Check again if enough arguments are available,
+ * accounting for options
+ */
+ if ((argc - optind) < 2)
+ print_error_exit("Not enough arguments for tcp socket");
+
+ state = CLI_PARSE_STATE_SOCKET_TCP_HOST;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_TCP_HOST:
+ if (!is_resolveable_host(argv[optind]))
+ print_error_exit("cannot resolve \"%s\"\n", argv[optind]);
+
+ current_config->host = argv[optind++];
+
+ state = CLI_PARSE_STATE_SOCKET_TCP_PORT;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_TCP_PORT:
+ if (!is_valid_port_number(argv[optind]))
+ print_error_exit("\"%s\" is not a valid port number\n", argv[optind]);
+
+ current_config->port = argv[optind++];
+
+ state = CLI_PARSE_STATE_SOCKET_BEGIN;
+ break;
+
+#ifdef RTRLIB_HAVE_LIBSSH
+ case CLI_PARSE_STATE_SOCKET_SSH_OPTIONS:
+ parse_socket_opts(argc, argv, current_config);
+
+ /* Check again if enough arguments are available,
+ * accounting for options
+ */
+ if ((argc - optind) < 4)
+ print_error_exit("Not enough arguments for ssh socket");
+
+ state = CLI_PARSE_STATE_SOCKET_SSH_HOST;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_SSH_HOST:
+ if (!is_resolveable_host(argv[optind]))
+ print_error_exit("cannot resolve \"%s\"\n", argv[optind]);
+
+ current_config->host = argv[optind++];
+
+ state = CLI_PARSE_STATE_SOCKET_SSH_PORT;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_SSH_PORT:
+ if (!is_valid_port_number(argv[optind]))
+ print_error_exit("\"%s\" is not a valid port number\n", argv[optind]);
+
+ current_config->port = argv[optind++];
+
+ state = CLI_PARSE_STATE_SOCKET_SSH_USERNAME;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_SSH_USERNAME:
+ current_config->ssh_username = argv[optind++];
+
+ state = CLI_PARSE_STATE_SOCKET_SSH_PRIVATE_KEY_PASSWORD;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_SSH_PRIVATE_KEY_PASSWORD:
+ if (current_config->force_key && !is_readable_file(argv[optind])) {
+ print_error_exit("\"%s\" is not a readable file\n", argv[optind]);
+
+ } else if (!current_config->force_password && !current_config->force_key &&
+ is_readable_file(argv[optind])) {
+ current_config->ssh_private_key = argv[optind];
+
+ } else if (!current_config->force_password && is_utf8(argv[optind])) {
+ fprintf(stderr, "\"%s\" does not seem to be a file. Trying password authentication.\n",
+ argv[optind]);
+ fprintf(stderr, "Use -r to force key authentication or -w to silence this warning");
+ current_config->ssh_password = argv[optind];
+
+ } else if (current_config->force_password && !is_utf8(argv[optind])) {
+ print_error_exit("\"%s\" is not a valid utf8 string", argv[optind]);
+
+ } else if (current_config->force_password && !current_config->force_key &&
+ is_utf8(argv[optind])) {
+ current_config->ssh_password = argv[optind];
+
+ } else {
+ print_error_exit("\"%s\" is neither a readable file nor a valid utf8 string",
+ argv[optind]);
+ }
+
+ ++optind;
+ state = CLI_PARSE_STATE_SOCKET_SSH_HOST_KEY;
+ break;
+
+ case CLI_PARSE_STATE_SOCKET_SSH_HOST_KEY:
+ if (strncasecmp(argv[optind], "tcp", strlen(argv[optind])) == 0 ||
+ strncasecmp(argv[optind], "ssh", strlen(argv[optind])) == 0) {
+ state = CLI_PARSE_STATE_SOCKET_BEGIN;
+ break;
+ }
+
+ if (!is_readable_file(argv[optind]))
+ print_error_exit("\"%s\" is not a readable file\n", argv[optind]);
+
+ current_config->ssh_host_key = argv[optind++];
+
+ state = CLI_PARSE_STATE_SOCKET_BEGIN;
+ break;
+#endif
+
+ case CLI_PARSE_STATE_END:
+ return 0;
+ }
+
+ if (optind >= argc)
+ state = CLI_PARSE_STATE_END;
+ }
+}
+
+static void init_sockets(void)
+{
+ for (size_t i = 0; i < socket_count; ++i) {
+ struct socket_config *config = socket_config[i];
+ struct tr_tcp_config tcp_config = {};
+#ifdef RTRLIB_HAVE_LIBSSH
+ struct tr_ssh_config ssh_config = {};
+#endif
+
+ switch (config->type) {
+ case SOCKET_TYPE_TCP:
+ tcp_config.host = config->host;
+ tcp_config.port = config->port;
+ tcp_config.bindaddr = config->bindaddr;
+
+ tr_tcp_init(&tcp_config, &config->tr_socket);
+ config->socket.tr_socket = &config->tr_socket;
+ break;
+
+#ifdef RTRLIB_HAVE_LIBSSH
+ case SOCKET_TYPE_SSH:
+ ssh_config.host = config->host;
+ ssh_config.port = atoi(config->port);
+ ssh_config.bindaddr = config->bindaddr;
+ ssh_config.username = config->ssh_username;
+ ssh_config.client_privkey_path = config->ssh_private_key;
+ ssh_config.server_hostkey_path = config->ssh_host_key;
+ ssh_config.password = config->ssh_password;
+
+ tr_ssh_init(&ssh_config, &config->tr_socket);
+ config->socket.tr_socket = &config->tr_socket;
+ break;
+#endif
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ parse_cli(argc, argv);
+
+ if (socket_count == 0) {
+ print_usage(argv);
+ exit(EXIT_FAILURE);
+ }
+
+ init_sockets();
+
+ struct rtr_mgr_config *conf;
+ struct rtr_mgr_group groups[1];
+
+ groups[0].sockets_len = socket_count;
+ groups[0].sockets = (struct rtr_socket **)socket_config;
+ groups[0].preference = 1;
+
+ spki_update_fp spki_update_fp = activate_spki_update_cb ? update_spki : NULL;
+ pfx_update_fp pfx_update_fp = activate_pfx_update_cb ? update_cb : NULL;
+
+ int ret = rtr_mgr_init(&conf, groups, 1, 30, 600, 600, pfx_update_fp, spki_update_fp, status_fp, NULL);
+
+ if (ret == RTR_ERROR)
+ fprintf(stderr, "Error in rtr_mgr_init!\n");
+ else if (ret == RTR_INVALID_PARAM)
+ fprintf(stderr, "Invalid params passed to rtr_mgr_init\n");
+
+ if (!conf)
+ return EXIT_FAILURE;
+
+ if (!export_pfx && activate_pfx_update_cb && socket_count > 1)
+ printf("%-40s %-40s %3s %3s %3s\n", "host", "Prefix", "Prefix Length", "", "ASN");
+ else if (!export_pfx && activate_pfx_update_cb)
+ printf("%-40s %3s %3s %3s\n", "Prefix", "Prefix Length", "", "ASN");
+
+ if (export_pfx) {
+ const char *template;
+
+ if (template_name)
+ template = get_template(template_name);
+ else
+ template = templates->template;
+ FILE *export_file = stdout;
+
+ if (export_file_path) {
+ export_file = fopen(export_file_path, "w");
+ if (!export_file) {
+ char *errormsg = strerror(errno);
+
+ print_error_exit("\"%s\" could not be opened for writing: %s", optarg, errormsg);
+ }
+ }
+
+ rtr_mgr_start(conf);
+
+ while (!rtr_mgr_conf_in_sync(conf))
+ sleep(1);
+
+ printf("Sync done\n");
+ tommy_array prefixes;
+ tommy_hashlin prefix_hash;
+ struct pfx_export_cb_arg arg = {
+ .array = &prefixes,
+ .hashtable = &prefix_hash,
+ };
+
+ tommy_array_init(&prefixes);
+ tommy_hashlin_init(&prefix_hash);
+
+ pfx_table_for_each_ipv4_record(conf->pfx_table, pfx_export_cb, &arg);
+ pfx_table_for_each_ipv6_record(conf->pfx_table, pfx_export_cb, &arg);
+
+ struct exporter_state state = {
+ .roa_section = false,
+ .current_roa = 0,
+ .roas = &prefixes,
+ };
+
+ fmustach(template, &template_itf, &state, export_file);
+
+ tommy_hashlin_foreach(&prefix_hash, free);
+ tommy_hashlin_done(&prefix_hash);
+ tommy_array_done(&prefixes);
+
+ if (export_file != stdout && fclose(export_file) == EOF) {
+ char *errormsg = strerror(errno);
+
+ print_error_exit("\"Error during write into output file: %s\"", errormsg);
+ }
+
+ } else {
+ rtr_mgr_start(conf);
+ pause();
+ }
+
+ rtr_mgr_stop(conf);
+ rtr_mgr_free(conf);
+ free(groups[0].sockets);
+
+ return EXIT_SUCCESS;
+}
+
+struct pfx_record_entry {
+ struct pfx_record record;
+ tommy_node hash_node;
+};
+
+static void pfx_export_cb(const struct pfx_record *pfx_record, void *data)
+{
+ struct pfx_export_cb_arg *arg = data;
+ tommy_array *roa_array = arg->array;
+ tommy_hashlin *roa_hashtable = arg->hashtable;
+
+ tommy_hash_t record_hash = hash_pfx_record(pfx_record);
+
+ if (tommy_hashlin_search(roa_hashtable, &pfx_record_cmp, pfx_record, record_hash) != 0)
+ return;
+
+ struct pfx_record_entry *pfx_record_entry = malloc(sizeof(struct pfx_record_entry));
+
+ memcpy(&pfx_record_entry->record, pfx_record, sizeof(struct pfx_record));
+
+ tommy_hashlin_insert(roa_hashtable, &pfx_record_entry->hash_node, pfx_record_entry, record_hash);
+ tommy_array_insert(roa_array, pfx_record_entry);
+}