forked from Telodendria/Telodendria
[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
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:
parent
001b4821fe
commit
9bc36f324a
5 changed files with 408 additions and 37 deletions
|
@ -168,10 +168,15 @@ CanonicalJsonEncode(HashMap * object, Stream * out)
|
|||
static ssize_t
|
||||
StringIoRead(void *cookie, void *buf, size_t count)
|
||||
{
|
||||
(void) cookie;
|
||||
(void) buf;
|
||||
(void) count;
|
||||
return -1; /* TODO: Consider reading properly */
|
||||
}
|
||||
static ssize_t
|
||||
StringIoWrite(void *c, void *buf, size_t count)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
char **str = c;
|
||||
char *stringised = Malloc(count + 1);
|
||||
|
@ -185,6 +190,7 @@ StringIoWrite(void *c, void *buf, size_t count)
|
|||
Free(*str);
|
||||
}
|
||||
*str = new;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
static const IoFunctions Functions = {
|
||||
|
@ -202,6 +208,7 @@ CanonicalJsonHash(HashMap *json)
|
|||
Io *string_writer = IoCreate(&string, Functions);
|
||||
Stream *io_stream = StreamIo(string_writer);
|
||||
CanonicalJsonEncode(json, io_stream);
|
||||
StreamFlush(io_stream);
|
||||
|
||||
sha = Sha256(string);
|
||||
shastr = ShaToHex(sha);
|
||||
|
|
367
src/Room.c
367
src/Room.c
|
@ -23,9 +23,11 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "Cytoplasm/Json.h"
|
||||
#include "Cytoplasm/Stream.h"
|
||||
#include <Cytoplasm/Array.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
/*#include "Cytoplasm/Stream.h"*/
|
||||
#include "Parser.h"
|
||||
#include "User.h"
|
||||
#include <Room.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
|
@ -42,7 +44,10 @@
|
|||
#include <State.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define IsState(p, typ, key) (StrEquals(p->type, typ) && \
|
||||
StrEquals(p->state_key, key))
|
||||
struct Room
|
||||
{
|
||||
Db *db;
|
||||
|
@ -84,7 +89,6 @@ RoomCreate(Db * db, User *user, RoomCreateRequest * req)
|
|||
room->id = GenerateRoomId(); /* TODO: Check config. */
|
||||
room->version = version_num;
|
||||
|
||||
/* TODO: A room is *not just* its state? */
|
||||
room->ref = DbCreate(db, 3, "rooms", room->id, "state");
|
||||
|
||||
/* 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 */
|
||||
|
||||
/* TODO: Signature and alldat. */
|
||||
|
||||
(void) room;
|
||||
return false;
|
||||
}
|
||||
static bool
|
||||
PopulateEventV3(Room * room, HashMap * event, PduV3 *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)
|
||||
AuthoriseCreateV1(PduV1 pdu)
|
||||
{
|
||||
bool ret = true;
|
||||
CommonID sender = { 0 }, room_id = { 0 };
|
||||
|
@ -244,23 +237,298 @@ finish:
|
|||
CommonIDFree(room_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
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"))
|
||||
{
|
||||
return AuthoriseCreateV1(pdu);
|
||||
}
|
||||
|
||||
/* TODO: Consider, everything else! */
|
||||
for (i = 0; i < ArraySize(pdu.auth_events); i++)
|
||||
/* Step 2: Considering the event's auth_events. */
|
||||
if (!ConsiderAuthEventsV1(room, pdu))
|
||||
{
|
||||
char *event_id = ArrayGet(pdu.auth_events, i);
|
||||
/* TODO: Fetch event there */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
static char *
|
||||
|
@ -357,7 +625,6 @@ RoomEventSendV1(Room * room, HashMap * event)
|
|||
#undef AddState
|
||||
}
|
||||
/* TODO: Do a size check! */
|
||||
finish:
|
||||
if (state)
|
||||
{
|
||||
JsonFree(state);
|
||||
|
@ -373,6 +640,8 @@ static HashMap *
|
|||
RoomEventSendV3(Room * room, HashMap * event)
|
||||
{
|
||||
/* TODO */
|
||||
(void) room;
|
||||
(void) event;
|
||||
return NULL;
|
||||
}
|
||||
HashMap *
|
||||
|
@ -382,7 +651,7 @@ RoomEventSend(Room * room, HashMap * event)
|
|||
{
|
||||
return NULL;
|
||||
}
|
||||
if (room->version == 1)
|
||||
if (room->version < 3)
|
||||
{
|
||||
/* Manage with PDUv1 */
|
||||
return RoomEventSendV1(room, event);
|
||||
|
@ -390,3 +659,51 @@ RoomEventSend(Room * room, HashMap * event)
|
|||
/* Manage with PDUv3 otherwise */
|
||||
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;
|
||||
}
|
||||
|
|
36
src/State.c
36
src/State.c
|
@ -26,10 +26,12 @@
|
|||
#include <State.h>
|
||||
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Array.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <Room.h>
|
||||
#include <Event.h>
|
||||
#include <Room.h>
|
||||
|
||||
static HashMap *
|
||||
StateResolveV1(Array * states)
|
||||
|
@ -86,3 +88,35 @@ StateResolve(Room * room, HashMap * event)
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -130,4 +130,12 @@ extern int RoomPrevEventsSet(Room *, Array *);
|
|||
*/
|
||||
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 */
|
||||
|
|
|
@ -54,4 +54,9 @@ extern char *StateSet(HashMap *, char *, char *, char *);
|
|||
*/
|
||||
extern HashMap * StateResolve(Room *, HashMap *);
|
||||
|
||||
/**
|
||||
* Frees an entire state table from the heap.
|
||||
*/
|
||||
extern void StateFree(HashMap *);
|
||||
|
||||
#endif /* TELODENDRIA_STATE_H */
|
||||
|
|
Loading…
Reference in a new issue