From f5132b68a24cc49483df057fd4bf15dc2062ccc4 Mon Sep 17 00:00:00 2001
From: Paul Fariello <paul@fariello.eu>
Date: Wed, 5 Nov 2014 23:54:13 +0100
Subject: [PATCH 04/44] Add muc admin command

---
 src/core/Makefile              |   2 +
 src/core/xep/muc-affiliation.c |  51 ++++++++++++++++
 src/core/xep/muc-affiliation.h |  17 ++++++
 src/core/xep/muc-commands.c    | 109 ++++++++++++++++++++++++++++++++++
 src/core/xep/muc-events.c      |  45 +++++++++++++-
 src/core/xep/muc-nicklist.c    |  68 +++-------------------
 src/core/xep/muc-nicklist.h    |  20 +------
 src/core/xep/muc-role.c        |  45 ++++++++++++++
 src/core/xep/muc-role.h        |  16 +++++
 src/core/xep/muc.c             | 129 ++++++++++++++++++++++++++++++++++++++++-
 src/core/xep/muc.h             |   9 +++
 src/fe-common/xep/fe-muc.c     |  51 +++++++++++++---
 12 files changed, 473 insertions(+), 89 deletions(-)
 create mode 100644 src/core/xep/muc-affiliation.c
 create mode 100644 src/core/xep/muc-affiliation.h
 create mode 100644 src/core/xep/muc-role.c
 create mode 100644 src/core/xep/muc-role.h

--- a/src/core/Makefile
+++ b/src/core/Makefile
@@ -18,10 +18,12 @@
 	xep/datetime.c \
 	xep/delay.c \
 	xep/disco.c \
+	xep/muc-affiliation.c \
 	xep/muc-commands.c \
 	xep/muc-events.c \
 	xep/muc-nicklist.c \
 	xep/muc-reconnect.c \
+	xep/muc-role.c \
 	xep/muc.c \
 	xep/oob.c \
 	xep/ping.c \
--- /dev/null
+++ b/src/core/xep/muc-affiliation.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 Colin DIDIER
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+
+#include "module.h"
+
+#include "muc-affiliation.h"
+
+const char *xmpp_affiliation[] = {
+	"none",
+	"owner",
+	"admin",
+	"member",
+	"outcast",
+	NULL
+};
+
+int
+xmpp_nicklist_get_affiliation(const char *affiliation)
+{
+	if (affiliation != NULL) {
+		if (g_ascii_strcasecmp(affiliation,
+		    xmpp_affiliation[XMPP_AFFILIATION_OWNER]) == 0)
+			return XMPP_AFFILIATION_OWNER;
+		else if (g_ascii_strcasecmp(affiliation,
+		    xmpp_affiliation[XMPP_AFFILIATION_ADMIN]) == 0)
+			return XMPP_AFFILIATION_ADMIN;
+		else if (g_ascii_strcasecmp(affiliation,
+		    xmpp_affiliation[XMPP_AFFILIATION_MEMBER]) == 0)
+			return XMPP_AFFILIATION_MEMBER;
+		else if (g_ascii_strcasecmp(affiliation,
+		    xmpp_affiliation[XMPP_AFFILIATION_OUTCAST]) == 0)
+			return XMPP_AFFILIATION_OUTCAST;
+	}
+	return XMPP_AFFILIATION_NONE;
+}
--- /dev/null
+++ b/src/core/xep/muc-affiliation.h
@@ -0,0 +1,17 @@
+#ifndef __MUC_AFFILIATION_H
+#define __MUC_AFFILIATION_H
+
+enum {
+	XMPP_AFFILIATION_NONE,
+	XMPP_AFFILIATION_OWNER,
+	XMPP_AFFILIATION_ADMIN,
+	XMPP_AFFILIATION_MEMBER,
+	XMPP_AFFILIATION_OUTCAST
+};
+extern const char *xmpp_affiliation[];
+
+__BEGIN_DECLS
+int		 xmpp_nicklist_get_affiliation(const char *);
+__END_DECLS
+
+#endif
--- a/src/core/xep/muc-commands.c
+++ b/src/core/xep/muc-commands.c
@@ -149,6 +149,107 @@
 	cmd_params_free(free_arg);
 }
 
