about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/xmpp/presence.c560
1 files changed, 311 insertions, 249 deletions
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index 31fc6cde..6b13f4a7 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -78,7 +78,6 @@ static void _available_handler(xmpp_stanza_t *const stanza);
 
 void _send_caps_request(char *node, char *caps_key, char *id, char *from);
 static void _send_room_presence(xmpp_stanza_t *presence);
-
 static void _send_presence_stanza(xmpp_stanza_t *const stanza);
 
 void
@@ -87,52 +86,6 @@ presence_sub_requests_init(void)
     sub_requests_ac = autocomplete_new();
 }
 
-static int
-_presence_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata)
-{
-    log_debug("Presence stanza handler fired");
-
-    char *text;
-    size_t text_size;
-    xmpp_stanza_to_text(stanza, &text, &text_size);
-    gboolean cont = plugins_on_presence_stanza_receive(text);
-    xmpp_free(connection_get_ctx(), text);
-    if (!cont) {
-        return 1;
-    }
-
-    const char *type = xmpp_stanza_get_type(stanza);
-
-    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
-        _presence_error_handler(stanza);
-    }
-
-    if (g_strcmp0(type, STANZA_TYPE_UNAVAILABLE) == 0) {
-        _unavailable_handler(stanza);
-    }
-
-    if (g_strcmp0(type, STANZA_TYPE_SUBSCRIBE) == 0) {
-        _subscribe_handler(stanza);
-    }
-
-    if (g_strcmp0(type, STANZA_TYPE_SUBSCRIBED) == 0) {
-        _subscribed_handler(stanza);
-    }
-
-    if (g_strcmp0(type, STANZA_TYPE_UNSUBSCRIBED) == 0) {
-        _unsubscribed_handler(stanza);
-    }
-
-    xmpp_stanza_t *mucuser = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
-    if (mucuser) {
-        _muc_user_handler(stanza);
-    }
-
-    _available_handler(stanza);
-
-    return 1;
-}
-
 void
 presence_handlers_init(void)
 {
@@ -146,13 +99,10 @@ presence_subscription(const char *const jid, const jabber_subscr_t action)
 {
     assert(jid != NULL);
 
-    xmpp_ctx_t * const ctx = connection_get_ctx();
-    const char *type = NULL;
-
     Jid *jidp = jid_create(jid);
-
     autocomplete_remove(sub_requests_ac, jidp->barejid);
 
+    const char *type = NULL;
     switch (action)
     {
         case PRESENCE_SUBSCRIBE:
@@ -168,20 +118,27 @@ presence_subscription(const char *const jid, const jabber_subscr_t action)
             type = STANZA_TYPE_UNSUBSCRIBED;
             break;
         default:
-            log_warning("Attempt to send unknown subscription action: %s", jid);
             break;
     }
+    if (!type) {
+        log_error("Attempt to send unknown subscription action: %s", jid);
+        return;
+    }
 
+    xmpp_ctx_t * const ctx = connection_get_ctx();
     xmpp_stanza_t *presence = xmpp_presence_new(ctx);
+
     char *id = create_unique_id("sub");
     xmpp_stanza_set_id(presence, id);
+    free(id);
+
     xmpp_stanza_set_type(presence, type);
     xmpp_stanza_set_to(presence, jidp->barejid);
+    jid_destroy(jidp);
+
     _send_presence_stanza(presence);
-    xmpp_stanza_release(presence);
 
-    jid_destroy(jidp);
-    free(id);
+    xmpp_stanza_release(presence);
 }
 
 GSList*
@@ -212,20 +169,17 @@ gboolean
 presence_sub_request_exists(const char *const bare_jid)
 {
     gboolean result = FALSE;
-    GSList *requests_p = autocomplete_create_list(sub_requests_ac);
-    GSList *requests = requests_p;
 
-    while (requests) {
-        if (strcmp(requests->data, bare_jid) == 0) {
+    GSList *requests = autocomplete_create_list(sub_requests_ac);
+    GSList *curr = requests;
+    while (curr) {
+        if (strcmp(curr->data, bare_jid) == 0) {
             result = TRUE;
             break;
         }
-        requests = g_slist_next(requests);
-    }
-
-    if (requests_p) {
-        g_slist_free_full(requests_p, free);
+        curr = g_slist_next(curr);
     }
+    g_slist_free_full(requests, free);
 
     return result;
 }
@@ -250,16 +204,19 @@ presence_send(const resource_presence_t presence_type, const char *const msg, co
         log_debug("Updating presence: %s", string_from_resource_presence(presence_type));
     }
 
-    xmpp_ctx_t * const ctx = connection_get_ctx();
-    const int pri = accounts_get_priority_for_presence_type(session_get_account_name(), presence_type);
-    const char *show = stanza_get_presence_string_from_type(presence_type);
-
     connection_set_presence_msg(msg);
+
+    const int pri = accounts_get_priority_for_presence_type(session_get_account_name(), presence_type);
     connection_set_priority(pri);
 
+    xmpp_ctx_t * const ctx = connection_get_ctx();
     xmpp_stanza_t *presence = xmpp_presence_new(ctx);
+
     char *id = create_unique_id("presence");
     xmpp_stanza_set_id(presence, id);
+    free(id);
+
+    const char *show = stanza_get_presence_string_from_type(presence_type);
     stanza_attach_show(ctx, presence, show);
 
     stanza_attach_status(ctx, presence, msg);
@@ -268,21 +225,28 @@ presence_send(const resource_presence_t presence_type, const char *const msg, co
         xmpp_stanza_t *x = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(x, STANZA_NAME_X);
         xmpp_stanza_set_ns(x, STANZA_NS_SIGNED);
+
         xmpp_stanza_t *signed_text = xmpp_stanza_new(ctx);
         xmpp_stanza_set_text(signed_text, signed_status);
+
         xmpp_stanza_add_child(x, signed_text);
         xmpp_stanza_release(signed_text);
+
         xmpp_stanza_add_child(presence, x);
         xmpp_stanza_release(x);
     }
 
     stanza_attach_priority(ctx, presence, pri);
+
     if (idle > 0) {
         stanza_attach_last_activity(ctx, presence, idle);
     }
+
     stanza_attach_caps(ctx, presence);
+
     _send_presence_stanza(presence);
     _send_room_presence(presence);
+
     xmpp_stanza_release(presence);
 
     // set last presence for account
@@ -290,10 +254,10 @@ presence_send(const resource_presence_t presence_type, const char *const msg, co
     if (last == NULL) {
         last = STANZA_TEXT_ONLINE;
     }
+
     char *account = session_get_account_name();
     accounts_set_last_presence(account, last);
     accounts_set_last_status(account, msg);
-    free(id);
 }
 
 static void
@@ -301,23 +265,21 @@ _send_room_presence(xmpp_stanza_t *presence)
 {
     GList *rooms = muc_rooms();
     GList *curr = rooms;
-
     while (curr) {
         const char *room = curr->data;
         const char *nick = muc_nick(room);
 
         if (nick) {
             char *full_room_jid = create_fulljid(room, nick);
-
             xmpp_stanza_set_to(presence, full_room_jid);
             log_debug("Sending presence to room: %s", full_room_jid);
-            _send_presence_stanza(presence);
             free(full_room_jid);
+
+            _send_presence_stanza(presence);
         }
 
         curr = g_list_next(curr);
     }
-
     g_list_free(rooms);
 }
 
@@ -325,16 +287,14 @@ void
 presence_join_room(const char *const room, const char *const nick, const char *const passwd)
 {
     Jid *jid = jid_create_from_bare_and_resource(room, nick);
-
     log_debug("Sending room join presence to: %s", jid->fulljid);
-    xmpp_ctx_t *ctx = connection_get_ctx();
-    resource_presence_t presence_type =
-        accounts_get_last_presence(session_get_account_name());
+
+    resource_presence_t presence_type = accounts_get_last_presence(session_get_account_name());
     const char *show = stanza_get_presence_string_from_type(presence_type);
     char *status = connection_get_presence_msg();
-    int pri = accounts_get_priority_for_presence_type(session_get_account_name(),
-        presence_type);
+    int pri = accounts_get_priority_for_presence_type(session_get_account_name(), presence_type);
 
+    xmpp_ctx_t *ctx = connection_get_ctx();
     xmpp_stanza_t *presence = stanza_create_room_join_presence(ctx, jid->fulljid, passwd);
     stanza_attach_show(ctx, presence, show);
     stanza_attach_status(ctx, presence, status);
@@ -342,8 +302,8 @@ presence_join_room(const char *const room, const char *const nick, const char *c
     stanza_attach_caps(ctx, presence);
 
     _send_presence_stanza(presence);
-    xmpp_stanza_release(presence);
 
+    xmpp_stanza_release(presence);
     jid_destroy(jid);
 }
 
@@ -354,25 +314,22 @@ presence_change_room_nick(const char *const room, const char *const nick)
     assert(nick != NULL);
 
     log_debug("Sending room nickname change to: %s, nick: %s", room, nick);
-    xmpp_ctx_t *ctx = connection_get_ctx();
-    resource_presence_t presence_type =
-        accounts_get_last_presence(session_get_account_name());
+    resource_presence_t presence_type = accounts_get_last_presence(session_get_account_name());
     const char *show = stanza_get_presence_string_from_type(presence_type);
     char *status = connection_get_presence_msg();
-    int pri = accounts_get_priority_for_presence_type(session_get_account_name(),
-        presence_type);
-
+    int pri = accounts_get_priority_for_presence_type(session_get_account_name(), presence_type);
     char *full_room_jid = create_fulljid(room, nick);
-    xmpp_stanza_t *presence =
-        stanza_create_room_newnick_presence(ctx, full_room_jid);
+
+    xmpp_ctx_t *ctx = connection_get_ctx();
+    xmpp_stanza_t *presence = stanza_create_room_newnick_presence(ctx, full_room_jid);
     stanza_attach_show(ctx, presence, show);
     stanza_attach_status(ctx, presence, status);
     stanza_attach_priority(ctx, presence, pri);
     stanza_attach_caps(ctx, presence);
 
     _send_presence_stanza(presence);
-    xmpp_stanza_release(presence);
 
+    xmpp_stanza_release(presence);
     free(full_room_jid);
 }
 
@@ -381,37 +338,83 @@ presence_leave_chat_room(const char *const room_jid)
 {
     assert(room_jid != NULL);
 
+    char *nick = muc_nick(room_jid);
+    if (!nick) {
+        log_error("Could not get nickname for room: %s", room_jid);
+        return;
+    }
+
     log_debug("Sending room leave presence to: %s", room_jid);
+
     xmpp_ctx_t *ctx = connection_get_ctx();
-    char *nick = muc_nick(room_jid);
+    xmpp_stanza_t *presence = stanza_create_room_leave_presence(ctx, room_jid, nick);
+
+    _send_presence_stanza(presence);
+
+    xmpp_stanza_release(presence);
+}
+
+static int
+_presence_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata)
+{
+    log_debug("Presence stanza handler fired");
+
+    char *text = NULL;
+    size_t text_size;
+    xmpp_stanza_to_text(stanza, &text, &text_size);
+
+    gboolean cont = plugins_on_presence_stanza_receive(text);
+    xmpp_free(connection_get_ctx(), text);
+    if (!cont) {
+        return 1;
+    }
+
+    const char *type = xmpp_stanza_get_type(stanza);
+
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        _presence_error_handler(stanza);
+    }
+
+    if (g_strcmp0(type, STANZA_TYPE_UNAVAILABLE) == 0) {
+        _unavailable_handler(stanza);
+    }
+
+    if (g_strcmp0(type, STANZA_TYPE_SUBSCRIBE) == 0) {
+        _subscribe_handler(stanza);
+    }
+
+    if (g_strcmp0(type, STANZA_TYPE_SUBSCRIBED) == 0) {
+        _subscribed_handler(stanza);
+    }
+
+    if (g_strcmp0(type, STANZA_TYPE_UNSUBSCRIBED) == 0) {
+        _unsubscribed_handler(stanza);
+    }
 
-    if (nick) {
-        xmpp_stanza_t *presence = stanza_create_room_leave_presence(ctx, room_jid, nick);
-        _send_presence_stanza(presence);
-        xmpp_stanza_release(presence);
+    xmpp_stanza_t *mucuser = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
+    if (mucuser) {
+        _muc_user_handler(stanza);
     }
+
+    _available_handler(stanza);
+
+    return 1;
 }
 
 static void
 _presence_error_handler(xmpp_stanza_t *const stanza)
 {
-    const char *id = xmpp_stanza_get_id(stanza);
-    const char *from = xmpp_stanza_get_from(stanza);
-    xmpp_stanza_t *error_stanza = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ERROR);
-    xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X);
     const char *xmlns = NULL;
+    xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X);
     if (x) {
         xmlns = xmpp_stanza_get_ns(x);
     }
