From 0cca38115af52e97cd95de2fd65a836c30b2dda7 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Wed, 19 Apr 2023 00:33:38 +0000 Subject: [PATCH] Move configuration to database, add process control API, fix memory leaks. --- TODO.txt | 13 +- contrib/development.conf | 1 - src/Config.c | 213 ++++++++++++++++++++---------- src/Db.c | 38 +++++- src/HashMap.c | 8 +- src/Json.c | 78 +++++++++++ src/Log.c | 1 - src/Main.c | 240 ++++++++++++++++++++++------------ src/RegToken.c | 2 +- src/Routes.c | 3 + src/Routes/RouteChangePwd.c | 40 ++++-- src/Routes/RouteConfig.c | 137 +++++++++++++++++++ src/Routes/RouteLogin.c | 19 ++- src/Routes/RouteProcControl.c | 92 +++++++++++++ src/Routes/RouteRegister.c | 28 ++-- src/Routes/RouteUserProfile.c | 13 +- src/Routes/RouteWellKnown.c | 17 ++- src/Routes/RouteWhoami.c | 18 ++- src/Str.c | 5 + src/Telodendria.c | 1 + src/User.c | 5 + src/include/Config.h | 29 +++- src/include/Db.h | 6 + src/include/Json.h | 7 + src/include/Main.h | 16 +++ src/include/Matrix.h | 1 - src/include/Routes.h | 3 + src/include/User.h | 3 +- tools/bin/td | 5 +- tools/bin/tt | 1 + 30 files changed, 830 insertions(+), 213 deletions(-) create mode 100644 src/Routes/RouteConfig.c create mode 100644 src/Routes/RouteProcControl.c create mode 100644 src/include/Main.h diff --git a/TODO.txt b/TODO.txt index 5810355..1c5c96e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -52,10 +52,11 @@ Milestone: v0.3.0 [~] Move configuration to database [x] Token permissions - [ ] Initial configuration - [ ] If no config, create one-time use registration token that + [x] Initial configuration + [x] If no config, create one-time use registration token that grants user admin privileges. - [ ] /_telodendria/admin/config endpoint + [~] /_telodendria/admin/config endpoint + [x] JsonDuplicate() [x] Refactor TelodendriaConfig to just Config [ ] Documentation @@ -72,9 +73,11 @@ Milestone: v0.3.0 [ ] send-patch [ ] Log [ ] TelodendriaConfig -> Config + [ ] telodendria.conf [ ] HashMap [ ] HttpRouter [ ] Str + [ ] Admin API [~] Client-Server API [x] 4: Token-based user registration @@ -91,7 +94,9 @@ Milestone: v0.3.0 [~] Deactivate [x] Make sure UserLogin() fails if user is deactivated. [x] Change password - [x] Whoami + [~] Whoami + [ ] Attach device id to user object + [ ] Use UserAuthenticate() [~] 9: User Data [ ] 5: Capabilities negotiation [ ] 10: Security (Rate Limiting) diff --git a/contrib/development.conf b/contrib/development.conf index effc86e..122f903 100644 --- a/contrib/development.conf +++ b/contrib/development.conf @@ -5,7 +5,6 @@ "timestampFormat": "none", "level": "debug" }, - "dataDir": "./data", "listen": [ { "port": 8008, diff --git a/src/Config.c b/src/Config.c index c098d59..383933c 100644 --- a/src/Config.c +++ b/src/Config.c @@ -25,31 +25,32 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include +#include #define CONFIG_REQUIRE(key, type) \ value = HashMapGet(config, key); \ if (!value) \ { \ - Log(LOG_ERR, "Missing required " key " directive."); \ + tConfig->err = "Missing required " key " directive."; \ goto error; \ } \ if (JsonValueType(value) == JSON_NULL) \ { \ - Log(LOG_ERR, "Missing value for " key " directive."); \ + tConfig->err = "Missing value for " key " directive."; \ goto error; \ } \ 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; \ } @@ -62,14 +63,13 @@ { \ 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; \ } \ into = StrDuplicate(JsonValueAsString(value)); \ } \ else \ { \ - Log(LOG_INFO, "Using default value " #default " for " key "."); \ into = default ? StrDuplicate(default) : NULL; \ } @@ -79,14 +79,13 @@ { \ 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; \ } \ into = JsonValueAsInteger(value); \ } \ else \ { \ - Log(LOG_INFO, "Using default value " #default " for " key "."); \ into = default; \ } @@ -113,8 +112,7 @@ ConfigParseListen(Config * tConfig, Array * listen) if (!ArraySize(listen)) { - Log(LOG_ERR, "Listen array cannot be empty; you must specify at least"); - Log(LOG_ERR, "one listener."); + tConfig->err = "Listen array cannot be empty; you must specify at least one listener."; goto error; } @@ -123,7 +121,7 @@ ConfigParseListen(Config * tConfig, Array * listen) tConfig->servers = ArrayCreate(); if (!tConfig->servers) { - Log(LOG_ERR, "Unable to allocate memory for listener configurations."); + tConfig->err = "Unable to allocate memory for listener configurations."; goto error; } } @@ -136,14 +134,13 @@ ConfigParseListen(Config * tConfig, Array * listen) if (!serverCfg) { - Log(LOG_ERR, "Unable to allocate memory for listener configuration."); + tConfig->err = "Unable to allocate memory for listener configuration."; goto error; } if (JsonValueType(val) != JSON_OBJECT) { - Log(LOG_ERR, "Invalid value in listener array."); - Log(LOG_ERR, "All listeners must be objects."); + tConfig->err = "Invalid value in listener array. All listeners must be objects."; goto error; } @@ -155,22 +152,17 @@ ConfigParseListen(Config * tConfig, Array * listen) if (!serverCfg->port) { - Log(LOG_WARNING, "No or invalid port specified, listener will be ignored."); Free(serverCfg); continue; } 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; } 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; } @@ -183,7 +175,7 @@ ConfigParseListen(Config * tConfig, Array * listen) } 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; } else @@ -196,7 +188,7 @@ ConfigParseListen(Config * tConfig, Array * listen) 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; } } @@ -231,7 +223,7 @@ ConfigParseLog(Config * tConfig, HashMap * config) } else { - Log(LOG_ERR, "Invalid value for log.output: '%s'.", str); + tConfig->err = "Invalid value for log.output"; goto error; } @@ -259,7 +251,7 @@ ConfigParseLog(Config * tConfig, HashMap * config) } else { - Log(LOG_ERR, "Invalid value for log.level: '%s'.", tConfig->logLevel); + tConfig->err = "Invalid value for log.level."; goto error; } @@ -278,7 +270,7 @@ ConfigParseLog(Config * tConfig, HashMap * config) { 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; } @@ -294,8 +286,44 @@ 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) +ConfigParse(HashMap *config) { Config *tConfig; JsonValue *value; @@ -329,11 +357,10 @@ ConfigParse(HashMap * config) } else { - Log(LOG_WARNING, "Base URL not specified. Assuming it's 'https://%s'.", tConfig->serverName); tConfig->baseUrl = Malloc(strlen(tConfig->serverName) + 10); if (!tConfig->baseUrl) { - Log(LOG_ERR, "Error allocating memory for default config value 'baseUrl'."); + tConfig->err = "Error allocating memory for default config value 'baseUrl'."; goto error; } @@ -354,15 +381,11 @@ ConfigParse(HashMap * config) } else { - Log(LOG_ERR, "Config directive 'runAs' should be a JSON object"); - Log(LOG_ERR, "that contains a 'uid' and 'gid'."); + tConfig->err = "Config directive 'runAs' should be a JSON object that contains a 'uid' and 'gid'."; goto error; } } - CONFIG_REQUIRE("dataDir", JSON_STRING); - CONFIG_COPY_STRING(tConfig->dataDir); - CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0); CONFIG_REQUIRE("federation", JSON_BOOLEAN); @@ -383,46 +406,100 @@ ConfigParse(HashMap * config) goto error; } + tConfig->ok = 1; + tConfig->err = NULL; return tConfig; error: - ConfigFree(tConfig); - return NULL; + tConfig->ok = 0; + return tConfig; } -void -ConfigFree(Config * tConfig) +int +ConfigExists(Db *db) { - if (!tConfig) - { - return; - } - - Free(tConfig->serverName); - Free(tConfig->baseUrl); - Free(tConfig->identityServer); - - Free(tConfig->uid); - Free(tConfig->gid); - Free(tConfig->dataDir); - - 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); + return DbExists(db, 1, "config"); +} + +int +ConfigCreateDefault(Db *db) +{ + DbRef *ref; + HashMap *json; + Array *listeners; + HashMap *listen; + + char hostname[HOST_NAME_MAX + 1]; + + if (!db) + { + return 0; + } + + ref = DbCreate(db, 1, "config"); + if (!ref) + { + return 0; + } + + json = DbJson(ref); + + JsonSet(json, JsonValueString("file"), 2, "log", "output"); + + listeners = ArrayCreate(); + listen = HashMapCreate(); + HashMapSet(listen, "port", JsonValueInteger(8008)); + 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); } diff --git a/src/Db.c b/src/Db.c index ea2d395..a77a416 100644 --- a/src/Db.c +++ b/src/Db.c @@ -346,6 +346,9 @@ DbOpen(char *dir, size_t cache) pthread_mutex_init(&db->lock, NULL); + db->mostRecent = NULL; + db->leastRecent = NULL; + if (db->maxCache) { db->cache = HashMapCreate(); @@ -353,9 +356,6 @@ DbOpen(char *dir, size_t cache) { return NULL; } - - db->mostRecent = NULL; - db->leastRecent = NULL; } else { @@ -365,6 +365,18 @@ DbOpen(char *dir, size_t cache) return db; } +void +DbMaxCacheSet(Db * db, size_t cache) +{ + if (!db) + { + return; + } + + db->maxCache = cache; + DbCacheEvict(db); +} + void DbClose(Db * db) { @@ -520,7 +532,7 @@ DbLockFromArr(Db * db, Array * args) } else { - Array *name = ArrayCreate(); + Array *name; size_t i; /* Not in cache; load from disk */ @@ -548,13 +560,14 @@ DbLockFromArr(Db * db, Array * args) pthread_mutex_init(&ref->lock, NULL); pthread_mutex_lock(&ref->lock); + name = ArrayCreate(); for (i = 0; i < ArraySize(args); i++) { ArrayAdd(name, StrDuplicate(ArrayGet(args, i))); } ref->name = name; - if (db->cache) + if (db->maxCache) { ref->ts = UtilServerTs(); ref->size = DbComputeSize(ref->json); @@ -758,7 +771,7 @@ DbUnlock(Db * db, DbRef * ref) StreamClose(ref->stream); - if (db->cache) + if (db->maxCache) { db->cacheSize -= ref->size; ref->size = DbComputeSize(ref->json); @@ -887,3 +900,16 @@ DbJson(DbRef * ref) { 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; +} diff --git a/src/HashMap.c b/src/HashMap.c index fba25a1..620092d 100644 --- a/src/HashMap.c +++ b/src/HashMap.c @@ -321,13 +321,17 @@ HashMapSet(HashMap * map, char *key, void *value) unsigned long hash; size_t index; - key = StrDuplicate(key); - if (!map || !key || !value) { return NULL; } + key = StrDuplicate(key); + if (!key) + { + return NULL; + } + if (map->count + 1 > map->capacity * map->maxLoad) { HashMapGrow(map); diff --git a/src/Json.c b/src/Json.c index af4159b..74b4f4f 100644 --- a/src/Json.c +++ b/src/Json.c @@ -701,6 +701,11 @@ JsonFree(HashMap * object) char *key; JsonValue *value; + if (!object) + { + return; + } + while (HashMapIterate(object, &key, (void **) &value)) { JsonValueFree(value); @@ -709,6 +714,79 @@ JsonFree(HashMap * 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 JsonConsumeWhitespace(JsonParserState * state) { diff --git a/src/Log.c b/src/Log.c index c279c69..f24ee23 100644 --- a/src/Log.c +++ b/src/Log.c @@ -122,7 +122,6 @@ LogConfigFree(LogConfig * config) return; } - StreamClose(config->out); Free(config); if (config == globalConfig) diff --git a/src/Main.c b/src/Main.c index 94f3ea9..15a6130 100644 --- a/src/Main.c +++ b/src/Main.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -39,17 +40,41 @@ #include #include #include -#include -#include #include #include #include #include +#include -static Array *httpServers = NULL; +#include +#include +#include +#include + +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 -TelodendriaSignalHandler(int signal) +SignalHandler(int signal) { size_t i; @@ -57,6 +82,9 @@ TelodendriaSignalHandler(int signal) { case SIGPIPE: return; + case SIGUSR1: + restart = 1; + /* Fall through */ case SIGINT: if (!httpServers) { @@ -76,30 +104,26 @@ TelodendriaSignalHandler(int signal) typedef enum ArgFlag { ARG_VERSION = (1 << 0), - ARG_CONFIGTEST = (1 << 1), ARG_VERBOSE = (1 << 2) } ArgFlag; int main(int argc, char **argv) { - int exit = EXIT_SUCCESS; + int exit; /* Arg parsing */ int opt; - int flags = 0; - char *configArg = NULL; - - /* Config file */ - Stream *configFile = NULL; - HashMap *config = NULL; + int flags; + char *dbPath; /* Program configuration */ - Config *tConfig = NULL; + Config *tConfig; + Stream *logFile; /* User validation */ - struct passwd *userInfo = NULL; - struct group *groupInfo = NULL; + struct passwd *userInfo; + struct group *groupInfo; /* HTTP server management */ size_t i; @@ -109,7 +133,30 @@ main(int argc, char **argv) struct sigaction sigAction; 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)); @@ -121,12 +168,12 @@ main(int argc, char **argv) TelodendriaPrintHeader(); - while ((opt = getopt(argc, argv, "f:Vvn")) != -1) + while ((opt = getopt(argc, argv, "d:Vv")) != -1) { switch (opt) { - case 'f': - configArg = optarg; + case 'd': + dbPath = optarg; break; case 'V': flags |= ARG_VERSION; @@ -134,9 +181,6 @@ main(int argc, char **argv) case 'v': flags |= ARG_VERBOSE; break; - case 'n': - flags |= ARG_CONFIGTEST; - break; case '?': exit = EXIT_FAILURE; goto finish; @@ -156,27 +200,41 @@ main(int argc, char **argv) goto finish; } - if (!configArg) + if (!dbPath) { - Log(LOG_ERR, "No configuration file specified."); + Log(LOG_ERR, "No database directory specified."); exit = EXIT_FAILURE; 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 { - StreamClose(StreamStdin()); + Log(LOG_DEBUG, "Changed working directory to: %s", dbPath); + } - configFile = StreamOpen(configArg, "r"); - if (!configFile) - { - Log(LOG_ERR, "Unable to open configuration file '%s' for reading.", configArg); - exit = EXIT_FAILURE; - goto finish; - } + matrixArgs.db = DbOpen(".", 0); + if (!matrixArgs.db) + { + Log(LOG_ERR, "Unable to open data directory as a database."); + exit = EXIT_FAILURE; + goto finish; + } + else + { + Log(LOG_DEBUG, "Opened database."); } Log(LOG_NOTICE, "Building routing tree..."); @@ -188,30 +246,56 @@ main(int argc, char **argv) goto finish; } - Log(LOG_NOTICE, "Processing configuration file '%s'.", configArg); - - config = JsonDecode(configFile); - StreamClose(configFile); - - if (!config) + if (!ConfigExists(matrixArgs.db)) { - Log(LOG_ERR, "Syntax error in configuration file."); - exit = EXIT_FAILURE; - goto finish; + 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; + goto finish; + } + + token = StrRandom(32); + 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); } - tConfig = ConfigParse(config); - JsonFree(config); + Log(LOG_NOTICE, "Loading configuration..."); + tConfig = ConfigLock(matrixArgs.db); 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; goto finish; - } - - if (flags & ARG_CONFIGTEST) + } else if (!tConfig->ok) { - Log(LOG_INFO, "Configuration is OK."); + Log(LOG_ERR, tConfig->err); + exit = EXIT_FAILURE; goto finish; } @@ -236,20 +320,9 @@ main(int argc, char **argv) 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) { - Stream *logFile = StreamOpen("telodendria.log", "a"); + logFile = StreamOpen("telodendria.log", "a"); 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."); LogConfigOutputSet(LogConfigGlobal(), logFile); - StreamClose(StreamStdout()); } 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, "Identity Server: %s", tConfig->identityServer); 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, "Flags: %x", tConfig->flags); LogConfigUnindent(LogConfigGlobal()); - /* Arguments to pass into the HTTP handler */ - matrixArgs.config = tConfig; - httpServers = ArrayCreate(); if (!httpServers) { @@ -423,11 +491,9 @@ main(int argc, char **argv) /* These config values are no longer needed; don't hold them in * memory anymore */ - Free(tConfig->dataDir); Free(tConfig->uid); Free(tConfig->gid); - tConfig->dataDir = NULL; tConfig->uid = 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."); } - matrixArgs.db = DbOpen(".", tConfig->maxCache); + DbMaxCacheSet(matrixArgs.db, tConfig->maxCache); - if (!matrixArgs.db) - { - Log(LOG_ERR, "Unable to open data directory as a database."); - exit = EXIT_FAILURE; - goto finish; - } - else - { - Log(LOG_DEBUG, "Opened database."); - } + ConfigUnlock(tConfig); + tConfig = NULL; cron = CronCreate(60 * 1000); /* 1-minute tick */ if (!cron) @@ -489,7 +547,7 @@ main(int argc, char **argv) } - sigAction.sa_handler = TelodendriaSignalHandler; + sigAction.sa_handler = SignalHandler; sigfillset(&sigAction.sa_mask); sigAction.sa_flags = SA_RESTART; @@ -507,6 +565,7 @@ main(int argc, char **argv) SIGACTION(SIGINT, &sigAction, NULL); SIGACTION(SIGPIPE, &sigAction, NULL); + SIGACTION(SIGUSR1, &sigAction, NULL); #undef SIGACTION @@ -542,16 +601,15 @@ finish: Log(LOG_DEBUG, "Stopped and freed job scheduler."); } + ConfigUnlock(tConfig); + Log(LOG_DEBUG, "Unlocked configuration."); + DbClose(matrixArgs.db); Log(LOG_DEBUG, "Closed database."); HttpRouterFree(matrixArgs.router); 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 * API, whose configuration is being freed now, so it @@ -560,6 +618,19 @@ finish: MemoryHook(NULL, NULL); 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 * it was, this doesn't hurt anything. */ @@ -572,5 +643,6 @@ finish: /* Free any leaked memory now, just in case the operating system * we're running on won't do it for us. */ MemoryFreeAll(); + return exit; } diff --git a/src/RegToken.c b/src/RegToken.c index 9888ede..e2fcc40 100644 --- a/src/RegToken.c +++ b/src/RegToken.c @@ -203,7 +203,7 @@ RegTokenCreate(Db * db, char *name, char *owner, unsigned long expires, int uses unsigned long timestamp = UtilServerTs(); - if (!db || !name || !owner) + if (!db || !name) { return NULL; } diff --git a/src/Routes.c b/src/Routes.c index 88d39ab..ae4c6a6 100644 --- a/src/Routes.c +++ b/src/Routes.c @@ -67,6 +67,9 @@ RouterBuild(void) R("/_matrix/client/v3/profile/(.*)", RouteUserProfile); R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile); + R("/_telodendria/admin/(restart|shutdown|stats)", RouteProcControl); + R("/_telodendria/admin/config", RouteConfig); + #undef R return router; diff --git a/src/Routes/RouteChangePwd.c b/src/Routes/RouteChangePwd.c index bddeae4..1d647fc 100644 --- a/src/Routes/RouteChangePwd.c +++ b/src/Routes/RouteChangePwd.c @@ -65,45 +65,53 @@ ROUTE_IMPL(RouteChangePwd, path, argp) char *token; 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; if (HttpRequestMethodGet(args->context) != HTTP_POST) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_UNRECOGNIZED); + response = MatrixErrorCreate(M_UNRECOGNIZED); + goto finish; } response = MatrixGetAccessToken(args->context, &token); if (response) { - JsonFree(request); - return response; + goto finish; } request = JsonDecode(HttpServerStream(args->context)); if (!request) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_NOT_JSON); + response = MatrixErrorCreate(M_NOT_JSON); + goto finish; } uiaFlows = ArrayCreate(); ArrayAdd(uiaFlows, PasswordFlow()); uiaResult = UiaComplete(uiaFlows, args->context, - args->matrixArgs->db, request, &response, - args->matrixArgs->config); + db, request, &response, + config); UiaFlowsFree(uiaFlows); if (uiaResult < 0) { - JsonFree(request); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); - return MatrixErrorCreate(M_UNKNOWN); + response = MatrixErrorCreate(M_UNKNOWN); + goto finish; } else if (!uiaResult) { - JsonFree(request); - return response; + goto finish; } newPassword = JsonValueAsString(HashMapGet(request, "new_password")); @@ -111,7 +119,8 @@ ROUTE_IMPL(RouteChangePwd, path, argp) { JsonFree(request); HttpResponseStatus(args->context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_BAD_JSON); + response = MatrixErrorCreate(M_BAD_JSON); + goto finish; } val = HashMapGet(request, "logout_devices"); @@ -125,9 +134,9 @@ ROUTE_IMPL(RouteChangePwd, path, argp) if (!user) { - JsonFree(request); HttpResponseStatus(args->context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_UNKNOWN_TOKEN); + response = MatrixErrorCreate(M_UNKNOWN_TOKEN); + goto finish; } UserSetPassword(user, newPassword); @@ -139,8 +148,11 @@ ROUTE_IMPL(RouteChangePwd, path, argp) UserDeleteTokens(user, token); } + response = HashMapCreate(); + +finish: + ConfigUnlock(config); UserUnlock(user); JsonFree(request); - response = HashMapCreate(); return response; } diff --git a/src/Routes/RouteConfig.c b/src/Routes/RouteConfig.c new file mode 100644 index 0000000..767a2b1 --- /dev/null +++ b/src/Routes/RouteConfig.c @@ -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 + +#include +#include +#include + +#include + +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; +} diff --git a/src/Routes/RouteLogin.c b/src/Routes/RouteLogin.c index 6acb0cb..f07b668 100644 --- a/src/Routes/RouteLogin.c +++ b/src/Routes/RouteLogin.c @@ -57,6 +57,14 @@ ROUTE_IMPL(RouteLogin, path, argp) UserLoginInfo *loginInfo; 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; switch (HttpRequestMethodGet(args->context)) @@ -157,8 +165,7 @@ ROUTE_IMPL(RouteLogin, path, argp) break; } - userId = UserIdParse(JsonValueAsString(val), - args->matrixArgs->config->serverName); + userId = UserIdParse(JsonValueAsString(val), config->serverName); if (!userId) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); @@ -166,7 +173,7 @@ ROUTE_IMPL(RouteLogin, path, argp) break; } - if (strcmp(userId->server, args->matrixArgs->config->serverName) != 0 + if (strcmp(userId->server, config->serverName) != 0 || !UserExists(db, userId->localpart)) { HttpResponseStatus(args->context, HTTP_FORBIDDEN); @@ -276,14 +283,13 @@ ROUTE_IMPL(RouteLogin, path, argp) } fullUsername = StrConcat(4, "@", UserGetName(user), ":", - args->matrixArgs->config->serverName); + config->serverName); HashMapSet(response, "user_id", JsonValueString(fullUsername)); Free(fullUsername); HashMapSet(response, "well_known", JsonValueObject( - MatrixClientWellKnown(args->matrixArgs->config->baseUrl, - args->matrixArgs->config->identityServer))); + MatrixClientWellKnown(config->baseUrl, config->identityServer))); UserAccessTokenFree(loginInfo->accessToken); Free(loginInfo->refreshToken); @@ -300,5 +306,6 @@ ROUTE_IMPL(RouteLogin, path, argp) UserIdFree(userId); JsonFree(request); + ConfigUnlock(config); return response; } diff --git a/src/Routes/RouteProcControl.c b/src/Routes/RouteProcControl.c new file mode 100644 index 0000000..b9a85b1 --- /dev/null +++ b/src/Routes/RouteProcControl.c @@ -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 + +#include +#include +#include + +#include + +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; +} diff --git a/src/Routes/RouteRegister.c b/src/Routes/RouteRegister.c index e93ac44..6b84573 100644 --- a/src/Routes/RouteRegister.c +++ b/src/Routes/RouteRegister.c @@ -77,19 +77,29 @@ ROUTE_IMPL(RouteRegister, path, argp) char *session; 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 (HttpRequestMethodGet(args->context) != HTTP_POST) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_UNRECOGNIZED); + response = MatrixErrorCreate(M_UNRECOGNIZED); + goto end; } request = JsonDecode(HttpServerStream(args->context)); if (!request) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_NOT_JSON); + response = MatrixErrorCreate(M_NOT_JSON); + goto end; } val = HashMapGet(request, "username"); @@ -103,7 +113,7 @@ ROUTE_IMPL(RouteRegister, path, argp) } username = StrDuplicate(JsonValueAsString(val)); - if (!UserValidate(username, args->matrixArgs->config->serverName)) + if (!UserValidate(username, config->serverName)) { HttpResponseStatus(args->context, HTTP_BAD_REQUEST); response = MatrixErrorCreate(M_INVALID_USERNAME); @@ -121,14 +131,14 @@ ROUTE_IMPL(RouteRegister, path, argp) uiaFlows = ArrayCreate(); ArrayAdd(uiaFlows, RouteRegisterRegFlow()); - if (args->matrixArgs->config->flags & CONFIG_REGISTRATION) + if (config->flags & CONFIG_REGISTRATION) { ArrayAdd(uiaFlows, UiaDummyFlow()); } uiaResult = UiaComplete(uiaFlows, args->context, - args->matrixArgs->db, request, &response, - args->matrixArgs->config); + db, request, &response, + config); if (uiaResult < 0) { @@ -224,7 +234,7 @@ ROUTE_IMPL(RouteRegister, path, argp) user = UserCreate(db, username, password); response = HashMapCreate(); - fullUsername = StrConcat(4, "@", UserGetName(user), ":", args->matrixArgs->config->serverName); + fullUsername = StrConcat(4, "@", UserGetName(user), ":", config->serverName); HashMapSet(response, "user_id", JsonValueString(fullUsername)); Free(fullUsername); @@ -302,7 +312,7 @@ finish: HttpResponseStatus(args->context, HTTP_BAD_REQUEST); 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); response = MatrixErrorCreate(M_INVALID_USERNAME); @@ -325,5 +335,7 @@ finish: } } +end: + ConfigUnlock(config); return response; } diff --git a/src/Routes/RouteUserProfile.c b/src/Routes/RouteUserProfile.c index 62de088..fe473a5 100644 --- a/src/Routes/RouteUserProfile.c +++ b/src/Routes/RouteUserProfile.c @@ -42,12 +42,22 @@ ROUTE_IMPL(RouteUserProfile, path, argp) UserId *userId = NULL; User *user = NULL; - char *serverName = args->matrixArgs->config->serverName; + char *serverName; char *username = NULL; char *entry = NULL; char *token = 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); userId = UserIdParse(username, serverName); if (!userId) @@ -160,6 +170,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp) break; } finish: + ConfigUnlock(config); Free(username); Free(entry); UserIdFree(userId); diff --git a/src/Routes/RouteWellKnown.c b/src/Routes/RouteWellKnown.c index 26cda71..cee62f2 100644 --- a/src/Routes/RouteWellKnown.c +++ b/src/Routes/RouteWellKnown.c @@ -33,15 +33,26 @@ ROUTE_IMPL(RouteWellKnown, path, 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")) { - return MatrixClientWellKnown(args->matrixArgs->config->baseUrl, - args->matrixArgs->config->identityServer); + response = MatrixClientWellKnown(config->baseUrl, config->identityServer); } else { HttpResponseStatus(args->context, HTTP_NOT_FOUND); - return MatrixErrorCreate(M_NOT_FOUND); + response = MatrixErrorCreate(M_NOT_FOUND); } + + ConfigUnlock(config); + return response; } diff --git a/src/Routes/RouteWhoami.c b/src/Routes/RouteWhoami.c index 6bd57fc..3967db4 100644 --- a/src/Routes/RouteWhoami.c +++ b/src/Routes/RouteWhoami.c @@ -45,6 +45,14 @@ ROUTE_IMPL(RouteWhoami, path, argp) char *userID; 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; /* Get the request */ @@ -52,14 +60,15 @@ ROUTE_IMPL(RouteWhoami, path, argp) if (response) { /* No token? */ - return response; + goto finish; } /* Authenticate with our token */ if (!DbExists(db, 3, "tokens", "access", token)) { HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); - return MatrixErrorCreate(M_UNKNOWN_TOKEN); + response = MatrixErrorCreate(M_UNKNOWN_TOKEN); + goto finish; } ref = DbLock(db, 3, "tokens", "access", token); @@ -69,7 +78,7 @@ ROUTE_IMPL(RouteWhoami, path, argp) userID = StrConcat(4, "@", JsonValueAsString(HashMapGet(tokenJson, "user")), - ":", args->matrixArgs->config->serverName); + ":", config->serverName); deviceID = StrDuplicate(JsonValueAsString(HashMapGet(tokenJson, "device"))); @@ -80,5 +89,8 @@ ROUTE_IMPL(RouteWhoami, path, argp) Free(userID); Free(deviceID); + +finish: + ConfigUnlock(config); return response; } diff --git a/src/Str.c b/src/Str.c index 0d07c82..9afcf96 100644 --- a/src/Str.c +++ b/src/Str.c @@ -89,6 +89,11 @@ StrDuplicate(const char *inStr) size_t len; char *outStr; + if (!inStr) + { + return NULL; + } + len = strlen(inStr); outStr = Malloc(len + 1); /* For the null terminator */ if (!outStr) diff --git a/src/Telodendria.c b/src/Telodendria.c index d504af6..68db2a9 100644 --- a/src/Telodendria.c +++ b/src/Telodendria.c @@ -197,3 +197,4 @@ TelodendriaPrintHeader(void) "Documentation/Support: https://telodendria.io"); Log(LOG_INFO, ""); } + diff --git a/src/User.c b/src/User.c index d42edbb..153a684 100644 --- a/src/User.c +++ b/src/User.c @@ -784,6 +784,10 @@ UserDecodePrivilege(const char *p) { return USER_GRANT_PRIVILEGES; } + else if (strcmp(p, "PROC_CONTROL") == 0) + { + return USER_PROC_CONTROL; + } else { return USER_NONE; @@ -816,6 +820,7 @@ UserEncodePrivileges(int privileges) A(USER_ISSUE_TOKENS, "ISSUE_TOKENS"); A(USER_CONFIG, "CONFIG"); A(USER_GRANT_PRIVILEGES, "GRANT_PRIVILEGES"); + A(USER_PROC_CONTROL, "PROC_CONTROL"); #undef A diff --git a/src/include/Config.h b/src/include/Config.h index 83cfb43..06a335a 100644 --- a/src/include/Config.h +++ b/src/include/Config.h @@ -25,9 +25,9 @@ #ifndef TELODENDRIA_CONFIG_H #define TELODENDRIA_CONFIG_H -#include #include #include +#include typedef enum ConfigFlag { @@ -41,13 +41,15 @@ typedef enum ConfigFlag typedef struct Config { + Db *db; + DbRef *ref; + char *serverName; char *baseUrl; char *identityServer; char *uid; char *gid; - char *dataDir; unsigned int flags; @@ -57,12 +59,27 @@ typedef struct Config int logLevel; Array *servers; + + int ok; + char *err; } Config; -extern Config * - ConfigParse(HashMap *); +Config * +ConfigParse(HashMap *); -extern void - ConfigFree(Config *); +void +ConfigFree(Config *); + +extern int +ConfigExists(Db *); + +extern int +ConfigCreateDefault(Db *); + +extern Config * +ConfigLock(Db *); + +extern int +ConfigUnlock(Config *); #endif /* TELODENDRIA_CONFIG_H */ diff --git a/src/include/Db.h b/src/include/Db.h index 611b303..e0fc9cc 100644 --- a/src/include/Db.h +++ b/src/include/Db.h @@ -35,6 +35,9 @@ typedef struct DbRef DbRef; extern Db * DbOpen(char *, size_t); +extern void +DbMaxCacheSet(Db *, size_t); + extern void DbClose(Db *); @@ -62,4 +65,7 @@ extern void extern HashMap * DbJson(DbRef *); +extern int + DbJsonSet(DbRef *, HashMap *); + #endif diff --git a/src/include/Json.h b/src/include/Json.h index dfedba5..c76dea2 100644 --- a/src/include/Json.h +++ b/src/include/Json.h @@ -93,9 +93,16 @@ extern JsonValue * extern void JsonValueFree(JsonValue *); +extern JsonValue * + JsonValueDuplicate(JsonValue *); + +extern HashMap * + JsonDuplicate(HashMap *); + extern void JsonFree(HashMap *); + extern void JsonEncodeString(const char *, Stream *); diff --git a/src/include/Main.h b/src/include/Main.h new file mode 100644 index 0000000..5e8676c --- /dev/null +++ b/src/include/Main.h @@ -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 */ diff --git a/src/include/Matrix.h b/src/include/Matrix.h index 59bfefe..b85538a 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -70,7 +70,6 @@ typedef enum MatrixError typedef struct MatrixHttpHandlerArgs { - Config *config; Db *db; HttpRouter *router; } MatrixHttpHandlerArgs; diff --git a/src/include/Routes.h b/src/include/Routes.h index 9d2f7ad..95bbe88 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -69,6 +69,9 @@ ROUTE(RouteUiaFallback); ROUTE(RouteStaticDefault); ROUTE(RouteStaticLogin); +ROUTE(RouteProcControl); +ROUTE(RouteConfig); + #undef ROUTE #endif diff --git a/src/include/User.h b/src/include/User.h index b121b51..c5ee471 100644 --- a/src/include/User.h +++ b/src/include/User.h @@ -32,9 +32,10 @@ #define USER_ISSUE_TOKENS (1 << 1) #define USER_CONFIG (1 << 2) #define USER_GRANT_PRIVILEGES (1 << 3) +#define USER_PROC_CONTROL (1 << 4) #define USER_NONE 0 -#define USER_ALL ((1 << 4) - 1) +#define USER_ALL ((1 << 5) - 1) typedef struct User User; diff --git a/tools/bin/td b/tools/bin/td index bb710d6..4fde88d 100644 --- a/tools/bin/td +++ b/tools/bin/td @@ -158,10 +158,9 @@ recipe_build() { recipe_run() { 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 "$dataDir/Memory.txt" ]; then + if [ -f "data/Memory.txt" ]; then echo "WARNING: Memory.txt exists in the data directory; this means" echo "Telodendria is leaking memory. Please fix memory leaks." fi diff --git a/tools/bin/tt b/tools/bin/tt index 4833624..9b525b4 100755 --- a/tools/bin/tt +++ b/tools/bin/tt @@ -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") if [ -z "$ACCESS_TOKEN" ]; then + echo "Failed to log in." echo "$RESPONSE" | json exit 1 fi