forked from Telodendria/Telodendria
[REF] Separate the room functions
Some checks failed
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Has been cancelled
Some checks failed
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Has been cancelled
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Has been cancelled
User.c, you're next.
This commit is contained in:
parent
5b51d9159e
commit
01de46b299
21 changed files with 2324 additions and 1926 deletions
|
@ -1 +0,0 @@
|
|||
Subproject commit 346b912a0633cceac10780b8a103f6c89b5ba89f
|
4
configure
vendored
4
configure
vendored
|
@ -15,7 +15,7 @@ TOOLS="tools/src"
|
|||
SCHEMA="Schema"
|
||||
CYTOPLASM="Cytoplasm"
|
||||
|
||||
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
|
||||
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${SRC} -I${BUILD}"
|
||||
LIBS="-lm -pthread -lCytoplasm"
|
||||
|
||||
|
||||
|
@ -155,7 +155,7 @@ print_obj() {
|
|||
get_deps() {
|
||||
src="$1"
|
||||
|
||||
${CC} -I${INCLUDE} -I${BUILD} $(if [ -n "${CYTOPLASM}" ]; then echo "-I${CYTOPLASM}/include"; fi) -E "$src" \
|
||||
${CC} -I${SRC} -I${INCLUDE} -I${BUILD} $(if [ -n "${CYTOPLASM}" ]; then echo "-I${CYTOPLASM}/include"; fi) -E "$src" \
|
||||
| grep '^#' \
|
||||
| awk '{print $3}' \
|
||||
| cut -d '"' -f 2 \
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
all:
|
||||
sh tools/bin/td
|
||||
|
||||
install:
|
||||
install build/telodendria $(PREFIX)/bin/telodendria
|
||||
find man -name 'telodendria*\.[1-8]' -exec install {} $(PREFIX)/{} \;
|
|
@ -79,6 +79,8 @@ SignalHandler(int signal)
|
|||
for (i = 0; i < ArraySize(httpServers); i++)
|
||||
{
|
||||
HttpServer *server = ArrayGet(httpServers, i);
|
||||
/* TODO: Notify all users, so that the server doesn't have to
|
||||
* await for a sync reply. */
|
||||
|
||||
HttpServerStop(server);
|
||||
}
|
||||
|
|
1922
src/Room.c
1922
src/Room.c
File diff suppressed because it is too large
Load diff
73
src/Room/Info.c
Normal file
73
src/Room/Info.c
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include "Room/internal.h"
|
||||
|
||||
char *
|
||||
RoomIdGet(Room * room)
|
||||
{
|
||||
return room ? room->id : NULL;
|
||||
}
|
||||
|
||||
int
|
||||
RoomVersionGet(Room * room)
|
||||
{
|
||||
return room ? room->version : 0;
|
||||
}
|
||||
|
||||
ServerPart
|
||||
RoomGetCreator(Room *room)
|
||||
{
|
||||
ServerPart ret = { 0 };
|
||||
if (!room)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
return room->creator;
|
||||
}
|
||||
|
||||
Array *
|
||||
RoomPrevEventsGet(Room *room)
|
||||
{
|
||||
HashMap *json;
|
||||
if (!room)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
json = DbJson(room->leaves_ref);
|
||||
return JsonValueAsArray(JsonGet(json, 1, "leaves"));
|
||||
}
|
||||
|
||||
/* TODO: This has it's fair share of problems, as a malicious
|
||||
* homeserver could *easily* fake a PDU depth and put this
|
||||
* function to the depth limit. */
|
||||
uint64_t
|
||||
RoomGetDepth(Room *room)
|
||||
{
|
||||
Array *leaves;
|
||||
HashMap *pdu;
|
||||
size_t i;
|
||||
uint64_t max;
|
||||
if (!room)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
leaves = RoomPrevEventsGet(room);
|
||||
if (!leaves)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
max = 0;
|
||||
for (i = 0; i < ArraySize(leaves); i++)
|
||||
{
|
||||
uint64_t depth;
|
||||
pdu = JsonValueAsObject(ArrayGet(leaves, i));
|
||||
|
||||
depth = JsonValueAsInteger(HashMapGet(pdu, "depth"));
|
||||
if (depth > max)
|
||||
{
|
||||
max = depth;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
286
src/Room/Populate.c
Normal file
286
src/Room/Populate.c
Normal file
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* 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 <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Array.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Parser.h>
|
||||
#include <Config.h>
|
||||
#include <User.h>
|
||||
#include <Room.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Base64.h>
|
||||
#include <Cytoplasm/Util.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
#include <Cytoplasm/Db.h>
|
||||
|
||||
#include <Schema/RoomCreateRequest.h>
|
||||
#include <Schema/PduV1.h>
|
||||
#include <Schema/PduV3.h>
|
||||
|
||||
#include <CanonicalJson.h>
|
||||
#include <Config.h>
|
||||
#include <Parser.h>
|
||||
#include <State.h>
|
||||
#include <Event.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Room/internal.h"
|
||||
|
||||
void
|
||||
RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
|
||||
{
|
||||
CommonID sender;
|
||||
char *sender_str, *key, *version;
|
||||
HashMap *content, *event, *override, *pl_content;
|
||||
Array *initial_states;
|
||||
JsonValue *val;
|
||||
int64_t pl = 100;
|
||||
size_t i;
|
||||
|
||||
char *join_rules_preset = NULL;
|
||||
char *history_visibility_preset = NULL;
|
||||
char *guest_access_preset = NULL;
|
||||
bool trusted_room = false;
|
||||
|
||||
sender.sigil = '@';
|
||||
sender.local = UserGetName(user);
|
||||
sender.server = s;
|
||||
sender_str = ParserRecomposeCommonID(sender);
|
||||
|
||||
/* m.room.create */
|
||||
content = HashMapCreate();
|
||||
if (room->version <= 10)
|
||||
{
|
||||
JsonSet(content, JsonValueString(sender_str), 1, "creator");
|
||||
}
|
||||
|
||||
version = req->room_version ?
|
||||
StrDuplicate(req->room_version) : StrInt(room->version);
|
||||
JsonSet(content, JsonValueString(version), 1, "room_version");
|
||||
Free(version);
|
||||
|
||||
while (HashMapIterate(req->creation_content, &key, (void **) &val))
|
||||
{
|
||||
JsonValue *content_v = HashMapGet(content, key);
|
||||
if (content_v &&
|
||||
!StrEquals(key, "creator") &&
|
||||
!StrEquals(key, "room_version"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
JsonValueFree(HashMapSet(content, key, JsonValueDuplicate(val)));
|
||||
}
|
||||
event = RoomEventCreate(sender_str, "m.room.create", "", content);
|
||||
JsonFree(RoomEventSend(room, event, NULL));
|
||||
JsonFree(event);
|
||||
UserAddJoin(user, room->id);
|
||||
|
||||
/* m.room.member */
|
||||
content = HashMapCreate();
|
||||
JsonSet(content, JsonValueString("join"), 1, "membership");
|
||||
event = RoomEventCreate(sender_str, "m.room.member", sender_str, content);
|
||||
JsonFree(RoomEventSend(room, event, NULL));
|
||||
JsonFree(event);
|
||||
|
||||
/* m.room.power_levels */
|
||||
content = HashMapCreate();
|
||||
JsonSet(
|
||||
content,
|
||||
JsonValueInteger(pl),
|
||||
2, "users", sender_str);
|
||||
override = req->power_level_content_override;
|
||||
while (override && HashMapIterate(override, &key, (void **) &val))
|
||||
{
|
||||
JsonValue *main = HashMapGet(content, key);
|
||||
if (main)
|
||||
{
|
||||
HashMapDelete(content, key);
|
||||
JsonValueFree(main);
|
||||
}
|
||||
HashMapSet(content, key, JsonValueDuplicate(val));
|
||||
}
|
||||
event = RoomEventCreate(sender_str, "m.room.power_levels", "", content);
|
||||
JsonFree(RoomEventSend(room, event, NULL));
|
||||
JsonFree(event);
|
||||
|
||||
/* Presets */
|
||||
switch (req->preset)
|
||||
{
|
||||
case ROOM_CREATE_PUBLIC:
|
||||
join_rules_preset = "public";
|
||||
history_visibility_preset = "shared";
|
||||
guest_access_preset = "forbidden";
|
||||
break;
|
||||
case ROOM_CREATE_TRUSTED:
|
||||
trusted_room = true;
|
||||
/* Fallthrough */
|
||||
case ROOM_CREATE_PRIVATE:
|
||||
join_rules_preset = "invite";
|
||||
history_visibility_preset = "shared";
|
||||
guest_access_preset = "can_join";
|
||||
break;
|
||||
default:
|
||||
switch (req->visibility)
|
||||
{
|
||||
case ROOM_PUBLIC:
|
||||
join_rules_preset = "public";
|
||||
history_visibility_preset = "shared";
|
||||
guest_access_preset = "forbidden";
|
||||
break;
|
||||
case ROOM_PRIVATE:
|
||||
join_rules_preset = "invite";
|
||||
history_visibility_preset = "shared";
|
||||
guest_access_preset = "can_join";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Write out presets */
|
||||
#define SetIfExistent(p,a) do { \
|
||||
if (p##_preset) \
|
||||
{ \
|
||||
content = HashMapCreate(); \
|
||||
JsonSet( \
|
||||
content, \
|
||||
JsonValueString(join_rules_preset) \
|
||||
, 1, a); \
|
||||
event = RoomEventCreate( \
|
||||
sender_str, \
|
||||
"m.room." #p, "", content); \
|
||||
JsonFree(RoomEventSend(room, event, NULL)); \
|
||||
JsonFree(event); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
SetIfExistent(join_rules, "join_rule");
|
||||
SetIfExistent(history_visibility, "history_visibility");
|
||||
SetIfExistent(guest_access, "guest_access");
|
||||
#undef SetIfExistent
|
||||
|
||||
/* User-provided initial states */
|
||||
initial_states = req->initial_state;
|
||||
for (i = 0; i < ArraySize(initial_states); i++)
|
||||
{
|
||||
RoomStateEvent *rse = ArrayGet(initial_states, i);
|
||||
HashMap *rseObject = RoomStateEventToJson(rse);
|
||||
|
||||
if (!HashMapGet(rseObject, "state_key"))
|
||||
{
|
||||
HashMapSet(rseObject, "state_key", JsonValueString(""));
|
||||
}
|
||||
HashMapSet(rseObject, "sender", JsonValueString(sender_str));
|
||||
|
||||
JsonFree(RoomEventSend(room, rseObject, NULL));
|
||||
JsonFree(rseObject);
|
||||
}
|
||||
|
||||
/* Name and topic. */
|
||||
if (req->name)
|
||||
{
|
||||
content = HashMapCreate();
|
||||
JsonSet(content, JsonValueString(req->name), 1, "name");
|
||||
event = RoomEventCreate(sender_str, "m.room.name", "", content);
|
||||
JsonFree(RoomEventSend(room, event, NULL));
|
||||
JsonFree(event);
|
||||
}
|
||||
if (req->topic)
|
||||
{
|
||||
content = HashMapCreate();
|
||||
JsonSet(content, JsonValueString(req->topic), 1, "topic");
|
||||
event = RoomEventCreate(sender_str, "m.room.topic", "", content);
|
||||
JsonFree(RoomEventSend(room, event, NULL));
|
||||
JsonFree(event);
|
||||
}
|
||||
|
||||
/* Custom alias */
|
||||
if (req->room_alias_name && !RoomResolveAlias(room->db, room->id))
|
||||
{
|
||||
CommonID full;
|
||||
char *fullStr, *serverStr;
|
||||
|
||||
full.sigil = '#';
|
||||
full.local = req->room_alias_name;
|
||||
full.server = s;
|
||||
fullStr = ParserRecomposeCommonID(full);
|
||||
|
||||
serverStr = ParserRecomposeServerPart(room->creator);
|
||||
|
||||
RoomAddAlias(room->db, fullStr, room->id, sender_str, serverStr);
|
||||
|
||||
content = HashMapCreate();
|
||||
JsonSet(content, JsonValueString(fullStr), 1, "alias");
|
||||
event = RoomEventCreate(
|
||||
sender_str,
|
||||
"m.room.canonical_alias", "", content);
|
||||
JsonFree(RoomEventSend(room, event, NULL));
|
||||
JsonFree(event);
|
||||
|
||||
Free(fullStr);
|
||||
Free(serverStr);
|
||||
}
|
||||
/* Invites */
|
||||
pl_content = HashMapCreate();
|
||||
JsonSet(pl_content, JsonValueInteger(pl), 2, "users", sender_str);
|
||||
for (i = 0; i < ArraySize(req->invite); i++)
|
||||
{
|
||||
char *user_id = ArrayGet(req->invite, i);
|
||||
|
||||
if (!user_id || !ValidCommonID(user_id, '@'))
|
||||
{
|
||||
/* TODO: Raise error. */
|
||||
break;
|
||||
}
|
||||
|
||||
RoomSendInvite(user, req->is_direct, user_id, room);
|
||||
|
||||
if (trusted_room)
|
||||
{
|
||||
JsonValue *own = JsonValueInteger(pl);
|
||||
JsonSet(pl_content, own, 2, "users", user_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
event = RoomEventCreate(sender_str, "m.room.power_levels", "", pl_content);
|
||||
JsonFree(RoomEventSend(room, event, NULL));
|
||||
JsonFree(event);
|
||||
|
||||
JsonValueFree(JsonSet(
|
||||
DbJson(room->leaves_ref),
|
||||
JsonValueBoolean(req->is_direct), 1, "is_direct"));
|
||||
/* TODO: The rest of the events mandated by the specification on
|
||||
* POST /createRoom, and error management. */
|
||||
Free(sender_str);
|
||||
|
||||
/* TODO: Error management (and invite_3pid, later) */
|
||||
}
|
||||
|
282
src/Room/State.c
Normal file
282
src/Room/State.c
Normal file
|
@ -0,0 +1,282 @@
|
|||
#include "Room/internal.h"
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
|
||||
#include <Config.h>
|
||||
#include <State.h>
|
||||
|
||||
State *
|
||||
RoomStateGet(Room * room)
|
||||
{
|
||||
HashMap *database_state;
|
||||
if (!room)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO: Consider caching the deserialised result, as doing that on a
|
||||
* large state would probably eat up a lot of time! */
|
||||
|
||||
/* TODO: Update the cached state */
|
||||
database_state = DbJson(room->state_ref);
|
||||
return StateDeserialise(database_state);
|
||||
}
|
||||
State *
|
||||
RoomStateGetID(Room * room, char *event_id)
|
||||
{
|
||||
HashMap *event;
|
||||
State *state;
|
||||
if (!room || !event_id)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
event = RoomEventFetch(room, event_id);
|
||||
if (!event)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state = StateResolve(room, event);
|
||||
|
||||
JsonFree(event);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
GetCurrentMembership(Room *room, State *s, char *mxid)
|
||||
{
|
||||
State *state;
|
||||
HashMap *event;
|
||||
char *event_id;
|
||||
|
||||
char *ret;
|
||||
|
||||
if (!room || !mxid)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (s)
|
||||
{
|
||||
state = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = StateCurrent(room);
|
||||
}
|
||||
|
||||
event_id = StateGet(state, "m.room.member", mxid);
|
||||
event = RoomEventFetch(room, event_id);
|
||||
|
||||
if (!s)
|
||||
{
|
||||
StateFree(state);
|
||||
}
|
||||
|
||||
ret = StrDuplicate(
|
||||
JsonValueAsString(JsonGet(event, 2, "content", "membership"))
|
||||
);
|
||||
JsonFree(event);
|
||||
|
||||
return ret;
|
||||
}
|
||||
bool
|
||||
RoomIsEventVisible(Room *room, User *user, char *eventId)
|
||||
{
|
||||
State *state;
|
||||
HashMap *entryV;
|
||||
char *visibility;
|
||||
char *membership, *currentMembership;
|
||||
|
||||
CommonID cid;
|
||||
char *server;
|
||||
char *mxid;
|
||||
|
||||
bool ret = false;
|
||||
if (!room || !user || !eventId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
server = ConfigGetServerName(room->db);
|
||||
cid.sigil = '@';
|
||||
cid.local = UserGetName(user);
|
||||
ParseServerPart(server, &cid.server);
|
||||
mxid = ParserRecomposeCommonID(cid);
|
||||
Free(server);
|
||||
|
||||
state = RoomStateGetID(room, eventId);
|
||||
|
||||
entryV = RoomEventFetch(room,
|
||||
StateGet(state, "m.room.history_visibility", "")
|
||||
);
|
||||
if (!(visibility = JsonValueAsString(
|
||||
JsonGet(entryV, 2, "content", "history_visibility"))))
|
||||
{
|
||||
visibility = "shared";
|
||||
}
|
||||
visibility = StrDuplicate(visibility);
|
||||
JsonFree(entryV);
|
||||
|
||||
membership = GetCurrentMembership(room, state, mxid);
|
||||
currentMembership = GetCurrentMembership(room, NULL, mxid);
|
||||
StateFree(state);
|
||||
|
||||
/* TODO: (for shared) "and the user joined the room at _any_
|
||||
* point after the event was sent, allow."
|
||||
* Aargh. *Why?* */
|
||||
if (StrEquals(visibility, "world_readable"))
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
else if (StrEquals(membership, "join"))
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
else if (StrEquals(visibility, "shared") &&
|
||||
StrEquals(currentMembership, "join")) /* Check the current
|
||||
* membership, as a hack */
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
else if (StrEquals(visibility, "invited") &&
|
||||
StrEquals(membership, "invite"))
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
|
||||
Free(mxid);
|
||||
Free(membership);
|
||||
Free(visibility);
|
||||
Free(currentMembership);
|
||||
ServerPartFree(cid.server);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define PrepareState(room, S, type, key, n) \
|
||||
do \
|
||||
{ \
|
||||
id_##n = StateGet(S, type, key); \
|
||||
if (!id_##n) \
|
||||
{ \
|
||||
goto finish; \
|
||||
} \
|
||||
n = RoomEventFetch(room, id_##n); \
|
||||
if (!n) \
|
||||
{ \
|
||||
goto finish; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
#define FinishState(name) \
|
||||
if (name) \
|
||||
{ \
|
||||
JsonFree(name); \
|
||||
} \
|
||||
return ret
|
||||
bool
|
||||
RoomIsJoinRule(Room * room, State *state, char *jr)
|
||||
{
|
||||
HashMap *joinrule = NULL;
|
||||
char *id_joinrule = NULL;
|
||||
bool ret = false;
|
||||
|
||||
if (!room || !state || !jr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PrepareState(room, state, "m.room.join_rules", "", joinrule);
|
||||
ret = StrEquals(
|
||||
JsonValueAsString(JsonGet(joinrule, 2, "content", "join_rule")),
|
||||
jr);
|
||||
finish:
|
||||
FinishState(joinrule);
|
||||
}
|
||||
/* Verifies if user has a specific membership before [e_id] in the room. */
|
||||
bool
|
||||
RoomUserHasMembership(Room * room, State *state, char *user, char *mbr)
|
||||
{
|
||||
HashMap *membership = NULL;
|
||||
char *id_membership = NULL;
|
||||
char *membership_value;
|
||||
bool ret = false;
|
||||
if (!room || !state || !user || !mbr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PrepareState(room, state, "m.room.member", user, membership);
|
||||
|
||||
membership_value =
|
||||
JsonValueAsString(JsonGet(membership, 2, "content", "membership"));
|
||||
|
||||
ret = StrEquals(membership_value, mbr);
|
||||
|
||||
finish:
|
||||
FinishState(membership);
|
||||
}
|
||||
/* Computes the smallest PL needed to do something somewhere */
|
||||
int64_t
|
||||
RoomMinPL(Room * room, State *state, char *type, char *act)
|
||||
{
|
||||
HashMap *pl = NULL;
|
||||
JsonValue *val;
|
||||
char *id_pl;
|
||||
int64_t ret = 0, def = 0;
|
||||
if (!room || !state || !act)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
PrepareState(room, state, "m.room.power_levels", "", pl);
|
||||
|
||||
/* Every other act has a minimum PL of 0 */
|
||||
def = 0;
|
||||
if (StrEquals(act, "ban") ||
|
||||
StrEquals(act, "kick") ||
|
||||
StrEquals(act, "redact") ||
|
||||
StrEquals(act, "state_default") ||
|
||||
(StrEquals(type, "notifications") && StrEquals(act, "room")))
|
||||
{
|
||||
def = 50;
|
||||
}
|
||||
|
||||
if (!type)
|
||||
{
|
||||
val = JsonGet(pl, 2, "content", act);
|
||||
}
|
||||
else
|
||||
{
|
||||
val = JsonGet(pl, 3, "content", type, act);
|
||||
}
|
||||
|
||||
ret = ParsePL(val, def);
|
||||
finish:
|
||||
FinishState(pl);
|
||||
}
|
||||
/* Finds the power level of an user at a state. */
|
||||
/* TODO: The creator should have PL100 by default. */
|
||||
int64_t
|
||||
RoomUserPL(Room * room, State *state, char *user)
|
||||
{
|
||||
HashMap *pl = NULL;
|
||||
char *id_pl;
|
||||
int64_t ret = 0, def = 0;
|
||||
|
||||
if (!room || !state || !user)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
PrepareState(room, state, "m.room.power_levels", "", pl);
|
||||
|
||||
def = RoomMinPL(room, state, NULL, "users_default");
|
||||
ret = ParsePL(JsonGet(pl, 3, "content", "users", user), def);
|
||||
|
||||
finish:
|
||||
FinishState(pl);
|
||||
}
|
65
src/Room/V1/Auth/Alias.c
Normal file
65
src/Room/V1/Auth/Alias.c
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include "Room/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
static bool
|
||||
VerifyServers(char *id1, char *id2)
|
||||
{
|
||||
CommonID cid1;
|
||||
CommonID cid2;
|
||||
|
||||
char *str1;
|
||||
char *str2;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
if (!ParseCommonID(id1, &cid1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!ParseCommonID(id2, &cid2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
str1 = ParserRecomposeServerPart(cid1.server);
|
||||
str2 = ParserRecomposeServerPart(cid2.server);
|
||||
|
||||
if (StrEquals(str1, str2))
|
||||
{
|
||||
ret = true;
|
||||
goto end;
|
||||
}
|
||||
end:
|
||||
Free(str1);
|
||||
Free(str2);
|
||||
CommonIDFree(cid1);
|
||||
CommonIDFree(cid2);
|
||||
return ret;
|
||||
}
|
||||
bool
|
||||
AuthoriseAliasV1(PduV1 pdu, char **errp)
|
||||
{
|
||||
/* Step 4.1: If event has no state_key, reject. */
|
||||
if (!pdu.state_key || StrEquals(pdu.state_key, ""))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 4.1 fail: no state key in the alias";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/* Step 4.2: If sender's domain doesn't matches state_key, reject. */
|
||||
if (!VerifyServers(pdu.state_key, pdu.sender))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 4.2 fail: alias domain doesnt match statekey";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 4.3: Otherwise, allow. */
|
||||
return true;
|
||||
}
|
174
src/Room/V1/Auth/Auth.c
Normal file
174
src/Room/V1/Auth/Auth.c
Normal file
|
@ -0,0 +1,174 @@
|
|||
#include "Room/internal.h"
|
||||
#include "Room/V1/Auth/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
static bool
|
||||
VerifyServers(char *id1, char *id2)
|
||||
{
|
||||
CommonID cid1;
|
||||
CommonID cid2;
|
||||
|
||||
char *str1;
|
||||
char *str2;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
if (!ParseCommonID(id1, &cid1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!ParseCommonID(id2, &cid2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
str1 = ParserRecomposeServerPart(cid1.server);
|
||||
str2 = ParserRecomposeServerPart(cid2.server);
|
||||
|
||||
if (StrEquals(str1, str2))
|
||||
{
|
||||
ret = true;
|
||||
goto end;
|
||||
}
|
||||
end:
|
||||
Free(str1);
|
||||
Free(str2);
|
||||
CommonIDFree(cid1);
|
||||
CommonIDFree(cid2);
|
||||
return ret;
|
||||
}
|
||||
bool
|
||||
RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state, char **errp)
|
||||
{
|
||||
HashMap *create_event;
|
||||
char *create_event_id;
|
||||
JsonValue *federate;
|
||||
int64_t pdu_pl = RoomUserPL(room, state, pdu.sender);
|
||||
int64_t event_pl = RoomMinPL(room,state, "events", pdu.type);
|
||||
/* Step 1: If m.room.create */
|
||||
if (StrEquals(pdu.type, "m.room.create"))
|
||||
{
|
||||
return AuthoriseCreateV1(pdu, errp);
|
||||
}
|
||||
|
||||
/* Step 2: Considering the event's auth_events. */
|
||||
if (!ConsiderAuthEventsV1(room, pdu, errp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 3: If the content of the m.room.create event in the room state
|
||||
* has the property m.federate set to false, and the sender domain of
|
||||
* the event does not match the sender domain of the create event,
|
||||
* reject.
|
||||
*/
|
||||
create_event_id = StateGet(state, "m.room.create", "");
|
||||
if (!state || !create_event_id)
|
||||
{
|
||||
/* At this point, [create_event_id] has to exist */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "No creation event in the state. Needless to say, "
|
||||
"your room is done for.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
create_event = RoomEventFetch(room, create_event_id);
|
||||
federate = JsonGet(create_event, 2, "content", "m.federate");
|
||||
if (JsonValueType(federate) == JSON_BOOLEAN)
|
||||
{
|
||||
if (!JsonValueAsBoolean(federate))
|
||||
{
|
||||
char *c_sender =
|
||||
JsonValueAsString(JsonGet(create_event, 1, "sender"));
|
||||
char *p_sender = pdu.sender;
|
||||
if (!VerifyServers(c_sender, p_sender))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Trying to access a room with m.federate off.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
JsonFree(create_event);
|
||||
|
||||
/* Step 4: If type is m.room.aliases */
|
||||
if (StrEquals(pdu.type, "m.room.aliases"))
|
||||
{
|
||||
return AuthoriseAliasV1(pdu, errp);
|
||||
}
|
||||
|
||||
/* Step 5: If type is m.room.member */
|
||||
if (StrEquals(pdu.type, "m.room.member"))
|
||||
{
|
||||
return AuthoriseMemberV1(room, pdu, state, errp);
|
||||
}
|
||||
/* Step 6: If the sender's current membership state is not join, reject. */
|
||||
if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/* Step 7: If type is m.room.third_party_invite */
|
||||
if (StrEquals(pdu.type, "m.room.third_party_invite"))
|
||||
{
|
||||
/* Allow if and only if sender's current power level is greater than
|
||||
* or equal to the invite level */
|
||||
int64_t min_pl = RoomMinPL(room, state, NULL, "invite");
|
||||
|
||||
return pdu_pl >= min_pl;
|
||||
}
|
||||
|
||||
/* Step 8: If the event type's required power level is greater than the
|
||||
* sender's power level, reject. */
|
||||
if (event_pl > pdu_pl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step (9): If the event has a state_key that starts with an @ and does
|
||||
* not match the sender, reject. */
|
||||
if (pdu.state_key && *pdu.state_key == '@')
|
||||
{
|
||||
if (!StrEquals(pdu.state_key, pdu.sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 10: If type is m.room.power_levels */
|
||||
if (StrEquals(pdu.type, "m.room.power_levels"))
|
||||
{
|
||||
return AuthorisePowerLevelsV1(room, pdu, state);
|
||||
}
|
||||
|
||||
/* Step 11: If type is m.room.redaction */
|
||||
if (StrEquals(pdu.type, "m.room.redaction"))
|
||||
{
|
||||
int64_t min_pl = RoomMinPL(room, state, NULL, "redact");
|
||||
|
||||
/* Step 11.1: If the sender's power level is greater than or equal
|
||||
* to the redact level, allow. */
|
||||
if (pdu_pl >= min_pl)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step 11.2: If the domain of the event_id of the event being
|
||||
* redacted is the same as the domain of the event_id of the
|
||||
* m.room.redaction, allow. */
|
||||
if (pdu.redacts && VerifyServers(pdu.redacts, pdu.event_id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step 11.3: Otherwise, reject. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 12: Otherwise, allow. */
|
||||
return true;
|
||||
}
|
183
src/Room/V1/Auth/AuthEvents.c
Normal file
183
src/Room/V1/Auth/AuthEvents.c
Normal file
|
@ -0,0 +1,183 @@
|
|||
#include "Room/internal.h"
|
||||
#include "Room/V1/Auth/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Cytoplasm/Log.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
static bool
|
||||
VerifyPDUV1(PduV1 *auth_pdu)
|
||||
{
|
||||
/* TODO: The PDU could come from an unknown source, which may lack
|
||||
* the tools to verify softfailing(or we may not trust them)*/
|
||||
/* TODO:
|
||||
* https://spec.matrix.org/v1.7/server-server-api/
|
||||
* #checks-performed-on-receipt-of-a-pdu */
|
||||
if (RoomIsRejectedV1(*auth_pdu))
|
||||
{
|
||||
Log(LOG_ERR, "Auth PDU has been rejected.");
|
||||
return false;
|
||||
}
|
||||
if (RoomIsSoftfailedV1(*auth_pdu))
|
||||
{
|
||||
Log(LOG_ERR, "Auth PDU has been softfailed.");
|
||||
}
|
||||
return true; /* This only shows whenever an event was rejected, not
|
||||
* soft-failed */
|
||||
}
|
||||
static bool
|
||||
ValidAuthEventV1(PduV1 *auth_pdu, PduV1 *pdu)
|
||||
{
|
||||
if (IsState(auth_pdu, "m.room.create", ""))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (IsState(auth_pdu, "m.room.power_levels", ""))
|
||||
{
|
||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
||||
return true;
|
||||
}
|
||||
if (IsState(auth_pdu, "m.room.member", pdu->sender))
|
||||
{
|
||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
||||
return true;
|
||||
}
|
||||
if (StrEquals(pdu->type, "m.room.member"))
|
||||
{
|
||||
char *membership =
|
||||
JsonValueAsString(JsonGet(pdu->content, 1, "membership"));
|
||||
JsonValue *third_pid =
|
||||
JsonGet(pdu->content, 1, "third_party_invite");
|
||||
|
||||
/* The PDU's state_key is the target. So we check if if the
|
||||
* auth PDU would count as the target's membership. Was very fun to
|
||||
* find that out when I wanted to kick my 'yukari' alt. */
|
||||
if (IsState(auth_pdu, "m.room.member", pdu->state_key))
|
||||
{
|
||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
||||
return true;
|
||||
}
|
||||
if ((StrEquals(membership, "join") ||
|
||||
StrEquals(membership, "invite")) &&
|
||||
IsState(auth_pdu, "m.room.join_rules",""))
|
||||
{
|
||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
||||
return true;
|
||||
}
|
||||
if (StrEquals(membership, "invite") && third_pid)
|
||||
{
|
||||
HashMap *tpid = JsonValueAsObject(third_pid);
|
||||
JsonValue *token =
|
||||
JsonGet(tpid, 2, "signed", "token");
|
||||
char *token_str = JsonValueAsString(token);
|
||||
if (IsState(auth_pdu, "m.room.third_party_invite", token_str))
|
||||
{
|
||||
/* TODO: Check if it is the latest. */
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/* V1 simply doesn't have the concept of restricted rooms,
|
||||
* so we can safely skip this one for this function. */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool
|
||||
ConsiderAuthEventsV1(Room * room, PduV1 pdu, char **errp)
|
||||
{
|
||||
char *ignored;
|
||||
size_t i;
|
||||
bool room_create = false;
|
||||
HashMap *state_keytype;
|
||||
state_keytype = HashMapCreate();
|
||||
for (i = 0; i < ArraySize(pdu.auth_events); i++)
|
||||
{
|
||||
char *event_id = JsonValueAsString(ArrayGet(pdu.auth_events, i));
|
||||
HashMap *event = RoomEventFetch(room, event_id);
|
||||
PduV1 auth_pdu = { 0 };
|
||||
|
||||
char *key_type_id;
|
||||
|
||||
if (!PduV1FromJson(event, &auth_pdu, &ignored))
|
||||
{
|
||||
JsonFree(event);
|
||||
HashMapFree(state_keytype);
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Couldn't parse an auth event";
|
||||
}
|
||||
return false; /* Yeah... we aren't doing that. */
|
||||
}
|
||||
|
||||
/* TODO: Find a better way to do this. Using HashMaps as sets
|
||||
* *works*, but it's not the best of ideas here. Also, we're using
|
||||
* strings to compare things, which yeah. */
|
||||
key_type_id = StrConcat(3, auth_pdu.type, ",", auth_pdu.state_key);
|
||||
|
||||
if (HashMapGet(state_keytype, key_type_id))
|
||||
{
|
||||
/* Duplicate found! We actually ignore it's actual value. */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Duplicate auth event was found";
|
||||
}
|
||||
|
||||
JsonFree(event);
|
||||
PduV1Free(&auth_pdu);
|
||||
|
||||
HashMapFree(state_keytype);
|
||||
Free(key_type_id);
|
||||
return false;
|
||||
}
|
||||
/* Whenever event is valid or not really doesn't matter, as we're
|
||||
* not using it's value anywhere. */
|
||||
HashMapSet(state_keytype, key_type_id, event);
|
||||
Free(key_type_id);
|
||||
|
||||
/* Step 2.2: If there are entries whose type and state_key don't
|
||||
* match those specified by the auth events selection algorithm
|
||||
* described in the server specification, reject. */
|
||||
if (!ValidAuthEventV1(&auth_pdu, &pdu))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Invalid authevent given.";
|
||||
}
|
||||
JsonFree(event);
|
||||
PduV1Free(&auth_pdu);
|
||||
HashMapFree(state_keytype);
|
||||
return false;
|
||||
}
|
||||
/* Step 2.3: If there are entries which were themselves rejected
|
||||
* under the checks performed on receipt of a PDU, reject.
|
||||
* TODO */
|
||||
if (!VerifyPDUV1(&auth_pdu))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Event depends on rejected PDUs";
|
||||
}
|
||||
PduV1Free(&auth_pdu);
|
||||
JsonFree(event);
|
||||
HashMapFree(state_keytype);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 2.4: If there is no m.room.create event among the entries,
|
||||
* reject. */
|
||||
if (!room_create && IsState(&auth_pdu, "m.room.create", ""))
|
||||
{
|
||||
room_create = true; /* Here, we check for the opposite. */
|
||||
}
|
||||
|
||||
JsonFree(event);
|
||||
PduV1Free(&auth_pdu);
|
||||
}
|
||||
HashMapFree(state_keytype);
|
||||
if (!room_create && errp)
|
||||
{
|
||||
*errp = "Room creation event was not in the PDU's auth events";
|
||||
}
|
||||
return room_create; /* Step 2.4 is actually done here. */
|
||||
}
|
68
src/Room/V1/Auth/Create.c
Normal file
68
src/Room/V1/Auth/Create.c
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include "Room/internal.h"
|
||||
#include "Room/V1/Auth/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
bool
|
||||
AuthoriseCreateV1(PduV1 pdu, char **errp)
|
||||
{
|
||||
bool ret = true;
|
||||
CommonID sender = { 0 }, room_id = { 0 };
|
||||
char *sender_serv = NULL, *id_serv = NULL;
|
||||
|
||||
if (ArraySize(pdu.auth_events) > 0)
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Room creation event has auth events";
|
||||
}
|
||||
ret = false;
|
||||
goto finish;
|
||||
}
|
||||
if (!ParseCommonID(pdu.room_id, &room_id) ||
|
||||
!ParseCommonID(pdu.sender, &sender))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Couldn't parse the sender/room ID";
|
||||
}
|
||||
ret = false;
|
||||
goto finish;
|
||||
}
|
||||
sender_serv = ParserRecomposeServerPart(sender.server);
|
||||
id_serv = ParserRecomposeServerPart(room_id.server);
|
||||
if (!StrEquals(sender_serv, id_serv))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Room is not properly namespaced";
|
||||
}
|
||||
ret = false;
|
||||
goto finish;
|
||||
}
|
||||
/* TODO: Check room_version as in step 1.3 */
|
||||
if (!HashMapGet(pdu.content, "creator"))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Room creation event has auth events";
|
||||
}
|
||||
ret = false;
|
||||
goto finish;
|
||||
}
|
||||
finish:
|
||||
if (sender_serv)
|
||||
{
|
||||
Free(sender_serv);
|
||||
}
|
||||
if (id_serv)
|
||||
{
|
||||
Free(id_serv);
|
||||
}
|
||||
CommonIDFree(sender);
|
||||
CommonIDFree(room_id);
|
||||
return ret;
|
||||
}
|
||||
|
371
src/Room/V1/Auth/Member.c
Normal file
371
src/Room/V1/Auth/Member.c
Normal file
|
@ -0,0 +1,371 @@
|
|||
#include "Room/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
static bool
|
||||
AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state, char **errp)
|
||||
{
|
||||
int64_t invite_level;
|
||||
int64_t pdu_level;
|
||||
JsonValue *third_pi;
|
||||
/* Step 5.3.1: If content has a third_party_invite property */
|
||||
if ((third_pi = JsonGet(pdu.content, 1, "third_party_invite")))
|
||||
{
|
||||
JsonValue *signed_val, *mxid, *token;
|
||||
HashMap *third_pi_obj = JsonValueAsObject(third_pi), *signed_obj;
|
||||
HashMap *third_pi_event;
|
||||
char *third_pi_id, *thirdpi_event_sender;
|
||||
/* Step 5.3.1.1: If target user is banned, reject. */
|
||||
if (RoomUserHasMembership(room, state, pdu.state_key, "ban"))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.1.1 fail: target is banned";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/* Step 5.3.1.2: If content.third_party_invite does not have a signed
|
||||
* property, reject. */
|
||||
if (!(signed_val = JsonGet(third_pi_obj, 1, "signed")))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.1.2 fail: unsigned 3PII";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
signed_obj = JsonValueAsObject(signed_val);
|
||||
|
||||
/* Step 5.3.1.3: If signed does not have mxid and token properties,
|
||||
* reject. */
|
||||
if (!(mxid = JsonGet(signed_obj, 1, "mxid")))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.1.3 fail: no MXID in 3PII";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!(token = JsonGet(signed_obj, 1, "token")))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.1.3 fail: no token in 3PII";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 5.3.1.4: If mxid does not match state_key, reject. */
|
||||
if (!StrEquals(JsonValueAsString(mxid), pdu.state_key))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.1.4 fail: 3PII's MXID != state_key";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 5.3.1.5: If there is no m.room.third_party_invite event
|
||||
* in the current room state with state_key matching token, reject. */
|
||||
if (!(third_pi_id = StateGet(
|
||||
state,
|
||||
"m.room.third_party_invite", JsonValueAsString(token))))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.1.5 fail: no proper 3PII event";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
third_pi_event = RoomEventFetch(room, third_pi_id);
|
||||
|
||||
/* Step 5.3.1.6: If sender does not match sender of the
|
||||
* m.room.third_party_invite, reject. */
|
||||
thirdpi_event_sender = JsonValueAsString(JsonGet(third_pi_event, 1, "sender"));
|
||||
if (!StrEquals(thirdpi_event_sender, pdu.sender))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.1.6 fail: sender does not match 3PII";
|
||||
}
|
||||
JsonFree(third_pi_event);
|
||||
return false;
|
||||
}
|
||||
JsonFree(third_pi_event);
|
||||
|
||||
/* TODO:
|
||||
* Step 5.3.1.7: If any signature in signed matches any public key in
|
||||
* the m.room.third_party_invite event, allow.
|
||||
*
|
||||
* The public keys are in content of m.room.third_party_invite as:
|
||||
* - A single public key in the public_key property.
|
||||
* - A list of public keys in the public_keys property. */
|
||||
|
||||
/* Step 5.3.1.8: Otherwise, reject. */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.1.8 fail: signature check do not match";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 5.3.2: If the sender's current membership state is not join,
|
||||
* reject. */
|
||||
if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.2 fail: sender is not 'join'ed";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/* Step 5.3.3: If target user's current membership state is join or ban, reject. */
|
||||
if (RoomUserHasMembership(room, state, pdu.state_key, "join") ||
|
||||
RoomUserHasMembership(room, state, pdu.state_key, "ban"))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.3 fail: target is 'join'|'ban'd";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 5.3.4: If the sender's power level is greater than or equal to the
|
||||
* invite level, allow. */
|
||||
invite_level = RoomMinPL(room, state, NULL, "invite");
|
||||
pdu_level = RoomUserPL(room, state, pdu.sender);
|
||||
if (pdu_level >= invite_level)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
/* Step 5.3.5: Otherwise, reject. */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.3.5 fail: sender has no permissions to do so";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool
|
||||
AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, State *state, char **errp)
|
||||
{
|
||||
int64_t ban_level = RoomMinPL(room, state, NULL, "ban");
|
||||
int64_t kick_level = RoomMinPL(room, state, NULL, "kick");
|
||||
int64_t sender_level = RoomUserPL(room, state, pdu.sender);
|
||||
int64_t target_level = RoomUserPL(room, state, pdu.state_key);
|
||||
|
||||
/* Step 5.4.1: If the sender matches state_key, allow if and only if
|
||||
* that user's current membership state is invite or join. */
|
||||
if (StrEquals(pdu.sender, pdu.state_key))
|
||||
{
|
||||
bool flag =
|
||||
RoomUserHasMembership(room, state, pdu.sender, "invite") ||
|
||||
RoomUserHasMembership(room, state, pdu.sender, "join");
|
||||
if (!flag && errp)
|
||||
{
|
||||
*errp = "Step 5.4.1 fail: user tries to leave but is "
|
||||
"~'invite' AND ~'join'.";
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/* Step 5.4.2: If the sender's current membership state is not join,
|
||||
* reject. */
|
||||
if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.4.2 fail: sender tries to kick but is "
|
||||
"~'invite'.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 5.4.3: If the target user's current membership state is ban,
|
||||
* and the sender's power level is less than the ban level, reject. */
|
||||
if (RoomUserHasMembership(room, state, pdu.state_key, "ban") &&
|
||||
sender_level < ban_level)
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.4.3 fail: sender tries to unban but has no "
|
||||
"permissions to do so.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 5.4.4: If the sender's power level is greater than or equal to
|
||||
* the kick level, and the target user's power level is less than the
|
||||
* sender's power level, allow. */
|
||||
if ((sender_level >= kick_level) && target_level < sender_level)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step 5.4.5: Otherwise, reject. */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.4.5 fail: sender tried to kick but has no "
|
||||
"permissions to do so.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool
|
||||
AuthorizeBanMembershipV1(Room * room, PduV1 pdu, State *state, char **errp)
|
||||
{
|
||||
int64_t ban_pl, pdu_pl, target_pl;
|
||||
|
||||
/* Step 5.5.1: If the sender's current membership state is not join, reject. */
|
||||
if (!RoomUserHasMembership(room, state, pdu.sender, "join"))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.5.1 fail: sender tries to ban but is not "
|
||||
"'join'ed";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 5.5.2: If the sender's power level is greater than or equal
|
||||
* to the ban level, and the target user's power level is less than
|
||||
* the sender's power level, allow. */
|
||||
ban_pl = RoomMinPL(room, state, NULL, "ban");
|
||||
pdu_pl = RoomUserPL(room, state, pdu.sender);
|
||||
target_pl = RoomUserPL(room, state, pdu.state_key);
|
||||
if ((pdu_pl >= ban_pl) && (target_pl < pdu_pl))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step 5.5.3: Otherwise, reject. */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.5.3 fail: sender tries to ban has no permissions to "
|
||||
"do so";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool
|
||||
AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, State *state, char **errp)
|
||||
{
|
||||
/* Step 5.2.1: If the only previous event is an m.room.create and the
|
||||
* state_key is the creator, allow. */
|
||||
Array *prev = pdu.prev_events;
|
||||
if (ArraySize(prev) == 1)
|
||||
{
|
||||
/* Interperet prev properly, as a list of JsonObjects. */
|
||||
char *prev_id = JsonValueAsString(ArrayGet(prev, 0));
|
||||
HashMap *prev_event = RoomEventFetch(room, prev_id);
|
||||
bool flag = false;
|
||||
|
||||
if (prev_event)
|
||||
{
|
||||
char *type = JsonValueAsString(HashMapGet(prev_event, "type"));
|
||||
char *sender = JsonValueAsString(HashMapGet(prev_event, "sender"));
|
||||
if (StrEquals(type, "m.room.create") &&
|
||||
StrEquals(sender, pdu.state_key))
|
||||
{
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
JsonFree(prev_event);
|
||||
|
||||
if (flag)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 5.2.2: If the sender does not match state_key, reject. */
|
||||
if (!StrEquals(pdu.sender, pdu.state_key))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.2.2 fail: sender does not match statekey "
|
||||
"on 'join'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/* Step 5.2.3: If the sender is banned, reject. */
|
||||
if (RoomUserHasMembership(room, state, pdu.sender, "ban"))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.2.2 fail: sender is banned on 'join'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 5.2.4: If the join_rule is invite then allow if membership
|
||||
* state is invite or join. */
|
||||
if (RoomIsJoinRule(room, state, "invite") &&
|
||||
(RoomUserHasMembership(room, state, pdu.sender, "invite") ||
|
||||
RoomUserHasMembership(room, state, pdu.sender, "join")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
/* Step 5.2.5: If the join_rule is public, allow. */
|
||||
if (RoomIsJoinRule(room, state, "public"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step 5.2.6: Otherwise, reject. */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.2.6 fail: join_rule does not allow 'join'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
AuthoriseMemberV1(Room * room, PduV1 pdu, State *state, char **errp)
|
||||
{
|
||||
JsonValue *membership;
|
||||
char *membership_str;
|
||||
/* Step 5.1: If there is no state_key property, or no membership
|
||||
* property in content, reject. */
|
||||
if (!pdu.state_key ||
|
||||
StrEquals(pdu.state_key, "") ||
|
||||
!(membership = JsonGet(pdu.content, 1, "membership")))
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.1 fail: broken membership's statekey/membership";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (JsonValueType(membership) != JSON_STRING)
|
||||
{
|
||||
/* Also check for the type */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Step 5.1 fail: broken membership's membership";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
membership_str = JsonValueAsString(membership);
|
||||
#define JumpIfMembership(mem, func) do { \
|
||||
if (StrEquals(membership_str, mem)) \
|
||||
{ \
|
||||
return func(room, pdu, state, errp); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Step 5.2: If membership is join. */
|
||||
JumpIfMembership("join", AuthorizeJoinMembershipV1);
|
||||
|
||||
/* Step 5.3: If membership is invite. */
|
||||
JumpIfMembership("invite", AuthorizeInviteMembershipV1);
|
||||
|
||||
/* Step 5.4: If membership is leave. */
|
||||
JumpIfMembership("leave", AuthorizeLeaveMembershipV1);
|
||||
|
||||
/* Step 5.5: If membership is ban. */
|
||||
JumpIfMembership("ban", AuthorizeBanMembershipV1);
|
||||
|
||||
/* Step 5.6: Otherwise, the membership is unknown. Reject. */
|
||||
return false;
|
||||
#undef JumpIfMembership
|
||||
}
|
182
src/Room/V1/Auth/PL.c
Normal file
182
src/Room/V1/Auth/PL.c
Normal file
|
@ -0,0 +1,182 @@
|
|||
#include "Room/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
bool
|
||||
AuthorisePowerLevelsV1(Room * room, PduV1 pdu, State *state)
|
||||
{
|
||||
/* Step 10.1: If the users property in content is not an object with
|
||||
* keys that are valid user IDs with values that are integers
|
||||
* (or a string that is an integer), reject. */
|
||||
JsonValue *users = JsonGet(pdu.content, 1, "users");
|
||||
HashMap *users_o, *prev_plevent;
|
||||
|
||||
char *user_id, *prev_pl_id, *ev_type;
|
||||
JsonValue *power_level, *ev_obj;
|
||||
|
||||
bool flag = true;
|
||||
int64_t userpl = RoomUserPL(room, state, pdu.sender);
|
||||
HashMap *event_obj;
|
||||
if (JsonValueType(users) != JSON_OBJECT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
users_o = JsonValueAsObject(users);
|
||||
while (HashMapIterate(users_o, &user_id, (void **) &power_level))
|
||||
{
|
||||
CommonID as_cid;
|
||||
if (!flag)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!ParseCommonID(user_id, &as_cid))
|
||||
{
|
||||
flag = false;
|
||||
}
|
||||
if (as_cid.sigil != '@')
|
||||
{
|
||||
CommonIDFree(as_cid);
|
||||
flag = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Verify powerlevels.
|
||||
* We'll use INT64_MAX as a sentinel value, as this isn't
|
||||
* a valid powervalue for the specification. */
|
||||
if (ParsePL(power_level, INT64_MAX) == INT64_MAX)
|
||||
{
|
||||
flag = false;
|
||||
CommonIDFree(as_cid);
|
||||
continue;
|
||||
}
|
||||
CommonIDFree(as_cid);
|
||||
}
|
||||
|
||||
/* HashMapIterate does not support breaking, so we just set a
|
||||
* flag to be used. */
|
||||
if (!flag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 10.2: If there is no previous m.room.power_levels event
|
||||
* in the room, allow. */
|
||||
if (!(prev_pl_id = StateGet(state, "m.room.power_levels", "")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step 10.3: For the properties users_default, events_default,
|
||||
* state_default, ban, redact, kick, invite, check if they were
|
||||
* added, changed or removed. For each found alteration: */
|
||||
prev_plevent = RoomEventFetch(room, prev_pl_id);
|
||||
#define CheckChange(prop) do \
|
||||
{ \
|
||||
JsonValue *old = \
|
||||
JsonGet(prev_plevent, 2, "content", prop);\
|
||||
JsonValue *new = \
|
||||
JsonGet(pdu.content, 1, prop); \
|
||||
int64_t oldv = 0, newv = 0; \
|
||||
oldv = JsonValueAsInteger(old); \
|
||||
newv = JsonValueAsInteger(new); \
|
||||
if ((old && !new) || (!old && new) || \
|
||||
(oldv != newv)) \
|
||||
{ \
|
||||
if (old && (oldv > userpl)) \
|
||||
{ \
|
||||
return false; \
|
||||
} \
|
||||
if (new && (newv > userpl)) \
|
||||
{ \
|
||||
return false; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
while(0)
|
||||
CheckChange("users_default");
|
||||
CheckChange("events_default");
|
||||
CheckChange("state_default");
|
||||
CheckChange("ban");
|
||||
CheckChange("redact");
|
||||
CheckChange("kick");
|
||||
CheckChange("invite");
|
||||
#undef CheckChange
|
||||
#define CheckPLOld(prop) \
|
||||
event_obj = \
|
||||
JsonValueAsObject(JsonGet(prev_plevent, 2, "content", prop)); \
|
||||
flag = true; \
|
||||
while (HashMapIterate(event_obj, &ev_type, (void **) &ev_obj)) \
|
||||
{ \
|
||||
JsonValue *new; \
|
||||
int64_t new_pl, old_pl; \
|
||||
\
|
||||
if (!flag) \
|
||||
{ \
|
||||
continue; \
|
||||
} \
|
||||
\
|
||||
new = JsonGet(pdu.content, 2, prop, ev_type); \
|
||||
old_pl = ParsePL(new, INT64_MAX); \
|
||||
if (((new_pl = ParsePL(new, INT64_MAX)) != INT64_MAX) && \
|
||||
((new_pl != old_pl) && old_pl > userpl)) \
|
||||
{ \
|
||||
flag = false; \
|
||||
} \
|
||||
} \
|
||||
if (!flag) \
|
||||
{ \
|
||||
JsonFree(prev_plevent); \
|
||||
return false; \
|
||||
} \
|
||||
flag = true
|
||||
#define CheckPLNew(prop) \
|
||||
event_obj = \
|
||||
JsonValueAsObject(JsonGet(pdu.content, 1, prop)); \
|
||||
flag = true; \
|
||||
while (HashMapIterate(event_obj, &ev_type, (void **) &ev_obj)) \
|
||||
{ \
|
||||
JsonValue *old; \
|
||||
int64_t new_pl, old_pl; \
|
||||
\
|
||||
if (!flag) \
|
||||
{ \
|
||||
continue; \
|
||||
} \
|
||||
\
|
||||
old = JsonGet(prev_plevent, 3, "content", prop, ev_type); \
|
||||
new_pl = ParsePL(ev_obj, INT64_MAX); \
|
||||
if (((old_pl = ParsePL(old, INT64_MAX)) != INT64_MAX && \
|
||||
new_pl != old_pl) && new_pl > userpl) \
|
||||
{ \
|
||||
flag = false; \
|
||||
} \
|
||||
} \
|
||||
if (!flag) \
|
||||
{ \
|
||||
JsonFree(prev_plevent); \
|
||||
return false; \
|
||||
} \
|
||||
flag = true
|
||||
/* Step 10.4: For each entry being changed in, or removed from, the
|
||||
* events property:
|
||||
* - If the current value is greater than the sender's current
|
||||
* power level, reject. */
|
||||
CheckPLOld("events");
|
||||
|
||||
/* Step 10.5: For each entry being added to, or changed in, the events
|
||||
* property:
|
||||
* - If the new value is greater than the sender's current power level,
|
||||
* reject. */
|
||||
CheckPLNew("events");
|
||||
|
||||
/* Steps 10.6 and 10.7 are effectively the same. */
|
||||
CheckPLOld("users");
|
||||
CheckPLNew("users");
|
||||
#undef CheckPLOld
|
||||
#undef CheckPLNew
|
||||
/* Step 10.8: Otherwise, allow. */
|
||||
JsonFree(prev_plevent);
|
||||
return true;
|
||||
}
|
35
src/Room/V1/Auth/internal.h
Normal file
35
src/Room/V1/Auth/internal.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef TELODENDRIA_IROOM_V1_AUTH_H
|
||||
#define TELODENDRIA_IROOM_V1_AUTH_H
|
||||
|
||||
#include "Room/internal.h"
|
||||
|
||||
/**
|
||||
* Verifies if a room creation PDU would be allowed
|
||||
* by the auth rules in room version 1 (step 1)
|
||||
*/
|
||||
extern bool AuthoriseCreateV1(PduV1, char **);
|
||||
|
||||
/**
|
||||
* Verifies if an auth event PDU would be allowed by the
|
||||
* auth rules in room version 1 (step 2)
|
||||
*/
|
||||
extern bool ConsiderAuthEventsV1(Room *, PduV1, char **);
|
||||
|
||||
/**
|
||||
* Verifies if an alias PDU would be authorised in room
|
||||
* version 1 (step 4)
|
||||
*/
|
||||
extern bool AuthoriseAliasV1(PduV1, char **);
|
||||
|
||||
/**
|
||||
* Verifies if a membership PDUv1 would be allowed by the
|
||||
* auth rules in room version 1 (step 5)
|
||||
*/
|
||||
extern bool AuthoriseMemberV1(Room *, PduV1, State *, char **);
|
||||
|
||||
/**
|
||||
* Verifies if a PDU (power levels) would be allowed by
|
||||
* the auth rules in room version 1 (step 10)
|
||||
*/
|
||||
extern bool AuthorisePowerLevelsV1(Room *, PduV1, State *);
|
||||
#endif
|
17
src/Room/V1/Info.c
Normal file
17
src/Room/V1/Info.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include "Room/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
bool
|
||||
RoomIsSoftfailedV1(PduV1 pdu)
|
||||
{
|
||||
return pdu._unsigned.pdu_status == PDUV1_STATUS_SOFTFAIL;
|
||||
}
|
||||
|
||||
bool
|
||||
RoomIsRejectedV1(PduV1 pdu)
|
||||
{
|
||||
return pdu._unsigned.pdu_status == PDUV1_STATUS_SOFTFAIL;
|
||||
}
|
73
src/Room/V1/Populate.c
Normal file
73
src/Room/V1/Populate.c
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include "Room/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
bool
|
||||
PopulateEventV1(Room * room, HashMap * event, PduV1 * pdu, ServerPart serv)
|
||||
{
|
||||
char *unused;
|
||||
Array *prev_events;
|
||||
size_t i;
|
||||
CommonID cid;
|
||||
if (PduV1FromJson(event, pdu, &unused))
|
||||
{
|
||||
/* TODO: Clean up some fields */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Consider the PDU dropped by default */
|
||||
pdu->_unsigned.pdu_status = PDUV1_STATUS_DROPPED;
|
||||
|
||||
/* TODO: Create a PDU of our own, signed and everything.
|
||||
* https://telodendria.io/blog/matrix-protocol-overview
|
||||
* has some ideas on how this could be done(up until stage 5). */
|
||||
pdu->sender =
|
||||
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "sender")));
|
||||
pdu->type =
|
||||
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "type")));
|
||||
pdu->redacts = NULL;
|
||||
if (JsonGet(event, 1, "state_key"))
|
||||
{
|
||||
pdu->state_key =
|
||||
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "state_key")));
|
||||
}
|
||||
pdu->auth_events = ArrayCreate();
|
||||
pdu->origin_server_ts = UtilTsMillis();
|
||||
pdu->content =
|
||||
JsonDuplicate(JsonValueAsObject(JsonGet(event, 1, "content")));
|
||||
pdu->room_id = StrDuplicate(room->id);
|
||||
pdu->signatures = HashMapCreate();
|
||||
pdu->depth = RoomGetDepth(room) + 1;
|
||||
pdu->depth = pdu->depth >= INT64_MAX ? INT64_MAX : pdu->depth;
|
||||
|
||||
/* Create a random event ID for this PDU.
|
||||
* TODO: Optionally verify whenever it's already used, but that
|
||||
* would be unlikely considering the lengths of event IDs. */
|
||||
cid.sigil = '$';
|
||||
cid.local = StrRandom(32);
|
||||
cid.server.hostname = StrDuplicate(serv.hostname);
|
||||
cid.server.port = serv.port ? StrDuplicate(serv.port) : NULL;
|
||||
pdu->event_id = ParserRecomposeCommonID(cid);
|
||||
CommonIDFree(cid);
|
||||
|
||||
|
||||
/* Fill prev_events with actual event data.
|
||||
* Note that we don't actually *clear* out these from our list, as
|
||||
* that should be done later. */
|
||||
pdu->prev_events = ArrayCreate();
|
||||
prev_events = RoomPrevEventsGet(room);
|
||||
for (i = 0; i < ArraySize(prev_events); i++)
|
||||
{
|
||||
HashMap *event = JsonValueAsObject(ArrayGet(prev_events, i));
|
||||
JsonValue *event_id = JsonGet(event, 1, "event_id");
|
||||
|
||||
ArrayAdd(pdu->prev_events, JsonValueDuplicate(event_id));
|
||||
}
|
||||
|
||||
/* TODO: Signatures.
|
||||
* We currently *don't* have an Ed25519 implementation. */
|
||||
|
||||
return false;
|
||||
}
|
396
src/Room/V1/Send.c
Normal file
396
src/Room/V1/Send.c
Normal file
|
@ -0,0 +1,396 @@
|
|||
#include "Room/internal.h"
|
||||
|
||||
#include <Schema/PduV1.h>
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
static char *
|
||||
RoomHashEventV1(PduV1 pdu)
|
||||
{
|
||||
HashMap *json = PduV1ToJson(&pdu);
|
||||
char *b64;
|
||||
|
||||
b64 = EventContentHash(json);
|
||||
|
||||
JsonFree(json);
|
||||
return b64;
|
||||
}
|
||||
static bool
|
||||
EventFits(HashMap *pdu)
|
||||
{
|
||||
int size = CanonicalJsonEncode(pdu, NULL);
|
||||
JsonValue *key;
|
||||
|
||||
/* Main PDU length is 65536 bytes */
|
||||
if (size > 65536)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#define VerifyKey(k,s) do \
|
||||
{ \
|
||||
if ((key = JsonGet(pdu, 1, k)) && \
|
||||
(strlen(JsonValueAsString(key)) > s)) \
|
||||
{ \
|
||||
return false; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
VerifyKey("sender", 255);
|
||||
VerifyKey("room_id", 255);
|
||||
VerifyKey("state_key", 255);
|
||||
VerifyKey("type", 255);
|
||||
VerifyKey("event_id", 255);
|
||||
|
||||
return true;
|
||||
#undef VerifyKey
|
||||
}
|
||||
static bool
|
||||
EventFitsV1(PduV1 pdu)
|
||||
{
|
||||
HashMap *hm;
|
||||
bool ret;
|
||||
|
||||
hm = PduV1ToJson(&pdu);
|
||||
ret = EventFits(hm);
|
||||
|
||||
JsonFree(hm);
|
||||
return ret;
|
||||
}
|
||||
static PduV1Status
|
||||
RoomGetEventStatusV1(Room *room, PduV1 *pdu, State *prev, bool client, char **errp)
|
||||
{
|
||||
if (!room || !pdu || !prev)
|
||||
{
|
||||
if (errp)
|
||||
{
|
||||
*errp = "Illegal arguments given to RoomGetEventStatusV1";
|
||||
}
|
||||
return PDUV1_STATUS_DROPPED;
|
||||
}
|
||||
|
||||
if (!EventFitsV1(*pdu))
|
||||
{
|
||||
/* Reject this event as it is too large. */
|
||||
if (errp)
|
||||
{
|
||||
*errp = "PDU is too large to fit";
|
||||
}
|
||||
return PDUV1_STATUS_DROPPED;
|
||||
}
|
||||
if (!RoomAuthoriseEventV1(room, *pdu, prev, errp))
|
||||
{
|
||||
/* Reject this event as the current state does not allow it.
|
||||
* TODO: Make the auth check function return a string showing the
|
||||
* errorr status to the user. */
|
||||
return PDUV1_STATUS_DROPPED;
|
||||
}
|
||||
|
||||
if (!client)
|
||||
{
|
||||
State *current = StateCurrent(room);
|
||||
|
||||
if (!RoomAuthoriseEventV1(room, *pdu, current, NULL))
|
||||
{
|
||||
StateFree(current);
|
||||
return PDUV1_STATUS_SOFTFAIL;
|
||||
}
|
||||
StateFree(current);
|
||||
}
|
||||
|
||||
return PDUV1_STATUS_ACCEPTED;
|
||||
}
|
||||
bool
|
||||
RoomAddEventV1(Room *room, PduV1 pdu)
|
||||
{
|
||||
DbRef *event_ref;
|
||||
Array *prev_events = NULL, *leaves = NULL;
|
||||
HashMap *leaves_json = NULL, *pdu_json = NULL;
|
||||
JsonValue *leaves_val;
|
||||
char *safe_id;
|
||||
size_t i;
|
||||
|
||||
if (!room || room->version >= 3 ||
|
||||
pdu._unsigned.pdu_status == PDUV1_STATUS_DROPPED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Insert our PDU into the event table, regardless of status */
|
||||
safe_id = CreateSafeID(pdu.event_id);
|
||||
event_ref = DbCreate(room->db, 4, "rooms", room->id, "events", safe_id);
|
||||
pdu_json = PduV1ToJson(&pdu);
|
||||
|
||||
DbJsonSet(event_ref, pdu_json);
|
||||
|
||||
DbUnlock(room->db, event_ref);
|
||||
JsonFree(pdu_json);
|
||||
Free(safe_id);
|
||||
|
||||
/* Only accepted PDUs get to do the news */
|
||||
if (pdu._unsigned.pdu_status == PDUV1_STATUS_ACCEPTED)
|
||||
{
|
||||
/* Remove managed leaves here. */
|
||||
leaves_json = DbJson(room->leaves_ref);
|
||||
leaves_val = JsonValueDuplicate(JsonGet(leaves_json, 1, "leaves"));
|
||||
leaves = JsonValueAsArray(leaves_val);
|
||||
Free(leaves_val); /* We do not care about the array's JSON shell. */
|
||||
|
||||
prev_events = pdu.prev_events;
|
||||
for (i = 0; i < ArraySize(prev_events); i++)
|
||||
{
|
||||
JsonValue *event_val = ArrayGet(prev_events, i);
|
||||
char *event_id = JsonValueAsString(event_val);
|
||||
size_t j;
|
||||
ssize_t delete_index = -1;
|
||||
|
||||
for (j = 0; j < ArraySize(leaves); j++)
|
||||
{
|
||||
JsonValue *leaf_val = ArrayGet(leaves, j);
|
||||
HashMap *leaf_object = JsonValueAsObject(leaf_val);
|
||||
char *leaf_id =
|
||||
JsonValueAsString(JsonGet(leaf_object, 1, "event_id"));
|
||||
|
||||
if (StrEquals(leaf_id, event_id))
|
||||
{
|
||||
delete_index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (delete_index == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
JsonValueFree(ArrayDelete(leaves, delete_index));
|
||||
}
|
||||
|
||||
/* Add our current PDU to the leaves. */
|
||||
ArrayAdd(leaves, JsonValueObject(PduV1ToJson(&pdu)));
|
||||
leaves_json = JsonDuplicate(leaves_json);
|
||||
JsonValueFree(HashMapDelete(leaves_json, "leaves"));
|
||||
JsonSet(leaves_json, JsonValueArray(leaves), 1, "leaves");
|
||||
DbJsonSet(room->leaves_ref, leaves_json);
|
||||
JsonFree(leaves_json);
|
||||
}
|
||||
|
||||
for (i = 0; i < ArraySize(prev_events); i++)
|
||||
{
|
||||
JsonValue *event_val = ArrayGet(prev_events, i);
|
||||
char *id = JsonValueAsString(event_val);
|
||||
char *error = NULL;
|
||||
PduV1 prev_pdu = { 0 };
|
||||
HashMap *prev_object = NULL;
|
||||
Array *next_events = NULL;
|
||||
event_ref = DbLock(room->db, 4, "rooms", room->id, "events", id);
|
||||
PduV1FromJson(DbJson(event_ref), &prev_pdu, &error);
|
||||
|
||||
/* Update the next events view. Note that this works even if
|
||||
* the event is soft-failed/rejected. */
|
||||
if (!prev_pdu._unsigned.next_events)
|
||||
{
|
||||
prev_pdu._unsigned.next_events = ArrayCreate();
|
||||
}
|
||||
next_events = prev_pdu._unsigned.next_events;
|
||||
|
||||
ArrayAdd(next_events, StrDuplicate(pdu.event_id));
|
||||
|
||||
prev_object = PduV1ToJson(&prev_pdu);
|
||||
DbJsonSet(event_ref, prev_object);
|
||||
|
||||
JsonFree(prev_object);
|
||||
PduV1Free(&prev_pdu);
|
||||
DbUnlock(room->db, event_ref);
|
||||
}
|
||||
|
||||
/* Accepted PDUs should be the only one that users should be
|
||||
* notified about. */
|
||||
if (pdu._unsigned.pdu_status == PDUV1_STATUS_ACCEPTED)
|
||||
{
|
||||
State *state;
|
||||
char *type, *state_key, *event_id;
|
||||
|
||||
pdu_json = PduV1ToJson(&pdu);
|
||||
|
||||
/* If we have a membership change, then add it to the
|
||||
* proper table. */
|
||||
if (StrEquals(pdu.type, "m.room.member"))
|
||||
{
|
||||
CommonID *id = UserIdParse(pdu.state_key, NULL);
|
||||
User *user = UserLock(room->db, id->local);
|
||||
char *membership = JsonValueAsString(
|
||||
HashMapGet(pdu.content, "membership")
|
||||
);
|
||||
|
||||
if (StrEquals(membership, "join") && user)
|
||||
{
|
||||
UserAddJoin(user, room->id);
|
||||
UserPushJoinSync(user, room->id);
|
||||
}
|
||||
else if (StrEquals(membership, "invite") && user)
|
||||
{
|
||||
UserAddInvite(user, room->id);
|
||||
UserPushInviteSync(user, room->id);
|
||||
}
|
||||
else if ((StrEquals(membership, "leave") && user) ||
|
||||
StrEquals(membership, "ban"))
|
||||
{
|
||||
UserRemoveInvite(user, room->id);
|
||||
UserRemoveJoin(user, room->id);
|
||||
}
|
||||
|
||||
UserIdFree(id);
|
||||
UserUnlock(user);
|
||||
}
|
||||
|
||||
/* Notify the user by pushing out the user */
|
||||
state = StateCurrent(room);
|
||||
while (StateIterate(state, &type, &state_key, (void **) &event_id))
|
||||
{
|
||||
if (StrEquals(type, "m.room.member"))
|
||||
{
|
||||
CommonID *id = UserIdParse(state_key, NULL);
|
||||
User *user = UserLock(room->db, id->local);
|
||||
|
||||
UserPushEvent(user, pdu_json);
|
||||
|
||||
UserIdFree(id);
|
||||
UserUnlock(user);
|
||||
}
|
||||
Free(type);
|
||||
Free(state_key);
|
||||
}
|
||||
StateFree(state);
|
||||
JsonFree(pdu_json);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
HashMap *
|
||||
RoomEventSendV1(Room * room, HashMap * event, char **errp)
|
||||
{
|
||||
PduV1 pdu = { 0 };
|
||||
HashMap *pdu_object = NULL;
|
||||
bool client_event, valid = false;
|
||||
State *state = NULL;
|
||||
PduV1Status status;
|
||||
|
||||
client_event = !PopulateEventV1(room, event, &pdu, RoomGetCreator(room));
|
||||
pdu_object = PduV1ToJson(&pdu);
|
||||
|
||||
state = StateResolve(room, pdu_object);
|
||||
|
||||
if (client_event)
|
||||
{
|
||||
char *ev_id;
|
||||
#define AddState(type, key) do \
|
||||
{ \
|
||||
ev_id = StateGet(state, type, key); \
|
||||
if (ev_id) \
|
||||
{ \
|
||||
JsonValue *v = JsonValueString(ev_id); \
|
||||
ArrayAdd(pdu.auth_events, v); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/*
|
||||
* Implemented from
|
||||
* https://spec.matrix.org/v1.7/server-server-api/
|
||||
* #auth-events-selection */
|
||||
AddState("m.room.create", "");
|
||||
AddState("m.room.power_levels", "");
|
||||
AddState("m.room.member", pdu.sender);
|
||||
if (StrEquals(pdu.type, "m.room.member"))
|
||||
{
|
||||
char *target = pdu.state_key;
|
||||
char *membership =
|
||||
JsonValueAsString(JsonGet(pdu.content, 1, "membership"));
|
||||
char *auth_via =
|
||||
JsonValueAsString(
|
||||
JsonGet(
|
||||
pdu.content, 1, "join_authorised_via_users_server")
|
||||
);
|
||||
HashMap *inv =
|
||||
JsonValueAsObject(
|
||||
JsonGet(pdu.content, 1, "third_party_invite"));
|
||||
|
||||
if (target && !StrEquals(target, pdu.sender))
|
||||
{
|
||||
AddState("m.room.member", target);
|
||||
}
|
||||
if (StrEquals(membership, "join") ||
|
||||
StrEquals(membership, "invite"))
|
||||
{
|
||||
AddState("m.room.join_rules", "");
|
||||
}
|
||||
if (StrEquals(membership, "invite") && inv)
|
||||
{
|
||||
char *token =
|
||||
JsonValueAsString(JsonGet(inv, 2, "signed", "token"));
|
||||
AddState("m.room.third_party_invite", token);
|
||||
}
|
||||
if (auth_via && room->version >= 8)
|
||||
{
|
||||
AddState("m.room.member", auth_via);
|
||||
}
|
||||
}
|
||||
pdu.hashes.sha256 = RoomHashEventV1(pdu);
|
||||
#undef AddState
|
||||
}
|
||||
/* It seems like we need to behave differently in terms of
|
||||
* verifying PDUs from the client/federation.
|
||||
* - In the client, we just do not care about any events that
|
||||
* are incorrect. We simply drop them, as if they never existed.
|
||||
* - In the server on the otherhand, the only place where we can
|
||||
* possibly drop events as such is if it fails signatures. In
|
||||
* other cases, we *have* to store it(ableit with flags, to
|
||||
* restrict what we can do).
|
||||
* - Rejection: We avoid relaying/linking those to anything.
|
||||
* They must NOT be used for stateres.
|
||||
* - Softfail: Essentially almost the same as rejects, except
|
||||
* that they *are* used for stateres.
|
||||
* I guess a way to do this may be to add a CheckAuthStatus
|
||||
* function that also verifies if it is a client event, and returns
|
||||
* an enum:
|
||||
* - DROPPED: Do NOT process it _at all_
|
||||
* - REJECT: Process that event as if it was rejected
|
||||
* - SOFTFAIL: Process the event as if it was softfailed
|
||||
* The main issue is storing it in the PDU. A naive approach would be to
|
||||
* add the status to the unsigned field of the PDU, and add functions to
|
||||
* return the status. I guess that is possible, but then again, can we
|
||||
* really abuse the unsigned field for this?
|
||||
*/
|
||||
|
||||
/* TODO: For PDU events, we should verify their hashes. */
|
||||
status = RoomGetEventStatusV1(room, &pdu, state, client_event, errp);
|
||||
if (status == PDUV1_STATUS_DROPPED)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
pdu._unsigned.pdu_status = status;
|
||||
|
||||
StateFree(state);
|
||||
|
||||
RoomAddEventV1(room, pdu);
|
||||
state = NULL;
|
||||
valid = true;
|
||||
|
||||
/* If it is a client event, we should make sure that we shout at
|
||||
* every other homeserver about our new event. */
|
||||
|
||||
finish:
|
||||
if (state)
|
||||
{
|
||||
StateFree(state);
|
||||
}
|
||||
if (pdu_object)
|
||||
{
|
||||
JsonFree(pdu_object);
|
||||
pdu_object = NULL;
|
||||
}
|
||||
if (valid)
|
||||
{
|
||||
pdu_object = PduV1ToJson(&pdu);
|
||||
}
|
||||
PduV1Free(&pdu);
|
||||
return pdu_object;
|
||||
}
|
90
src/Room/internal.h
Normal file
90
src/Room/internal.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
#ifndef TELODENDRIA_IROOM_H
|
||||
#define TELODENDRIA_IROOM_H
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Util.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Db.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <CanonicalJson.h>
|
||||
#include <Parser.h>
|
||||
#include <Config.h>
|
||||
#include <State.h>
|
||||
#include <User.h>
|
||||
|
||||
#define IsState(p, typ, key) (StrEquals((p)->type, typ) && \
|
||||
StrEquals((p)->state_key, key))
|
||||
struct Room
|
||||
{
|
||||
Db *db;
|
||||
|
||||
DbRef *state_ref;
|
||||
DbRef *leaves_ref; /* Reference to the leaf list */
|
||||
|
||||
ServerPart creator;
|
||||
|
||||
char *id;
|
||||
int version;
|
||||
};
|
||||
|
||||
/**
|
||||
* Populates a room with the events required for its creation.
|
||||
*/
|
||||
extern void RoomPopulate(Room *, User *, RoomCreateRequest *, ServerPart);
|
||||
|
||||
/**
|
||||
* Verifies if the current room state has a joinrule set to a
|
||||
* specific value.
|
||||
*/
|
||||
extern bool RoomIsJoinRule(Room *, State *, char *);
|
||||
|
||||
/**
|
||||
* Tries to parse a PL value from a JsonValue, with a default
|
||||
* powerlevel, if it cannot parse it.
|
||||
*/
|
||||
extern int64_t ParsePL(JsonValue *, int64_t);
|
||||
|
||||
/**
|
||||
* Computes the lowest powerlevel required to execute an action
|
||||
* in a room.
|
||||
*/
|
||||
extern int64_t RoomMinPL(Room *, State *, char *, char *);
|
||||
|
||||
/**
|
||||
* Computes the user's powerlevel at a specific state.
|
||||
*/
|
||||
extern int64_t RoomUserPL(Room *, State *, char *);
|
||||
|
||||
|
||||
/**
|
||||
* Populates an event to a valid PDUv1(and returns true if
|
||||
* properly created).
|
||||
*/
|
||||
extern bool PopulateEventV1(Room *, HashMap *, PduV1 *, ServerPart);
|
||||
|
||||
/**
|
||||
* Sends an event to a room, be it a PDUv1/client event
|
||||
*/
|
||||
extern HashMap * RoomEventSendV1(Room *, HashMap *, char **);
|
||||
|
||||
|
||||
/**
|
||||
* Verifies if the user has a specific membership at a given state.
|
||||
*/
|
||||
extern bool RoomUserHasMembership(Room *, State *, char *, char *);
|
||||
|
||||
/**
|
||||
* Computes a PDU's contenthash.
|
||||
*/
|
||||
extern char * EventContentHash(HashMap *);
|
||||
|
||||
/**
|
||||
* Creates a new "DB-safe" ID for events.
|
||||
*/
|
||||
extern char * CreateSafeID(char *);
|
||||
|
||||
#endif
|
14
src/User.c
14
src/User.c
|
@ -1626,12 +1626,17 @@ UserFetchMessages(User *user, int n, char *token, char **next)
|
|||
HashMap *event = RoomEventFetch(room, curr);
|
||||
Array *prevEvents;
|
||||
size_t j;
|
||||
bool toFree = true;
|
||||
|
||||
Free(curr);
|
||||
|
||||
/* Push event into our message list.
|
||||
* TODO: Check if the user has the right to see the event/room. */
|
||||
ArrayAdd(messages, event);
|
||||
if (RoomIsEventVisible(room, user, curr))
|
||||
{
|
||||
ArrayAdd(messages, event);
|
||||
toFree = false;
|
||||
}
|
||||
Free(curr);
|
||||
|
||||
prevEvents = JsonValueAsArray(HashMapGet(event, "prev_events"));
|
||||
if (dir)
|
||||
|
@ -1653,6 +1658,11 @@ UserFetchMessages(User *user, int n, char *token, char **next)
|
|||
{
|
||||
limited = true;
|
||||
}
|
||||
if (toFree)
|
||||
{
|
||||
JsonFree(event);
|
||||
event = NULL;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < ArraySize(nexts); i++)
|
||||
{
|
||||
|
|
|
@ -152,6 +152,12 @@ extern HashMap * RoomEventSend(Room *, HashMap *, char **);
|
|||
*/
|
||||
extern void RoomSendInvite(User *, bool, char *, Room *);
|
||||
|
||||
/**
|
||||
* See if a user is allowed to see an event in a room,
|
||||
* based on its visibility.
|
||||
*/
|
||||
extern bool RoomIsEventVisible(Room *, User *, char *);
|
||||
|
||||
/**
|
||||
* Fetch a single event's PDU in a room into an
|
||||
* hashmap, given an event ID, from the database
|
||||
|
|
Loading…
Reference in a new issue