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
[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)

View file

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

View file

@ -25,31 +25,32 @@
#include <Memory.h>
#include <Json.h>
#include <HashMap.h>
#include <Log.h>
#include <Array.h>
#include <Str.h>
#include <Db.h>
#include <HttpServer.h>
#include <Log.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#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,6 +286,42 @@ 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)
{
@ -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);
tConfig->ok = 0;
return tConfig;
}
int
ConfigExists(Db *db)
{
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;
}
void
ConfigFree(Config * tConfig)
config = ConfigParse(DbJson(ref));
if (config)
{
if (!tConfig)
{
return;
config->db = db;
config->ref = ref;
}
Free(tConfig->serverName);
Free(tConfig->baseUrl);
Free(tConfig->identityServer);
return config;
}
Free(tConfig->uid);
Free(tConfig->gid);
Free(tConfig->dataDir);
Free(tConfig->logTimestamp);
if (tConfig->servers)
int
ConfigUnlock(Config *config)
{
size_t i;
Db *db;
DbRef *dbRef;
for (i = 0; i < ArraySize(tConfig->servers); i++)
if (!config)
{
HttpServerConfig *serverCfg = ArrayGet(tConfig->servers, i);
Free(serverCfg->tlsCert);
Free(serverCfg->tlsKey);
Free(serverCfg);
return 0;
}
ArrayFree(tConfig->servers);
}
db = config->db;
dbRef = config->ref;
Free(tConfig);
ConfigFree(config);
return DbUnlock(db, dbRef);
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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)
{

View file

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

View file

@ -28,6 +28,7 @@
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <limits.h>
#include <grp.h>
#include <pwd.h>
@ -39,17 +40,41 @@
#include <HashMap.h>
#include <Json.h>
#include <HttpServer.h>
#include <Routes.h>
#include <Matrix.h>
#include <Db.h>
#include <Cron.h>
#include <Uia.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
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)
matrixArgs.db = DbOpen(".", 0);
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;
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.");
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;
}
tConfig = ConfigParse(config);
JsonFree(config);
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);
}
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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

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;
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;
}

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;
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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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)

View file

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

View file

@ -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

View file

@ -25,9 +25,9 @@
#ifndef TELODENDRIA_CONFIG_H
#define TELODENDRIA_CONFIG_H
#include <Log.h>
#include <HashMap.h>
#include <Array.h>
#include <Db.h>
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 *
Config *
ConfigParse(HashMap *);
extern void
void
ConfigFree(Config *);
extern int
ConfigExists(Db *);
extern int
ConfigCreateDefault(Db *);
extern Config *
ConfigLock(Db *);
extern int
ConfigUnlock(Config *);
#endif /* TELODENDRIA_CONFIG_H */

View file

@ -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

View file

@ -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 *);

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
{
Config *config;
Db *db;
HttpRouter *router;
} MatrixHttpHandlerArgs;

View file

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

View file

@ -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;

View file

@ -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

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")
if [ -z "$ACCESS_TOKEN" ]; then
echo "Failed to log in."
echo "$RESPONSE" | json
exit 1
fi