Move configuration to database, add process control API, fix memory leaks.

This commit is contained in:
Jordan Bancino 2023-04-19 00:33:38 +00:00
parent ff4d265dcc
commit 0cca38115a
30 changed files with 830 additions and 213 deletions

View file

@ -52,10 +52,11 @@ Milestone: v0.3.0
[~] Move configuration to database [~] Move configuration to database
[x] Token permissions [x] Token permissions
[ ] Initial configuration [x] Initial configuration
[ ] If no config, create one-time use registration token that [x] If no config, create one-time use registration token that
grants user admin privileges. grants user admin privileges.
[ ] /_telodendria/admin/config endpoint [~] /_telodendria/admin/config endpoint
[x] JsonDuplicate()
[x] Refactor TelodendriaConfig to just Config [x] Refactor TelodendriaConfig to just Config
[ ] Documentation [ ] Documentation
@ -72,9 +73,11 @@ Milestone: v0.3.0
[ ] send-patch [ ] send-patch
[ ] Log [ ] Log
[ ] TelodendriaConfig -> Config [ ] TelodendriaConfig -> Config
[ ] telodendria.conf
[ ] HashMap [ ] HashMap
[ ] HttpRouter [ ] HttpRouter
[ ] Str [ ] Str
[ ] Admin API
[~] Client-Server API [~] Client-Server API
[x] 4: Token-based user registration [x] 4: Token-based user registration
@ -91,7 +94,9 @@ Milestone: v0.3.0
[~] Deactivate [~] Deactivate
[x] Make sure UserLogin() fails if user is deactivated. [x] Make sure UserLogin() fails if user is deactivated.
[x] Change password [x] Change password
[x] Whoami [~] Whoami
[ ] Attach device id to user object
[ ] Use UserAuthenticate()
[~] 9: User Data [~] 9: User Data
[ ] 5: Capabilities negotiation [ ] 5: Capabilities negotiation
[ ] 10: Security (Rate Limiting) [ ] 10: Security (Rate Limiting)

View file

