Use j2s for the Config API. (#49)

Closes #7.
Closes #46.
Closes #47.

This pull request makes some minor on top of #46.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-authored-by: lda <lda@freetards.xyz>
Co-authored-by: lda <lda@noreply.git.telodendria.io>
Reviewed-on: Telodendria/Telodendria#49
This commit is contained in:
Jordan Bancino 2024-01-05 21:00:27 -05:00
parent 22d0e88dde
commit 83eb69f2cd
18 changed files with 383 additions and 628 deletions

88
Schema/Config.json Normal file
View file

@ -0,0 +1,88 @@
{
"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

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

View file

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

View file

@ -22,405 +22,89 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
#include <Config.h> #include <Schema/Config.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Cytoplasm/HashMap.h> #include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Array.h> #include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Db.h> #include <Cytoplasm/Db.h>
#include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <Cytoplasm/Int64.h> #include <Cytoplasm/Int64.h>
#include <Cytoplasm/Util.h>
#include <sys/types.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <string.h> #include <string.h>
#include <limits.h> #include <limits.h>
#include <grp.h>
#include <pwd.h>
#ifndef HOST_NAME_MAX #ifndef HOST_NAME_MAX
#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX #define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
#endif #endif
#define CONFIG_REQUIRE(key, type) \ void
value = HashMapGet(config, key); \ ConfigParse(HashMap * config, Config *tConfig)
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; 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) if (!config)
{ {
return NULL; tConfig->ok = 0;
} tConfig->err = "Invalid object given as config.";
return;
tConfig = Malloc(sizeof(Config));
if (!tConfig)
{
return NULL;
} }
memset(tConfig, 0, sizeof(Config)); memset(tConfig, 0, sizeof(Config));
CONFIG_REQUIRE("listen", JSON_ARRAY); tConfig->maxCache = Int64Create(0, 0);
if (!ConfigParseListen(tConfig, JsonValueAsArray(value)))
if (!ConfigFromJson(config, tConfig, &tConfig->err))
{ {
ConfigFree(tConfig);
goto error; 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; size_t len = strlen(tConfig->serverName) + 10;
tConfig->baseUrl = Malloc(len); tConfig->baseUrl = Malloc(len);
if (!tConfig->baseUrl) if (!tConfig->baseUrl)
{ {
tConfig->err = "Error allocating memory for default config value 'baseUrl'."; tConfig->err = "Couldn't allocate enough memory for 'baseUrl'.";
goto error; 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)
{ {
if (JsonValueType(value) == JSON_OBJECT) tConfig->log.timestampFormat = StrDuplicate("default");
}
for (i = 0; i < ArraySize(tConfig->listen); i++)
{
ConfigListener *listener = ArrayGet(tConfig->listen, i);
if (Int64Eq(listener->maxConnections, Int64Create(0, 0)))
{ {
if (!ConfigParseRunAs(tConfig, JsonValueAsObject(value))) listener->maxConnections = Int64Create(0, 32);
{
goto error;
}
} }
else if (Int64Eq(listener->threads, Int64Create(0, 0)))
{ {
tConfig->err = "Config directive 'runAs' should be a JSON object that contains a 'uid' and 'gid'."; listener->threads = Int64Create(0, 4);
goto error; }
if (Int64Eq(listener->port, Int64Create(0, 0)))
{
listener->port = Int64Create(0, 8008);
} }
} }
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;
}
tConfig->ok = 1; tConfig->ok = 1;
tConfig->err = NULL; tConfig->err = NULL;
return tConfig; return;
error: error:
tConfig->ok = 0; tConfig->ok = 0;
return tConfig; return;
} }
int int
@ -432,75 +116,91 @@ ConfigExists(Db * db)
int int
ConfigCreateDefault(Db * db) ConfigCreateDefault(Db * db)
{ {
DbRef *ref; Config config;
ConfigListener *listener;
HashMap *json; HashMap *json;
Array *listeners; JsonValue *val;
HashMap *listen;
char hostname[HOST_NAME_MAX + 1]; DbRef *ref;
if (!db) size_t len;
{
return 0; 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"));
ref = DbCreate(db, 1, "config"); ref = DbCreate(db, 1, "config");
if (!ref) if (!ref)
{ {
ConfigFree(&config);
return 0; return 0;
} }
DbJsonSet(ref, json);
DbUnlock(db, ref);
json = DbJson(ref); ConfigFree(&config);
JsonFree(json);
JsonSet(json, JsonValueString("file"), 2, "log", "output"); return 1;
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 * void
ConfigLock(Db * db) ConfigLock(Db * db, Config *config)
{ {
Config *config;
DbRef *ref = DbLock(db, 1, "config"); DbRef *ref = DbLock(db, 1, "config");
if (!ref) if (!ref)
{ {
return NULL; config->ok = 0;
config->err = "Couldn't lock configuration.";
} }
config = ConfigParse(DbJson(ref)); ConfigParse(DbJson(ref), config);
if (config) if (config->ok)
{ {
config->db = db; config->db = db;
config->ref = ref; config->ref = ref;
} }
return config;
} }
int int
ConfigUnlock(Config * config) ConfigUnlock(Config *config)
{ {
Db *db; Db *db;
DbRef *dbRef; DbRef *dbRef;
if (!config) if (!config->ok)
{ {
return 0; return 0;
} }
@ -509,5 +209,25 @@ ConfigUnlock(Config * config)
dbRef = config->ref; dbRef = config->ref;
ConfigFree(config); ConfigFree(config);
config->ok = 0;
return DbUnlock(db, dbRef); 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

@ -103,8 +103,11 @@ Main(Array * args)
char *dbPath; char *dbPath;
/* Program configuration */ /* Program configuration */
Config *tConfig; Config tConfig;
Stream *logFile; Stream *logFile;
Stream *pidFile = NULL;
char *pidPath = NULL;
/* User validation */ /* User validation */
struct passwd *userInfo; struct passwd *userInfo;
@ -133,7 +136,6 @@ start:
exit = EXIT_SUCCESS; exit = EXIT_SUCCESS;
flags = 0; flags = 0;
dbPath = NULL; dbPath = NULL;
tConfig = NULL;
logFile = NULL; logFile = NULL;
userInfo = NULL; userInfo = NULL;
groupInfo = NULL; groupInfo = NULL;
@ -262,33 +264,20 @@ start:
Log(LOG_NOTICE, "Loading configuration..."); Log(LOG_NOTICE, "Loading configuration...");
tConfig = ConfigLock(matrixArgs.db); ConfigLock(matrixArgs.db, &tConfig);
if (!tConfig) if (!tConfig.ok)
{ {
Log(LOG_ERR, "Error locking the configuration."); Log(LOG_ERR, tConfig.err);
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; exit = EXIT_FAILURE;
goto finish; 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;
} }
if (tConfig->flags & CONFIG_LOG_COLOR) if (tConfig.log.color)
{ {
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR); LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR);
} }
@ -297,9 +286,13 @@ start:
LogConfigFlagClear(LogConfigGlobal(), LOG_FLAG_COLOR); LogConfigFlagClear(LogConfigGlobal(), LOG_FLAG_COLOR);
} }
LogConfigLevelSet(LogConfigGlobal(), flags & ARG_VERBOSE ? LOG_DEBUG : tConfig->logLevel); LogConfigLevelSet(
LogConfigGlobal(),
flags & ARG_VERBOSE ?
LOG_DEBUG :
ConfigLogLevelToSyslog(tConfig.log.level));
if (tConfig->flags & CONFIG_LOG_FILE) if (tConfig.log.output == CONFIG_LOG_OUTPUT_FILE)
{ {
logFile = StreamOpen("telodendria.log", "a"); logFile = StreamOpen("telodendria.log", "a");
@ -307,18 +300,18 @@ start:
{ {
Log(LOG_ERR, "Unable to open log file for appending."); Log(LOG_ERR, "Unable to open log file for appending.");
exit = EXIT_FAILURE; exit = EXIT_FAILURE;
tConfig->flags &= CONFIG_LOG_STDOUT; tConfig.log.output = CONFIG_LOG_OUTPUT_STDOUT;
goto finish; goto finish;
} }
Log(LOG_INFO, "Logging to the log file. Check there for all future messages."); Log(LOG_INFO, "Logging to the log file. Check there for all future messages.");
LogConfigOutputSet(LogConfigGlobal(), logFile); LogConfigOutputSet(LogConfigGlobal(), logFile);
} }
else if (tConfig->flags & CONFIG_LOG_STDOUT) else if (tConfig.log.output == CONFIG_LOG_OUTPUT_STDOUT)
{ {
Log(LOG_DEBUG, "Already logging to standard output."); 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."); Log(LOG_INFO, "Logging to the syslog. Check there for all future messages.");
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_SYSLOG); LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_SYSLOG);
@ -328,13 +321,6 @@ start:
* messages get passed to the syslog */ * messages get passed to the syslog */
setlogmask(LOG_UPTO(LOG_DEBUG)); 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 /* If a token was created with a default config, print it to the
* log */ * log */
@ -344,14 +330,30 @@ start:
Free(token); 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:"); Log(LOG_DEBUG, "Configuration:");
LogConfigIndent(LogConfigGlobal()); LogConfigIndent(LogConfigGlobal());
Log(LOG_DEBUG, "Server Name: %s", tConfig->serverName); Log(LOG_DEBUG, "Server Name: %s", tConfig.serverName);
Log(LOG_DEBUG, "Base URL: %s", tConfig->baseUrl); Log(LOG_DEBUG, "Base URL: %s", tConfig.baseUrl);
Log(LOG_DEBUG, "Identity Server: %s", tConfig->identityServer); 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, "Max Cache: %ld", tConfig.maxCache);
Log(LOG_DEBUG, "Flags: %x", tConfig->flags); Log(LOG_DEBUG, "Registration: %s", tConfig.registration ? "true" : "false");
Log(LOG_DEBUG, "Federation: %s", tConfig.federation ? "true" : "false");
LogConfigUnindent(LogConfigGlobal()); LogConfigUnindent(LogConfigGlobal());
httpServers = ArrayCreate(); httpServers = ArrayCreate();
@ -363,41 +365,51 @@ start:
} }
/* Bind servers before possibly dropping permissions. */ /* 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);
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;
Log(LOG_DEBUG, "HTTP listener: %lu", i); Log(LOG_DEBUG, "HTTP listener: %lu", i);
LogConfigIndent(LogConfigGlobal()); LogConfigIndent(LogConfigGlobal());
Log(LOG_DEBUG, "Port: %hu", serverCfg->port); Log(LOG_DEBUG, "Port: %hu", serverCfg->port);
Log(LOG_DEBUG, "Threads: %u", serverCfg->threads); Log(LOG_DEBUG, "Threads: %u", serverCfg->threads);
Log(LOG_DEBUG, "Max Connections: %u", serverCfg->maxConnections); Log(LOG_DEBUG, "Max Connections: %u", serverCfg->maxConnections);
Log(LOG_DEBUG, "Flags: %d", serverCfg->flags); Log(LOG_DEBUG, "Flags: %d", args.flags);
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tlsCert); Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tls.cert);
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tlsKey); Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tls.key);
LogConfigUnindent(LogConfigGlobal()); LogConfigUnindent(LogConfigGlobal());
serverCfg->handler = MatrixHttpHandler;
serverCfg->handlerArgs = &matrixArgs;
if (serverCfg->flags & HTTP_FLAG_TLS) args.handler = MatrixHttpHandler;
args.handlerArgs = &matrixArgs;
if (args.flags & HTTP_FLAG_TLS)
{ {
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; exit = EXIT_FAILURE;
goto finish; 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; exit = EXIT_FAILURE;
goto finish; goto finish;
} }
} }
server = HttpServerCreate(serverCfg); server = HttpServerCreate(&args);
if (!server) if (!server)
{ {
Log(LOG_ERR, "Unable to create HTTP server on port %d: %s", Log(LOG_ERR, "Unable to create HTTP server on port %d: %s",
@ -418,10 +430,10 @@ start:
Log(LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid()); 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); userInfo = getpwnam(tConfig.runAs.uid);
groupInfo = getgrnam(tConfig->gid); groupInfo = getgrnam(tConfig.runAs.gid);
if (!userInfo || !groupInfo) if (!userInfo || !groupInfo)
{ {
@ -451,7 +463,7 @@ start:
} }
else 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 else
@ -463,7 +475,7 @@ start:
} }
else else
{ {
if (tConfig->uid && tConfig->gid) if (tConfig.runAs.uid && tConfig.runAs.gid)
{ {
if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid) if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid)
{ {
@ -476,17 +488,16 @@ start:
} }
} }
if (!tConfig->maxCache) if (!tConfig.maxCache)
{ {
Log(LOG_WARNING, "Database caching is disabled."); Log(LOG_WARNING, "Database caching is disabled.");
Log(LOG_WARNING, "If this is not what you intended, check the config file"); 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."); 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 */ cron = CronCreate(60 * 1000); /* 1-minute tick */
if (!cron) if (!cron)
@ -593,7 +604,7 @@ finish:
Log(LOG_DEBUG, "Stopped and freed job scheduler."); Log(LOG_DEBUG, "Stopped and freed job scheduler.");
} }
ConfigUnlock(tConfig); ConfigUnlock(&tConfig);
Log(LOG_DEBUG, "Unlocked configuration."); Log(LOG_DEBUG, "Unlocked configuration.");
DbClose(matrixArgs.db); DbClose(matrixArgs.db);
@ -602,6 +613,12 @@ finish:
HttpRouterFree(matrixArgs.router); HttpRouterFree(matrixArgs.router);
Log(LOG_DEBUG, "Freed routing tree."); Log(LOG_DEBUG, "Freed routing tree.");
if (pidPath)
{
remove(pidPath);
Free(pidPath);
}
/* /*
* Uninstall the memory hook because it uses the Log * Uninstall the memory hook because it uses the Log
* API, whose configuration is being freed now, so it * API, whose configuration is being freed now, so it

View file

@ -68,13 +68,14 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
char *msg; char *msg;
Config *config = ConfigLock(db); Config config;
if (!config) ConfigLock(db, &config);
if (!config.ok)
{ {
Log(LOG_ERR, "Password endpoint failed to lock configuration."); Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL); return MatrixErrorCreate(M_UNKNOWN, config.err);
} }
(void) path; (void) path;
@ -157,7 +158,7 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
response = HashMapCreate(); response = HashMapCreate();
finish: finish:
ConfigUnlock(config); ConfigUnlock(&config);
UserUnlock(user); UserUnlock(user);
JsonFree(request); JsonFree(request);
return response; return response;

View file

@ -37,10 +37,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
char *msg; char *msg;
User *user = NULL; User *user = NULL;
Config *config = NULL; Config config;
HashMap *request = NULL; HashMap *request = NULL;
Config *newConf; Config newConf;
HashMap *newJson = NULL; HashMap *newJson = NULL;
(void) path; (void) path;
@ -67,20 +67,19 @@ ROUTE_IMPL(RouteConfig, path, argp)
goto finish; goto finish;
} }
config = ConfigLock(args->matrixArgs->db); ConfigLock(args->matrixArgs->db, &config);
if (!config) if (!config.ok)
{ {
msg = "Internal server error while locking configuration."; Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "Config endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, msg); response = MatrixErrorCreate(M_UNKNOWN, config.err);
goto finish; goto finish;
} }
switch (HttpRequestMethodGet(args->context)) switch (HttpRequestMethodGet(args->context))
{ {
case HTTP_GET: case HTTP_GET:
response = JsonDuplicate(DbJson(config->ref)); response = JsonDuplicate(DbJson(config.ref));
break; break;
case HTTP_POST: case HTTP_POST:
request = JsonDecode(HttpServerStream(args->context)); request = JsonDecode(HttpServerStream(args->context));
@ -91,18 +90,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
break; break;
} }
newConf = ConfigParse(request); ConfigParse(request, &newConf);
if (!newConf) if (newConf.ok)
{ {
msg = "Internal server error while parsing config."; if (DbJsonSet(config.ref, request))
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, msg);
break;
}
if (newConf->ok)
{
if (DbJsonSet(config->ref, request))
{ {
response = HashMapCreate(); response = HashMapCreate();
/* /*
@ -121,10 +112,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
else else
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); 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(request);
break; break;
case HTTP_PUT: case HTTP_PUT:
@ -136,22 +127,14 @@ ROUTE_IMPL(RouteConfig, path, argp)
break; break;
} }
newJson = JsonDuplicate(DbJson(config->ref)); newJson = JsonDuplicate(DbJson(config.ref));
JsonMerge(newJson, request); JsonMerge(newJson, request);
newConf = ConfigParse(newJson); ConfigParse(newJson, &newConf);
if (!newConf) if (newConf.ok)
{ {
msg = "Internal server error while parsing config."; if (DbJsonSet(config.ref, newJson))
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, msg);
break;
}
if (newConf->ok)
{
if (DbJsonSet(config->ref, newJson))
{ {
response = HashMapCreate(); response = HashMapCreate();
/* /*
@ -170,10 +153,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
else else
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); 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(request);
JsonFree(newJson); JsonFree(newJson);
break; break;
@ -186,6 +169,6 @@ ROUTE_IMPL(RouteConfig, path, argp)
finish: finish:
UserUnlock(user); UserUnlock(user);
ConfigUnlock(config); ConfigUnlock(&config);
return response; return response;
} }

View file

@ -46,17 +46,19 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
Db *db = args->matrixArgs->db; Db *db = args->matrixArgs->db;
User *user = NULL; User *user = NULL;
Config *config = ConfigLock(db); Config config;
char *msg; char *msg;
(void) path; (void) path;
if (!config) ConfigLock(db, &config);
if (!config.ok)
{ {
Log(LOG_ERR, "Deactivate endpoint failed to lock configuration."); Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL); response = MatrixErrorCreate(M_UNKNOWN, config.err);
goto finish; goto finish;
} }
@ -149,6 +151,6 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
finish: finish:
JsonFree(request); JsonFree(request);
UserUnlock(user); UserUnlock(user);
ConfigUnlock(config); ConfigUnlock(&config);
return response; return response;
} }

View file

@ -40,16 +40,17 @@ GetServerName(Db * db)
{ {
char *name; char *name;
Config *config = ConfigLock(db); Config config;
if (!config) ConfigLock(db, &config);
if (!config.ok)
{ {
return NULL; return NULL;
} }
name = StrDuplicate(config->serverName); name = StrDuplicate(config.serverName);
ConfigUnlock(config); ConfigUnlock(&config);
return name; return name;
} }

View file

@ -65,13 +65,14 @@ ROUTE_IMPL(RouteLogin, path, argp)
char *msg; char *msg;
Config *config = ConfigLock(db); Config config;
if (!config) ConfigLock(db, &config);
if (!config.ok)
{ {
Log(LOG_ERR, "Login endpoint failed to lock configuration."); Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL); return MatrixErrorCreate(M_UNKNOWN, config.err);
} }
(void) path; (void) path;
@ -150,7 +151,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
} }
userId = UserIdParse(userIdentifier.user, config->serverName); userId = UserIdParse(userIdentifier.user, config.serverName);
if (!userId) if (!userId)
{ {
msg = "Invalid user ID."; msg = "Invalid user ID.";
@ -159,7 +160,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
break; break;
} }
if (!StrEquals(userId->server, config->serverName) if (!StrEquals(userId->server, config.serverName)
|| !UserExists(db, userId->localpart)) || !UserExists(db, userId->localpart))
{ {
msg = "Unknown user ID."; msg = "Unknown user ID.";
@ -222,13 +223,13 @@ ROUTE_IMPL(RouteLogin, path, argp)
} }
fullUsername = StrConcat(4, "@", UserGetName(user), ":", fullUsername = StrConcat(4, "@", UserGetName(user), ":",
config->serverName); config.serverName);
HashMapSet(response, "user_id", JsonValueString(fullUsername)); HashMapSet(response, "user_id", JsonValueString(fullUsername));
Free(fullUsername); Free(fullUsername);
HashMapSet(response, "well_known", HashMapSet(response, "well_known",
JsonValueObject( JsonValueObject(
MatrixClientWellKnown(config->baseUrl, config->identityServer))); MatrixClientWellKnown(config.baseUrl, config.identityServer)));
UserAccessTokenFree(loginInfo->accessToken); UserAccessTokenFree(loginInfo->accessToken);
Free(loginInfo->refreshToken); Free(loginInfo->refreshToken);
@ -246,7 +247,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
UserIdFree(userId); UserIdFree(userId);
JsonFree(request); JsonFree(request);
ConfigUnlock(config); ConfigUnlock(&config);
LoginRequestFree(&loginRequest); LoginRequestFree(&loginRequest);
LoginRequestUserIdentifierFree(&userIdentifier); LoginRequestUserIdentifierFree(&userIdentifier);

View file

@ -73,7 +73,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
char *session; char *session;
DbRef *sessionRef; DbRef *sessionRef;
Config *config = ConfigLock(db); Config config;
regReq.username = NULL; regReq.username = NULL;
regReq.password = NULL; regReq.password = NULL;
@ -82,15 +82,12 @@ ROUTE_IMPL(RouteRegister, path, argp)
regReq.refresh_token = 0; regReq.refresh_token = 0;
regReq.inhibit_login = 0; regReq.inhibit_login = 0;
ConfigLock(db, &config);
if (!config.ok)
if (!config)
{ {
msg = "Internal server error while locking configuration."; Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "Registration endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, msg); return MatrixErrorCreate(M_UNKNOWN, config.err);
} }
if (ArraySize(path) == 0) if (ArraySize(path) == 0)
@ -118,7 +115,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
if (regReq.username) if (regReq.username)
{ {
if (!UserValidate(regReq.username, config->serverName)) if (!UserValidate(regReq.username, config.serverName))
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL); response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
@ -136,7 +133,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
uiaFlows = ArrayCreate(); uiaFlows = ArrayCreate();
ArrayAdd(uiaFlows, RouteRegisterRegFlow()); ArrayAdd(uiaFlows, RouteRegisterRegFlow());
if (config->flags & CONFIG_REGISTRATION) if (config.registration)
{ {
ArrayAdd(uiaFlows, UiaDummyFlow()); ArrayAdd(uiaFlows, UiaDummyFlow());
} }
@ -183,7 +180,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
response = HashMapCreate(); response = HashMapCreate();
fullUsername = StrConcat(4, fullUsername = StrConcat(4,
"@", UserGetName(user), ":", config->serverName); "@", UserGetName(user), ":", config.serverName);
HashMapSet(response, "user_id", JsonValueString(fullUsername)); HashMapSet(response, "user_id", JsonValueString(fullUsername));
Free(fullUsername); Free(fullUsername);
@ -260,7 +257,7 @@ finish:
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, msg); response = MatrixErrorCreate(M_MISSING_PARAM, msg);
} }
else if (!UserValidate(username, config->serverName)) else if (!UserValidate(username, config.serverName))
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL); response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
@ -284,6 +281,6 @@ finish:
} }
end: end:
ConfigUnlock(config); ConfigUnlock(&config);
return response; return response;
} }

View file

@ -52,23 +52,22 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
HashMap *request; HashMap *request;
HashMap *response; HashMap *response;
int uiaResult; int uiaResult;
Config *config; Config config;
Array *flows; Array *flows;
Array *flow; Array *flow;
config = ConfigLock(args->matrixArgs->db); ConfigLock(args->matrixArgs->db, &config);
if (!config) if (!config.ok)
{ {
msg = "Internal server error: failed to lock configuration."; Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "UIA fallback failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, msg); return MatrixErrorCreate(M_UNKNOWN, config.err);
} }
request = JsonDecode(HttpServerStream(args->context)); request = JsonDecode(HttpServerStream(args->context));
if (!request) if (!request)
{ {
ConfigUnlock(config); ConfigUnlock(&config);
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_NOT_JSON, NULL); return MatrixErrorCreate(M_NOT_JSON, NULL);
} }
@ -92,7 +91,7 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
} }
JsonFree(request); JsonFree(request);
ConfigUnlock(config); ConfigUnlock(&config);
return response; return response;
} }
else if (HttpRequestMethodGet(args->context) != HTTP_GET) else if (HttpRequestMethodGet(args->context) != HTTP_GET)

View file

@ -51,16 +51,18 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
char *msg; char *msg;
Config *config = ConfigLock(db); Config config;
if (!config) ConfigLock(db, &config);
if (!config.ok)
{ {
Log(LOG_ERR, "User profile endpoint failed to lock configuration."); Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL); return MatrixErrorCreate(M_UNKNOWN, config.err);
} }
serverName = config->serverName; serverName = config.serverName;
username = ArrayGet(path, 0); username = ArrayGet(path, 0);
userId = UserIdParse(username, serverName); userId = UserIdParse(username, serverName);
@ -181,11 +183,9 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
break; break;
} }
finish: finish:
ConfigUnlock(config); ConfigUnlock(&config);
/* Username is handled by the router, freeing it *will* cause issues /* Username is handled by the router, freeing it would cause issues. */
* (see #33). I honestly don't know how it didn't come to bite us sooner.
Free(username); */
Free(entry); Free(entry);
UserIdFree(userId); UserIdFree(userId);
UserUnlock(user); UserUnlock(user);

View file

@ -36,21 +36,19 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
RouteArgs *args = argp; RouteArgs *args = argp;
HashMap *response; HashMap *response;
Config *config = ConfigLock(args->matrixArgs->db); Config config;
char *msg; ConfigLock(args->matrixArgs->db, &config);
if (!config.ok)
if (!config)
{ {
Log(LOG_ERR, "Well-known endpoint failed to lock configuration."); Log(LOG_ERR, "%s", config.err);
msg = "Internal server error: couldn't lock database.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, msg); return MatrixErrorCreate(M_UNKNOWN, config.err);
} }
if (StrEquals(ArrayGet(path, 0), "client")) if (StrEquals(ArrayGet(path, 0), "client"))
{ {
response = MatrixClientWellKnown(config->baseUrl, config->identityServer); response = MatrixClientWellKnown(config.baseUrl, config.identityServer);
} }
else else
{ {
@ -58,6 +56,6 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
response = MatrixErrorCreate(M_NOT_FOUND, NULL); response = MatrixErrorCreate(M_NOT_FOUND, NULL);
} }
ConfigUnlock(config); ConfigUnlock(&config);
return response; return response;
} }

