Improvements to #44: Implement #22 and #9 (#51)

This pull request makes a very small commit on top of #44.

Closes #44.
Closes #9.
Closes #22.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: lda <lda@freetards.xyz>
Co-authored-by: lda <lda@noreply.git.telodendria.io>
Reviewed-on: Telodendria/Telodendria#51
This commit is contained in:
Jordan Bancino 2024-01-11 19:33:50 -05:00
parent 243de4f1a0
commit 54420f0036
14 changed files with 817 additions and 77 deletions

View File

@ -41,6 +41,8 @@ will now be maintained separately and have its own releases as well.
custom scripts.
- Greatly simplified some endpoint code by using Cytoplasm's `j2s` for
parsing request bodies.
- Create a parser API for grammars found in Matrix, and refactor the
User API to use it.
### New Features
@ -58,6 +60,10 @@ the ability to change only a subset of the configuration.
- **GET** `/_telodendria/admin/tokens/[token]`
- **POST** `/_telodendria/admin/tokens`
- **DELETE** `/_telodendria/admin/tokens/[token]`
- **GET** `/_matrix/client/v3/directory/room/[alias]`
- **PUT** `/_matrix/client/v3/directory/room/[alias]`
- **DELETE** `/_matrix/client/v3/directory/room/[alias]`
- **GET** `/_matrix/client/v3/rooms/[id]/aliases`
## v0.3.0

View File

@ -16,10 +16,10 @@ registration tokens.
configuration.
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own
privileges or the privileges of other local users.
- **ALIAS:** Allows a user to modify room aliases created by other
users. By default, users can only manage their own room aliases, but
an administrator may wish to take over an alias or remove an offensive
alias.
- **ALIAS:** Allows a user to modify and see room aliases created by
other users. By default, users can only manage their own room aliases,
but an administrator may wish to take over an alias or remove an
offensive alias.
- **PROC_CONTROL:** Allows a user to get statistics on the running
process, as well as shutdown and resetart the Telodendria daemon
itself. Typically this will pair well with **CONFIG**, because there

503
src/Parser.c Normal file
View File