+/* SYNTAX: AFFILIATION [<channel>] <type> [<jid>] [<reason>] */
+static void
+cmd_affiliation(const char *data, XMPP_SERVER_REC *server, WI_ITEM_REC *item)
+{
+	MUC_REC *channel;
+	LmMessage *lmsg;
+	char *channame, *type, *jid, *reason;
+	void *free_arg;
+
+	CMD_XMPP_SERVER(server);
+	if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTCHAN |
+	    PARAM_FLAG_GETREST, item, &channame, &type, &jid, &reason))
+		return;
+	if (*channame == '\0' || *type == '\0')
+		cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+	if ((channel = muc_find(server, channame)) == NULL)
+		cmd_param_error(CMDERR_NOT_JOINED);
+	if (*jid == '\0') {
+		muc_get_affiliation(server, channel, type);
+	} else {
+		if (*reason == '\0')
+			reason = NULL;
+		muc_set_affiliation(server, channel, type, jid, reason);
+	}
+	cmd_params_free(free_arg);
+}
+
+/* SYNTAX: BAN [<channel>] <jid> [<reason>] */
+static void
+cmd_ban(const char *data, XMPP_SERVER_REC *server, WI_ITEM_REC *item)
+{
+	MUC_REC *channel;
+	LmMessage *lmsg;
+	char *channame, *jid, *reason;
+	void *free_arg;
+
+	CMD_XMPP_SERVER(server);
+	if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTCHAN |
+	    PARAM_FLAG_GETREST, item, &channame, &jid, &reason))
+		return;
+	if (*channame == '\0' || *jid == '\0')
+		cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+	if ((channel = muc_find(server, channame)) == NULL)
+		cmd_param_error(CMDERR_NOT_JOINED);
+	if (*reason == '\0')
+		reason = NULL;
+
+	muc_set_affiliation(server, channel, "outcast", jid, reason);
+	cmd_params_free(free_arg);
+}
+
+/* SYNTAX: ROLE [<channel>] <type> [<nick>] [<reason>]*/
+static void
+cmd_role(const char *data, XMPP_SERVER_REC *server, WI_ITEM_REC *item)
+{
+	MUC_REC *channel;
+	LmMessage *lmsg;
+	char *channame, *type, *nick, *reason;
+	void *free_arg;
+
+	CMD_XMPP_SERVER(server);
+	if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTCHAN |
+	    PARAM_FLAG_GETREST, item, &channame, &type, &nick, &reason))
+		return;
+	if (*channame == '\0' || *type == '\0')
+		cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+	if ((channel = muc_find(server, channame)) == NULL)
+		cmd_param_error(CMDERR_NOT_JOINED);
+	if (*nick == '\0') {
+		muc_get_role(server, channel, type);
+	} else {
+		if (*reason == '\0')
+			reason = NULL;
+		muc_set_role(server, channel, type, nick, reason);
+	}
+	cmd_params_free(free_arg);
+}
+
+/* SYNTAX: kick [<channel>] <nick> [<reason>]*/
+static void
+cmd_kick(const char *data, XMPP_SERVER_REC *server, WI_ITEM_REC *item)
+{
+	MUC_REC *channel;
+	LmMessage *lmsg;
+	char *channame, *nick, *reason;
+	void *free_arg;
+
+	CMD_XMPP_SERVER(server);
+	if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTCHAN |
+	    PARAM_FLAG_GETREST, item, &channame, &nick, &reason))
+		return;
+	if (*channame == '\0' || *nick == '\0')
+		cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+	if ((channel = muc_find(server, channame)) == NULL)
+		cmd_param_error(CMDERR_NOT_JOINED);
+	if (*reason == '\0')
+		reason = NULL;
+	muc_set_role(server, channel, "none", nick, reason);
+	cmd_params_free(free_arg);
+}
+
 /* SYNTAX: DESTROY [<channel>] [<alternate>] [<reason>]*/
 static void
 cmd_destroy(const char *data, XMPP_SERVER_REC *server, WI_ITEM_REC *item)
