Prototype the configuration file parser.

Right now there's a nasty memory bug I need to fix. Will have to run this
through valgrind.
This commit is contained in:
Jordan Bancino 2022-08-09 20:05:41 -04:00
parent 0a101f0853
commit cdd7808642
9 changed files with 515 additions and 32 deletions

View file

@ -1,2 +1,4 @@
build
.env
*.log
vgcore.*

13
contrib/development.conf Normal file
View file

@ -0,0 +1,13 @@
server-name "example.com";
chroot "/var/telodendria";
id "_telodendria" "_telodendria";
data-dir "./data";
federation "true";
registration "false";
log "stdout" {
level "debug";
timestampFormat "none";
color "true";
};
threads "4";

View file

@ -13,18 +13,20 @@
# should go through and check.
#
# The address to listen on. You can specify multiple addresses by
# simply adding more values to this directive. It is recommended
# to only listen on localhost, and then configure a reverse proxy
# such as relayd(8) in front of it, because the server does not
# implement TLS.
# The address to listen on. It is recommended to listen on localhost,
# and then configure a reverse proxy # such as relayd(8) in front of
# it, because the server does not implement TLS.
#
# Also note that Telodendria doesn't provide multiple ports for
# different things. All APIs are made available over the same port.
# This works because Matrix allows the port configuration to be
# shared via .well-known/matrix/, which this server does properly
# serve.
listen "localhost:8008";
#
# The first parameter is the host name or IP address to listen on,
# and the second parameter is the port name or number. See the
# getaddrinfo() manual page for more information.
listen "localhost" "8008";
# Configure the domain name of your homeserver. Note that Matrix
# servers cannot be migrated to other domains, so once this is set,
@ -55,7 +57,7 @@ id "_telodendria" "_telodendria";
# event information. Telodendria doesn't use a database; it uses a
# flat-file directory structure, sort of like how most SMTP servers
# use Maildirs or mbox files.
data-dir "/data";
data-dir "./data";
# Whether to enable federation or not. Matrix is by default
# a federated protocol, but if you just want your own internal chat
@ -90,8 +92,8 @@ registration "false";
# configures the log file. If you're going to be running Telodendria
# in a chroot, the log file will have to live inside the chroot.
#
# Acceptable values here are "stdout", "stderr", or a log file.
log "/telodendria.log" {
# Acceptable values here are "stdout" or a log file.
log "./telodendria.log" {
# The level to log. This can be one of "error", "warning",
# "task", "message", or "debug", with each level showing all
# the levels above it as well. For example, "error" shows

View file

@ -100,6 +100,12 @@ LogConfigFlagSet(LogConfig * config, int flags)
void
LogConfigFree(LogConfig * config)
{
if (!config)
{
return;
}
fclose(config->out);
free(config);
}
@ -218,10 +224,6 @@ Log(LogConfig * config, LogLevel level, const char *msg,...)
pthread_mutex_lock(&config->lock);
for (i = 0; i < config->indent; i++)
{
fputc(' ', config->out);
}
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
&& isatty(fileno(config->out));
@ -312,6 +314,10 @@ Log(LogConfig * config, LogLevel level, const char *msg,...)
}
fputc(' ', config->out);
for (i = 0; i < config->indent; i++)
{
fputc(' ', config->out);
}
va_start(argp, msg);
vfprintf(config->out, msg, argp);

View file

@ -26,6 +26,8 @@
#include <string.h>
#include <unistd.h>
#include <TelodendriaConfig.h>
#include <Log.h>
#include <HashMap.h>
#include <Config.h>
@ -83,10 +85,10 @@ main(int argc, char **argv)
ConfigParseResult *configParseResult = NULL;
HashMap *config = NULL;
lc = LogConfigCreate();
/* Program configuration */
TelodendriaConfig *tConfig = NULL;
/* TODO: Remove */
LogConfigLevelSet(lc, LOG_DEBUG);
lc = LogConfigCreate();
if (!lc)
{
@ -147,15 +149,9 @@ main(int argc, char **argv)
}
}
Log(lc, LOG_MESSAGE, "Using configuration file '%s'.", configArg);
Log(lc, LOG_TASK, "Processing configuration file '%s'.", configArg);
Log(lc, LOG_DEBUG, "Executing ConfigParse()");
/* Read config here */
configParseResult = ConfigParse(configFile);
Log(lc, LOG_DEBUG, "Exitting ConfigParse()");
if (!ConfigParseResultOk(configParseResult))
{
Log(lc, LOG_ERROR, "Syntax error on line %d.",
@ -167,18 +163,30 @@ main(int argc, char **argv)
config = ConfigParseResultGet(configParseResult);
ConfigParseResultFree(configParseResult);
Log(lc, LOG_DEBUG, "Closing configuration file.");
fclose(configFile);
/* Configure log file */
tConfig = TelodendriaConfigParse(config, lc);
if (!tConfig)
{
exit = EXIT_FAILURE;
goto finish;
}
ConfigFree(config);
Log(lc, LOG_DEBUG, "Configuration:");
LogConfigIndent(lc);
Log(lc, LOG_DEBUG, "Listen On: %s:%s", tConfig->listenHost, tConfig->listenPort);
Log(lc, LOG_DEBUG, "Server Name: %s", tConfig->serverName);
Log(lc, LOG_DEBUG, "Chroot: %s", tConfig->chroot);
Log(lc, LOG_DEBUG, "Run As: %s:%s", tConfig->uid, tConfig->gid);
Log(lc, LOG_DEBUG, "Data Directory: %s", tConfig->dataDir);
Log(lc, LOG_DEBUG, "Threads: %d", tConfig->threads);
Log(lc, LOG_DEBUG, "Flags: %x", tConfig->flags);
LogConfigUnindent(lc);
finish:
if (config)
{
Log(lc, LOG_DEBUG, "Freeing configuration structure.");
ConfigFree(config);
}
Log(lc, LOG_DEBUG, "Freeing log configuration and exiting with code '%d'.", exit);
LogConfigFree(lc);
Log(lc, LOG_DEBUG, "Exiting with code '%d'.", exit);
TelodendriaConfigFree(tConfig);
return exit;
}

317
src/TelodendriaConfig.c Normal file
View file

@ -0,0 +1,317 @@
/*
* 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 <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(*str))
{
return 0;
}
str++;
}
return 1;
}
#define GET_DIRECTIVE(name) \
directive = (ConfigDirective *) HashMapGet(config, name); \
if (!directive) { \
Log(lc, LOG_ERROR, "Missing required configuration directive: '%s'.", name); \
goto error; \
} \
children = ConfigChildrenGet(directive); \
value = ConfigValuesGet(directive); \
#define ASSERT_NO_CHILDREN(name) if (children) { \
Log(lc, LOG_ERROR, "Unexpected child values in directive: '%s'.", name); \
goto error; \
}
#define ASSERT_VALUES(name, expected) if (ArraySize(value) != expected) { \
Log(lc, LOG_ERROR, \
"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 = calloc(1, sizeof(tConfig));
if (!tConfig)
{
return NULL;
}
directive = (ConfigDirective *) HashMapGet(config, "listen");
children = ConfigChildrenGet(directive);
value = ConfigValuesGet(directive);
if (!directive)
{
Log(lc, LOG_WARNING, "No 'listen' directive specified; using defaults, which may change.");
tConfig->listenHost = UtilStringDuplicate("localhost");
tConfig->listenPort = UtilStringDuplicate("8008");
}
else
{
ASSERT_NO_CHILDREN("listen");
ASSERT_VALUES("listen", 2);
COPY_VALUE(tConfig->listenHost, 0);
COPY_VALUE(tConfig->listenPort, 1);
}
GET_DIRECTIVE("server-name");
ASSERT_NO_CHILDREN("server-name");
ASSERT_VALUES("server-name", 1);
COPY_VALUE(tConfig->serverName, 0);
GET_DIRECTIVE("chroot");
ASSERT_NO_CHILDREN("chroot");
ASSERT_VALUES("chroot", 1);
COPY_VALUE(tConfig->chroot, 0);
GET_DIRECTIVE("id");
ASSERT_NO_CHILDREN("id");
ASSERT_VALUES("id", 2);
COPY_VALUE(tConfig->uid, 0);
COPY_VALUE(tConfig->gid, 1);
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));
}
else
{
Log(lc, LOG_ERROR,
"Expected integer for directive 'threads', "
"but got '%s'.", ArrayGet(value, 0));
goto error;
}
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_ERROR,
"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_ERROR,
"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_ERROR, "Expected 1 value for log.level, got %d.", size);
goto error;
}
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
if (strcmp(cVal, "message") == 0)
{
LogConfigLevelSet(lc, LOG_MESSAGE);
}
else if (strcmp(cVal, "debug") == 0)
{
LogConfigLevelSet(lc, LOG_DEBUG);
}
else if (strcmp(cVal, "task") == 0)
{
LogConfigLevelSet(lc, LOG_TASK);
}
else if (strcmp(cVal, "warning") == 0)
{
LogConfigLevelSet(lc, LOG_WARNING);
}
else if (strcmp(cVal, "error") == 0)
{
LogConfigLevelSet(lc, LOG_ERROR);
}
else
{
Log(lc, LOG_ERROR, "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_ERROR, "Expected 1 value for log.level, got %d.", size);
goto error;
}
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
if (strcmp(cVal, "none") == 0)
{
LogConfigTimeStampFormatSet(lc, NULL);
}
else if (strcmp(cVal, "default") != 0)
{
LogConfigTimeStampFormatSet(lc, UtilStringDuplicate(cVal));
}
}
cDirective = HashMapGet(children, "color");
if (cDirective)
{
size = ArraySize(ConfigValuesGet(cDirective));
if (size > 1)
{
Log(lc, LOG_ERROR, "Expected 1 value for log.level, got %d.", size);
goto error;
}
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
if (strcmp(cVal, "false") == 0)
{
LogConfigFlagClear(lc, LOG_FLAG_COLOR);
}
else if (strcmp(cVal, "true") != 0)
{
Log(lc, LOG_ERROR, "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)
{
FILE *out = fopen(ArrayGet(value, 0), "w");
if (!out)
{
Log(lc, LOG_ERROR, "Unable to open log file '%s' for writing.",
ArrayGet(value, 0));
goto error;
}
Log(lc, LOG_DEBUG, "Redirecting output to '%s'.", ArrayGet(value, 0));
LogConfigOutputSet(lc, out);
}
tConfig->logConfig = lc;
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->listenHost);
free(tConfig->listenPort);
free(tConfig->serverName);
free(tConfig->chroot);
free(tConfig->uid);
free(tConfig->gid);
free(tConfig->dataDir);
LogConfigFree(tConfig->logConfig);
free(tConfig);
}

View file

@ -24,6 +24,7 @@
#include <Util.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
long
@ -86,3 +87,21 @@ UtilUtf8Encode(unsigned long utf8)
return str;
}
char *
UtilStringDuplicate(char *inStr)
{
size_t len;
char *outStr;
len = strlen(inStr);
outStr = malloc(len + 1); /* For the null terminator */
if (!outStr)
{
return NULL;
}
strcpy(outStr, inStr);
return outStr;
}

