From: dhfelix Date: Fri, 9 Nov 2018 22:12:02 +0000 (-0600) Subject: Add jansson reference as json parser X-Git-Tag: v0.0.2~52^2~71 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=142be34596dc915e8b26c5d74614b01fb50fe7cd;p=thirdparty%2FFORT-validator.git Add jansson reference as json parser --- diff --git a/configure.ac b/configure.ac index 6f5e7f9d..2a6aa36a 100644 --- a/configure.ac +++ b/configure.ac @@ -28,6 +28,7 @@ AC_SEARCH_LIBS([pthread_create], [pthread]) # Uhhh... this one starts with "PKG_" so it's probably different. # No idea. PKG_CHECK_MODULES([CHECK], [check]) +PKG_CHECK_MODULES([JANSSON], [jansson]) # Spit out the makefiles. AC_OUTPUT(Makefile src/Makefile man/Makefile test/Makefile) diff --git a/src/Makefile.am b/src/Makefile.am index 60516789..d4972ac6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ AM_CFLAGS = -pedantic -Wall -std=gnu11 -O3 -AM_LDFLAGS = +AM_LDFLAGS = bin_PROGRAMS = rtr_server @@ -13,3 +13,13 @@ rtr_server_SOURCES += rtr/primitive_reader.c rtr_server_SOURCES += rtr/primitive_reader.h rtr_server_SOURCES += rtr/rtr.c rtr_server_SOURCES += rtr/rtr.h +rtr_server_SOURCES += log.h +rtr_server_SOURCES += file.c +rtr_server_SOURCES += file.h +rtr_server_SOURCES += types.h +rtr_server_SOURCES += configuration.c +rtr_server_SOURCES += configuration.h +rtr_server_SOURCES += str_utils.c +rtr_server_SOURCES += str_utils.h + +rtr_server_LDADD = ${JANSSON_LIBS} \ No newline at end of file diff --git a/src/common.h b/src/common.h index 1f54d5d6..1c075332 100644 --- a/src/common.h +++ b/src/common.h @@ -1,5 +1,7 @@ -#ifndef COMMON_H_ -#define COMMON_H_ +#ifndef _SRC_COMMON_H_ +#define _SRC_COMMON_H_ + +#include /* __BEGIN_DECLS should be used at the beginning of your declarations, so that C++ compilers don't mangle their names. Use __END_DECLS at @@ -18,4 +20,16 @@ #define EUNIMPLEMENTED 566456 -#endif /* COMMON_H_ */ +#define warnxerror0(error, msg) \ + warnx(msg ": %s", strerror(error)) +#define warnxerrno0(msg) \ + warnxerror0(errno, msg) +#define warnxerror(error, msg, ...) \ + warnx(msg ": %s", ##__VA_ARGS__, strerror(error)) +#define warnxerrno(msg, ...) \ + warnxerror(errno, msg, ##__VA_ARGS__) + +#define pr_debug0(msg) printf("Debug: " msg "\n"); +#define pr_debug(msg, ...) printf("Debug: " msg "\n", ##__VA_ARGS__); + +#endif /* _SRC_COMMON_H_ */ diff --git a/src/configuration.c b/src/configuration.c new file mode 100644 index 00000000..3612ede7 --- /dev/null +++ b/src/configuration.c @@ -0,0 +1,137 @@ +#include "configuration.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "file.h" +#include "str_utils.h" + +#define OPTNAME_LISTEN "listen" +#define OPTNAME_LISTEN_IPV4 "ipv4_server_addr" + +static int json_to_config(json_t *, struct rtr_config *); +static int handle_listen_config(json_t *, struct ipv4_transport_addr *); +static json_t *load_json(const char *); + + +int +read_config_from_file(char *json_file_path, struct rtr_config **result) +{ + int error; + int is_json_file; + json_t *root_json; + struct rtr_config *config; + struct file_contents fc; + + is_json_file = endsWith(json_file_path, ".json"); + if (!is_json_file) { + log_err("Invalid Json file extension for file '%s'", json_file_path); + return -EINVAL; + } + + *result = NULL; + error = file_load(json_file_path, &fc); + if (error) + return error; + + root_json = load_json(fc.buffer); + file_free(&fc); + if (!root_json) + return -ENOENT; + + config = malloc(sizeof(*config)); + if (!config) + return -ENOMEM; + + error = json_to_config(root_json, config); + if (error != 0) + free(config); + + *result = config; + json_decref(root_json); + return error; +} + +static void +check_duplicates(bool *found, char *section) +{ + if (*found) + log_info("Note: I found multiple '%s' sections.", section); + *found = true; +} + +static int +json_to_config(json_t *json, struct rtr_config *config) +{ + bool listen_found = false; + int error = 0; + const char *key; + json_t *value; + + if (!json || json->type != JSON_OBJECT) { + log_err0("Invalid JSON config."); + return -EINVAL; + } + + json_object_foreach(json, key, value) { + if(strcasecmp(OPTNAME_LISTEN, key) == 0) { + check_duplicates(&listen_found, OPTNAME_LISTEN); + error = handle_listen_config(value, &config->ipv4_server_addr); + } + } + + return error; +} + + +static int +handle_listen_config(json_t *json, struct ipv4_transport_addr *ipv4_server_addr) +{ + bool listen_ipv4_found = false; + const char *key; + json_t *value; + + if (!json || json->type != JSON_OBJECT) { + log_err0("Invalid JSON config."); + return -EINVAL; + } + + json_object_foreach(json, key, value) { + if (strcasecmp(OPTNAME_LISTEN_IPV4, key) == 0) { + check_duplicates(&listen_ipv4_found, OPTNAME_LISTEN_IPV4); + if (json_typeof(value) != JSON_STRING) { + log_err("Invalid value for key '%s'", key); + return -EINVAL; + } + + str_to_addr4_port(json_string_value(value), ipv4_server_addr); + } + } + + return 0; +} + + +/* + * Parse text into a JSON object. If text is valid JSON, returns a + * json_t structure, otherwise prints and error and returns null. + */ +static json_t *load_json(const char *text) { + json_t *root; + json_error_t error; + + root = json_loads(text, 0, &error); + + if (root) + return root; + else { + log_err("json error on line %d column %d: %s\n", error.line, error.column, error.text); + return (json_t *)0; + } +} + diff --git a/src/configuration.h b/src/configuration.h new file mode 100644 index 00000000..928ad2c5 --- /dev/null +++ b/src/configuration.h @@ -0,0 +1,15 @@ +#ifndef _SRC_CONFIGURATION_H_ +#define _SRC_CONFIGURATION_H_ + +#include "types.h" + +struct rtr_config { + /** The listener address of the RTR server. */ + struct ipv4_transport_addr ipv4_server_addr; +}; + + +int read_config_from_file(char *, struct rtr_config **); + + +#endif /* _SRC_CONFIGURATION_H_ */ diff --git a/src/file.c b/src/file.c new file mode 100644 index 00000000..72903a7f --- /dev/null +++ b/src/file.c @@ -0,0 +1,90 @@ +#include "file.h" + +#include +#include +#include +#include + +#include "common.h" + +/* + * Will also rewind the file as a side effect. + * This is currently perfect for calling users. + */ +static int +get_file_size(FILE *file, long int *size) +{ + if (fseek(file, 0L, SEEK_END) == -1) + return errno ? errno : -EINVAL; + *size = ftell(file); + rewind(file); + return 0; +} + +int +file_load(const char *file_name, struct file_contents *fc) +{ + FILE *file; + long int file_size; + size_t fread_result; + int error; + + file = fopen(file_name, "rb"); + if (file == NULL) { + warnxerrno("Could not open file '%s'", file_name); + return errno; + } + + /* TODO if @file is a directory, this returns a very large integer. */ + error = get_file_size(file, &file_size); + if (error) { + warnxerror0(error, "Could not compute file size"); + fclose(file); + return error; + } + + fc->buffer_size = file_size; + fc->buffer = malloc(fc->buffer_size); + if (fc->buffer == NULL) { + warnx("Out of memory."); + fclose(file); + return -ENOMEM; + } + + fread_result = fread(fc->buffer, 1, fc->buffer_size, file); + if (fread_result < fc->buffer_size) { + error = ferror(file); + if (error) { + /* + * The manpage doesn't say that the result is an error + * code. It literally doesn't say how to obtain the + * error code. + */ + warnx("File read error. The errcode is presumably %d. (%s)", + error, strerror(error)); + free(fc->buffer); + fclose(file); + return error; + } + + /* + * As far as I can tell from the man page, feof() cannot return + * less bytes that requested like read() does. + */ + warnx("Likely programming error: fread() < file size"); + warnx("fr:%zu bs:%zu EOF:%d", fread_result, fc->buffer_size, + feof(file)); + free(fc->buffer); + fclose(file); + return -EINVAL; + } + + fclose(file); + return 0; +} + +void +file_free(struct file_contents *fc) +{ + free(fc->buffer); +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 00000000..5affcf19 --- /dev/null +++ b/src/file.h @@ -0,0 +1,19 @@ +#ifndef SRC_FILE_H_ +#define SRC_FILE_H_ + +#include + +/* + * The entire contents of the file, loaded into a buffer. + * + * Instances of this struct are expected to live on the stack. + */ +struct file_contents { + char *buffer; + size_t buffer_size; +}; + +int file_load(const char *, struct file_contents *); +void file_free(struct file_contents *); + +#endif /* SRC_FILE_H_ */ diff --git a/src/log.h b/src/log.h new file mode 100644 index 00000000..736dcda1 --- /dev/null +++ b/src/log.h @@ -0,0 +1,13 @@ +#ifndef _SRC_LOG_H +#define _SRC_LOG_H + + +#include +#define log_debug(text, ...) printf(text "\n", ##__VA_ARGS__) +#define log_info(text, ...) log_debug(text, ##__VA_ARGS__) +#define log_err(text, ...) fprintf(stderr, text "\n", ##__VA_ARGS__) +#define log_err0(text) fprintf(stderr, text "\n") + + + +#endif /* _SRC_LOG_H */ diff --git a/src/main.c b/src/main.c index bb1ab320..4c17aeed 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,9 @@ #include #include +#include #include "rtr/rtr.h" - +#include "configuration.h" /* * This program is an RTR server. * @@ -14,7 +15,46 @@ int main(int argc, char *argv[]) { + int err = 0; + char *json_file = NULL; + struct rtr_config *config; + int c; + int fflag=0; + static char usage[] = "usage: %s -f fname \n"; + puts("!!!Hello World!!!"); - rtr_listen(); + + while ((c = getopt(argc, argv, "f:")) != -1) + switch (c) { + case 'f': + fflag = 1; + json_file = optarg; + break; + case '?': + err = 1; + break; + } + + if (fflag == 0) { /* -f was mandatory */ + fprintf(stderr, "%s: missing -f option\n", argv[0]); + fprintf(stderr, usage, argv[0]); + exit(1); + } else if (err) { + fprintf(stderr, usage, argv[0]); + exit(1); + } + + err = read_config_from_file(json_file, &config); + if (err) + return err; + + err = rtr_listen(&config->ipv4_server_addr.l3, + config->ipv4_server_addr.l4); + if (config) + free(config); + + if (err) + return err; + return EXIT_SUCCESS; } diff --git a/src/rtr/rtr.c b/src/rtr/rtr.c index a89240da..664672e5 100644 --- a/src/rtr/rtr.c +++ b/src/rtr/rtr.c @@ -9,6 +9,7 @@ #include #include "../common.h" +#include "../types.h" #include "pdu.h" /* @@ -16,7 +17,7 @@ * from the clients. */ static int -create_server_socket(void) +create_server_socket(struct in_addr *server_addr, __u16 port) { int fd; /* "file descriptor" */ struct sockaddr_in address; @@ -31,8 +32,8 @@ create_server_socket(void) memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons(5001); + address.sin_addr.s_addr = server_addr->s_addr; + address.sin_port = htons(port); if (bind(fd, (struct sockaddr *)&address, sizeof(address)) < 0) { err = errno; warn("Could not bind the address"); @@ -189,11 +190,11 @@ handle_client_connections(int server_fd) * This function blocks. */ int -rtr_listen(void) +rtr_listen(struct in_addr *server_addr, __u16 port) { int server_fd; /* "file descriptor" */ - server_fd = create_server_socket(); + server_fd = create_server_socket(server_addr, port); if (server_fd < 0) return server_fd; diff --git a/src/rtr/rtr.h b/src/rtr/rtr.h index f68668b0..c3307e0c 100644 --- a/src/rtr/rtr.h +++ b/src/rtr/rtr.h @@ -2,9 +2,10 @@ #define RTR_RTR_H_ #include "../common.h" +#include "../types.h" __BEGIN_DECLS -int rtr_listen(void); +int rtr_listen(struct in_addr *, __u16); __END_DECLS #endif /* RTR_RTR_H_ */ diff --git a/src/str_utils.c b/src/str_utils.c new file mode 100644 index 00000000..a6f2747a --- /dev/null +++ b/src/str_utils.c @@ -0,0 +1,313 @@ +#include "str_utils.h" + +#include +#include +#include +#include +#include +#include +#include "types.h" + + +#define MAX_PORT 0xFFFF + +int +validate_int(const char *str) +{ + regex_t integer_regex; + int error; + + if (!str) { + log_err0("Programming error: 'str' is NULL."); + return -EINVAL; + } + + /* It seems this RE implementation doesn't understand '+'. */ + if (regcomp(&integer_regex, "^[0-9][0-9]*", 0)) { + log_err0("Warning: Integer regex didn't compile."); + log_err0("(I will be unable to validate integer inputs.)"); + regfree(&integer_regex); + /* + * Don't punish the user over our incompetence. + * If the number is valid, this will not bother the user. + * Otherwise strtoull() will just read a random value, but then + * the user is at fault. + */ + return 0; + } + + error = regexec(&integer_regex, str, 0, NULL, 0); + if (error) { + log_err("'%s' is not a number. (error code %d)", str, error); + regfree(&integer_regex); + return error; + } + + regfree(&integer_regex); + return 0; +} + +static int +str_to_ull(const char *str, char **endptr, + const unsigned long long int min, + const unsigned long long int max, + unsigned long long int *result) +{ + unsigned long long int parsed; + int error; + + error = validate_int(str); + if (error) + return error; + + errno = 0; + parsed = strtoull(str, endptr, 10); + if (errno) { + log_err("Parsing of '%s' threw error code %d.", str, errno); + return errno; + } + + if (parsed < min || max < parsed) { + log_err("'%s' is out of bounds (%llu-%llu).", str, min, max); + return -EINVAL; + } + + *result = parsed; + return 0; +} + +int +str_to_bool(const char *str, __u8 *bool_out) +{ + if (strcasecmp(str, "true") == 0 + || strcasecmp(str, "1") == 0 + || strcasecmp(str, "yes") == 0 + || strcasecmp(str, "on") == 0) { + *bool_out = true; + return 0; + } + + if (strcasecmp(str, "false") == 0 + || strcasecmp(str, "0") == 0 + || strcasecmp(str, "no") == 0 + || strcasecmp(str, "off") == 0) { + *bool_out = false; + return 0; + } + + log_err("Cannot parse '%s' as a bool (true|false|1|0|yes|no|on|off).", + str); + return -EINVAL; +} + +int +str_to_u8(const char *str, __u8 *u8_out, __u8 min, __u8 max) +{ + unsigned long long int result; + int error; + + error = str_to_ull(str, NULL, min, max, &result); + + *u8_out = result; + return error; +} + +int +str_to_u16(const char *str, __u16 *u16_out, __u16 min, __u16 max) +{ + unsigned long long int result; + int error; + + error = str_to_ull(str, NULL, min, max, &result); + + *u16_out = result; + return error; +} + +int +str_to_u32(const char *str, __u32 *u32_out, __u32 min, __u32 max) +{ + unsigned long long int result; + int error; + + error = str_to_ull(str, NULL, min, max, &result); + + *u32_out = result; + return error; +} + +int +str_to_u64(const char *str, __u64 *u64_out, __u64 min, __u64 max) +{ + unsigned long long int result; + int error; + + error = str_to_ull(str, NULL, min, max, &result); + + *u64_out = result; + return error; +} + +#define STR_MAX_LEN 2048 +int +str_to_u16_array(const char *str, __u16 **array_out, size_t *array_len_out) +{ + /* strtok corrupts the string, so we'll be using this copy instead. */ + char str_copy[STR_MAX_LEN]; + char *token; + __u16 *array; + size_t array_len; + + /* Validate str and copy it to the temp buffer. */ + if (strlen(str) + 1 > STR_MAX_LEN) { + log_err("'%s' is too long for this poor, limited parser...", str); + return -EINVAL; + } + strcpy(str_copy, str); + + /* Count the number of ints in the string. */ + array_len = 0; + token = strtok(str_copy, ","); + while (token) { + array_len++; + token = strtok(NULL, ","); + } + + if (array_len == 0) { + log_err("'%s' seems to be an empty list, which is not supported.", str); + return -EINVAL; + } + + /* Build the result. */ + array = malloc(array_len * sizeof(*array)); + if (!array) { + log_err0("Memory allocation failed. Cannot parse the input..."); + return -ENOMEM; + } + + strcpy(str_copy, str); + + array_len = 0; + token = strtok(str_copy, ","); + while (token) { + int error; + + error = str_to_u16(token, &array[array_len], 0, 0xFFFF); + if (error) { + free(array); + return error; /* Error msg already printed. */ + } + + array_len++; + token = strtok(NULL, ","); + } + + /* Finish. */ + *array_out = array; + *array_len_out = array_len; + return 0; +} + +int +str_to_addr4(const char *str, struct in_addr *result) +{ + if (!inet_pton(AF_INET, str, result)) { + log_err("Cannot parse '%s' as an IPv4 address.", str); + return -EINVAL; + } + return 0; +} + +int +str_to_addr6(const char *str, struct in6_addr *result) +{ + if (!inet_pton(AF_INET6, str, result)) { + log_err("Cannot parse '%s' as an IPv6 address.", str); + return -EINVAL; + } + return 0; +} + +#undef STR_MAX_LEN +#define STR_MAX_LEN (INET_ADDRSTRLEN + 1 + 5) /* [addr + null chara] + # + port */ +int +str_to_addr4_port(const char *str, struct ipv4_transport_addr *addr) +{ + const char *FORMAT = "# (eg. 203.0.113.8#80)"; + /* strtok corrupts the string, so we'll be using this copy instead. */ + char str_copy[STR_MAX_LEN]; + char *token; + int error; + + if (strlen(str) + 1 > STR_MAX_LEN) { + log_err("'%s' is too long for this poor, limited parser...", str); + return -EINVAL; + } + strcpy(str_copy, str); + + token = strtok(str_copy, "#"); + if (!token) { + log_err("Cannot parse '%s' as a %s.", str, FORMAT); + return -EINVAL; + } + + error = str_to_addr4(token, &addr->l3); + if (error) + return error; + + token = strtok(NULL, "#"); + if (!token) { + log_err("'%s' does not seem to contain a port (format: %s).", str, FORMAT); + return -EINVAL; + } + return str_to_u16(token, &addr->l4, 0, MAX_PORT); /* Error msg already printed. */ +} + +#undef STR_MAX_LEN +#define STR_MAX_LEN (INET6_ADDRSTRLEN + 1 + 5) /* [addr + null chara] + # + port */ +int +str_to_addr6_port(const char *str, struct ipv6_transport_addr *addr) +{ + const char *FORMAT = "# (eg. 2001:db8::1#96)"; + /* strtok corrupts the string, so we'll be using this copy instead. */ + char str_copy[STR_MAX_LEN]; + char *token; + int error; + + if (strlen(str) + 1 > STR_MAX_LEN) { + log_err("'%s' is too long for this poor, limited parser...", str); + return -EINVAL; + } + strcpy(str_copy, str); + + token = strtok(str_copy, "#"); + if (!token) { + log_err("Cannot parse '%s' as a %s.", str, FORMAT); + return -EINVAL; + } + + error = str_to_addr6(token, &addr->l3); + if (error) + return error; + + token = strtok(NULL, "#"); + if (!token) { + log_err("'%s' does not seem to contain a port (format: %s).", str, FORMAT); + return -EINVAL; + } + return str_to_u16(token, &addr->l4, 0, MAX_PORT); /* Error msg already printed. */ +} + +bool +endsWith(char *string, char *suffix) +{ + size_t strilen; + size_t suflen; + if (!string || !suffix) + return false; + + strilen = strlen(string); + suflen = strlen(suffix); + + return ((strilen >= suflen) && (0 == strcmp(string + strilen - suflen, suffix))); +} diff --git a/src/str_utils.h b/src/str_utils.h new file mode 100644 index 00000000..79ae3de0 --- /dev/null +++ b/src/str_utils.h @@ -0,0 +1,71 @@ +#ifndef _SRC_STR_UTILS_H +#define _SRC_STR_UTILS_H + +/** + * @file + * Two-liners (since you need to check the return value) for string-to-something + * else conversions. + * This is very noisy on the console on purpose because it is only used by the + * parser of the userspace app's arguments. + */ + +#include "types.h" + +/** Maximum storable value on a __u8. */ +#define MAX_U8 0xFFU +/** Maximum storable value on a __u16. */ +#define MAX_U16 0xFFFFU +/** Maximum storable value on a __u32. */ +#define MAX_U32 0xFFFFFFFFU +/** Maximum storable value on a __u64. */ +#define MAX_U64 0xFFFFFFFFFFFFFFFFU + + +/** + * Converts "str" to a IPv4 address. Stores the result in "result". + * + * Useful mainly in code common to kernelspace and userspace, since their conversion functions + * differ, but meant to be used everywhere to strip the parameters from in4_pton() we don't want. + */ +int str_to_addr4(const char *, struct in_addr *); +/** + * Converts "str" to a IPv6 address. Stores the result in "result". + * + * Useful mainly in code common to kernelspace and userspace, since their conversion functions + * differ, but meant to be used everywhere to strip the parameters from in6_pton() we don't want. + */ +int str_to_addr6(const char *, struct in6_addr *); + +/** + * Parses @str as a boolean value, which it then copies to @out. + */ +int str_to_bool(const char *, __u8 *); + +int validate_int(const char *); + +/** + * Parses @str" as a number, which it then copies to @out. + * Refuses to succeed if @out is less than @min or higher than @max. + */ +int str_to_u8(const char *, __u8 *, __u8, __u8); +int str_to_u16(const char *, __u16 *, __u16, __u16); +int str_to_u32(const char *, __u32 *, __u32, __u32); +int str_to_u64(const char *, __u64 *, __u64, __u64); + +/** + * Parses @str as a comma-separated array of __u16s, which it then copies to + * @out. + * It sets @out_len as @out's length in elements (not bytes). + */ +int str_to_u16_array(const char *, __u16 **, size_t *); + +/** + * Parses @str as a '#' separated l3-address and l4-identifier, which it then + * copies to @out". + */ +int str_to_addr4_port(const char *, struct ipv4_transport_addr *); +int str_to_addr6_port(const char *, struct ipv6_transport_addr *); + +bool endsWith(char *, char *); + +#endif /* _JOOL_COMM_STR_UTILS_H */ diff --git a/src/types.h b/src/types.h new file mode 100644 index 00000000..9043f5b2 --- /dev/null +++ b/src/types.h @@ -0,0 +1,41 @@ +#ifndef _SRC_COMMON_TYPES_H +#define _SRC_COMMON_TYPES_H + +/** + * @file + * The NAT64's core data types. Structures used all over the code. + * + * Both the kernel module and the userspace application can see this file. + */ + +#include +#include +#include +#include +#include "log.h" + + + +/** + * A layer-3 (IPv4) identifier attached to a layer-4 identifier. + * Because they're paired all the time in this project. + */ +struct ipv4_transport_addr { + /** The layer-3 identifier. */ + struct in_addr l3; + /** The layer-4 identifier (Either the TCP/UDP port or the ICMP id). */ + __u16 l4; +}; + +/** + * A layer-3 (IPv6) identifier attached to a layer-4 identifier. + * Because they're paired all the time in this project. + */ +struct ipv6_transport_addr { + /** The layer-3 identifier. */ + struct in6_addr l3; + /** The layer-4 identifier (Either the TCP/UDP port or the ICMP id). */ + __u16 l4; +}; + +#endif /* _COMMON_TYPES_H */