forked from Telodendria/Telodendria
396 lines
12 KiB
C
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;
|
|
}
|