[MOD/WIP] Refactor out code to use the j2s schema.

Do note this is more of a hack/bodge than anything right now,
and this is still a WIP.
This commit is contained in:
lda 2023-12-02 17:03:31 +01:00
parent 74ab3f350f
commit fbc4486930
Signed by: lda
GPG key ID: 6898757653ABE3E6
6 changed files with 108 additions and 485 deletions

View file

@ -1,6 +1,8 @@
{
"guard": "TELODENDRIA_SCHEMA_CONFIG_H",
"header": "Schema\/Config.h",
"include": [ "Cytoplasm\/Db.h", "Cytoplasm/HttpServer.h" ],
"types": {
"ConfigTls": {
"fields": {
@ -9,8 +11,15 @@
},
"type": "struct"
},
"HttpHandler *": { "type": "extern" },
"void *": { "type": "extern" },
"ConfigListener": {
"fields": {
"handler": { "type": "HttpHandler *", "ignore": true },
"handlerArgs": { "type": "void *", "ignore": true },
"port": { "type": "integer", "required": true },
"threads": { "type": "integer", "required": false },
"maxConnections": { "type": "integer", "required": false },
@ -52,8 +61,19 @@
},
"type": "struct"
},
"Db *": { "type": "extern" },
"DbRef *": { "type": "extern" },
"char *": { "type": "extern" },
"Config": {
"fields": {
"db": { "type": "Db *", "ignore": true },
"ref": { "type": "DbRef *", "ignore": true },
"ok": { "type": "boolean", "ignore": true },
"err": { "type": "char *", "ignore": true },
"listen": { "type": "[ConfigListener]", "required": true },
"runAs": { "type": "ConfigRunAs", "required": false },
"log": { "type": "ConfigLogConfig", "required": true },

View file

@ -21,7 +21,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Config.h>
#include <Schema/Config.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/HashMap.h>
@ -41,378 +41,60 @@
#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
#endif
#define CONFIG_REQUIRE(key, type) \
value = HashMapGet(config, key); \
if (!value) \
{ \
tConfig->err = "Missing required " key " directive."; \
goto error; \
} \
if (JsonValueType(value) == JSON_NULL) \
{ \
tConfig->err = "Missing value for " key " directive."; \
goto error; \
} \
if (JsonValueType(value) != type) \
{ \
tConfig->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) \
{ \
tConfig->err = "Expected " key " to be of type JSON_STRING"; \
goto error; \
} \
into = StrDuplicate(JsonValueAsString(value)); \
} \
else \
{ \
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) \
{ \
tConfig->err = "Expected " key " to be of type JSON_INTEGER"; \
goto error; \
} \
into = Int64Low(JsonValueAsInteger(value)); \
} \
else \
{ \
into = default; \
}
static 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;
}
static int
ConfigParseListen(Config * tConfig, Array * listen)
{
size_t i;
if (!ArraySize(listen))
{
tConfig->err = "Listen array cannot be empty; you must specify at least one listener.";
goto error;
}
if (!tConfig->servers)
{
tConfig->servers = ArrayCreate();
if (!tConfig->servers)
{
tConfig->err = "Unable to allocate memory for listener configurations.";
goto error;
}
}
for (i = 0; i < ArraySize(listen); i++)
{
JsonValue *val = ArrayGet(listen, i);
HashMap *obj;
HttpServerConfig *serverCfg = Malloc(sizeof(HttpServerConfig));
if (!serverCfg)
{
tConfig->err = "Unable to allocate memory for listener configuration.";
goto error;
}
if (JsonValueType(val) != JSON_OBJECT)
{
tConfig->err = "Invalid value in listener array. All listeners must be objects.";
goto error;
}
obj = JsonValueAsObject(val);
serverCfg->port = Int64Low(JsonValueAsInteger(HashMapGet(obj, "port")));
serverCfg->threads = Int64Low(JsonValueAsInteger(HashMapGet(obj, "threads")));
serverCfg->maxConnections = Int64Low(JsonValueAsInteger(HashMapGet(obj, "maxConnections")));
if (!serverCfg->port)
{
Free(serverCfg);
continue;
}
if (!serverCfg->threads)
{
serverCfg->threads = 4;
}
if (!serverCfg->maxConnections)
{
serverCfg->maxConnections = 32;
}
val = HashMapGet(obj, "tls");
if ((JsonValueType(val) == JSON_BOOLEAN && !JsonValueAsBoolean(val)) || JsonValueType(val) == JSON_NULL)
{
serverCfg->flags = HTTP_FLAG_NONE;
serverCfg->tlsCert = NULL;
serverCfg->tlsKey = NULL;
}
else if (JsonValueType(val) != JSON_OBJECT)
{
tConfig->err = "Invalid value for listener.tls. It must be an object.";
goto error;
}
else
{
serverCfg->flags = HTTP_FLAG_TLS;
obj = JsonValueAsObject(val);
serverCfg->tlsCert = StrDuplicate(JsonValueAsString(HashMapGet(obj, "cert")));
serverCfg->tlsKey = StrDuplicate(JsonValueAsString(HashMapGet(obj, "key")));
if (!serverCfg->tlsCert || !serverCfg->tlsKey)
{
tConfig->err = "TLS cert and key must both be valid file names.";
goto error;
}
}
ArrayAdd(tConfig->servers, serverCfg);
}
return 1;
error:
return 0;
}
static int
ConfigParseLog(Config * tConfig, HashMap * config)
{
JsonValue *value;
char *str;
CONFIG_REQUIRE("output", JSON_STRING);
str = JsonValueAsString(value);
if (StrEquals(str, "stdout"))
{
tConfig->flags |= CONFIG_LOG_STDOUT;
}
else if (StrEquals(str, "file"))
{
tConfig->flags |= CONFIG_LOG_FILE;
}
else if (StrEquals(str, "syslog"))
{
tConfig->flags |= CONFIG_LOG_SYSLOG;
}
else
{
tConfig->err = "Invalid value for log.output";
goto error;
}
CONFIG_OPTIONAL_STRING(str, "level", "message");
if (StrEquals(str, "message"))
{
tConfig->logLevel = LOG_INFO;
}
else if (StrEquals(str, "debug"))
{
tConfig->logLevel = LOG_DEBUG;
}
else if (StrEquals(str, "notice"))
{
tConfig->logLevel = LOG_NOTICE;
}
else if (StrEquals(str, "warning"))
{
tConfig->logLevel = LOG_WARNING;
}
else if (StrEquals(str, "error"))
{
tConfig->logLevel = LOG_ERR;
}
else
{
tConfig->err = "Invalid value for log.level.";
goto error;
}
Free(str);
CONFIG_OPTIONAL_STRING(tConfig->logTimestamp, "timestampFormat", "default");
if (StrEquals(tConfig->logTimestamp, "none"))
{
Free(tConfig->logTimestamp);
tConfig->logTimestamp = NULL;
}
value = HashMapGet(config, "color");
if (value && JsonValueType(value) != JSON_NULL)
{
if (JsonValueType(value) != JSON_BOOLEAN)
{
tConfig->err = "Expected type JSON_BOOLEAN for log.color.";
goto error;
}
if (JsonValueAsBoolean(value))
{
tConfig->flags |= CONFIG_LOG_COLOR;
}
}
return 1;
error:
return 0;
}
void
ConfigFree(Config * tConfig)
{
if (!tConfig)
{
return;
}
Free(tConfig->serverName);
Free(tConfig->baseUrl);
Free(tConfig->identityServer);
Free(tConfig->uid);
Free(tConfig->gid);
Free(tConfig->logTimestamp);
if (tConfig->servers)
{
size_t i;
for (i = 0; i < ArraySize(tConfig->servers); i++)
{
HttpServerConfig *serverCfg = ArrayGet(tConfig->servers, i);
Free(serverCfg->tlsCert);
Free(serverCfg->tlsKey);
Free(serverCfg);
}
ArrayFree(tConfig->servers);
}
Free(tConfig);
}
Config *
ConfigParse(HashMap * config)
{
Config *tConfig;
JsonValue *value;
size_t i;
if (!config)
{
return NULL;
}
tConfig = Malloc(sizeof(Config));
if (!tConfig)
{
return NULL;
}
memset(tConfig, 0, sizeof(Config));
CONFIG_REQUIRE("listen", JSON_ARRAY);
if (!ConfigParseListen(tConfig, JsonValueAsArray(value)))
tConfig->maxCache = Int64Create(0, 0);
if (!ConfigFromJson(config, tConfig, &tConfig->err))
{
ConfigFree(tConfig);
goto error;
}
CONFIG_REQUIRE("serverName", JSON_STRING);
CONFIG_COPY_STRING(tConfig->serverName);
value = HashMapGet(config, "baseUrl");
if (value)
{
CONFIG_COPY_STRING(tConfig->baseUrl);
}
else
if (!tConfig->baseUrl)
{
size_t len = strlen(tConfig->serverName) + 10;
tConfig->baseUrl = Malloc(len);
if (!tConfig->baseUrl)
{
tConfig->err = "Error allocating memory for default config value 'baseUrl'.";
goto error;
}
snprintf(tConfig->baseUrl, len, "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)))
{
tConfig->err = "Couldn't allocate enough memory for 'baseUrl'.";
goto error;
}
}
else
if (!tConfig->log.timestampFormat)
{
tConfig->err = "Config directive 'runAs' should be a JSON object that contains a 'uid' and 'gid'.";
goto error;
tConfig->log.timestampFormat = StrDuplicate("default");
}
}
CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0);
CONFIG_REQUIRE("federation", JSON_BOOLEAN);
if (JsonValueAsBoolean(value))
for (i = 0; i < ArraySize(tConfig->listen); i++)
{
tConfig->flags |= CONFIG_FEDERATION;
}
CONFIG_REQUIRE("registration", JSON_BOOLEAN);
if (JsonValueAsBoolean(value))
ConfigListener *listener = ArrayGet(tConfig->listen, i);
if (Int64Eq(listener->maxConnections, Int64Create(0, 0)))
{
tConfig->flags |= CONFIG_REGISTRATION;
listener->maxConnections = Int64Create(0, 32);
}
CONFIG_REQUIRE("log", JSON_OBJECT);
if (!ConfigParseLog(tConfig, JsonValueAsObject(value)))
if (Int64Eq(listener->threads, Int64Create(0, 0)))
{
goto error;
listener->threads = Int64Create(0, 4);
}
if (Int64Eq(listener->port, Int64Create(0, 0)))
{
listener->port = Int64Create(0, 8008);
}
}
tConfig->ok = 1;
tConfig->err = NULL;
return tConfig;
@ -435,41 +117,7 @@ ConfigCreateDefault(Db * db)
HashMap *json;
Array *listeners;
HashMap *listen;
char hostname[HOST_NAME_MAX + 1];
if (!db)
{
return 0;
}
ref = DbCreate(db, 1, "config");
if (!ref)
{
return 0;
}
json = DbJson(ref);
JsonSet(json, JsonValueString("file"), 2, "log", "output");
listeners = ArrayCreate();
listen = HashMapCreate();
HashMapSet(listen, "port", JsonValueInteger(Int64Create(0, 8008)));
HashMapSet(listen, "tls", JsonValueBoolean(0));
ArrayAdd(listeners, JsonValueObject(listen));
HashMapSet(json, "listen", JsonValueArray(listeners));
if (gethostname(hostname, HOST_NAME_MAX + 1) < 0)
{
strncpy(hostname, "localhost", HOST_NAME_MAX);
}
HashMapSet(json, "serverName", JsonValueString(hostname));
HashMapSet(json, "federation", JsonValueBoolean(1));
HashMapSet(json, "registration", JsonValueBoolean(0));
return DbUnlock(db, ref);
}
Config *
@ -493,6 +141,17 @@ ConfigLock(Db * db)
return config;
}
void
ConfigFullyFree(Config * config)
{
if (!config)
{
return;
}
ConfigFree(config);
Free(config);
}
int
ConfigUnlock(Config * config)
{
@ -507,6 +166,6 @@ ConfigUnlock(Config * config)
db = config->db;
dbRef = config->ref;
ConfigFree(config);
ConfigFullyFree(config);
return DbUnlock(db, dbRef);
}

