[ADD/WIP/UNTESTED] Start verifying membership, tested hashing
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

This commit is contained in:
lda 2024-05-08 12:14:10 +02:00
parent 001b4821fe
commit 9bc36f324a
5 changed files with 408 additions and 37 deletions

View file

@ -168,10 +168,15 @@ CanonicalJsonEncode(HashMap * object, Stream * out)
static ssize_t static ssize_t
StringIoRead(void *cookie, void *buf, size_t count) StringIoRead(void *cookie, void *buf, size_t count)
{ {
(void) cookie;
(void) buf;
(void) count;
return -1; /* TODO: Consider reading properly */ return -1; /* TODO: Consider reading properly */
} }
static ssize_t static ssize_t
StringIoWrite(void *c, void *buf, size_t count) StringIoWrite(void *c, void *buf, size_t count)
{
if (count > 0)
{ {
char **str = c; char **str = c;
char *stringised = Malloc(count + 1); char *stringised = Malloc(count + 1);
@ -185,6 +190,7 @@ StringIoWrite(void *c, void *buf, size_t count)
Free(*str); Free(*str);
} }
*str = new; *str = new;
}
return count; return count;
} }
static const IoFunctions Functions = { static const IoFunctions Functions = {
@ -202,6 +208,7 @@ CanonicalJsonHash(HashMap *json)
Io *string_writer = IoCreate(&string, Functions); Io *string_writer = IoCreate(&string, Functions);
Stream *io_stream = StreamIo(string_writer); Stream *io_stream = StreamIo(string_writer);
CanonicalJsonEncode(json, io_stream); CanonicalJsonEncode(json, io_stream);
StreamFlush(io_stream);
sha = Sha256(string); sha = Sha256(string);
shastr = ShaToHex(sha); shastr = ShaToHex(sha);

View file

@ -23,9 +23,11 @@
* SOFTWARE. * SOFTWARE.
*/ */
#include "Cytoplasm/Json.h" #include <Cytoplasm/Array.h>
#include "Cytoplasm/Stream.h" #include <Cytoplasm/Json.h>
/*#include "Cytoplasm/Stream.h"*/
#include "Parser.h" #include "Parser.h"
#include "User.h"
#include <Room.h> #include <Room.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
@ -42,7 +44,10 @@
#include <State.h> #include <State.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#define IsState(p, typ, key) (StrEquals(p->type, typ) && \
StrEquals(p->state_key, key))
struct Room struct Room
{ {
Db *db; Db *db;
@ -84,7 +89,6 @@ RoomCreate(Db * db, User *user, RoomCreateRequest * req)
room->id = GenerateRoomId(); /* TODO: Check config. */ room->id = GenerateRoomId(); /* TODO: Check config. */
room->version = version_num; room->version = version_num;
/* TODO: A room is *not just* its state? */
room->ref = DbCreate(db, 3, "rooms", room->id, "state"); room->ref = DbCreate(db, 3, "rooms", room->id, "state");
/* TODO: Populate room with information */ /* TODO: Populate room with information */
@ -185,23 +189,12 @@ PopulateEventV1(Room * room, HashMap * event, PduV1 * pdu)
pdu->content = JsonDuplicate(event); /* Copy the original JSON data */ pdu->content = JsonDuplicate(event); /* Copy the original JSON data */
/* TODO: Signature and alldat. */ /* TODO: Signature and alldat. */
(void) room;
return false; return false;
} }
static bool static bool
PopulateEventV3(Room * room, HashMap * event, PduV3 *pdu) AuthoriseCreateV1(PduV1 pdu)
{
char *unused;
if (PduV3FromJson(event, pdu, &unused))
{
return true;
}
/* 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). */
return false;
}
static AuthoriseCreateV1(PduV1 pdu)
{ {
bool ret = true; bool ret = true;
CommonID sender = { 0 }, room_id = { 0 }; CommonID sender = { 0 }, room_id = { 0 };
@ -244,23 +237,298 @@ finish:
CommonIDFree(room_id); CommonIDFree(room_id);
return ret; return ret;
} }
static bool static bool
AuthoriseEventV1(PduV1 pdu) 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 (StrEquals(pdu->type, "m.room.member"))
{
char *membership =
JsonValueAsString(JsonGet(pdu->content, 1, "membership"));
if (IsState(auth_pdu, "m.room.member", pdu->sender))
{
/* 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;
}
/* TODO: The rest.
* https://spec.matrix.org/v1.7/server-server-api/#auth-events-selection */
}
return false;
}
static bool
VerifyPDUV1(PduV1 *auth_pdu)
{
/* TODO:
* https://spec.matrix.org/v1.7/server-server-api/
* #checks-performed-on-receipt-of-a-pdu */
(void) auth_pdu;
return false; /* This only shows whenever an event was rejected, not
* soft-failed */
}
static bool
ConsiderAuthEventsV1(Room * room, PduV1 pdu)
{
char *ignored;
size_t i; size_t i;
HashMap *state_keytype;
state_keytype = HashMapCreate();
for (i = 0; i < ArraySize(pdu.auth_events); i++)
{
char *event_id = ArrayGet(pdu.auth_events, i);
HashMap *event = RoomEventFetch(room, event_id);
PduV1 auth_pdu;
char *key_type_id;
if (!PduV1FromJson(event, &auth_pdu, &ignored))
{
HashMapFree(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. */
HashMapFree(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))
{
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))
{
return false;
}
/* Step 2.4: If there is no m.room.create event among the entries,
* reject. */
HashMapFree(event);
PduV1Free(&auth_pdu);
}
HashMapFree(state_keytype);
return true;
}
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;
}
static bool
AuthoriseAliasV1(PduV1 pdu)
{
/* Step 4.1: If event has no state_key, reject. */
if (!pdu.state_key || StrEquals(pdu.state_key, ""))
{
return false;
}
/* Step 4.2: If sender's domain doesn't matches state_key, reject. */
if (!VerifyServers(pdu.state_key, pdu.sender))
{
return false;
}
/* Step 4.3: Otherwise, allow. */
return true;
}
static bool
AuthorizeJoinMembershipV1(Room * room, PduV1 pdu)
{
/* 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)
{
char *prev_id = ArrayGet(prev, 0);
char *ignored;
HashMap *prev = RoomEventFetch(room, prev_id);
PduV1 prev_pdu;
if (prev && PduV1FromJson(prev, &prev_pdu, &ignored))
{
if (StrEquals(prev_pdu.type, "m.room.create") &&
StrEquals(prev_pdu.sender, pdu.state_key))
{
PduV1Free(&prev_pdu);
JsonFree(prev);
return true;
}
PduV1Free(&prev_pdu);
}
JsonFree(prev);
}
/* Step 5.2.2: If the sender does not match state_key, reject. */
if (!StrEquals(pdu.sender, pdu.state_key))
{
return false;
}
/* Step 5.2.3: If the sender is banned, reject. */
/* TODO */
/* Step 5.2.4: If the join_rule is invite then allow if membership
* state is invite or join. */
/* Step 5.2.6: Otherwise, reject. */
return false;
}
static bool
AuthoriseMemberV1(Room * room, PduV1 pdu)
{
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")))
{
return false;
}
if (JsonValueType(membership) != JSON_STRING)
{
/* Also check for the type */
return false;
}
membership_str = JsonValueAsString(membership);
#define JumpIfMembership(mem, func) do { \
if (StrEquals(membership_str, mem)) \
{ \
return func(room, pdu); \
} \
} while (0)
/* Step 5.2: If membership is join. */
JumpIfMembership("join", AuthorizeJoinMembershipV1);
/* Step 4.3: Otherwise, allow. */
return true;
#undef JumpIfMembership
}
bool
AuthoriseEventV1(Room * room, PduV1 pdu)
{
HashMap *state, *create_event;
char *create_event_id;
JsonValue *federate;
/* Step 1: If m.room.create */
if (StrEquals(pdu.type, "m.room.create")) if (StrEquals(pdu.type, "m.room.create"))
{ {
return AuthoriseCreateV1(pdu); return AuthoriseCreateV1(pdu);
} }
/* TODO: Consider, everything else! */ /* Step 2: Considering the event's auth_events. */
for (i = 0; i < ArraySize(pdu.auth_events); i++) if (!ConsiderAuthEventsV1(room, pdu))
{ {
char *event_id = ArrayGet(pdu.auth_events, i); return false;
/* TODO: Fetch event there */
} }
/* TODO */ /* 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.
*/
state = RoomStateGet(room);
create_event_id = StateGet(state, "m.room.create", "");
if (!state || !create_event_id)
{
/* At this point, [create_event_id] has to exist */
StateFree(state); /* create_event_id is also freed. */
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))
{
return false;
}
}
}
/* Step 4: If type is m.room.aliases */
if (StrEquals(pdu.type, "m.room.aliases"))
{
return AuthoriseAliasV1(pdu);
}
/* Step 5: If type is m.room.member */
if (StrEquals(pdu.type, "m.room.member"))
{
return AuthoriseMemberV1(room, pdu);
}
return true; return true;
} }
static char * static char *
@ -357,7 +625,6 @@ RoomEventSendV1(Room * room, HashMap * event)
#undef AddState #undef AddState
} }
/* TODO: Do a size check! */ /* TODO: Do a size check! */
finish:
if (state) if (state)
{ {
JsonFree(state); JsonFree(state);
@ -373,6 +640,8 @@ static HashMap *
RoomEventSendV3(Room * room, HashMap * event) RoomEventSendV3(Room * room, HashMap * event)
{ {
/* TODO */ /* TODO */
(void) room;
(void) event;
return NULL; return NULL;
} }
HashMap * HashMap *
@ -382,7 +651,7 @@ RoomEventSend(Room * room, HashMap * event)
{ {
return NULL; return NULL;
} }
if (room->version == 1) if (room->version < 3)
{ {
/* Manage with PDUv1 */ /* Manage with PDUv1 */
return RoomEventSendV1(room, event); return RoomEventSendV1(room, event);
@ -390,3 +659,51 @@ RoomEventSend(Room * room, HashMap * event)
/* Manage with PDUv3 otherwise */ /* Manage with PDUv3 otherwise */
return RoomEventSendV3(room, event); return RoomEventSendV3(room, event);
} }
static char *
CreateSafeID(char *unsafe_id)
{
size_t length = strlen(unsafe_id);
char *safe_id = Malloc(length + 1);
size_t i;
/* Creates a room ID safe to be put into the database
* for room version 3 and above.
* (with '/'s replaced by '-') */
memcpy(safe_id, unsafe_id, length + 1);
for (i = 0; i < length; i++)
{
if (safe_id[i] == '/')
{
safe_id[i] = '-';
}
}
return safe_id;
}
HashMap *
RoomEventFetch(Room *room, char *id)
{
DbRef *event_ref;
HashMap *ret;
char *safe_id;
if (!room || !id)
{
return NULL;
}
/* Let's try to locally find that event in our junk. */
safe_id = CreateSafeID(id);
event_ref = DbLock(room->db, 4, "rooms", room->id, "events", safe_id);
if (!event_ref)
{
/* TODO: Fetch from another homeserver if possible. */
ret = NULL;
goto finish;
}
ret = JsonDuplicate(DbJson(event_ref));
DbUnlock(room->db, event_ref);
finish:
Free(safe_id);
return ret;
}

View file

@ -26,10 +26,12 @@
#include <State.h> #include <State.h>
#include <Cytoplasm/HashMap.h> #include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Array.h> #include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h>
#include <Room.h>
#include <Event.h> #include <Event.h>
#include <Room.h>
static HashMap * static HashMap *
StateResolveV1(Array * states) StateResolveV1(Array * states)
@ -86,3 +88,35 @@ StateResolve(Room * room, HashMap * event)
return StateResolveV2(states); return StateResolveV2(states);
} }
} }
char *
StateGet(HashMap *state, char *type, char *key)
{
char *full_string;
char *ret;
if (!state || !type || !key)
{
return NULL;
}
full_string = StrConcat(3, type, ",", key);
ret = HashMapGet(state, full_string);
Free(full_string);
return ret;
}
void
StateFree(HashMap *state)
{
char *full;
char *event_id;
if (!state)
{
return;
}
while (HashMapIterate(state, &full, (void **) &event_id))
{
Free(event_id);
}
HashMapFree(state);
}

View file

@ -130,4 +130,12 @@ extern int RoomPrevEventsSet(Room *, Array *);
*/ */
extern HashMap * RoomEventSend(Room *, HashMap *); extern HashMap * RoomEventSend(Room *, HashMap *);
/**
* Fetch a single event's PDU in a room into an
* hashmap, given an event ID, from the database
* if possible, or otherwise fetched from a remote
* homeserver participating in the room.
*/
extern HashMap * RoomEventFetch(Room *, char *);
#endif /* TELODENDRIA_ROOM_H */ #endif /* TELODENDRIA_ROOM_H */

View file

@ -54,4 +54,9 @@ extern char *StateSet(HashMap *, char *, char *, char *);
*/ */
extern HashMap * StateResolve(Room *, HashMap *); extern HashMap * StateResolve(Room *, HashMap *);
/**
* Frees an entire state table from the heap.
*/
extern void StateFree(HashMap *);
#endif /* TELODENDRIA_STATE_H */ #endif /* TELODENDRIA_STATE_H */