@@ -180,6 +281,10 @@
 	command_bind_xmpp("part", NULL, (SIGNAL_FUNC)cmd_part);
 	command_bind_xmpp("nick", NULL, (SIGNAL_FUNC)cmd_nick);
 	command_bind_xmpp("topic", NULL, (SIGNAL_FUNC)cmd_topic);
+	command_bind_xmpp("affiliation", NULL, (SIGNAL_FUNC)cmd_affiliation);
+	command_bind_xmpp("ban", NULL, (SIGNAL_FUNC)cmd_ban);
+	command_bind_xmpp("role", NULL, (SIGNAL_FUNC)cmd_role);
+	command_bind_xmpp("kick", NULL, (SIGNAL_FUNC)cmd_kick);
 	command_bind_xmpp("destroy", NULL, (SIGNAL_FUNC)cmd_destroy);
 }
 
@@ -190,5 +295,9 @@
 	command_unbind("part", (SIGNAL_FUNC)cmd_part);
 	command_unbind("nick", (SIGNAL_FUNC)cmd_nick);
 	command_unbind("topic", (SIGNAL_FUNC)cmd_topic);
+	command_unbind("affiliation", (SIGNAL_FUNC)cmd_affiliation);
+	command_unbind("ban", (SIGNAL_FUNC)cmd_ban);
+	command_unbind("role", (SIGNAL_FUNC)cmd_role);
+	command_unbind("kick", (SIGNAL_FUNC)cmd_kick);
 	command_unbind("destroy", (SIGNAL_FUNC)cmd_destroy);
 }
--- a/src/core/xep/muc-events.c
+++ b/src/core/xep/muc-events.c
@@ -33,6 +33,8 @@
 #include "disco.h"
 #include "muc.h"
 #include "muc-nicklist.h"
+#include "muc-affiliation.h"
+#include "muc-role.h"
 
 #define MAX_LONG_STRLEN ((sizeof(long) * CHAR_BIT + 2) / 3 + 1)
 
@@ -164,7 +166,7 @@
 
 	if ((nick = xmpp_nicklist_find(channel, nickname)) == NULL)
 		nick_join(channel, nickname, full_jid, affiliation, role);
-	else 
+	else
 		nick_mode(channel, nick, affiliation, role);
 }
 
@@ -414,6 +416,42 @@
 }
 
 static void
+admin(MUC_REC *channel, LmMessage *lmsg)
+{
+	LmMessageNode *node, *query;
+	const char *item_affiliation, *item_role, *item_jid, *item_nick;
+	int affiliation, role;
+
+	if ((query = lm_find_node(lmsg->node, "query", XMLNS,
+	    XMLNS_MUC_ADMIN)) == NULL)
+		return;
+
+	for (node = query->children; node != NULL; node = node->next) {
+		/* <item affiliation='item_affiliation'
+		 *     role='item_role'
+		 *     jid='item_jid'
+		 *     nick='item_nick'/> */
+		item_jid = xmpp_recode_in(lm_message_node_get_attribute(node,
+		    "jid"));
+		item_affiliation = lm_message_node_get_attribute(node,
+		    "affiliation");
+		item_nick = xmpp_recode_in(lm_message_node_get_attribute(node,
+		    "nick"));
+		item_role = lm_message_node_get_attribute(node, "role");
+		affiliation = xmpp_nicklist_get_affiliation(item_affiliation);
+		if (item_role == NULL) {
+			/* affiliation query */
+			signal_emit("message xmpp muc affiliation", 4, channel,
+			    item_jid, item_nick, affiliation);
+		} else {
+			role = xmpp_nicklist_get_role(item_role);
+			signal_emit("message xmpp muc mode", 4, channel,
+			    item_nick, affiliation, role);
+		}
+	}
+}
+
+static void
 invite(XMPP_SERVER_REC *server, const char *channame, LmMessageNode *node,
     LmMessageNode *invite_node)
 {
@@ -581,6 +619,9 @@
 			}
 		}
 		break;
