about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorJames Booth <boothj5@gmail.com>2016-05-01 19:39:39 +0100
committerJames Booth <boothj5@gmail.com>2016-05-01 19:39:39 +0100
commitb8c94376aad2d5edf4390e47b8dc89db8ab1a517 (patch)
treeed498c6b0fed9428e5d0f0cdd28cdd12f82cb447
parentc31913f8d582c57dc2d148739e190ddff743cb94 (diff)
downloadprofani-tty-b8c94376aad2d5edf4390e47b8dc89db8ab1a517.tar.gz
Add /blocked command
-rw-r--r--Makefile.am1
-rw-r--r--src/command/command.c51
-rw-r--r--src/command/commands.c57
-rw-r--r--src/command/commands.h1
-rw-r--r--src/xmpp/blocking.c323
-rw-r--r--src/xmpp/blocking.h41
-rw-r--r--src/xmpp/connection.c17
-rw-r--r--src/xmpp/iq.c6
-rw-r--r--src/xmpp/stanza.c20
-rw-r--r--src/xmpp/stanza.h6
-rw-r--r--src/xmpp/xmpp.h9
-rw-r--r--tests/functionaltests/test_muc.c32
-rw-r--r--tests/functionaltests/test_ping.c10
-rw-r--r--tests/functionaltests/test_presence.c24
-rw-r--r--tests/unittests/xmpp/stub_xmpp.c28
15 files changed, 592 insertions, 34 deletions
diff --git a/Makefile.am b/Makefile.am
index 95f6df92..7711cda9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -12,6 +12,7 @@ core_sources = \
 	src/xmpp/capabilities.h src/xmpp/connection.h \
 	src/xmpp/roster.c src/xmpp/roster.h \
 	src/xmpp/bookmark.c src/xmpp/bookmark.h \
+	src/xmpp/blocking.c src/xmpp/blocking.h \
 	src/xmpp/form.c src/xmpp/form.h \
 	src/event/server_events.c src/event/server_events.h \
 	src/event/client_events.c src/event/client_events.h \
diff --git a/src/command/command.c b/src/command/command.c
index 5895ad9d..fd357668 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -125,6 +125,7 @@ static char* _win_autocomplete(ProfWin *window, const char *const input);
 static char* _close_autocomplete(ProfWin *window, const char *const input);
 static char* _plugins_autocomplete(ProfWin *window, const char *const input);
 static char* _sendfile_autocomplete(ProfWin *window, const char *const input);
+static char* _blocked_autocomplete(ProfWin *window, const char *const input);
 
 GHashTable *commands = NULL;
 
@@ -423,6 +424,26 @@ static struct cmd_t command_defs[] =
             "/roster size 15")
     },
 
