Compare commits

..

No commits in common. "d050cef9fdb842edcffe63150e5845440cc39abc" and "af4fd5dcebf73fe88f6ac7fe690d19834ea16b49" have entirely different histories.

61 changed files with 687 additions and 554 deletions

View file

@ -1,13 +0,0 @@
N: Jordan Bancino
E: jordan@bancino.net
M: @jordan:bancino.net
W: https://bancino.net
D: Project Lead
L: United States
N: LDA
E: marie@doskel.net
E: ldasta@tedomum.fr
E: lda@freetards.xyz
D: Developer
L: France

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,88 +0,0 @@
{
"guard": "TELODENDRIA_SCHEMA_CONFIG_H",
"header": "Schema\/Config.h",
"include": [ "Cytoplasm\/Db.h", "Cytoplasm/HttpServer.h" ],
"types": {
"ConfigTls": {
"fields": {
"cert": { "type": "string", "required": true },
"key": { "type": "string", "required": true }
},
"type": "struct"
},
"ConfigListener": {
"fields": {
"port": { "type": "integer", "required": true },
"threads": { "type": "integer", "required": false },
"maxConnections": { "type": "integer", "required": false },
"tls": { "type": "ConfigTls", "required": false }
},
"type": "struct"
},
"ConfigRunAs": {
"fields": {
"uid": { "type": "string", "required": false },
"gid": { "type": "string", "required": true }
},
"type": "struct"
},
"ConfigLogOutput": {
"fields": {
"stdout": { "name": "CONFIG_LOG_OUTPUT_STDOUT" },
"file": { "name": "CONFIG_LOG_OUTPUT_FILE" },
"syslog": { "name": "CONFIG_LOG_OUTPUT_SYSLOG" }
},
"type": "enum"
},
"ConfigLogLevel": {
"fields": {
"message": { "name": "CONFIG_LOG_LEVEL_MESSAGE" },
"debug": { "name": "CONFIG_LOG_LEVEL_DEBUG" },
"notice": { "name": "CONFIG_LOG_LEVEL_NOTICE" },
"warning": { "name": "CONFIG_LOG_LEVEL_WARNING" },
"error": { "name": "CONFIG_LOG_LEVEL_ERROR" }
},
"type": "enum"
},
"ConfigLogConfig": {
"fields": {
"output": { "type": "ConfigLogOutput", "required": true },
"level": { "type": "ConfigLogLevel", "required": false },
"timestampFormat":{ "type": "string", "required": false },
"color": { "type": "boolean", "required": false }
},
"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 },
"serverName": { "type": "string", "required": true },
"baseUrl": { "type": "string", "required": false },
"identityServer": { "type": "string", "required": false },
"pid": { "type": "string", "required": false },
"maxCache": { "type": "integer", "required": false },
"federation": { "type": "boolean", "required": true },
"registration": { "type": "boolean", "required": true }
},
"type": "struct"
}
}
}

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -31,7 +31,6 @@ The following endpoints were added:
### Bug Fixes & General Improvements
- Use `j2s` for parsing the configuration
- Fixed a double-free in `RouteUserProfile()` that would cause errors
with certain Matrix clients. (#35)
- Improved compatibility with NetBSD on various platforms.
@ -44,8 +43,6 @@ parsing request bodies.
### New Features
- Implemented a `"pid"` option in the configuration, allowing Telodendria
to write its process ID to a specified file.
- Moved all administrator API endpoints to `/_telodendria/admin/v1`,
because later revisions of the administrator API may break clients, so
we want a way to give those breaking revisions new endpoints.

View file

@ -213,44 +213,3 @@ comments to the appropriate header.
If your pull request does not also include proper documentation, it
will likely be rejected.
### Be Recognized!
If your pull request gets approved, you should be recognized for your
contributions to the project!
To have your work recognized, add your information to the `CONTRIBUTORS.txt`
file in the root of the Telodendria repository if it isn't there already.
You should do this as a part of your pull request so that when it is merged,
your information will be automatically added to the repository.
The `CONTRIBUTORS.txt` file loosely follows the Linux kernel's
[CREDITS](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/CREDITS)
file format. It is designed to be human-readable, but also parsable by
scripts.
The following fields are available:
```
(N) Name
(E) Email
(M) Matrix ID
(W) Website
(D) Description of contribution
(L) Physical location
```
Here are the rules:
* All fields are optional. If you don't want to include a field, that's
okay, simply omit it.
* All fields identify you however you wish. The goal is to recognize you for
your contribution, but if you wish to remain anonymous, you don't have to
use your real information.
* All fields can be specified multiple times. For example, if you have
multiple email addresses, websites, or Matrix IDs and you want to include
all of them, you absolutely may. Likewise, if you have made multiple
contributions, you can add multiple description entries.
* You can make up your own fields if you want. Just add their description
above.
* Leave exactly one blank like between entries in this file.

View file

@ -19,7 +19,8 @@ key-value form:
"serverName": "telodendria.io",
"listen": [
{
"port": 8008
"port": 8008,
"tls": false
}
]
@ -50,7 +51,7 @@ Here are the top-level directives:
this is a concern, a reverse-proxy such as `relayd` can be placed
in front of Telodendria to block access to undesired APIs.
- **tls:** `Object`
- **tls:** `Object|null|false`
Telodendria can be compiled with TLS support. If it is, then a
particular listener can be set to use TLS for connections. If
@ -105,9 +106,6 @@ Here are the top-level directives:
or you want to start over. **serverName** should be a DNS name that
can be publicly resolved. This directive is required.
- **pid:** `String`
Configure the file Telodendria writes its PID to.
- **baseUrl:** `String`
Set the server's base URL. **baseUrl** should be a valid URL,

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -22,89 +21,405 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Schema/Config.h>
#include <Config.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Db.h>
#include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Int64.h>
#include <Cytoplasm/Util.h>
#include <sys/types.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <grp.h>
#include <pwd.h>
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
#endif
void
ConfigParse(HashMap * config, Config *tConfig)
#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;
if (!config)
{
tConfig->ok = 0;
tConfig->err = "Invalid object given as config.";
return;
return NULL;
}
tConfig = Malloc(sizeof(Config));
if (!tConfig)
{
return NULL;
}
memset(tConfig, 0, sizeof(Config));
tConfig->maxCache = Int64Create(0, 0);
if (!ConfigFromJson(config, tConfig, &tConfig->err))
CONFIG_REQUIRE("listen", JSON_ARRAY);
if (!ConfigParseListen(tConfig, JsonValueAsArray(value)))
{
ConfigFree(tConfig);
goto error;
}
if (!tConfig->baseUrl)
CONFIG_REQUIRE("serverName", JSON_STRING);
CONFIG_COPY_STRING(tConfig->serverName);
value = HashMapGet(config, "baseUrl");
if (value)
{
CONFIG_COPY_STRING(tConfig->baseUrl);
}
else
{
size_t len = strlen(tConfig->serverName) + 10;
tConfig->baseUrl = Malloc(len);
if (!tConfig->baseUrl)
{
tConfig->err = "Couldn't allocate enough memory for 'baseUrl'.";
tConfig->err = "Error allocating memory for default config value 'baseUrl'.";
goto error;
}
snprintf(tConfig->baseUrl, len, "https://%s/", tConfig->serverName);
snprintf(tConfig->baseUrl, len, "https://%s", tConfig->serverName);
}
if (!tConfig->log.timestampFormat)
CONFIG_OPTIONAL_STRING(tConfig->identityServer, "identityServer", NULL);
value = HashMapGet(config, "runAs");
if (value && JsonValueType(value) != JSON_NULL)
{
tConfig->log.timestampFormat = StrDuplicate("default");
if (JsonValueType(value) == JSON_OBJECT)
{
if (!ConfigParseRunAs(tConfig, JsonValueAsObject(value)))
{
goto error;
}
}
else
{
tConfig->err = "Config directive 'runAs' should be a JSON object that contains a 'uid' and 'gid'.";
goto error;
}
}
for (i = 0; i < ArraySize(tConfig->listen); i++)
CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0);
CONFIG_REQUIRE("federation", JSON_BOOLEAN);
if (JsonValueAsBoolean(value))
{
ConfigListener *listener = ArrayGet(tConfig->listen, i);
if (Int64Eq(listener->maxConnections, Int64Create(0, 0)))
{
listener->maxConnections = Int64Create(0, 32);
}
if (Int64Eq(listener->threads, Int64Create(0, 0)))
{
listener->threads = Int64Create(0, 4);
}
if (Int64Eq(listener->port, Int64Create(0, 0)))
{
listener->port = Int64Create(0, 8008);
}
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;
}
tConfig->ok = 1;
tConfig->err = NULL;
return;
return tConfig;
error:
tConfig->ok = 0;
return;
return tConfig;
}
int
@ -116,91 +431,75 @@ ConfigExists(Db * db)
int
ConfigCreateDefault(Db * db)
{
Config config;
ConfigListener *listener;
HashMap *json;
JsonValue *val;
DbRef *ref;
HashMap *json;
Array *listeners;
HashMap *listen;
size_t len;
char hostname[HOST_NAME_MAX + 1];
memset(&config, 0, sizeof(Config));
config.log.output = CONFIG_LOG_OUTPUT_FILE;
config.runAs.gid = StrDuplicate(getgrgid(getgid())->gr_name);
config.runAs.uid = StrDuplicate(getpwuid(getuid())->pw_name);
config.registration = 0;
config.federation = 1;
/* Create serverName and baseUrl. */
config.serverName = Malloc(HOST_NAME_MAX + 1);
memset(config.serverName, 0, HOST_NAME_MAX + 1);
gethostname(config.serverName, HOST_NAME_MAX);
len = strlen(config.serverName) + 10;
config.baseUrl = Malloc(len);
snprintf(config.baseUrl, len, "https://%s/", config.serverName);
/* Add simple listener without TLS. */
config.listen = ArrayCreate();
listener = Malloc(sizeof(ConfigListener));
listener->maxConnections = Int64Create(0, 32);
listener->port = Int64Create(0, 8008);
listener->threads = Int64Create(0, 4);
ArrayAdd(config.listen, listener);
/* Write it all out to the configuration file. */
json = ConfigToJson(&config);
val = JsonGet(json, 1, "listen");
val = ArrayGet(JsonValueAsArray(val), 0);
JsonValueFree(HashMapDelete(JsonValueAsObject(val), "tls"));
if (!db)
{
return 0;
}
ref = DbCreate(db, 1, "config");
if (!ref)
{
ConfigFree(&config);
return 0;
}
DbJsonSet(ref, json);
DbUnlock(db, ref);
ConfigFree(&config);
JsonFree(json);
json = DbJson(ref);
return 1;
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);
}
void
ConfigLock(Db * db, Config *config)
Config *
ConfigLock(Db * db)
{
Config *config;
DbRef *ref = DbLock(db, 1, "config");
if (!ref)
{
config->ok = 0;
config->err = "Couldn't lock configuration.";
return NULL;
}
ConfigParse(DbJson(ref), config);
if (config->ok)
config = ConfigParse(DbJson(ref));
if (config)
{
config->db = db;
config->ref = ref;
}
return config;
}
int
ConfigUnlock(Config *config)
ConfigUnlock(Config * config)
{
Db *db;
DbRef *dbRef;
if (!config->ok)
if (!config)
{
return 0;
}
@ -209,25 +508,5 @@ ConfigUnlock(Config *config)
dbRef = config->ref;
ConfigFree(config);
config->ok = 0;
return DbUnlock(db, dbRef);
}
int
ConfigLogLevelToSyslog(ConfigLogLevel level)
{
switch (level)
{
case CONFIG_LOG_LEVEL_NOTICE:
return LOG_NOTICE;
case CONFIG_LOG_LEVEL_ERROR:
return LOG_ERR;
case CONFIG_LOG_LEVEL_MESSAGE:
return LOG_INFO;
case CONFIG_LOG_LEVEL_DEBUG:
return LOG_DEBUG;
case CONFIG_LOG_LEVEL_WARNING:
return LOG_WARNING;
}
return LOG_INFO;
}

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -103,11 +102,8 @@ Main(Array * args)
char *dbPath;
/* Program configuration */
Config tConfig;
Config *tConfig;
Stream *logFile;
Stream *pidFile;
char *pidPath;
/* User validation */
struct passwd *userInfo;
@ -136,9 +132,8 @@ start:
exit = EXIT_SUCCESS;
flags = 0;
dbPath = NULL;
tConfig = NULL;
logFile = NULL;
pidFile = NULL;
pidPath = NULL;
userInfo = NULL;
groupInfo = NULL;
cron = NULL;
@ -266,20 +261,33 @@ start:
Log(LOG_NOTICE, "Loading configuration...");
ConfigLock(matrixArgs.db, &tConfig);
if (!tConfig.ok)
tConfig = ConfigLock(matrixArgs.db);
if (!tConfig)
{
Log(LOG_ERR, tConfig.err);
Log(LOG_ERR, "Error locking the configuration.");
Log(LOG_ERR, "The configuration object is corrupted or otherwise invalid.");
Log(LOG_ERR, "Please restore from a backup.");
exit = EXIT_FAILURE;
goto finish;
}
else if (!tConfig->ok)
{
Log(LOG_ERR, tConfig->err);
exit = EXIT_FAILURE;
goto finish;
}
if (!tConfig.log.timestampFormat || !StrEquals(tConfig.log.timestampFormat, "default"))
if (!tConfig->logTimestamp || !StrEquals(tConfig->logTimestamp, "default"))
{
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig.log.timestampFormat);
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig->logTimestamp);
}
else
{
Free(tConfig->logTimestamp);
tConfig->logTimestamp = NULL;
}
if (tConfig.log.color)
if (tConfig->flags & CONFIG_LOG_COLOR)
{
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR);
}
@ -288,13 +296,9 @@ start:
LogConfigFlagClear(LogConfigGlobal(), LOG_FLAG_COLOR);
}
LogConfigLevelSet(
LogConfigGlobal(),
flags & ARG_VERBOSE ?
LOG_DEBUG :
ConfigLogLevelToSyslog(tConfig.log.level));
LogConfigLevelSet(LogConfigGlobal(), flags & ARG_VERBOSE ? LOG_DEBUG : tConfig->logLevel);
if (tConfig.log.output == CONFIG_LOG_OUTPUT_FILE)
if (tConfig->flags & CONFIG_LOG_FILE)
{
logFile = StreamOpen("telodendria.log", "a");
@ -302,18 +306,18 @@ start:
{
Log(LOG_ERR, "Unable to open log file for appending.");
exit = EXIT_FAILURE;
tConfig.log.output = CONFIG_LOG_OUTPUT_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.log.output == CONFIG_LOG_OUTPUT_STDOUT)
else if (tConfig->flags & CONFIG_LOG_STDOUT)
{
Log(LOG_DEBUG, "Already logging to standard output.");
}
else if (tConfig.log.output == CONFIG_LOG_OUTPUT_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);
@ -323,6 +327,13 @@ 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 */
@ -332,30 +343,14 @@ start:
Free(token);
}
if (tConfig.pid)
{
pidFile = StreamOpen(tConfig.pid, "w+");
if (!pidFile)
{
char *msg = "Couldn't lock PID file at '%s'";
Log(LOG_ERR, msg, tConfig.pid);
exit = EXIT_FAILURE;
goto finish;
}
pidPath = StrDuplicate(tConfig.pid);
StreamPrintf(pidFile, "%ld", (long) getpid());
StreamClose(pidFile);
}
Log(LOG_DEBUG, "Configuration:");
LogConfigIndent(LogConfigGlobal());
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.runAs.uid, tConfig.runAs.gid);
Log(LOG_DEBUG, "Max Cache: %ld", tConfig.maxCache);
Log(LOG_DEBUG, "Registration: %s", tConfig.registration ? "true" : "false");
Log(LOG_DEBUG, "Federation: %s", tConfig.federation ? "true" : "false");
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, "Max Cache: %ld", tConfig->maxCache);
Log(LOG_DEBUG, "Flags: %x", tConfig->flags);
LogConfigUnindent(LogConfigGlobal());
httpServers = ArrayCreate();
@ -367,51 +362,41 @@ start:
}
/* Bind servers before possibly dropping permissions. */
for (i = 0; i < ArraySize(tConfig.listen); i++)
for (i = 0; i < ArraySize(tConfig->servers); i++)
{
ConfigListener *serverCfg = ArrayGet(tConfig.listen, i);
HttpServerConfig args;
args.port = serverCfg->port;
args.threads = serverCfg->maxConnections;
args.maxConnections = serverCfg->maxConnections;
args.tlsCert = serverCfg->tls.cert;
args.tlsKey = serverCfg->tls.key;
args.flags = args.tlsCert && args.tlsKey ? HTTP_FLAG_TLS : HTTP_FLAG_NONE;
HttpServerConfig *serverCfg = ArrayGet(tConfig->servers, i);
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", args.flags);
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tls.cert);
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tls.key);
Log(LOG_DEBUG, "Flags: %d", serverCfg->flags);
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tlsCert);
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tlsKey);
LogConfigUnindent(LogConfigGlobal());
serverCfg->handler = MatrixHttpHandler;
serverCfg->handlerArgs = &matrixArgs;
args.handler = MatrixHttpHandler;
args.handlerArgs = &matrixArgs;
if (args.flags & HTTP_FLAG_TLS)
if (serverCfg->flags & HTTP_FLAG_TLS)
{
if (UInt64Eq(UtilLastModified(serverCfg->tls.cert), UInt64Create(0, 0)))
if (UInt64Eq(UtilLastModified(serverCfg->tlsCert), UInt64Create(0, 0)))
{
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.cert);
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tlsCert);
exit = EXIT_FAILURE;
goto finish;
}
if (UInt64Eq(UtilLastModified(serverCfg->tls.key), UInt64Create(0, 0)))
if (UInt64Eq(UtilLastModified(serverCfg->tlsKey), UInt64Create(0, 0)))
{
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.key);
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tlsKey);
exit = EXIT_FAILURE;
goto finish;
}
}
server = HttpServerCreate(&args);
server = HttpServerCreate(serverCfg);
if (!server)
{
Log(LOG_ERR, "Unable to create HTTP server on port %d: %s",
@ -432,10 +417,10 @@ start:
Log(LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid());
if (tConfig.runAs.uid && tConfig.runAs.gid)
if (tConfig->uid && tConfig->gid)
{
userInfo = getpwnam(tConfig.runAs.uid);
groupInfo = getgrnam(tConfig.runAs.gid);
userInfo = getpwnam(tConfig->uid);
groupInfo = getgrnam(tConfig->gid);
if (!userInfo || !groupInfo)
{
@ -465,7 +450,7 @@ start:
}
else
{
Log(LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig.runAs.uid, tConfig.runAs.gid);
Log(LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig->uid, tConfig->gid);
}
}
else
@ -477,7 +462,7 @@ start:
}
else
{
if (tConfig.runAs.uid && tConfig.runAs.gid)
if (tConfig->uid && tConfig->gid)
{
if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid)
{
@ -490,16 +475,17 @@ start:
}
}
if (!tConfig.maxCache)
if (!tConfig->maxCache)
{
Log(LOG_WARNING, "Database caching is disabled.");
Log(LOG_WARNING, "If this is not what you intended, check the config file");
Log(LOG_WARNING, "and ensure that maxCache is a valid number of bytes.");
}
DbMaxCacheSet(matrixArgs.db, tConfig.maxCache);
DbMaxCacheSet(matrixArgs.db, tConfig->maxCache);
ConfigUnlock(&tConfig);
ConfigUnlock(tConfig);
tConfig = NULL;
cron = CronCreate(60 * 1000); /* 1-minute tick */
if (!cron)
@ -606,7 +592,7 @@ finish:
Log(LOG_DEBUG, "Stopped and freed job scheduler.");
}
ConfigUnlock(&tConfig);
ConfigUnlock(tConfig);
Log(LOG_DEBUG, "Unlocked configuration.");
DbClose(matrixArgs.db);
@ -615,12 +601,6 @@ finish:
HttpRouterFree(matrixArgs.router);
Log(LOG_DEBUG, "Freed routing tree.");
if (pidPath)
{
remove(pidPath);
Free(pidPath);
}
/*
* Uninstall the memory hook because it uses the Log
* API, whose configuration is being freed now, so it

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -68,14 +67,13 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
char *msg;
Config config;
Config *config = ConfigLock(db);
ConfigLock(db, &config);
if (!config.ok)
if (!config)
{
Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "Password endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, config.err);
return MatrixErrorCreate(M_UNKNOWN, NULL);
}
(void) path;
@ -158,7 +156,7 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
response = HashMapCreate();
finish:
ConfigUnlock(&config);
ConfigUnlock(config);
UserUnlock(user);
JsonFree(request);
return response;

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -37,10 +36,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
char *msg;
User *user = NULL;
Config config;
Config *config = NULL;
HashMap *request = NULL;
Config newConf;
Config *newConf;
HashMap *newJson = NULL;
(void) path;
@ -67,19 +66,20 @@ ROUTE_IMPL(RouteConfig, path, argp)
goto finish;
}
ConfigLock(args->matrixArgs->db, &config);
if (!config.ok)
config = ConfigLock(args->matrixArgs->db);
if (!config)
{
Log(LOG_ERR, "%s", config.err);
msg = "Internal server error while locking configuration.";
Log(LOG_ERR, "Config endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, config.err);
response = MatrixErrorCreate(M_UNKNOWN, msg);
goto finish;
}
switch (HttpRequestMethodGet(args->context))
{
case HTTP_GET:
response = JsonDuplicate(DbJson(config.ref));
response = JsonDuplicate(DbJson(config->ref));
break;
case HTTP_POST:
request = JsonDecode(HttpServerStream(args->context));
@ -90,10 +90,18 @@ ROUTE_IMPL(RouteConfig, path, argp)
break;
}
ConfigParse(request, &newConf);
if (newConf.ok)
newConf = ConfigParse(request);
if (!newConf)
{
if (DbJsonSet(config.ref, request))
msg = "Internal server error while parsing config.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, msg);
break;
}
if (newConf->ok)
{
if (DbJsonSet(config->ref, request))
{
response = HashMapCreate();
/*
@ -112,10 +120,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
else
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, newConf.err);
response = MatrixErrorCreate(M_BAD_JSON, newConf->err);
}
ConfigFree(&newConf);
ConfigFree(newConf);
JsonFree(request);
break;
case HTTP_PUT:
@ -127,14 +135,22 @@ ROUTE_IMPL(RouteConfig, path, argp)
break;
}
newJson = JsonDuplicate(DbJson(config.ref));
newJson = JsonDuplicate(DbJson(config->ref));
JsonMerge(newJson, request);
ConfigParse(newJson, &newConf);
newConf = ConfigParse(newJson);
if (newConf.ok)
if (!newConf)
{
if (DbJsonSet(config.ref, newJson))
msg = "Internal server error while parsing config.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, msg);
break;
}
if (newConf->ok)
{
if (DbJsonSet(config->ref, newJson))
{
response = HashMapCreate();
/*
@ -153,10 +169,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
else
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, newConf.err);
response = MatrixErrorCreate(M_BAD_JSON, newConf->err);
}
ConfigFree(&newConf);
ConfigFree(newConf);
JsonFree(request);
JsonFree(newJson);
break;
@ -169,6 +185,6 @@ ROUTE_IMPL(RouteConfig, path, argp)
finish:
UserUnlock(user);
ConfigUnlock(&config);
ConfigUnlock(config);
return response;
}

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -46,19 +45,17 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
Db *db = args->matrixArgs->db;
User *user = NULL;
Config config;
Config *config = ConfigLock(db);
char *msg;
(void) path;
ConfigLock(db, &config);
if (!config.ok)
if (!config)
{
Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "Deactivate endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, config.err);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
goto finish;
}
@ -151,6 +148,6 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
finish:
JsonFree(request);
UserUnlock(user);
ConfigUnlock(&config);
ConfigUnlock(config);
return response;
}

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -40,17 +39,16 @@ GetServerName(Db * db)
{
char *name;
Config config;
Config *config = ConfigLock(db);
ConfigLock(db, &config);
if (!config.ok)
if (!config)
{
return NULL;
}
name = StrDuplicate(config.serverName);
name = StrDuplicate(config->serverName);
ConfigUnlock(&config);
ConfigUnlock(config);
return name;
}

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -65,14 +64,13 @@ ROUTE_IMPL(RouteLogin, path, argp)
char *msg;
Config config;
Config *config = ConfigLock(db);
ConfigLock(db, &config);
if (!config.ok)
if (!config)
{
Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "Login endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, config.err);
return MatrixErrorCreate(M_UNKNOWN, NULL);
}
(void) path;
@ -151,7 +149,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
}
userId = UserIdParse(userIdentifier.user, config.serverName);
userId = UserIdParse(userIdentifier.user, config->serverName);
if (!userId)
{
msg = "Invalid user ID.";
@ -160,7 +158,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
break;
}
if (!StrEquals(userId->server, config.serverName)
if (!StrEquals(userId->server, config->serverName)
|| !UserExists(db, userId->localpart))
{
msg = "Unknown user ID.";
@ -223,13 +221,13 @@ ROUTE_IMPL(RouteLogin, path, argp)
}
fullUsername = StrConcat(4, "@", UserGetName(user), ":",
config.serverName);
config->serverName);
HashMapSet(response, "user_id", JsonValueString(fullUsername));
Free(fullUsername);
HashMapSet(response, "well_known",
JsonValueObject(
MatrixClientWellKnown(config.baseUrl, config.identityServer)));
MatrixClientWellKnown(config->baseUrl, config->identityServer)));
UserAccessTokenFree(loginInfo->accessToken);
Free(loginInfo->refreshToken);
@ -247,7 +245,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
UserIdFree(userId);
JsonFree(request);
ConfigUnlock(&config);
ConfigUnlock(config);
LoginRequestFree(&loginRequest);
LoginRequestUserIdentifierFree(&userIdentifier);

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -73,7 +72,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
char *session;
DbRef *sessionRef;
Config config;
Config *config = ConfigLock(db);
regReq.username = NULL;
regReq.password = NULL;
@ -82,12 +81,15 @@ ROUTE_IMPL(RouteRegister, path, argp)
regReq.refresh_token = 0;
regReq.inhibit_login = 0;
ConfigLock(db, &config);
if (!config.ok)
if (!config)
{
Log(LOG_ERR, "%s", config.err);
msg = "Internal server error while locking configuration.";
Log(LOG_ERR, "Registration endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, config.err);
return MatrixErrorCreate(M_UNKNOWN, msg);
}
if (ArraySize(path) == 0)
@ -115,7 +117,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
if (regReq.username)
{
if (!UserValidate(regReq.username, config.serverName))
if (!UserValidate(regReq.username, config->serverName))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
@ -133,7 +135,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
uiaFlows = ArrayCreate();
ArrayAdd(uiaFlows, RouteRegisterRegFlow());
if (config.registration)
if (config->flags & CONFIG_REGISTRATION)
{
ArrayAdd(uiaFlows, UiaDummyFlow());
}
@ -180,7 +182,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
response = HashMapCreate();
fullUsername = StrConcat(4,
"@", UserGetName(user), ":", config.serverName);
"@", UserGetName(user), ":", config->serverName);
HashMapSet(response, "user_id", JsonValueString(fullUsername));
Free(fullUsername);
@ -257,7 +259,7 @@ finish:
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, msg);
}
else if (!UserValidate(username, config.serverName))
else if (!UserValidate(username, config->serverName))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
@ -281,6 +283,6 @@ finish:
}
end:
ConfigUnlock(&config);
ConfigUnlock(config);
return response;
}

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -52,22 +51,23 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
HashMap *request;
HashMap *response;
int uiaResult;
Config config;
Config *config;
Array *flows;
Array *flow;
ConfigLock(args->matrixArgs->db, &config);
if (!config.ok)
config = ConfigLock(args->matrixArgs->db);
if (!config)
{
Log(LOG_ERR, "%s", config.err);
msg = "Internal server error: failed to lock configuration.";
Log(LOG_ERR, "UIA fallback failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, config.err);
return MatrixErrorCreate(M_UNKNOWN, msg);
}
request = JsonDecode(HttpServerStream(args->context));
if (!request)
{
ConfigUnlock(&config);
ConfigUnlock(config);
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_NOT_JSON, NULL);
}
@ -91,7 +91,7 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
}
JsonFree(request);
ConfigUnlock(&config);
ConfigUnlock(config);
return response;
}
else if (HttpRequestMethodGet(args->context) != HTTP_GET)

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -51,18 +50,16 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
char *msg;
Config config;
Config *config = ConfigLock(db);
ConfigLock(db, &config);
if (!config.ok)
if (!config)
{
Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "User profile endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, config.err);
return MatrixErrorCreate(M_UNKNOWN, NULL);
}
serverName = config.serverName;
serverName = config->serverName;
username = ArrayGet(path, 0);
userId = UserIdParse(username, serverName);
@ -183,9 +180,11 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
break;
}
finish:
ConfigUnlock(&config);
ConfigUnlock(config);
/* Username is handled by the router, freeing it would cause issues. */
/* Username is handled by the router, freeing it *will* cause issues
* (see #33). I honestly don't know how it didn't come to bite us sooner.
Free(username); */
Free(entry);
UserIdFree(userId);
UserUnlock(user);

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -36,19 +35,21 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
RouteArgs *args = argp;
HashMap *response;
Config config;
Config *config = ConfigLock(args->matrixArgs->db);
ConfigLock(args->matrixArgs->db, &config);
if (!config.ok)
char *msg;
if (!config)
{
Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "Well-known endpoint failed to lock configuration.");
msg = "Internal server error: couldn't lock database.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, config.err);
return MatrixErrorCreate(M_UNKNOWN, msg);
}
if (StrEquals(ArrayGet(path, 0), "client"))
{
response = MatrixClientWellKnown(config.baseUrl, config.identityServer);
response = MatrixClientWellKnown(config->baseUrl, config->identityServer);
}
else
{
@ -56,6 +57,6 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
}
ConfigUnlock(&config);
ConfigUnlock(config);
return response;
}

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -45,15 +44,14 @@ ROUTE_IMPL(RouteWhoami, path, argp)
char *deviceID;
char *msg;
Config config;
Config *config = ConfigLock(db);
ConfigLock(db, &config);
if (!config.ok)
if (!config)
{
Log(LOG_ERR, "%s", config.err);
msg = "Internal server error: couldn't lock database.";
Log(LOG_ERR, "Who am I endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, config.err);
return MatrixErrorCreate(M_UNKNOWN, msg);
}
(void) path;
@ -77,7 +75,7 @@ ROUTE_IMPL(RouteWhoami, path, argp)
response = HashMapCreate();
userID = StrConcat(4, "@", UserGetName(user), ":", config.serverName);
userID = StrConcat(4, "@", UserGetName(user), ":", config->serverName);
deviceID = StrDuplicate(UserGetDeviceId(user));
UserUnlock(user);
@ -89,6 +87,6 @@ ROUTE_IMPL(RouteWhoami, path, argp)
Free(deviceID);
finish:
ConfigUnlock(&config);
ConfigUnlock(config);
return response;
}

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -136,7 +135,7 @@ TelodendriaPrintHeader(void)
Log(LOG_INFO, "Telodendria v" TELODENDRIA_VERSION " (%s v%s)", CytoplasmGetName(), CytoplasmGetVersion());
Log(LOG_INFO, "");
Log(LOG_INFO,
"Copyright (C) 2024 Jordan Bancino <@jordan:bancino.net>");
"Copyright (C) 2023 Jordan Bancino <@jordan:bancino.net>");
Log(LOG_INFO,
"Documentation/Support: https://telodendria.io");
Log(LOG_INFO, "");

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -206,7 +205,7 @@ UiaStageBuild(char *type, HashMap * params)
int
UiaComplete(Array * flows, HttpServerContext * context, Db * db,
HashMap * request, HashMap ** response, Config config)
HashMap * request, HashMap ** response, Config * config)
{
JsonValue *val;
HashMap *auth;
@ -363,10 +362,10 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
type = JsonValueAsString(HashMapGet(identifier, "type"));
userId = UserIdParse(JsonValueAsString(HashMapGet(identifier, "user")),
config.serverName);
config->serverName);
if (!type || !StrEquals(type, "m.id.user")
|| !userId || !StrEquals(userId->server, config.serverName))
|| !userId || !StrEquals(userId->server, config->serverName))
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
ret = BuildResponse(flows, db, response, session, dbRef);

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -49,12 +48,68 @@
* .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
@ -65,7 +120,14 @@
* set the ok flag to 0. The caller should always check the ok flag,
* and if there is an error, it should display the error to the user.
*/
extern void ConfigParse(HashMap *, Config *);
extern Config * ConfigParse(HashMap *);
/**
* Free all the values inside of the given configuration structure,
* as well as the structure itself, such that it is completely invalid
* when this function returns.
*/
extern void ConfigFree(Config *);
/**
* Check whether or not the configuration exists in the database,
@ -88,7 +150,7 @@ extern int ConfigCreateDefault(Db *);
* The return value of this function is the same as
* .Fn ConfigParse .
*/
extern void ConfigLock(Db *, Config *);
extern Config * ConfigLock(Db *);
/**
* Unlock the specified configuration, returning it back to the
@ -98,9 +160,4 @@ extern void ConfigLock(Db *, Config *);
*/
extern int ConfigUnlock(Config *);
/**
* Converts a ConfigLogLevel into a valid syslog level.
*/
extern int ConfigLogLevelToSyslog(ConfigLogLevel);
#endif /* TELODENDRIA_CONFIG_H */

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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
@ -133,7 +132,7 @@ extern void UiaCleanup(MatrixHttpHandlerArgs *);
* the caller proceed with its logic.
*/
extern int
UiaComplete(Array *, HttpServerContext *, Db *, HashMap *, HashMap **, Config);
UiaComplete(Array *, HttpServerContext *, Db *, HashMap *, HashMap **, Config *);
/**
* Free an array of flows, as described above. Even though the caller

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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

View file

@ -1,6 +1,5 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
* Copyright (C) 2022-2024 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