diff libpurple/protocols/silc10/chat.c @ 17567:ba1b50f114f6

Duplicate the current SILC prpl as silc10 for backwards compatibility with SILC Toolkit 1.0
author Stu Tomlinson <stu@nosnilmot.com>
date Sat, 09 Jun 2007 16:39:00 +0000
parents
children ab6d2763b8d8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/chat.c	Sat Jun 09 16:39:00 2007 +0000
@@ -0,0 +1,1456 @@
+/*
+
+  silcpurple_chat.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  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.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+#include "wb.h"
+
+/***************************** Channel Routines ******************************/
+
+GList *silcpurple_chat_info(PurpleConnection *gc)
+{
+	GList *ci = NULL;
+	struct proto_chat_entry *pce;
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Channel:");
+	pce->identifier = "channel";
+	pce->required = TRUE;
+	ci = g_list_append(ci, pce);
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Passphrase:");
+	pce->identifier = "passphrase";
+	pce->secret = TRUE;
+	ci = g_list_append(ci, pce);
+
+	return ci;
+}
+
+GHashTable *silcpurple_chat_info_defaults(PurpleConnection *gc, const char *chat_name)
+{
+	GHashTable *defaults;
+
+	defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+	if (chat_name != NULL)
+		g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
+
+	return defaults;
+}
+
+static void
+silcpurple_chat_getinfo(PurpleConnection *gc, GHashTable *components);
+
+static void
+silcpurple_chat_getinfo_res(SilcClient client,
+			  SilcClientConnection conn,
+			  SilcChannelEntry *channels,
+			  SilcUInt32 channels_count,
+			  void *context)
+{
+	GHashTable *components = context;
+	PurpleConnection *gc = client->application;
+	const char *chname;
+	char tmp[256];
+
+	chname = g_hash_table_lookup(components, "channel");
+	if (!chname)
+		return;
+
+	if (!channels) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("Channel %s does not exist in the network"), chname);
+		purple_notify_error(gc, _("Channel Information"),
+				  _("Cannot get channel information"), tmp);
+		return;
+	}
+
+	silcpurple_chat_getinfo(gc, components);
+}
+
+
+static void
+silcpurple_chat_getinfo(PurpleConnection *gc, GHashTable *components)
+{
+	SilcPurple sg = gc->proto_data;
+	const char *chname;
+	char *buf, tmp[256], *tmp2;
+	GString *s;
+	SilcChannelEntry channel;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+
+	if (!components)
+		return;
+
+	chname = g_hash_table_lookup(components, "channel");
+	if (!chname)
+		return;
+	channel = silc_client_get_channel(sg->client, sg->conn,
+					  (char *)chname);
+	if (!channel) {
+		silc_client_get_channel_resolve(sg->client, sg->conn,
+						(char *)chname,
+						silcpurple_chat_getinfo_res,
+						components);
+		return;
+	}
+
+	s = g_string_new("");
+	tmp2 = g_markup_escape_text(channel->channel_name, -1);
+	g_string_append_printf(s, _("<b>Channel Name:</b> %s"), tmp2);
+	g_free(tmp2);
+	if (channel->user_list && silc_hash_table_count(channel->user_list))
+		g_string_append_printf(s, _("<br><b>User Count:</b> %d"),
+				       (int)silc_hash_table_count(channel->user_list));
+
+	silc_hash_table_list(channel->user_list, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+			tmp2 = g_markup_escape_text(chu->client->nickname, -1);
+			g_string_append_printf(s, _("<br><b>Channel Founder:</b> %s"),
+					       tmp2);
+			g_free(tmp2);
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+
+	if (channel->channel_key)
+		g_string_append_printf(s, _("<br><b>Channel Cipher:</b> %s"),
+				       silc_cipher_get_name(channel->channel_key));
+	if (channel->hmac)
+		/* Definition of HMAC: http://en.wikipedia.org/wiki/HMAC */
+		g_string_append_printf(s, _("<br><b>Channel HMAC:</b> %s"),
+				       silc_hmac_get_name(channel->hmac));
+
+	if (channel->topic) {
+		tmp2 = g_markup_escape_text(channel->topic, -1);
+		g_string_append_printf(s, _("<br><b>Channel Topic:</b><br>%s"), tmp2);
+		g_free(tmp2);
+	}
+
+	if (channel->mode) {
+		g_string_append_printf(s, _("<br><b>Channel Modes:</b> "));
+		silcpurple_get_chmode_string(channel->mode, tmp, sizeof(tmp));
+		g_string_append(s, tmp);
+	}
+
+	if (channel->founder_key) {
+		char *fingerprint, *babbleprint;
+		unsigned char *pk;
+		SilcUInt32 pk_len;
+		pk = silc_pkcs_public_key_encode(channel->founder_key, &pk_len);
+		fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+		babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+
+		g_string_append_printf(s, _("<br><b>Founder Key Fingerprint:</b><br>%s"), fingerprint);
+		g_string_append_printf(s, _("<br><b>Founder Key Babbleprint:</b><br>%s"), babbleprint);
+
+		silc_free(fingerprint);
+		silc_free(babbleprint);
+		silc_free(pk);
+	}
+
+	buf = g_string_free(s, FALSE);
+	purple_notify_formatted(gc, NULL, _("Channel Information"), NULL, buf, NULL, NULL);
+	g_free(buf);
+}
+
+
+static void
+silcpurple_chat_getinfo_menu(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat = (PurpleChat *)node;
+	silcpurple_chat_getinfo(chat->account->gc, chat->components);
+}
+
+
+#if 0   /* XXX For now these are not implemented.  We need better
+	   listview dialog from Purple for these. */
+/************************** Channel Invite List ******************************/
+
+static void
+silcpurple_chat_invitelist(PurpleBlistNode *node, gpointer data);
+{
+
+}
+
+
+/**************************** Channel Ban List *******************************/
+
+static void
+silcpurple_chat_banlist(PurpleBlistNode *node, gpointer data);
+{
+
+}
+#endif
+
+
+/************************* Channel Authentication ****************************/
+
+typedef struct {
+	SilcPurple sg;
+	SilcChannelEntry channel;
+	PurpleChat *c;
+	SilcBuffer pubkeys;
+} *SilcPurpleChauth;
+
+static void
+silcpurple_chat_chpk_add(void *user_data, const char *name)
+{
+	SilcPurpleChauth sgc = (SilcPurpleChauth)user_data;
+	SilcPurple sg = sgc->sg;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcPublicKey public_key;
+	SilcBuffer chpks, pk, chidp;
+	unsigned char mode[4];
+	SilcUInt32 m;
+
+	/* Load the public key */
+	if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) &&
+	    !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) {
+		silcpurple_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys);
+		silc_buffer_free(sgc->pubkeys);
+		silc_free(sgc);
+		purple_notify_error(client->application,
+				  _("Add Channel Public Key"),
+				  _("Could not load public key"), NULL);
+		return;
+	}
+
+	pk = silc_pkcs_public_key_payload_encode(public_key);
+	chpks = silc_buffer_alloc_size(2);
+	SILC_PUT16_MSB(1, chpks->head);
+	chpks = silc_argument_payload_encode_one(chpks, pk->data,
+						 pk->len, 0x00);
+	silc_buffer_free(pk);
+
+	m = sgc->channel->mode;
+	m |= SILC_CHANNEL_MODE_CHANNEL_AUTH;
+
+	/* Send CMODE */
+	SILC_PUT32_MSB(m, mode);
+	chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL);
+	silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
+				 ++conn->cmd_ident, 3,
+				 1, chidp->data, chidp->len,
+				 2, mode, sizeof(mode),
+				 9, chpks->data, chpks->len);
+	silc_buffer_free(chpks);
+	silc_buffer_free(chidp);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcpurple_chat_chpk_cancel(void *user_data, const char *name)
+{
+	SilcPurpleChauth sgc = (SilcPurpleChauth)user_data;
+	silcpurple_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcpurple_chat_chpk_cb(SilcPurpleChauth sgc, PurpleRequestFields *fields)
+{
+	SilcPurple sg = sgc->sg;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	PurpleRequestField *f;
+	const GList *list;
+	SilcPublicKey public_key;
+	SilcBuffer chpks, pk, chidp;
+	SilcUInt16 c = 0, ct;
+	unsigned char mode[4];
+	SilcUInt32 m;
+
+	f = purple_request_fields_get_field(fields, "list");
+	if (!purple_request_field_list_get_selected(f)) {
+		/* Add new public key */
+		purple_request_file(sg->gc, _("Open Public Key..."), NULL, FALSE,
+				  G_CALLBACK(silcpurple_chat_chpk_add),
+				  G_CALLBACK(silcpurple_chat_chpk_cancel),
+				  purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+		return;
+	}
+
+	list = purple_request_field_list_get_items(f);
+	chpks = silc_buffer_alloc_size(2);
+
+	for (ct = 0; list; list = list->next, ct++) {
+		public_key = purple_request_field_list_get_data(f, list->data);
+		if (purple_request_field_list_is_selected(f, list->data)) {
+			/* Delete this public key */
+			pk = silc_pkcs_public_key_payload_encode(public_key);
+			chpks = silc_argument_payload_encode_one(chpks, pk->data,
+								 pk->len, 0x01);
+			silc_buffer_free(pk);
+			c++;
+		}
+		silc_pkcs_public_key_free(public_key);
+	}
+	if (!c) {
+		silc_buffer_free(chpks);
+		return;
+	}
+	SILC_PUT16_MSB(c, chpks->head);
+
+	m = sgc->channel->mode;
+	if (ct == c)
+		m &= ~SILC_CHANNEL_MODE_CHANNEL_AUTH;
+
+	/* Send CMODE */
+	SILC_PUT32_MSB(m, mode);
+	chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL);
+	silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
+				 ++conn->cmd_ident, 3,
+				 1, chidp->data, chidp->len,
+				 2, mode, sizeof(mode),
+				 9, chpks->data, chpks->len);
+	silc_buffer_free(chpks);
+	silc_buffer_free(chidp);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcpurple_chat_chauth_ok(SilcPurpleChauth sgc, PurpleRequestFields *fields)
+{
+	SilcPurple sg = sgc->sg;
+	PurpleRequestField *f;
+	const char *curpass, *val;
+	int set;
+
+	f = purple_request_fields_get_field(fields, "passphrase");
+	val = purple_request_field_string_get_value(f);
+	curpass = purple_blist_node_get_string((PurpleBlistNode *)sgc->c, "passphrase");
+
+	if (!val && curpass)
+		set = 0;
+	else if (val && !curpass)
+		set = 1;
+	else if (val && curpass && strcmp(val, curpass))
+		set = 1;
+	else
+		set = -1;
+
+	if (set == 1) {
+		silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+					 sgc->channel->channel_name, "+a", val, NULL);
+		purple_blist_node_set_string((PurpleBlistNode *)sgc->c, "passphrase", val);
+	} else if (set == 0) {
+		silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+					 sgc->channel->channel_name, "-a", NULL);
+		purple_blist_node_remove_setting((PurpleBlistNode *)sgc->c, "passphrase");
+	}
+
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+void silcpurple_chat_chauth_show(SilcPurple sg, SilcChannelEntry channel,
+			       SilcBuffer channel_pubkeys)
+{
+	SilcUInt16 argc;
+	SilcArgumentPayload chpks;
+	unsigned char *pk;
+	SilcUInt32 pk_len, type;
+	char *fingerprint, *babbleprint;
+	SilcPublicKey pubkey;
+	SilcPublicKeyIdentifier ident;
+	char tmp2[1024], t[512];
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *g;
+	PurpleRequestField *f;
+	SilcPurpleChauth sgc;
+	const char *curpass = NULL;
+
+	sgc = silc_calloc(1, sizeof(*sgc));
+	if (!sgc)
+		return;
+	sgc->sg = sg;
+	sgc->channel = channel;
+
+	fields = purple_request_fields_new();
+
+	if (sgc->c)
+	  curpass = purple_blist_node_get_string((PurpleBlistNode *)sgc->c, "passphrase");
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("passphrase", _("Channel Passphrase"),
+					  curpass, FALSE);
+	purple_request_field_string_set_masked(f, TRUE);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_label_new("l1", _("Channel Public Keys List"));
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g_snprintf(t, sizeof(t),
+		   _("Channel authentication is used to secure the channel from "
+		     "unauthorized access. The authentication may be based on "
+		     "passphrase and digital signatures. If passphrase is set, it "
+		     "is required to be able to join. If channel public keys are set "
+		     "then only users whose public keys are listed are able to join."));
+
+	if (!channel_pubkeys) {
+		f = purple_request_field_list_new("list", NULL);
+		purple_request_field_group_add_field(g, f);
+		purple_request_fields(sg->gc, _("Channel Authentication"),
+				    _("Channel Authentication"), t, fields,
+				    _("Add / Remove"), G_CALLBACK(silcpurple_chat_chpk_cb),
+				    _("OK"), G_CALLBACK(silcpurple_chat_chauth_ok),
+					purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+		return;
+	}
+	sgc->pubkeys = silc_buffer_copy(channel_pubkeys);
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_list_new("list", NULL);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	SILC_GET16_MSB(argc, channel_pubkeys->data);
+	chpks = silc_argument_payload_parse(channel_pubkeys->data + 2,
+					    channel_pubkeys->len - 2, argc);
+	if (!chpks)
+		return;
+
+	pk = silc_argument_get_first_arg(chpks, &type, &pk_len);
+	while (pk) {
+		fingerprint = silc_hash_fingerprint(NULL, pk + 4, pk_len - 4);
+		babbleprint = silc_hash_babbleprint(NULL, pk + 4, pk_len - 4);
+		silc_pkcs_public_key_payload_decode(pk, pk_len, &pubkey);
+		ident = silc_pkcs_decode_identifier(pubkey->identifier);
+
+		g_snprintf(tmp2, sizeof(tmp2), "%s\n  %s\n  %s",
+			   ident->realname ? ident->realname : ident->username ?
+			   ident->username : "", fingerprint, babbleprint);
+		purple_request_field_list_add(f, tmp2, pubkey);
+
+		silc_free(fingerprint);
+		silc_free(babbleprint);
+		silc_pkcs_free_identifier(ident);
+		pk = silc_argument_get_next_arg(chpks, &type, &pk_len);
+	}
+
+	purple_request_field_list_set_multi_select(f, FALSE);
+	purple_request_fields(sg->gc, _("Channel Authentication"),
+			    _("Channel Authentication"), t, fields,
+			    _("Add / Remove"), G_CALLBACK(silcpurple_chat_chpk_cb),
+			    _("OK"), G_CALLBACK(silcpurple_chat_chauth_ok),
+				purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+
+	silc_argument_payload_free(chpks);
+}
+
+static void
+silcpurple_chat_chauth(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "+C", NULL);
+}
+
+
+/************************** Channel Private Groups **************************/
+
+/* Private groups are "virtual" channels.  They are groups inside a channel.
+   This is implemented by using channel private keys.  By knowing a channel
+   private key user becomes part of that group and is able to talk on that
+   group.  Other users, on the same channel, won't be able to see the
+   messages of that group.  It is possible to have multiple groups inside
+   a channel - and thus having multiple private keys on the channel. */
+
+typedef struct {
+	SilcPurple sg;
+	PurpleChat *c;
+	const char *channel;
+} *SilcPurpleCharPrv;
+
+static void
+silcpurple_chat_prv_add(SilcPurpleCharPrv p, PurpleRequestFields *fields)
+{
+	SilcPurple sg = p->sg;
+	char tmp[512];
+	PurpleRequestField *f;
+	const char *name, *passphrase, *alias;
+	GHashTable *comp;
+	PurpleGroup *g;
+	PurpleChat *cn;
+
+	f = purple_request_fields_get_field(fields, "name");
+	name = purple_request_field_string_get_value(f);
+	if (!name) {
+		silc_free(p);
+		return;
+	}
+	f = purple_request_fields_get_field(fields, "passphrase");
+	passphrase = purple_request_field_string_get_value(f);
+	f = purple_request_fields_get_field(fields, "alias");
+	alias = purple_request_field_string_get_value(f);
+
+	/* Add private group to buddy list */
+	g_snprintf(tmp, sizeof(tmp), "%s [Private Group]", name);
+	comp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	g_hash_table_replace(comp, g_strdup("channel"), g_strdup(tmp));
+	g_hash_table_replace(comp, g_strdup("passphrase"), g_strdup(passphrase));
+
+	cn = purple_chat_new(sg->account, alias, comp);
+	g = (PurpleGroup *)p->c->node.parent;
+	purple_blist_add_chat(cn, g, (PurpleBlistNode *)p->c);
+
+	/* Associate to a real channel */
+	purple_blist_node_set_string((PurpleBlistNode *)cn, "parentch", p->channel);
+
+	/* Join the group */
+	silcpurple_chat_join(sg->gc, comp);
+
+	silc_free(p);
+}
+
+static void
+silcpurple_chat_prv_cancel(SilcPurpleCharPrv p, PurpleRequestFields *fields)
+{
+	silc_free(p);
+}
+
+static void
+silcpurple_chat_prv(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	SilcPurpleCharPrv p;
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *g;
+	PurpleRequestField *f;
+	char tmp[512];
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	p = silc_calloc(1, sizeof(*p));
+	if (!p)
+		return;
+	p->sg = sg;
+
+	p->channel = g_hash_table_lookup(chat->components, "channel");
+	p->c = purple_blist_find_chat(sg->account, p->channel);
+
+	fields = purple_request_fields_new();
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("name", _("Group Name"),
+					  NULL, FALSE);
+	purple_request_field_group_add_field(g, f);
+
+	f = purple_request_field_string_new("passphrase", _("Passphrase"),
+					  NULL, FALSE);
+	purple_request_field_string_set_masked(f, TRUE);
+	purple_request_field_group_add_field(g, f);
+
+	f = purple_request_field_string_new("alias", _("Alias"),
+					  NULL, FALSE);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g_snprintf(tmp, sizeof(tmp),
+		   _("Please enter the %s channel private group name and passphrase."),
+		   p->channel);
+	purple_request_fields(gc, _("Add Channel Private Group"), NULL, tmp, fields,
+			    _("Add"), G_CALLBACK(silcpurple_chat_prv_add),
+			    _("Cancel"), G_CALLBACK(silcpurple_chat_prv_cancel),
+				purple_connection_get_account(gc), NULL, NULL, p);
+}
+
+
+/****************************** Channel Modes ********************************/
+
+static void
+silcpurple_chat_permanent_reset(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "-f", NULL);
+}
+
+static void
+silcpurple_chat_permanent(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+	const char *channel;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	if (!sg->conn)
+		return;
+
+	/* XXX we should have ability to define which founder
+	   key to use.  Now we use the user's own public key
+	   (default key). */
+
+	/* Call CMODE */
+	channel = g_hash_table_lookup(chat->components, "channel");
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", channel,
+				 "+f", NULL);
+}
+
+typedef struct {
+	SilcPurple sg;
+	const char *channel;
+} *SilcPurpleChatInput;
+
+static void
+silcpurple_chat_ulimit_cb(SilcPurpleChatInput s, const char *limit)
+{
+	SilcChannelEntry channel;
+	int ulimit = 0;
+
+	channel = silc_client_get_channel(s->sg->client, s->sg->conn,
+					  (char *)s->channel);
+	if (!channel)
+		return;
+	if (limit)
+		ulimit = atoi(limit);
+
+	if (!limit || !(*limit) || *limit == '0') {
+		if (limit && ulimit == channel->user_limit) {
+			silc_free(s);
+			return;
+		}
+		silc_client_command_call(s->sg->client, s->sg->conn, NULL, "CMODE",
+					 s->channel, "-l", NULL);
+
+		silc_free(s);
+		return;
+	}
+
+	if (ulimit == channel->user_limit) {
+		silc_free(s);
+		return;
+	}
+
+	/* Call CMODE */
+	silc_client_command_call(s->sg->client, s->sg->conn, NULL, "CMODE",
+				 s->channel, "+l", limit, NULL);
+
+	silc_free(s);
+}
+
+static void
+silcpurple_chat_ulimit(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	SilcPurpleChatInput s;
+	SilcChannelEntry channel;
+	const char *ch;
+	char tmp[32];
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	if (!sg->conn)
+		return;
+
+	ch = g_strdup(g_hash_table_lookup(chat->components, "channel"));
+	channel = silc_client_get_channel(sg->client, sg->conn, (char *)ch);
+	if (!channel)
+		return;
+
+	s = silc_calloc(1, sizeof(*s));
+	if (!s)
+		return;
+	s->channel = ch;
+	s->sg = sg;
+	g_snprintf(tmp, sizeof(tmp), "%d", (int)channel->user_limit);
+	purple_request_input(gc, _("User Limit"), NULL,
+			   _("Set user limit on channel. Set to zero to reset user limit."),
+			   tmp, FALSE, FALSE, NULL,
+			   _("OK"), G_CALLBACK(silcpurple_chat_ulimit_cb),
+			   _("Cancel"), G_CALLBACK(silcpurple_chat_ulimit_cb),
+			   purple_connection_get_account(gc), NULL, NULL, s);
+}
+
+static void
+silcpurple_chat_resettopic(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "-t", NULL);
+}
+
+static void
+silcpurple_chat_settopic(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "+t", NULL);
+}
+
+static void
+silcpurple_chat_resetprivate(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "-p", NULL);
+}
+
+static void
+silcpurple_chat_setprivate(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "+p", NULL);
+}
+
+static void
+silcpurple_chat_resetsecret(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "-s", NULL);
+}
+
+static void
+silcpurple_chat_setsecret(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "+s", NULL);
+}
+
+typedef struct {
+	SilcPurple sg;
+	SilcChannelEntry channel;
+} *SilcPurpleChatWb;
+
+static void
+silcpurple_chat_wb(PurpleBlistNode *node, gpointer data)
+{
+	SilcPurpleChatWb wb = data;
+	silcpurple_wb_init_ch(wb->sg, wb->channel);
+	silc_free(wb);
+}
+
+GList *silcpurple_chat_menu(PurpleChat *chat)
+{
+	GHashTable *components = chat->components;
+	PurpleConnection *gc = purple_account_get_connection(chat->account);
+	SilcPurple sg = gc->proto_data;
+	SilcClientConnection conn = sg->conn;
+	const char *chname = NULL;
+	SilcChannelEntry channel = NULL;
+	SilcChannelUser chu = NULL;
+	SilcUInt32 mode = 0;
+
+	GList *m = NULL;
+	PurpleMenuAction *act;
+
+	if (components)
+		chname = g_hash_table_lookup(components, "channel");
+	if (chname)
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)chname);
+	if (channel) {
+		chu = silc_client_on_channel(channel, conn->local_entry);
+		if (chu)
+			mode = chu->mode;
+	}
+
+	if (strstr(chname, "[Private Group]"))
+		return NULL;
+
+	act = purple_menu_action_new(_("Get Info"),
+	                           PURPLE_CALLBACK(silcpurple_chat_getinfo_menu),
+	                           NULL, NULL);
+	m = g_list_append(m, act);
+
+#if 0   /* XXX For now these are not implemented.  We need better
+	   listview dialog from Purple for these. */
+	if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+		act = purple_menu_action_new(_("Invite List"),
+		                           PURPLE_CALLBACK(silcpurple_chat_invitelist),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		act = purple_menu_action_new(_("Ban List"),
+		                           PURPLE_CALLBACK(silcpurple_chat_banlist),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+#endif
+
+	if (chu) {
+		act = purple_menu_action_new(_("Add Private Group"),
+		                           PURPLE_CALLBACK(silcpurple_chat_prv),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	if (mode & SILC_CHANNEL_UMODE_CHANFO) {
+		act = purple_menu_action_new(_("Channel Authentication"),
+		                           PURPLE_CALLBACK(silcpurple_chat_chauth),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+			act = purple_menu_action_new(_("Reset Permanent"),
+			                           PURPLE_CALLBACK(silcpurple_chat_permanent_reset),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		} else {
+			act = purple_menu_action_new(_("Set Permanent"),
+			                           PURPLE_CALLBACK(silcpurple_chat_permanent),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+	}
+
+	if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+		act = purple_menu_action_new(_("Set User Limit"),
+		                           PURPLE_CALLBACK(silcpurple_chat_ulimit),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		if (channel->mode & SILC_CHANNEL_MODE_TOPIC) {
+			act = purple_menu_action_new(_("Reset Topic Restriction"),
+			                           PURPLE_CALLBACK(silcpurple_chat_resettopic),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		} else {
+			act = purple_menu_action_new(_("Set Topic Restriction"),
+			                           PURPLE_CALLBACK(silcpurple_chat_settopic),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+
+		if (channel->mode & SILC_CHANNEL_MODE_PRIVATE) {
+			act = purple_menu_action_new(_("Reset Private Channel"),
+			                           PURPLE_CALLBACK(silcpurple_chat_resetprivate),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		} else {
+			act = purple_menu_action_new(_("Set Private Channel"),
+			                           PURPLE_CALLBACK(silcpurple_chat_setprivate),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+
+		if (channel->mode & SILC_CHANNEL_MODE_SECRET) {
+			act = purple_menu_action_new(_("Reset Secret Channel"),
+			                           PURPLE_CALLBACK(silcpurple_chat_resetsecret),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		} else {
+			act = purple_menu_action_new(_("Set Secret Channel"),
+			                           PURPLE_CALLBACK(silcpurple_chat_setsecret),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+	}
+
+	if (channel) {
+		SilcPurpleChatWb wb;
+		wb = silc_calloc(1, sizeof(*wb));
+		wb->sg = sg;
+		wb->channel = channel;
+		act = purple_menu_action_new(_("Draw On Whiteboard"),
+		                           PURPLE_CALLBACK(silcpurple_chat_wb),
+		                           (void *)wb, NULL);
+		m = g_list_append(m, act);
+	}
+
+	return m;
+}
+
+
+/******************************* Joining Etc. ********************************/
+
+void silcpurple_chat_join_done(SilcClient client,
+			     SilcClientConnection conn,
+			     SilcClientEntry *clients,
+			     SilcUInt32 clients_count,
+			     void *context)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	SilcChannelEntry channel = context;
+	PurpleConversation *convo;
+	SilcUInt32 retry = SILC_PTR_TO_32(channel->context);
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	GList *users = NULL, *flags = NULL;
+	char tmp[256];
+
+	if (!clients && retry < 1) {
+		/* Resolving users failed, try again. */
+		channel->context = SILC_32_TO_PTR(retry + 1);
+		silc_client_get_clients_by_channel(client, conn, channel,
+						   silcpurple_chat_join_done, channel);
+		return;
+	}
+
+	/* Add channel to Purple */
+	channel->context = SILC_32_TO_PTR(++sg->channel_ids);
+	serv_got_joined_chat(gc, sg->channel_ids, channel->channel_name);
+	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+							channel->channel_name, sg->account);
+	if (!convo)
+		return;
+
+	/* Add all users to channel */
+	silc_hash_table_list(channel->user_list, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		PurpleConvChatBuddyFlags f = PURPLE_CBFLAGS_NONE;
+		if (!chu->client->nickname)
+			continue;
+		chu->context = SILC_32_TO_PTR(sg->channel_ids);
+
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO)
+			f |= PURPLE_CBFLAGS_FOUNDER;
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANOP)
+			f |= PURPLE_CBFLAGS_OP;
+		users = g_list_append(users, g_strdup(chu->client->nickname));
+		flags = g_list_append(flags, GINT_TO_POINTER(f));
+
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+			if (chu->client == conn->local_entry)
+				g_snprintf(tmp, sizeof(tmp),
+					   _("You are channel founder on <I>%s</I>"),
+					   channel->channel_name);
+			else
+				g_snprintf(tmp, sizeof(tmp),
+					   _("Channel founder on <I>%s</I> is <I>%s</I>"),
+					   channel->channel_name, chu->client->nickname);
+
+			purple_conversation_write(convo, NULL, tmp,
+						PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+
+	purple_conv_chat_add_users(PURPLE_CONV_CHAT(convo), users, NULL, flags, FALSE);
+	g_list_free(users);
+	g_list_free(flags);
+
+	/* Set topic */
+	if (channel->topic)
+		purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo), NULL, channel->topic);
+
+	/* Set nick */
+	purple_conv_chat_set_nick(PURPLE_CONV_CHAT(convo), conn->local_entry->nickname);
+}
+
+char *silcpurple_get_chat_name(GHashTable *data)
+{
+	return g_strdup(g_hash_table_lookup(data, "channel"));
+}	
+
+void silcpurple_chat_join(PurpleConnection *gc, GHashTable *data)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	const char *channel, *passphrase, *parentch;
+
+	if (!conn)
+		return;
+
+	channel = g_hash_table_lookup(data, "channel");
+	passphrase = g_hash_table_lookup(data, "passphrase");
+
+	/* Check if we are joining a private group.  Handle it
+	   purely locally as it's not a real channel */
+	if (strstr(channel, "[Private Group]")) {
+		SilcChannelEntry channel_entry;
+		SilcChannelPrivateKey key;
+		PurpleChat *c;
+		SilcPurplePrvgrp grp;
+
+		c = purple_blist_find_chat(sg->account, channel);
+		parentch = purple_blist_node_get_string((PurpleBlistNode *)c, "parentch");
+		if (!parentch)
+			return;
+
+		channel_entry = silc_client_get_channel(sg->client, sg->conn,
+							(char *)parentch);
+		if (!channel_entry ||
+		    !silc_client_on_channel(channel_entry, sg->conn->local_entry)) {
+			char tmp[512];
+			g_snprintf(tmp, sizeof(tmp),
+				   _("You have to join the %s channel before you are "
+				     "able to join the private group"), parentch);
+			purple_notify_error(gc, _("Join Private Group"),
+					  _("Cannot join private group"), tmp);
+			return;
+		}
+
+		/* Add channel private key */
+		if (!silc_client_add_channel_private_key(client, conn,
+							 channel_entry, channel,
+							 NULL, NULL,
+							 (unsigned char *)passphrase,
+							 strlen(passphrase), &key))
+			return;
+
+		/* Join the group */
+		grp = silc_calloc(1, sizeof(*grp));
+		if (!grp)
+			return;
+		grp->id = ++sg->channel_ids + SILCPURPLE_PRVGRP;
+		grp->chid = SILC_PTR_TO_32(channel_entry->context);
+		grp->parentch = parentch;
+		grp->channel = channel;
+		grp->key = key;
+		sg->grps = g_list_append(sg->grps, grp);
+		serv_got_joined_chat(gc, grp->id, channel);
+		return;
+	}
+
+	/* XXX We should have other properties here as well:
+	   1. whether to try to authenticate to the channel
+	     1a. with default key,
+	     1b. with specific key.
+	   2. whether to try to authenticate to become founder.
+	     2a. with default key,
+	     2b. with specific key.
+
+	   Since now such variety is not possible in the join dialog
+	   we always use -founder and -auth options, which try to
+	   do both 1 and 2 with default keys. */
+
+	/* Call JOIN */
+	if ((passphrase != NULL) && (*passphrase != '\0'))
+		silc_client_command_call(client, conn, NULL, "JOIN",
+					 channel, passphrase, "-auth", "-founder", NULL);
+	else
+		silc_client_command_call(client, conn, NULL, "JOIN",
+					 channel, "-auth", "-founder", NULL);
+}
+
+void silcpurple_chat_invite(PurpleConnection *gc, int id, const char *msg,
+			  const char *name)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+
+	if (!conn)
+		return;
+
+	/* See if we are inviting on a private group.  Invite
+	   to the actual channel */
+	if (id > SILCPURPLE_PRVGRP) {
+		GList *l;
+		SilcPurplePrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		id = prv->chid;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call INVITE */
+	silc_client_command_call(client, conn, NULL, "INVITE",
+				 chu->channel->channel_name,
+				 name, NULL);
+}
+
+void silcpurple_chat_leave(PurpleConnection *gc, int id)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+	GList *l;
+	SilcPurplePrvgrp prv;
+
+	if (!conn)
+		return;
+
+	/* See if we are leaving a private group */
+	if (id > SILCPURPLE_PRVGRP) {
+		SilcChannelEntry channel;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)prv->parentch);
+		if (!channel)
+			return;
+		silc_client_del_channel_private_key(client, conn,
+						    channel, prv->key);
+		silc_free(prv);
+		sg->grps = g_list_remove(sg->grps, prv);
+		serv_got_chat_left(gc, id);
+		return;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call LEAVE */
+	silc_client_command_call(client, conn, NULL, "LEAVE",
+				 chu->channel->channel_name, NULL);
+
+	serv_got_chat_left(gc, id);
+
+	/* Leave from private groups on this channel as well */
+	for (l = sg->grps; l; l = l->next)
+		if (((SilcPurplePrvgrp)l->data)->chid == id) {
+			prv = l->data;
+			silc_client_del_channel_private_key(client, conn,
+							    chu->channel,
+							    prv->key);
+			serv_got_chat_left(gc, prv->id);
+			silc_free(prv);
+			sg->grps = g_list_remove(sg->grps, prv);
+			if (!sg->grps)
+				break;
+		}
+}
+
+int silcpurple_chat_send(PurpleConnection *gc, int id, const char *msg, PurpleMessageFlags msgflags)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	SilcChannelEntry channel = NULL;
+	SilcChannelPrivateKey key = NULL;
+	SilcUInt32 flags;
+	int ret;
+	char *msg2, *tmp;
+	gboolean found = FALSE;
+	gboolean sign = purple_account_get_bool(sg->account, "sign-verify", FALSE);
+
+	if (!msg || !conn)
+		return 0;
+
+	flags = SILC_MESSAGE_FLAG_UTF8;
+
+	tmp = msg2 = purple_unescape_html(msg);
+
+	if (!g_ascii_strncasecmp(msg2, "/me ", 4))
+	{
+		msg2 += 4;
+		if (!*msg2) {
+			g_free(tmp);
+			return 0;
+		}
+		flags |= SILC_MESSAGE_FLAG_ACTION;
+	} else if (strlen(msg) > 1 && msg[0] == '/') {
+		if (!silc_client_command_call(client, conn, msg + 1))
+			purple_notify_error(gc, _("Call Command"), _("Cannot call command"),
+							  _("Unknown command"));
+		g_free(tmp);
+		return 0;
+	}
+
+
+	if (sign)
+		flags |= SILC_MESSAGE_FLAG_SIGNED;
+
+	/* Get the channel private key if we are sending on
+	   private group */
+	if (id > SILCPURPLE_PRVGRP) {
+		GList *l;
+		SilcPurplePrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->id == id)
+				break;
+		if (!l) {
+			g_free(tmp);
+			return 0;
+		}
+		prv = l->data;
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)prv->parentch);
+		if (!channel) {
+			g_free(tmp);
+			return 0;
+		}
+		key = prv->key;
+	}
+
+	if (!channel) {
+		/* Find channel by id */
+		silc_hash_table_list(conn->local_entry->channels, &htl);
+		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+			if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+				found = TRUE;
+				break;
+			}
+		}
+		silc_hash_table_list_reset(&htl);
+		if (!found) {
+			g_free(tmp);
+			return 0;
+		}
+		channel = chu->channel;
+	}
+
+	/* Send channel message */
+	ret = silc_client_send_channel_message(client, conn, channel, key,
+					       flags, (unsigned char *)msg2,
+					       strlen(msg2), TRUE);
+	if (ret) {
+		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg,
+				 time(NULL));
+	}
+	g_free(tmp);
+
+	return ret;
+}
+
+void silcpurple_chat_set_topic(PurpleConnection *gc, int id, const char *topic)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+
+	if (!conn)
+		return;
+
+	/* See if setting topic on private group.  Set it
+	   on the actual channel */
+	if (id > SILCPURPLE_PRVGRP) {
+		GList *l;
+		SilcPurplePrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		id = prv->chid;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call TOPIC */
+	silc_client_command_call(client, conn, NULL, "TOPIC",
+				 chu->channel->channel_name, topic, NULL);
+}
+
+PurpleRoomlist *silcpurple_roomlist_get_list(PurpleConnection *gc)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	GList *fields = NULL;
+	PurpleRoomlistField *f;
+
+	if (!conn)
+		return NULL;
+
+	if (sg->roomlist)
+		purple_roomlist_unref(sg->roomlist);
+
+	sg->roomlist_canceled = FALSE;
+
+	sg->roomlist = purple_roomlist_new(purple_connection_get_account(gc));
+	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "channel", TRUE);
+	fields = g_list_append(fields, f);
+	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT,
+				    _("Users"), "users", FALSE);
+	fields = g_list_append(fields, f);
+	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING,
+				    _("Topic"), "topic", FALSE);
+	fields = g_list_append(fields, f);
+	purple_roomlist_set_fields(sg->roomlist, fields);
+
+	/* Call LIST */
+	silc_client_command_call(client, conn, "LIST");
+
+	purple_roomlist_set_in_progress(sg->roomlist, TRUE);
+
+	return sg->roomlist;
+}
+
+void silcpurple_roomlist_cancel(PurpleRoomlist *list)
+{
+	PurpleConnection *gc = purple_account_get_connection(list->account);
+	SilcPurple sg;
+
+	if (!gc)
+		return;
+	sg = gc->proto_data;
+
+	purple_roomlist_set_in_progress(list, FALSE);
+	if (sg->roomlist == list) {
+		purple_roomlist_unref(sg->roomlist);
+		sg->roomlist = NULL;
+		sg->roomlist_canceled = TRUE;
+	}
+}