@ -5,7 +5,6 @@
"timestampFormat": "none", "timestampFormat": "none",
"level": "debug" "level": "debug"
}, },
"dataDir": "./data",
"listen": [ "listen": [
{ {
"port": 8008, "port": 8008,

View file

@ -25,31 +25,32 @@
#include <Memory.h> #include <Memory.h>
#include <Json.h> #include <Json.h>
#include <HashMap.h> #include <HashMap.h>
#include <Log.h>
#include <Array.h> #include <Array.h>
#include <Str.h> #include <Str.h>
#include <Db.h> #include <Db.h>
#include <HttpServer.h> #include <HttpServer.h>
#include <Log.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <string.h> #include <string.h>
#include <limits.h>
#define CONFIG_REQUIRE(key, type) \ #define CONFIG_REQUIRE(key, type) \
value = HashMapGet(config, key); \ value = HashMapGet(config, key); \
if (!value) \ if (!value) \
{ \ { \
Log(LOG_ERR, "Missing required " key " directive."); \ tConfig->err = "Missing required " key " directive."; \
goto error; \ goto error; \
} \ } \
if (JsonValueType(value) == JSON_NULL) \ if (JsonValueType(value) == JSON_NULL) \
{ \ { \
Log(LOG_ERR, "Missing value for " key " directive."); \ tConfig->err = "Missing value for " key " directive."; \
goto error; \ goto error; \
} \ } \
if (JsonValueType(value) != type) \ if (JsonValueType(value) != type) \
{ \ { \
Log(LOG_ERR, "Expected " key " to be of type " #type); \ tConfig->err = "Expected " key " to be of type " #type; \
goto error; \ goto error; \
} }
@ -62,14 +63,13 @@
{ \ { \
if (JsonValueType(value) != JSON_STRING) \ if (JsonValueType(value) != JSON_STRING) \
{ \ { \
Log(LOG_ERR, "Expected " key " to be of type JSON_STRING"); \ tConfig->err = "Expected " key " to be of type JSON_STRING"; \
goto error; \ goto error; \
} \ } \
into = StrDuplicate(JsonValueAsString(value)); \ into = StrDuplicate(JsonValueAsString(value)); \
} \ } \
else \ else \
{ \ { \
Log(LOG_INFO, "Using default value " #default " for " key "."); \
into = default ? StrDuplicate(default) : NULL; \ into = default ? StrDuplicate(default) : NULL; \
} }
@ -79,14 +79,13 @@
{ \ { \
if (JsonValueType(value) != JSON_INTEGER) \ if (JsonValueType(value) != JSON_INTEGER) \
{ \ { \
Log(LOG_ERR, "Expected " key " to be of type JSON_INTEGER"); \ tConfig->err = "Expected " key " to be of type JSON_INTEGER"; \
goto error; \ goto error; \
} \ } \
into = JsonValueAsInteger(value); \ into = JsonValueAsInteger(value); \
} \ } \
else \ else \
{ \ { \
Log(LOG_INFO, "Using default value " #default " for " key "."); \
into = default; \ into = default; \
} }
@ -113,8 +112,7 @@ ConfigParseListen(Config * tConfig, Array * listen)
if (!ArraySize(listen)) if (!ArraySize(listen))
{ {
Log(LOG_ERR, "Listen array cannot be empty; you must specify at least"); tConfig->err = "Listen array cannot be empty; you must specify at least one listener.";
Log(LOG_ERR, "one listener.");
goto error; goto error;
} }
@ -123,7 +121,7 @@ ConfigParseListen(Config * tConfig, Array * listen)
tConfig->servers = ArrayCreate(); tConfig->servers = ArrayCreate();
if (!tConfig->servers) if (!tConfig->servers)
{ {
Log(LOG_ERR, "Unable to allocate memory for listener configurations."); tConfig->err = "Unable to allocate memory for listener configurations.";
goto error; goto error;
} }
} }
@ -136,14 +134,13 @@ ConfigParseListen(Config * tConfig, Array * listen)
if (!serverCfg) if (!serverCfg)
{ {
Log(LOG_ERR, "Unable to allocate memory for listener configuration."); tConfig->err = "Unable to allocate memory for listener configuration.";
goto error; goto error;
} }
if (JsonValueType(val) != JSON_OBJECT) if (JsonValueType(val) != JSON_OBJECT)
{ {
Log(LOG_ERR, "Invalid value in listener array."); tConfig->err = "Invalid value in listener array. All listeners must be objects.";
Log(LOG_ERR, "All listeners must be objects.");
goto error; goto error;
} }
@ -155,22 +152,17 @@ ConfigParseListen(Config * tConfig, Array * listen)
if (!serverCfg->port) if (!serverCfg->port)
{ {
Log(LOG_WARNING, "No or invalid port specified, listener will be ignored.");
Free(serverCfg); Free(serverCfg);
continue; continue;
} }
if (!serverCfg->threads) if (!serverCfg->threads)
{ {
Log(LOG_DEBUG, "No or invalid number of threads specified for listener.");
Log(LOG_DEBUG, "Using default, which may be subject to change.");
serverCfg->threads = 4; serverCfg->threads = 4;
} }
if (!serverCfg->maxConnections) if (!serverCfg->maxConnections)
{ {
Log(LOG_DEBUG, "No or invalid number of maximum connections specified.");
Log(LOG_DEBUG, "Using default, which may be subject to change.");
serverCfg->maxConnections = 32; serverCfg->maxConnections = 32;
} }
@ -183,7 +175,7 @@ ConfigParseListen(Config * tConfig, Array * listen)
} }
else if (JsonValueType(val) != JSON_OBJECT) else if (JsonValueType(val) != JSON_OBJECT)
{ {
Log(LOG_ERR, "Invalid value for listener.tls. It must be an object."); tConfig->err = "Invalid value for listener.tls. It must be an object.";
goto error; goto error;
} }
else else
@ -196,7 +188,7 @@ ConfigParseListen(Config * tConfig, Array * listen)
if (!serverCfg->tlsCert || !serverCfg->tlsKey) if (!serverCfg->tlsCert || !serverCfg->tlsKey)
{ {
Log(LOG_ERR, "TLS cert and key must both be valid file names."); tConfig->err = "TLS cert and key must both be valid file names.";
goto error; goto error;
} }
} }
@ -231,7 +223,7 @@ ConfigParseLog(Config * tConfig, HashMap * config)
} }
else else
{ {
Log(LOG_ERR, "Invalid value for log.output: '%s'.", str); tConfig->err = "Invalid value for log.output";
goto error; goto error;
} }
@ -259,7 +251,7 @@ ConfigParseLog(Config * tConfig, HashMap * config)
} }
else else
{ {
Log(LOG_ERR, "Invalid value for log.level: '%s'.", tConfig->logLevel); tConfig->err = "Invalid value for log.level.";
goto error; goto error;
} }
@ -278,7 +270,7 @@ ConfigParseLog(Config * tConfig, HashMap * config)
{ {
if (JsonValueType(value) != JSON_BOOLEAN) if (JsonValueType(value) != JSON_BOOLEAN)
{ {
Log(LOG_ERR, "Expected type JSON_BOOLEAN for log.color."); tConfig->err = "Expected type JSON_BOOLEAN for log.color.";
goto error; goto error;
} }
@ -294,8 +286,44 @@ error:
return 0; 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 * Config *
ConfigParse(HashMap * config) ConfigParse(HashMap *config)
{ {
Config *tConfig; Config *tConfig;
JsonValue *value; JsonValue *value;
@ -329,11 +357,10 @@ ConfigParse(HashMap * config)
} }
else else
{ {
Log(LOG_WARNING, "Base URL not specified. Assuming it's 'https://%s'.", tConfig->serverName);
tConfig->baseUrl = Malloc(strlen(tConfig->serverName) + 10); tConfig->baseUrl = Malloc(strlen(tConfig->serverName) + 10);
if (!tConfig->baseUrl) if (!tConfig->baseUrl)
{ {
Log(LOG_ERR, "Error allocating memory for default config value 'baseUrl'."); tConfig->err = "Error allocating memory for default config value 'baseUrl'.";
goto error; goto error;
} }
@ -354,15 +381,11 @@ ConfigParse(HashMap * config)
} }
else else
{ {
Log(LOG_ERR, "Config directive 'runAs' should be a JSON object"); tConfig->err = "Config directive 'runAs' should be a JSON object that contains a 'uid' and 'gid'.";
Log(LOG_ERR, "that contains a 'uid' and 'gid'.");
goto error; goto error;
} }
} }
CONFIG_REQUIRE("dataDir", JSON_STRING);
CONFIG_COPY_STRING(tConfig->dataDir);
CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0); CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0);
CONFIG_REQUIRE("federation", JSON_BOOLEAN); CONFIG_REQUIRE("federation", JSON_BOOLEAN);
@ -383,46 +406,100 @@ ConfigParse(HashMap * config)
goto error; goto error;
} }
tConfig->ok = 1;
tConfig->err = NULL;
return tConfig; return tConfig;
error: error:
ConfigFree(tConfig); tConfig->ok = 0;
return NULL; return tConfig;
} }
void int
ConfigFree(Config * tConfig) ConfigExists(Db *db)
{ {
if (!tConfig) return DbExists(db, 1, "config");
{ }
return;
} int
ConfigCreateDefault(Db *db)
Free(tConfig->serverName); {
Free(tConfig->baseUrl); DbRef *ref;
Free(tConfig->identityServer); HashMap *json;
Array *listeners;
Free(tConfig->uid); HashMap *listen;
Free(tConfig->gid);
Free(tConfig->dataDir); char hostname[HOST_NAME_MAX + 1];
Free(tConfig->logTimestamp); if (!db)
{
if (tConfig->servers) return 0;
{ }
size_t i;
ref = DbCreate(db, 1, "config");
for (i = 0; i < ArraySize(tConfig->servers); i++) if (!ref)
{ {
HttpServerConfig *serverCfg = ArrayGet(tConfig->servers, i); return 0;
}
Free(serverCfg->tlsCert);
Free(serverCfg->tlsKey); json = DbJson(ref);
Free(serverCfg);
} JsonSet(json, JsonValueString("file"), 2, "log", "output");
ArrayFree(tConfig->servers); listeners = ArrayCreate();
} listen = HashMapCreate();
HashMapSet(listen, "port", JsonValueInteger(8008));
Free(tConfig); HashMapSet(listen, "tls", JsonValueBoolean(0));
ArrayAdd(listeners, JsonValueObject(listen));
HashMapSet(json, "listen", JsonValueArray(listeners));
if (gethostname(hostname, HOST_NAME_MAX + 1) < 0)
{
strcpy(hostname, "localhost");
}
HashMapSet(json, "serverName", JsonValueString(hostname));
HashMapSet(json, "federation", JsonValueBoolean(1));
HashMapSet(json, "registration", JsonValueBoolean(0));
return DbUnlock(db, ref);
}
Config *
ConfigLock(Db *db)
{
Config *config;
DbRef *ref = DbLock(db, 1, "config");
if (!ref)
{
return NULL;
}
config = ConfigParse(DbJson(ref));
if (config)
{
config->db = db;
config->ref = ref;
}
return config;
}
int
ConfigUnlock(Config *config)
{
Db *db;
DbRef *dbRef;
if (!config)
{
return 0;
}
db = config->db;
dbRef = config->ref;
ConfigFree(config);
return DbUnlock(db, dbRef);
} }

View file