+	case LM_MESSAGE_SUB_TYPE_RESULT:
+		admin(channel, lmsg);
+		break;
 	}
 
 out:
--- a/src/core/xep/muc-nicklist.c
+++ b/src/core/xep/muc-nicklist.c
@@ -21,24 +21,9 @@
 #include "signals.h"
 
 #include "rosters.h"
+#include "muc-affiliation.h"
 #include "muc-nicklist.h"
-
-const char *xmpp_nicklist_affiliation[] = {
-	"none",
-	"owner",
-	"admin",
-	"member",
-	"outcast",
-	NULL
-};
-
-const char *xmpp_nicklist_role[] = {
-	"none",
-	"moderator",
-	"participant",
-	"visitor",
-	NULL
-};
+#include "muc-role.h"
 
 XMPP_NICK_REC *
 xmpp_nicklist_insert(MUC_REC *channel, const char *nickname,
@@ -54,8 +39,8 @@
 	    g_strdup(full_jid) : g_strconcat(channel->name, "/", rec->nick, (void *)NULL);
 	rec->show = XMPP_PRESENCE_AVAILABLE;
 	rec->status = NULL;
-	rec->affiliation = XMPP_NICKLIST_AFFILIATION_NONE;
-	rec->role = XMPP_NICKLIST_ROLE_NONE;
+	rec->affiliation = XMPP_AFFILIATION_NONE;
+	rec->role = XMPP_ROLE_NONE;
 	nicklist_insert(CHANNEL(channel), (NICK_REC *)rec);
 	return rec;
 }
@@ -123,43 +108,6 @@
 	}
 }
 
