diff --git a/TODO.txt b/TODO.txt index e5b423d..6986fd9 100644 --- a/TODO.txt +++ b/TODO.txt @@ -38,10 +38,10 @@ Milestone: v0.3.0 [x] Global log object - So we don't have to pass LogConfig around everywhere - Also allows debug and error logging in other APIs -[ ] Make 'listen' directive in config an array of objects +[x] Make 'listen' directive in config an array of objects - Each object has port, threads, maxConnections - If tls is given, it can be null, false, or an object with cert and key -[ ] Pass TLS certs and keys into HttpServer +[x] Pass TLS certs and keys into HttpServer [ ] Move configuration to database [ ] Initial configuration [ ] If no config, create one-time use registration token that diff --git a/contrib/development.conf b/contrib/development.conf index da6aa0b..effc86e 100644 --- a/contrib/development.conf +++ b/contrib/development.conf @@ -1,14 +1,19 @@ { - "serverName": "localhost", - "baseUrl": "http://localhost:8008", - "dataDir": "./data", - "federation": true, - "registration": true, - "threads": 2, - "log": { - "output": "stdout", - "level": "debug", - "timestampFormat": "none", - "color": true - } + "log": { + "output": "stdout", + "color": true, + "timestampFormat": "none", + "level": "debug" + }, + "dataDir": "./data", + "listen": [ + { + "port": 8008, + "tls": false + } + ], + "registration": true, + "serverName": "localhost", + "baseUrl": "http:\/\/localhost:8008", + "federation": true } diff --git a/contrib/production.conf b/contrib/production.conf index ce11832..a5ef83b 100644 --- a/contrib/production.conf +++ b/contrib/production.conf @@ -1,13 +1,25 @@ { - "serverName": "example.com", - "baseUrl": "https://matrix.example.com", - "identityServer": "https://identity.example.com", - "dataDir": "/var/telodendria", - "federation": true, - "registration": false, - "threads": 4, - "maxCache": 512000000, - "log": { - "output": "file" + "log": { + "output": "file" + }, + "dataDir": "/var/telodendria", + "listen": [ + { + "port": 8008, + "tls": false + }, + { + "port": 8448, + "tls": { + "cert": "telodendria.crt", + "key": "telodendria.key" + } } + ], + "serverName": "example.com", + "identityServer": "https://identity.example.com", + "baseUrl": "https://matrix.example.com", + "registration": false, + "federation": true, + "maxCache": 512000000 } diff --git a/src/Array.c b/src/Array.c index c5c41da..948d9d9 100644 --- a/src/Array.c +++ b/src/Array.c @@ -164,7 +164,7 @@ ArrayInsert(Array * array, size_t index, void *value) } extern void * -ArraySet(Array * array, size_t index, void * value) +ArraySet(Array * array, size_t index, void *value) { void *oldValue; diff --git a/src/Config.c b/src/Config.c index dd27922..c098d59 100644 --- a/src/Config.c +++ b/src/Config.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -89,7 +90,7 @@ into = default; \ } -int +static int ConfigParseRunAs(Config * tConfig, HashMap * config) { JsonValue *value; @@ -105,7 +106,109 @@ error: return 0; } -int +static int +ConfigParseListen(Config * tConfig, Array * listen) +{ + size_t i; + + if (!ArraySize(listen)) + { + Log(LOG_ERR, "Listen array cannot be empty; you must specify at least"); + Log(LOG_ERR, "one listener."); + goto error; + } + + if (!tConfig->servers) + { + tConfig->servers = ArrayCreate(); + if (!tConfig->servers) + { + Log(LOG_ERR, "Unable to allocate memory for listener configurations."); + goto error; + } + } + + for (i = 0; i < ArraySize(listen); i++) + { + JsonValue *val = ArrayGet(listen, i); + HashMap *obj; + HttpServerConfig *serverCfg = Malloc(sizeof(HttpServerConfig)); + + if (!serverCfg) + { + Log(LOG_ERR, "Unable to allocate memory for listener configuration."); + goto error; + } + + if (JsonValueType(val) != JSON_OBJECT) + { + Log(LOG_ERR, "Invalid value in listener array."); + Log(LOG_ERR, "All listeners must be objects."); + goto error; + } + + obj = JsonValueAsObject(val); + + serverCfg->port = JsonValueAsInteger(HashMapGet(obj, "port")); + serverCfg->threads = JsonValueAsInteger(HashMapGet(obj, "threads")); + serverCfg->maxConnections = JsonValueAsInteger(HashMapGet(obj, "maxConnections")); + + if (!serverCfg->port) + { + Log(LOG_WARNING, "No or invalid port specified, listener will be ignored."); + Free(serverCfg); + continue; + } + + if (!serverCfg->threads) + { + Log(LOG_DEBUG, "No or invalid number of threads specified for listener."); + Log(LOG_DEBUG, "Using default, which may be subject to change."); + serverCfg->threads = 4; + } + + if (!serverCfg->maxConnections) + { + Log(LOG_DEBUG, "No or invalid number of maximum connections specified."); + Log(LOG_DEBUG, "Using default, which may be subject to change."); + serverCfg->maxConnections = 32; + } + + val = HashMapGet(obj, "tls"); + if ((JsonValueType(val) == JSON_BOOLEAN && !JsonValueAsBoolean(val)) || JsonValueType(val) == JSON_NULL) + { + serverCfg->flags = HTTP_FLAG_NONE; + serverCfg->tlsCert = NULL; + serverCfg->tlsKey = NULL; + } + else if (JsonValueType(val) != JSON_OBJECT) + { + Log(LOG_ERR, "Invalid value for listener.tls. It must be an object."); + goto error; + } + else + { + serverCfg->flags = HTTP_FLAG_TLS; + + obj = JsonValueAsObject(val); + serverCfg->tlsCert = StrDuplicate(JsonValueAsString(HashMapGet(obj, "cert"))); + serverCfg->tlsKey = StrDuplicate(JsonValueAsString(HashMapGet(obj, "key"))); + + if (!serverCfg->tlsCert || !serverCfg->tlsKey) + { + Log(LOG_ERR, "TLS cert and key must both be valid file names."); + goto error; + } + } + ArrayAdd(tConfig->servers, serverCfg); + } + + return 1; +error: + return 0; +} + +static int ConfigParseLog(Config * tConfig, HashMap * config) { JsonValue *value; @@ -210,7 +313,11 @@ ConfigParse(HashMap * config) memset(tConfig, 0, sizeof(Config)); - CONFIG_OPTIONAL_INTEGER(tConfig->listenPort, "listen", 8008); + CONFIG_REQUIRE("listen", JSON_ARRAY); + if (!ConfigParseListen(tConfig, JsonValueAsArray(value))) + { + goto error; + } CONFIG_REQUIRE("serverName", JSON_STRING); CONFIG_COPY_STRING(tConfig->serverName); @@ -256,8 +363,6 @@ ConfigParse(HashMap * config) CONFIG_REQUIRE("dataDir", JSON_STRING); CONFIG_COPY_STRING(tConfig->dataDir); - CONFIG_OPTIONAL_INTEGER(tConfig->threads, "threads", 1); - CONFIG_OPTIONAL_INTEGER(tConfig->maxConnections, "maxConnections", 32); CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0); CONFIG_REQUIRE("federation", JSON_BOOLEAN); @@ -303,5 +408,21 @@ ConfigFree(Config * tConfig) 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); } diff --git a/src/HttpServer.c b/src/HttpServer.c index 420e54c..d01c068 100644 --- a/src/HttpServer.c +++ b/src/HttpServer.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -48,20 +49,13 @@ static const char ENABLE = 1; struct HttpServer { + HttpServerConfig config; int sd; - unsigned int nThreads; - unsigned int maxConnections; pthread_t socketThread; - int flags; - char *tlsCrt; - char *tlsKey; volatile unsigned int stop:1; volatile unsigned int isRunning:1; - HttpHandler *requestHandler; - void *handlerArgs; - Queue *connQueue; pthread_mutex_t connQueueMutex; @@ -234,7 +228,7 @@ HttpResponseStatus(HttpServerContext * c, HttpStatus status) } HttpStatus -HttpResponseStatusGet(HttpServerContext *c) +HttpResponseStatusGet(HttpServerContext * c) { if (!c) { @@ -291,19 +285,23 @@ DequeueConnection(HttpServer * server) } HttpServer * -HttpServerCreate(int flags, unsigned short port, unsigned int nThreads, unsigned int maxConnections, - HttpHandler * requestHandler, void *handlerArgs) +HttpServerCreate(HttpServerConfig * config) { HttpServer *server; struct sockaddr_in sa; - if (!requestHandler) + if (!config) + { + return NULL; + } + + if (!config->handler) { return NULL; } #ifndef TLS_IMPL - if (flags & HTTP_FLAG_TLS) + if (config->flags & HTTP_FLAG_TLS) { return NULL; } @@ -317,7 +315,7 @@ HttpServerCreate(int flags, unsigned short port, unsigned int nThreads, unsigned memset(server, 0, sizeof(HttpServer)); - server->flags = flags; + server->config = *config; server->threadPool = ArrayCreate(); if (!server->threadPool) @@ -325,7 +323,7 @@ HttpServerCreate(int flags, unsigned short port, unsigned int nThreads, unsigned goto error; } - server->connQueue = QueueCreate(maxConnections); + server->connQueue = QueueCreate(config->maxConnections); if (!server->connQueue) { goto error; @@ -363,7 +361,7 @@ HttpServerCreate(int flags, unsigned short port, unsigned int nThreads, unsigned memset(&sa, 0, sizeof(struct sockaddr_in)); sa.sin_family = AF_INET; - sa.sin_port = htons(port); + sa.sin_port = htons(config->port); sa.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server->sd, (struct sockaddr *) & sa, sizeof(sa)) < 0) @@ -371,15 +369,11 @@ HttpServerCreate(int flags, unsigned short port, unsigned int nThreads, unsigned goto error; } - if (listen(server->sd, maxConnections) < 0) + if (listen(server->sd, config->maxConnections) < 0) { goto error; } - server->nThreads = nThreads; - server->maxConnections = maxConnections; - server->requestHandler = requestHandler; - server->handlerArgs = handlerArgs; server->stop = 0; server->isRunning = 0; @@ -410,6 +404,17 @@ error: return NULL; } +HttpServerConfig * +HttpServerConfigGet(HttpServer * server) +{ + if (!server) + { + return NULL; + } + + return &server->config; +} + void HttpServerFree(HttpServer * server) { @@ -561,7 +566,7 @@ HttpServerWorkerThread(void *args) goto internal_error; } - server->requestHandler(context, server->handlerArgs); + server->config.handler(context, server->config.handlerArgs); HttpServerContextFree(context); fp = NULL; /* The above call will close this @@ -603,7 +608,7 @@ HttpServerEventThread(void *args) pollFds[0].fd = server->sd; pollFds[0].events = POLLIN; - for (i = 0; i < server->nThreads; i++) + for (i = 0; i < server->config.threads; i++) { HttpServerWorkerThreadArgs *workerThread = Malloc(sizeof(HttpServerWorkerThreadArgs)); @@ -657,9 +662,9 @@ HttpServerEventThread(void *args) } #ifdef TLS_IMPL - if (server->flags & HTTP_FLAG_TLS) + if (server->config.flags & HTTP_FLAG_TLS) { - fp = TlsServerStream(connFd, server->tlsCrt, server->tlsKey); + fp = TlsServerStream(connFd, server->config.tlsCert, server->config.tlsKey); } else { @@ -681,7 +686,7 @@ HttpServerEventThread(void *args) pthread_mutex_unlock(&server->connQueueMutex); } - for (i = 0; i < server->nThreads; i++) + for (i = 0; i < server->config.threads; i++) { HttpServerWorkerThreadArgs *workerThread = ArrayGet(server->threadPool, i); diff --git a/src/Log.c b/src/Log.c index cb0618a..c279c69 100644 --- a/src/Log.c +++ b/src/Log.c @@ -369,7 +369,7 @@ Logv(LogConfig * config, int level, const char *msg, va_list argp) } void -LogTo(LogConfig *config, int level, const char *fmt, ...) +LogTo(LogConfig * config, int level, const char *fmt,...) { va_list argp; @@ -379,7 +379,7 @@ LogTo(LogConfig *config, int level, const char *fmt, ...) } extern void -Log(int level, const char *fmt, ...) +Log(int level, const char *fmt,...) { va_list argp; diff --git a/src/Main.c b/src/Main.c index 7dd67b9..5b701ed 100644 --- a/src/Main.c +++ b/src/Main.c @@ -43,6 +43,7 @@ #include #include #include +#include static Array *httpServers = NULL; @@ -61,6 +62,7 @@ TelodendriaSignalHandler(int signalNo) for (i = 0; i < ArraySize(httpServers); i++) { HttpServer *server = ArrayGet(httpServers, i); + HttpServerStop(server); } } @@ -239,6 +241,7 @@ main(int argc, char **argv) { Log(LOG_ERR, "Unable to open log file for appending."); exit = EXIT_FAILURE; + tConfig->flags &= CONFIG_LOG_STDOUT; goto finish; } @@ -269,14 +272,11 @@ main(int argc, char **argv) Log(LOG_DEBUG, "Configuration:"); LogConfigIndent(LogConfigGlobal()); - Log(LOG_DEBUG, "Listen On: %d", tConfig->listenPort); Log(LOG_DEBUG, "Server Name: %s", tConfig->serverName); Log(LOG_DEBUG, "Base URL: %s", tConfig->baseUrl); Log(LOG_DEBUG, "Identity Server: %s", tConfig->identityServer); Log(LOG_DEBUG, "Run As: %s:%s", tConfig->uid, tConfig->gid); Log(LOG_DEBUG, "Data Directory: %s", tConfig->dataDir); - Log(LOG_DEBUG, "Threads: %d", tConfig->threads); - Log(LOG_DEBUG, "Max Connections: %d", tConfig->maxConnections); Log(LOG_DEBUG, "Max Cache: %ld", tConfig->maxCache); Log(LOG_DEBUG, "Flags: %x", tConfig->flags); LogConfigUnindent(LogConfigGlobal()); @@ -292,19 +292,60 @@ main(int argc, char **argv) goto finish; } - /* Bind the socket before possibly dropping permissions */ - server = HttpServerCreate(HTTP_FLAG_NONE, tConfig->listenPort, tConfig->threads, - tConfig->maxConnections, MatrixHttpHandler, &matrixArgs); - if (!server) + /* Bind servers before possibly dropping permissions. */ + for (i = 0; i < ArraySize(tConfig->servers); i++) { - Log(LOG_ERR, "Unable to create HTTP server on port %d: %s", - tConfig->listenPort, strerror(errno)); + HttpServerConfig *serverCfg = ArrayGet(tConfig->servers, i); + + Log(LOG_DEBUG, "HTTP listener: %lu", i); + LogConfigIndent(LogConfigGlobal()); + Log(LOG_DEBUG, "Port: %hu", serverCfg->port); + Log(LOG_DEBUG, "Threads: %u", serverCfg->threads); + Log(LOG_DEBUG, "Max Connections: %u", serverCfg->maxConnections); + Log(LOG_DEBUG, "Flags: %d", serverCfg->flags); + Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tlsCert); + Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tlsKey); + LogConfigUnindent(LogConfigGlobal()); + + serverCfg->handler = MatrixHttpHandler; + serverCfg->handlerArgs = &matrixArgs; + + if (serverCfg->flags & HTTP_FLAG_TLS) + { + if (!UtilLastModified(serverCfg->tlsCert)) + { + Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tlsCert); + exit = EXIT_FAILURE; + goto finish; + } + + if (!UtilLastModified(serverCfg->tlsKey)) + { + Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tlsKey); + exit = EXIT_FAILURE; + goto finish; + } + } + + server = HttpServerCreate(serverCfg); + if (!server) + { + Log(LOG_ERR, "Unable to create HTTP server on port %d: %s", + serverCfg->port, strerror(errno)); + + exit = EXIT_FAILURE; + goto finish; + } + ArrayAdd(httpServers, server); + } + + if (!ArraySize(httpServers)) + { + Log(LOG_ERR, "No valid HTTP listeners specified in the configuration."); exit = EXIT_FAILURE; goto finish; } - ArrayAdd(httpServers, server); - Log(LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid()); if (tConfig->uid && tConfig->gid) @@ -375,7 +416,6 @@ main(int argc, char **argv) tConfig->uid = NULL; tConfig->gid = NULL; - if (!tConfig->maxCache) { Log(LOG_WARNING, "Database caching is disabled."); @@ -391,6 +431,10 @@ main(int argc, char **argv) exit = EXIT_FAILURE; goto finish; } + else + { + Log(LOG_DEBUG, "Opened database."); + } cron = CronCreate(60 * 1000); /* 1-minute tick */ if (!cron) @@ -411,21 +455,24 @@ main(int argc, char **argv) for (i = 0; i < ArraySize(httpServers); i++) { + HttpServerConfig *serverCfg; + server = ArrayGet(httpServers, i); + serverCfg = HttpServerConfigGet(server); if (!HttpServerStart(server)) { - Log(LOG_ERR, "Unable to start HTTP server %lu.", i); + Log(LOG_ERR, "Unable to start HTTP server %lu on port %hu.", i, serverCfg->port); exit = EXIT_FAILURE; goto finish; } else { Log(LOG_DEBUG, "Started HTTP server %lu.", i); + Log(LOG_INFO, "Listening on port: %hu", serverCfg->port); } } - Log(LOG_INFO, "Listening on port: %d", tConfig->listenPort); sigAction.sa_handler = TelodendriaSignalHandler; sigfillset(&sigAction.sa_mask); @@ -437,6 +484,10 @@ main(int argc, char **argv) exit = EXIT_FAILURE; goto finish; } + else + { + Log(LOG_DEBUG, "Installed SIGINT signal handler."); + } /* Block this thread until the servers are terminated by a signal * handler */ @@ -444,6 +495,7 @@ main(int argc, char **argv) { server = ArrayGet(httpServers, i); HttpServerJoin(server); + Log(LOG_DEBUG, "Joined HTTP server %lu.", i); } finish: @@ -452,11 +504,14 @@ finish: { for (i = 0; i < ArraySize(httpServers); i++) { + Log(LOG_DEBUG, "Freeing HTTP server %lu...", i); server = ArrayGet(httpServers, i); HttpServerStop(server); HttpServerFree(server); + Log(LOG_DEBUG, "Freed HTTP server %lu.", i); } ArrayFree(httpServers); + Log(LOG_DEBUG, "Freed HTTP servers array."); } if (cron) @@ -466,16 +521,8 @@ finish: Log(LOG_DEBUG, "Stopped and freed job scheduler."); } - /* - * If we're not logging to standard output, then we can close it. Otherwise, - * if we are logging to stdout, LogConfigFree() will close it for us. - */ - if (tConfig && !(tConfig->flags & CONFIG_LOG_STDOUT)) - { - StreamClose(StreamStdout()); - } - DbClose(matrixArgs.db); + Log(LOG_DEBUG, "Closed database."); ConfigFree(tConfig); @@ -498,6 +545,8 @@ finish: * memory should be allocated. */ TelodendriaGenerateMemReport(); + /* Free any leaked memory now, just in case the operating system + * we're running on won't do it for us. */ MemoryFreeAll(); return exit; } diff --git a/src/Tls/TlsLibreSSL.c b/src/Tls/TlsLibreSSL.c index e06f949..ddcba3f 100644 --- a/src/Tls/TlsLibreSSL.c +++ b/src/Tls/TlsLibreSSL.c @@ -119,21 +119,25 @@ TlsInitServer(int fd, const char *crt, const char *key) if (tls_config_set_cert_file(cookie->cfg, crt) == -1) { + Log(LOG_ERR, "Error with certificate file."); goto error; } if (tls_config_set_key_file(cookie->cfg, key) == -1) { + Log(LOG_ERR, "Error with key file."); goto error; } if (tls_configure(cookie->ctx, cookie->cfg) == -1) { + Log(LOG_ERR, "Error configuring context."); goto error; } if (tls_accept_socket(cookie->ctx, &cookie->cctx, fd) == -1) { + Log(LOG_ERR, "Error accepting socket."); goto error; } diff --git a/src/Uia.c b/src/Uia.c index 1935115..f0afad2 100644 --- a/src/Uia.c +++ b/src/Uia.c @@ -205,7 +205,7 @@ UiaStageBuild(char *type, HashMap * params) int UiaComplete(Array * flows, HttpServerContext * context, Db * db, - HashMap * request, HashMap ** response, Config * config) + HashMap * request, HashMap ** response, Config * config) { JsonValue *val; HashMap *auth; diff --git a/src/include/Array.h b/src/include/Array.h index f56e7fa..89e7cdd 100644 --- a/src/include/Array.h +++ b/src/include/Array.h @@ -43,7 +43,7 @@ extern int ArrayInsert(Array *, size_t, void *); extern void * -ArraySet(Array *, size_t, void *); + ArraySet(Array *, size_t, void *); extern int ArrayAdd(Array *, void *); diff --git a/src/include/Config.h b/src/include/Config.h index fd73ae4..83cfb43 100644 --- a/src/include/Config.h +++ b/src/include/Config.h @@ -27,6 +27,7 @@ #include #include +#include typedef enum ConfigFlag { @@ -48,15 +49,14 @@ typedef struct Config char *gid; char *dataDir; - unsigned short listenPort; unsigned int flags; - unsigned int threads; - unsigned int maxConnections; size_t maxCache; char *logTimestamp; int logLevel; + + Array *servers; } Config; extern Config * @@ -65,4 +65,4 @@ extern Config * extern void ConfigFree(Config *); -#endif /* TELODENDRIA_CONFIG_H */ +#endif /* TELODENDRIA_CONFIG_H */ diff --git a/src/include/HttpServer.h b/src/include/HttpServer.h index 593431a..a739017 100644 --- a/src/include/HttpServer.h +++ b/src/include/HttpServer.h @@ -36,8 +36,25 @@ typedef struct HttpServer HttpServer; typedef struct HttpServerContext HttpServerContext; typedef void (HttpHandler) (HttpServerContext *, void *); +typedef struct HttpServerConfig +{ + unsigned short port; + unsigned int threads; + unsigned int maxConnections; + + int flags; + char *tlsCert; + char *tlsKey; + + HttpHandler *handler; + void *handlerArgs; +} HttpServerConfig; + extern HttpServer * - HttpServerCreate(int, unsigned short, unsigned int, unsigned int, HttpHandler *, void *); + HttpServerCreate(HttpServerConfig *); + +extern HttpServerConfig * + HttpServerConfigGet(HttpServer *); extern void HttpServerFree(HttpServer *); diff --git a/src/include/Log.h b/src/include/Log.h index f1a39b9..148574c 100644 --- a/src/include/Log.h +++ b/src/include/Log.h @@ -76,6 +76,6 @@ extern void LogTo(LogConfig *, int, const char *,...); extern void - Log(int, const char *, ...); + Log(int, const char *,...); #endif diff --git a/tools/src/http-debug-server.c b/tools/src/http-debug-server.c index cb42329..cbffa09 100644 --- a/tools/src/http-debug-server.c +++ b/tools/src/http-debug-server.c @@ -73,8 +73,16 @@ int main(void) { struct sigaction sa; + HttpServerConfig cfg; - server = HttpServerCreate(HTTP_FLAG_NONE, 8008, 1, 1, HttpHandle, NULL); + cfg.flags = HTTP_FLAG_NONE; + cfg.port = 8008; + cfg.threads = 1; + cfg.maxConnections = 1; + cfg.handler = HttpHandle; + cfg.handlerArgs = NULL; + + server = HttpServerCreate(&cfg); if (!HttpServerStart(server)) { StreamPuts(StreamStderr(), "Unable to start HTTP server.\n");