View file

@ -0,0 +1,100 @@
/*
* 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.
*/
/*
* TelodendriaConfig.h: Validate and maintain the Telodendria server's
* configuration data. This API builds on the Config API to add
* Telodendria-specific parsing. It takes a fully parsed Config, and
* converts it into a TelodendriaConfig, which is more structured.
*/
#ifndef TELODENDRIA_TELODENDRIACONFIG_H
#define TELODENDRIA_TELODENDRIACONFIG_H
#include <Log.h>
#include <HashMap.h>
typedef enum TelodendriaConfigFlag
{
TELODENDRIA_FEDERATION = (1 << 0),
TELODENDRIA_REGISTRATION = (1 << 1)
} TelodendriaConfigFlag;
/*
* Since this configuration will live in memory for a long time, it is
* important that unused values are freed as soon as possible. Therefore,
* the TelodendriaConfig structure is not opaque; values are accessed
* directly, and they can be freed as the program wishes.
*
* NOTE: If you're going to free a value, make sure you set the pointer
* to NULL. TelodendriaConfigFree() will call free() on all values.
*/
typedef struct TelodendriaConfig
{
char *listenHost;
char *listenPort;
char *serverName;
char *chroot;
char *uid;
char *gid;
char *dataDir;
unsigned int flags;
unsigned int threads;
LogConfig *logConfig;
} TelodendriaConfig;
/*
* Parse a Config map, extracting the necessary values, validating them,
* and then adding them to a new TelodendriaConfig for future use by the
* program. All values are copied, so the Config hash map can be safely
* freed if this function succeeds.
*
* Params:
* (HashMap *) A hash map from ConfigParse(). This should be a map of
* ConfigDirectives.
* (LogConfig *) A working log configuration. Messages are written to
* this log as the parsing progresses, and this log is
* copied into the resulting TelodendriaConfig. It is
* also potentially modified by the configuration file's
* "log" block.
*
* Return: A TelodendriaConfig that is completely independent of the passed
* configuration hash map, or NULL if one or more required values is missing.
*/
extern TelodendriaConfig *
TelodendriaConfigParse(HashMap *, LogConfig *);
/*
* Free all of the memory allocated to the given configuration. This
* function unconditionally calls free() on all items in the structure,
* so make sure items that were already freed are NULL.
*
* Params:
* (TelodendriaConfig *) The configuration to free all the values for.
*/
extern void
TelodendriaConfigFree(TelodendriaConfig *);
#endif

View file

@ -68,4 +68,20 @@ extern long
extern char *
UtilUtf8Encode(unsigned long);
/*
* Duplicate a null-terminated string, and return a new string on the
* heap.
*
* Params:
* (char *) The string to duplicate. It can be located anywhere on
* the heap or the stack.
*
* Return: A pointer to a null-terminated string on the heap. You must
* free() it when you're done with it. This may also return NULL if the
* call to malloc() fails.
*/
extern char *
UtilStringDuplicate(char *);
#endif /* TELODENDRIA_UTIL_H */