@ -346,6 +346,9 @@ DbOpen(char *dir, size_t cache)
pthread_mutex_init(&db->lock, NULL); pthread_mutex_init(&db->lock, NULL);
db->mostRecent = NULL;
db->leastRecent = NULL;
if (db->maxCache) if (db->maxCache)
{ {
db->cache = HashMapCreate(); db->cache = HashMapCreate();
@ -353,9 +356,6 @@ DbOpen(char *dir, size_t cache)
{ {
return NULL; return NULL;
} }
db->mostRecent = NULL;
db->leastRecent = NULL;
} }
else else
{ {
@ -365,6 +365,18 @@ DbOpen(char *dir, size_t cache)
return db; return db;
} }
void
DbMaxCacheSet(Db * db, size_t cache)
{
if (!db)
{
return;
}
db->maxCache = cache;
DbCacheEvict(db);
}
void void
DbClose(Db * db) DbClose(Db * db)
{ {
@ -520,7 +532,7 @@ DbLockFromArr(Db * db, Array * args)
} }
else else
{ {
Array *name = ArrayCreate(); Array *name;
size_t i; size_t i;
/* Not in cache; load from disk */ /* Not in cache; load from disk */
@ -548,13 +560,14 @@ DbLockFromArr(Db * db, Array * args)
pthread_mutex_init(&ref->lock, NULL); pthread_mutex_init(&ref->lock, NULL);
pthread_mutex_lock(&ref->lock); pthread_mutex_lock(&ref->lock);
name = ArrayCreate();
for (i = 0; i < ArraySize(args); i++) for (i = 0; i < ArraySize(args); i++)
{ {
ArrayAdd(name, StrDuplicate(ArrayGet(args, i))); ArrayAdd(name, StrDuplicate(ArrayGet(args, i)));
} }
ref->name = name; ref->name = name;
if (db->cache) if (db->maxCache)
{ {
ref->ts = UtilServerTs(); ref->ts = UtilServerTs();
ref->size = DbComputeSize(ref->json); ref->size = DbComputeSize(ref->json);
@ -758,7 +771,7 @@ DbUnlock(Db * db, DbRef * ref)
StreamClose(ref->stream); StreamClose(ref->stream);
if (db->cache) if (db->maxCache)
{ {
db->cacheSize -= ref->size; db->cacheSize -= ref->size;
ref->size = DbComputeSize(ref->json); ref->size = DbComputeSize(ref->json);
@ -887,3 +900,16 @@ DbJson(DbRef * ref)
{ {
return ref ? ref->json : NULL; return ref ? ref->json : NULL;
} }
int
DbJsonSet(DbRef * ref, HashMap * json)
{
if (!ref || !json)
{
return 0;
}
JsonFree(ref->json);
ref->json = JsonDuplicate(json);
return 1;
}

View file

@ -321,13 +321,17 @@ HashMapSet(HashMap * map, char *key, void *value)
unsigned long hash; unsigned long hash;
size_t index; size_t index;
key = StrDuplicate(key);
if (!map || !key || !value) if (!map || !key || !value)
{ {
return NULL; return NULL;
} }
key = StrDuplicate(key);
if (!key)
{
return NULL;
}
if (map->count + 1 > map->capacity * map->maxLoad) if (map->count + 1 > map->capacity * map->maxLoad)
{ {
HashMapGrow(map); HashMapGrow(map);

View file

@ -701,6 +701,11 @@ JsonFree(HashMap * object)
char *key; char *key;
JsonValue *value; JsonValue *value;
if (!object)
{
return;
}
while (HashMapIterate(object, &key, (void **) &value)) while (HashMapIterate(object, &key, (void **) &value))
{ {
JsonValueFree(value); JsonValueFree(value);
@ -709,6 +714,79 @@ JsonFree(HashMap * object)
HashMapFree(object); HashMapFree(object);
} }
JsonValue *
JsonValueDuplicate(JsonValue *val)
{
JsonValue *new;
size_t i;
if (!val)
{
return NULL;
}
new = JsonValueAllocate();
if (!new)
{
return NULL;
}
new->type = val->type;
switch(val->type)
{
case JSON_OBJECT:
new->as.object = JsonDuplicate(val->as.object);
break;
case JSON_ARRAY:
new->as.array = ArrayCreate();
for (i = 0; i < ArraySize(val->as.array); i++)
{
ArrayAdd(new->as.array, JsonValueDuplicate(ArrayGet(val->as.array, i)));
}
break;
case JSON_STRING:
new->as.string = StrDuplicate(val->as.string);
break;
case JSON_INTEGER:
case JSON_FLOAT:
case JSON_BOOLEAN:
/* These are by value, not by reference */
new->as = val->as;
case JSON_NULL:
default:
break;
}
return new;
}
HashMap *
JsonDuplicate(HashMap * object)
{
HashMap *new;
char *key;
JsonValue *val;
if (!object)
{
return NULL;
}
new = HashMapCreate();
if (!new)
{
return NULL;
}
while (HashMapIterate(object, &key, (void **) &val))
{
HashMapSet(new, key, JsonValueDuplicate(val));
}
return new;
}
static int static int
JsonConsumeWhitespace(JsonParserState * state) JsonConsumeWhitespace(JsonParserState * state)
{ {

View file

@ -122,7 +122,6 @@ LogConfigFree(LogConfig * config)
return; return;
} }
StreamClose(config->out);
Free(config); Free(config);
if (config == globalConfig) if (config == globalConfig)

View file

@ -28,6 +28,7 @@
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <signal.h> #include <signal.h>
#include <limits.h>
#include <grp.h> #include <grp.h>
#include <pwd.h> #include <pwd.h>
@ -39,17 +40,41 @@
#include <HashMap.h> #include <HashMap.h>
#include <Json.h> #include <Json.h>
#include <HttpServer.h> #include <HttpServer.h>
#include <Routes.h>
#include <Matrix.h>
#include <Db.h> #include <Db.h>
#include <Cron.h> #include <Cron.h>
#include <Uia.h> #include <Uia.h>
#include <Util.h> #include <Util.h>
#include <Str.h>
static Array *httpServers = NULL; #include <Matrix.h>
#include <User.h>
#include <RegToken.h>
#include <Routes.h>
static Array *httpServers;
static volatile int restart;
static unsigned long startTs;
void
Restart(void)
{
raise(SIGUSR1);
}
void
Shutdown(void)
{
raise(SIGINT);
}
unsigned long
Uptime(void)
{
return UtilServerTs() - startTs;
}
static void static void
TelodendriaSignalHandler(int signal) SignalHandler(int signal)
{ {
size_t i; size_t i;
@ -57,6 +82,9 @@ TelodendriaSignalHandler(int signal)
{ {
case SIGPIPE: case SIGPIPE:
return; return;
case SIGUSR1:
restart = 1;
/* Fall through */
case SIGINT: case SIGINT:
if (!httpServers) if (!httpServers)
{ {
@ -76,30 +104,26 @@ TelodendriaSignalHandler(int signal)
typedef enum ArgFlag typedef enum ArgFlag
{ {
ARG_VERSION = (1 << 0), ARG_VERSION = (1 << 0),
ARG_CONFIGTEST = (1 << 1),
ARG_VERBOSE = (1 << 2) ARG_VERBOSE = (1 << 2)
} ArgFlag; } ArgFlag;
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
int exit = EXIT_SUCCESS; int exit;
/* Arg parsing */ /* Arg parsing */
int opt; int opt;
int flags = 0; int flags;
char *configArg = NULL; char *dbPath;
/* Config file */
Stream *configFile = NULL;
HashMap *config = NULL;
/* Program configuration */ /* Program configuration */
Config *tConfig = NULL; Config *tConfig;
Stream *logFile;
/* User validation */ /* User validation */
struct passwd *userInfo = NULL; struct passwd *userInfo;
struct group *groupInfo = NULL; struct group *groupInfo;
/* HTTP server management */ /* HTTP server management */
size_t i; size_t i;
@ -109,7 +133,30 @@ main(int argc, char **argv)
struct sigaction sigAction; struct sigaction sigAction;
MatrixHttpHandlerArgs matrixArgs; MatrixHttpHandlerArgs matrixArgs;
Cron *cron = NULL; Cron *cron;
char startDir[PATH_MAX];
start:
/* Global variables */
httpServers = NULL;
restart = 0;
/* For getopt() */
opterr = 1;
optind = 1;
/* Local variables */
exit = EXIT_SUCCESS;
flags = 0;
dbPath = NULL;
tConfig = NULL;
logFile = NULL;
userInfo = NULL;
groupInfo = NULL;
cron = NULL;
startTs = UtilServerTs();
memset(&matrixArgs, 0, sizeof(matrixArgs)); memset(&matrixArgs, 0, sizeof(matrixArgs));
@ -121,12 +168,12 @@ main(int argc, char **argv)
TelodendriaPrintHeader(); TelodendriaPrintHeader();
while ((opt = getopt(argc, argv, "f:Vvn")) != -1) while ((opt = getopt(argc, argv, "d:Vv")) != -1)
{ {
switch (opt) switch (opt)
{ {
case 'f': case 'd':
configArg = optarg; dbPath = optarg;
break; break;
case 'V': case 'V':
flags |= ARG_VERSION; flags |= ARG_VERSION;
@ -134,9 +181,6 @@ main(int argc, char **argv)
case 'v': case 'v':
flags |= ARG_VERBOSE; flags |= ARG_VERBOSE;
break; break;
case 'n':
flags |= ARG_CONFIGTEST;
break;
case '?': case '?':
exit = EXIT_FAILURE; exit = EXIT_FAILURE;
goto finish; goto finish;
@ -156,27 +200,41 @@ main(int argc, char **argv)
goto finish; goto finish;
} }
if (!configArg) if (!dbPath)
{ {
Log(LOG_ERR, "No configuration file specified."); Log(LOG_ERR, "No database directory specified.");
exit = EXIT_FAILURE; exit = EXIT_FAILURE;
goto finish; goto finish;
} }
else if (strcmp(configArg, "-") == 0)
if (!getcwd(startDir, PATH_MAX))
{ {
configFile = StreamStdin(); Log(LOG_ERR, "Unable to determine current working directory.");
exit = EXIT_FAILURE;
goto finish;
}
if (chdir(dbPath) != 0)
{
Log(LOG_ERR, "Unable to change into data directory: %s.", strerror(errno));
exit = EXIT_FAILURE;
goto finish;
} }
else else
{ {
StreamClose(StreamStdin()); Log(LOG_DEBUG, "Changed working directory to: %s", dbPath);
}
configFile = StreamOpen(configArg, "r"); matrixArgs.db = DbOpen(".", 0);
if (!configFile) if (!matrixArgs.db)
{ {
Log(LOG_ERR, "Unable to open configuration file '%s' for reading.", configArg); Log(LOG_ERR, "Unable to open data directory as a database.");
exit = EXIT_FAILURE; exit = EXIT_FAILURE;
goto finish; goto finish;
} }
else
{
Log(LOG_DEBUG, "Opened database.");
} }
Log(LOG_NOTICE, "Building routing tree..."); Log(LOG_NOTICE, "Building routing tree...");
@ -188,30 +246,56 @@ main(int argc, char **argv)
goto finish; goto finish;
} }
Log(LOG_NOTICE, "Processing configuration file '%s'.", configArg); if (!ConfigExists(matrixArgs.db))
config = JsonDecode(configFile);
StreamClose(configFile);
if (!config)
{ {
Log(LOG_ERR, "Syntax error in configuration file."); char *token;
RegTokenInfo *info;
Log(LOG_NOTICE, "No configuration exists in the opened database.");
Log(LOG_NOTICE, "A default configuration will be created, and a");
Log(LOG_NOTICE, "new single-use registration token that grants all");
Log(LOG_NOTICE, "privileges will be created so an admin user can");
Log(LOG_NOTICE, "be created to configure this database using the");
Log(LOG_NOTICE, "administrator API.");
if (!ConfigCreateDefault(matrixArgs.db))
{
Log(LOG_ERR, "Unable to create default configuration.");
exit = EXIT_FAILURE; exit = EXIT_FAILURE;
goto finish; goto finish;
} }
tConfig = ConfigParse(config); token = StrRandom(32);
JsonFree(config); info = RegTokenCreate(matrixArgs.db, token, NULL, 0, 1, USER_ALL);
if (!info)
{
Free(token);
Log(LOG_ERR, "Unable to create admin registration token.");
exit = EXIT_FAILURE;
goto finish;
}
Log(LOG_NOTICE, "Admin Registration token: %s", token);
Free(token);
RegTokenClose(info);
RegTokenFree(info);
}
Log(LOG_NOTICE, "Loading configuration...");
tConfig = ConfigLock(matrixArgs.db);
if (!tConfig) if (!tConfig)
{ {
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; exit = EXIT_FAILURE;
goto finish; goto finish;
} } else if (!tConfig->ok)
if (flags & ARG_CONFIGTEST)
{ {
Log(LOG_INFO, "Configuration is OK."); Log(LOG_ERR, tConfig->err);
exit = EXIT_FAILURE;
goto finish; goto finish;
} }
@ -236,20 +320,9 @@ main(int argc, char **argv)
LogConfigLevelSet(LogConfigGlobal(), flags & ARG_VERBOSE ? LOG_DEBUG : tConfig->logLevel); LogConfigLevelSet(LogConfigGlobal(), flags & ARG_VERBOSE ? LOG_DEBUG : tConfig->logLevel);
if (chdir(tConfig->dataDir) != 0)
{
Log(LOG_ERR, "Unable to change into data directory: %s.", strerror(errno));
exit = EXIT_FAILURE;
goto finish;
}
else
{
Log(LOG_DEBUG, "Changed working directory to: %s", tConfig->dataDir);
}
if (tConfig->flags & CONFIG_LOG_FILE) if (tConfig->flags & CONFIG_LOG_FILE)
{ {
Stream *logFile = StreamOpen("telodendria.log", "a"); logFile = StreamOpen("telodendria.log", "a");
if (!logFile) if (!logFile)
{ {
@ -261,7 +334,6 @@ main(int argc, char **argv)
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);
StreamClose(StreamStdout());
} }
else if (tConfig->flags & CONFIG_LOG_STDOUT) else if (tConfig->flags & CONFIG_LOG_STDOUT)
{ {
@ -291,14 +363,10 @@ main(int argc, char **argv)
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->uid, tConfig->gid);
Log(LOG_DEBUG, "Data Directory: %s", tConfig->dataDir);
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, "Flags: %x", tConfig->flags);
LogConfigUnindent(LogConfigGlobal()); LogConfigUnindent(LogConfigGlobal());
/* Arguments to pass into the HTTP handler */
matrixArgs.config = tConfig;
httpServers = ArrayCreate(); httpServers = ArrayCreate();
if (!httpServers) if (!httpServers)
{ {
@ -423,11 +491,9 @@ main(int argc, char **argv)
/* These config values are no longer needed; don't hold them in /* These config values are no longer needed; don't hold them in
* memory anymore */ * memory anymore */
Free(tConfig->dataDir);
Free(tConfig->uid); Free(tConfig->uid);
Free(tConfig->gid); Free(tConfig->gid);
tConfig->dataDir = NULL;
tConfig->uid = NULL; tConfig->uid = NULL;
tConfig->gid = NULL; tConfig->gid = NULL;
@ -438,18 +504,10 @@ main(int argc, char **argv)
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.");
} }
matrixArgs.db = DbOpen(".", tConfig->maxCache); DbMaxCacheSet(matrixArgs.db, tConfig->maxCache);
if (!matrixArgs.db) ConfigUnlock(tConfig);
{ tConfig = NULL;
Log(LOG_ERR, "Unable to open data directory as a database.");
exit = EXIT_FAILURE;
goto finish;
}
else
{
Log(LOG_DEBUG, "Opened database.");
}
cron = CronCreate(60 * 1000); /* 1-minute tick */ cron = CronCreate(60 * 1000); /* 1-minute tick */
if (!cron) if (!cron)
@ -489,7 +547,7 @@ main(int argc, char **argv)
} }
sigAction.sa_handler = TelodendriaSignalHandler; sigAction.sa_handler = SignalHandler;
sigfillset(&sigAction.sa_mask); sigfillset(&sigAction.sa_mask);
sigAction.sa_flags = SA_RESTART; sigAction.sa_flags = SA_RESTART;
@ -507,6 +565,7 @@ main(int argc, char **argv)
SIGACTION(SIGINT, &sigAction, NULL); SIGACTION(SIGINT, &sigAction, NULL);
SIGACTION(SIGPIPE, &sigAction, NULL); SIGACTION(SIGPIPE, &sigAction, NULL);
SIGACTION(SIGUSR1, &sigAction, NULL);
#undef SIGACTION #undef SIGACTION
@ -542,16 +601,15 @@ finish:
Log(LOG_DEBUG, "Stopped and freed job scheduler."); Log(LOG_DEBUG, "Stopped and freed job scheduler.");
} }
ConfigUnlock(tConfig);
Log(LOG_DEBUG, "Unlocked configuration.");
DbClose(matrixArgs.db); DbClose(matrixArgs.db);
Log(LOG_DEBUG, "Closed database."); Log(LOG_DEBUG, "Closed database.");
HttpRouterFree(matrixArgs.router); HttpRouterFree(matrixArgs.router);
Log(LOG_DEBUG, "Freed routing tree."); Log(LOG_DEBUG, "Freed routing tree.");
ConfigFree(tConfig);
Log(LOG_DEBUG, "Exiting with code '%d'.", exit);
/* /*
* 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
@ -560,6 +618,19 @@ finish:
MemoryHook(NULL, NULL); MemoryHook(NULL, NULL);
LogConfigFree(LogConfigGlobal()); LogConfigFree(LogConfigGlobal());
StreamClose(logFile);
if (restart)
{
/*
* Change back into starting directory so initial chdir()
* call works.
*/
chdir(startDir);
goto start;
}
StreamClose(StreamStdout());
/* Standard error should never have been opened, but just in case /* Standard error should never have been opened, but just in case
* it was, this doesn't hurt anything. */ * it was, this doesn't hurt anything. */
@ -572,5 +643,6 @@ finish:
/* Free any leaked memory now, just in case the operating system /* Free any leaked memory now, just in case the operating system
* we're running on won't do it for us. */ * we're running on won't do it for us. */
MemoryFreeAll(); MemoryFreeAll();
return exit; return exit;
} }

View file

@ -203,7 +203,7 @@ RegTokenCreate(Db * db, char *name, char *owner, unsigned long expires, int uses
unsigned long timestamp = UtilServerTs(); unsigned long timestamp = UtilServerTs();
if (!db || !name || !owner) if (!db || !name)
{ {
return NULL; return NULL;
} }

View file

@ -67,6 +67,9 @@ RouterBuild(void)
R("/_matrix/client/v3/profile/(.*)", RouteUserProfile); R("/_matrix/client/v3/profile/(.*)", RouteUserProfile);
R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile); R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile);
R("/_telodendria/admin/(restart|shutdown|stats)", RouteProcControl);
R("/_telodendria/admin/config", RouteConfig);
#undef R #undef R
return router; return router;

View file

@ -65,45 +65,53 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
char *token; char *token;
char *newPassword; char *newPassword;
Config *config = ConfigLock(db);
if (!config)
{
Log(LOG_ERR, "Password endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN);
}
(void) path; (void) path;
if (HttpRequestMethodGet(args->context) != HTTP_POST) if (HttpRequestMethodGet(args->context) != HTTP_POST)
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_UNRECOGNIZED); response = MatrixErrorCreate(M_UNRECOGNIZED);
goto finish;
} }
response = MatrixGetAccessToken(args->context, &token); response = MatrixGetAccessToken(args->context, &token);
if (response) if (response)
{ {
JsonFree(request); goto finish;
return response;
} }
request = JsonDecode(HttpServerStream(args->context)); request = JsonDecode(HttpServerStream(args->context));
if (!request) if (!request)
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_NOT_JSON); response = MatrixErrorCreate(M_NOT_JSON);
goto finish;
} }
uiaFlows = ArrayCreate(); uiaFlows = ArrayCreate();
ArrayAdd(uiaFlows, PasswordFlow()); ArrayAdd(uiaFlows, PasswordFlow());
uiaResult = UiaComplete(uiaFlows, args->context, uiaResult = UiaComplete(uiaFlows, args->context,
args->matrixArgs->db, request, &response, db, request, &response,
args->matrixArgs->config); config);
UiaFlowsFree(uiaFlows); UiaFlowsFree(uiaFlows);
if (uiaResult < 0) if (uiaResult < 0)
{ {
JsonFree(request);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN); response = MatrixErrorCreate(M_UNKNOWN);
goto finish;
} }
else if (!uiaResult) else if (!uiaResult)
{ {
JsonFree(request); goto finish;
return response;
} }
newPassword = JsonValueAsString(HashMapGet(request, "new_password")); newPassword = JsonValueAsString(HashMapGet(request, "new_password"));
@ -111,7 +119,8 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
{ {
JsonFree(request); JsonFree(request);
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_BAD_JSON); response = MatrixErrorCreate(M_BAD_JSON);
goto finish;
} }
val = HashMapGet(request, "logout_devices"); val = HashMapGet(request, "logout_devices");
@ -125,9 +134,9 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
if (!user) if (!user)
{ {
JsonFree(request);
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_UNKNOWN_TOKEN); response = MatrixErrorCreate(M_UNKNOWN_TOKEN);
goto finish;
} }
UserSetPassword(user, newPassword); UserSetPassword(user, newPassword);
@ -139,8 +148,11 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
UserDeleteTokens(user, token); UserDeleteTokens(user, token);
} }
response = HashMapCreate();
finish:
ConfigUnlock(config);
UserUnlock(user); UserUnlock(user);
JsonFree(request); JsonFree(request);
response = HashMapCreate();
return response; return response;
} }