@ -0,0 +1,503 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* 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 <Parser.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Int.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
/* Iterate through a char **. */
#define Iterate(s) (*(*s)++)
/* Parse an extended localpart */
static int
ParseUserLocalpart(char **str, char **out)
{
char c;
char *start;
size_t length;
if (!str || !out)
{
return 0;
}
/* An extended localpart contains every ASCII printable character,
* except an ':'. */
start = *str;
while (isascii((c = Iterate(str))) && c != ':' && c)
{
/* Do nothing */
}
length = (size_t) (*str - start) - 1;
if (length < 1)
{
*str = start;
return 0;
}
if (c == ':')
{
--(*str);
}
*out = Malloc(length + 1);
memcpy(*out, start, length);
(*out)[length] = '\0';
return 1;
}
/* Parses an IPv4 address. */
static int
ParseIPv4(char **str, char **out)
{
/* Be *very* careful with this buffer */
char buffer[4];
char *start;
size_t length;
char c;
int digit = 0;
int digits = 0;
memset(buffer, 0, sizeof(buffer));
start = *str;
/* An IPv4 address is made of 4 blocks between 1-3 digits, like so:
* (1-3)*DIGIT.(1-3)*DIGIT.(1-3)*DIGIT.(1-3)*DIGIT */
while ((isdigit(c = Iterate(str)) || c == '.') && c && digits < 4)
{
if (isdigit(c))
{
digit++;
continue;
}
if (digit < 1 || digit > 3)
{
/* Current digit is too long for the spec! */
*str = start;
return 0;
}
memcpy(buffer, *str - digit - 1, digit);
if (atoi(buffer) > 255)
{
/* Current digit is too large for the spec! */
*str = start;
return 0;
}
memset(buffer, 0, sizeof(buffer));
digit = 0;
digits++; /* We have parsed a digit. */
}
if (c == '.' || digits != 3)
{
*str = start;
return 0;
}
length = (size_t) (*str - start) - 1;
*out = Malloc(length + 1);
memcpy(*out, start, length);
(*str)--;
return 1;
}
static int
IsIPv6Char(char c)
{
return isxdigit(c) || c == ':' || c == '.';
}
static int
ParseIPv6(char **str, char **out)
{
char *start;
size_t length;
char c;
int filled = 0;
int digit = 0;
int digits = 0;
start = *str;
length = 0;
if (Iterate(str) != '[')
{
goto fail;
}
while ((c = Iterate(str)) && IsIPv6Char(c))
{
char *ipv4;
if (isxdigit(c))
{
digit++;
length++;
continue;
}
if (c == ':')
{
if (**str == ':')
{
digit = 0;
if (!filled)
{
filled = 1;
length++;
c = Iterate(str); /* Skip over the character */
continue;
}
/* RFC3513 says the following:
* > 'The "::" can only appear once in an address.' */
*str = start;
return 0;
}
if (digit < 1 || digit > 4)
{
goto fail;
}
/* We do not have to check whenever the digit here is valid,
* because it has to be. */
digit = 0;
digits++;
length++;
continue;
}
/* The only remaining character being '.', we are probably dealing
* with an IPv4 literal. */
*str -= digit + 1;
length -= digit + 1;
if (ParseIPv4(str, &ipv4))
{
length += strlen(ipv4);
Free(ipv4);
c = Iterate(str);
filled = 1;
goto end;
}
}
end:
--(*str);
if (Iterate(str) != ']')
{
goto fail;
}
length = (size_t) (*str - start);
if (length < 4 || length > 47)
{
goto fail;
}
*out = Malloc(length + 1);
memset(*out, '\0', length + 1);
memcpy(*out, start, length);
return 1;
fail:
*str = start;
return 0;
}
static int
ParseHostname(char **str, char **out)
{
char *start;
size_t length = 0;
char c;
start = *str;
while ((c = Iterate(str)) &&
(isalnum(c) || c == '.' || c == '-') &&
++length < 256)
{
/* Do nothing. */
}
if (length < 1 || length > 255)
{
*str = start;
return 0;
}
length = (size_t) (*str - start) - 1;
*out = Malloc(length + 1);
memcpy(*out, start, length);
(*str)--;
return 1;
}
static int
ParseServerName(char **str, ServerPart *out)
{
char c;
char *start;
char *startPort;
size_t chars = 0;
char *host = NULL;
char *port = NULL;
if (!str || !out)
{
return 0;
}
start = *str;
if (!host)
{
/* If we can parse an IPv4 address, use that. */
ParseIPv4(str, &host);
}
if (!host)
{
/* If we can parse an IPv6 address, use that. */
ParseIPv6(str, &host);
}
if (!host)
{
/* If we can parse an hostname, use that. */
ParseHostname(str, &host);
}
if (!host)
{
/* Can't parse a valid server name. */
return 0;
}
/* Now, there's only 2 options: a ':', or the end(everything else.) */
if (**str != ':')
{
/* We're done. */
out->hostname = host;
out->port = NULL;
return 1;
}
/* TODO: Separate this out */
startPort = ++(*str);
while(isdigit(c = Iterate(str)) && c && ++chars < 5)
{
/* Do nothing. */
}
if (chars < 1 || chars > 5)
{
*str = start;
Free(host);
host = NULL;
return 0;
}
port = Malloc(chars + 1);
memcpy(port, startPort, chars);
port[chars] = '\0';
if (atol(port) > 65535)
{
Free(port);
Free(host);
*str = start;
return 0;
}
out->hostname = host;
out->port = port;
return 1;
}
int
ParseServerPart(char *str, ServerPart *part)
{
/* This is a wrapper behind the internal ParseServerName. */
if (!str || !part)
{
return 0;
}
return ParseServerName(&str, part);
}
void
ServerPartFree(ServerPart part)
{
if (part.hostname)
{
Free(part.hostname);
}
if (part.port)
{
Free(part.port);
}
}
int
ParseCommonID(char *str, CommonID *id)
{
char sigil;
if (!str || !id)
{
return 0;
}
/* There must at least be 2 chararacters: the sigil and a string.*/
if (strlen(str) < 2)
{
return 0;
}
sigil = *str++;
/* Some sigils have the following restriction:
* > MUST NOT exceed 255 bytes (including the # sigil and the domain).
*/
if ((sigil == '#' || sigil == '@') && strlen(str) > 255)
{
return 0;
}
id->sigil = sigil;
id->local = NULL;
id->server.hostname = NULL;
id->server.port = NULL;
switch (sigil)
{
case '$':
/* For event IDs, it depends on the version, so we're just
* accepting it all. */
if (!ParseUserLocalpart(&str, &id->local))
{
return 0;
}
if (*str == ':')
{
(*str)++;
if (!ParseServerName(&str, &id->server))
{
Free(id->local);
id->local = NULL;
return 0;
}
return 1;
}
break;
case '!':
case '#': /* It seems like the localpart should be the same as the
user's: everything, except ':'. */
case '@':
if (!ParseUserLocalpart(&str, &id->local))
{
return 0;
}
if (*str++ != ':')
{
Free(id->local);
id->local = NULL;
return 0;
}
if (!ParseServerName(&str, &id->server))
{
Free(id->local);
id->local = NULL;
return 0;
}
break;
}
return 1;
}
void
CommonIDFree(CommonID id)
{
if (id.local)
{
Free(id.local);
}
ServerPartFree(id.server);
}
int
ValidCommonID(char *str, char sigil)
{
CommonID id;
int ret;
memset(&id, 0, sizeof(CommonID));
if (!str)
{
return 0;
}
ret = ParseCommonID(str, &id) && id.sigil == sigil;
CommonIDFree(id);
return ret;
}
char *
ParserRecomposeServerPart(ServerPart serverPart)
{
if (serverPart.hostname && serverPart.port)
{
return StrConcat(3, serverPart.hostname, ":", serverPart.port);
}
if (serverPart.hostname)
{
return StrDuplicate(serverPart.hostname);
}
return NULL;
}
char *
ParserRecomposeCommonID(CommonID id)
{
char *ret = Malloc(2 * sizeof(char));
ret[0] = id.sigil;
ret[1] = '\0';
if (id.local)
{
char *tmp = StrConcat(2, ret, id.local);
Free(ret);
ret = tmp;
}
if (id.server.hostname)
{
char *server = ParserRecomposeServerPart(id.server);
char *tmp = StrConcat(4, "@", ret, ":", server);
Free(ret);
Free(server);
ret = tmp;
}
return ret;
}
int
ParserServerNameEquals(ServerPart serverPart, char *str)
{
char *idServer;
int ret;
if (!str)
{
return 0;
}
idServer = ParserRecomposeServerPart(serverPart);
ret = StrEquals(idServer, str);
Free(idServer);
return ret;
}