+    { "/blocked",
+        parse_args, 0, 2, NULL,
+        CMD_NOSUBFUNCS
+        CMD_MAINFUNC(cmd_blocked)
+        CMD_TAGS(
+            CMD_TAG_ROSTER,
+            CMD_TAG_CHAT)
+        CMD_SYN(
+            "/blocked",
+            "/blocked add [<jid>]",
+            "/blocked remove <jid>")
+        CMD_DESC(
+            "Manage blocked users, calling with no arguments shows the current list of blocked users.")
+        CMD_ARGS(
+            { "add [<jid>]",    "Block the specified Jabber ID, if called in a chat window, the current recipient will be used." },
+            { "remove <jid>",   "Remove the specified Jabber ID from the blocked list." })
+        CMD_EXAMPLES(
+            "/blocked add spammer@spam.org")
+    },
+
     { "/group",
         parse_args_with_freetext, 0, 3, NULL,
         CMD_NOSUBFUNCS
@@ -2279,6 +2300,7 @@ static Autocomplete autoping_ac;
 static Autocomplete plugins_ac;
 static Autocomplete plugins_load_ac;
 static Autocomplete sendfile_ac;
+static Autocomplete blocked_ac;
 
 /*
  * Initialise command autocompleter and history
@@ -2835,6 +2857,10 @@ cmd_init(void)
     autocomplete_add(plugins_ac, "load");
 
     sendfile_ac = autocomplete_new();
+
+    blocked_ac = autocomplete_new();
+    autocomplete_add(blocked_ac, "add");
+    autocomplete_add(blocked_ac, "remove");
 }
 
 void
@@ -2925,6 +2951,7 @@ cmd_uninit(void)
     autocomplete_free(plugins_ac);
     autocomplete_free(plugins_load_ac);
     autocomplete_free(sendfile_ac);
+    autocomplete_free(blocked_ac);
 }
 
 gboolean
@@ -3154,6 +3181,7 @@ cmd_reset_autocomplete(ProfWin *window)
     autocomplete_reset(console_msg_ac);
     autocomplete_reset(autoping_ac);
     autocomplete_reset(plugins_ac);
+    autocomplete_reset(blocked_ac);
     autocomplete_reset(script_ac);
     if (script_show_ac) {
         autocomplete_free(script_show_ac);
@@ -3176,6 +3204,7 @@ cmd_reset_autocomplete(ProfWin *window)
     }
 
     bookmark_autocomplete_reset();
+    blocked_ac_reset();
     prefs_reset_room_trigger_ac();
     win_reset_search_attempts();
     win_close_reset_search_attempts();
@@ -3445,8 +3474,9 @@ _cmd_complete_parameters(ProfWin *window, const char *const input)
     g_hash_table_insert(ac_funcs, "/console",       _console_autocomplete);
     g_hash_table_insert(ac_funcs, "/win",           _win_autocomplete);
     g_hash_table_insert(ac_funcs, "/close",         _close_autocomplete);
-    g_hash_table_insert(ac_funcs, "/plugins",         _plugins_autocomplete);
+    g_hash_table_insert(ac_funcs, "/plugins",       _plugins_autocomplete);
     g_hash_table_insert(ac_funcs, "/sendfile",      _sendfile_autocomplete);
+    g_hash_table_insert(ac_funcs, "/blocked",       _blocked_autocomplete);
 
     int len = strlen(input);
     char parsed[len+1];
@@ -3717,6 +3747,25 @@ _group_autocomplete(ProfWin *window, const char *const input)
 }
 
 static char*
+_blocked_autocomplete(ProfWin *window, const char *const input)
+{
+    char *result = NULL;
+
+    result = autocomplete_param_with_func(input, "/blocked remove", blocked_ac_find);
+    if (result) {
+        return result;
+    }
+
+    result = autocomplete_param_with_ac(input, "/blocked", blocked_ac, FALSE);
+    if (result) {
+        return result;
+    }
+
+    return NULL;
+}
+
+
+static char*
 _bookmark_autocomplete(ProfWin *window, const char *const input)
 {
     char *found = NULL;
diff --git a/src/command/commands.c b/src/command/commands.c
index 58777a2d..a717054b 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -2985,6 +2985,63 @@ cmd_roster(ProfWin *window, const char *const command, gchar **args)
 }
 
 gboolean
+cmd_blocked(ProfWin *window, const char *const command, gchar **args)
+{
+    if (jabber_get_connection_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    if (!jabber_service_supports(XMPP_FEATURE_BLOCKING)) {
+        cons_show("Blocking not supported by server.");
+        return TRUE;
+    }
+
+    if (g_strcmp0(args[0], "add") == 0) {
+        if (args[1] == NULL) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+
+        gboolean res = blocked_add(args[1]);
+        if (!res) {
+            cons_show("User %s already blocked.", args[1]);
+        }
+
+        return TRUE;
+    }
+
+    if (g_strcmp0(args[0], "remove") == 0) {
+        if (args[1] == NULL) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+
+        gboolean res = blocked_remove(args[1]);
+        if (!res) {
+            cons_show("User %s is not currently blocked.", args[1]);
+        }
+
+        return TRUE;
+    }
+
+    GList *blocked = blocked_list();
+    GList *curr = blocked;
+    if (curr) {
+        cons_show("Blocked users:");
+        while (curr) {
+            cons_show("  %s", curr->data);
+            curr = g_list_next(curr);
+        }
+    } else {
+        cons_show("No blocked users.");
+    }
+
+    return TRUE;
+}
+
+
+gboolean
 cmd_resource(ProfWin *window, const char *const command, gchar **args)
 {
     char *cmd = args[0];
diff --git a/src/command/commands.h b/src/command/commands.h
index c5463d88..24828989 100644
--- a/src/command/commands.h
+++ b/src/command/commands.h
@@ -199,6 +199,7 @@ gboolean cmd_export(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_charset(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_console(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_plugins(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_blocked(ProfWin *window, const char *const command, gchar **args);
 
 gboolean cmd_form_field(ProfWin *window, char *tag, gchar **args);
 
diff --git a/src/xmpp/blocking.c b/src/xmpp/blocking.c
new file mode 100644
index 00000000..06bbadf6
--- /dev/null
+++ b/src/xmpp/blocking.c
@@ -0,0 +1,323 @@
+/*
+ * blocking.c
+ *
+ * Copyright (C) 2012 - 2016 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#include <stdlib.h>
+
+#ifdef HAVE_LIBMESODE
+#include <mesode.h>
+#endif
+#ifdef HAVE_LIBSTROPHE
+#include <strophe.h>
+#endif
+
+#include <glib.h>
+
+#include "log.h"
+#include "common.h"
+#include "ui/ui.h"
+#include "xmpp/connection.h"
+#include "xmpp/stanza.h"
+
+static int _blocklist_result_handler(xmpp_stanza_t *const stanza, void *const userdata);
+static int _block_add_result_handler(xmpp_stanza_t *const stanza, void *const userdata);
+static int _block_remove_result_handler(xmpp_stanza_t *const stanza, void *const userdata);
+
+static GList *blocked;
+static Autocomplete blocked_ac;
+
+void
+blocking_request(void)
+{
+    char *id = create_unique_id("blocked_list_request");
+    xmpp_ctx_t *ctx = connection_get_ctx();
+    xmpp_stanza_t *iq;
+
+    if (blocked) {
+        g_list_free_full(blocked, free);
+        blocked = NULL;
+    }
+
+    if (blocked_ac) {
+        autocomplete_free(blocked_ac);
+    }
+    blocked_ac = autocomplete_new();
+
+    id_handler_add(id, _blocklist_result_handler, id);
+
+    iq = stanza_create_blocked_list_request(ctx);
+    xmpp_stanza_set_id(iq, id);
+    send_iq_stanza(iq);
+    xmpp_stanza_release(iq);
+    free(id);
+}
+
+GList*
+blocked_list(void)
+{
+    return blocked;
+}
+
+char*
+blocked_ac_find(const char *const search_str)
+{
+    return autocomplete_complete(blocked_ac, search_str, TRUE);
+}
+
+void
+blocked_ac_reset(void)
+{
+    if (blocked_ac) {
+        autocomplete_reset(blocked_ac);
+    }
+}
+
+gboolean
+blocked_add(char *jid)
+{
+    GList *found = g_list_find_custom(blocked, jid, (GCompareFunc)g_strcmp0);
+    if (found) {
+        return FALSE;
+    }
+
+    xmpp_ctx_t *ctx = connection_get_ctx();
+
+    xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
+    xmpp_stanza_set_type(iq, STANZA_TYPE_SET);
+    char *id = create_unique_id("block");
+    xmpp_stanza_set_id(iq, id);
+
+    xmpp_stanza_t *block = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(block, STANZA_NAME_BLOCK);
+    xmpp_stanza_set_ns(block, STANZA_NS_BLOCKING);
+
+    xmpp_stanza_t *item = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(item, STANZA_NAME_ITEM);
+    xmpp_stanza_set_attribute(item, STANZA_ATTR_JID, jid);
+
+    xmpp_stanza_add_child(block, item);
+    xmpp_stanza_release(item);
+
+    xmpp_stanza_add_child(iq, block);
+    xmpp_stanza_release(block);
+
+    id_handler_add(id, _block_add_result_handler, strdup(jid));
+
+    send_iq_stanza(iq);
+    xmpp_stanza_release(iq);
+    free(id);
+
+    return TRUE;
+}
+
+gboolean
+blocked_remove(char *jid)
+{
+    GList *found = g_list_find_custom(blocked, jid, (GCompareFunc)g_strcmp0);
+    if (!found) {
+        return FALSE;
+    }
+
+    xmpp_ctx_t *ctx = connection_get_ctx();
+
+    xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
+    xmpp_stanza_set_type(iq, STANZA_TYPE_SET);
+    char *id = create_unique_id("unblock");
+    xmpp_stanza_set_id(iq, id);
+
+    xmpp_stanza_t *block = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(block, STANZA_NAME_UNBLOCK);
+    xmpp_stanza_set_ns(block, STANZA_NS_BLOCKING);
+
+    xmpp_stanza_t *item = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(item, STANZA_NAME_ITEM);
+    xmpp_stanza_set_attribute(item, STANZA_ATTR_JID, jid);
+
+    xmpp_stanza_add_child(block, item);
+    xmpp_stanza_release(item);
+
+    xmpp_stanza_add_child(iq, block);
+    xmpp_stanza_release(block);
+
+    id_handler_add(id, _block_remove_result_handler, strdup(jid));
+
+    send_iq_stanza(iq);
+    xmpp_stanza_release(iq);
+    free(id);
+
+    return TRUE;
+}
+
+int
+blocked_set_handler(xmpp_stanza_t *stanza)
+{
+    xmpp_stanza_t *block = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BLOCK);
+    if (block) {
+        xmpp_stanza_t *child = xmpp_stanza_get_children(block);
+        while (child) {
+            if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_ITEM) == 0) {
+                const char *jid = xmpp_stanza_get_attribute(child, STANZA_ATTR_JID);
+                if (jid) {
+                    blocked = g_list_append(blocked, strdup(jid));
+                    autocomplete_add(blocked_ac, jid);
+                }
+
+            }
+
+            child = xmpp_stanza_get_next(child);
+        }
+    }
+
+    xmpp_stanza_t *unblock = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_UNBLOCK);
+    if (unblock) {
+        xmpp_stanza_t *child = xmpp_stanza_get_children(unblock);
+        if (!child) {
+            g_list_free_full(blocked, free);
+            blocked = NULL;
+            autocomplete_clear(blocked_ac);
+        } else {
+            while (child) {
+                if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_ITEM) == 0) {
+                    const char *jid = xmpp_stanza_get_attribute(child, STANZA_ATTR_JID);
+                    if (jid) {
+                        GList *found = g_list_find_custom(blocked, jid, (GCompareFunc)g_strcmp0);
+                        if (found) {
+                            blocked = g_list_remove_link(blocked, found);
+                            g_list_free_full(found, free);
+                            autocomplete_remove(blocked_ac, jid);
+                        }
+                    }
+
+                }
+
+                child = xmpp_stanza_get_next(child);
+            }
+        }
+    }
+
+    return 1;
+}
+
+static int
+_block_add_result_handler(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    char *jid = (char*)userdata;
+
+    const char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE);
+    if (type == NULL) {
+        log_info("Block response received for %s with no type attribute.", jid);
+        free(jid);
+        return 0;
+    }
+
+    if (g_strcmp0(type, "result") != 0) {
+        log_info("Block response received for %s with unrecognised type attribute.", jid);
+        free(jid);
+        return 0;
+    }
+
+    cons_show("User %s successfully blocked.", jid);
+    free(jid);
+
+    return 0;
+}
+
+static int
+_block_remove_result_handler(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    char *jid = (char*)userdata;
+
+    const char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE);
+    if (type == NULL) {
+        log_info("Unblock response received for %s with no type attribute.", jid);
+        free(jid);
+        return 0;
+    }
+
+    if (g_strcmp0(type, "result") != 0) {
+        log_info("Unblock response received for %s with unrecognised type attribute.", jid);
+        free(jid);
+        return 0;
+    }
+
+    cons_show("User %s successfully unblocked.", jid);
+    free(jid);
+
+    return 0;
+}
+
+static int
+_blocklist_result_handler(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    log_info("Blocked list result handler fired.");
+
+    const char *type = xmpp_stanza_get_type(stanza);
+    if (g_strcmp0(type, "result") != 0) {
+        log_info("Received blocklist without result type");
+        return 0;
+    }
+
+    xmpp_stanza_t *blocklist = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BLOCKLIST);
+    if (!blocklist) {
+        log_info("Received blocklist without blocklist element");
+        return 0;
+    }
+
+    if (blocked) {
+        g_list_free_full(blocked, free);
+        blocked = NULL;
+    }
+
+    xmpp_stanza_t *items = xmpp_stanza_get_children(blocklist);
+    if (!items) {
+        log_info("No blocked users.");
+        return 0;
+    }
+
+    xmpp_stanza_t *curr = items;
+    while (curr) {
+        const char *name = xmpp_stanza_get_name(curr);
+        if (g_strcmp0(name, "item") == 0) {
+            const char *jid = xmpp_stanza_get_attribute(curr, STANZA_ATTR_JID);
+            if (jid) {
+                blocked = g_list_append(blocked, strdup(jid));
+                autocomplete_add(blocked_ac, jid);
+            }
+        }
+        curr = xmpp_stanza_get_next(curr);
+    }
+
+    return 0;
+}
diff --git a/src/xmpp/blocking.h b/src/xmpp/blocking.h
new file mode 100644
index 00000000..f541cef1
--- /dev/null
+++ b/src/xmpp/blocking.h
@@ -0,0 +1,41 @@
+/*
+ * blocking.h
+ *
+ * Copyright (C) 2012 - 2016 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#ifndef XMPP_BLOCKING_H
+#define XMPP_BLOCKING_H
+
+void blocking_request(void);
+int blocked_set_handler(xmpp_stanza_t *stanza);
+
+#endif
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index 710e71b2..7db3b2ed 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -55,6 +55,7 @@
 #include "profanity.h"
 #include "event/server_events.h"
 #include "xmpp/bookmark.h"
+#include "xmpp/blocking.h"
 #include "xmpp/capabilities.h"
 #include "xmpp/connection.h"
 #include "xmpp/iq.h"
@@ -343,6 +344,21 @@ jabber_get_disco_items(void)
     return (disco_items);
 }
 
+gboolean
+jabber_service_supports(const char *const feature)
+{
+    DiscoInfo *disco_info;
+    while (disco_items) {
+        disco_info = disco_items->data;
+        if (g_hash_table_lookup_extended(disco_info->features, feature, NULL, NULL)) {
+            return TRUE;
+        }
+        disco_items = g_slist_next(disco_items);
+    }
+
+    return FALSE;
+}
+
 void
 jabber_set_disco_items(GSList *_disco_items)
 {
@@ -678,6 +694,7 @@ _connection_handler(xmpp_conn_t *const conn, const xmpp_conn_event_t status, con
 
         roster_request();
         bookmark_request();
+        blocking_request();
 
         // items discovery
         DiscoInfo *info = malloc(sizeof(struct disco_info_t));
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index c874a721..cba3d4d3 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -58,6 +58,7 @@
 #include "config/preferences.h"
 #include "event/server_events.h"
 #include "xmpp/capabilities.h"
+#include "xmpp/blocking.h"
 #include "xmpp/connection.h"
 #include "xmpp/stanza.h"
 #include "xmpp/form.h"
@@ -170,6 +171,11 @@ _iq_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const us
         roster_result_handler(stanza);
     }
 
+    xmpp_stanza_t *blocking = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_BLOCKING);
+    if (blocking && (g_strcmp0(type, STANZA_TYPE_SET) == 0)) {
+        blocked_set_handler(stanza);
+    }
+
     const char *id = xmpp_stanza_get_id(stanza);
     if (id) {
         ProfIdHandler *handler = g_hash_table_lookup(id_handlers, id);
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index dc2fb68a..13450e20 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -117,6 +117,26 @@ stanza_create_bookmarks_storage_request(xmpp_ctx_t *ctx)
     return iq;
 }
 
+xmpp_stanza_t*
+stanza_create_blocked_list_request(xmpp_ctx_t *ctx)
+{
+    xmpp_stanza_t *iq, *blocklist;
+
+    iq = xmpp_stanza_new(ctx);
+    blocklist = xmpp_stanza_new(ctx);
+
+    xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
+    xmpp_stanza_set_type(iq, STANZA_TYPE_GET);
+
+    xmpp_stanza_set_name(blocklist, STANZA_NAME_BLOCKLIST);
+    xmpp_stanza_set_ns(blocklist, STANZA_NS_BLOCKING);
+
+    xmpp_stanza_add_child(iq, blocklist);
+    xmpp_stanza_release(blocklist);
+
+    return iq;
+}
+
 #if 0
 xmpp_stanza_t*
 stanza_create_bookmarks_pubsub_add(xmpp_ctx_t *ctx, const char *const jid,
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 977e34c5..d557b5c7 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -54,6 +54,9 @@
 
 #define STANZA_NAME_MESSAGE "message"
 #define STANZA_NAME_BODY "body"
+#define STANZA_NAME_BLOCK "block"
+#define STANZA_NAME_UNBLOCK "unblock"
+#define STANZA_NAME_BLOCKLIST "blocklist"
 #define STANZA_NAME_PRESENCE "presence"
 #define STANZA_NAME_PRIORITY "priority"
 #define STANZA_NAME_X "x"
@@ -181,6 +184,7 @@
 #define STANZA_NS_ENCRYPTED "jabber:x:encrypted"
 #define STANZA_NS_HTTP_UPLOAD "urn:xmpp:http:upload"
 #define STANZA_NS_X_OOB "jabber:x:oob"
+#define STANZA_NS_BLOCKING "urn:xmpp:blocking"
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"
 
@@ -205,6 +209,8 @@ typedef enum {
 
 xmpp_stanza_t* stanza_create_bookmarks_storage_request(xmpp_ctx_t *ctx);
 
+xmpp_stanza_t* stanza_create_blocked_list_request(xmpp_ctx_t *ctx);
+
 xmpp_stanza_t* stanza_create_http_upload_request(xmpp_ctx_t *ctx, const char *const id, const char *const jid, HTTPUpload *upload);
 
 xmpp_stanza_t* stanza_enable_carbons(xmpp_ctx_t *ctx);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index d6e6031b..bf7bbbac 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -54,6 +54,8 @@
 #define JABBER_PRIORITY_MIN -128
 #define JABBER_PRIORITY_MAX 127
 
+#define XMPP_FEATURE_BLOCKING "urn:xmpp:blocking"
+
 typedef enum {
     JABBER_UNDEFINED,
     JABBER_STARTED,
@@ -126,6 +128,7 @@ TLSCertificate* jabber_get_tls_peer_cert(void);
 #endif
 gboolean jabber_conn_is_secured(void);
 gboolean jabber_send_stanza(const char *const stanza);
+gboolean jabber_service_supports(const char *const feature);
 
 // message functions
 char* message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url);
@@ -206,6 +209,12 @@ void roster_send_remove_from_group(const char *const group, PContact contact);
 void roster_send_add_new(const char *const barejid, const char *const name);
 void roster_send_remove(const char *const barejid);
 
+GList* blocked_list(void);
+gboolean blocked_add(char *jid);
+gboolean blocked_remove(char *jid);
+char* blocked_ac_find(const char *const search_str);
+void blocked_ac_reset(void);
+
 void form_destroy(DataForm *form);
 char* form_get_form_type_field(DataForm *form);
 void form_set_value(DataForm *form, const char *const tag, char *value);
diff --git a/tests/functionaltests/test_muc.c b/tests/functionaltests/test_muc.c
index 4ee0e698..5a117045 100644
--- a/tests/functionaltests/test_muc.c
+++ b/tests/functionaltests/test_muc.c
@@ -95,8 +95,8 @@ shows_role_and_affiliation_on_join(void **state)
 {
     prof_connect();
 
-    stbbr_for_id("prof_join_3",
-        "<presence id='prof_join_3' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
             "<x xmlns='http://jabber.org/protocol/muc#user'>"
                 "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
@@ -115,8 +115,8 @@ shows_subject_on_join(void **state)
 {
     prof_connect();
 
-    stbbr_for_id("prof_join_3",
-        "<presence id='prof_join_3' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
             "<x xmlns='http://jabber.org/protocol/muc#user'>"
                 "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
@@ -143,8 +143,8 @@ shows_history_message(void **state)
 {
     prof_connect();
 
-    stbbr_for_id("prof_join_3",
-        "<presence id='prof_join_3' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
             "<x xmlns='http://jabber.org/protocol/muc#user'>"
                 "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
@@ -172,8 +172,8 @@ shows_occupant_join(void **state)
 {
     prof_connect();
 
-    stbbr_for_id("prof_join_3",
-        "<presence id='prof_join_3' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
             "<x xmlns='http://jabber.org/protocol/muc#user'>"
                 "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
@@ -201,8 +201,8 @@ shows_message(void **state)
 {
     prof_connect();
 
-    stbbr_for_id("prof_join_3",
-        "<presence id='prof_join_3' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
             "<x xmlns='http://jabber.org/protocol/muc#user'>"
                 "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
@@ -228,8 +228,8 @@ shows_all_messages_in_console_when_window_not_focussed(void **state)
 {
     prof_connect();
 
-    stbbr_for_id("prof_join_3",
-        "<presence id='prof_join_3' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
             "<x xmlns='http://jabber.org/protocol/muc#user'>"
                 "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
@@ -269,8 +269,8 @@ shows_first_message_in_console_when_window_not_focussed(void **state)
     prof_input("/console muc first");
     assert_true(prof_output_exact("Console MUC messages set: first"));
 
-    stbbr_for_id("prof_join_3",
-        "<presence id='prof_join_3' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
             "<x xmlns='http://jabber.org/protocol/muc#user'>"
                 "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
@@ -315,8 +315,8 @@ shows_no_message_in_console_when_window_not_focussed(void **state)
     prof_input("/console muc none");
     assert_true(prof_output_exact("Console MUC messages set: none"));
 
-    stbbr_for_id("prof_join_3",
-        "<presence id='prof_join_3' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
             "<x xmlns='http://jabber.org/protocol/muc#user'>"
                 "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
diff --git a/tests/functionaltests/test_ping.c b/tests/functionaltests/test_ping.c
index 8a065f15..ecbbfee1 100644
--- a/tests/functionaltests/test_ping.c
+++ b/tests/functionaltests/test_ping.c
@@ -14,18 +14,18 @@
 void
 ping_multiple(void **state)
 {
-    stbbr_for_id("prof_ping_3",
-        "<iq id='prof_ping_3' type='result' to='stabber@localhost/profanity'/>"
-    );
     stbbr_for_id("prof_ping_4",
         "<iq id='prof_ping_4' type='result' to='stabber@localhost/profanity'/>"
     );
+    stbbr_for_id("prof_ping_5",
+        "<iq id='prof_ping_5' type='result' to='stabber@localhost/profanity'/>"
+    );
 
     prof_connect();
 
     prof_input("/ping");
     assert_true(stbbr_received(
-        "<iq id='prof_ping_3' type='get'>"
+        "<iq id='prof_ping_4' type='get'>"
             "<ping xmlns='urn:xmpp:ping'/>"
         "</iq>"
     ));
@@ -33,7 +33,7 @@ ping_multiple(void **state)
 
     prof_input("/ping");
     assert_true(stbbr_received(
-        "<iq id='prof_ping_4' type='get'>"
+        "<iq id='prof_ping_5' type='get'>"
             "<ping xmlns='urn:xmpp:ping'/>"
         "</iq>"
     ));
diff --git a/tests/functionaltests/test_presence.c b/tests/functionaltests/test_presence.c
index 1a933134..97cf168c 100644
--- a/tests/functionaltests/test_presence.c
+++ b/tests/functionaltests/test_presence.c
@@ -35,7 +35,7 @@ presence_online_with_message(void **state)
     prof_input("/online \"Hi there\"");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<status>Hi there</status>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
         "</presence>"
@@ -52,7 +52,7 @@ presence_away(void **state)
     prof_input("/away");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<show>away</show>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
         "</presence>"
@@ -69,7 +69,7 @@ presence_away_with_message(void **state)
     prof_input("/away \"I'm not here for a bit\"");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<show>away</show>"
             "<status>I'm not here for a bit</status>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
@@ -87,7 +87,7 @@ presence_xa(void **state)
     prof_input("/xa");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<show>xa</show>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
         "</presence>"
@@ -104,7 +104,7 @@ presence_xa_with_message(void **state)
     prof_input("/xa \"Gone to the shops\"");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<show>xa</show>"
             "<status>Gone to the shops</status>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
@@ -122,7 +122,7 @@ presence_dnd(void **state)
     prof_input("/dnd");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<show>dnd</show>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
         "</presence>"
@@ -139,7 +139,7 @@ presence_dnd_with_message(void **state)
     prof_input("/dnd \"Working\"");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<show>dnd</show>"
             "<status>Working</status>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
@@ -157,7 +157,7 @@ presence_chat(void **state)
     prof_input("/chat");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<show>chat</show>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
         "</presence>"
@@ -174,7 +174,7 @@ presence_chat_with_message(void **state)
     prof_input("/chat \"Free to talk\"");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<show>chat</show>"
             "<status>Free to talk</status>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
@@ -192,7 +192,7 @@ presence_set_priority(void **state)
     prof_input("/priority 25");
 
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<priority>25</priority>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
         "</presence>"
@@ -208,7 +208,7 @@ presence_includes_priority(void **state)
 
     prof_input("/priority 25");
     assert_true(stbbr_received(
-        "<presence id='prof_presence_3'>"
+        "<presence id='prof_presence_4'>"
             "<priority>25</priority>"
             "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
         "</presence>"
@@ -217,7 +217,7 @@ presence_includes_priority(void **state)
 
     prof_input("/chat \"Free to talk\"");
     assert_true(stbbr_received(
-        "<presence id='prof_presence_4'>"
+        "<presence id='prof_presence_5'>"
             "<priority>25</priority>"
             "<show>chat</show>"
             "<status>Free to talk</status>"
diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c
index c27c51cd..fef17bf6 100644
--- a/tests/unittests/xmpp/stub_xmpp.c
+++ b/tests/unittests/xmpp/stub_xmpp.c
@@ -70,6 +70,12 @@ jabber_send_stanza(const char *const stanza)
     return TRUE;
 }
 
+gboolean
+jabber_service_supports(const char *const feature)
+{
+    return FALSE;
+}
+
 // message functions
 char* message_send_chat(const char * const barejid, const char * const msg, const char *const oob_url)
 {
@@ -249,3 +255,25 @@ void roster_send_remove(const char * const barejid)
 {
     check_expected(barejid);
 }
+
+GList* blocked_list(void)
+{
+    return NULL;
+}
+
+gboolean blocked_add(char *jid)
+{
+    return TRUE;
+}
+
+gboolean blocked_remove(char *jid)
+{
+    return TRUE;
+}
+
+char* blocked_ac_find(const char *const search_str)
+{
+    return NULL;
+}
+
+void blocked_ac_reset(void) {}