137
src/Routes/RouteConfig.c Normal file
View file

@ -0,0 +1,137 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Routes.h>
#include <User.h>
#include <Main.h>
#include <Memory.h>
#include <string.h>
ROUTE_IMPL(RouteConfig, path, argp)
{
RouteArgs *args = argp;
HashMap *response;
char *token;
User *user = NULL;
Config *config = NULL;
HashMap *request = NULL;
Config *newConf;
(void) path;
response = MatrixGetAccessToken(args->context, &token);
if (response)
{
goto finish;
}
user = UserAuthenticate(args->matrixArgs->db, token);
if (!user)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNKNOWN_TOKEN);
goto finish;
}
if (!(UserGetPrivileges(user) & USER_CONFIG))
{
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN);
goto finish;
}
config = ConfigLock(args->matrixArgs->db);
if (!config)
{
Log(LOG_ERR, "Config endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN);
goto finish;
}
switch (HttpRequestMethodGet(args->context))
{
case HTTP_GET:
response = JsonDuplicate(DbJson(config->ref));
break;
case HTTP_POST:
request = JsonDecode(HttpServerStream(args->context));
if (!request)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_NOT_JSON);
break;
}
newConf = ConfigParse(request);
if (!newConf)
{
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN);
break;
}
if (newConf->ok)
{
if (DbJsonSet(config->ref, request))
{
response = HashMapCreate();
/*
* TODO: Apply configuration and set this only if a main
* component was reconfigured, such as the listeners.
*/
HashMapSet(response, "restart_required", JsonValueBoolean(1));
}
else
{
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN);
}
}
else
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
/* TODO: Attach newConf->err as message */
response = MatrixErrorCreate(M_BAD_JSON);
}
ConfigFree(newConf);
JsonFree(request);
break;
case HTTP_PUT:
/* TODO: Support incremental changes to the config */
default:
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED);
break;
}
finish:
UserUnlock(user);
ConfigUnlock(config);
return response;
}

