diff --git a/src/Html.c b/src/Html.c index 4c9b0da..df19bed 100644 --- a/src/Html.c +++ b/src/Html.c @@ -46,7 +46,7 @@ HtmlBegin(Stream * stream, char *title) "" "
" diff --git a/src/Main.c b/src/Main.c index 7eed17a..ba5a476 100644 --- a/src/Main.c +++ b/src/Main.c @@ -297,7 +297,8 @@ start: goto finish; } - if (!tConfig.log.timestampFormat || !StrEquals(tConfig.log.timestampFormat, "default")) + if (!tConfig.log.timestampFormat || + !StrEquals(tConfig.log.timestampFormat, "default")) { LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig.log.timestampFormat); } diff --git a/src/Room.c b/src/Room.c index 8ea40ca..f07b389 100644 --- a/src/Room.c +++ b/src/Room.c @@ -126,7 +126,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) JsonValueFree(HashMapSet(content, key, JsonValueDuplicate(val))); } event = RoomEventCreate(sender_str, "m.room.create", "", content); - JsonFree(RoomEventSend(room, event)); + JsonFree(RoomEventSend(room, event, NULL)); JsonFree(event); UserAddJoin(user, room->id); @@ -134,7 +134,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) content = HashMapCreate(); JsonSet(content, JsonValueString("join"), 1, "membership"); event = RoomEventCreate(sender_str, "m.room.member", sender_str, content); - JsonFree(RoomEventSend(room, event)); + JsonFree(RoomEventSend(room, event, NULL)); JsonFree(event); /* m.room.power_levels */ @@ -155,7 +155,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) HashMapSet(content, key, JsonValueDuplicate(val)); } event = RoomEventCreate(sender_str, "m.room.power_levels", "", content); - JsonFree(RoomEventSend(room, event)); + JsonFree(RoomEventSend(room, event, NULL)); JsonFree(event); /* Presets */ @@ -203,7 +203,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) event = RoomEventCreate( \ sender_str, \ "m.room." #p, "", content); \ - JsonFree(RoomEventSend(room, event)); \ + JsonFree(RoomEventSend(room, event, NULL)); \ JsonFree(event); \ } \ } \ @@ -227,7 +227,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) } HashMapSet(rseObject, "sender", JsonValueString(sender_str)); - JsonFree(RoomEventSend(room, rseObject)); + JsonFree(RoomEventSend(room, rseObject, NULL)); JsonFree(rseObject); } @@ -237,7 +237,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) content = HashMapCreate(); JsonSet(content, JsonValueString(req->name), 1, "name"); event = RoomEventCreate(sender_str, "m.room.name", "", content); - JsonFree(RoomEventSend(room, event)); + JsonFree(RoomEventSend(room, event, NULL)); JsonFree(event); } if (req->topic) @@ -245,7 +245,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) content = HashMapCreate(); JsonSet(content, JsonValueString(req->topic), 1, "topic"); event = RoomEventCreate(sender_str, "m.room.topic", "", content); - JsonFree(RoomEventSend(room, event)); + JsonFree(RoomEventSend(room, event, NULL)); JsonFree(event); } @@ -269,7 +269,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) event = RoomEventCreate( sender_str, "m.room.canonical_alias", "", content); - JsonFree(RoomEventSend(room, event)); + JsonFree(RoomEventSend(room, event, NULL)); JsonFree(event); Free(fullStr); @@ -299,7 +299,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s) } event = RoomEventCreate(sender_str, "m.room.power_levels", "", pl_content); - JsonFree(RoomEventSend(room, event)); + JsonFree(RoomEventSend(room, event, NULL)); JsonFree(event); JsonValueFree(JsonSet( @@ -688,7 +688,7 @@ PopulateEventV1(Room * room, HashMap * event, PduV1 * pdu, ServerPart serv) return false; } static bool -AuthoriseCreateV1(PduV1 pdu) +AuthoriseCreateV1(PduV1 pdu, char **errp) { bool ret = true; CommonID sender = { 0 }, room_id = { 0 }; @@ -696,12 +696,20 @@ AuthoriseCreateV1(PduV1 pdu) if (ArraySize(pdu.auth_events) > 0) { + if (errp) + { + *errp = "Room creation event has auth events"; + } ret = false; goto finish; } if (!ParseCommonID(pdu.room_id, &room_id) || !ParseCommonID(pdu.sender, &sender)) { + if (errp) + { + *errp = "Couldn't parse the sender/room ID"; + } ret = false; goto finish; } @@ -709,12 +717,20 @@ AuthoriseCreateV1(PduV1 pdu) id_serv = ParserRecomposeServerPart(room_id.server); if (!StrEquals(sender_serv, id_serv)) { + if (errp) + { + *errp = "Room is not properly namespaced"; + } ret = false; goto finish; } /* TODO: Check room_version as in step 1.3 */ if (!HashMapGet(pdu.content, "creator")) { + if (errp) + { + *errp = "Room creation event has auth events"; + } ret = false; goto finish; } @@ -756,7 +772,10 @@ ValidAuthEventV1(PduV1 *auth_pdu, PduV1 *pdu) JsonValue *third_pid = JsonGet(pdu->content, 1, "third_party_invite"); - if (IsState(auth_pdu, "m.room.member", auth_pdu->sender)) + /* The PDU's state_key is the target. So we check if if the + * auth PDU would count as the target's membership. Was very fun to + * find that out when I wanted to kick my 'yukari' alt. */ + if (IsState(auth_pdu, "m.room.member", pdu->state_key)) { /* TODO: Check if it's the latest in terms of [pdu] */ return true; @@ -788,6 +807,8 @@ ValidAuthEventV1(PduV1 *auth_pdu, PduV1 *pdu) static bool VerifyPDUV1(PduV1 *auth_pdu) { + /* TODO: The PDU could come from an unknown source, which may lack + * the tools to verify softfailing(or a source that we may not trust)*/ /* TODO: * https://spec.matrix.org/v1.7/server-server-api/ * #checks-performed-on-receipt-of-a-pdu */ @@ -804,7 +825,7 @@ VerifyPDUV1(PduV1 *auth_pdu) * soft-failed */ } static bool -ConsiderAuthEventsV1(Room * room, PduV1 pdu) +ConsiderAuthEventsV1(Room * room, PduV1 pdu, char **errp) { char *ignored; size_t i; @@ -823,6 +844,10 @@ ConsiderAuthEventsV1(Room * room, PduV1 pdu) { JsonFree(event); HashMapFree(state_keytype); + if (errp) + { + *errp = "Couldn't parse an auth event"; + } return false; /* Yeah... we aren't doing that. */ } @@ -834,6 +859,11 @@ ConsiderAuthEventsV1(Room * room, PduV1 pdu) if (HashMapGet(state_keytype, key_type_id)) { /* Duplicate found! We actually ignore it's actual value. */ + if (errp) + { + *errp = "Duplicate auth event was found"; + } + JsonFree(event); PduV1Free(&auth_pdu); @@ -851,6 +881,10 @@ ConsiderAuthEventsV1(Room * room, PduV1 pdu) * described in the server specification, reject. */ if (!ValidAuthEventV1(&auth_pdu, &pdu)) { + if (errp) + { + *errp = "Invalid authevent given."; + } JsonFree(event); PduV1Free(&auth_pdu); HashMapFree(state_keytype); @@ -861,6 +895,10 @@ ConsiderAuthEventsV1(Room * room, PduV1 pdu) * TODO */ if (!VerifyPDUV1(&auth_pdu)) { + if (errp) + { + *errp = "Event depends on rejected PDUs"; + } PduV1Free(&auth_pdu); JsonFree(event); HashMapFree(state_keytype); @@ -878,6 +916,10 @@ ConsiderAuthEventsV1(Room * room, PduV1 pdu) PduV1Free(&auth_pdu); } HashMapFree(state_keytype); + if (!room_create && errp) + { + *errp = "Room creation event was not in the PDU's auth events"; + } return room_create; /* Step 2.4 is actually done here. */ } static bool @@ -915,16 +957,24 @@ end: return ret; } static bool -AuthoriseAliasV1(PduV1 pdu) +AuthoriseAliasV1(PduV1 pdu, char **errp) { /* Step 4.1: If event has no state_key, reject. */ if (!pdu.state_key || StrEquals(pdu.state_key, "")) { + if (errp) + { + *errp = "Step 4.1 fail: no state key in the alias"; + } return false; } /* Step 4.2: If sender's domain doesn't matches state_key, reject. */ if (!VerifyServers(pdu.state_key, pdu.sender)) { + if (errp) + { + *errp = "Step 4.2 fail: alias domain doesnt match statekey"; + } return false; } @@ -932,7 +982,7 @@ AuthoriseAliasV1(PduV1 pdu) return true; } static bool -AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state) +AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state, char **errp) { int64_t invite_level; int64_t pdu_level; @@ -947,12 +997,20 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state) /* Step 5.3.1.1: If target user is banned, reject. */ if (RoomUserHasMembership(room, state, pdu.state_key, "ban")) { + if (errp) + { + *errp = "Step 5.3.1.1 fail: target is banned"; + } return false; } /* Step 5.3.1.2: If content.third_party_invite does not have a signed * property, reject. */ if (!(signed_val = JsonGet(third_pi_obj, 1, "signed"))) { + if (errp) + { + *errp = "Step 5.3.1.2 fail: unsigned 3PII"; + } return false; } signed_obj = JsonValueAsObject(signed_val); @@ -961,16 +1019,28 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state) * reject. */ if (!(mxid = JsonGet(signed_obj, 1, "mxid"))) { + if (errp) + { + *errp = "Step 5.3.1.3 fail: no MXID in 3PII"; + } return false; } if (!(token = JsonGet(signed_obj, 1, "token"))) { + if (errp) + { + *errp = "Step 5.3.1.3 fail: no token in 3PII"; + } return false; } /* Step 5.3.1.4: If mxid does not match state_key, reject. */ if (!StrEquals(JsonValueAsString(mxid), pdu.state_key)) { + if (errp) + { + *errp = "Step 5.3.1.4 fail: 3PII's MXID != state_key"; + } return false; } @@ -980,6 +1050,10 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state) state, "m.room.third_party_invite", JsonValueAsString(token)))) { + if (errp) + { + *errp = "Step 5.3.1.5 fail: no proper 3PII event"; + } return false; } third_pi_event = RoomEventFetch(room, third_pi_id); @@ -989,6 +1063,10 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state) thirdpi_event_sender = JsonValueAsString(JsonGet(third_pi_event, 1, "sender")); if (!StrEquals(thirdpi_event_sender, pdu.sender)) { + if (errp) + { + *errp = "Step 5.3.1.6 fail: sender does not match 3PII"; + } JsonFree(third_pi_event); return false; } @@ -1003,6 +1081,10 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state) * - A list of public keys in the public_keys property. */ /* Step 5.3.1.8: Otherwise, reject. */ + if (errp) + { + *errp = "Step 5.3.1.8 fail: signature check do not match"; + } return false; } @@ -1010,12 +1092,20 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state) * reject. */ if (!RoomUserHasMembership(room, state, pdu.sender, "join")) { + if (errp) + { + *errp = "Step 5.3.2 fail: sender is not 'join'ed"; + } return false; } - /* Step 5.3.3: If target user’s current membership state is join or ban, reject. */ + /* Step 5.3.3: If target user's current membership state is join or ban, reject. */ if (RoomUserHasMembership(room, state, pdu.state_key, "join") || RoomUserHasMembership(room, state, pdu.state_key, "ban")) { + if (errp) + { + *errp = "Step 5.3.3 fail: target is 'join'|'ban'd"; + } return false; } @@ -1028,10 +1118,14 @@ AuthorizeInviteMembershipV1(Room * room, PduV1 pdu, State *state) return true; } /* Step 5.3.5: Otherwise, reject. */ + if (errp) + { + *errp = "Step 5.3.5 fail: sender has no permissions to do so"; + } return false; } static bool -AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, State *state) +AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, State *state, char **errp) { int64_t ban_level = RoomMinPL(room, state, NULL, "ban"); int64_t kick_level = RoomMinPL(room, state, NULL, "kick"); @@ -1042,15 +1136,26 @@ AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, State *state) * that user's current membership state is invite or join. */ if (StrEquals(pdu.sender, pdu.state_key)) { - return + bool flag = RoomUserHasMembership(room, state, pdu.sender, "invite") || RoomUserHasMembership(room, state, pdu.sender, "join"); + if (!flag && errp) + { + *errp = "Step 5.4.1 fail: user tries to leave but is " + "~'invite' AND ~'join'."; + } + return flag; } /* Step 5.4.2: If the sender's current membership state is not join, * reject. */ if (!RoomUserHasMembership(room, state, pdu.sender, "join")) { + if (errp) + { + *errp = "Step 5.4.2 fail: sender tries to kick but is " + "~'invite'."; + } return false; } @@ -1059,6 +1164,11 @@ AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, State *state) if (RoomUserHasMembership(room, state, pdu.state_key, "ban") && sender_level < ban_level) { + if (errp) + { + *errp = "Step 5.4.3 fail: sender tries to unban but has no " + "permissions to do so."; + } return false; } @@ -1071,16 +1181,26 @@ AuthorizeLeaveMembershipV1(Room * room, PduV1 pdu, State *state) } /* Step 5.4.5: Otherwise, reject. */ + if (errp) + { + *errp = "Step 5.4.5 fail: sender tried to kick but has no " + "permissions to do so."; + } return false; } static bool -AuthorizeBanMembershipV1(Room * room, PduV1 pdu, State *state) +AuthorizeBanMembershipV1(Room * room, PduV1 pdu, State *state, char **errp) { int64_t ban_pl, pdu_pl, target_pl; /* Step 5.5.1: If the sender's current membership state is not join, reject. */ if (!RoomUserHasMembership(room, state, pdu.sender, "join")) { + if (errp) + { + *errp = "Step 5.5.1 fail: sender tries to ban but is not " + "'join'ed"; + } return false; } @@ -1096,10 +1216,15 @@ AuthorizeBanMembershipV1(Room * room, PduV1 pdu, State *state) } /* Step 5.5.3: Otherwise, reject. */ + if (errp) + { + *errp = "Step 5.5.3 fail: sender tries to ban has no permissions to " + "do so"; + } return false; } static bool -AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, State *state) +AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, State *state, char **errp) { /* Step 5.2.1: If the only previous event is an m.room.create and the * state_key is the creator, allow. */ @@ -1132,11 +1257,20 @@ AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, State *state) /* Step 5.2.2: If the sender does not match state_key, reject. */ if (!StrEquals(pdu.sender, pdu.state_key)) { + if (errp) + { + *errp = "Step 5.2.2 fail: sender does not match statekey " + "on 'join'"; + } return false; } /* Step 5.2.3: If the sender is banned, reject. */ if (RoomUserHasMembership(room, state, pdu.sender, "ban")) { + if (errp) + { + *errp = "Step 5.2.2 fail: sender is banned on 'join'"; + } return false; } @@ -1153,11 +1287,16 @@ AuthorizeJoinMembershipV1(Room * room, PduV1 pdu, State *state) { return true; } + /* Step 5.2.6: Otherwise, reject. */ + if (errp) + { + *errp = "Step 5.2.6 fail: join_rule does not allow 'join'"; + } return false; } static bool -AuthoriseMemberV1(Room * room, PduV1 pdu, State *state) +AuthoriseMemberV1(Room * room, PduV1 pdu, State *state, char **errp) { JsonValue *membership; char *membership_str; @@ -1167,18 +1306,26 @@ AuthoriseMemberV1(Room * room, PduV1 pdu, State *state) StrEquals(pdu.state_key, "") || !(membership = JsonGet(pdu.content, 1, "membership"))) { + if (errp) + { + *errp = "Step 5.1 fail: broken membership's statekey/membership"; + } return false; } if (JsonValueType(membership) != JSON_STRING) { /* Also check for the type */ + if (errp) + { + *errp = "Step 5.1 fail: broken membership's membership"; + } return false; } membership_str = JsonValueAsString(membership); #define JumpIfMembership(mem, func) do { \ if (StrEquals(membership_str, mem)) \ { \ - return func(room, pdu, state); \ + return func(room, pdu, state, errp); \ } \ } while (0) @@ -1375,7 +1522,7 @@ AuthorisePowerLevelsV1(Room * room, PduV1 pdu, State *state) return true; } bool -RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state) +RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state, char **errp) { HashMap *create_event; char *create_event_id; @@ -1385,11 +1532,11 @@ RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state) /* Step 1: If m.room.create */ if (StrEquals(pdu.type, "m.room.create")) { - return AuthoriseCreateV1(pdu); + return AuthoriseCreateV1(pdu, errp); } /* Step 2: Considering the event's auth_events. */ - if (!ConsiderAuthEventsV1(room, pdu)) + if (!ConsiderAuthEventsV1(room, pdu, errp)) { return false; } @@ -1403,6 +1550,11 @@ RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state) if (!state || !create_event_id) { /* At this point, [create_event_id] has to exist */ + if (errp) + { + *errp = "No creation event in the state. Needless to say, " + "your room is done for."; + } return false; } create_event = RoomEventFetch(room, create_event_id); @@ -1416,6 +1568,10 @@ RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state) char *p_sender = pdu.sender; if (!VerifyServers(c_sender, p_sender)) { + if (errp) + { + *errp = "Trying to access a room with m.federate off."; + } return false; } } @@ -1425,13 +1581,13 @@ RoomAuthoriseEventV1(Room * room, PduV1 pdu, State *state) /* Step 4: If type is m.room.aliases */ if (StrEquals(pdu.type, "m.room.aliases")) { - return AuthoriseAliasV1(pdu); + return AuthoriseAliasV1(pdu, errp); } /* Step 5: If type is m.room.member */ if (StrEquals(pdu.type, "m.room.member")) { - return AuthoriseMemberV1(room, pdu, state); + return AuthoriseMemberV1(room, pdu, state, errp); } /* Step 6: If the sender's current membership state is not join, reject. */ if (!RoomUserHasMembership(room, state, pdu.sender, "join")) @@ -1571,19 +1727,27 @@ EventFitsV1(PduV1 pdu) return ret; } static PduV1Status -RoomGetEventStatusV1(Room *room, PduV1 *pdu, State *prev, bool client) +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)) + 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 @@ -1595,7 +1759,7 @@ RoomGetEventStatusV1(Room *room, PduV1 *pdu, State *prev, bool client) { State *current = StateCurrent(room); - if (!RoomAuthoriseEventV1(room, *pdu, current)) + if (!RoomAuthoriseEventV1(room, *pdu, current, NULL)) { StateFree(current); return PDUV1_STATUS_SOFTFAIL; @@ -1606,7 +1770,7 @@ RoomGetEventStatusV1(Room *room, PduV1 *pdu, State *prev, bool client) return PDUV1_STATUS_ACCEPTED; } static HashMap * -RoomEventSendV1(Room * room, HashMap * event) +RoomEventSendV1(Room * room, HashMap * event, char **errp) { PduV1 pdu = { 0 }; HashMap *pdu_object = NULL; @@ -1702,8 +1866,7 @@ RoomEventSendV1(Room * room, HashMap * event) */ /* TODO: For PDU events, we should verify their hashes. */ - status = RoomGetEventStatusV1(room, &pdu, state, client_event); - Log(LOG_DEBUG, "status='%s'", PduV1StatusToStr(status)); + status = RoomGetEventStatusV1(room, &pdu, state, client_event, errp); if (status == PDUV1_STATUS_DROPPED) { goto finish; @@ -1745,7 +1908,7 @@ RoomEventSendV3(Room * room, HashMap * event) return NULL; } HashMap * -RoomEventSend(Room * room, HashMap * event) +RoomEventSend(Room * room, HashMap * event, char **errp) { if (!room || !event) { @@ -1754,7 +1917,7 @@ RoomEventSend(Room * room, HashMap * event) if (room->version < 3) { /* Manage with PDUv1 */ - return RoomEventSendV1(room, event); + return RoomEventSendV1(room, event, errp); } /* Manage with PDUv3 otherwise */ return RoomEventSendV3(room, event); @@ -2198,7 +2361,7 @@ RoomSendInvite(User *sender, bool direct, char *user, Room *room) JsonSet(content, JsonValueBoolean(direct), 1, "is_direct"); JsonSet(content, JsonValueString("invite"), 1, "membership"); event = RoomEventCreate(senderStr, "m.room.member", user, content); - JsonFree(RoomEventSend(room, event)); + JsonFree(RoomEventSend(room, event, NULL)); JsonFree(event); /* TODO: Send to "local" invite list if user is local. */ @@ -2303,7 +2466,55 @@ end: } bool -RoomJoin(Room *room, User *user) +RoomLeave(Room *room, User *user, char **errp) +{ + CommonID *userId = NULL; + char *userString = NULL; + char *server = NULL; + HashMap *content = NULL; + HashMap *event = NULL; + HashMap *pdu = NULL; + bool ret = false; + + if (!room || !user) + { + return false; + } + + server = ConfigGetServerName(room->db); + if (!server) + { + return false; + } + userId = UserIdParse(UserGetName(user), server); + userId->sigil = '@'; + userString = ParserRecomposeCommonID(*userId); + Free(server); + server = NULL; + + content = HashMapCreate(); + JsonSet(content, JsonValueString("leave"), 1, "membership"); + event = RoomEventCreate(userString, "m.room.member", userString, content); + pdu = RoomEventSend(room, event, errp); + + /* TODO: One ought to be extremely careful with managing users in those + * scenarios, as the DB flushes do not sync. */ + ret = !!pdu; + if (ret) + { + UserRemoveJoin(user, room->id); + } + UserIdFree(userId); + JsonFree(event); + JsonFree(pdu); + if (userString) + { + Free(userString); + } + return ret; +} +bool +RoomJoin(Room *room, User *user, char **errp) { CommonID *userId = NULL; char *userString = NULL; @@ -2338,9 +2549,15 @@ RoomJoin(Room *room, User *user) content = HashMapCreate(); JsonSet(content, JsonValueString("join"), 1, "membership"); event = RoomEventCreate(userString, "m.room.member", userString, content); - pdu = RoomEventSend(room, event); + pdu = RoomEventSend(room, event, errp); + /* TODO: One ought to be extremely careful with managing users in those + * scenarios, as the DB flushes do not sync. */ ret = !!pdu; + if (ret) + { + UserAddJoin(user, room->id); + } end: UserIdFree(userId); JsonFree(event); diff --git a/src/Routes.c b/src/Routes.c index 52d801e..32f8f68 100644 --- a/src/Routes.c +++ b/src/Routes.c @@ -82,7 +82,7 @@ RouterBuild(void) R("/_matrix/client/v3/rooms/(.*)/state/(.*)", RouteSendState); R("/_matrix/client/v3/rooms/(.*)/event/(.*)", RouteFetchEvent); R("/_matrix/client/v3/rooms/(.*)/(join|leave)", RouteJoinRoom); - R("/_matrix/client/v3/rooms/(.*)/(kick|ban)", RouteKickRoom); + R("/_matrix/client/v3/rooms/(.*)/(kick|ban|unban)", RouteKickRoom); R("/_matrix/client/v3/rooms/(.*)/messages", RouteMessages); R("/_matrix/client/v3/join/(.*)", RouteJoinRoomAlias); diff --git a/src/Routes/RouteActRoom.c b/src/Routes/RouteActRoom.c index 3782af7..173dcd1 100644 --- a/src/Routes/RouteActRoom.c +++ b/src/Routes/RouteActRoom.c @@ -130,11 +130,10 @@ ROUTE_IMPL(RouteJoinRoom, path, argp) goto finish; } /* TODO: Custom reason parameter. */ - if (!RoomJoin(room, user)) + if (!RoomJoin(room, user, &err)) { - err = "User could not be the room due to unknown reasons."; - HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); - response = MatrixErrorCreate(M_UNKNOWN, err); + HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); + response = MatrixErrorCreate(M_BAD_STATE, err); goto finish; } @@ -143,9 +142,6 @@ ROUTE_IMPL(RouteJoinRoom, path, argp) } else if (StrEquals(action, "leave")) { - HashMap *membership, *content; - HashMap *pduResponse; - if (!RoomContainsUser(room, sender)) { err = "User is not already in the room."; @@ -155,25 +151,12 @@ ROUTE_IMPL(RouteJoinRoom, path, argp) goto finish; } - content = HashMapCreate(); - membership = RoomEventCreate( - sender, "m.room.member", sender, - content - ); - - HashMapSet(content, "membership", JsonValueString("leave")); - pduResponse = RoomEventSend(room, membership); - if (!pduResponse) + if (!RoomLeave(room, user, &err)) { - err = "Couldn't accept leave event"; - HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); - response = MatrixErrorCreate(M_UNKNOWN, err); - - JsonFree(membership); + HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); + response = MatrixErrorCreate(M_BAD_STATE, err); goto finish; } - JsonFree(pduResponse); - JsonFree(membership); response = HashMapCreate(); } @@ -213,8 +196,7 @@ ROUTE_IMPL(RouteKickRoom, path, argp) char *action = ArrayGet(path, 1); char *kicked = NULL, *reason = NULL; char *sender = NULL, *serverName = NULL; - char *membershipState = StrEquals(action, "kick") ? - "leave" : "ban"; + char *membershipState; Room *room = NULL; @@ -226,6 +208,26 @@ ROUTE_IMPL(RouteKickRoom, path, argp) HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); return MatrixErrorCreate(M_UNKNOWN, NULL); } + + if (StrEquals(action, "kick")) + { + membershipState = "leave"; + } + else if (StrEquals(action, "ban")) + { + membershipState = "ban"; + } + else if (StrEquals(action, "unban")) + { + membershipState = "leave"; + } + else + { + /* Should be impossible */ + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + return MatrixErrorCreate(M_UNKNOWN, NULL); + } + if (HttpRequestMethodGet(args->context) != HTTP_POST) { err = "Unknown request method."; @@ -284,7 +286,7 @@ ROUTE_IMPL(RouteKickRoom, path, argp) response = MatrixErrorCreate(M_FORBIDDEN, err); goto finish; } - if (!RoomContainsUser(room, kicked)) + if (RoomContainsUser(room, kicked) == StrEquals(action, "unban")) { err = "Victim is not present in the room"; HttpResponseStatus(args->context, HTTP_BAD_REQUEST); @@ -305,12 +307,11 @@ ROUTE_IMPL(RouteKickRoom, path, argp) HashMapSet(content, "reason", JsonValueString(reason)); } - pduResponse = RoomEventSend(room, membership); + pduResponse = RoomEventSend(room, membership, &err); if (!pduResponse) { - err = "Couldn't accept event"; - HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); - response = MatrixErrorCreate(M_UNKNOWN, err); + HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); + response = MatrixErrorCreate(M_BAD_STATE, err); JsonFree(membership); goto finish; diff --git a/src/Routes/RouteJoinRoomAlias.c b/src/Routes/RouteJoinRoomAlias.c index 1cd1e15..51bb1d0 100644 --- a/src/Routes/RouteJoinRoomAlias.c +++ b/src/Routes/RouteJoinRoomAlias.c @@ -143,11 +143,10 @@ ROUTE_IMPL(RouteJoinRoomAlias, path, argp) goto finish; } /* TODO: Custom reason parameter. */ - if (!RoomJoin(room, user)) + if (!RoomJoin(room, user, &err)) { - err = "User could not be the room due to unknown reasons."; - HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); - response = MatrixErrorCreate(M_UNKNOWN, err); + HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); + response = MatrixErrorCreate(M_FORBIDDEN, err); goto finish; } diff --git a/src/Routes/RouteSendEvent.c b/src/Routes/RouteSendEvent.c index 807da60..b10bd9d 100644 --- a/src/Routes/RouteSendEvent.c +++ b/src/Routes/RouteSendEvent.c @@ -59,7 +59,7 @@ ROUTE_IMPL(RouteSendEvent, path, argp) Room *room = NULL; - char *err; + char *err = NULL; if (!roomId || !eventType || !transId) { @@ -123,12 +123,11 @@ ROUTE_IMPL(RouteSendEvent, path, argp) } event = RoomEventCreate(sender, eventType, NULL, JsonDuplicate(request)); - filled = RoomEventSend(room, event); + filled = RoomEventSend(room, event, &err); JsonFree(event); if (!filled) { - err = "User is not allowed to send event."; HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); response = MatrixErrorCreate(M_FORBIDDEN, err); goto finish; @@ -238,12 +237,11 @@ ROUTE_IMPL(RouteSendState, path, argp) eventType, stateKey ? stateKey : "", JsonDuplicate(request) ); - filled = RoomEventSend(room, event); + filled = RoomEventSend(room, event, &err); JsonFree(event); if (!filled) { - err = "User is not allowed to send state."; HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); response = MatrixErrorCreate(M_FORBIDDEN, err); goto finish; diff --git a/src/Routes/RouteUserProfile.c b/src/Routes/RouteUserProfile.c index 376114b..9e144da 100644 --- a/src/Routes/RouteUserProfile.c +++ b/src/Routes/RouteUserProfile.c @@ -63,7 +63,7 @@ SendMembership(Db *db, User *user) HashMapSet(content, "displayname", JsonValueString(displayname)); HashMapSet(content, "avatar_url", JsonValueString(avatar_url)); - JsonFree(RoomEventSend(room, membership)); + JsonFree(RoomEventSend(room, membership, NULL)); JsonFree(membership); RoomUnlock(room); diff --git a/src/State.c b/src/State.c index 2ef3f6f..c5756dc 100644 --- a/src/State.c +++ b/src/State.c @@ -209,7 +209,7 @@ FixoutConflictV1(char *t, Room *room, State *R, HashMap *conflicts) char *msg; PduV1FromJson(event, &pdu, &msg); - if (RoomAuthoriseEventV1(room, pdu, R)) + if (RoomAuthoriseEventV1(room, pdu, R, NULL)) { StateSet(R, pdu.type, pdu.state_key, pdu.event_id); } @@ -289,7 +289,7 @@ StateResolveV1(Room * room, Array * states) continue; } - if (RoomAuthoriseEventV1(room, pdu, R)) + if (RoomAuthoriseEventV1(room, pdu, R, NULL)) { StateSet(R, pdu.type, pdu.state_key, pdu.event_id); PduV1Free(&pdu); diff --git a/src/User.c b/src/User.c index e1eee00..d5c507e 100644 --- a/src/User.c +++ b/src/User.c @@ -130,8 +130,14 @@ UserLock(Db * db, char *name) user->name = StrDuplicate(name); user->deviceId = NULL; - user->inviteRef = DbLock(db, 3, "users", user->name, "invites"); - user->joinRef = DbLock(db, 3, "users", user->name, "joins"); + if (!(user->inviteRef = DbLock(db, 3, "users", user->name, "invites"))) + { + user->inviteRef = DbCreate(db, 3, "users", user->name, "invites"); + } + if (!(user->joinRef = DbLock(db, 3, "users", user->name, "joins"))) + { + user->joinRef = DbCreate(db, 3, "users", user->name, "joins"); + } return user; } @@ -184,7 +190,7 @@ UserAuthenticate(Db * db, char *accessToken) bool UserUnlock(User * user) { - bool ret; + bool refOk, joinOk, inviteOk; Db *db; DbRef *ref; @@ -198,14 +204,15 @@ UserUnlock(User * user) Free(user->name); Free(user->deviceId); - ret = DbUnlock(db, ref) && - DbUnlock(db, user->joinRef) && - DbUnlock(db, user->inviteRef); + refOk = DbUnlock(db, ref); + joinOk = DbUnlock(db, user->joinRef); + inviteOk = DbUnlock(db, user->inviteRef); + user->db = NULL; user->ref = NULL; Free(user); - return ret; + return refOk && joinOk && inviteOk; } User * @@ -1101,10 +1108,7 @@ UserAddJoin(User *user, char *roomId) data = DbJson(user->joinRef); - if (data && !HashMapGet(data, roomId)) - { - JsonValueFree(HashMapSet(data, roomId, JsonValueNull())); - } + JsonValueFree(HashMapSet(data, roomId, JsonValueObject(HashMapCreate()))); UserNotifyUser(user); } @@ -1112,13 +1116,13 @@ void UserRemoveJoin(User *user, char *roomId) { HashMap *data; - if (!user || !roomId) + if (!user || !roomId || !user->joinRef) { return; } data = DbJson(user->joinRef); - + JsonValueFree(HashMapDelete(data, roomId)); UserNotifyUser(user); } diff --git a/src/include/Room.h b/src/include/Room.h index f9c6241..880a3dd 100644 --- a/src/include/Room.h +++ b/src/include/Room.h @@ -144,7 +144,7 @@ extern int RoomPrevEventsSet(Room *, Array *); * the room version, which includes setting the * prev_events and auth_events fields correctly. */ -extern HashMap * RoomEventSend(Room *, HashMap *); +extern HashMap * RoomEventSend(Room *, HashMap *, char **); /** * Sends an invite to a user in a room, and tries @@ -170,7 +170,7 @@ extern HashMap * RoomEventClientify(HashMap *); * Verifies whenever an event(as a PDUv1) is * authorised by a room. */ -extern bool RoomAuthoriseEventV1(Room *, PduV1, State *); +extern bool RoomAuthoriseEventV1(Room *, PduV1, State *, char **); /** * Gets the room's creator as a ServerPart. This value should @@ -229,7 +229,13 @@ extern bool RoomCanJoin(Room *, char *); * Makes a local user join a room, and returns true if * the room was joined. */ -extern bool RoomJoin(Room *, User *); +extern bool RoomJoin(Room *, User *, char **); + +/** + * Makes a local user leave a room, and returns true if + * the room was left. + */ +extern bool RoomLeave(Room *, User *, char **); /** * Adds or overwrites a room alias.