From fc975e6a93a96f2e2d75ec489ba6f01c9e92ad41 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Wed, 22 Mar 2023 17:17:30 +0000 Subject: [PATCH] TelodendriaConfig -> Config --- TODO.txt | 3 +- src/Config.c | 307 ++++++++++++++++++ src/Main.c | 18 +- src/Routes/RouteRegister.c | 2 +- src/Uia.c | 2 +- src/include/{TelodendriaConfig.h => Config.h} | 32 +- src/include/Matrix.h | 4 +- src/include/Uia.h | 2 +- 8 files changed, 339 insertions(+), 31 deletions(-) create mode 100644 src/Config.c rename src/include/{TelodendriaConfig.h => Config.h} (73%) diff --git a/TODO.txt b/TODO.txt index 9e0e682..e5b423d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -47,7 +47,7 @@ Milestone: v0.3.0 [ ] If no config, create one-time use registration token that grants user admin privileges. [ ] /_telodendria/admin/config endpoint - [ ] Refactor TelodendriaConfig to just Config (ConfigLock() and ConfigUnlock()) + [x] Refactor TelodendriaConfig to just Config [ ] Proper HTTP request router - Support regex matching @@ -64,6 +64,7 @@ Milestone: v0.3.0 [ ] tp [ ] send-patch [ ] Log + [ ] TelodendriaConfig -> Config [~] Client-Server API [x] 4: Token-based user registration diff --git a/src/Config.c b/src/Config.c new file mode 100644 index 0000000..dd27922 --- /dev/null +++ b/src/Config.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CONFIG_REQUIRE(key, type) \ + value = HashMapGet(config, key); \ + if (!value) \ + { \ + Log(LOG_ERR, "Missing required " key " directive."); \ + goto error; \ + } \ + if (JsonValueType(value) == JSON_NULL) \ + { \ + Log(LOG_ERR, "Missing value for " key " directive."); \ + goto error; \ + } \ + if (JsonValueType(value) != type) \ + { \ + Log(LOG_ERR, "Expected " key " to be of type " #type); \ + goto error; \ + } + +#define CONFIG_COPY_STRING(into) \ + into = StrDuplicate(JsonValueAsString(value)); + +#define CONFIG_OPTIONAL_STRING(into, key, default) \ + value = HashMapGet(config, key); \ + if (value && JsonValueType(value) != JSON_NULL) \ + { \ + if (JsonValueType(value) != JSON_STRING) \ + { \ + Log(LOG_ERR, "Expected " key " to be of type JSON_STRING"); \ + goto error; \ + } \ + into = StrDuplicate(JsonValueAsString(value)); \ + } \ + else \ + { \ + Log(LOG_INFO, "Using default value " #default " for " key "."); \ + into = default ? StrDuplicate(default) : NULL; \ + } + +#define CONFIG_OPTIONAL_INTEGER(into, key, default) \ + value = HashMapGet(config, key); \ + if (value && JsonValueType(value) != JSON_NULL) \ + { \ + if (JsonValueType(value) != JSON_INTEGER) \ + { \ + Log(LOG_ERR, "Expected " key " to be of type JSON_INTEGER"); \ + goto error; \ + } \ + into = JsonValueAsInteger(value); \ + } \ + else \ + { \ + Log(LOG_INFO, "Using default value " #default " for " key "."); \ + into = default; \ + } + +int +ConfigParseRunAs(Config * tConfig, HashMap * config) +{ + JsonValue *value; + + CONFIG_REQUIRE("uid", JSON_STRING); + CONFIG_COPY_STRING(tConfig->uid); + + CONFIG_OPTIONAL_STRING(tConfig->gid, "gid", tConfig->uid); + + return 1; + +error: + return 0; +} + +int +ConfigParseLog(Config * tConfig, HashMap * config) +{ + JsonValue *value; + char *str; + + CONFIG_REQUIRE("output", JSON_STRING); + str = JsonValueAsString(value); + + if (strcmp(str, "stdout") == 0) + { + tConfig->flags |= CONFIG_LOG_STDOUT; + } + else if (strcmp(str, "file") == 0) + { + tConfig->flags |= CONFIG_LOG_FILE; + } + else if (strcmp(str, "syslog") == 0) + { + tConfig->flags |= CONFIG_LOG_SYSLOG; + } + else + { + Log(LOG_ERR, "Invalid value for log.output: '%s'.", str); + goto error; + } + + CONFIG_OPTIONAL_STRING(str, "level", "message"); + + if (strcmp(str, "message") == 0) + { + tConfig->logLevel = LOG_INFO; + } + else if (strcmp(str, "debug") == 0) + { + tConfig->logLevel = LOG_DEBUG; + } + else if (strcmp(str, "notice") == 0) + { + tConfig->logLevel = LOG_NOTICE; + } + else if (strcmp(str, "warning") == 0) + { + tConfig->logLevel = LOG_WARNING; + } + else if (strcmp(str, "error") == 0) + { + tConfig->logLevel = LOG_ERR; + } + else + { + Log(LOG_ERR, "Invalid value for log.level: '%s'.", tConfig->logLevel); + goto error; + } + + Free(str); + + CONFIG_OPTIONAL_STRING(tConfig->logTimestamp, "timestampFormat", "default"); + + if (strcmp(tConfig->logTimestamp, "none") == 0) + { + Free(tConfig->logTimestamp); + tConfig->logTimestamp = NULL; + } + + value = HashMapGet(config, "color"); + if (value && JsonValueType(value) != JSON_NULL) + { + if (JsonValueType(value) != JSON_BOOLEAN) + { + Log(LOG_ERR, "Expected type JSON_BOOLEAN for log.color."); + goto error; + } + + if (JsonValueAsBoolean(value)) + { + tConfig->flags |= CONFIG_LOG_COLOR; + } + } + + return 1; + +error: + return 0; +} + +Config * +ConfigParse(HashMap * config) +{ + Config *tConfig; + JsonValue *value; + + if (!config) + { + return NULL; + } + + tConfig = Malloc(sizeof(Config)); + if (!tConfig) + { + return NULL; + } + + memset(tConfig, 0, sizeof(Config)); + + CONFIG_OPTIONAL_INTEGER(tConfig->listenPort, "listen", 8008); + + CONFIG_REQUIRE("serverName", JSON_STRING); + CONFIG_COPY_STRING(tConfig->serverName); + + value = HashMapGet(config, "baseUrl"); + if (value) + { + CONFIG_COPY_STRING(tConfig->baseUrl); + } + else + { + Log(LOG_WARNING, "Base URL not specified. Assuming it's 'https://%s'.", tConfig->serverName); + tConfig->baseUrl = Malloc(strlen(tConfig->serverName) + 10); + if (!tConfig->baseUrl) + { + Log(LOG_ERR, "Error allocating memory for default config value 'baseUrl'."); + goto error; + } + + sprintf(tConfig->baseUrl, "https://%s", tConfig->serverName); + } + + CONFIG_OPTIONAL_STRING(tConfig->identityServer, "identityServer", NULL); + + value = HashMapGet(config, "runAs"); + if (value && JsonValueType(value) != JSON_NULL) + { + if (JsonValueType(value) == JSON_OBJECT) + { + if (!ConfigParseRunAs(tConfig, JsonValueAsObject(value))) + { + goto error; + } + } + else + { + Log(LOG_ERR, "Config directive 'runAs' should be a JSON object"); + Log(LOG_ERR, "that contains a 'uid' and 'gid'."); + goto error; + } + } + + CONFIG_REQUIRE("dataDir", JSON_STRING); + CONFIG_COPY_STRING(tConfig->dataDir); + + CONFIG_OPTIONAL_INTEGER(tConfig->threads, "threads", 1); + CONFIG_OPTIONAL_INTEGER(tConfig->maxConnections, "maxConnections", 32); + CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0); + + CONFIG_REQUIRE("federation", JSON_BOOLEAN); + if (JsonValueAsBoolean(value)) + { + tConfig->flags |= CONFIG_FEDERATION; + } + + CONFIG_REQUIRE("registration", JSON_BOOLEAN); + if (JsonValueAsBoolean(value)) + { + tConfig->flags |= CONFIG_REGISTRATION; + } + + CONFIG_REQUIRE("log", JSON_OBJECT); + if (!ConfigParseLog(tConfig, JsonValueAsObject(value))) + { + goto error; + } + + return tConfig; + +error: + ConfigFree(tConfig); + return NULL; +} + +void +ConfigFree(Config * tConfig) +{ + if (!tConfig) + { + return; + } + + Free(tConfig->serverName); + Free(tConfig->baseUrl); + Free(tConfig->identityServer); + + Free(tConfig->uid); + Free(tConfig->gid); + Free(tConfig->dataDir); + + Free(tConfig->logTimestamp); + + Free(tConfig); +} diff --git a/src/Main.c b/src/Main.c index c2452c2..6ede4be 100644 --- a/src/Main.c +++ b/src/Main.c @@ -34,7 +34,7 @@ #include #include -#include +#include #include #include #include @@ -87,7 +87,7 @@ main(int argc, char **argv) HashMap *config = NULL; /* Program configuration */ - TelodendriaConfig *tConfig = NULL; + Config *tConfig = NULL; /* User validation */ struct passwd *userInfo = NULL; @@ -184,7 +184,7 @@ main(int argc, char **argv) goto finish; } - tConfig = TelodendriaConfigParse(config); + tConfig = ConfigParse(config); JsonFree(config); if (!tConfig) @@ -209,7 +209,7 @@ main(int argc, char **argv) tConfig->logTimestamp = NULL; } - if (tConfig->flags & TELODENDRIA_LOG_COLOR) + if (tConfig->flags & CONFIG_LOG_COLOR) { LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR); } @@ -232,7 +232,7 @@ main(int argc, char **argv) } - if (tConfig->flags & TELODENDRIA_LOG_FILE) + if (tConfig->flags & CONFIG_LOG_FILE) { Stream *logFile = StreamOpen("telodendria.log", "a"); @@ -246,11 +246,11 @@ main(int argc, char **argv) Log(LOG_INFO, "Logging to the log file. Check there for all future messages."); LogConfigOutputSet(LogConfigGlobal(), logFile); } - else if (tConfig->flags & TELODENDRIA_LOG_STDOUT) + else if (tConfig->flags & CONFIG_LOG_STDOUT) { Log(LOG_DEBUG, "Already logging to standard output."); } - else if (tConfig->flags & TELODENDRIA_LOG_SYSLOG) + else if (tConfig->flags & CONFIG_LOG_SYSLOG) { Log(LOG_INFO, "Logging to the syslog. Check there for all future messages."); LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_SYSLOG); @@ -473,14 +473,14 @@ finish: * If we're not logging to standard output, then we can close it. Otherwise, * if we are logging to stdout, LogConfigFree() will close it for us. */ - if (tConfig && !(tConfig->flags & TELODENDRIA_LOG_STDOUT)) + if (tConfig && !(tConfig->flags & CONFIG_LOG_STDOUT)) { StreamClose(StreamStdout()); } DbClose(matrixArgs.db); - TelodendriaConfigFree(tConfig); + ConfigFree(tConfig); Log(LOG_DEBUG, "Exiting with code '%d'.", exit); diff --git a/src/Routes/RouteRegister.c b/src/Routes/RouteRegister.c index 035e730..af138b3 100644 --- a/src/Routes/RouteRegister.c +++ b/src/Routes/RouteRegister.c @@ -116,7 +116,7 @@ ROUTE_IMPL(RouteRegister, args) uiaFlows = ArrayCreate(); ArrayAdd(uiaFlows, RouteRegisterRegFlow()); - if (args->matrixArgs->config->flags & TELODENDRIA_REGISTRATION) + if (args->matrixArgs->config->flags & CONFIG_REGISTRATION) { ArrayAdd(uiaFlows, UiaDummyFlow()); } diff --git a/src/Uia.c b/src/Uia.c index 8f0b748..1935115 100644 --- a/src/Uia.c +++ b/src/Uia.c @@ -205,7 +205,7 @@ UiaStageBuild(char *type, HashMap * params) int UiaComplete(Array * flows, HttpServerContext * context, Db * db, - HashMap * request, HashMap ** response, TelodendriaConfig * config) + HashMap * request, HashMap ** response, Config * config) { JsonValue *val; HashMap *auth; diff --git a/src/include/TelodendriaConfig.h b/src/include/Config.h similarity index 73% rename from src/include/TelodendriaConfig.h rename to src/include/Config.h index 4e5ce6e..fd73ae4 100644 --- a/src/include/TelodendriaConfig.h +++ b/src/include/Config.h @@ -22,23 +22,23 @@ * SOFTWARE. */ -#ifndef TELODENDRIA_TELODENDRIACONFIG_H -#define TELODENDRIA_TELODENDRIACONFIG_H +#ifndef TELODENDRIA_CONFIG_H +#define TELODENDRIA_CONFIG_H #include #include -typedef enum TelodendriaConfigFlag +typedef enum ConfigFlag { - TELODENDRIA_FEDERATION = (1 << 0), - TELODENDRIA_REGISTRATION = (1 << 1), - TELODENDRIA_LOG_COLOR = (1 << 2), - TELODENDRIA_LOG_FILE = (1 << 3), - TELODENDRIA_LOG_STDOUT = (1 << 4), - TELODENDRIA_LOG_SYSLOG = (1 << 5) -} TelodendriaConfigFlag; + CONFIG_FEDERATION = (1 << 0), + CONFIG_REGISTRATION = (1 << 1), + CONFIG_LOG_COLOR = (1 << 2), + CONFIG_LOG_FILE = (1 << 3), + CONFIG_LOG_STDOUT = (1 << 4), + CONFIG_LOG_SYSLOG = (1 << 5) +} ConfigFlag; -typedef struct TelodendriaConfig +typedef struct Config { char *serverName; char *baseUrl; @@ -57,12 +57,12 @@ typedef struct TelodendriaConfig char *logTimestamp; int logLevel; -} TelodendriaConfig; +} Config; -extern TelodendriaConfig * - TelodendriaConfigParse(HashMap *); +extern Config * + ConfigParse(HashMap *); extern void - TelodendriaConfigFree(TelodendriaConfig *); + ConfigFree(Config *); -#endif +#endif /* TELODENDRIA_CONFIG_H */ diff --git a/src/include/Matrix.h b/src/include/Matrix.h index a07f559..3b4e26f 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -28,7 +28,7 @@ #include #include -#include +#include #include typedef enum MatrixError @@ -69,7 +69,7 @@ typedef enum MatrixError typedef struct MatrixHttpHandlerArgs { - TelodendriaConfig *config; + Config *config; Db *db; } MatrixHttpHandlerArgs; diff --git a/src/include/Uia.h b/src/include/Uia.h index 17fdec0..551bd6e 100644 --- a/src/include/Uia.h +++ b/src/include/Uia.h @@ -41,7 +41,7 @@ extern void UiaCleanup(MatrixHttpHandlerArgs *); extern int - UiaComplete(Array *, HttpServerContext *, Db *, HashMap *, HashMap **, TelodendriaConfig *); + UiaComplete(Array *, HttpServerContext *, Db *, HashMap *, HashMap **, Config *); extern void UiaFlowsFree(Array *);