View file

@ -57,6 +57,14 @@ ROUTE_IMPL(RouteLogin, path, argp)
UserLoginInfo *loginInfo; UserLoginInfo *loginInfo;
char *fullUsername; char *fullUsername;
Config *config = ConfigLock(db);
if (!config)
{
Log(LOG_ERR, "Login endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN);
}
(void) path; (void) path;
switch (HttpRequestMethodGet(args->context)) switch (HttpRequestMethodGet(args->context))
@ -157,8 +165,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
break; break;
} }
userId = UserIdParse(JsonValueAsString(val), userId = UserIdParse(JsonValueAsString(val), config->serverName);
args->matrixArgs->config->serverName);
if (!userId) if (!userId)
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
@ -166,7 +173,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
break; break;
} }
if (strcmp(userId->server, args->matrixArgs->config->serverName) != 0 if (strcmp(userId->server, config->serverName) != 0
|| !UserExists(db, userId->localpart)) || !UserExists(db, userId->localpart))
{ {
HttpResponseStatus(args->context, HTTP_FORBIDDEN); HttpResponseStatus(args->context, HTTP_FORBIDDEN);
@ -276,14 +283,13 @@ ROUTE_IMPL(RouteLogin, path, argp)
} }
fullUsername = StrConcat(4, "@", UserGetName(user), ":", fullUsername = StrConcat(4, "@", UserGetName(user), ":",
args->matrixArgs->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(args->matrixArgs->config->baseUrl, MatrixClientWellKnown(config->baseUrl, config->identityServer)));
args->matrixArgs->config->identityServer)));
UserAccessTokenFree(loginInfo->accessToken); UserAccessTokenFree(loginInfo->accessToken);
Free(loginInfo->refreshToken); Free(loginInfo->refreshToken);
@ -300,5 +306,6 @@ ROUTE_IMPL(RouteLogin, path, argp)
UserIdFree(userId); UserIdFree(userId);
JsonFree(request); JsonFree(request);
ConfigUnlock(config);
return response; return response;
} }

