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. custom scripts.
- Greatly simplified some endpoint code by using Cytoplasm's `j2s` for - Greatly simplified some endpoint code by using Cytoplasm's `j2s` for
parsing request bodies. parsing request bodies.
- Create a parser API for grammars found in Matrix, and refactor the
User API to use it.
### New Features ### New Features
@ -58,6 +60,10 @@ the ability to change only a subset of the configuration.
- **GET** `/_telodendria/admin/tokens/[token]` - **GET** `/_telodendria/admin/tokens/[token]`
- **POST** `/_telodendria/admin/tokens` - **POST** `/_telodendria/admin/tokens`
- **DELETE** `/_telodendria/admin/tokens/[token]` - **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 ## v0.3.0

View file

@ -16,10 +16,10 @@ registration tokens.
configuration. configuration.
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own - **GRANT_PRIVILEGES:** Allows a user to modify his or her own
privileges or the privileges of other local users. privileges or the privileges of other local users.
- **ALIAS:** Allows a user to modify room aliases created by other - **ALIAS:** Allows a user to modify and see room aliases created by
users. By default, users can only manage their own room aliases, but other users. By default, users can only manage their own room aliases,
an administrator may wish to take over an alias or remove an offensive but an administrator may wish to take over an alias or remove an
alias. offensive alias.
- **PROC_CONTROL:** Allows a user to get statistics on the running - **PROC_CONTROL:** Allows a user to get statistics on the running
process, as well as shutdown and resetart the Telodendria daemon process, as well as shutdown and resetart the Telodendria daemon
itself. Typically this will pair well with **CONFIG**, because there 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 <Routes.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Config.h>
#include <Parser.h>
#include <User.h> #include <User.h>
ROUTE_IMPL(RouteAliasDirectory, path, argp) ROUTE_IMPL(RouteAliasDirectory, path, argp)
@ -38,20 +42,40 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
HashMap *response; HashMap *response;
Db *db = args->matrixArgs->db; Db *db = args->matrixArgs->db;
DbRef *ref; DbRef *ref = NULL;
HashMap *aliases; HashMap *aliases;
HashMap *idObject;
JsonValue *val; JsonValue *val;
Array *arr;
char *token; char *token;
char *msg;
User *user = NULL; 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"); ref = DbLock(db, 1, "aliases");
if (!ref && !(ref = DbCreate(db, 1, "aliases"))) if (!ref && !(ref = DbCreate(db, 1, "aliases")))
{ {
msg = "Unable to access alias database.",
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, "Unable to access alias database."); response = MatrixErrorCreate(M_UNKNOWN, msg);
goto finish; goto finish;
} }
@ -69,8 +93,9 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
} }
else else
{ {
msg = "There is no mapped room ID for this room alias.";
HttpResponseStatus(args->context, HTTP_NOT_FOUND); 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; break;
case HTTP_PUT: case HTTP_PUT:
@ -92,9 +117,21 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
if (HttpRequestMethodGet(args->context) == HTTP_PUT) if (HttpRequestMethodGet(args->context) == HTTP_PUT)
{ {
HashMap *newAlias; HashMap *newAlias;
char *id;
char *serverPart;
/* TODO: Validate alias domain and make sure it matches serverPart = ParserRecomposeServerPart(aliasID.server);
* server name and is well formed. */ 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)) if (JsonGet(aliases, 2, "alias", alias))
{ {
@ -111,40 +148,81 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
goto finish; goto finish;
} }
if (!JsonValueAsString(HashMapGet(request, "room_id"))) id = JsonValueAsString(HashMapGet(request, "room_id"));
if (!id)
{ {
HttpResponseStatus(args->context, HTTP_BAD_REQUEST); HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, "Missing or invalid room_id."); response = MatrixErrorCreate(M_BAD_JSON, "Missing or invalid room_id.");
goto finish; goto finish;
} }
/* TODO: Validate room ID to make sure it is well if (!ValidCommonID(id, '!'))
* formed. */ {
msg = "Invalid room ID.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
newAlias = HashMapCreate(); newAlias = HashMapCreate();
HashMapSet(newAlias, "createdBy", JsonValueString(UserGetName(user))); HashMapSet(newAlias, "createdBy", JsonValueString(UserGetName(user)));
HashMapSet(newAlias, "id", JsonValueDuplicate(HashMapGet(request, "room_id"))); HashMapSet(newAlias, "id", JsonValueString(id));
HashMapSet(newAlias, "servers", JsonValueArray(ArrayCreate())); HashMapSet(newAlias, "servers", JsonValueArray(ArrayCreate()));
JsonSet(aliases, JsonValueObject(newAlias), 2, "alias", alias); 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 else
{ {
if (!JsonGet(aliases, 2, "alias", alias)) HashMap *roomAlias;
char *id;
if (!(val = JsonGet(aliases, 2, "alias", alias)))
{ {
HttpResponseStatus(args->context, HTTP_NOT_FOUND); HttpResponseStatus(args->context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, "Room alias not found."); response = MatrixErrorCreate(M_NOT_FOUND, "Room alias not found.");
goto finish; 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); HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL); response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
Free(id);
goto finish; goto finish;
} }
JsonValueFree(HashMapDelete(JsonValueAsObject(HashMapGet(aliases, "alias")), alias)); 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(); response = HashMapCreate();
@ -156,6 +234,8 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
} }
finish: finish:
CommonIDFree(aliasID);
ConfigUnlock(&config);
UserUnlock(user); UserUnlock(user);
DbUnlock(db, ref); DbUnlock(db, ref);
JsonFree(request); JsonFree(request);