View file

@ -45,14 +45,15 @@ ROUTE_IMPL(RouteWhoami, path, argp)
char *deviceID; char *deviceID;
char *msg; char *msg;
Config *config = ConfigLock(db); Config config;
if (!config) ConfigLock(db, &config);
if (!config.ok)
{ {
msg = "Internal server error: couldn't lock database."; Log(LOG_ERR, "%s", config.err);
Log(LOG_ERR, "Who am I endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, msg); return MatrixErrorCreate(M_UNKNOWN, config.err);
} }
(void) path; (void) path;
@ -76,7 +77,7 @@ ROUTE_IMPL(RouteWhoami, path, argp)
response = HashMapCreate(); response = HashMapCreate();
userID = StrConcat(4, "@", UserGetName(user), ":", config->serverName); userID = StrConcat(4, "@", UserGetName(user), ":", config.serverName);
deviceID = StrDuplicate(UserGetDeviceId(user)); deviceID = StrDuplicate(UserGetDeviceId(user));
UserUnlock(user); UserUnlock(user);
@ -88,6 +89,6 @@ ROUTE_IMPL(RouteWhoami, path, argp)
Free(deviceID); Free(deviceID);
finish: finish:
ConfigUnlock(config); ConfigUnlock(&config);
return response; return response;
} }

