From 45324ce77a07366d673197653408eaead871264c Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Thu, 29 Sep 2022 13:41:26 -0400 Subject: [PATCH] Implement /_matrix/client/versions and /.well-known/matrix/client --- TODO.txt | 10 +++-- contrib/development.conf | 1 + contrib/production.conf | 4 ++ docs/telodendria.conf.5 | 25 ++++++++++- src/Array.c | 2 +- src/Matrix.c | 30 ++++++++++++- src/Routes/RouteMatrix.c | 68 ++++++++++++++++++++++++++++++ src/Routes/RouteWellKnown.c | 74 +++++++++++++++++++++++++++++++++ src/Telodendria.c | 18 ++++---- src/TelodendriaConfig.c | 44 +++++++++++++++++++- src/include/Matrix.h | 7 ++++ src/include/Routes.h | 39 +++++++++++++++++ src/include/TelodendriaConfig.h | 3 ++ 13 files changed, 305 insertions(+), 20 deletions(-) create mode 100644 src/Routes/RouteMatrix.c create mode 100644 src/Routes/RouteWellKnown.c create mode 100644 src/include/Routes.h diff --git a/TODO.txt b/TODO.txt index 873c650..5e34131 100644 --- a/TODO.txt +++ b/TODO.txt @@ -55,11 +55,13 @@ Phase 2: Building a foundation Phase 3: Welcome to Matrix -[ ] client-Server API +[~] client-Server API [x] Error responses [x] CORS headers - [ ] /_matrix/client/versions - [ ] Well-known URIs + [x] /_matrix/client/versions + [x] Well-known URIs + [x] Make base-url optional in config + [x] Make identity-server optional in config [ ] Client authentication [ ] Capabilities negotiation [ ] Server-Server API @@ -88,7 +90,7 @@ Documentation [x] Make a note in Getting the Code that the password for the anoncvs account is just anoncvs [x] Add contributors list [x] Add list of make.sh recipes and what they do -[ ] Improve Google Lighthouse score on website +[x] Improve Google Lighthouse score on website [!] Image elements do not have explicit width and height [x] Background and foreground colors do not have sufficient contrast ratio (msg-error div) [x] Lists do not contain only
  • elements diff --git a/contrib/development.conf b/contrib/development.conf index 9da932d..49dbce9 100644 --- a/contrib/development.conf +++ b/contrib/development.conf @@ -3,6 +3,7 @@ # server-name "localhost"; +base-url "http://localhost:8008"; chroot "./chroot"; id "jordan"; data-dir "./data"; diff --git a/contrib/production.conf b/contrib/production.conf index a639ea4..b0e73d4 100644 --- a/contrib/production.conf +++ b/contrib/production.conf @@ -10,7 +10,11 @@ # listen "8008"; + server-name "example.com"; +base-url "https://matrix.example.com"; +identity-server "https://identity.example.com"; + chroot "/var/telodendria"; id "_telodendria" "_telodendria"; data-dir "./data"; diff --git a/docs/telodendria.conf.5 b/docs/telodendria.conf.5 index 24fc3e7..2bce499 100644 --- a/docs/telodendria.conf.5 +++ b/docs/telodendria.conf.5 @@ -1,4 +1,4 @@ -.Dd $Mdocdate: September 23 2022 $ +.Dd $Mdocdate: September 29 2022 $ .Dt TELODENDRIA.CONF 5 .Os Telodendria Project .Sh NAME @@ -55,6 +55,29 @@ over. .Ar name should be a DNS name that can be publically resolved. This directive is required. +.It Ic base-url Ar url +Set the server's base URL. +.Ar url +should be a valid URL, complete with the protocol. It does not need to +be the same as the server name; in fact, it is common for a subdomain of +the server name to be the base URL for the Matrix homeserver. +.Pp +This URL is the URL at which Matrix clients will connect to the server, +and is thus served as a part of the +.Pa .well-known +manifest. +.Pp +This directive is optional. If it is not specified, it is automatically +deduced from the server name. +.It Ic identity-server Ar url +The identity server that clients should use to perform identity lookups. +.Pp +.Ar url +follows the same rules as +.Ic base-url . +.Pp +This directive is optional. If it is not specified, it is automatically +set to be the same as the base URL. .It Ic chroot Ar directory Change the root directory to the specified directory as soon as possible. Note that all other paths and files specified in diff --git a/src/Array.c b/src/Array.c index 035f86c..6071f9a 100644 --- a/src/Array.c +++ b/src/Array.c @@ -78,7 +78,7 @@ ArrayDelete(Array * array, size_t index) size_t i; void *element; - if (!array) + if (!array || array->size <= index) { return NULL; } diff --git a/src/Matrix.c b/src/Matrix.c index 89bf498..51c93ad 100644 --- a/src/Matrix.c +++ b/src/Matrix.c @@ -31,6 +31,8 @@ #include #include +#include + void MatrixHttpHandler(HttpServerContext * context, void *argp) { @@ -99,15 +101,39 @@ MatrixHttpHandler(HttpServerContext * context, void *argp) ArrayAdd(pathParts, decoded); } - /* TODO: Route requests here */ + pathPart = MATRIX_PATH_POP(pathParts); + + if (MATRIX_PATH_EQUALS(pathPart, ".well-known")) + { + response = RouteWellKnown(args, context, pathParts); + } + else if (MATRIX_PATH_EQUALS(pathPart, "_matrix")) + { + response = RouteMatrix(args, context, pathParts); + } + else + { + HttpResponseStatus(context, HTTP_NOT_FOUND); + response = MatrixErrorCreate(M_NOT_FOUND); + } + + free(pathPart); HttpSendHeaders(context); stream = HttpStream(context); - response = MatrixErrorCreate(M_UNKNOWN); + if (!response) + { + Log(lc, LOG_WARNING, "A route handler returned NULL."); + HttpResponseStatus(context, HTTP_INTERNAL_SERVER_ERROR); + response = MatrixErrorCreate(M_UNKNOWN); + } + JsonEncode(response, stream); fprintf(stream, "\n"); + /* By this point, ArraySize(pathParts) should be 0, but just in + * case some elements remain, free them up now */ for (i = 0; i < ArraySize(pathParts); i++) { free(ArrayGet(pathParts, i)); diff --git a/src/Routes/RouteMatrix.c b/src/Routes/RouteMatrix.c new file mode 100644 index 0000000..ee73835 --- /dev/null +++ b/src/Routes/RouteMatrix.c @@ -0,0 +1,68 @@ +/* + * 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 + +#include +#include + +#include +#include + +ROUTE(RouteMatrix) +{ + HashMap *response = NULL; + char *pathPart = MATRIX_PATH_POP(path); + + (void) args; + + if (!MATRIX_PATH_EQUALS(pathPart, "client") || ArraySize(path) != 1) + { + free(pathPart); + HttpResponseStatus(context, HTTP_NOT_FOUND); + return MatrixErrorCreate(M_NOT_FOUND); + } + + free(pathPart); + pathPart = MATRIX_PATH_POP(path); + + if (MATRIX_PATH_EQUALS(pathPart, "versions")) + { + Array *versions = ArrayCreate(); + + free(pathPart); + + ArrayAdd(versions, JsonValueString(UtilStringDuplicate("v1.4"))); + + response = HashMapCreate(); + HashMapSet(response, "versions", JsonValueArray(versions)); + + return response; + } + else + { + free(pathPart); + HttpResponseStatus(context, HTTP_NOT_FOUND); + return MatrixErrorCreate(M_NOT_FOUND); + } +} diff --git a/src/Routes/RouteWellKnown.c b/src/Routes/RouteWellKnown.c new file mode 100644 index 0000000..1130f5a --- /dev/null +++ b/src/Routes/RouteWellKnown.c @@ -0,0 +1,74 @@ +/* + * 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 + +#include +#include + +#include +#include + +ROUTE(RouteWellKnown) +{ + HashMap *response = NULL; + char *pathPart = MATRIX_PATH_POP(path); + + if (!MATRIX_PATH_EQUALS(pathPart, "matrix") || ArraySize(path) != 1) + { + free(pathPart); + HttpResponseStatus(context, HTTP_NOT_FOUND); + return MatrixErrorCreate(M_NOT_FOUND); + } + + free(pathPart); + pathPart = MATRIX_PATH_POP(path); + + if (MATRIX_PATH_EQUALS(pathPart, "client")) + { + HashMap *homeserver = HashMapCreate(); + + free(pathPart); + + response = HashMapCreate(); + + HashMapSet(homeserver, "base_url", JsonValueString(UtilStringDuplicate(args->config->baseUrl))); + HashMapSet(response, "m.homeserver", JsonValueObject(homeserver)); + + if (args->config->identityServer) + { + HashMap *identityServer = HashMapCreate(); + + HashMapSet(identityServer, "base_url", JsonValueString(UtilStringDuplicate(args->config->identityServer))); + HashMapSet(response, "m.identity_server", identityServer); + } + + return response; + } + else + { + free(pathPart); + HttpResponseStatus(context, HTTP_NOT_FOUND); + return MatrixErrorCreate(M_NOT_FOUND); + } +} diff --git a/src/Telodendria.c b/src/Telodendria.c index 0ef2bef..538168d 100644 --- a/src/Telodendria.c +++ b/src/Telodendria.c @@ -104,7 +104,7 @@ main(int argc, char **argv) /* Signal handling */ struct sigaction sigAction; - MatrixHttpHandlerArgs *matrixArgs; + MatrixHttpHandlerArgs matrixArgs; lc = LogConfigCreate(); @@ -250,6 +250,8 @@ main(int argc, char **argv) LogConfigIndent(lc); Log(lc, LOG_DEBUG, "Listen On: %d", tConfig->listenPort); Log(lc, LOG_DEBUG, "Server Name: %s", tConfig->serverName); + Log(lc, LOG_DEBUG, "Base URL: %s", tConfig->baseUrl); + Log(lc, LOG_DEBUG, "Identity Server: %s", tConfig->identityServer); 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); @@ -284,19 +286,13 @@ main(int argc, char **argv) Log(lc, LOG_DEBUG, "Found user/group information using getpwnam() and getgrnam()."); } - matrixArgs = malloc(sizeof(MatrixHttpHandlerArgs)); - if (!matrixArgs) - { - Log(lc, LOG_ERROR, "Unable to allocate memory for HTTP handler arguments."); - exit = EXIT_FAILURE; - goto finish; - } - - matrixArgs->lc = lc; + /* Arguments to pass into the HTTP handler */ + matrixArgs.lc = lc; + matrixArgs.config = tConfig; /* Bind the socket before possibly dropping permissions */ httpServer = HttpServerCreate(tConfig->listenPort, tConfig->threads, tConfig->maxConnections, - MatrixHttpHandler, matrixArgs); + MatrixHttpHandler, &matrixArgs); if (!httpServer) { Log(lc, LOG_ERROR, "Unable to create HTTP server on port %d: %s", diff --git a/src/TelodendriaConfig.c b/src/TelodendriaConfig.c index f16fdea..5baadf1 100644 --- a/src/TelodendriaConfig.c +++ b/src/TelodendriaConfig.c @@ -116,6 +116,45 @@ TelodendriaConfigParse(HashMap * config, LogConfig * lc) 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_ERROR, "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; + } + GET_DIRECTIVE("chroot"); ASSERT_NO_CHILDREN("chroot"); ASSERT_VALUES("chroot", 1); @@ -138,7 +177,7 @@ TelodendriaConfigParse(HashMap * config, LogConfig * lc) Log(lc, LOG_ERROR, "Wrong value count in directive 'id': got '%d', but expected 1 or 2.", ArraySize(value)); - break; + goto error; } GET_DIRECTIVE("data-dir"); @@ -348,6 +387,9 @@ TelodendriaConfigFree(TelodendriaConfig * tConfig) } free(tConfig->serverName); + free(tConfig->baseUrl); + free(tConfig->identityServer); + free(tConfig->chroot); free(tConfig->uid); free(tConfig->gid); diff --git a/src/include/Matrix.h b/src/include/Matrix.h index 0d55058..af8804c 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -28,6 +28,12 @@ #include #include +#include + +#define MATRIX_PATH_POP(arr) ArrayDelete(arr, 0) + +#define MATRIX_PATH_EQUALS(pathPart, str) ((pathPart != NULL) && (strcmp(pathPart, str) == 0)) + typedef enum MatrixError { M_FORBIDDEN, @@ -72,6 +78,7 @@ typedef enum MatrixError typedef struct MatrixHttpHandlerArgs { LogConfig *lc; + TelodendriaConfig *config; } MatrixHttpHandlerArgs; extern void diff --git a/src/include/Routes.h b/src/include/Routes.h new file mode 100644 index 0000000..dd5ad24 --- /dev/null +++ b/src/include/Routes.h @@ -0,0 +1,39 @@ +/* + * 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. + */ +#ifndef TELODENDRIA_ROUTES_H +#define TELODENDRIA_ROUTES_H + +#include +#include +#include +#include + +#include + +#define ROUTE(name) HashMap * name(MatrixHttpHandlerArgs *args, HttpServerContext *context, Array *path) + +extern ROUTE(RouteWellKnown); +extern ROUTE(RouteMatrix); + +#endif diff --git a/src/include/TelodendriaConfig.h b/src/include/TelodendriaConfig.h index 3bca82c..ccb071d 100644 --- a/src/include/TelodendriaConfig.h +++ b/src/include/TelodendriaConfig.h @@ -53,6 +53,9 @@ typedef enum TelodendriaConfigFlag typedef struct TelodendriaConfig { char *serverName; + char *baseUrl; + char *identityServer; + char *chroot; char *uid; char *gid;