View file

@ -0,0 +1,92 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Routes.h>
#include <User.h>
#include <Main.h>
#include <Memory.h>
#include <string.h>
ROUTE_IMPL(RouteProcControl, path, argp)
{
RouteArgs *args = argp;
char *op = ArrayGet(path, 0);
HashMap *response;
char *token;
User *user = NULL;
response = MatrixGetAccessToken(args->context, &token);
if (response)
{
goto finish;
}
user = UserAuthenticate(args->matrixArgs->db, token);
if (!user)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNKNOWN_TOKEN);
goto finish;
}
if (!(UserGetPrivileges(user) & USER_PROC_CONTROL))
{
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN);
goto finish;
}
if (strcmp(op, "restart") == 0)
{
Restart();
}
else if (strcmp(op, "shutdown") == 0)
{
Shutdown();
}
else if (strcmp(op, "stats") == 0)
{
response = HashMapCreate();
HashMapSet(response, "version", JsonValueString(TELODENDRIA_VERSION));
HashMapSet(response, "memory_allocated", JsonValueInteger(MemoryAllocated()));
HashMapSet(response, "uptime", JsonValueInteger(Uptime()));
goto finish;
}
else
{
/* Should be impossible */
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN);
goto finish;
}
response = HashMapCreate();
finish:
UserUnlock(user);
return response;
}

View file

