+/*#############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2023 IPFire Network Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "address.h"
+#include "config.h"
+#include "logging.h"
+#include "string.h"
+
+struct nw_config_entry {
+ STAILQ_ENTRY(nw_config_entry) nodes;
+
+ char key[NETWORK_CONFIG_KEY_MAX_LENGTH];
+ char value[NETWORK_CONFIG_KEY_MAX_LENGTH];
+};
+
+struct nw_config_option {
+ STAILQ_ENTRY(nw_config_option) nodes;
+
+ const char* key;
+ void* value;
+ size_t length;
+
+ // Callbacks
+ nw_config_option_read_callback_t read_callback;
+ nw_config_option_write_callback_t write_callback;
+ void* data;
+};
+
+struct nw_config {
+ int nrefs;
+
+ STAILQ_HEAD(config_entries, nw_config_entry) entries;
+
+ // Options
+ STAILQ_HEAD(parser_entries, nw_config_option) options;
+};
+
+static void nw_config_entry_free(struct nw_config_entry* entry) {
+ free(entry);
+}
+
+static void nw_config_option_free(struct nw_config_option* option) {
+ free(option);
+}
+
+static struct nw_config_entry* nw_config_entry_create(
+ nw_config* config, const char* key) {
+ int r;
+
+ // Check input value
+ if (!key) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ // Allocate a new object
+ struct nw_config_entry* entry = calloc(1, sizeof(*entry));
+ if (!entry)
+ return NULL;
+
+ // Store the key
+ r = nw_string_set(entry->key, key);
+ if (r)
+ goto ERROR;
+
+ // Append the new entry
+ STAILQ_INSERT_TAIL(&config->entries, entry, nodes);
+
+ return entry;
+
+ERROR:
+ nw_config_entry_free(entry);
+ return NULL;
+}
+
+static void nw_config_free(nw_config* config) {
+ struct nw_config_option* option = NULL;
+
+ // Flush all entries
+ nw_config_flush(config);
+
+ // Free all options
+ while (!STAILQ_EMPTY(&config->options)) {
+ option = STAILQ_FIRST(&config->options);
+ STAILQ_REMOVE_HEAD(&config->options, nodes);
+
+ // Free the options
+ nw_config_option_free(option);
+ }
+
+ free(config);
+}
+
+int nw_config_create(nw_config** config, FILE* f) {
+ int r;
+
+ nw_config* c = calloc(1, sizeof(*c));
+ if (!c)
+ return 1;
+
+ // Initialize reference counter
+ c->nrefs = 1;
+
+ // Initialise entries
+ STAILQ_INIT(&c->entries);
+
+ // Initialise options
+ STAILQ_INIT(&c->options);
+
+ // Read configuration
+ if (f) {
+ r = nw_config_read(c, f);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ *config = c;
+
+ return 0;
+
+ERROR:
+ nw_config_free(c);
+
+ return r;
+}
+
+int nw_config_open(nw_config** config, const char* path) {
+ FILE* f = NULL;
+ int r;
+
+ // Open path
+ f = fopen(path, "r");
+ if (!f)
+ return -errno;
+
+ // Create a new configuration
+ r = nw_config_create(config, f);
+
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+nw_config* nw_config_ref(nw_config* config) {
+ config->nrefs++;
+
+ return config;
+}
+
+nw_config* nw_config_unref(nw_config* config) {
+ if (--config->nrefs > 0)
+ return config;
+
+ nw_config_free(config);
+ return NULL;
+}
+
+int nw_config_copy(nw_config* config, nw_config** copy) {
+ struct nw_config_entry* entry = NULL;
+ nw_config* c = NULL;
+ int r;
+
+ // Create a new configuration
+ r = nw_config_create(&c, NULL);
+ if (r)
+ return r;
+
+ // Copy everything
+ STAILQ_FOREACH(entry, &config->entries, nodes) {
+ r = nw_config_set(c, entry->key, entry->value);
+ if (r)
+ goto ERROR;
+ }
+
+ *copy = c;
+ return 0;
+
+ERROR:
+ if (c)
+ nw_config_unref(c);
+
+ return r;
+}
+
+int nw_config_flush(nw_config* config) {
+ struct nw_config_entry* entry = NULL;
+
+ while (!STAILQ_EMPTY(&config->entries)) {
+ entry = STAILQ_FIRST(&config->entries);
+ STAILQ_REMOVE_HEAD(&config->entries, nodes);
+
+ // Free the entry
+ nw_config_entry_free(entry);
+ }
+
+ return 0;
+}
+
+int nw_config_read(nw_config* config, FILE* f) {
+ char* line = NULL;
+ size_t length = 0;
+ int r;
+
+ ssize_t bytes_read = 0;
+
+ char* key = NULL;
+ char* val = NULL;
+
+ for (;;) {
+ // Read the next line
+ bytes_read = getline(&line, &length, f);
+ if (bytes_read < 0)
+ break;
+
+ // Key starts at the beginning of the line
+ key = line;
+
+ // Value starts after '='
+ val = strchr(line, '=');
+
+ // Invalid line without a '=' character
+ if (!val)
+ continue;
+
+ // Split the string
+ *val++ = '\0';
+
+ // Strip any whitespace from value
+ r = nw_string_strip(val);
+ if (r)
+ break;
+
+ // Store the setting
+ r = nw_config_set(config, key, val);
+ if (r)
+ break;
+ }
+
+ if (line)
+ free(line);
+
+ return r;
+}
+
+int nw_config_write(nw_config* config, FILE* f) {
+ struct nw_config_entry* entry = NULL;
+ int r;
+
+ STAILQ_FOREACH(entry, &config->entries, nodes) {
+ // Skip if value is NULL
+ if (!*entry->value)
+ continue;
+
+ // Write the entry
+ r = fprintf(f, "%s=%s\n", entry->key, entry->value);
+ if (r < 0) {
+ ERROR("Failed to write configuration: %m\n");
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static struct nw_config_entry* nw_config_find(nw_config* config, const char* key) {
+ struct nw_config_entry* entry = NULL;
+
+ STAILQ_FOREACH(entry, &config->entries, nodes) {
+ // Key must match
+ if (strcmp(entry->key, key) != 0)
+ continue;
+
+ // Match!
+ return entry;
+ }
+
+ // No match
+ return NULL;
+}
+
+int nw_config_del(nw_config* config, const char* key) {
+ struct nw_config_entry* entry = NULL;
+
+ // Find an entry matching the key
+ entry = nw_config_find(config, key);
+
+ // If there is no entry, there is nothing to do
+ if (!entry)
+ return 0;
+
+ // Otherwise remove the object
+ STAILQ_REMOVE(&config->entries, entry, nw_config_entry, nodes);
+
+ // Free the entry
+ nw_config_entry_free(entry);
+
+ return 0;
+}
+
+const char* nw_config_get(nw_config* config, const char* key) {
+ struct nw_config_entry* entry = nw_config_find(config, key);
+
+ // Return the value if found and set
+ if (entry && *entry->value)
+ return entry->value;
+
+ // Otherwise return NULL
+ return NULL;
+}
+
+int nw_config_set(nw_config* config, const char* key, const char* value) {
+ struct nw_config_entry* entry = NULL;
+
+ // Log the change
+ DEBUG("%p: Setting %s = %s\n", config, key, value);
+
+ // Delete the entry if val is NULL
+ if (!value)
+ return nw_config_del(config, key);
+
+ // Find any existing entries
+ entry = nw_config_find(config, key);
+
+ // Create a new entry if it doesn't exist, yet
+ if (!entry) {
+ entry = nw_config_entry_create(config, key);
+ if (!entry)
+ return 1;
+ }
+
+ // Store the new value
+ return nw_string_set(entry->value, value);
+}
+
+int nw_config_get_int(nw_config* config, const char* key, const int __default) {
+ char* p = NULL;
+ int r;
+
+ const char* value = nw_config_get(config, key);
+
+ // Return zero if not set
+ if (!value)
+ return __default;
+
+ // Parse the input
+ r = strtoul(value, &p, 10);
+
+ // If we have characters following the input, we throw it away
+ if (p)
+ return __default;
+
+ return r;
+}
+
+int nw_config_set_int(nw_config* config, const char* key, const int value) {
+ char __value[1024];
+ int r;
+
+ // Format the value as string
+ r = nw_string_format(__value, "%d", value);
+ if (r)
+ return r;
+
+ return nw_config_set(config, key, __value);
+}
+
+static const char* nw_config_true[] = {
+ "true",
+ "yes",
+ "1",
+ NULL,
+};
+
+int nw_config_get_bool(nw_config* config, const char* key) {
+ const char* value = nw_config_get(config, key);
+
+ // No value indicates false
+ if (!value)
+ return 0;
+
+ // Check if we match any known true words
+ for (const char** s = nw_config_true; *s; s++) {
+ if (strcasecmp(value, *s) == 0)
+ return 1;
+ }
+
+ // No match means false
+ return 0;
+}
+
+int nw_config_set_bool(nw_config* config, const char* key, const int value) {
+ return nw_config_set(config, key, value ? "true" : "false");
+}
+
+/*
+ Directory
+*/
+
+struct nw_configd {
+ int nrefs;
+
+ char path[PATH_MAX];
+ int fd;
+};
+
+static void nw_configd_free(nw_configd* dir) {
+ if (dir->fd >= 0)
+ close(dir->fd);
+
+ free(dir);
+}
+
+static int __nw_configd_create(nw_configd** dir, int fd, const char* path) {
+ nw_configd* d = NULL;
+ int r;
+
+ // Allocate a new object
+ d = calloc(1, sizeof(*d));
+ if (!d)
+ return -errno;
+
+ // Initialize the reference counter
+ d->nrefs = 1;
+
+ // Store the file descriptor
+ d->fd = dup(fd);
+ if (d->fd < 0) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Store path
+ if (path) {
+ r = nw_string_set(d->path, path);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ *dir = d;
+ return 0;
+
+ERROR:
+ nw_configd_free(d);
+ return r;
+}
+
+int nw_configd_create(nw_configd** dir, const char* path) {
+ int fd;
+
+ // Open the directory
+ fd = open(path, O_DIRECTORY);
+ if (fd < 0) {
+ ERROR("Could not open %s: %m\n", path);
+ return -errno;
+ }
+
+ return __nw_configd_create(dir, fd, path);
+}
+
+nw_configd* nw_configd_ref(nw_configd* dir) {
+ dir->nrefs++;
+
+ return dir;
+}
+
+nw_configd* nw_configd_unref(nw_configd* dir) {
+ if (--dir->nrefs > 0)
+ return dir;
+
+ nw_configd_free(dir);
+ return NULL;
+}
+
+static int nw_configd_open(nw_configd* dir, const char* path, int flags) {
+ return openat(dir->fd, path, flags);
+}
+
+FILE* nw_configd_fopen(nw_configd* dir, const char* path, const char* mode) {
+ int fd;
+
+ // Open file
+ fd = nw_configd_open(dir, path, 0);
+ if (fd < 0)
+ return NULL;
+
+ // Return a file handle
+ return fdopen(fd, mode);
+}
+
+int nw_configd_open_config(nw_config** config, nw_configd* dir, const char* path) {
+ FILE* f = NULL;
+ int r;
+
+ // Open the file
+ f = nw_configd_fopen(dir, path, "r");
+ if (!f)
+ return -errno;
+
+ // Create configuration
+ r = nw_config_create(config, f);
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+int nw_configd_unlink(nw_configd* dir, const char* path, int flags) {
+ return unlinkat(dir->fd, path, flags);
+}
+
+nw_configd* nw_configd_descend(nw_configd* dir, const char* path) {
+ nw_configd* d = NULL;
+ char p[PATH_MAX];
+ int fd = -1;
+ int r;
+
+ // Join paths
+ r = nw_path_join(p, dir->path, path);
+ if (r < 0)
+ goto ERROR;
+
+ // Open directory
+ fd = nw_configd_open(dir, path, O_DIRECTORY);
+ if (fd < 0) {
+ ERROR("Could not open %s: %m\n", p);
+ goto ERROR;
+ }
+
+ // Create a new config directory object
+ r = __nw_configd_create(&d, fd, p);
+ if (r < 0)
+ goto ERROR;
+
+ERROR:
+ if (fd >= 0)
+ close(fd);
+
+ return d;
+}
+
+int nw_configd_walk(nw_configd* dir, nw_configd_walk_callback callback, void* data) {
+ FILE* f = NULL;
+ DIR* d = NULL;
+ struct dirent* e = NULL;
+ int r;
+
+ // Re-open the directory
+ d = fdopendir(dir->fd);
+ if (!d) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Walk trough everything
+ for (;;) {
+ // Read the next entry
+ e = readdir(d);
+ if (!e)
+ break;
+
+ // Skip anything that is not a regular file
+ if (e->d_type != DT_REG)
+ continue;
+
+ // Skip hidden files
+ if (e->d_name[0] == '.')
+ continue;
+
+ // Open the file
+ f = nw_configd_fopen(dir, e->d_name, "r");
+ if (!f) {
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Call the callback
+ r = callback(e, f, data);
+ fclose(f);
+
+ if (r < 0)
+ goto ERROR;
+ }
+
+ r = 0;
+
+ERROR:
+ if (d)
+ closedir(d);
+
+ return r;
+}
+
+/*
+ Options
+*/
+
+int nw_config_options_read(nw_config* config) {
+ struct nw_config_option* option = NULL;
+ int r;
+
+ STAILQ_FOREACH(option, &config->options, nodes) {
+ r = option->read_callback(config,
+ option->key, option->value, option->length, option->data);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int nw_config_options_write(nw_config* config) {
+ struct nw_config_option* option = NULL;
+ int r;
+
+ STAILQ_FOREACH(option, &config->options, nodes) {
+ r = option->write_callback(config,
+ option->key, option->value, option->length, option->data);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int nw_config_option_add(nw_config* config,
+ const char* key, void* value, const size_t length,
+ nw_config_option_read_callback_t read_callback,
+ nw_config_option_write_callback_t write_callback, void* data) {
+ // Check input
+ if (!key || !value || !read_callback || !write_callback)
+ return -EINVAL;
+
+ // Allocate a new option
+ struct nw_config_option* option = calloc(1, sizeof(*option));
+ if (!option)
+ return -errno;
+
+ // Set key
+ option->key = key;
+
+ // Set value
+ option->value = value;
+ option->length = length;
+
+ // Set callbacks
+ option->read_callback = read_callback;
+ option->write_callback = write_callback;
+ option->data = data;
+
+ // Append the new option
+ STAILQ_INSERT_TAIL(&config->options, option, nodes);
+
+ return 0;
+}
+
+int nw_config_read_int(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ // Fetch the value
+ *(int*)value = nw_config_get_int(config, key, -1);
+
+ return 0;
+}
+
+int nw_config_write_int(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data) {
+ return 0;
+}
+
+// String
+
+int nw_config_read_string(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ // Fetch the value
+ const char* p = nw_config_get(config, key);
+ if (p)
+ *(const char**)value = p;
+
+ return 0;
+}
+
+int nw_config_write_string(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data) {
+ return nw_config_set(config, key, *(const char**)value);
+}
+
+// String Buffer
+
+int nw_config_read_string_buffer(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ char* string = (char*)value;
+
+ // Fetch the value
+ const char* p = nw_config_get(config, key);
+ if (p)
+ return __nw_string_set(string, length, p);
+
+ return 0;
+}
+
+// String Table
+
+int nw_config_read_string_table(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ const char* s = NULL;
+ int* v = (int*)value;
+
+ const nw_string_table_t* table = (nw_string_table_t*)data;
+
+ // Fetch the string
+ s = nw_config_get(config, key);
+ if (!s)
+ return -errno;
+
+ // Lookup the string in the table
+ *v = nw_string_table_lookup_id(table, s);
+
+ // If the result is negative, nothing was found
+ if (*v < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+int nw_config_write_string_table(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data) {
+ int* v = (int*)value;
+
+ const nw_string_table_t* table = (nw_string_table_t*)data;
+
+ // Lookup the string
+ const char* s = nw_string_table_lookup_string(table, *v);
+ if (!s)
+ return -errno;
+
+ return nw_config_set(config, key, s);
+}
+
+// Address
+
+int nw_config_read_address(nw_config* config,
+ const char* key, void* value, const size_t length, void* data) {
+ nw_address_t* address = (nw_address_t*)value;
+ int r;
+
+ // Fetch the value
+ const char* p = nw_config_get(config, key);
+ if (!p)
+ return -EINVAL;
+
+ r = nw_address_from_string(address, p);
+ if (r < 0)
+ ERROR("Could not parse address: %s\n", p);
+
+ return r;
+}
+
+int nw_config_write_address(nw_config* config,
+ const char* key, const void* value, const size_t length, void* data) {
+ const nw_address_t* address = (nw_address_t*)value;
+ int r;
+
+ // Format the address to string
+ char* p = nw_address_to_string(address);
+ if (!p)
+ return -errno;
+
+ // Store the value
+ r = nw_config_set(config, key, p);
+ if (r < 0)
+ goto ERROR;
+
+ // Success
+ r = 0;
+
+ERROR:
+ if (p)
+ free(p);
+
+ return r;
+}