View file

@ -206,7 +206,7 @@ UiaStageBuild(char *type, HashMap * params)
int int
UiaComplete(Array * flows, HttpServerContext * context, Db * db, UiaComplete(Array * flows, HttpServerContext * context, Db * db,
HashMap * request, HashMap ** response, Config * config) HashMap * request, HashMap ** response, Config config)
{ {
JsonValue *val; JsonValue *val;
HashMap *auth; HashMap *auth;
@ -363,10 +363,10 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
type = JsonValueAsString(HashMapGet(identifier, "type")); type = JsonValueAsString(HashMapGet(identifier, "type"));
userId = UserIdParse(JsonValueAsString(HashMapGet(identifier, "user")), userId = UserIdParse(JsonValueAsString(HashMapGet(identifier, "user")),
config->serverName); config.serverName);
if (!type || !StrEquals(type, "m.id.user") if (!type || !StrEquals(type, "m.id.user")
|| !userId || !StrEquals(userId->server, config->serverName)) || !userId || !StrEquals(userId->server, config.serverName))
{ {
HttpResponseStatus(context, HTTP_UNAUTHORIZED); HttpResponseStatus(context, HTTP_UNAUTHORIZED);
ret = BuildResponse(flows, db, response, session, dbRef); ret = BuildResponse(flows, db, response, session, dbRef);

View file

@ -49,68 +49,12 @@
* .Xr telodendria-config 7 . * .Xr telodendria-config 7 .
*/ */
#include <Schema/Config.h>
#include <Cytoplasm/HashMap.h> #include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Array.h> #include <Cytoplasm/Array.h>
#include <Cytoplasm/Db.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 * Parse a JSON object, extracting the necessary values, validating
* them, and adding them to the configuration structure for use by the * them, and adding them to the configuration structure for use by the
@ -121,14 +65,7 @@ typedef struct Config
* set the ok flag to 0. The caller should always check the ok flag, * 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. * and if there is an error, it should display the error to the user.
*/ */
extern Config * ConfigParse(HashMap *); extern void ConfigParse(HashMap *, Config *);
/**
* 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, * Check whether or not the configuration exists in the database,
@ -151,7 +88,7 @@ extern int ConfigCreateDefault(Db *);
* The return value of this function is the same as * The return value of this function is the same as
* .Fn ConfigParse . * .Fn ConfigParse .
*/ */
extern Config * ConfigLock(Db *); extern void ConfigLock(Db *, Config *);
/** /**
* Unlock the specified configuration, returning it back to the * Unlock the specified configuration, returning it back to the
@ -161,4 +98,9 @@ extern Config * ConfigLock(Db *);
*/ */
extern int ConfigUnlock(Config *); extern int ConfigUnlock(Config *);
/**
* Converts a ConfigLogLevel into a valid syslog level.
*/
extern int ConfigLogLevelToSyslog(ConfigLogLevel);
#endif /* TELODENDRIA_CONFIG_H */ #endif /* TELODENDRIA_CONFIG_H */

View file

@ -133,7 +133,7 @@ extern void UiaCleanup(MatrixHttpHandlerArgs *);
* the caller proceed with its logic. * the caller proceed with its logic.
*/ */
extern int 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 * Free an array of flows, as described above. Even though the caller