@ -77,19 +77,29 @@ ROUTE_IMPL(RouteRegister, path, argp)
char *session; char *session;
DbRef *sessionRef; DbRef *sessionRef;
Config *config = ConfigLock(db);
if (!config)
{
Log(LOG_ERR, "Registration endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN);
}
if (ArraySize(path) == 0) if (ArraySize(path) == 0)
{ {
if (HttpRequestMethodGet(args->context) != HTTP_POST) if (HttpRequestMethodGet(args->context) != HTTP_POST)
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_UNRECOGNIZED); response = MatrixErrorCreate(M_UNRECOGNIZED);
goto end;
} }
request = JsonDecode(HttpServerStream(args->context)); request = JsonDecode(HttpServerStream(args->context));
if (!request) if (!request)
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_NOT_JSON); response = MatrixErrorCreate(M_NOT_JSON);
goto end;
} }
val = HashMapGet(request, "username"); val = HashMapGet(request, "username");
@ -103,7 +113,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
} }
username = StrDuplicate(JsonValueAsString(val)); username = StrDuplicate(JsonValueAsString(val));
if (!UserValidate(username, args->matrixArgs->config->serverName)) if (!UserValidate(username, config->serverName))
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_USERNAME); response = MatrixErrorCreate(M_INVALID_USERNAME);
@ -121,14 +131,14 @@ ROUTE_IMPL(RouteRegister, path, argp)
uiaFlows = ArrayCreate(); uiaFlows = ArrayCreate();
ArrayAdd(uiaFlows, RouteRegisterRegFlow()); ArrayAdd(uiaFlows, RouteRegisterRegFlow());
if (args->matrixArgs->config->flags & CONFIG_REGISTRATION) if (config->flags & CONFIG_REGISTRATION)
{ {
ArrayAdd(uiaFlows, UiaDummyFlow()); ArrayAdd(uiaFlows, UiaDummyFlow());
} }
uiaResult = UiaComplete(uiaFlows, args->context, uiaResult = UiaComplete(uiaFlows, args->context,
args->matrixArgs->db, request, &response, db, request, &response,
args->matrixArgs->config); config);
if (uiaResult < 0) if (uiaResult < 0)
{ {
@ -224,7 +234,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
user = UserCreate(db, username, password); user = UserCreate(db, username, password);
response = HashMapCreate(); response = HashMapCreate();
fullUsername = StrConcat(4, "@", UserGetName(user), ":", args->matrixArgs->config->serverName); fullUsername = StrConcat(4, "@", UserGetName(user), ":", config->serverName);
HashMapSet(response, "user_id", JsonValueString(fullUsername)); HashMapSet(response, "user_id", JsonValueString(fullUsername));
Free(fullUsername); Free(fullUsername);
@ -302,7 +312,7 @@ finish:
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM); response = MatrixErrorCreate(M_MISSING_PARAM);
} }
else if (!UserValidate(username, args->matrixArgs->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); response = MatrixErrorCreate(M_INVALID_USERNAME);
@ -325,5 +335,7 @@ finish:
} }
} }
end:
ConfigUnlock(config);
return response; return response;
} }

View file

@ -42,12 +42,22 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
UserId *userId = NULL; UserId *userId = NULL;
User *user = NULL; User *user = NULL;
char *serverName = args->matrixArgs->config->serverName; char *serverName;
char *username = NULL; char *username = NULL;
char *entry = NULL; char *entry = NULL;
char *token = NULL; char *token = NULL;
char *value = NULL; char *value = NULL;
Config *config = ConfigLock(db);
if (!config)
{
Log(LOG_ERR, "User profile endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN);
}
serverName = config->serverName;
username = ArrayGet(path, 0); username = ArrayGet(path, 0);
userId = UserIdParse(username, serverName); userId = UserIdParse(username, serverName);
if (!userId) if (!userId)
@ -160,6 +170,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
break; break;
} }
finish: finish:
ConfigUnlock(config);
Free(username); Free(username);
Free(entry); Free(entry);
UserIdFree(userId); UserIdFree(userId);

View file

@ -33,15 +33,26 @@
ROUTE_IMPL(RouteWellKnown, path, argp) ROUTE_IMPL(RouteWellKnown, path, argp)
{ {
RouteArgs *args = argp; RouteArgs *args = argp;
HashMap *response;
Config *config = ConfigLock(args->matrixArgs->db);
if (!config)
{
Log(LOG_ERR, "Well-known endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN);
}
if (MATRIX_PATH_EQUALS(ArrayGet(path, 0), "client")) if (MATRIX_PATH_EQUALS(ArrayGet(path, 0), "client"))
{ {
return MatrixClientWellKnown(args->matrixArgs->config->baseUrl, response = MatrixClientWellKnown(config->baseUrl, config->identityServer);
args->matrixArgs->config->identityServer);
} }
else else
{ {
HttpResponseStatus(args->context, HTTP_NOT_FOUND); HttpResponseStatus(args->context, HTTP_NOT_FOUND);
return MatrixErrorCreate(M_NOT_FOUND); response = MatrixErrorCreate(M_NOT_FOUND);
} }
ConfigUnlock(config);
return response;
} }

View file

@ -45,6 +45,14 @@ ROUTE_IMPL(RouteWhoami, path, argp)
char *userID; char *userID;
char *deviceID; char *deviceID;
Config *config = ConfigLock(db);
if (!config)
{
Log(LOG_ERR, "Who am I endpoint failed to lock configuration.");
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN);
}
(void) path; (void) path;
/* Get the request */ /* Get the request */
@ -52,14 +60,15 @@ ROUTE_IMPL(RouteWhoami, path, argp)
if (response) if (response)
{ {
/* No token? */ /* No token? */
return response; goto finish;
} }
/* Authenticate with our token */ /* Authenticate with our token */
if (!DbExists(db, 3, "tokens", "access", token)) if (!DbExists(db, 3, "tokens", "access", token))
{ {
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
return MatrixErrorCreate(M_UNKNOWN_TOKEN); response = MatrixErrorCreate(M_UNKNOWN_TOKEN);
goto finish;
} }
ref = DbLock(db, 3, "tokens", "access", token); ref = DbLock(db, 3, "tokens", "access", token);
@ -69,7 +78,7 @@ ROUTE_IMPL(RouteWhoami, path, argp)
userID = StrConcat(4, "@", userID = StrConcat(4, "@",
JsonValueAsString(HashMapGet(tokenJson, "user")), JsonValueAsString(HashMapGet(tokenJson, "user")),
":", args->matrixArgs->config->serverName); ":", config->serverName);
deviceID = StrDuplicate(JsonValueAsString(HashMapGet(tokenJson, "device"))); deviceID = StrDuplicate(JsonValueAsString(HashMapGet(tokenJson, "device")));
@ -80,5 +89,8 @@ ROUTE_IMPL(RouteWhoami, path, argp)
Free(userID); Free(userID);
Free(deviceID); Free(deviceID);
finish:
ConfigUnlock(config);
return response; return response;
} }

View file