View file

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

View file

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

View file

@ -25,25 +25,80 @@
#include <Routes.h> #include <Routes.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Cytoplasm/Db.h>
#include <Matrix.h>
#include <User.h>
ROUTE_IMPL(RouteRoomAliases, path, argp) ROUTE_IMPL(RouteRoomAliases, path, argp)
{ {
RouteArgs *args = argp; RouteArgs *args = argp;
char *roomId = ArrayGet(path, 0); char *roomId = ArrayGet(path, 0);
char *token;
char *msg;
HashMap *request = NULL;
HashMap *response = NULL; HashMap *response = NULL;
HashMap *aliases = NULL;
HashMap *reversealias = NULL;
JsonValue *val;
Db *db = args->matrixArgs->db; Db *db = args->matrixArgs->db;
DbRef *ref = NULL; DbRef *ref = NULL;
(void) roomId; User *user = NULL;
/* TODO: Placeholder; remove. */ if (HttpRequestMethodGet(args->context) != HTTP_GET)
{
msg = "Route only accepts GET.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish; 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: finish:
DbUnlock(db, ref); DbUnlock(db, ref);
JsonFree(request); UserUnlock(user);
return response; return response;
} }

View file

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

View file

@ -37,9 +37,14 @@ ROUTE_IMPL(RouteVersions, path, argp)
(void) argp; (void) argp;
#define DECLARE_SPEC_VERSION(x) ArrayAdd(versions, JsonValueString(x)) #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_SPEC_VERSION("v1.7");
/* Declare additional spec version support here. */
#undef DECLARE_SPEC_VERSION #undef DECLARE_SPEC_VERSION

View file

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

View file

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

View file

@ -31,6 +31,8 @@
#include <Cytoplasm/Int64.h> #include <Cytoplasm/Int64.h>
#include <Cytoplasm/UInt64.h> #include <Cytoplasm/UInt64.h>
#include <Parser.h>
#include <string.h> #include <string.h>
struct User struct User
@ -882,10 +884,11 @@ finish:
return arr; return arr;
} }
UserId * CommonID *
UserIdParse(char *id, char *defaultServer) UserIdParse(char *id, char *defaultServer)
{ {
UserId *userId; CommonID *userId;
char *server;
if (!id) if (!id)
{ {
@ -898,48 +901,38 @@ UserIdParse(char *id, char *defaultServer)
return NULL; return NULL;
} }
userId = Malloc(sizeof(UserId)); userId = Malloc(sizeof(CommonID));
if (!userId) if (!userId)
{ {
goto finish; goto finish;
} }
memset(userId, 0, sizeof(CommonID));
/* Fully-qualified user ID */ /* Fully-qualified user ID */
if (*id == '@') if (*id == '@')
{ {
char *localStart = id + 1; if (!ParseCommonID(id, userId) || !userId->server.hostname)
char *serverStart = localStart;
while (*serverStart != ':' && *serverStart != '\0')
{ {
serverStart++; UserIdFree(userId);
}
if (*serverStart == '\0')
{
Free(userId);
userId = NULL; userId = NULL;
goto finish; goto finish;
} }
*serverStart = '\0';
serverStart++;
userId->localpart = StrDuplicate(localStart);
userId->server = StrDuplicate(serverStart);
} }
else else
{ {
/* Treat it as just a localpart */ /* Treat it as just a localpart */
userId->localpart = StrDuplicate(id); userId->local = StrDuplicate(id);
userId->server = StrDuplicate(defaultServer); ParseServerPart(defaultServer, &userId->server);
} }
if (!UserHistoricalValidate(userId->localpart, userId->server)) server = ParserRecomposeServerPart(userId->server);
if (!UserHistoricalValidate(userId->local, server))
{ {
UserIdFree(userId); UserIdFree(userId);
userId = NULL; userId = NULL;
} }
Free(server);
finish: finish:
Free(id); Free(id);
@ -947,12 +940,11 @@ finish:
} }
void void
UserIdFree(UserId * id) UserIdFree(CommonID * id)
{ {
if (id) if (id)
{ {
Free(id->localpart); CommonIDFree(*id);
Free(id->server);
Free(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/Db.h>
#include <Cytoplasm/Json.h> #include <Cytoplasm/Json.h>
#include <Parser.h>
/** /**
* Many functions here operate on an opaque user structure. * Many functions here operate on an opaque user structure.
*/ */
@ -88,15 +90,6 @@ typedef struct UserLoginInfo
char *refreshToken; char *refreshToken;
} UserLoginInfo; } 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 * Take a localpart and domain as separate parameters and validate them
* against the rules of the Matrix specification. The reasion the * against the rules of the Matrix specification. The reasion the
@ -303,15 +296,15 @@ extern Array *UserEncodePrivileges(int);
extern int UserDecodePrivilege(const char *); 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 * first argument is a localpart, then the second argument is used as
* the server name. * 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 */ #endif /* TELODENDRIA_USER_H */