View File

@ -25,8 +25,12 @@
#include <Routes.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Config.h>
#include <Parser.h>
#include <User.h>
ROUTE_IMPL(RouteAliasDirectory, path, argp)
@ -38,20 +42,40 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
HashMap *response;
Db *db = args->matrixArgs->db;
DbRef *ref;
DbRef *ref = NULL;
HashMap *aliases;
HashMap *idObject;
JsonValue *val;
Array *arr;
char *token;
char *msg;
User *user = NULL;
/* TODO: Return HTTP 400 M_INVALID_PARAM if alias is invalid */
CommonID aliasID;
Config config;
aliasID.sigil = '\0';
aliasID.local = NULL;
aliasID.server.hostname = NULL;
aliasID.server.port = NULL;
ConfigLock(db, &config);
if (!ParseCommonID(alias, &aliasID) || aliasID.sigil != '#')
{
msg = "Invalid room alias.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
ref = DbLock(db, 1, "aliases");
if (!ref && !(ref = DbCreate(db, 1, "aliases")))
{
msg = "Unable to access alias database.",
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, "Unable to access alias database.");
response = MatrixErrorCreate(M_UNKNOWN, msg);
goto finish;
}
@ -69,8 +93,9 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
}
else
{
msg = "There is no mapped room ID for this room alias.";
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, "There is no mapped room ID for this room alias.");
response = MatrixErrorCreate(M_NOT_FOUND, msg);
}
break;
case HTTP_PUT:
@ -92,9 +117,21 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
if (HttpRequestMethodGet(args->context) == HTTP_PUT)
{
HashMap *newAlias;
char *id;
char *serverPart;
/* TODO: Validate alias domain and make sure it matches
* server name and is well formed. */
serverPart = ParserRecomposeServerPart(aliasID.server);
if (!StrEquals(serverPart, config.serverName))
{
msg = "Invalid server name.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
Free(serverPart);
goto finish;
}
Free(serverPart);
if (JsonGet(aliases, 2, "alias", alias))
{
@ -111,40 +148,81 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
goto finish;
}
if (!JsonValueAsString(HashMapGet(request, "room_id")))
id = JsonValueAsString(HashMapGet(request, "room_id"));
if (!id)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, "Missing or invalid room_id.");
goto finish;
}
/* TODO: Validate room ID to make sure it is well
* formed. */
if (!ValidCommonID(id, '!'))
{
msg = "Invalid room ID.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
newAlias = HashMapCreate();
HashMapSet(newAlias, "createdBy", JsonValueString(UserGetName(user)));
HashMapSet(newAlias, "id", JsonValueDuplicate(HashMapGet(request, "room_id")));
HashMapSet(newAlias, "id", JsonValueString(id));
HashMapSet(newAlias, "servers", JsonValueArray(ArrayCreate()));
JsonSet(aliases, JsonValueObject(newAlias), 2, "alias", alias);
if (!(idObject = JsonValueAsObject(JsonGet(aliases, 2, "id", id))))
{
arr = ArrayCreate();
idObject = HashMapCreate();
HashMapSet(idObject, "aliases", JsonValueArray(arr));
JsonSet(aliases, JsonValueObject(idObject), 2, "id", id);
}
val = HashMapGet(idObject, "aliases");
arr = JsonValueAsArray(val);
ArrayAdd(arr, JsonValueString(alias));
}
else
{
if (!JsonGet(aliases, 2, "alias", alias))
HashMap *roomAlias;
char *id;
if (!(val = JsonGet(aliases, 2, "alias", alias)))
{
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, "Room alias not found.");
goto finish;
}
roomAlias = JsonValueAsObject(val);
id = StrDuplicate(JsonValueAsString(HashMapGet(roomAlias, "id")));
if (!(UserGetPrivileges(user) & USER_ALIAS) && !StrEquals(UserGetName(user), JsonValueAsString(JsonGet(aliases, 3, "alias", alias, "createdBy"))))
if (!(UserGetPrivileges(user) & USER_ALIAS) && !StrEquals(UserGetName(user), JsonValueAsString(JsonGet(roomAlias, 1, "createdBy"))))
{
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
Free(id);
goto finish;
}
JsonValueFree(HashMapDelete(JsonValueAsObject(HashMapGet(aliases, "alias")), alias));
idObject = JsonValueAsObject(JsonGet(aliases, 2, "id", id));
if (idObject)
{
size_t i;
val = HashMapGet(idObject, "aliases");
arr = JsonValueAsArray(val);
for (i = 0; i < ArraySize(arr); i++)
{
if (StrEquals(JsonValueAsString(ArrayGet(arr, i)), alias))
{
JsonValueFree(ArrayDelete(arr, i));
break;
}
}
}
Free(id);
}
response = HashMapCreate();
@ -156,6 +234,8 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
}
finish:
CommonIDFree(aliasID);
ConfigUnlock(&config);
UserUnlock(user);
DbUnlock(db, ref);
JsonFree(request);

