forked from Telodendria/Telodendria
421 lines
12 KiB
C
421 lines
12 KiB
C
/*
|
|
* Copyright (C) 2022 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 <TelodendriaConfig.h>
|
|
#include <Memory.h>
|
|
#include <Config.h>
|
|
#include <HashMap.h>
|
|
#include <Log.h>
|
|
#include <Array.h>
|
|
#include <Util.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
static int
|
|
IsInteger(char *str)
|
|
{
|
|
while (*str)
|
|
{
|
|
if (!isdigit((unsigned char) *str))
|
|
{
|
|
return 0;
|
|
}
|
|
str++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#define GET_DIRECTIVE(name) \
|
|
directive = (ConfigDirective *) HashMapGet(config, name); \
|
|
if (!directive) { \
|
|
Log(lc, LOG_ERR, "Missing required configuration directive: '%s'.", name); \
|
|
goto error; \
|
|
} \
|
|
children = ConfigChildrenGet(directive); \
|
|
value = ConfigValuesGet(directive); \
|
|
|
|
#define ASSERT_NO_CHILDREN(name) if (children) { \
|
|
Log(lc, LOG_ERR, "Unexpected child values in directive: '%s'.", name); \
|
|
goto error; \
|
|
}
|
|
|
|
#define ASSERT_VALUES(name, expected) if (ArraySize(value) != expected) { \
|
|
Log(lc, LOG_ERR, \
|
|
"Wrong value count in directive '%s': got '%d', but expected '%d'.", \
|
|
name, ArraySize(value), expected); \
|
|
goto error; \
|
|
}
|
|
|
|
#define COPY_VALUE(into, index) into = UtilStringDuplicate(ArrayGet(value, index))
|
|
|
|
TelodendriaConfig *
|
|
TelodendriaConfigParse(HashMap * config, LogConfig * lc)
|
|
{
|
|
TelodendriaConfig *tConfig;
|
|
|
|
ConfigDirective *directive;
|
|
Array *value;
|
|
HashMap *children;
|
|
|
|
if (!config || !lc)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
tConfig = Malloc(sizeof(TelodendriaConfig));
|
|
if (!tConfig)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
memset(tConfig, 0, sizeof(TelodendriaConfig));
|
|
|
|
directive = (ConfigDirective *) HashMapGet(config, "listen");
|
|
children = ConfigChildrenGet(directive);
|
|
value = ConfigValuesGet(directive);
|
|
|
|
if (!directive)
|
|
{
|
|
tConfig->listenPort = 8008;
|
|
}
|
|
else
|
|
{
|
|
ASSERT_NO_CHILDREN("listen");
|
|
ASSERT_VALUES("listen", 1);
|
|
|
|
tConfig->listenPort = (unsigned short) atoi(ArrayGet(value, 0));
|
|
if (!tConfig->listenPort)
|
|
{
|
|
Log(lc, LOG_ERR, "Expected numeric value for listen port, got '%s'.", ArrayGet(value, 1));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
GET_DIRECTIVE("server-name");
|
|
ASSERT_NO_CHILDREN("server-name");
|
|
ASSERT_VALUES("server-name", 1);
|
|
COPY_VALUE(tConfig->serverName, 0);
|
|
|
|
directive = (ConfigDirective *) HashMapGet(config, "base-url");
|
|
children = ConfigChildrenGet(directive);
|
|
value = ConfigValuesGet(directive);
|
|
|
|
if (directive)
|
|
{
|
|
ASSERT_NO_CHILDREN("base-url");
|
|
ASSERT_VALUES("base-url", 1);
|
|
COPY_VALUE(tConfig->baseUrl, 0);
|
|
}
|
|
else
|
|
{
|
|
Log(lc, LOG_WARNING, "Base URL not specified. Assuming it's 'https://%s'.", tConfig->serverName);
|
|
tConfig->baseUrl = Malloc(strlen(tConfig->serverName) + 10);
|
|
if (!tConfig->baseUrl)
|
|
{
|
|
Log(lc, LOG_ERR, "Error allocating memory for default config value 'base-url'.");
|
|
goto error;
|
|
}
|
|
|
|
sprintf(tConfig->baseUrl, "https://%s", tConfig->serverName);
|
|
}
|
|
|
|
directive = (ConfigDirective *) HashMapGet(config, "identity-server");
|
|
children = ConfigChildrenGet(directive);
|
|
value = ConfigValuesGet(directive);
|
|
|
|
if (directive)
|
|
{
|
|
ASSERT_NO_CHILDREN("identity-server");
|
|
ASSERT_VALUES("identity-server", 1);
|
|
COPY_VALUE(tConfig->identityServer, 0);
|
|
}
|
|
else
|
|
{
|
|
Log(lc, LOG_WARNING, "Identity server not specified. No identity server will be advertised.");
|
|
tConfig->identityServer = NULL;
|
|
}
|
|
|
|
directive = (ConfigDirective *) HashMapGet(config, "id");
|
|
children = ConfigChildrenGet(directive);
|
|
value = ConfigValuesGet(directive);
|
|
|
|
ASSERT_NO_CHILDREN("id");
|
|
|
|
if (directive)
|
|
{
|
|
|
|
switch (ArraySize(value))
|
|
{
|
|
case 1:
|
|
Log(lc, LOG_WARNING, "No run group specified; assuming it's the same as the user.");
|
|
COPY_VALUE(tConfig->uid, 0);
|
|
tConfig->gid = UtilStringDuplicate(tConfig->uid);
|
|
break;
|
|
case 2:
|
|
COPY_VALUE(tConfig->uid, 0);
|
|
COPY_VALUE(tConfig->gid, 1);
|
|
break;
|
|
default:
|
|
Log(lc, LOG_ERR,
|
|
"Wrong value count in directive 'id': got '%d', but expected 1 or 2.",
|
|
ArraySize(value));
|
|
goto error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tConfig->uid = NULL;
|
|
tConfig->gid = NULL;
|
|
}
|
|
|
|
GET_DIRECTIVE("data-dir");
|
|
ASSERT_NO_CHILDREN("data-dir");
|
|
ASSERT_VALUES("data-dir", 1);
|
|
COPY_VALUE(tConfig->dataDir, 0);
|
|
|
|
GET_DIRECTIVE("threads");
|
|
ASSERT_NO_CHILDREN("threads");
|
|
ASSERT_VALUES("threads", 1);
|
|
|
|
if (IsInteger(ArrayGet(value, 0)))
|
|
{
|
|
tConfig->threads = atoi(ArrayGet(value, 0));
|
|
if (!tConfig->threads)
|
|
{
|
|
Log(lc, LOG_ERR, "threads must be greater than zero");
|
|
goto error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log(lc, LOG_ERR,
|
|
"Expected integer for directive 'threads', "
|
|
"but got '%s'.", ArrayGet(value, 0));
|
|
goto error;
|
|
}
|
|
|
|
directive = (ConfigDirective *) HashMapGet(config, "max-connections");
|
|
if (!directive)
|
|
{
|
|
Log(lc, LOG_WARNING, "max-connections not specified; using defaults, which may change");
|
|
tConfig->maxConnections = 32;
|
|
}
|
|
else
|
|
{
|
|
ASSERT_NO_CHILDREN("max-connections");
|
|
ASSERT_VALUES("max-connections", 1);
|
|
if (IsInteger(ArrayGet(value, 0)))
|
|
{
|
|
tConfig->maxConnections = atoi(ArrayGet(value, 0));
|
|
if (!tConfig->maxConnections)
|
|
{
|
|
Log(lc, LOG_ERR, "max-connections must be greater than zero.");
|
|
goto error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log(lc, LOG_ERR, "Expected integer for max-connections, got '%s'", ArrayGet(value, 0));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
GET_DIRECTIVE("max-cache");
|
|
ASSERT_NO_CHILDREN("max-cache");
|
|
ASSERT_VALUES("max-cache", 1);
|
|
tConfig->maxCache = UtilStringToBytes(ArrayGet(value, 0));
|
|
|
|
GET_DIRECTIVE("federation");
|
|
ASSERT_NO_CHILDREN("federation");
|
|
ASSERT_VALUES("federation", 1);
|
|
|
|
if (strcmp(ArrayGet(value, 0), "true") == 0)
|
|
{
|
|
tConfig->flags |= TELODENDRIA_FEDERATION;
|
|
}
|
|
else if (strcmp(ArrayGet(value, 0), "false") != 0)
|
|
{
|
|
Log(lc, LOG_ERR,
|
|
"Expected boolean value for directive 'federation', "
|
|
"but got '%s'.", ArrayGet(value, 0));
|
|
goto error;
|
|
}
|
|
|
|
GET_DIRECTIVE("registration");
|
|
ASSERT_NO_CHILDREN("registration");
|
|
ASSERT_VALUES("registration", 1);
|
|
if (strcmp(ArrayGet(value, 0), "true") == 0)
|
|
{
|
|
tConfig->flags |= TELODENDRIA_REGISTRATION;
|
|
}
|
|
else if (strcmp(ArrayGet(value, 0), "false") != 0)
|
|
{
|
|
Log(lc, LOG_ERR,
|
|
"Expected boolean value for directive 'registration', "
|
|
"but got '%s'.", ArrayGet(value, 0));
|
|
goto error;
|
|
}
|
|
|
|
GET_DIRECTIVE("log");
|
|
ASSERT_VALUES("log", 1);
|
|
|
|
if (children)
|
|
{
|
|
ConfigDirective *cDirective;
|
|
char *cVal;
|
|
size_t size;
|
|
|
|
cDirective = HashMapGet(children, "level");
|
|
if (cDirective)
|
|
{
|
|
size = ArraySize(ConfigValuesGet(cDirective));
|
|
if (size > 1)
|
|
{
|
|
Log(lc, LOG_ERR, "Expected 1 value for log.level, got %d.", size);
|
|
goto error;
|
|
}
|
|
|
|
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
|
|
if (strcmp(cVal, "message") == 0)
|
|
{
|
|
tConfig->logLevel = LOG_INFO;
|
|
}
|
|
else if (strcmp(cVal, "debug") == 0)
|
|
{
|
|
tConfig->logLevel = LOG_DEBUG;
|
|
}
|
|
else if (strcmp(cVal, "task") == 0)
|
|
{
|
|
tConfig->logLevel = LOG_NOTICE;
|
|
}
|
|
else if (strcmp(cVal, "warning") == 0)
|
|
{
|
|
tConfig->logLevel = LOG_WARNING;
|
|
}
|
|
else if (strcmp(cVal, "error") == 0)
|
|
{
|
|
tConfig->logLevel = LOG_ERR;
|
|
}
|
|
else
|
|
{
|
|
Log(lc, LOG_ERR, "Invalid value for log.level: '%s'.", cVal);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
cDirective = HashMapGet(children, "timestampFormat");
|
|
if (cDirective)
|
|
{
|
|
size = ArraySize(ConfigValuesGet(cDirective));
|
|
if (size > 1)
|
|
{
|
|
Log(lc, LOG_ERR, "Expected 1 value for log.level, got %d.", size);
|
|
goto error;
|
|
}
|
|
|
|
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
|
|
|
|
if (strcmp(cVal, "none") == 0)
|
|
{
|
|
tConfig->logTimestamp = NULL;
|
|
}
|
|
else if (strcmp(cVal, "default") != 0)
|
|
{
|
|
tConfig->logTimestamp = UtilStringDuplicate(cVal);
|
|
}
|
|
}
|
|
|
|
cDirective = HashMapGet(children, "color");
|
|
if (cDirective)
|
|
{
|
|
size = ArraySize(ConfigValuesGet(cDirective));
|
|
if (size > 1)
|
|
{
|
|
Log(lc, LOG_ERR, "Expected 1 value for log.level, got %d.", size);
|
|
goto error;
|
|
}
|
|
|
|
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
|
|
|
|
if (strcmp(cVal, "true") == 0)
|
|
{
|
|
tConfig->flags |= TELODENDRIA_LOG_COLOR;
|
|
}
|
|
else if (strcmp(cVal, "false") != 0)
|
|
{
|
|
Log(lc, LOG_ERR, "Expected boolean value for log.color, got '%s'.", cVal);
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set the actual log output file last */
|
|
if (strcmp(ArrayGet(value, 0), "stdout") == 0)
|
|
{
|
|
tConfig->flags |= TELODENDRIA_LOG_STDOUT;
|
|
}
|
|
else if (strcmp(ArrayGet(value, 0), "file") == 0)
|
|
{
|
|
tConfig->flags |= TELODENDRIA_LOG_FILE;
|
|
}
|
|
else if (strcmp(ArrayGet(value, 0), "syslog") == 0)
|
|
{
|
|
tConfig->flags |= TELODENDRIA_LOG_SYSLOG;
|
|
}
|
|
else
|
|
{
|
|
Log(lc, LOG_ERR, "Unknown log value '%s', expected 'stdout', 'file', or 'syslog'.",
|
|
ArrayGet(value, 0));
|
|
goto error;
|
|
}
|
|
|
|
return tConfig;
|
|
error:
|
|
TelodendriaConfigFree(tConfig);
|
|
return NULL;
|
|
}
|
|
|
|
#undef GET_DIRECTIVE
|
|
#undef ASSERT_NO_CHILDREN
|
|
#undef ASSERT_VALUES
|
|
|
|
void
|
|
TelodendriaConfigFree(TelodendriaConfig * tConfig)
|
|
{
|
|
if (!tConfig)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Free(tConfig->serverName);
|
|
Free(tConfig->baseUrl);
|
|
Free(tConfig->identityServer);
|
|
|
|
Free(tConfig->uid);
|
|
Free(tConfig->gid);
|
|
Free(tConfig->dataDir);
|
|
|
|
Free(tConfig);
|
|
}
|