@ -89,6 +89,11 @@ StrDuplicate(const char *inStr)
size_t len; size_t len;
char *outStr; char *outStr;
if (!inStr)
{
return NULL;
}
len = strlen(inStr); len = strlen(inStr);
outStr = Malloc(len + 1); /* For the null terminator */ outStr = Malloc(len + 1); /* For the null terminator */
if (!outStr) if (!outStr)

View file

@ -197,3 +197,4 @@ TelodendriaPrintHeader(void)
"Documentation/Support: https://telodendria.io"); "Documentation/Support: https://telodendria.io");
Log(LOG_INFO, ""); Log(LOG_INFO, "");
} }

View file

@ -784,6 +784,10 @@ UserDecodePrivilege(const char *p)
{ {
return USER_GRANT_PRIVILEGES; return USER_GRANT_PRIVILEGES;
} }
else if (strcmp(p, "PROC_CONTROL") == 0)
{
return USER_PROC_CONTROL;
}
else else
{ {
return USER_NONE; return USER_NONE;
@ -816,6 +820,7 @@ UserEncodePrivileges(int privileges)
A(USER_ISSUE_TOKENS, "ISSUE_TOKENS"); A(USER_ISSUE_TOKENS, "ISSUE_TOKENS");
A(USER_CONFIG, "CONFIG"); A(USER_CONFIG, "CONFIG");
A(USER_GRANT_PRIVILEGES, "GRANT_PRIVILEGES"); A(USER_GRANT_PRIVILEGES, "GRANT_PRIVILEGES");
A(USER_PROC_CONTROL, "PROC_CONTROL");
#undef A #undef A

View file

@ -25,9 +25,9 @@
#ifndef TELODENDRIA_CONFIG_H #ifndef TELODENDRIA_CONFIG_H
#define TELODENDRIA_CONFIG_H #define TELODENDRIA_CONFIG_H
#include <Log.h>
#include <HashMap.h> #include <HashMap.h>
#include <Array.h> #include <Array.h>
#include <Db.h>
typedef enum ConfigFlag typedef enum ConfigFlag
{ {
@ -41,13 +41,15 @@ typedef enum ConfigFlag
typedef struct Config typedef struct Config
{ {
Db *db;
DbRef *ref;
char *serverName; char *serverName;
char *baseUrl; char *baseUrl;
char *identityServer; char *identityServer;
char *uid; char *uid;
char *gid; char *gid;
char *dataDir;
unsigned int flags; unsigned int flags;
@ -57,12 +59,27 @@ typedef struct Config
int logLevel; int logLevel;
Array *servers; Array *servers;
int ok;
char *err;
} Config; } Config;
extern Config * Config *
ConfigParse(HashMap *); ConfigParse(HashMap *);
extern void void
ConfigFree(Config *); ConfigFree(Config *);
extern int
ConfigExists(Db *);
extern int
ConfigCreateDefault(Db *);
extern Config *
ConfigLock(Db *);
extern int
ConfigUnlock(Config *);
#endif /* TELODENDRIA_CONFIG_H */ #endif /* TELODENDRIA_CONFIG_H */

View file

@ -35,6 +35,9 @@ typedef struct DbRef DbRef;
extern Db * extern Db *
DbOpen(char *, size_t); DbOpen(char *, size_t);
extern void
DbMaxCacheSet(Db *, size_t);
extern void extern void
DbClose(Db *); DbClose(Db *);
@ -62,4 +65,7 @@ extern void
extern HashMap * extern HashMap *
DbJson(DbRef *); DbJson(DbRef *);
extern int
DbJsonSet(DbRef *, HashMap *);
#endif #endif

View file

@ -93,9 +93,16 @@ extern JsonValue *
extern void extern void
JsonValueFree(JsonValue *); JsonValueFree(JsonValue *);
extern JsonValue *
JsonValueDuplicate(JsonValue *);
extern HashMap *
JsonDuplicate(HashMap *);
extern void extern void
JsonFree(HashMap *); JsonFree(HashMap *);
extern void extern void
JsonEncodeString(const char *, Stream *); JsonEncodeString(const char *, Stream *);

16
src/include/Main.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef TELODENDRIA_MAIN_H
#define TELODENDRIA_MAIN_H
extern void
Restart(void);
extern void
Shutdown(void);
extern unsigned long
Uptime(void);
extern int
main(int argc, char **argv);
#endif /* TELODENDRIA_MAIN_H */

View file

@ -70,7 +70,6 @@ typedef enum MatrixError
typedef struct MatrixHttpHandlerArgs typedef struct MatrixHttpHandlerArgs
{ {
Config *config;
Db *db; Db *db;
HttpRouter *router; HttpRouter *router;
} MatrixHttpHandlerArgs; } MatrixHttpHandlerArgs;

View file

@ -69,6 +69,9 @@ ROUTE(RouteUiaFallback);
ROUTE(RouteStaticDefault); ROUTE(RouteStaticDefault);
ROUTE(RouteStaticLogin); ROUTE(RouteStaticLogin);
ROUTE(RouteProcControl);
ROUTE(RouteConfig);
#undef ROUTE #undef ROUTE
#endif #endif

View file

@ -32,9 +32,10 @@
#define USER_ISSUE_TOKENS (1 << 1) #define USER_ISSUE_TOKENS (1 << 1)
#define USER_CONFIG (1 << 2) #define USER_CONFIG (1 << 2)
#define USER_GRANT_PRIVILEGES (1 << 3) #define USER_GRANT_PRIVILEGES (1 << 3)
#define USER_PROC_CONTROL (1 << 4)
#define USER_NONE 0 #define USER_NONE 0
#define USER_ALL ((1 << 4) - 1) #define USER_ALL ((1 << 5) - 1)
typedef struct User User; typedef struct User User;

View file

@ -158,10 +158,9 @@ recipe_build() {
recipe_run() { recipe_run() {
if [ -f "build/$PROG" ]; then if [ -f "build/$PROG" ]; then
"build/$PROG" -f contrib/development.conf "build/$PROG" -d data
dataDir=$(< contrib/development.conf json -s "dataDir->@decode") if [ -f "data/Memory.txt" ]; then
if [ -f "$dataDir/Memory.txt" ]; then
echo "WARNING: Memory.txt exists in the data directory; this means" echo "WARNING: Memory.txt exists in the data directory; this means"
echo "Telodendria is leaking memory. Please fix memory leaks." echo "Telodendria is leaking memory. Please fix memory leaks."
fi fi

View file

@ -44,6 +44,7 @@ RESPONSE=$(login_payload | http -X POST -d @- "$BASE/_matrix/client/v3/login")
ACCESS_TOKEN=$(echo "$RESPONSE" | json -s "access_token->@decode") ACCESS_TOKEN=$(echo "$RESPONSE" | json -s "access_token->@decode")
if [ -z "$ACCESS_TOKEN" ]; then if [ -z "$ACCESS_TOKEN" ]; then
echo "Failed to log in."
echo "$RESPONSE" | json echo "$RESPONSE" | json
exit 1 exit 1
fi fi