-int
-xmpp_nicklist_get_affiliation(const char *affiliation)
-{
-	if (affiliation != NULL) {
-		if (g_ascii_strcasecmp(affiliation,
-		    xmpp_nicklist_affiliation[XMPP_NICKLIST_AFFILIATION_OWNER]) == 0)
-			return XMPP_NICKLIST_AFFILIATION_OWNER;
-		else if (g_ascii_strcasecmp(affiliation,
-		    xmpp_nicklist_affiliation[XMPP_NICKLIST_AFFILIATION_ADMIN]) == 0)
-			return XMPP_NICKLIST_AFFILIATION_ADMIN;
-		else if (g_ascii_strcasecmp(affiliation,
-		    xmpp_nicklist_affiliation[XMPP_NICKLIST_AFFILIATION_MEMBER]) == 0)
-			return XMPP_NICKLIST_AFFILIATION_MEMBER;
-		else if (g_ascii_strcasecmp(affiliation,
-		    xmpp_nicklist_affiliation[XMPP_NICKLIST_AFFILIATION_OUTCAST]) == 0)
-			return XMPP_NICKLIST_AFFILIATION_OUTCAST;
-	}
-	return XMPP_NICKLIST_AFFILIATION_NONE;
-}
-
-int
-xmpp_nicklist_get_role(const char *role)
-{
-	if (role != NULL) {
-		if (g_ascii_strcasecmp(role,
-		    xmpp_nicklist_role[XMPP_NICKLIST_ROLE_MODERATOR]) == 0)
-			return XMPP_NICKLIST_ROLE_MODERATOR;
-		else if (g_ascii_strcasecmp(role,
-		    xmpp_nicklist_role[XMPP_NICKLIST_ROLE_PARTICIPANT]) == 0)
-			return XMPP_NICKLIST_ROLE_PARTICIPANT;
-		else if (g_ascii_strcasecmp(role,
-		    xmpp_nicklist_role[XMPP_NICKLIST_ROLE_VISITOR]) == 0)
-			return XMPP_NICKLIST_ROLE_VISITOR;
-	}
-	return XMPP_NICKLIST_ROLE_NONE;
-}
-
 gboolean
 xmpp_nicklist_modes_changed(XMPP_NICK_REC *nick, int affiliation, int role)
 {
@@ -175,12 +123,12 @@
 	nick->affiliation = affiliation;
 	nick->role = role;
 	switch (affiliation) {
-	case XMPP_NICKLIST_AFFILIATION_OWNER:
+	case XMPP_AFFILIATION_OWNER:
 		nick->prefixes[0] = '&';
 		nick->prefixes[1] = '\0';
 		nick->op = TRUE;
 		break;
-	case XMPP_NICKLIST_AFFILIATION_ADMIN:
+	case XMPP_AFFILIATION_ADMIN:
 		nick->prefixes[0] = '\0';
 		nick->op = TRUE;
 		break;
@@ -189,11 +137,11 @@
 		nick->op = FALSE;
 	}
 	switch (role) {
-	case XMPP_NICKLIST_ROLE_MODERATOR:
+	case XMPP_ROLE_MODERATOR:
 		nick->voice = TRUE;
 		nick->halfop = TRUE;
 		break;
-	case XMPP_NICKLIST_ROLE_PARTICIPANT:
+	case XMPP_ROLE_PARTICIPANT:
 		nick->halfop = FALSE;
 		nick->voice = TRUE;
 		break;
--- a/src/core/xep/muc-nicklist.h
+++ b/src/core/xep/muc-nicklist.h
@@ -14,6 +14,7 @@
 #define xmpp_nicklist_find(channel, name) 			\
 	XMPP_NICK(nicklist_find(CHANNEL(channel), name))
 
+
 struct _XMPP_NICK_REC {
 	#include "nick-rec.h"
 
@@ -24,29 +25,10 @@
 	int 	 role;
 };
 
-enum {
-	XMPP_NICKLIST_AFFILIATION_NONE,
-	XMPP_NICKLIST_AFFILIATION_OWNER,
-	XMPP_NICKLIST_AFFILIATION_ADMIN,
-	XMPP_NICKLIST_AFFILIATION_MEMBER,
-	XMPP_NICKLIST_AFFILIATION_OUTCAST
-};
-extern const char *xmpp_nicklist_affiliation[];
-
-enum {
-	XMPP_NICKLIST_ROLE_NONE,
-	XMPP_NICKLIST_ROLE_MODERATOR,
-	XMPP_NICKLIST_ROLE_PARTICIPANT,
-	XMPP_NICKLIST_ROLE_VISITOR
-};
-extern const char *xmpp_nicklist_role[];
-
 __BEGIN_DECLS
 XMPP_NICK_REC	*xmpp_nicklist_insert(MUC_REC *, const char *, const char *);
 void		 xmpp_nicklist_rename(MUC_REC *, XMPP_NICK_REC *, const char *,
 		     const char *);
-int		 xmpp_nicklist_get_affiliation(const char *);
-int		 xmpp_nicklist_get_role(const char *);
 gboolean	 xmpp_nicklist_modes_changed(XMPP_NICK_REC *, int, int);
 void		 xmpp_nicklist_set_modes(XMPP_NICK_REC *, int, int);
 void		 xmpp_nicklist_set_presence(XMPP_NICK_REC *, int,
--- /dev/null
+++ b/src/core/xep/muc-role.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2007 Colin DIDIER
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+
+#include "muc-role.h"
+
+const char *xmpp_role[] = {
+	"none",
+	"moderator",
+	"participant",
+	"visitor",
+	NULL
+};
+
+int
+xmpp_nicklist_get_role(const char *role)
+{
+	if (role != NULL) {
+		if (g_ascii_strcasecmp(role,
+		    xmpp_role[XMPP_ROLE_MODERATOR]) == 0)
+			return XMPP_ROLE_MODERATOR;
+		else if (g_ascii_strcasecmp(role,
+		    xmpp_role[XMPP_ROLE_PARTICIPANT]) == 0)
+			return XMPP_ROLE_PARTICIPANT;
+		else if (g_ascii_strcasecmp(role,
+		    xmpp_role[XMPP_ROLE_VISITOR]) == 0)
+			return XMPP_ROLE_VISITOR;
+	}
+	return XMPP_ROLE_NONE;
+}
--- /dev/null
+++ b/src/core/xep/muc-role.h
@@ -0,0 +1,16 @@
+#ifndef __MUC_ROLE_H
+#define __MUC_ROLE_H
+
+enum {
+	XMPP_ROLE_NONE,
+	XMPP_ROLE_MODERATOR,
+	XMPP_ROLE_PARTICIPANT,
+	XMPP_ROLE_VISITOR
+};
+extern const char *xmpp_role[];
+
+__BEGIN_DECLS
+int		 xmpp_nicklist_get_role(const char *);
+__END_DECLS
+
+#endif
--- a/src/core/xep/muc.c
+++ b/src/core/xep/muc.c
@@ -33,6 +33,8 @@
 #include "muc-commands.h"
 #include "muc-events.h"
 #include "muc-nicklist.h"
+#include "muc-affiliation.h"
+#include "muc-role.h"
 #include "muc-reconnect.h"
 
 static char *
@@ -187,6 +189,132 @@
 }
 
 void
+muc_get_affiliation(XMPP_SERVER_REC *server, MUC_REC *channel, const char *type)
+{
+	LmMessage *lmsg;
+	LmMessageNode *query, *item;
+	char *recoded, *str;
+
+	g_return_if_fail(IS_MUC(channel));
+	g_return_if_fail(IS_XMPP_SERVER(server));
+	if (!channel->server->connected)
+		return;
+
+	lmsg = lm_message_new_with_sub_type(channel->name, LM_MESSAGE_TYPE_IQ,
+	    LM_MESSAGE_SUB_TYPE_GET);
+	recoded = xmpp_recode_out(server->jid);
+	lm_message_node_set_attribute(lmsg->node, "from", recoded);
+	g_free(recoded);
+	query = lm_message_node_add_child(lmsg->node, "query", NULL);
+	lm_message_node_set_attribute(query, XMLNS, XMLNS_MUC_ADMIN);
+	item = lm_message_node_add_child(query, "item", NULL);
+	recoded = xmpp_recode_out(type);
+	lm_message_node_set_attribute(item, "affiliation", recoded);
+	g_free(recoded);
+	signal_emit("xmpp send iq", 2, channel->server, lmsg);
+	lm_message_unref(lmsg);
+}
+
+void
+muc_set_affiliation(XMPP_SERVER_REC *server, MUC_REC *channel, const char *type,
+		const char *jid, const char *reason)
+{
+	LmMessage *lmsg;
+	LmMessageNode *query, *item;
+	char *recoded, *str;
+
+	g_return_if_fail(IS_MUC(channel));
+	g_return_if_fail(IS_XMPP_SERVER(server));
+	if (!channel->server->connected)
+		return;
+
+	lmsg = lm_message_new_with_sub_type(channel->name, LM_MESSAGE_TYPE_IQ,
+	    LM_MESSAGE_SUB_TYPE_SET);
+	recoded = xmpp_recode_out(server->jid);
+	lm_message_node_set_attribute(lmsg->node, "from", recoded);
+	g_free(recoded);
+	query = lm_message_node_add_child(lmsg->node, "query", NULL);
+	lm_message_node_set_attribute(query, XMLNS, XMLNS_MUC_ADMIN);
+	item = lm_message_node_add_child(query, "item", NULL);
+	recoded = xmpp_recode_out(type);
+	lm_message_node_set_attribute(item, "affiliation", recoded);
+	g_free(recoded);
+	recoded = xmpp_recode_out(jid);
+	lm_message_node_set_attribute(item, "jid", recoded);
+	g_free(recoded);
+	if (reason != NULL) {
+		recoded = xmpp_recode_out(reason);
+		lm_message_node_add_child(item, "reason", recoded);
+		g_free(recoded);
+	}
+	signal_emit("xmpp send iq", 2, channel->server, lmsg);
+	lm_message_unref(lmsg);
+}
+
+void
+muc_get_role(XMPP_SERVER_REC *server, MUC_REC *channel, const char *type)
+{
+	LmMessage *lmsg;
+	LmMessageNode *query, *item;
+	char *recoded, *str;
+
+	g_return_if_fail(IS_MUC(channel));
+	g_return_if_fail(IS_XMPP_SERVER(server));
+	if (!channel->server->connected)
+		return;
+
+	lmsg = lm_message_new_with_sub_type(channel->name, LM_MESSAGE_TYPE_IQ,
+	    LM_MESSAGE_SUB_TYPE_GET);
+	recoded = xmpp_recode_out(server->jid);
+	lm_message_node_set_attribute(lmsg->node, "from", recoded);
+	g_free(recoded);
+	query = lm_message_node_add_child(lmsg->node, "query", NULL);
+	lm_message_node_set_attribute(query, XMLNS, XMLNS_MUC_ADMIN);
+	item = lm_message_node_add_child(query, "item", NULL);
+	recoded = xmpp_recode_out(type);
+	lm_message_node_set_attribute(item, "role", recoded);
+	g_free(recoded);
+	signal_emit("xmpp send iq", 2, channel->server, lmsg);
+	lm_message_unref(lmsg);
+}
+
+void
+muc_set_role(XMPP_SERVER_REC *server, MUC_REC *channel, const char *type,
+		const char *nick, const char *reason)
+{
+	LmMessage *lmsg;
+	LmMessageNode *query, *item;
+	char *recoded, *str;
+
+	g_return_if_fail(IS_MUC(channel));
+	g_return_if_fail(IS_XMPP_SERVER(server));
+	if (!channel->server->connected)
+		return;
+
+	lmsg = lm_message_new_with_sub_type(channel->name, LM_MESSAGE_TYPE_IQ,
+	    LM_MESSAGE_SUB_TYPE_SET);
+	recoded = xmpp_recode_out(server->jid);
+	lm_message_node_set_attribute(lmsg->node, "from", recoded);
+	g_free(recoded);
+	query = lm_message_node_add_child(lmsg->node, "query", NULL);
+	lm_message_node_set_attribute(query, XMLNS, XMLNS_MUC_ADMIN);
+	item = lm_message_node_add_child(query, "item", NULL);
+	recoded = xmpp_recode_out(type);
+	lm_message_node_set_attribute(item, "role", recoded);
+	g_free(recoded);
+	recoded = xmpp_recode_out(nick);
+	lm_message_node_set_attribute(item, "nick", recoded);
+	g_free(recoded);
+	if (reason != NULL) {
+		recoded = xmpp_recode_out(reason);
+		lm_message_node_add_child(item, "reason", recoded);
+		g_free(recoded);
+	}
+	signal_emit("xmpp send iq", 2, channel->server, lmsg);
+	lm_message_unref(lmsg);
+}
+
+void
 muc_destroy(XMPP_SERVER_REC *server ,MUC_REC *channel, const char *alternate,
 		const char *reason)
 {
@@ -219,7 +347,6 @@
 	}
 	signal_emit("xmpp send iq", 2, channel->server, lmsg);
 	lm_message_unref(lmsg);
-
 }
 
 static void
--- a/src/core/xep/muc.h
+++ b/src/core/xep/muc.h
@@ -9,6 +9,7 @@
 #define XMLNS_MUC	"http://jabber.org/protocol/muc"
 #define XMLNS_MUC_USER	"http://jabber.org/protocol/muc#user"
 #define XMLNS_MUC_OWNER	"http://jabber.org/protocol/muc#owner"
+#define XMLNS_MUC_ADMIN	"http://jabber.org/protocol/muc#admin"
 
 #define muc_extract_nick(jid)						\
 	xmpp_extract_resource(jid)
@@ -51,9 +52,17 @@
 };
 
 __BEGIN_DECLS
