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) "" "%s | Telodendria" "" - "" + "" "" "" "
"
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.