forked from Telodendria/Telodendria
Move configuration to database, add process control API, fix memory leaks.
This commit is contained in:
parent
ff4d265dcc
commit
0cca38115a
30 changed files with 830 additions and 213 deletions
13
TODO.txt
13
TODO.txt
|
@ -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)
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
"timestampFormat": "none",
|
"timestampFormat": "none",
|
||||||
"level": "debug"
|
"level": "debug"
|
||||||
},
|
},
|
||||||
"dataDir": "./data",
|
|
||||||
"listen": [
|
"listen": [
|
||||||
{
|
{
|
||||||
"port": 8008,
|
"port": 8008,
|
||||||
|
|
213
src/Config.c
213
src/Config.c
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
38
src/Db.c
38
src/Db.c
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
78
src/Json.c
78
src/Json.c
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -122,7 +122,6 @@ LogConfigFree(LogConfig * config)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamClose(config->out);
|
|
||||||
Free(config);
|
Free(config);
|
||||||
|
|
||||||
if (config == globalConfig)
|
if (config == globalConfig)
|
||||||
|
|
228
src/Main.c
228
src/Main.c
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
137
src/Routes/RouteConfig.c
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
92
src/Routes/RouteProcControl.c
Normal file
92
src/Routes/RouteProcControl.c
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -197,3 +197,4 @@ TelodendriaPrintHeader(void)
|
||||||
"Documentation/Support: https://telodendria.io");
|
"Documentation/Support: https://telodendria.io");
|
||||||
Log(LOG_INFO, "");
|
Log(LOG_INFO, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
16
src/include/Main.h
Normal 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 */
|
|
@ -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;
|
||||||
|
|
|
@ -69,6 +69,9 @@ ROUTE(RouteUiaFallback);
|
||||||
ROUTE(RouteStaticDefault);
|
ROUTE(RouteStaticDefault);
|
||||||
ROUTE(RouteStaticLogin);
|
ROUTE(RouteStaticLogin);
|
||||||
|
|
||||||
|
ROUTE(RouteProcControl);
|
||||||
|
ROUTE(RouteConfig);
|
||||||
|
|
||||||
#undef ROUTE
|
#undef ROUTE
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue