telodendria/src/Room/V1/Send.c
LDA 01de46b299 [REF] Separate the room functions
User.c, you're next.
2024-08-30 09:42:58 +02:00

396 lines
12 KiB
C

#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;
}