+
+void muc_destroy(XMPP_SERVER_REC *, MUC_REC *, const char *, const char *);
 void muc_join(XMPP_SERVER_REC *, const char *, gboolean);
 void muc_part(MUC_REC *, const char *);
 void muc_nick(MUC_REC *, const char *);
+void muc_get_affiliation(XMPP_SERVER_REC *, MUC_REC *, const char *);
+void muc_set_affiliation(XMPP_SERVER_REC *, MUC_REC *, const char *,
+		const char *, const char *);
+void muc_get_role(XMPP_SERVER_REC *, MUC_REC *, const char *);
+void muc_set_role(XMPP_SERVER_REC *, MUC_REC *, const char *,
+		const char *, const char *);
 MUC_REC	*get_muc(XMPP_SERVER_REC *, const char *);
 
 void muc_init(void);
--- a/src/fe-common/xep/fe-muc.c
+++ b/src/fe-common/xep/fe-muc.c
@@ -30,6 +30,8 @@
 #include "rosters-tools.h"
 #include "xep/muc.h"
 #include "xep/muc-nicklist.h"
+#include "xep/muc-affiliation.h"
+#include "xep/muc-role.h"
 
 static void
 sig_invite(XMPP_SERVER_REC *server, const char *from, const char *channame)
@@ -140,29 +142,29 @@
 	if ((nick = xmpp_nicklist_find(channel, nickname)) == NULL)
 		return;
 	switch (affiliation) {
-	case XMPP_NICKLIST_AFFILIATION_OWNER:
+	case XMPP_AFFILIATION_OWNER:
 		affiliation_str = "O";
 		break;
-	case XMPP_NICKLIST_AFFILIATION_ADMIN:
+	case XMPP_AFFILIATION_ADMIN:
 		affiliation_str = "A";
 		break;
-	case XMPP_NICKLIST_AFFILIATION_MEMBER:
+	case XMPP_AFFILIATION_MEMBER:
 		affiliation_str = "M";
 		break;
-	case XMPP_NICKLIST_AFFILIATION_OUTCAST:
+	case XMPP_AFFILIATION_OUTCAST:
 		affiliation_str = "U";
 		break;
 	default:
 		affiliation_str = "";
 	}
 	switch (role) {
-	case XMPP_NICKLIST_ROLE_MODERATOR:
+	case XMPP_ROLE_MODERATOR:
 		role_str = "m";
 		break;
-	case XMPP_NICKLIST_ROLE_PARTICIPANT:
+	case XMPP_ROLE_PARTICIPANT:
 		role_str = "p";
 		break;
-	case XMPP_NICKLIST_ROLE_VISITOR:
+	case XMPP_ROLE_VISITOR:
 		role_str = "v";
 		break;
 	default:
@@ -181,6 +183,40 @@
 out:	g_free(mode);
 }
 
