about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorMichael Vetter <jubalh@iodoru.org>2018-09-27 17:27:37 +0200
committerGitHub <noreply@github.com>2018-09-27 17:27:37 +0200
commit47f90d7a39002ce4ee683179e25797a78cf95905 (patch)
treed3ab27090ef03ed6c88a414293eb97e8e54a03f0 /src
parentbb87122af9f477f8f0b53240537aae20c105a88c (diff)
parent2e0bc27bf0d19f07fa60a48dfd8189edb4462f54 (diff)
downloadprofani-tty-47f90d7a39002ce4ee683179e25797a78cf95905.tar.gz
Merge pull request #991 from paulfariello/feature/xep-0050
Add support for xep 0050 ad-hoc commands, without multi-step
Diffstat (limited to 'src')
-rw-r--r--src/command/cmd_ac.c16
-rw-r--r--src/command/cmd_defs.c28
-rw-r--r--src/command/cmd_funcs.c203
-rw-r--r--src/command/cmd_funcs.h2
-rw-r--r--src/config/preferences.c10
-rw-r--r--src/config/preferences.h2
-rw-r--r--src/config/theme.c2
-rw-r--r--src/ui/confwin.c (renamed from src/ui/mucconfwin.c)32
-rw-r--r--src/ui/console.c10
-rw-r--r--src/ui/core.c20
-rw-r--r--src/ui/statusbar.c2
-rw-r--r--src/ui/ui.h21
-rw-r--r--src/ui/win_types.h12
-rw-r--r--src/ui/window.c94
-rw-r--r--src/ui/window_list.c18
-rw-r--r--src/ui/window_list.h6
-rw-r--r--src/xmpp/iq.c256
-rw-r--r--src/xmpp/roster.c9
-rw-r--r--src/xmpp/roster.h1
-rw-r--r--src/xmpp/roster_list.c24
-rw-r--r--src/xmpp/roster_list.h2
-rw-r--r--src/xmpp/stanza.c52
-rw-r--r--src/xmpp/stanza.h8
-rw-r--r--src/xmpp/xmpp.h7
24 files changed, 670 insertions, 167 deletions
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index f9d5a22a..7a340e7f 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -664,7 +664,7 @@ cmd_ac_init(void)
     autocomplete_add(time_ac, "console");
     autocomplete_add(time_ac, "chat");
     autocomplete_add(time_ac, "muc");
-    autocomplete_add(time_ac, "mucconfig");
+    autocomplete_add(time_ac, "config");
     autocomplete_add(time_ac, "private");
     autocomplete_add(time_ac, "xml");
     autocomplete_add(time_ac, "statusbar");
@@ -1107,8 +1107,8 @@ cmd_ac_reset(ProfWin *window)
         muc_jid_autocomplete_reset(mucwin->roomjid);
     }
 