View file

@ -277,17 +277,18 @@ start:
goto finish;
}
if (!tConfig->logTimestamp || !StrEquals(tConfig->logTimestamp, "default"))
if (!tConfig->log.timestampFormat || !StrEquals(tConfig->log.timestampFormat, "default"))
{
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig->logTimestamp);
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig->log.timestampFormat);
}
else
{
Free(tConfig->logTimestamp);
tConfig->logTimestamp = NULL;
/* TODO */
/*Free(tConfig->logTimestamp);
tConfig->logTimestamp = NULL;*/
}
if (tConfig->flags & CONFIG_LOG_COLOR)
if (tConfig->log.color)
{
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR);
}
@ -296,9 +297,10 @@ start:
LogConfigFlagClear(LogConfigGlobal(), LOG_FLAG_COLOR);
}
LogConfigLevelSet(LogConfigGlobal(), flags & ARG_VERBOSE ? LOG_DEBUG : tConfig->logLevel);
/* TODO: Set level properly here. */
/*LogConfigLevelSet(LogConfigGlobal(), flags & ARG_VERBOSE ? LOG_DEBUG : tConfig->logLevel);*/
if (tConfig->flags & CONFIG_LOG_FILE)
if (tConfig->log.output == CONFIG_LOG_OUTPUT_FILE)
{
logFile = StreamOpen("telodendria.log", "a");
@ -306,18 +308,14 @@ start:
{
Log(LOG_ERR, "Unable to open log file for appending.");
exit = EXIT_FAILURE;
tConfig->flags &= CONFIG_LOG_STDOUT;
/*tConfig->flags &= CONFIG_LOG_STDOUT;*/
goto finish;
}
Log(LOG_INFO, "Logging to the log file. Check there for all future messages.");
LogConfigOutputSet(LogConfigGlobal(), logFile);
}
else if (tConfig->flags & CONFIG_LOG_STDOUT)
{
Log(LOG_DEBUG, "Already logging to standard output.");
}
else if (tConfig->flags & CONFIG_LOG_SYSLOG)
else if (tConfig->log.output == CONFIG_LOG_OUTPUT_SYSLOG)
{
Log(LOG_INFO, "Logging to the syslog. Check there for all future messages.");
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_SYSLOG);
@ -327,13 +325,6 @@ start:
* messages get passed to the syslog */
setlogmask(LOG_UPTO(LOG_DEBUG));
}
else
{
Log(LOG_ERR, "Unknown logging method in flags: '%d'", tConfig->flags);
Log(LOG_ERR, "This is a programmer error; please report it.");
exit = EXIT_FAILURE;
goto finish;
}
/* If a token was created with a default config, print it to the
* log */
@ -348,9 +339,9 @@ start:
Log(LOG_DEBUG, "Server Name: %s", tConfig->serverName);
Log(LOG_DEBUG, "Base URL: %s", tConfig->baseUrl);
Log(LOG_DEBUG, "Identity Server: %s", tConfig->identityServer);
Log(LOG_DEBUG, "Run As: %s:%s", tConfig->uid, tConfig->gid);
Log(LOG_DEBUG, "Run As: %s:%s", tConfig->runAs.uid, tConfig->runAs.gid);
Log(LOG_DEBUG, "Max Cache: %ld", tConfig->maxCache);
Log(LOG_DEBUG, "Flags: %x", tConfig->flags);
/*Log(LOG_DEBUG, "Flags: %x", tConfig->flags);*/
LogConfigUnindent(LogConfigGlobal());
httpServers = ArrayCreate();
@ -362,41 +353,50 @@ start:
}
/* Bind servers before possibly dropping permissions. */
for (i = 0; i < ArraySize(tConfig->servers); i++)
for (i = 0; i < ArraySize(tConfig->listen); i++)
{
HttpServerConfig *serverCfg = ArrayGet(tConfig->servers, i);
ConfigListener *serverCfg = ArrayGet(tConfig->listen, i);
/* TODO: Think of a nicer solution. */
HttpServerConfig args;
Log(LOG_DEBUG, "HTTP listener: %lu", i);
LogConfigIndent(LogConfigGlobal());
Log(LOG_DEBUG, "Port: %hu", serverCfg->port);
Log(LOG_DEBUG, "Threads: %u", serverCfg->threads);
Log(LOG_DEBUG, "Max Connections: %u", serverCfg->maxConnections);
Log(LOG_DEBUG, "Flags: %d", serverCfg->flags);
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tlsCert);
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tlsKey);
/*Log(LOG_DEBUG, "Flags: %d", serverCfg->flags);*/
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tls.cert);
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tls.key);
LogConfigUnindent(LogConfigGlobal());
serverCfg->handler = MatrixHttpHandler;
serverCfg->handlerArgs = &matrixArgs;
args.port = serverCfg->port;
args.threads = serverCfg->maxConnections;
args.maxConnections = serverCfg->maxConnections;
args.tlsCert = serverCfg->tls.cert;
args.tlsKey = serverCfg->tls.key;
if (serverCfg->flags & HTTP_FLAG_TLS)
args.handler = MatrixHttpHandler;
args.handlerArgs = &matrixArgs;
if (serverCfg->tls.cert && serverCfg->tls.key)
{
if (UInt64Eq(UtilLastModified(serverCfg->tlsCert), UInt64Create(0, 0)))
if (UInt64Eq(UtilLastModified(serverCfg->tls.cert), UInt64Create(0, 0)))
{
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tlsCert);
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.cert);
exit = EXIT_FAILURE;
goto finish;
}
if (UInt64Eq(UtilLastModified(serverCfg->tlsKey), UInt64Create(0, 0)))
if (UInt64Eq(UtilLastModified(serverCfg->tls.key), UInt64Create(0, 0)))
{
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tlsKey);
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.key);
exit = EXIT_FAILURE;
goto finish;
}
}
server = HttpServerCreate(serverCfg);
server = HttpServerCreate(&args);
if (!server)
{
Log(LOG_ERR, "Unable to create HTTP server on port %d: %s",
@ -417,10 +417,10 @@ start:
Log(LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid());
if (tConfig->uid && tConfig->gid)
if (tConfig->runAs.uid && tConfig->runAs.gid)
{
userInfo = getpwnam(tConfig->uid);
groupInfo = getgrnam(tConfig->gid);
userInfo = getpwnam(tConfig->runAs.uid);
groupInfo = getgrnam(tConfig->runAs.gid);
if (!userInfo || !groupInfo)
{
@ -450,7 +450,7 @@ start:
}
else
{
Log(LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig->uid, tConfig->gid);
Log(LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig->runAs.uid, tConfig->runAs.gid);
}
}
else
@ -462,7 +462,7 @@ start:
}
else
{
if (tConfig->uid && tConfig->gid)
if (tConfig->runAs.uid && tConfig->runAs.gid)
{
if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid)
{

View file

@ -118,7 +118,7 @@ ROUTE_IMPL(RouteConfig, path, argp)
response = MatrixErrorCreate(M_BAD_JSON, newConf->err);
}
ConfigFree(newConf);
ConfigFullyFree(newConf);
JsonFree(request);
break;
case HTTP_PUT:
@ -165,7 +165,7 @@ ROUTE_IMPL(RouteConfig, path, argp)
response = MatrixErrorCreate(M_BAD_JSON, newConf->err);
}
ConfigFree(newConf);
ConfigFullyFree(newConf);
JsonFree(request);
JsonFree(newJson);
break;

View file

@ -134,7 +134,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
uiaFlows = ArrayCreate();
ArrayAdd(uiaFlows, RouteRegisterRegFlow());
if (config->flags & CONFIG_REGISTRATION)
if (config->registration)
{
ArrayAdd(uiaFlows, UiaDummyFlow());
}

View file

@ -48,68 +48,12 @@
* .Xr telodendria-config 7 .
*/
#include <Schema/Config.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Db.h>
/**
* Bit flags that can be set in the flags field of the configuration
* structure.
*/
typedef enum ConfigFlag
{
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;
/**
* The configuration structure is not opaque like many of the other
* structures present in the other public APIs. This is intentional;
* defining functions for all of the fields would simply add too much
* unnecessary overhead.
*/
typedef struct Config
{
/*
* These are used internally and should not be touched outside of
* the functions defined in this API.
*/
Db *db;
DbRef *ref;
/*
* Whether or not the parsing was successful. If this boolean
* value is 0, then read the error message and assume that all
* other fields are invalid.
*/
int ok;
char *err;
char *serverName;
char *baseUrl;
char *identityServer;
char *uid;
char *gid;
unsigned int flags;
size_t maxCache;
char *logTimestamp;
int logLevel;
/*
* An array of HttpServerConfig structures. Consult the HttpServer
* API.
*/
Array *servers;
} Config;
/**
* Parse a JSON object, extracting the necessary values, validating
* them, and adding them to the configuration structure for use by the
@ -127,7 +71,7 @@ extern Config * ConfigParse(HashMap *);
* as well as the structure itself, such that it is completely invalid
* when this function returns.
*/
extern void ConfigFree(Config *);
extern void ConfigFullyFree(Config *);
/**
* Check whether or not the configuration exists in the database,