+static void
+sig_affiliation(MUC_REC *channel, const char *jid, const char *nickname, int affiliation)
+{
+	XMPP_NICK_REC *nick;
+	char *mode, *affiliation_str;
+
+	g_return_if_fail(IS_MUC(channel));
+
+	switch (affiliation) {
+	case XMPP_AFFILIATION_OWNER:
+		affiliation_str = "O";
+		break;
+	case XMPP_AFFILIATION_ADMIN:
+		affiliation_str = "A";
+		break;
+	case XMPP_AFFILIATION_MEMBER:
+		affiliation_str = "M";
+		break;
+	case XMPP_AFFILIATION_OUTCAST:
+		affiliation_str = "U";
+		break;
+	default:
+		affiliation_str = "";
+	}
+	if (*affiliation_str == '\0')
+		return;
+	mode = g_strconcat("+", affiliation_str, " ", jid,
+	    (void *)NULL);
+	printformat_module(IRC_MODULE_NAME, channel->server, channel->name,
+	    MSGLEVEL_MODES, IRCTXT_CHANMODE_CHANGE, channel->name, mode,
+	    channel->name);
+out:	g_free(mode);
+}
+
 struct cycle_data {
 	XMPP_SERVER_REC	*server;
 	char		*joindata;
@@ -240,6 +276,7 @@
 	signal_add("message xmpp muc own_nick", sig_own_nick);
 	signal_add("message xmpp muc nick in use", sig_nick_in_use);
 	signal_add("message xmpp muc mode", sig_mode);
+	signal_add("message xmpp muc affiliation", sig_affiliation);
 	signal_add_first("command cycle", cmd_cycle);
 }
 