-    const char *type = NULL;
-    if (error_stanza) {
-        type = xmpp_stanza_get_type(error_stanza);
-    }
+
+    const char *from = xmpp_stanza_get_from(stanza);
+    xmpp_stanza_t *error_stanza = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ERROR);
 
     // handle MUC join errors
     if (g_strcmp0(xmlns, STANZA_NS_MUC) == 0) {
-        Jid *fulljid = jid_create(from);
-
         const char *error_cond = NULL;
         xmpp_stanza_t *reason_st = xmpp_stanza_get_child_by_ns(error_stanza, STANZA_NS_STANZAS);
         if (reason_st) {
@@ -421,19 +424,19 @@ _presence_error_handler(xmpp_stanza_t *const stanza)
             error_cond = "unknown";
         }
 
+        Jid *fulljid = jid_create(from);
         log_info("Error joining room: %s, reason: %s", fulljid->barejid, error_cond);
         if (muc_active(fulljid->barejid)) {
             muc_leave(fulljid->barejid);
         }
         cons_show_error("Error joining room %s, reason: %s", fulljid->barejid, error_cond);
         jid_destroy(fulljid);
+
         return;
     }
 
-    // stanza_get_error never returns NULL
-    char *err_msg = stanza_get_error_message(stanza);
-
     GString *log_msg = g_string_new("presence stanza error received");