View File

@ -64,7 +64,7 @@ ROUTE_IMPL(RouteFilter, path, argp)
HashMap *response = NULL;
User *user = NULL;
UserId *id = NULL;
CommonID *id = NULL;
char *token = NULL;
char *serverName = NULL;
@ -97,7 +97,7 @@ ROUTE_IMPL(RouteFilter, path, argp)
goto finish;
}
if (!StrEquals(id->server, serverName))
if (!ParserServerNameEquals(id->server, serverName))
{
msg = "Cannot use /filter for non-local users.";
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
@ -119,7 +119,7 @@ ROUTE_IMPL(RouteFilter, path, argp)
goto finish;
}
if (!StrEquals(id->localpart, UserGetName(user)))
if (!StrEquals(id->local, UserGetName(user)))
{
msg = "Unauthorized to use /filter.";
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);

View File

@ -49,7 +49,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
LoginRequest loginRequest;
LoginRequestUserIdentifier userIdentifier;
UserId *userId = NULL;
CommonID *userId = NULL;
Db *db = args->matrixArgs->db;
@ -160,8 +160,8 @@ ROUTE_IMPL(RouteLogin, path, argp)
break;
}
if (!StrEquals(userId->server, config.serverName)
|| !UserExists(db, userId->localpart))
if (!ParserServerNameEquals(userId->server, config.serverName)
|| !UserExists(db, userId->local))
{
msg = "Unknown user ID.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
@ -175,7 +175,7 @@ ROUTE_IMPL(RouteLogin, path, argp)
password = loginRequest.password;
refreshToken = loginRequest.refresh_token;
user = UserLock(db, userId->localpart);
user = UserLock(db, userId->local);
if (!user)
{

View File

@ -25,25 +25,80 @@
#include <Routes.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Db.h>
#include <Matrix.h>
#include <User.h>
ROUTE_IMPL(RouteRoomAliases, path, argp)
{
RouteArgs *args = argp;
char *roomId = ArrayGet(path, 0);
char *token;
char *msg;
HashMap *request = NULL;
HashMap *response = NULL;
HashMap *aliases = NULL;
HashMap *reversealias = NULL;
JsonValue *val;
Db *db = args->matrixArgs->db;
DbRef *ref = NULL;
(void) roomId;
User *user = NULL;
/* TODO: Placeholder; remove. */
goto finish;
if (HttpRequestMethodGet(args->context) != HTTP_GET)
{
msg = "Route only accepts GET.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
response = MatrixGetAccessToken(args->context, &token);
if (response)
{
goto finish;
}
user = UserAuthenticate(db, token);
if (!user)
{
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
goto finish;
}
/* TODO: Check whenever the user is in the room or if its world readable
* once this is implemented instead of just checking for the ALIAS
* privilege. */
if (!(UserGetPrivileges(user) & USER_ALIAS))
{
msg = "User is not allowed to get this room's aliases.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
ref = DbLock(db, 1, "aliases");
aliases = DbJson(ref);
reversealias = JsonValueAsObject(JsonGet(aliases, 2, "id", roomId));
if (!reversealias)
{
/* We do not know about the room ID. */
msg = "Unknown room ID.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
response = HashMapCreate();
val = JsonGet(reversealias, 1, "aliases");
HashMapSet(response, "aliases", JsonValueDuplicate(val));
finish:
DbUnlock(db, ref);
JsonFree(request);
UserUnlock(user);
return response;
}

View File

@ -40,7 +40,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
HashMap *request = NULL;
HashMap *response = NULL;
UserId *userId = NULL;
CommonID *userId = NULL;
User *user = NULL;
char *serverName;
@ -73,7 +73,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
if (strcmp(userId->server, serverName))
if (!ParserServerNameEquals(userId->server, serverName))
{
/* TODO: Implement lookup over federation. */
msg = "User profile endpoint currently doesn't support lookup over "
@ -87,7 +87,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
switch (HttpRequestMethodGet(args->context))
{
case HTTP_GET:
user = UserLock(db, userId->localpart);
user = UserLock(db, userId->local);
if (!user)
{
msg = "Couldn't lock user.";
@ -147,11 +147,11 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
StrEquals(entry, "avatar_url"))
{
/* Check if user has privilege to do that action. */
if (StrEquals(userId->localpart, UserGetName(user)))
if (StrEquals(userId->local, UserGetName(user)))
{
value = JsonValueAsString(HashMapGet(request, entry));
/* TODO: Make UserSetProfile notify other
* parties of the change */
/* TODO: Make UserSetProfile notify other parties of
* the change */
UserSetProfile(user, entry, value);
response = HashMapCreate();
goto finish;

View File

@ -37,9 +37,14 @@ ROUTE_IMPL(RouteVersions, path, argp)
(void) argp;
#define DECLARE_SPEC_VERSION(x) ArrayAdd(versions, JsonValueString(x))
DECLARE_SPEC_VERSION("v1.2");
DECLARE_SPEC_VERSION("v1.3");
DECLARE_SPEC_VERSION("v1.4");
DECLARE_SPEC_VERSION("v1.5");
DECLARE_SPEC_VERSION("v1.6");
/* The curently supported version. */
DECLARE_SPEC_VERSION("v1.7");
/* Declare additional spec version support here. */
#undef DECLARE_SPEC_VERSION

View File

@ -43,7 +43,6 @@ ROUTE_IMPL(RouteWhoami, path, argp)
char *token;
char *userID;
char *deviceID;
char *msg;
Config config;

View File

@ -351,7 +351,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
char *password = JsonValueAsString(HashMapGet(auth, "password"));
HashMap *identifier = JsonValueAsObject(HashMapGet(auth, "identifier"));
char *type;
UserId *userId;
CommonID *userId;
User *user;
if (!password || !identifier)
@ -366,7 +366,8 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
config.serverName);
if (!type || !StrEquals(type, "m.id.user")
|| !userId || !StrEquals(userId->server, config.serverName))
|| !userId
|| !ParserServerNameEquals(userId->server, config.serverName))
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
ret = BuildResponse(flows, db, response, session, dbRef);
@ -374,7 +375,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
goto finish;
}
user = UserLock(db, userId->localpart);
user = UserLock(db, userId->local);
if (!user)
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);

View File

@ -31,6 +31,8 @@
#include <Cytoplasm/Int64.h>
#include <Cytoplasm/UInt64.h>
#include <Parser.h>
#include <string.h>
struct User
@ -882,10 +884,11 @@ finish:
return arr;
}
UserId *
CommonID *
UserIdParse(char *id, char *defaultServer)
{
UserId *userId;
CommonID *userId;
char *server;
if (!id)
{
@ -898,48 +901,38 @@ UserIdParse(char *id, char *defaultServer)
return NULL;
}
userId = Malloc(sizeof(UserId));
userId = Malloc(sizeof(CommonID));
if (!userId)
{
goto finish;
}
memset(userId, 0, sizeof(CommonID));
/* Fully-qualified user ID */
if (*id == '@')
{
char *localStart = id + 1;
char *serverStart = localStart;
while (*serverStart != ':' && *serverStart != '\0')
if (!ParseCommonID(id, userId) || !userId->server.hostname)
{
serverStart++;
}
UserIdFree(userId);
if (*serverStart == '\0')
{
Free(userId);
userId = NULL;
goto finish;
}
*serverStart = '\0';
serverStart++;
userId->localpart = StrDuplicate(localStart);
userId->server = StrDuplicate(serverStart);
}
else
{
/* Treat it as just a localpart */
userId->localpart = StrDuplicate(id);
userId->server = StrDuplicate(defaultServer);
userId->local = StrDuplicate(id);
ParseServerPart(defaultServer, &userId->server);
}
if (!UserHistoricalValidate(userId->localpart, userId->server))
server = ParserRecomposeServerPart(userId->server);
if (!UserHistoricalValidate(userId->local, server))
{
UserIdFree(userId);
userId = NULL;
}
Free(server);
finish:
Free(id);
@ -947,12 +940,11 @@ finish:
}
void
UserIdFree(UserId * id)
UserIdFree(CommonID * id)
{
if (id)
{
Free(id->localpart);
Free(id->server);
CommonIDFree(*id);
Free(id);
}
}

106
src/include/Parser.h Normal file
View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* 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_PARSER_H
#define TELODENDRIA_PARSER_H
/***
* @Nm Parser
* @Nd Functions for dealing with grammars found in Matrix
* @Dd November 25 2023
* @Xr User
*
* The
* .Nm
* API provides an interface for parsing grammars within the
* Matrix specification
*/
/**
* The host[:port] format in a servername.
*/
typedef struct ServerPart {
char *hostname;
char *port;
} ServerPart;
/**
* A common identifier in the form '&local[:server]', where & determines the
* *type* of the identifier.
*/
typedef struct CommonID {
char sigil;
char *local;
ServerPart server;
} CommonID;
/**
* Parses a common identifier, as per the Common Identifier Format as defined
* by the [matrix] specification.
*/
extern int ParseCommonID(char *, CommonID *);
/**
* Parses the server part in a common identifier.
*/
extern int ParseServerPart(char *, ServerPart *);
/**
* Checks whenever the string is a valid common ID with the correct sigil.
*/
extern int ValidCommonID(char *, char);
/**
* Frees a CommonID's values. Note that it doesn't free the CommonID itself.
*/
extern void CommonIDFree(CommonID);
/**
* Frees a ServerPart's values. Note that it doesn't free the ServerPart
* itself, and that
* .Fn CommonIDFree
* automatically deals with its server part.
*/
extern void ServerPartFree(ServerPart);
/**
* Recompose a Common ID into a string which lives in the heap, and must
* therefore be freed with
* .Fn Free .
*/
extern char * ParserRecomposeCommonID(CommonID);
/**
* Recompose a server part into a string which lives in the heap, and must
* therefore be freed with
* .Fn Free .
*/
extern char * ParserRecomposeServerPart(ServerPart);
/**
* Compares whenever a ServerName is equivalent to a server name string.
*/
extern int ParserServerNameEquals(ServerPart, char *);
#endif /* TELODENDRIA_PARSER_H */

View File

@ -43,6 +43,8 @@
#include <Cytoplasm/Db.h>
#include <Cytoplasm/Json.h>
#include <Parser.h>
/**
* Many functions here operate on an opaque user structure.
*/
@ -88,15 +90,6 @@ typedef struct UserLoginInfo
char *refreshToken;
} UserLoginInfo;
/**
* A description of a Matrix user ID.
*/
typedef struct UserId
{
char *localpart;
char *server;
} UserId;
/**
* Take a localpart and domain as separate parameters and validate them
* against the rules of the Matrix specification. The reasion the
@ -303,15 +296,15 @@ extern Array *UserEncodePrivileges(int);
extern int UserDecodePrivilege(const char *);
/**
* Parse either a localpart or a fully qualified Matrix ID. If the
* Parse either a localpart or a fully qualified Matrix common ID. If the
* first argument is a localpart, then the second argument is used as
* the server name.
*/
extern UserId * UserIdParse(char *, char *);
extern CommonID * UserIdParse(char *, char *);
/**
* Free the memory associated with the parsed Matrix ID.
* Frees the user's common ID and the memory allocated for it.
*/
extern void UserIdFree(UserId *);
extern void UserIdFree(CommonID *);
#endif /* TELODENDRIA_USER_H */