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