-    if (window->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+    if (window->type == WIN_CONFIG) {
+        ProfConfWin *confwin = (ProfConfWin*)window;
         assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
         if (confwin->form) {
             form_reset_autocompleters(confwin->form);
@@ -2349,13 +2349,13 @@ _inpblock_autocomplete(ProfWin *window, const char *const input, gboolean previo
 static char*
 _form_autocomplete(ProfWin *window, const char *const input, gboolean previous)
 {
-    if (window->type != WIN_MUC_CONFIG) {
+    if (window->type != WIN_CONFIG) {
         return NULL;
     }
 
     char *found = NULL;
 
-    ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+    ProfConfWin *confwin = (ProfConfWin*)window;
     DataForm *form = confwin->form;
     if (form) {
         found = autocomplete_param_with_ac(input, "/form help", form->tag_ac, TRUE, previous);
@@ -2375,13 +2375,13 @@ _form_autocomplete(ProfWin *window, const char *const input, gboolean previous)
 static char*
 _form_field_autocomplete(ProfWin *window, const char *const input, gboolean previous)
 {
-    if (window->type != WIN_MUC_CONFIG) {
+    if (window->type != WIN_CONFIG) {
         return NULL;
     }
 
     char *found = NULL;
 
-    ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+    ProfConfWin *confwin = (ProfConfWin*)window;
     DataForm *form = confwin->form;
     if (form == NULL) {
         return NULL;
@@ -2510,7 +2510,7 @@ _time_autocomplete(ProfWin *window, const char *const input, gboolean previous)
         return found;
     }
 
-    found = autocomplete_param_with_ac(input, "/time mucconfig", time_format_ac, TRUE, previous);
+    found = autocomplete_param_with_ac(input, "/time config", time_format_ac, TRUE, previous);
     if (found) {
         return found;
     }
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index ee320e84..04a2fe35 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -1265,8 +1265,8 @@ static struct cmd_t command_defs[] =
         CMD_TAGS(
             CMD_TAG_UI)
         CMD_SYN(
-            "/time console|chat|muc|mucconfig|private|xml set <format>",
-            "/time console|chat|muc|mucconfig|private|xml off",
+            "/time console|chat|muc|config|private|xml set <format>",
+            "/time console|chat|muc|config|private|xml off",
             "/time statusbar set <format>",
             "/time statusbar off",
             "/time lastactivity set <format>")
@@ -1283,8 +1283,8 @@ static struct cmd_t command_defs[] =
             { "chat off",                  "Do not show time in chat windows." },
             { "muc set <format>",          "Set time format for chat room windows." },
             { "muc off",                   "Do not show time in chat room windows." },
-            { "mucconfig set <format>",    "Set time format for chat room config windows." },
-            { "mucconfig off",             "Do not show time in chat room config windows." },
+            { "config set <format>",       "Set time format for config windows." },
+            { "config off",                "Do not show time in config windows." },
             { "private set <format>",      "Set time format for private chat windows." },
             { "private off",               "Do not show time in private chat windows." },
             { "xml set <format>",          "Set time format for XML console window." },
@@ -2306,6 +2306,26 @@ static struct cmd_t command_defs[] =
         CMD_EXAMPLES(
             "/export /path/to/output.csv",
             "/export ~/contacts.csv")
+    },
+
+    { "/cmd",
+        parse_args, 1, 3, NULL,
+        CMD_SUBFUNCS(
+            { "list", cmd_command_list },
+            { "exec", cmd_command_exec })
+        CMD_NOMAINFUNC
+        CMD_NOTAGS
+        CMD_SYN(
+            "/cmd list [<jid>]",
+            "/cmd exec <command> [<jid>]")
+        CMD_DESC(
+            "Execute ad hoc commands.")
+        CMD_ARGS(
+            { "list",           "List supported ad hoc commands." },
+            { "exec <command>", "Execute a command." })
+        CMD_EXAMPLES(
+            "/cmd list",
+            "/cmd exec ping")
     }
 };
 
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 9cc7b881..6ce23849 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -70,6 +70,7 @@
 #include "ui/ui.h"
 #include "ui/window_list.h"
 #include "xmpp/xmpp.h"
+#include "xmpp/connection.h"
 #include "xmpp/contact.h"
 #include "xmpp/roster_list.h"
 #include "xmpp/jid.h"
@@ -204,7 +205,7 @@ cmd_tls_trust(ProfWin *window, const char *const command, gchar **args)
 #ifdef HAVE_LIBMESODE
     jabber_conn_status_t conn_status = connection_get_status();
     if (conn_status != JABBER_CONNECTED) {
-        cons_show("You are not currently connected.");
+        cons_show("You are currently not connected.");
         return TRUE;
     }
     if (!connection_is_secured()) {
@@ -3632,11 +3633,11 @@ cmd_decline(ProfWin *window, const char *const command, gchar **args)
 gboolean
 cmd_form_field(ProfWin *window, char *tag, gchar **args)
 {
-    if (window->type != WIN_MUC_CONFIG) {
+    if (window->type != WIN_CONFIG) {
         return TRUE;
     }
 
-    ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+    ProfConfWin *confwin = (ProfConfWin*)window;
     DataForm *form = confwin->form;
     if (form) {
         if (!form_tag_exists(form, tag)) {
@@ -3657,14 +3658,14 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
             if (g_strcmp0(value, "on") == 0) {
                 form_set_value(form, tag, "1");
                 win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                mucconfwin_show_form_field(confwin, form, tag);
+                confwin_show_form_field(confwin, form, tag);
             } else if (g_strcmp0(value, "off") == 0) {
                 form_set_value(form, tag, "0");
                 win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                mucconfwin_show_form_field(confwin, form, tag);
+                confwin_show_form_field(confwin, form, tag);
             } else {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
             }
             break;
@@ -3675,24 +3676,24 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
             value = args[0];
             if (value == NULL) {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
             } else {
                 form_set_value(form, tag, value);
                 win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                mucconfwin_show_form_field(confwin, form, tag);
+                confwin_show_form_field(confwin, form, tag);
             }
             break;
         case FIELD_LIST_SINGLE:
             value = args[0];
             if ((value == NULL) || !form_field_contains_option(form, tag, value)) {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
             } else {
                 form_set_value(form, tag, value);
                 win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                mucconfwin_show_form_field(confwin, form, tag);
+                confwin_show_form_field(confwin, form, tag);
             }
             break;
 
@@ -3703,32 +3704,32 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
             }
             if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
                 break;
             }
             if (value == NULL) {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
                 break;
             }
             if (g_strcmp0(cmd, "add") == 0) {
                 form_add_value(form, tag, value);
                 win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                mucconfwin_show_form_field(confwin, form, tag);
+                confwin_show_form_field(confwin, form, tag);
                 break;
             }
             if (g_strcmp0(args[0], "remove") == 0) {
                 if (!g_str_has_prefix(value, "val")) {
                     win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                    mucconfwin_field_help(confwin, tag);
+                    confwin_field_help(confwin, tag);
                     win_println(window, THEME_DEFAULT, '-', "");
                     break;
                 }
                 if (strlen(value) < 4) {
                     win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                    mucconfwin_field_help(confwin, tag);
+                    confwin_field_help(confwin, tag);
                     win_println(window, THEME_DEFAULT, '-', "");
                     break;
                 }
@@ -3736,7 +3737,7 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
                 int index = strtol(&value[3], NULL, 10);
                 if ((index < 1) || (index > form_get_value_count(form, tag))) {
                     win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                    mucconfwin_field_help(confwin, tag);
+                    confwin_field_help(confwin, tag);
                     win_println(window, THEME_DEFAULT, '-', "");
                     break;
                 }
@@ -3744,7 +3745,7 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
                 removed = form_remove_text_multi_value(form, tag, index);
                 if (removed) {
                     win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                    mucconfwin_show_form_field(confwin, form, tag);
+                    confwin_show_form_field(confwin, form, tag);
                 } else {
                     win_println(window, THEME_DEFAULT, '-', "Could not remove %s from %s", value, tag);
                 }
@@ -3757,13 +3758,13 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
             }
             if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
                 break;
             }
             if (value == NULL) {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
                 break;
             }
@@ -3773,13 +3774,13 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
                     added = form_add_unique_value(form, tag, value);
                     if (added) {
                         win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                        mucconfwin_show_form_field(confwin, form, tag);
+                        confwin_show_form_field(confwin, form, tag);
                     } else {
                         win_println(window, THEME_DEFAULT, '-', "Value %s already selected for %s", value, tag);
                     }
                 } else {
                     win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                    mucconfwin_field_help(confwin, tag);
+                    confwin_field_help(confwin, tag);
                     win_println(window, THEME_DEFAULT, '-', "");
                 }
                 break;
@@ -3790,13 +3791,13 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
                     removed = form_remove_value(form, tag, value);
                     if (removed) {
                         win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                        mucconfwin_show_form_field(confwin, form, tag);
+                        confwin_show_form_field(confwin, form, tag);
                     } else {
                         win_println(window, THEME_DEFAULT, '-', "Value %s is not currently set for %s", value, tag);
                     }
                 } else {
                     win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                    mucconfwin_field_help(confwin, tag);
+                    confwin_field_help(confwin, tag);
                     win_println(window, THEME_DEFAULT, '-', "");
                 }
             }
@@ -3808,13 +3809,13 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
             }
             if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
                 break;
             }
             if (value == NULL) {
                 win_println(window, THEME_DEFAULT, '-', "Invalid command, usage:");
-                mucconfwin_field_help(confwin, tag);
+                confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, '-', "");
                 break;
             }
@@ -3822,7 +3823,7 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
                 added = form_add_unique_value(form, tag, value);
                 if (added) {
                     win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                    mucconfwin_show_form_field(confwin, form, tag);
+                    confwin_show_form_field(confwin, form, tag);
                 } else {
                     win_println(window, THEME_DEFAULT, '-', "JID %s already exists in %s", value, tag);
                 }
@@ -3832,7 +3833,7 @@ cmd_form_field(ProfWin *window, char *tag, gchar **args)
                 removed = form_remove_value(form, tag, value);
                 if (removed) {
                     win_println(window, THEME_DEFAULT, '-', "Field updated...");
-                    mucconfwin_show_form_field(confwin, form, tag);
+                    confwin_show_form_field(confwin, form, tag);
                 } else {
                     win_println(window, THEME_DEFAULT, '-', "Field %s does not contain %s", tag, value);
                 }
@@ -3857,7 +3858,7 @@ cmd_form(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
-    if (window->type != WIN_MUC_CONFIG) {
+    if (window->type != WIN_CONFIG) {
         cons_show("Command '/form' does not apply to this window.");
         return TRUE;
     }
@@ -3870,20 +3871,20 @@ cmd_form(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
-    ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+    ProfConfWin *confwin = (ProfConfWin*)window;
     assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
 
     if (g_strcmp0(args[0], "show") == 0) {
-        mucconfwin_show_form(confwin);
+        confwin_show_form(confwin);
         return TRUE;
     }
 
     if (g_strcmp0(args[0], "help") == 0) {
         char *tag = args[1];
         if (tag) {
-            mucconfwin_field_help(confwin, tag);
+            confwin_field_help(confwin, tag);
         } else {
-            mucconfwin_form_help(confwin);
+            confwin_form_help(confwin);
 
             gchar **help_text = NULL;
             Command *command = cmd_get("/form");
@@ -3898,12 +3899,12 @@ cmd_form(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
-    if (g_strcmp0(args[0], "submit") == 0) {
-        iq_submit_room_config(confwin->roomjid, confwin->form);
+    if (g_strcmp0(args[0], "submit") == 0 && confwin->submit != NULL) {
+        confwin->submit(confwin);
     }
 
-    if (g_strcmp0(args[0], "cancel") == 0) {
-        iq_room_config_cancel(confwin->roomjid);
+    if (g_strcmp0(args[0], "cancel") == 0 && confwin->cancel != NULL) {
+        confwin->cancel(confwin);
     }
 
     if ((g_strcmp0(args[0], "submit") == 0) || (g_strcmp0(args[0], "cancel") == 0)) {
@@ -4264,7 +4265,7 @@ cmd_room(ProfWin *window, const char *const command, gchar **args)
     }
 
     if (g_strcmp0(args[0], "config") == 0) {
-        ProfMucConfWin *confwin = wins_get_muc_conf(mucwin->roomjid);
+        ProfConfWin *confwin = wins_get_conf(mucwin->roomjid);
 
         if (confwin) {
             ui_focus_win((ProfWin*)confwin);
@@ -5159,20 +5160,20 @@ cmd_time(ProfWin *window, const char *const command, gchar **args)
             cons_bad_cmd_usage(command);
             return TRUE;
         }
-    } else if (g_strcmp0(args[0], "mucconfig") == 0) {
+    } else if (g_strcmp0(args[0], "config") == 0) {
         if (args[1] == NULL) {
-            char *format = prefs_get_string(PREF_TIME_MUCCONFIG);
-            cons_show("MUC config time format: '%s'.", format);
+            char *format = prefs_get_string(PREF_TIME_CONFIG);
+            cons_show("config time format: '%s'.", format);
             prefs_free_string(format);
             return TRUE;
         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
-            prefs_set_string(PREF_TIME_MUCCONFIG, args[2]);
-            cons_show("MUC config time format set to '%s'.", args[2]);
+            prefs_set_string(PREF_TIME_CONFIG, args[2]);
+            cons_show("config time format set to '%s'.", args[2]);
             wins_resize_all();
             return TRUE;
         } else if (g_strcmp0(args[1], "off") == 0) {
-            prefs_set_string(PREF_TIME_MUCCONFIG, "off");
-            cons_show("MUC config time display disabled.");
+            prefs_set_string(PREF_TIME_CONFIG, "off");
+            cons_show("config time display disabled.");
             wins_resize_all();
             return TRUE;
         } else {
@@ -7552,10 +7553,122 @@ cmd_encwarn(ProfWin *window, const char *const command, gchar **args)
     return TRUE;
 }
 
+gboolean
+cmd_command_list(ProfWin *window, const char *const command, gchar **args)
+{
+    jabber_conn_status_t conn_status = connection_get_status();
+
+    if (conn_status != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    if (connection_supports(XMPP_FEATURE_COMMANDS) == FALSE) {
+        cons_show("Server does not support ad hoc commands.");
+        return TRUE;
+    }
+
+    char *jid = args[1];
+    if (jid == NULL) {
+        switch (window->type) {
+        case WIN_MUC:
+        {
+            ProfMucWin *mucwin = (ProfMucWin*)window;
+            assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+            jid = mucwin->roomjid;
+            break;
+        }
+        case WIN_CHAT:
+        {
+            ProfChatWin *chatwin = (ProfChatWin*)window;
+            assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+            jid = chatwin->barejid;
+            break;
+        }
+        case WIN_PRIVATE:
+        {
+            ProfPrivateWin *privatewin = (ProfPrivateWin*)window;
+            assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
+            jid = privatewin->fulljid;
+            break;
+        }
+        case WIN_CONSOLE:
+        {
+            jid = connection_get_domain();
+            break;
+        }
+        default:
+            cons_show("Cannot send ad hoc commands.");
+            return TRUE;
+        }
+    }
+
+    iq_command_list(jid);
+
+    cons_show("List available ad hoc commands");
+    return TRUE;
+}
+
+gboolean
+cmd_command_exec(ProfWin *window, const char *const command, gchar **args)
+{
+    jabber_conn_status_t conn_status = connection_get_status();
+
+    if (conn_status != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    if (connection_supports(XMPP_FEATURE_COMMANDS) == FALSE) {
+        cons_show("Server does not support ad hoc commands.");
+        return TRUE;
+    }
+
+    char *jid = args[2];
+    if (jid == NULL) {
+        switch (window->type) {
+        case WIN_MUC:
+        {
+            ProfMucWin *mucwin = (ProfMucWin*)window;
+            assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+            jid = mucwin->roomjid;
+            break;
+        }
+        case WIN_CHAT:
+        {
+            ProfChatWin *chatwin = (ProfChatWin*)window;
+            assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+            jid = chatwin->barejid;
+            break;
+        }
+        case WIN_PRIVATE:
+        {
+            ProfPrivateWin *privatewin = (ProfPrivateWin*)window;
+            assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
+            jid = privatewin->fulljid;
+            break;
+        }
+        case WIN_CONSOLE:
+        {
+            jid = connection_get_domain();
+            break;
+        }
+        default:
+            cons_show("Cannot send ad hoc commands.");
+            return TRUE;
+        }
+    }
+
+    iq_command_exec(jid, args[1]);
+
+    cons_show("Execute %s...", args[1]);
+    return TRUE;
+}
+
 static gboolean
 _cmd_execute(ProfWin *window, const char *const command, const char *const inp)
 {
-    if (g_str_has_prefix(command, "/field") && window->type == WIN_MUC_CONFIG) {
+    if (g_str_has_prefix(command, "/field") && window->type == WIN_CONFIG) {
         gboolean result = FALSE;
         gchar **args = parse_args_with_freetext(inp, 1, 2, &result);
         if (!result) {
diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h
index f4933d44..d0a6c69a 100644
--- a/src/command/cmd_funcs.h
+++ b/src/command/cmd_funcs.h
@@ -158,6 +158,8 @@ gboolean cmd_script(ProfWin *window, const char *const command, gchar **args);
 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_command_list(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_command_exec(ProfWin *window, const char *const command, gchar **args);
 
 gboolean cmd_plugins(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_plugins_sourcepath(ProfWin *window, const char *const command, gchar **args);
diff --git a/src/config/preferences.c b/src/config/preferences.c
index e62c552c..bf4a6ab1 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -123,7 +123,7 @@ prefs_load(void)
         g_key_file_set_string(prefs, PREF_GROUP_UI, "time.console", val);
         g_key_file_set_string(prefs, PREF_GROUP_UI, "time.chat", val);
         g_key_file_set_string(prefs, PREF_GROUP_UI, "time.muc", val);
-        g_key_file_set_string(prefs, PREF_GROUP_UI, "time.mucconfig", val);
+        g_key_file_set_string(prefs, PREF_GROUP_UI, "time.config", val);
         g_key_file_set_string(prefs, PREF_GROUP_UI, "time.private", val);
         g_key_file_set_string(prefs, PREF_GROUP_UI, "time.xmlconsole", val);
         g_key_file_remove_key(prefs, PREF_GROUP_UI, "time", NULL);
@@ -1567,7 +1567,7 @@ _get_group(preference_t pref)
         case PREF_TIME_CONSOLE:
         case PREF_TIME_CHAT:
         case PREF_TIME_MUC:
-        case PREF_TIME_MUCCONFIG:
+        case PREF_TIME_CONFIG:
         case PREF_TIME_PRIVATE:
         case PREF_TIME_XMLCONSOLE:
         case PREF_TIME_STATUSBAR:
@@ -1777,8 +1777,8 @@ _get_key(preference_t pref)
             return "time.chat";
         case PREF_TIME_MUC:
             return "time.muc";
-        case PREF_TIME_MUCCONFIG:
-            return "time.mucconfig";
+        case PREF_TIME_CONFIG:
+            return "time.config";
         case PREF_TIME_PRIVATE:
             return "time.private";
         case PREF_TIME_XMLCONSOLE:
@@ -1967,7 +1967,7 @@ _get_default_string(preference_t pref)
             return "%H:%M:%S";
         case PREF_TIME_MUC:
             return "%H:%M:%S";
-        case PREF_TIME_MUCCONFIG:
+        case PREF_TIME_CONFIG:
             return "%H:%M:%S";
         case PREF_TIME_PRIVATE:
             return "%H:%M:%S";
diff --git a/src/config/preferences.h b/src/config/preferences.h
index bafe4a1f..e882e248 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -92,7 +92,7 @@ typedef enum {
     PREF_TIME_CONSOLE,
     PREF_TIME_CHAT,
     PREF_TIME_MUC,
-    PREF_TIME_MUCCONFIG,
+    PREF_TIME_CONFIG,
     PREF_TIME_PRIVATE,
     PREF_TIME_XMLCONSOLE,
     PREF_TIME_STATUSBAR,
diff --git a/src/config/theme.c b/src/config/theme.c
index 13f9cc2b..ad8a2d0e 100644
--- a/src/config/theme.c
+++ b/src/config/theme.c
@@ -413,7 +413,7 @@ _load_preferences(void)
     _set_string_preference("time.console", PREF_TIME_CONSOLE);
     _set_string_preference("time.chat", PREF_TIME_CHAT);
     _set_string_preference("time.muc", PREF_TIME_MUC);
-    _set_string_preference("time.mucconfig", PREF_TIME_MUCCONFIG);
+    _set_string_preference("time.config", PREF_TIME_CONFIG);
     _set_string_preference("time.private", PREF_TIME_PRIVATE);
     _set_string_preference("time.xmlconsole", PREF_TIME_XMLCONSOLE);
     _set_string_preference("time.statusbar", PREF_TIME_STATUSBAR);
diff --git a/src/ui/mucconfwin.c b/src/ui/confwin.c
index 7a658a1e..9791e5cf 100644
--- a/src/ui/mucconfwin.c
+++ b/src/ui/confwin.c
@@ -1,5 +1,5 @@
 /*
- * mucconfwin.c
+ * confwin.c
  *
  * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
@@ -40,10 +40,10 @@
 #include "ui/win_types.h"
 #include "ui/window_list.h"
 
-static void _mucconfwin_form_field(ProfWin *window, char *tag, FormField *field);
+static void _confwin_form_field(ProfWin *window, char *tag, FormField *field);
 
 void
-mucconfwin_show_form(ProfMucConfWin *confwin)
+confwin_show_form(ProfConfWin *confwin)
 {
     ProfWin *window = (ProfWin*) confwin;
     if (confwin->form->title) {
@@ -54,7 +54,7 @@ mucconfwin_show_form(ProfMucConfWin *confwin)
     }
     win_println(window, THEME_DEFAULT, '-', "");
 
-    mucconfwin_form_help(confwin);
+    confwin_form_help(confwin);
 
     GSList *fields = confwin->form->fields;
     GSList *curr_field = fields;
@@ -68,7 +68,7 @@ mucconfwin_show_form(ProfMucConfWin *confwin)
             }
         } else if (g_strcmp0(field->type, "hidden") != 0 && field->var) {
             char *tag = g_hash_table_lookup(confwin->form->var_to_tag, field->var);
-            _mucconfwin_form_field(window, tag, field);
+            _confwin_form_field(window, tag, field);
         }
 
         curr_field = g_slist_next(curr_field);
@@ -76,35 +76,37 @@ mucconfwin_show_form(ProfMucConfWin *confwin)
 }
 
 void
-mucconfwin_show_form_field(ProfMucConfWin *confwin, DataForm *form, char *tag)
+confwin_show_form_field(ProfConfWin *confwin, DataForm *form, char *tag)
 {
     assert(confwin != NULL);
 
     FormField *field = form_get_field_by_tag(form, tag);
     ProfWin *window = (ProfWin*)confwin;
-    _mucconfwin_form_field(window, tag, field);
+    _confwin_form_field(window, tag, field);
     win_println(window, THEME_DEFAULT, '-', "");
 }
 
 void
-mucconfwin_handle_configuration(ProfMucConfWin *confwin, DataForm *form)
+confwin_handle_configuration(ProfConfWin *confwin, DataForm *form)
 {
     assert(confwin != NULL);
 
     ProfWin *window = (ProfWin*)confwin;
     ui_focus_win(window);
 
-    mucconfwin_show_form(confwin);
+    confwin_show_form(confwin);
 
     win_println(window, THEME_DEFAULT, '-', "");
-    win_println(window, THEME_DEFAULT, '-', "Use '/form submit' to save changes.");
+    if (confwin->submit != NULL) {
+        win_println(window, THEME_DEFAULT, '-', "Use '/form submit' to save changes.");
+    }
     win_println(window, THEME_DEFAULT, '-', "Use '/form cancel' to cancel changes.");
     win_println(window, THEME_DEFAULT, '-', "See '/form help' for more information.");
     win_println(window, THEME_DEFAULT, '-', "");
 }
 
 void
-mucconfwin_field_help(ProfMucConfWin *confwin, char *tag)
+confwin_field_help(ProfConfWin *confwin, char *tag)
 {
     assert(confwin != NULL);
 
@@ -187,7 +189,7 @@ mucconfwin_field_help(ProfMucConfWin *confwin, char *tag)
 }
 
 void
-mucconfwin_form_help(ProfMucConfWin *confwin)
+confwin_form_help(ProfConfWin *confwin)
 {
     assert(confwin != NULL);
 
@@ -200,7 +202,7 @@ mucconfwin_form_help(ProfMucConfWin *confwin)
 }
 
 static void
-_mucconfwin_form_field(ProfWin *window, char *tag, FormField *field)
+_confwin_form_field(ProfWin *window, char *tag, FormField *field)
 {
     win_print(window, THEME_AWAY, '-', "[%s] ", tag);
     win_append(window, THEME_DEFAULT, "%s", field->label);
@@ -258,7 +260,7 @@ _mucconfwin_form_field(ProfWin *window, char *tag, FormField *field)
             if (value == NULL) {
                 win_appendln(window, THEME_OFFLINE, "FALSE");
             } else {
-                if (g_strcmp0(value, "0") == 0) {
+                if (g_strcmp0(value, "0") == 0 || g_strcmp0(value, "false") == 0) {
                     win_appendln(window, THEME_OFFLINE, "FALSE");
                 } else {
                     win_appendln(window, THEME_ONLINE, "TRUE");
@@ -331,7 +333,7 @@ _mucconfwin_form_field(ProfWin *window, char *tag, FormField *field)
 }
 
 char*
-mucconfwin_get_string(ProfMucConfWin *confwin)
+confwin_get_string(ProfConfWin *confwin)
 {
     assert(confwin != NULL);
 
diff --git a/src/ui/console.c b/src/ui/console.c
index 9bead705..f5c0379b 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -1282,12 +1282,12 @@ cons_time_setting(void)
         cons_show("Time MUC (/time)                    : %s", pref_time_muc);
     prefs_free_string(pref_time_muc);
 
-    char *pref_time_mucconf = prefs_get_string(PREF_TIME_MUCCONFIG);
-    if (g_strcmp0(pref_time_mucconf, "off") == 0)
-        cons_show("Time MUC config (/time)             : OFF");
+    char *pref_time_conf = prefs_get_string(PREF_TIME_CONFIG);
+    if (g_strcmp0(pref_time_conf, "off") == 0)
+        cons_show("Time config (/time)             : OFF");
     else
-        cons_show("Time MUC config (/time)             : %s", pref_time_mucconf);
-    prefs_free_string(pref_time_mucconf);
+        cons_show("Time config (/time)             : %s", pref_time_conf);
+    prefs_free_string(pref_time_conf);
 
     char *pref_time_private = prefs_get_string(PREF_TIME_PRIVATE);
     if (g_strcmp0(pref_time_private, "off") == 0)
diff --git a/src/ui/core.c b/src/ui/core.c
index 5246d06a..da4e6469 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -632,8 +632,8 @@ ui_win_has_unsaved_form(int num)
 {
     ProfWin *window = wins_get_by_num(num);
 
-    if (window->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+    if (window->type == WIN_CONFIG) {
+        ProfConfWin *confwin = (ProfConfWin*)window;
         assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
         return confwin->form->modified;
     } else {
@@ -651,13 +651,13 @@ ui_focus_win(ProfWin *window)
     }
 
     ProfWin *old_current = wins_get_current();
-    if (old_current->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*)old_current;
+    if (old_current->type == WIN_CONFIG) {
+        ProfConfWin *confwin = (ProfConfWin*)old_current;
         cmd_ac_remove_form_fields(confwin->form);
     }
 
-    if (window->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+    if (window->type == WIN_CONFIG) {
+        ProfConfWin *confwin = (ProfConfWin*)window;
         cmd_ac_add_form_fields(confwin->form);
     }
 
@@ -681,8 +681,8 @@ void
 ui_close_win(int index)
 {
     ProfWin *window = wins_get_by_num(index);
-    if (window && window->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+    if (window && window->type == WIN_CONFIG) {
+        ProfConfWin *confwin = (ProfConfWin*)window;
         if (confwin->form) {
             cmd_ac_remove_form_fields(confwin->form);
         }
@@ -1135,7 +1135,7 @@ ui_handle_room_config_submit_result(const char *const roomjid)
 
         GString *form_recipient = g_string_new(roomjid);
         g_string_append(form_recipient, " config");
-        form_window = (ProfWin*) wins_get_muc_conf(form_recipient->str);
+        form_window = (ProfWin*) wins_get_conf(form_recipient->str);
         g_string_free(form_recipient, TRUE);
 
         if (form_window) {
@@ -1167,7 +1167,7 @@ ui_handle_room_config_submit_result_error(const char *const roomjid, const char
 
         GString *form_recipient = g_string_new(roomjid);
         g_string_append(form_recipient, " config");
-        form_window = (ProfWin*) wins_get_muc_conf(form_recipient->str);
+        form_window = (ProfWin*) wins_get_conf(form_recipient->str);
         g_string_free(form_recipient, TRUE);
 
         if (form_window) {
diff --git a/src/ui/statusbar.c b/src/ui/statusbar.c
index ac1d7498..5d860440 100644
--- a/src/ui/statusbar.c
+++ b/src/ui/statusbar.c
@@ -544,7 +544,7 @@ _display_name(StatusBarTab *tab)
         } else {
             fullname = strdup(tab->identifier);
         }
-    } else if (tab->window_type == WIN_MUC_CONFIG) {
+    } else if (tab->window_type == WIN_CONFIG) {
         char *pref = prefs_get_string(PREF_STATUSBAR_ROOM);
         GString *display_str = g_string_new("");
         if (g_strcmp0("room", pref) == 0) {
diff --git a/src/ui/ui.h b/src/ui/ui.h
index d344f855..f608483d 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -217,13 +217,13 @@ void privwin_room_kicked(ProfPrivateWin *privwin, const char *const actor, const
 void privwin_room_banned(ProfPrivateWin *privwin, const char *const actor, const char *const reason);
 void privwin_room_joined(ProfPrivateWin *privwin);
 
-// MUC room config window
-void mucconfwin_handle_configuration(ProfMucConfWin *confwin, DataForm *form);
-void mucconfwin_show_form(ProfMucConfWin *confwin);
-void mucconfwin_show_form_field(ProfMucConfWin *confwin, DataForm *form, char *tag);
-void mucconfwin_form_help(ProfMucConfWin *confwin);
-void mucconfwin_field_help(ProfMucConfWin *confwin, char *tag);
-char* mucconfwin_get_string(ProfMucConfWin *confwin);
+// config window
+void confwin_handle_configuration(ProfConfWin *confwin, DataForm *form);
+void confwin_show_form(ProfConfWin *confwin);
+void confwin_show_form_field(ProfConfWin *confwin, DataForm *form, char *tag);
+void confwin_form_help(ProfConfWin *confwin);
+void confwin_field_help(ProfConfWin *confwin, char *tag);
+char* confwin_get_string(ProfConfWin *confwin);
 
 // xml console
 void xmlwin_show(ProfXMLWin *xmlwin, const char *const msg);
@@ -346,7 +346,7 @@ ProfWin* win_create_console(void);
 ProfWin* win_create_xmlconsole(void);
 ProfWin* win_create_chat(const char *const barejid);
 ProfWin* win_create_muc(const char *const roomjid);
-ProfWin* win_create_muc_config(const char *const title, DataForm *form);
+ProfWin* win_create_config(const char *const title, DataForm *form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void *userdata);
 ProfWin* win_create_private(const char *const fulljid);
 ProfWin* win_create_plugin(const char *const plugin_name, const char *const tag);
 void win_update_virtual(ProfWin *window);
@@ -377,6 +377,11 @@ void win_show_info(ProfWin *window, PContact contact);
 void win_clear(ProfWin *window);
 char* win_get_tab_identifier(ProfWin *window);
 char* win_to_string(ProfWin *window);
+void win_command_list_error(ProfWin *window, const char *const error);
+void win_command_exec_error(ProfWin *window, const char *const command, const char *const error, ...);
+void win_handle_command_list(ProfWin *window, GSList *cmds);
+void win_handle_command_exec_status(ProfWin *window, const char *const type, const char *const value);
+void win_handle_command_exec_result_note(ProfWin *window, const char *const type, const char *const value);
 
 // desktop notifications
 void notifier_initialise(void);
diff --git a/src/ui/win_types.h b/src/ui/win_types.h
index 7fa75b34..eb453cd0 100644
--- a/src/ui/win_types.h
+++ b/src/ui/win_types.h
@@ -128,7 +128,7 @@ typedef enum {
     WIN_CONSOLE,
     WIN_CHAT,
     WIN_MUC,
-    WIN_MUC_CONFIG,
+    WIN_CONFIG,
     WIN_PRIVATE,
     WIN_XML,
     WIN_PLUGIN
@@ -172,12 +172,18 @@ typedef struct prof_muc_win_t {
     char *message_char;
 } ProfMucWin;
 
-typedef struct prof_mucconf_win_t {
+typedef struct prof_conf_win_t ProfConfWin;
+typedef void (*ProfConfWinCallback)(ProfConfWin *);
+
+struct prof_conf_win_t {
     ProfWin window;
     char *roomjid;
     DataForm *form;
     unsigned long memcheck;
-} ProfMucConfWin;
+    ProfConfWinCallback submit;
+    ProfConfWinCallback cancel;
+    const void *userdata;
+};
 
 typedef struct prof_private_win_t {
     ProfWin window;
diff --git a/src/ui/window.c b/src/ui/window.c
index 5543707d..be89d295 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -203,13 +203,16 @@ win_create_muc(const char *const roomjid)
 }
 
 ProfWin*
-win_create_muc_config(const char *const roomjid, DataForm *form)
+win_create_config(const char *const roomjid, DataForm *form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void *userdata)
 {
-    ProfMucConfWin *new_win = malloc(sizeof(ProfMucConfWin));
-    new_win->window.type = WIN_MUC_CONFIG;
+    ProfConfWin *new_win = malloc(sizeof(ProfConfWin));
+    new_win->window.type = WIN_CONFIG;
     new_win->window.layout = _win_create_simple_layout();
     new_win->roomjid = strdup(roomjid);
     new_win->form = form;
+    new_win->submit = submit;
+    new_win->cancel = cancel;
+    new_win->userdata = userdata;
 
     new_win->memcheck = PROFCONFWIN_MEMCHECK;
 
@@ -289,8 +292,8 @@ win_get_title(ProfWin *window)
         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
         return strdup(mucwin->roomjid);
     }
-    if (window->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*) window;
+    if (window->type == WIN_CONFIG) {
+        ProfConfWin *confwin = (ProfConfWin*) window;
         assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
         GString *title = g_string_new(confwin->roomjid);
         g_string_append(title, " config");
@@ -338,10 +341,10 @@ win_get_tab_identifier(ProfWin *window)
             ProfMucWin *mucwin = (ProfMucWin*)window;
             return strdup(mucwin->roomjid);
         }
-        case WIN_MUC_CONFIG:
+        case WIN_CONFIG:
         {
-            ProfMucConfWin *mucconfwin = (ProfMucConfWin*)window;
-            return strdup(mucconfwin->roomjid);
+            ProfConfWin *confwin = (ProfConfWin*)window;
+            return strdup(confwin->roomjid);
         }
         case WIN_PRIVATE:
         {
@@ -383,10 +386,10 @@ win_to_string(ProfWin *window)
             ProfMucWin *mucwin = (ProfMucWin*)window;
             return mucwin_get_string(mucwin);
         }
-        case WIN_MUC_CONFIG:
+        case WIN_CONFIG:
         {
-            ProfMucConfWin *mucconfwin = (ProfMucConfWin*)window;
-            return mucconfwin_get_string(mucconfwin);
+            ProfConfWin *confwin = (ProfConfWin*)window;
+            return confwin_get_string(confwin);
         }
         case WIN_PRIVATE:
         {
@@ -491,11 +494,11 @@ win_free(ProfWin* window)
         free(mucwin->message_char);
         break;
     }
-    case WIN_MUC_CONFIG:
+    case WIN_CONFIG:
     {
-        ProfMucConfWin *mucconf = (ProfMucConfWin*)window;
-        free(mucconf->roomjid);
-        form_destroy(mucconf->form);
+        ProfConfWin *conf = (ProfConfWin*)window;
+        free(conf->roomjid);
+        form_destroy(conf->form);
         break;
     }
     case WIN_PRIVATE:
@@ -1389,8 +1392,8 @@ _win_print(ProfWin *window, const char show_char, int pad_indent, GDateTime *tim
         case WIN_MUC:
             time_pref = prefs_get_string(PREF_TIME_MUC);
             break;
-        case WIN_MUC_CONFIG:
-            time_pref = prefs_get_string(PREF_TIME_MUCCONFIG);
+        case WIN_CONFIG:
+            time_pref = prefs_get_string(PREF_TIME_CONFIG);
             break;
         case WIN_PRIVATE:
             time_pref = prefs_get_string(PREF_TIME_PRIVATE);
@@ -1724,3 +1727,60 @@ win_sub_newline_lazy(WINDOW *win)
         wmove(win, cury+1, 0);
     }
 }
+
+void
+win_command_list_error(ProfWin *window, const char *const error)
+{
+    assert(window != NULL);
+
+    win_println(window, THEME_ERROR, '!', "Error retrieving command list: %s", error);
+}
+
+void
+win_command_exec_error(ProfWin *window, const char *const command, const char *const error, ...)
+{
+    assert(window != NULL);
+    va_list arg;
+    va_start(arg, error);
+    GString *msg = g_string_new(NULL);
+    g_string_vprintf(msg, error, arg);
+
+    win_println(window, THEME_ERROR, '!', "Error executing command %s: %s", command, msg->str);
+
+    g_string_free(msg, TRUE);
+    va_end(arg);
+}
+
+void
+win_handle_command_list(ProfWin *window, GSList *cmds)
+{
+    assert(window != NULL);
+
+    if (cmds) {
+        win_println(window, THEME_DEFAULT, '!', "Ad hoc commands:");
+        GSList *curr_cmd = cmds;
+        while (curr_cmd) {
+            const char *cmd = curr_cmd->data;
+            win_println(window, THEME_DEFAULT, '!', "  %s", cmd);
+            curr_cmd = g_slist_next(curr_cmd);
+        }
+        win_println(window, THEME_DEFAULT, '!', "");
+    } else {
+        win_println(window, THEME_DEFAULT, '!', "No commands found");
+        win_println(window, THEME_DEFAULT, '!', "");
+    }
+}
+
+void
+win_handle_command_exec_status(ProfWin *window, const char *const command, const char *const value)
+{
+    assert(window != NULL);
+    win_println(window, THEME_DEFAULT, '!', "%s %s", command, value);
+}
+
+void
+win_handle_command_exec_result_note(ProfWin *window, const char *const type, const char *const value)
+{
+    assert(window != NULL);
+    win_println(window, THEME_DEFAULT, '!', value);
+}
diff --git a/src/ui/window_list.c b/src/ui/window_list.c
index 798f4e41..a12dc7cb 100644
--- a/src/ui/window_list.c
+++ b/src/ui/window_list.c
@@ -139,16 +139,16 @@ wins_get_chat_unsubscribed(void)
     return result;
 }
 
-ProfMucConfWin*
-wins_get_muc_conf(const char *const roomjid)
+ProfConfWin*
+wins_get_conf(const char *const roomjid)
 {
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
     while (curr) {
         ProfWin *window = curr->data;
-        if (window->type == WIN_MUC_CONFIG) {
-            ProfMucConfWin *confwin = (ProfMucConfWin*)window;
+        if (window->type == WIN_CONFIG) {
+            ProfConfWin *confwin = (ProfConfWin*)window;
             if (g_strcmp0(confwin->roomjid, roomjid) == 0) {
                 g_list_free(values);
                 return confwin;
@@ -364,7 +364,7 @@ wins_get_by_num(int i)
 }
 
 ProfWin*
-wins_get_by_string(char *str)
+wins_get_by_string(const char *str)
 {
     if (g_strcmp0(str, "console") == 0) {
         ProfWin *conswin = wins_get_console();
@@ -584,7 +584,7 @@ wins_close_by_num(int i)
                 autocomplete_remove(wins_close_ac, pluginwin->tag);
                 break;
             }
-            case WIN_MUC_CONFIG:
+            case WIN_CONFIG:
             default:
                 break;
             }
@@ -657,12 +657,12 @@ wins_new_muc(const char *const roomjid)
 }
 
 ProfWin*
-wins_new_muc_config(const char *const roomjid, DataForm *form)
+wins_new_config(const char *const roomjid, DataForm *form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void *userdata)
 {
     GList *keys = g_hash_table_get_keys(windows);
     int result = _wins_get_next_available_num(keys);
     g_list_free(keys);
-    ProfWin *newwin = win_create_muc_config(roomjid, form);
+    ProfWin *newwin = win_create_config(roomjid, form, submit, cancel, userdata);
     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
     return newwin;
 }
@@ -812,7 +812,7 @@ wins_get_prune_wins(void)
         ProfWin *window = curr->data;
         if (win_unread(window) == 0 &&
                 window->type != WIN_MUC &&
-                window->type != WIN_MUC_CONFIG &&
+                window->type != WIN_CONFIG &&
                 window->type != WIN_XML &&
                 window->type != WIN_CONSOLE) {
             result = g_slist_append(result, window);
diff --git a/src/ui/window_list.h b/src/ui/window_list.h
index 68e72739..b47ee79f 100644
--- a/src/ui/window_list.h
+++ b/src/ui/window_list.h
@@ -42,7 +42,7 @@ void wins_init(void);
 ProfWin* wins_new_xmlconsole(void);
 ProfWin* wins_new_chat(const char *const barejid);
 ProfWin* wins_new_muc(const char *const roomjid);
-ProfWin* wins_new_muc_config(const char *const roomjid, DataForm *form);
+ProfWin* wins_new_config(const char *const roomjid, DataForm *form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void *userdata);
 ProfWin* wins_new_private(const char *const fulljid);
 ProfWin* wins_new_plugin(const char *const plugin_name, const char *const tag);
 
@@ -56,7 +56,7 @@ ProfWin* wins_get_console(void);
 ProfChatWin* wins_get_chat(const char *const barejid);
 GList* wins_get_chat_unsubscribed(void);
 ProfMucWin* wins_get_muc(const char *const roomjid);
-ProfMucConfWin* wins_get_muc_conf(const char *const roomjid);
+ProfConfWin* wins_get_conf(const char *const roomjid);
 ProfPrivateWin* wins_get_private(const char *const fulljid);
 ProfPluginWin* wins_get_plugin(const char *const tag);
 ProfXMLWin* wins_get_xmlconsole(void);
@@ -68,7 +68,7 @@ ProfWin* wins_get_current(void);
 void wins_set_current_by_num(int i);
 
 ProfWin* wins_get_by_num(int i);
-ProfWin* wins_get_by_string(char *str);
+ProfWin* wins_get_by_string(const char *str);
 
 ProfWin* wins_get_next(void);
 ProfWin* wins_get_previous(void);
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index 0a5c100d..e5a74fc4 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -88,6 +88,11 @@ typedef struct privilege_set_t {
     char *privilege;
 } ProfPrivilegeSet;
 
+typedef struct command_config_data_t {
+    char *sessionid;
+    char *command;
+} CommandConfigData;
+
 static int _iq_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata);
 
 static void _error_handler(xmpp_stanza_t *const stanza);
@@ -120,6 +125,8 @@ static int _caps_response_for_jid_id_handler(xmpp_stanza_t *const stanza, void *
 static int _caps_response_legacy_id_handler(xmpp_stanza_t *const stanza, void *const userdata);
 static int _auto_pong_id_handler(xmpp_stanza_t *const stanza, void *const userdata);
 static int _room_list_id_handler(xmpp_stanza_t *const stanza, void *const userdata);
+static int _command_list_result_handler(xmpp_stanza_t *const stanza, void *const userdata);
+static int _command_exec_response_handler(xmpp_stanza_t *const stanza, void *const userdata);
 
 static void _iq_free_room_data(ProfRoomInfoData *roominfo);
 static void _iq_free_affiliation_set(ProfPrivilegeSet *affiliation_set);
@@ -319,7 +326,7 @@ iq_room_list_request(gchar *conferencejid, gchar *filter)
 
     xmpp_ctx_t * const ctx = connection_get_ctx();
     char *id = connection_create_stanza_id("confreq");
-    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, id, conferencejid);
+    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, id, conferencejid, NULL);
 
     iq_id_handler_add(id, _room_list_id_handler, NULL, filter);
 
@@ -521,7 +528,7 @@ void
 iq_disco_items_request(gchar *jid)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, "discoitemsreq", jid);
+    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, "discoitemsreq", jid, NULL);
     iq_send_stanza(iq);
     xmpp_stanza_release(iq);
 }
@@ -530,7 +537,7 @@ void
 iq_disco_items_request_onconnect(gchar *jid)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, "discoitemsreq_onconnect", jid);
+    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, "discoitemsreq_onconnect", jid, NULL);
     iq_send_stanza(iq);
     xmpp_stanza_release(iq);
 }
@@ -584,10 +591,10 @@ iq_request_room_config_form(const char *const room_jid)
 }
 
 void
-iq_submit_room_config(const char *const room, DataForm *form)
+iq_submit_room_config(ProfConfWin *confwin)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *iq = stanza_create_room_config_submit_iq(ctx, room, form);
+    xmpp_stanza_t *iq = stanza_create_room_config_submit_iq(ctx, confwin->roomjid, confwin->form);
 
     const char *id = xmpp_stanza_get_id(iq);
     iq_id_handler_add(id, _room_config_submit_id_handler, NULL, NULL);
@@ -597,10 +604,10 @@ iq_submit_room_config(const char *const room, DataForm *form)
 }
 
 void
-iq_room_config_cancel(const char *const room_jid)
+iq_room_config_cancel(ProfConfWin *confwin)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *iq = stanza_create_room_config_cancel_iq(ctx, room_jid);
+    xmpp_stanza_t *iq = stanza_create_room_config_cancel_iq(ctx, confwin->roomjid);
     iq_send_stanza(iq);
     xmpp_stanza_release(iq);
 }
@@ -696,6 +703,62 @@ iq_send_ping(const char *const target)
     xmpp_stanza_release(iq);
 }
 
+void
+iq_command_list(const char *const target)
+{
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    const char *id = connection_create_stanza_id("cmdlist");
+    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, id, target, STANZA_NS_COMMAND);
+
+    iq_id_handler_add(id, _command_list_result_handler, NULL, NULL);
+
+    iq_send_stanza(iq);
+    xmpp_stanza_release(iq);
+}
+
+void
+iq_command_exec(const char *const target, const char *const command)
+{
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_stanza_t *iq = stanza_create_command_exec_iq(ctx, target, command);
+    const char *id = xmpp_stanza_get_id(iq);
+
+    iq_id_handler_add(id, _command_exec_response_handler, free, strdup(command));
+
+    iq_send_stanza(iq);
+    xmpp_stanza_release(iq);
+}
+
+void
+iq_submit_command_config(ProfConfWin *confwin)
+{
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    CommandConfigData *data = (CommandConfigData *)confwin->userdata;
+    xmpp_stanza_t *iq = stanza_create_command_config_submit_iq(ctx, confwin->roomjid, data->command, data->sessionid, confwin->form);
+
+    const char *id = xmpp_stanza_get_id(iq);
+    iq_id_handler_add(id,  _command_exec_response_handler, free, strdup(data->command));
+
+    iq_send_stanza(iq);
+    xmpp_stanza_release(iq);
+    free(data->sessionid);
+    free(data->command);
+    free(data);
+}
+
+void
+iq_cancel_command_config(ProfConfWin *confwin)
+{
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    CommandConfigData *data = (CommandConfigData *)confwin->userdata;
+    xmpp_stanza_t *iq = stanza_create_room_config_cancel_iq(ctx, confwin->roomjid);
+    iq_send_stanza(iq);
+    xmpp_stanza_release(iq);
+    free(data->sessionid);
+    free(data->command);
+    free(data);
+}
+
 static void
 _error_handler(xmpp_stanza_t *const stanza)
 {
@@ -1012,6 +1075,181 @@ _room_list_id_handler(xmpp_stanza_t *const stanza, void *const userdata)
 }
 
 static int
+_command_list_result_handler(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    const char *id = xmpp_stanza_get_id(stanza);
+    const char *type = xmpp_stanza_get_type(stanza);
+    char *from = strdup(xmpp_stanza_get_from(stanza));
+
+    if (id) {
+        log_debug("IQ command list result handler fired, id: %s.", id);
+    } else {
+        log_debug("IQ command list result handler fired.");
+    }
+
+    // handle error responses
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        char *error_message = stanza_get_error_message(stanza);
+        log_debug("Error retrieving command list for %s: %s", from, error_message);
+        ProfWin *win = wins_get_by_string(from);
+        if (win) {
+            win_command_list_error(win, error_message);
+        }
+        free(error_message);
+        free(from);
+        return 0;
+    }
+
+    GSList *cmds = NULL;
+
+    xmpp_stanza_t *query = xmpp_stanza_get_child_by_ns(stanza, XMPP_NS_DISCO_ITEMS);
+    if (query) {
+        xmpp_stanza_t *child = xmpp_stanza_get_children(query);
+        while (child) {
+            const char *name = xmpp_stanza_get_name(child);
+            if (g_strcmp0(name, "item") == 0) {
+                const char *node = xmpp_stanza_get_attribute(child, STANZA_ATTR_NODE);
+                if (node) {
+                    cmds = g_slist_insert_sorted(cmds, (gpointer)node, (GCompareFunc)g_strcmp0);
+                }
+            }
+            child = xmpp_stanza_get_next(child);
+        }
+    }
+
+    ProfWin *win = wins_get_by_string(from);
+    if (win == NULL) {
+	    win = wins_get_console();
+    }
+
+    win_handle_command_list(win, cmds);
+    g_slist_free(cmds);
+    free(from);
+
+    return 0;
+}
+
+static int
+_command_exec_response_handler(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    const char *id = xmpp_stanza_get_id(stanza);
+    const char *type = xmpp_stanza_get_type(stanza);
+    const char *from = xmpp_stanza_get_from(stanza);
+    char *command = userdata;
+
+    if (id) {
+        log_debug("IQ command exec response handler fired, id: %s.", id);
+    } else {
+        log_debug("IQ command exec response handler fired.");
+    }
+
+    ProfWin *win = wins_get_by_string(from);
+    if (win == NULL) {
+        /* No more window associated with this command.
+         * Fallback to console. */
+        win = wins_get_console();
+    }
+
+    // handle error responses
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        char *error_message = stanza_get_error_message(stanza);
+        log_debug("Error executing command %s for %s: %s", command, from, error_message);
+        win_command_exec_error(win, command, error_message);
+        free(error_message);
+        return 0;
+    }
+
+    xmpp_stanza_t *cmd = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_COMMAND);
+    if (!cmd) {
+        log_error("No command element for command response");
+        win_command_exec_error(win, command, "Malformed command response");
+        return 0;
+    }
+
+
+    const char *status = xmpp_stanza_get_attribute(cmd, STANZA_ATTR_STATUS);
+    if (g_strcmp0(status, "completed") == 0) {
+        win_handle_command_exec_status(win, command, "completed");
+        xmpp_stanza_t *note = xmpp_stanza_get_child_by_name(cmd, "note");
+        if (note) {
+            const char *type = xmpp_stanza_get_attribute(note, "type");
+            const char *value = xmpp_stanza_get_text(note);
+            win_handle_command_exec_result_note(win, type, value);
+        }
+        xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(cmd, STANZA_NS_DATA);
+        if (x) {
+            xmpp_stanza_t *roster = xmpp_stanza_get_child_by_ns(x, XMPP_NS_ROSTER);
+            if (roster) {
+                /* Special handling of xep-0133 roster in response */
+                GSList *list = NULL;
+                xmpp_stanza_t *child = xmpp_stanza_get_children(roster);
+                while (child) {
+                    const char *barejid = xmpp_stanza_get_attribute(child, STANZA_ATTR_JID);
+                    gchar *barejid_lower = g_utf8_strdown(barejid, -1);
+                    const char *name = xmpp_stanza_get_attribute(child, STANZA_ATTR_NAME);
+                    const char *sub = xmpp_stanza_get_attribute(child, STANZA_ATTR_SUBSCRIPTION);
+                    const char *ask = xmpp_stanza_get_attribute(child, STANZA_ATTR_ASK);
+
+                    GSList *groups = NULL;
+                    groups = roster_get_groups_from_item(child);
+
+                    gboolean pending_out = FALSE;
+                    if (ask && (strcmp(ask, "subscribe") == 0)) {
+                        pending_out = TRUE;
+                    }
+
+                    PContact contact = p_contact_new(barejid_lower, name, groups, sub, NULL, pending_out);
+                    list = g_slist_insert_sorted(list, contact, (GCompareFunc)roster_compare_name);
+                    child = xmpp_stanza_get_next(child);
+                }
+
+                cons_show_roster(list);
+                g_slist_free(list);
+            } else {
+                DataForm *form = form_create(x);
+                ProfConfWin *confwin = (ProfConfWin*)wins_new_config(from, form, NULL, NULL, NULL);
+                confwin_handle_configuration(confwin, form);
+            }
+        }
+    } else if (g_strcmp0(status, "executing") == 0) {
+        win_handle_command_exec_status(win, command, "executing");
+
+        /* Looking for a jabber:x:data type form */
+        xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(cmd, STANZA_NS_DATA);
+        if (x == NULL) {
+            return 0;
+        }
+
+        const char *form_type = xmpp_stanza_get_type(x);
+        if (g_strcmp0(form_type, "form") != 0) {
+            log_error("Unsupported payload in command response");
+            win_command_exec_error(win, command, "Unsupported command response");
+            return 0;
+        }
+
+        DataForm *form = form_create(x);
+        CommandConfigData *data = malloc(sizeof(CommandConfigData));
+        data->sessionid = strdup(xmpp_stanza_get_attribute(cmd, "sessionid"));
+        data->command = command;
+        ProfConfWin *confwin = (ProfConfWin*)wins_new_config(from, form, iq_submit_command_config, iq_cancel_command_config, data);
+        confwin_handle_configuration(confwin, form);
+    } else if (g_strcmp0(status, "canceled") == 0) {
+        win_handle_command_exec_status(win, command, "canceled");
+        xmpp_stanza_t *note = xmpp_stanza_get_child_by_name(cmd, "note");
+        if (note) {
+            const char *type = xmpp_stanza_get_attribute(note, "type");
+            const char *value = xmpp_stanza_get_text(note);
+            win_handle_command_exec_result_note(win, type, value);
+        }
+    } else {
+        log_error("Unsupported command status %s", status);
+        win_command_exec_error(win, command, "Malformed command response");
+    }
+
+    return 0;
+}
+
+static int
 _enable_carbons_id_handler(xmpp_stanza_t *const stanza, void *const userdata)
 {
     const char *type = xmpp_stanza_get_type(stanza);
@@ -1540,8 +1778,8 @@ _room_config_id_handler(xmpp_stanza_t *const stanza, void *const userdata)
     }
 
     DataForm *form = form_create(x);
-    ProfMucConfWin *confwin = (ProfMucConfWin*)wins_new_muc_config(from, form);
-    mucconfwin_handle_configuration(confwin, form);
+    ProfConfWin *confwin = (ProfConfWin*)wins_new_config(from, form, iq_submit_room_config, iq_room_config_cancel, NULL);
+    confwin_handle_configuration(confwin, form);
 
     return 0;
 }
diff --git a/src/xmpp/roster.c b/src/xmpp/roster.c
index 4fa70052..994269ec 100644
--- a/src/xmpp/roster.c
+++ b/src/xmpp/roster.c
@@ -75,9 +75,6 @@ static int _group_add_id_handler(xmpp_stanza_t *const stanza, void *const userda
 static int _group_remove_id_handler(xmpp_stanza_t *const stanza, void *const userdata);
 static void _free_group_data(GroupData *data);
 
-// helper functions
-GSList* _get_groups_from_item(xmpp_stanza_t *item);
-
 void
 roster_request(void)
 {
@@ -254,7 +251,7 @@ roster_set_handler(xmpp_stanza_t *const stanza)
             pending_out = TRUE;
         }
 
-        GSList *groups = _get_groups_from_item(item);
+        GSList *groups = roster_get_groups_from_item(item);
 
         // update the local roster
         PContact contact = roster_get_contact(barejid_lower);
@@ -301,7 +298,7 @@ roster_result_handler(xmpp_stanza_t *const stanza)
             pending_out = TRUE;
         }
 
-        GSList *groups = _get_groups_from_item(item);
+        GSList *groups = roster_get_groups_from_item(item);
 
         gboolean added = roster_add(barejid_lower, name, groups, sub, pending_out);
         if (!added) {
@@ -318,7 +315,7 @@ roster_result_handler(xmpp_stanza_t *const stanza)
 }
 
 GSList*
-_get_groups_from_item(xmpp_stanza_t *item)
+roster_get_groups_from_item(xmpp_stanza_t *item)
 {
     GSList *groups = NULL;
     xmpp_stanza_t *group_element = xmpp_stanza_get_children(item);
diff --git a/src/xmpp/roster.h b/src/xmpp/roster.h
index 15614377..be710561 100644
--- a/src/xmpp/roster.h
+++ b/src/xmpp/roster.h
@@ -38,5 +38,6 @@
 void roster_request(void);
 void roster_set_handler(xmpp_stanza_t *const stanza);
 void roster_result_handler(xmpp_stanza_t *const stanza);
+GSList* roster_get_groups_from_item(xmpp_stanza_t *const item);
 
 #endif
diff --git a/src/xmpp/roster_list.c b/src/xmpp/roster_list.c
index a2c5653d..71c86ff5 100644
--- a/src/xmpp/roster_list.c
+++ b/src/xmpp/roster_list.c
@@ -73,8 +73,6 @@ static gboolean _key_equals(void *key1, void *key2);
 static gboolean _datetimes_equal(GDateTime *dt1, GDateTime *dt2);
 static void _replace_name(const char *const current_name, const char *const new_name, const char *const barejid);
 static void _add_name_and_barejid(const char *const name, const char *const barejid);
-static gint _compare_name(PContact a, PContact b);
-static gint _compare_presence(PContact a, PContact b);
 
 void
 roster_create(void)
@@ -397,7 +395,7 @@ roster_get_contacts_by_presence(const char *const presence)
     while (g_hash_table_iter_next(&iter, &key, &value)) {
         PContact contact = (PContact)value;
         if (g_strcmp0(p_contact_presence(contact), presence) == 0) {
-            result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_name);
+            result = g_slist_insert_sorted(result, value, (GCompareFunc)roster_compare_name);
         }
     }
 
@@ -417,9 +415,9 @@ roster_get_contacts(roster_ord_t order)
 
     GCompareFunc cmp_func;
     if (order == ROSTER_ORD_PRESENCE) {
-        cmp_func = (GCompareFunc) _compare_presence;
+        cmp_func = (GCompareFunc) roster_compare_presence;
     } else {
-        cmp_func = (GCompareFunc) _compare_name;
+        cmp_func = (GCompareFunc) roster_compare_name;
     }
 
     g_hash_table_iter_init(&iter, roster->contacts);
@@ -444,7 +442,7 @@ roster_get_contacts_online(void)
     g_hash_table_iter_init(&iter, roster->contacts);
     while (g_hash_table_iter_next(&iter, &key, &value)) {
         if(strcmp(p_contact_presence(value), "offline"))
-            result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_name);
+            result = g_slist_insert_sorted(result, value, (GCompareFunc)roster_compare_name);
     }
 
     // return all contact structs
@@ -499,9 +497,9 @@ roster_get_group(const char *const group, roster_ord_t order)
 
     GCompareFunc cmp_func;
     if (order == ROSTER_ORD_PRESENCE) {
-        cmp_func = (GCompareFunc) _compare_presence;
+        cmp_func = (GCompareFunc) roster_compare_presence;
     } else {
-        cmp_func = (GCompareFunc) _compare_name;
+        cmp_func = (GCompareFunc) roster_compare_name;
     }
 
     g_hash_table_iter_init(&iter, roster->contacts);
@@ -605,8 +603,8 @@ _add_name_and_barejid(const char *const name, const char *const barejid)
     }
 }
 
-static gint
-_compare_name(PContact a, PContact b)
+gint
+roster_compare_name(PContact a, PContact b)
 {
     const char * utf8_str_a = NULL;
     const char * utf8_str_b = NULL;
@@ -645,8 +643,8 @@ _get_presence_weight(const char *presence)
     }
 }
 
-static gint
-_compare_presence(PContact a, PContact b)
+gint
+roster_compare_presence(PContact a, PContact b)
 {
     const char *presence_a = p_contact_presence(a);
     const char *presence_b = p_contact_presence(b);
@@ -663,6 +661,6 @@ _compare_presence(PContact a, PContact b)
 
     // otherwise order by name
     } else {
-        return _compare_name(a, b);
+        return roster_compare_name(a, b);
     }
 }
diff --git a/src/xmpp/roster_list.h b/src/xmpp/roster_list.h
index a0b01625..b7484c04 100644
--- a/src/xmpp/roster_list.h
+++ b/src/xmpp/roster_list.h
@@ -70,5 +70,7 @@ char* roster_group_autocomplete(const char *const search_str, gboolean previous)
 char* roster_barejid_autocomplete(const char *const search_str, gboolean previous);
 GSList* roster_get_contacts_by_presence(const char *const presence);
 char* roster_get_msg_display_name(const char *const barejid, const char *const resource);
+gint roster_compare_name(PContact a, PContact b);
+gint roster_compare_presence(PContact a, PContact b);
 
 #endif
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index ed13b976..c44433ef 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -926,7 +926,7 @@ stanza_create_disco_info_iq(xmpp_ctx_t *ctx, const char *const id, const char *c
 
 xmpp_stanza_t*
 stanza_create_disco_items_iq(xmpp_ctx_t *ctx, const char *const id,
-    const char *const jid)
+    const char *const jid, const char *const node)
 {
     xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_GET, id);
     xmpp_stanza_set_to(iq, jid);
@@ -934,6 +934,9 @@ stanza_create_disco_items_iq(xmpp_ctx_t *ctx, const char *const id,
     xmpp_stanza_t *query = xmpp_stanza_new(ctx);
     xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
     xmpp_stanza_set_ns(query, XMPP_NS_DISCO_ITEMS);
+    if (node) {
+        xmpp_stanza_set_attribute(query, STANZA_ATTR_NODE, node);
+    }
 
     xmpp_stanza_add_child(iq, query);
     xmpp_stanza_release(query);
@@ -2040,6 +2043,53 @@ stanza_parse_presence(xmpp_stanza_t *stanza, int *err)
     return result;
 }
 
+xmpp_stanza_t*
+stanza_create_command_exec_iq(xmpp_ctx_t *ctx, const char *const target,
+    const char *const node)
+{
+    char *id = connection_create_stanza_id("cmdexec");
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+    free(id);
+    xmpp_stanza_set_to(iq, target);
+
+    xmpp_stanza_t *command = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(command, STANZA_NAME_COMMAND);
+
+    xmpp_stanza_set_ns(command, STANZA_NS_COMMAND);
+    xmpp_stanza_set_attribute(command, "node", node);
+    xmpp_stanza_set_attribute(command, "action", "execute");
+
+    xmpp_stanza_add_child(iq, command);
+    xmpp_stanza_release(command);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_command_config_submit_iq(xmpp_ctx_t *ctx, const char *const room,
+    const char *const node, const char *const sessionid, DataForm *form)
+{
+    char *id = connection_create_stanza_id("commandconf_submit");
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+    free(id);
+    xmpp_stanza_set_to(iq, room);
+
+    xmpp_stanza_t *command = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(command, STANZA_NAME_COMMAND);
+    xmpp_stanza_set_ns(command, STANZA_NS_COMMAND);
+    xmpp_stanza_set_attribute(command, "node", node);
+    xmpp_stanza_set_attribute(command, "sessionid", sessionid);
+
+    xmpp_stanza_t *x = form_create_submission(form);
+    xmpp_stanza_add_child(command, x);
+    xmpp_stanza_release(x);
+
+    xmpp_stanza_add_child(iq, command);
+    xmpp_stanza_release(command);
+
+    return iq;
+}
+
 static void
 _stanza_add_unique_id(xmpp_stanza_t *stanza, char *prefix)
 {
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index bd161616..696f60da 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -99,6 +99,7 @@
 #define STANZA_NAME_PUT "put"
 #define STANZA_NAME_GET "get"
 #define STANZA_NAME_URL "url"
+#define STANZA_NAME_COMMAND "command"
 
 // error conditions
 #define STANZA_NAME_BAD_REQUEST "bad-request"
@@ -156,6 +157,7 @@
 #define STANZA_ATTR_REASON "reason"
 #define STANZA_ATTR_AUTOJOIN "autojoin"
 #define STANZA_ATTR_PASSWORD "password"
+#define STANZA_ATTR_STATUS "status"
 
 #define STANZA_TEXT_AWAY "away"
 #define STANZA_TEXT_DND "dnd"
@@ -186,6 +188,7 @@
 #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_NS_COMMAND "http://jabber.org/protocol/commands"
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"
 
@@ -278,6 +281,9 @@ xmpp_stanza_t* stanza_create_room_subject_message(xmpp_ctx_t *ctx, const char *c
 xmpp_stanza_t* stanza_create_room_kick_iq(xmpp_ctx_t *const ctx, const char *const room, const char *const nick,
     const char *const reason);
 
+xmpp_stanza_t* stanza_create_command_exec_iq(xmpp_ctx_t *ctx, const char *const target, const char *const node);
+xmpp_stanza_t* stanza_create_command_config_submit_iq(xmpp_ctx_t *ctx, const char *const room, const char *const node, const char *const sessionid, DataForm *form);
+
 int stanza_get_idle_time(xmpp_stanza_t *const stanza);
 
 void stanza_attach_priority(xmpp_ctx_t *const ctx, xmpp_stanza_t *const presence, const int pri);
@@ -292,7 +298,7 @@ EntityCapabilities* stanza_create_caps_from_query_element(xmpp_stanza_t *query);
 
 const char* stanza_get_presence_string_from_type(resource_presence_t presence_type);
 xmpp_stanza_t* stanza_create_software_version_iq(xmpp_ctx_t *ctx, const char *const fulljid);
-xmpp_stanza_t* stanza_create_disco_items_iq(xmpp_ctx_t *ctx, const char *const id, const char *const jid);
+xmpp_stanza_t* stanza_create_disco_items_iq(xmpp_ctx_t *ctx, const char *const id, const char *const jid, const char *const node);
 
 char* stanza_get_status(xmpp_stanza_t *stanza, char *def);
 char* stanza_get_show(xmpp_stanza_t *stanza, char *def);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index d4a29196..44acd97e 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -60,6 +60,7 @@
 #define XMPP_FEATURE_RECEIPTS "urn:xmpp:receipts"
 #define XMPP_FEATURE_LASTACTIVITY "jabber:iq:last"
 #define XMPP_FEATURE_MUC "http://jabber.org/protocol/muc"
+#define XMPP_FEATURE_COMMANDS "http://jabber.org/protocol/commands"
 
 typedef enum {
     JABBER_CONNECTING,
@@ -170,8 +171,8 @@ void iq_set_autoping(int seconds);
 void iq_confirm_instant_room(const char *const room_jid);
 void iq_destroy_room(const char *const room_jid);
 void iq_request_room_config_form(const char *const room_jid);
-void iq_submit_room_config(const char *const room, DataForm *form);
-void iq_room_config_cancel(const char *const room_jid);
+void iq_submit_room_config(ProfConfWin *confwin);
+void iq_room_config_cancel(ProfConfWin *confwin);
 void iq_send_ping(const char *const target);
 void iq_room_info_request(const char *const room, gboolean display_result);
 void iq_room_affiliation_list(const char *const room, char *affiliation);
@@ -182,6 +183,8 @@ void iq_room_role_set(const char *const room, const char *const nick, char *role
 void iq_room_role_list(const char * const room, char *role);
 void iq_autoping_check(void);
 void iq_http_upload_request(HTTPUpload *upload);
+void iq_command_list(const char *const target);
+void iq_command_exec(const char *const target, const char *const command);
 
 EntityCapabilities* caps_lookup(const char *const jid);
 void caps_close(void);