+    const char *id = xmpp_stanza_get_id(stanza);
     if (id) {
         g_string_append(log_msg, " id=");
         g_string_append(log_msg, id);
@@ -442,10 +445,18 @@ _presence_error_handler(xmpp_stanza_t *const stanza)
         g_string_append(log_msg, " from=");
         g_string_append(log_msg, from);
     }
+
+    const char *type = NULL;
+    if (error_stanza) {
+        type = xmpp_stanza_get_type(error_stanza);
+    }
     if (type) {
         g_string_append(log_msg, " type=");
         g_string_append(log_msg, type);
     }
+
+    // stanza_get_error never returns NULL
+    char *err_msg = stanza_get_error_message(stanza);
     g_string_append(log_msg, " error=");
     g_string_append(log_msg, err_msg);
 
@@ -462,14 +473,17 @@ _presence_error_handler(xmpp_stanza_t *const stanza)
     free(err_msg);
 }
 
-
 static void
 _unsubscribed_handler(xmpp_stanza_t *const stanza)
 {
     const char *from = xmpp_stanza_get_from(stanza);
-    Jid *from_jid = jid_create(from);
+    if (!from) {
+        log_warning("Unsubscribed presence handler received with no from attribute");
+        return;
+    }
     log_debug("Unsubscribed presence handler fired for %s", from);
 
+    Jid *from_jid = jid_create(from);
     sv_ev_subscription(from_jid->barejid, PRESENCE_UNSUBSCRIBED);
     autocomplete_remove(sub_requests_ac, from_jid->barejid);
 
@@ -480,9 +494,13 @@ static void
 _subscribed_handler(xmpp_stanza_t *const stanza)
 {
     const char *from = xmpp_stanza_get_from(stanza);
-    Jid *from_jid = jid_create(from);
+    if (!from) {
+        log_warning("Subscribed presence handler received with no from attribute");
+        return;
+    }
     log_debug("Subscribed presence handler fired for %s", from);
 
+    Jid *from_jid = jid_create(from);
     sv_ev_subscription(from_jid->barejid, PRESENCE_SUBSCRIBED);
     autocomplete_remove(sub_requests_ac, from_jid->barejid);
 
@@ -493,6 +511,9 @@ static void
 _subscribe_handler(xmpp_stanza_t *const stanza)
 {
     const char *from = xmpp_stanza_get_from(stanza);
+    if (!from) {
+        log_warning("Subscribe presence handler received with no from attribute", from);
+    }
     log_debug("Subscribe presence handler fired for %s", from);
 
     Jid *from_jid = jid_create(from);
@@ -514,6 +535,9 @@ _unavailable_handler(xmpp_stanza_t *const stanza)
     xmpp_conn_t *conn = connection_get_conn();
     const char *jid = xmpp_conn_get_jid(conn);
     const char *from = xmpp_stanza_get_from(stanza);
+    if (!from) {
+        log_warning("Unavailable presence received with no from attribute");
+    }
     log_debug("Unavailable presence handler fired for %s", from);
 
     Jid *my_jid = jid_create(jid);
@@ -524,9 +548,8 @@ _unavailable_handler(xmpp_stanza_t *const stanza)
         return;
     }
 
-    char *status_str = stanza_get_status(stanza, NULL);
-
     if (strcmp(my_jid->barejid, from_jid->barejid) !=0) {
+        char *status_str = stanza_get_status(stanza, NULL);
         if (from_jid->resourcepart) {
             sv_ev_contact_offline(from_jid->barejid, from_jid->resourcepart, status_str);
 
@@ -534,13 +557,13 @@ _unavailable_handler(xmpp_stanza_t *const stanza)
         } else {
             sv_ev_contact_offline(from_jid->barejid, "__prof_default", status_str);
         }
+        free(status_str);
     } else {
         if (from_jid->resourcepart) {
             connection_remove_available_resource(from_jid->resourcepart);
         }
     }
 
-    free(status_str);
     jid_destroy(my_jid);
     jid_destroy(from_jid);
 }
@@ -661,174 +684,213 @@ _available_handler(xmpp_stanza_t *const stanza)
 void
 _send_caps_request(char *node, char *caps_key, char *id, char *from)
 {
-    xmpp_ctx_t *ctx = connection_get_ctx();
-
-    if (node) {
-        log_debug("Node string: %s.", node);
-        if (!caps_cache_contains(caps_key)) {
-            log_debug("Capabilities not cached for '%s', sending discovery IQ.", from);
-            xmpp_stanza_t *iq = stanza_create_disco_info_iq(ctx, id, from, node);
-            iq_send_stanza(iq);
-            xmpp_stanza_release(iq);
-        } else {
-            log_debug("Capabilities already cached, for %s", caps_key);
-        }
-    } else {
+    if (!node) {
         log_debug("No node string, not sending discovery IQ.");
+        return;
+    }
+
+    log_debug("Node string: %s.", node);
+    if (caps_cache_contains(caps_key)) {
+        log_debug("Capabilities already cached, for %s", caps_key);
+        return;
     }
+
+    log_debug("Capabilities not cached for '%s', sending discovery IQ.", from);
+    xmpp_ctx_t *ctx = connection_get_ctx();
+    xmpp_stanza_t *iq = stanza_create_disco_info_iq(ctx, id, from, node);
+
+    iq_send_stanza(iq);
+
+    xmpp_stanza_release(iq);
 }
 
 static void
-_muc_user_handler(xmpp_stanza_t *const stanza)
+_muc_user_self_handler(xmpp_stanza_t *stanza)
 {
-    inp_nonblocking(TRUE);
+    const char *from = xmpp_stanza_get_from(stanza);
+    Jid *from_jid = jid_create(from);
+
+    log_debug("Room self presence received from %s", from_jid->fulljid);
+
+    char *room = from_jid->barejid;
 
     const char *type = xmpp_stanza_get_type(stanza);
-    const char *from = xmpp_stanza_get_from(stanza);
+    if (g_strcmp0(type, STANZA_TYPE_UNAVAILABLE) == 0) {
 
-    // handler still fires if error
-    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
-        return;
+        // handle nickname change
+        const char *new_nick = stanza_get_new_nick(stanza);
+        if (new_nick) {
+            muc_nick_change_start(room, new_nick);
+
+        // handle left room
+        } else {
+            GSList *status_codes = stanza_get_status_codes_by_ns(stanza, STANZA_NS_MUC_USER);
+
+            // room destroyed
+            if (stanza_room_destroyed(stanza)) {
+                const char *new_jid = stanza_get_muc_destroy_alternative_room(stanza);
+                char *password = stanza_get_muc_destroy_alternative_password(stanza);
+                char *reason = stanza_get_muc_destroy_reason(stanza);
+                sv_ev_room_destroyed(room, new_jid, password, reason);
+                free(password);
+                free(reason);
+
+            // kicked from room
+            } else if (g_slist_find_custom(status_codes, "307", (GCompareFunc)g_strcmp0)) {
+                const char *actor = stanza_get_actor(stanza);
+                char *reason = stanza_get_reason(stanza);
+                sv_ev_room_kicked(room, actor, reason);
+                free(reason);
+
+            // banned from room
+            } else if (g_slist_find_custom(status_codes, "301", (GCompareFunc)g_strcmp0)) {
+                const char *actor = stanza_get_actor(stanza);
+                char *reason = stanza_get_reason(stanza);
+                sv_ev_room_banned(room, actor, reason);
+                free(reason);
+
+            // normal exit
+            } else {
+                sv_ev_leave_room(room);
+            }
+
+            g_slist_free_full(status_codes, free);
+        }
+    } else {
+        gboolean config_required = stanza_muc_requires_config(stanza);
+        const char *actor = stanza_get_actor(stanza);
+        char *reason = stanza_get_reason(stanza);
+        char *nick = from_jid->resourcepart;
+        char *show_str = stanza_get_show(stanza, "online");
+        char *status_str = stanza_get_status(stanza, NULL);
+        const char *jid = NULL;
+        const char *role = NULL;
+        const char *affiliation = NULL;
+        xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
+        if (x) {
+            xmpp_stanza_t *item = xmpp_stanza_get_child_by_name(x, STANZA_NAME_ITEM);
+            if (item) {
+                jid = xmpp_stanza_get_attribute(item, "jid");
+                role = xmpp_stanza_get_attribute(item, "role");
+                affiliation = xmpp_stanza_get_attribute(item, "affiliation");
+            }
+        }
+        sv_ev_muc_self_online(room, nick, config_required, role, affiliation, actor, reason, jid, show_str, status_str);
+        free(show_str);
+        free(status_str);
+        free(reason);
     }
+}
 
-    // invalid from attribute
+static void
+_muc_user_occupant_handler(xmpp_stanza_t *stanza)
+{
+    const char *from = xmpp_stanza_get_from(stanza);
     Jid *from_jid = jid_create(from);
-    if (from_jid == NULL || from_jid->resourcepart == NULL) {
-        jid_destroy(from_jid);
-        return;
-    }
+
+    log_debug("Room presence received from %s", from_jid->fulljid);
 
     char *room = from_jid->barejid;
     char *nick = from_jid->resourcepart;
-
-    char *show_str = stanza_get_show(stanza, "online");
     char *status_str = stanza_get_status(stanza, NULL);
 
-    const char *jid = NULL;
-    const char *role = NULL;
-    const char *affiliation = NULL;
-
-    xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
-    if (x) {
-        xmpp_stanza_t *item = xmpp_stanza_get_child_by_name(x, STANZA_NAME_ITEM);
-        if (item) {
-            jid = xmpp_stanza_get_attribute(item, "jid");
-            role = xmpp_stanza_get_attribute(item, "role");
-            affiliation = xmpp_stanza_get_attribute(item, "affiliation");
-        }
-    }
-
-    // handle self presence
-    if (stanza_is_muc_self_presence(stanza, connection_get_fulljid())) {
-        log_debug("Room self presence received from %s", from_jid->fulljid);
+    const char *type = xmpp_stanza_get_type(stanza);
+    if (g_strcmp0(type, STANZA_TYPE_UNAVAILABLE) == 0) {
 
-        // self unavailable
-        if (g_strcmp0(type, STANZA_TYPE_UNAVAILABLE) == 0) {
+        // handle nickname change
+        const char *new_nick = stanza_get_new_nick(stanza);
+        if (new_nick) {
+            muc_occupant_nick_change_start(room, new_nick, nick);
 
-            // handle nickname change
-            const char *new_nick = stanza_get_new_nick(stanza);
-            if (new_nick) {
-                muc_nick_change_start(room, new_nick);
+        // handle left room
+        } else {
+            GSList *status_codes = stanza_get_status_codes_by_ns(stanza, STANZA_NS_MUC_USER);
+
+            // kicked from room
+            if (g_slist_find_custom(status_codes, "307", (GCompareFunc)g_strcmp0)) {
+                const char *actor = stanza_get_actor(stanza);
+                char *reason = stanza_get_reason(stanza);
+                sv_ev_room_occupent_kicked(room, nick, actor, reason);
+                free(reason);
+
+            // banned from room
+            } else if (g_slist_find_custom(status_codes, "301", (GCompareFunc)g_strcmp0)) {
+                const char *actor = stanza_get_actor(stanza);
+                char *reason = stanza_get_reason(stanza);
+                sv_ev_room_occupent_banned(room, nick, actor, reason);
+                free(reason);
+
+            // normal exit
             } else {
-                GSList *status_codes = stanza_get_status_codes_by_ns(stanza, STANZA_NS_MUC_USER);
-
-                // room destroyed
-                if (stanza_room_destroyed(stanza)) {
-                    const char *new_jid = stanza_get_muc_destroy_alternative_room(stanza);
-                    char *password = stanza_get_muc_destroy_alternative_password(stanza);
-                    char *reason = stanza_get_muc_destroy_reason(stanza);
-                    sv_ev_room_destroyed(room, new_jid, password, reason);
-                    free(password);
-                    free(reason);
-
-                // kicked from room
-                } else if (g_slist_find_custom(status_codes, "307", (GCompareFunc)g_strcmp0)) {
-                    const char *actor = stanza_get_actor(stanza);
-                    char *reason = stanza_get_reason(stanza);
-                    sv_ev_room_kicked(room, actor, reason);
-                    free(reason);
-
-                // banned from room
-                } else if (g_slist_find_custom(status_codes, "301", (GCompareFunc)g_strcmp0)) {
-                    const char *actor = stanza_get_actor(stanza);
-                    char *reason = stanza_get_reason(stanza);
-                    sv_ev_room_banned(room, actor, reason);
-                    free(reason);
-
-                // normal exit
-                } else {
-                    sv_ev_leave_room(room);
-                }
-
-                g_slist_free_full(status_codes, free);
+                sv_ev_room_occupant_offline(room, nick, "offline", status_str);
             }
 
-        // self online
-        } else {
-            gboolean config_required = stanza_muc_requires_config(stanza);
-            const char *actor = stanza_get_actor(stanza);
-            char *reason = stanza_get_reason(stanza);
-            sv_ev_muc_self_online(room, nick, config_required, role, affiliation, actor, reason, jid, show_str, status_str);
-            free(reason);
+            g_slist_free_full(status_codes, free);
         }
 
-    // handle presence from room members
+    // room occupant online
     } else {
-        log_debug("Room presence received from %s", from_jid->fulljid);
-
-        if (g_strcmp0(type, STANZA_TYPE_UNAVAILABLE) == 0) {
+        // send disco info for capabilities, if not cached
+        XMPPCaps *caps = stanza_parse_caps(stanza);
+        if (caps) {
+            log_info("Presence contains capabilities.");
+            _handle_caps(from, caps);
+        }
+        stanza_free_caps(caps);
+
+        const char *actor = stanza_get_actor(stanza);
+        char *show_str = stanza_get_show(stanza, "online");
+        char *reason = stanza_get_reason(stanza);
+        const char *jid = NULL;
+        const char *role = NULL;
+        const char *affiliation = NULL;
+        xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
+        if (x) {
+            xmpp_stanza_t *item = xmpp_stanza_get_child_by_name(x, STANZA_NAME_ITEM);
+            if (item) {
+                jid = xmpp_stanza_get_attribute(item, "jid");
+                role = xmpp_stanza_get_attribute(item, "role");
+                affiliation = xmpp_stanza_get_attribute(item, "affiliation");
+            }
+        }
+        sv_ev_muc_occupant_online(room, nick, jid, role, affiliation, actor, reason, show_str, status_str);
+        free(show_str);
+        free(reason);
+    }
 
-            // handle nickname change
-            const char *new_nick = stanza_get_new_nick(stanza);
-            if (new_nick) {
-                muc_occupant_nick_change_start(room, new_nick, nick);
+    free(status_str);
+}
 
-            // handle left room
-            } else {
-                GSList *status_codes = stanza_get_status_codes_by_ns(stanza, STANZA_NS_MUC_USER);
-
-                // kicked from room
-                if (g_slist_find_custom(status_codes, "307", (GCompareFunc)g_strcmp0)) {
-                    const char *actor = stanza_get_actor(stanza);
-                    char *reason = stanza_get_reason(stanza);
-                    sv_ev_room_occupent_kicked(room, nick, actor, reason);
-                    free(reason);
-
-                // banned from room
-                } else if (g_slist_find_custom(status_codes, "301", (GCompareFunc)g_strcmp0)) {
-                    const char *actor = stanza_get_actor(stanza);
-                    char *reason = stanza_get_reason(stanza);
-                    sv_ev_room_occupent_banned(room, nick, actor, reason);
-                    free(reason);
-
-                // normal exit
-                } else {
-                    sv_ev_room_occupant_offline(room, nick, "offline", status_str);
-                }
-
-                g_slist_free_full(status_codes, free);
-            }
+static void
+_muc_user_handler(xmpp_stanza_t *const stanza)
+{
+    inp_nonblocking(TRUE);
 
-        // room occupant online
-        } else {
-            // send disco info for capabilities, if not cached
-            XMPPCaps *caps = stanza_parse_caps(stanza);
-            if (caps) {
-                log_info("Presence contains capabilities.");
-                _handle_caps(from, caps);
-            }
-            stanza_free_caps(caps);
+    const char *type = xmpp_stanza_get_type(stanza);
+    // handler still fires if error
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        return;
+    }
 
-            const char *actor = stanza_get_actor(stanza);
-            char *reason = stanza_get_reason(stanza);
-            sv_ev_muc_occupant_online(room, nick, jid, role, affiliation, actor, reason, show_str, status_str);
-            free(reason);
-        }
+    const char *from = xmpp_stanza_get_from(stanza);
+    if (!from) {
+        log_warning("MUC User stanza received with no from attribute");
+        return;
     }
 
-    free(show_str);
-    free(status_str);
+    Jid *from_jid = jid_create(from);
+    if (from_jid == NULL || from_jid->resourcepart == NULL) {
+        log_warning("MUC User stanza received with invalid from attribute: %s", from);
+        jid_destroy(from_jid);
+        return;
+    }
     jid_destroy(from_jid);
+
+    if (stanza_is_muc_self_presence(stanza, connection_get_fulljid())) {
+        _muc_user_self_handler(stanza);
+    } else {
+        _muc_user_occupant_handler(stanza);
+    }
 }
 
 static void