diff libpurple/protocols/silc/silc.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 21bc8d84974f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc/silc.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,1920 @@
+/*
+
+  silcgaim.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 - 2005 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 "silcgaim.h"
+#include "version.h"
+#include "wb.h"
+
+extern SilcClientOperations ops;
+static GaimPlugin *silc_plugin = NULL;
+
+static const char *
+silcgaim_list_icon(GaimAccount *a, GaimBuddy *b)
+{
+	return (const char *)"silc";
+}
+
+static void
+silcgaim_list_emblems(GaimBuddy *b, const char **se, const char **sw,
+		      const char **nw, const char **ne)
+{
+}
+
+static GList *
+silcgaim_away_states(GaimAccount *account)
+{
+	GaimStatusType *type;
+	GList *types = NULL;
+
+	type = gaim_status_type_new_full(GAIM_STATUS_AVAILABLE, SILCGAIM_STATUS_ID_AVAILABLE, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = gaim_status_type_new_full(GAIM_STATUS_AVAILABLE, SILCGAIM_STATUS_ID_HYPER, _("Hyper Active"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = gaim_status_type_new_full(GAIM_STATUS_AWAY, SILCGAIM_STATUS_ID_AWAY, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = gaim_status_type_new_full(GAIM_STATUS_UNAVAILABLE, SILCGAIM_STATUS_ID_BUSY, _("Busy"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = gaim_status_type_new_full(GAIM_STATUS_AWAY, SILCGAIM_STATUS_ID_INDISPOSED, _("Indisposed"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = gaim_status_type_new_full(GAIM_STATUS_AWAY, SILCGAIM_STATUS_ID_PAGE, _("Wake Me Up"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = gaim_status_type_new_full(GAIM_STATUS_OFFLINE, SILCGAIM_STATUS_ID_OFFLINE, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+
+	return types;
+}
+
+static void
+silcgaim_set_status(GaimAccount *account, GaimStatus *status)
+{
+	GaimConnection *gc = gaim_account_get_connection(account);
+	SilcGaim sg = NULL;
+	SilcUInt32 mode;
+	SilcBuffer idp;
+	unsigned char mb[4];
+	const char *state;
+
+	if (gc != NULL)
+		sg = gc->proto_data;
+
+	if (status == NULL)
+		return;
+
+	state = gaim_status_get_id(status);
+
+	if (state == NULL)
+		return;
+
+	if ((sg == NULL) || (sg->conn == NULL))
+		return;
+
+	mode = sg->conn->local_entry->mode;
+	mode &= ~(SILC_UMODE_GONE |
+		  SILC_UMODE_HYPER |
+		  SILC_UMODE_BUSY |
+		  SILC_UMODE_INDISPOSED |
+		  SILC_UMODE_PAGE);
+
+	if (!strcmp(state, "hyper"))
+		mode |= SILC_UMODE_HYPER;
+	else if (!strcmp(state, "away"))
+		mode |= SILC_UMODE_GONE;
+	else if (!strcmp(state, "busy"))
+		mode |= SILC_UMODE_BUSY;
+	else if (!strcmp(state, "indisposed"))
+		mode |= SILC_UMODE_INDISPOSED;
+	else if (!strcmp(state, "page"))
+		mode |= SILC_UMODE_PAGE;
+
+	/* Send UMODE */
+	idp = silc_id_payload_encode(sg->conn->local_id, SILC_ID_CLIENT);
+	SILC_PUT32_MSB(mode, mb);
+	silc_client_command_send(sg->client, sg->conn, SILC_COMMAND_UMODE,
+				 ++sg->conn->cmd_ident, 2,
+				 1, idp->data, idp->len,
+				 2, mb, sizeof(mb));
+	silc_buffer_free(idp);
+}
+
+
+/*************************** Connection Routines *****************************/
+
+static void
+silcgaim_keepalive(GaimConnection *gc)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_send_packet(sg->client, sg->conn, SILC_PACKET_HEARTBEAT,
+				NULL, 0);
+}
+
+static int
+silcgaim_scheduler(gpointer *context)
+{
+	SilcGaim sg = (SilcGaim)context;
+	silc_client_run_one(sg->client);
+	return 1;
+}
+
+static void
+silcgaim_nickname_parse(const char *nickname,
+			char **ret_nickname)
+{
+	silc_parse_userfqdn(nickname, ret_nickname, NULL);
+}
+
+static void
+silcgaim_login_connected(gpointer data, gint source, const gchar *error_message)
+{
+	GaimConnection *gc = data;
+	SilcGaim sg;
+	SilcClient client;
+	SilcClientConnection conn;
+	GaimAccount *account;
+	SilcClientConnectionParams params;
+	const char *dfile;
+
+	g_return_if_fail(gc != NULL);
+
+	sg = gc->proto_data;
+
+	if (source < 0) {
+		gaim_connection_error(gc, _("Connection failed"));
+		return;
+	}
+
+	client = sg->client;
+	account = sg->account;
+
+	/* Get session detachment data, if available */
+	memset(&params, 0, sizeof(params));
+	dfile = silcgaim_session_file(gaim_account_get_username(sg->account));
+	params.detach_data = (unsigned char *)silc_file_readfile(dfile, &params.detach_data_len);
+	if (params.detach_data)
+		params.detach_data[params.detach_data_len] = 0;
+
+	/* Add connection to SILC client library */
+	conn = silc_client_add_connection(
+			  sg->client, &params,
+			  (char *)gaim_account_get_string(account, "server",
+							  "silc.silcnet.org"),
+			  gaim_account_get_int(account, "port", 706), sg);
+	if (!conn) {
+		gaim_connection_error(gc, _("Cannot initialize SILC Client connection"));
+		gc->proto_data = NULL;
+		return;
+	}
+	sg->conn = conn;
+
+	/* Progress */
+	if (params.detach_data) {
+		gaim_connection_update_progress(gc, _("Resuming session"), 2, 5);
+		sg->resuming = TRUE;
+	} else {
+		gaim_connection_update_progress(gc, _("Performing key exchange"), 2, 5);
+	}
+
+	/* Perform SILC Key Exchange.  The "silc_connected" will be called
+	   eventually. */
+	silc_client_start_key_exchange(sg->client, sg->conn, source);
+
+	/* Set default attributes */
+	if (!gaim_account_get_bool(account, "reject-attrs", FALSE)) {
+		SilcUInt32 mask;
+		const char *tmp;
+#ifdef SILC_ATTRIBUTE_USER_ICON
+		char *icon;
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+		struct utsname u;
+#endif
+
+		mask = SILC_ATTRIBUTE_MOOD_NORMAL;
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_STATUS_MOOD,
+					  SILC_32_TO_PTR(mask),
+					  sizeof(SilcUInt32));
+		mask = SILC_ATTRIBUTE_CONTACT_CHAT;
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_PREFERRED_CONTACT,
+					  SILC_32_TO_PTR(mask),
+					  sizeof(SilcUInt32));
+#ifdef HAVE_SYS_UTSNAME_H
+		if (!uname(&u)) {
+			SilcAttributeObjDevice dev;
+			memset(&dev, 0, sizeof(dev));
+			dev.type = SILC_ATTRIBUTE_DEVICE_COMPUTER;
+			dev.version = u.release;
+			dev.model = u.sysname;
+			silc_client_attribute_add(client, conn,
+						  SILC_ATTRIBUTE_DEVICE_INFO,
+						  (void *)&dev, sizeof(dev));
+		}
+#endif
+#ifdef _WIN32
+		tmp = _tzname[0];
+#else
+		tmp = tzname[0];
+#endif
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_TIMEZONE,
+					  (void *)tmp, strlen(tmp));
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+		/* Set our buddy icon */
+		icon = gaim_buddy_icons_get_full_path(gaim_account_get_buddy_icon(account));
+		silcgaim_buddy_set_icon(gc, icon);
+		g_free(icon);
+#endif
+	}
+
+	silc_free(params.detach_data);
+}
+
+static void
+silcgaim_login(GaimAccount *account)
+{
+	SilcGaim sg;
+	SilcClient client;
+	SilcClientParams params;
+	GaimConnection *gc;
+	char pkd[256], prd[256];
+	const char *cipher, *hmac;
+	char *realname;
+	int i;
+
+	gc = account->gc;
+	if (!gc)
+		return;
+	gc->proto_data = NULL;
+
+	memset(&params, 0, sizeof(params));
+	strcat(params.nickname_format, "%n@%h%a");
+	params.nickname_parse = silcgaim_nickname_parse;
+	params.ignore_requested_attributes =
+		gaim_account_get_bool(account, "reject-attrs", FALSE);
+
+	/* Allocate SILC client */
+	client = silc_client_alloc(&ops, &params, gc, NULL);
+	if (!client) {
+		gaim_connection_error(gc, _("Out of memory"));
+		return;
+	}
+
+	/* Get username, real name and local hostname for SILC library */
+	if (gaim_account_get_username(account)) {
+		const char *u = gaim_account_get_username(account);
+		char **up = g_strsplit(u, "@", 2);
+		client->username = strdup(up[0]);
+		g_strfreev(up);
+	} else {
+		client->username = silc_get_username();
+		gaim_account_set_username(account, client->username);
+	}
+	realname = silc_get_real_name();
+	if (gaim_account_get_user_info(account)) {
+		client->realname = strdup(gaim_account_get_user_info(account));
+		free(realname);
+	} else if ((silc_get_real_name() != NULL) && (*realname != '\0')) {
+		client->realname = realname;
+		gaim_account_set_user_info(account, client->realname);
+	} else {
+		free(realname);
+		client->realname = strdup(_("Gaim User"));
+	}
+	client->hostname = silc_net_localhost();
+
+	gaim_connection_set_display_name(gc, client->username);
+
+	/* Register requested cipher and HMAC */
+	cipher = gaim_account_get_string(account, "cipher", SILC_DEFAULT_CIPHER);
+	for (i = 0; silc_default_ciphers[i].name; i++)
+		if (!strcmp(silc_default_ciphers[i].name, cipher)) {
+			silc_cipher_register(&(silc_default_ciphers[i]));
+			break;
+		}
+	hmac = gaim_account_get_string(account, "hmac", SILC_DEFAULT_HMAC);
+	for (i = 0; silc_default_hmacs[i].name; i++)
+		if (!strcmp(silc_default_hmacs[i].name, hmac)) {
+			silc_hmac_register(&(silc_default_hmacs[i]));
+			break;
+		}
+
+	/* Init SILC client */
+	if (!silc_client_init(client)) {
+		gc->wants_to_die = TRUE;
+		gaim_connection_error(gc, _("Cannot initialize SILC protocol"));
+		return;
+	}
+
+	/* Check the ~/.silc dir and create it, and new key pair if necessary. */
+	if (!silcgaim_check_silc_dir(gc)) {
+		gc->wants_to_die = TRUE;
+		gaim_connection_error(gc, _("Cannot find/access ~/.silc directory"));
+		return;
+	}
+
+	/* Progress */
+	gaim_connection_update_progress(gc, _("Connecting to SILC Server"), 1, 5);
+
+	/* Load SILC key pair */
+	g_snprintf(pkd, sizeof(pkd), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcgaim_silcdir());
+	g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcgaim_silcdir());
+	if (!silc_load_key_pair((char *)gaim_account_get_string(account, "public-key", pkd),
+							(char *)gaim_account_get_string(account, "private-key", prd),
+				(gc->password == NULL) ? "" : gc->password, &client->pkcs,
+				&client->public_key, &client->private_key)) {
+		g_snprintf(pkd, sizeof(pkd), _("Could not load SILC key pair: %s"), strerror(errno));
+		gaim_connection_error(gc, pkd);
+		return;
+	}
+
+	sg = silc_calloc(1, sizeof(*sg));
+	if (!sg)
+		return;
+	memset(sg, 0, sizeof(*sg));
+	sg->client = client;
+	sg->gc = gc;
+	sg->account = account;
+	gc->proto_data = sg;
+
+	/* Connect to the SILC server */
+	if (gaim_proxy_connect(gc, account,
+			       gaim_account_get_string(account, "server",
+						       "silc.silcnet.org"),
+			       gaim_account_get_int(account, "port", 706),
+			       silcgaim_login_connected, gc) == NULL)
+	{
+		gaim_connection_error(gc, _("Unable to create connection"));
+		return;
+	}
+
+	/* Schedule SILC using Glib's event loop */
+#ifndef _WIN32
+	sg->scheduler = g_timeout_add(5, (GSourceFunc)silcgaim_scheduler, sg);
+#else
+	sg->scheduler = g_timeout_add(300, (GSourceFunc)silcgaim_scheduler, sg);
+#endif
+}
+
+static int
+silcgaim_close_final(gpointer *context)
+{
+	SilcGaim sg = (SilcGaim)context;
+	silc_client_stop(sg->client);
+	silc_client_free(sg->client);
+#ifdef HAVE_SILCMIME_H
+	if (sg->mimeass)
+		silc_mime_assembler_free(sg->mimeass);
+#endif
+	silc_free(sg);
+	return 0;
+}
+
+static void
+silcgaim_close(GaimConnection *gc)
+{
+	SilcGaim sg = gc->proto_data;
+
+	g_return_if_fail(sg != NULL);
+
+	/* Send QUIT */
+	silc_client_command_call(sg->client, sg->conn, NULL,
+				 "QUIT", "Download Gaim: " GAIM_WEBSITE, NULL);
+
+	if (sg->conn)
+		silc_client_close_connection(sg->client, sg->conn);
+
+	g_source_remove(sg->scheduler);
+	g_timeout_add(1, (GSourceFunc)silcgaim_close_final, sg);
+}
+
+
+/****************************** Protocol Actions *****************************/
+
+static void
+silcgaim_attrs_cancel(GaimConnection *gc, GaimRequestFields *fields)
+{
+	/* Nothing */
+}
+
+static void
+silcgaim_attrs_cb(GaimConnection *gc, GaimRequestFields *fields)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	GaimRequestField *f;
+	char *tmp;
+	SilcUInt32 tmp_len, mask;
+	SilcAttributeObjService service;
+	SilcAttributeObjDevice dev;
+	SilcVCardStruct vcard;
+	const char *val;
+
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	memset(&service, 0, sizeof(service));
+	memset(&dev, 0, sizeof(dev));
+	memset(&vcard, 0, sizeof(vcard));
+
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_USER_INFO, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_SERVICE, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_STATUS_MOOD, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_STATUS_FREETEXT, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_STATUS_MESSAGE, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_PREFERRED_LANGUAGE, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_PREFERRED_CONTACT, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_TIMEZONE, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_GEOLOCATION, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_DEVICE_INFO, NULL);
+
+	/* Set mood */
+	mask = 0;
+	f = gaim_request_fields_get_field(fields, "mood_normal");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_NORMAL;
+	f = gaim_request_fields_get_field(fields, "mood_happy");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_HAPPY;
+	f = gaim_request_fields_get_field(fields, "mood_sad");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_SAD;
+	f = gaim_request_fields_get_field(fields, "mood_angry");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_ANGRY;
+	f = gaim_request_fields_get_field(fields, "mood_jealous");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_JEALOUS;
+	f = gaim_request_fields_get_field(fields, "mood_ashamed");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_ASHAMED;
+	f = gaim_request_fields_get_field(fields, "mood_invincible");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_INVINCIBLE;
+	f = gaim_request_fields_get_field(fields, "mood_inlove");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_INLOVE;
+	f = gaim_request_fields_get_field(fields, "mood_sleepy");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_SLEEPY;
+	f = gaim_request_fields_get_field(fields, "mood_bored");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_BORED;
+	f = gaim_request_fields_get_field(fields, "mood_excited");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_EXCITED;
+	f = gaim_request_fields_get_field(fields, "mood_anxious");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_ANXIOUS;
+	silc_client_attribute_add(client, conn,
+				  SILC_ATTRIBUTE_STATUS_MOOD,
+				  SILC_32_TO_PTR(mask),
+				  sizeof(SilcUInt32));
+
+	/* Set preferred contact */
+	mask = 0;
+	f = gaim_request_fields_get_field(fields, "contact_chat");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_CHAT;
+	f = gaim_request_fields_get_field(fields, "contact_email");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_EMAIL;
+	f = gaim_request_fields_get_field(fields, "contact_call");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_CALL;
+	f = gaim_request_fields_get_field(fields, "contact_sms");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_SMS;
+	f = gaim_request_fields_get_field(fields, "contact_mms");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_MMS;
+	f = gaim_request_fields_get_field(fields, "contact_video");
+	if (f && gaim_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_VIDEO;
+	if (mask)
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_PREFERRED_CONTACT,
+					  SILC_32_TO_PTR(mask),
+					  sizeof(SilcUInt32));
+
+	/* Set status text */
+	val = NULL;
+	f = gaim_request_fields_get_field(fields, "status_text");
+	if (f)
+		val = gaim_request_field_string_get_value(f);
+	if (val && *val)
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_STATUS_FREETEXT,
+					  (void *)val, strlen(val));
+
+	/* Set vcard */
+	val = NULL;
+	f = gaim_request_fields_get_field(fields, "vcard");
+	if (f)
+		val = gaim_request_field_string_get_value(f);
+	if (val && *val) {
+		gaim_account_set_string(sg->account, "vcard", val);
+		tmp = silc_file_readfile(val, &tmp_len);
+		if (tmp) {
+			tmp[tmp_len] = 0;
+			if (silc_vcard_decode((unsigned char *)tmp, tmp_len, &vcard))
+				silc_client_attribute_add(client, conn,
+							  SILC_ATTRIBUTE_USER_INFO,
+							  (void *)&vcard,
+							  sizeof(vcard));
+		}
+		silc_vcard_free(&vcard);
+		silc_free(tmp);
+	} else {
+		gaim_account_set_string(sg->account, "vcard", "");
+	}
+
+#ifdef HAVE_SYS_UTSNAME_H
+	/* Set device info */
+	f = gaim_request_fields_get_field(fields, "device");
+	if (f && gaim_request_field_bool_get_value(f)) {
+		struct utsname u;
+		if (!uname(&u)) {
+			dev.type = SILC_ATTRIBUTE_DEVICE_COMPUTER;
+			dev.version = u.release;
+			dev.model = u.sysname;
+			silc_client_attribute_add(client, conn,
+						  SILC_ATTRIBUTE_DEVICE_INFO,
+						  (void *)&dev, sizeof(dev));
+		}
+	}
+#endif
+
+	/* Set timezone */
+	val = NULL;
+	f = gaim_request_fields_get_field(fields, "timezone");
+	if (f)
+		val = gaim_request_field_string_get_value(f);
+	if (val && *val)
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_TIMEZONE,
+					  (void *)val, strlen(val));
+}
+
+static void
+silcgaim_attrs(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	GaimRequestFields *fields;
+	GaimRequestFieldGroup *g;
+	GaimRequestField *f;
+	SilcHashTable attrs;
+	SilcAttributePayload attr;
+	gboolean mnormal = TRUE, mhappy = FALSE, msad = FALSE,
+		mangry = FALSE, mjealous = FALSE, mashamed = FALSE,
+		minvincible = FALSE, minlove = FALSE, msleepy = FALSE,
+		mbored = FALSE, mexcited = FALSE, manxious = FALSE;
+	gboolean cemail = FALSE, ccall = FALSE, csms = FALSE,
+		cmms = FALSE, cchat = TRUE, cvideo = FALSE;
+	gboolean device = TRUE;
+	char status[1024];
+
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	memset(status, 0, sizeof(status));
+
+	attrs = silc_client_attributes_get(client, conn);
+	if (attrs) {
+		if (silc_hash_table_find(attrs,
+					 SILC_32_TO_PTR(SILC_ATTRIBUTE_STATUS_MOOD),
+					 NULL, (void *)&attr)) {
+			SilcUInt32 mood = 0;
+			silc_attribute_get_object(attr, &mood, sizeof(mood));
+			mnormal = !mood;
+			mhappy = (mood & SILC_ATTRIBUTE_MOOD_HAPPY);
+			msad = (mood & SILC_ATTRIBUTE_MOOD_SAD);
+			mangry = (mood & SILC_ATTRIBUTE_MOOD_ANGRY);
+			mjealous = (mood & SILC_ATTRIBUTE_MOOD_JEALOUS);
+			mashamed = (mood & SILC_ATTRIBUTE_MOOD_ASHAMED);
+			minvincible = (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE);
+			minlove = (mood & SILC_ATTRIBUTE_MOOD_INLOVE);
+			msleepy = (mood & SILC_ATTRIBUTE_MOOD_SLEEPY);
+			mbored = (mood & SILC_ATTRIBUTE_MOOD_BORED);
+			mexcited = (mood & SILC_ATTRIBUTE_MOOD_EXCITED);
+			manxious = (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS);
+		}
+
+		if (silc_hash_table_find(attrs,
+					 SILC_32_TO_PTR(SILC_ATTRIBUTE_PREFERRED_CONTACT),
+					 NULL, (void *)&attr)) {
+			SilcUInt32 contact = 0;
+			silc_attribute_get_object(attr, &contact, sizeof(contact));
+			cemail = (contact & SILC_ATTRIBUTE_CONTACT_EMAIL);
+			ccall = (contact & SILC_ATTRIBUTE_CONTACT_CALL);
+			csms = (contact & SILC_ATTRIBUTE_CONTACT_SMS);
+			cmms = (contact & SILC_ATTRIBUTE_CONTACT_MMS);
+			cchat = (contact & SILC_ATTRIBUTE_CONTACT_CHAT);
+			cvideo = (contact & SILC_ATTRIBUTE_CONTACT_VIDEO);
+		}
+
+		if (silc_hash_table_find(attrs,
+					 SILC_32_TO_PTR(SILC_ATTRIBUTE_STATUS_FREETEXT),
+					 NULL, (void *)&attr))
+			silc_attribute_get_object(attr, &status, sizeof(status));
+
+		if (!silc_hash_table_find(attrs,
+					  SILC_32_TO_PTR(SILC_ATTRIBUTE_DEVICE_INFO),
+					  NULL, (void *)&attr))
+			device = FALSE;
+	}
+
+	fields = gaim_request_fields_new();
+
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_label_new("l3", _("Your Current Mood"));
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_normal", _("Normal"), mnormal);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_happy", _("Happy"), mhappy);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_sad", _("Sad"), msad);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_angry", _("Angry"), mangry);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_jealous", _("Jealous"), mjealous);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_ashamed", _("Ashamed"), mashamed);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_invincible", _("Invincible"), minvincible);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_inlove", _("In love"), minlove);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_sleepy", _("Sleepy"), msleepy);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_bored", _("Bored"), mbored);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_excited", _("Excited"), mexcited);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("mood_anxious", _("Anxious"), manxious);
+	gaim_request_field_group_add_field(g, f);
+
+	f = gaim_request_field_label_new("l4", _("\nYour Preferred Contact Methods"));
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("contact_chat", _("Chat"), cchat);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("contact_email", _("E-mail"), cemail);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("contact_call", _("Phone"), ccall);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("contact_sms", _("SMS"), csms);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("contact_mms", _("MMS"), cmms);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("contact_video", _("Video conferencing"), cvideo);
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_fields_add_group(fields, g);
+
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_string_new("status_text", _("Your Current Status"),
+					  status[0] ? status : NULL, TRUE);
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_fields_add_group(fields, g);
+
+	g = gaim_request_field_group_new(NULL);
+#if 0
+	f = gaim_request_field_label_new("l2", _("Online Services"));
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_bool_new("services",
+					_("Let others see what services you are using"),
+					TRUE);
+	gaim_request_field_group_add_field(g, f);
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+	f = gaim_request_field_bool_new("device",
+					_("Let others see what computer you are using"),
+					device);
+	gaim_request_field_group_add_field(g, f);
+#endif
+	gaim_request_fields_add_group(fields, g);
+
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_string_new("vcard", _("Your VCard File"),
+					  gaim_account_get_string(sg->account, "vcard", ""),
+					  FALSE);
+	gaim_request_field_group_add_field(g, f);
+#ifdef _WIN32
+	f = gaim_request_field_string_new("timezone", _("Timezone"), _tzname[0], FALSE);
+#else
+	f = gaim_request_field_string_new("timezone", _("Timezone"), tzname[0], FALSE);
+#endif
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_fields_add_group(fields, g);
+
+	gaim_request_fields(gc, _("User Online Status Attributes"),
+			    _("User Online Status Attributes"),
+			    _("You can let other users see your online status information "
+			      "and your personal information. Please fill the information "
+			      "you would like other users to see about yourself."),
+			    fields,
+			    _("OK"), G_CALLBACK(silcgaim_attrs_cb),
+			    _("Cancel"), G_CALLBACK(silcgaim_attrs_cancel), gc);
+}
+
+static void
+silcgaim_detach(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	SilcGaim sg;
+
+	if (!gc)
+		return;
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	/* Call DETACH */
+	silc_client_command_call(sg->client, sg->conn, "DETACH");
+	sg->detaching = TRUE;
+}
+
+static void
+silcgaim_view_motd(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	SilcGaim sg;
+	char *tmp;
+
+	if (!gc)
+		return;
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	if (!sg->motd) {
+		gaim_notify_error(
+		     gc, _("Message of the Day"), _("No Message of the Day available"),
+		     _("There is no Message of the Day associated with this connection"));
+		return;
+	}
+
+	tmp = g_markup_escape_text(sg->motd, -1);
+	gaim_notify_formatted(gc, NULL, _("Message of the Day"), NULL,
+			      tmp, NULL, NULL);
+	g_free(tmp);
+}
+
+static void
+silcgaim_create_keypair_cancel(GaimConnection *gc, GaimRequestFields *fields)
+{
+	/* Nothing */
+}
+
+static void
+silcgaim_create_keypair_cb(GaimConnection *gc, GaimRequestFields *fields)
+{
+	SilcGaim sg = gc->proto_data;
+	GaimRequestField *f;
+	const char *val, *pkfile = NULL, *prfile = NULL;
+	const char *pass1 = NULL, *pass2 = NULL, *un = NULL, *hn = NULL;
+	const char *rn = NULL, *e = NULL, *o = NULL, *c = NULL;
+	char *identifier;
+	int keylen = SILCGAIM_DEF_PKCS_LEN;
+	SilcPublicKey public_key;
+
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	val = NULL;
+	f = gaim_request_fields_get_field(fields, "pass1");
+	if (f)
+		val = gaim_request_field_string_get_value(f);
+	if (val && *val)
+		pass1 = val;
+	else
+		pass1 = "";
+	val = NULL;
+	f = gaim_request_fields_get_field(fields, "pass2");
+	if (f)
+		val = gaim_request_field_string_get_value(f);
+	if (val && *val)
+		pass2 = val;
+	else
+		pass2 = "";
+
+	if (strcmp(pass1, pass2)) {
+		gaim_notify_error(
+		     gc, _("Create New SILC Key Pair"), _("Passphrases do not match"), NULL);
+		return;
+	}
+
+	val = NULL;
+	f = gaim_request_fields_get_field(fields, "key");
+	if (f)
+		val = gaim_request_field_string_get_value(f);
+	if (val && *val)
+		keylen = atoi(val);
+	f = gaim_request_fields_get_field(fields, "pkfile");
+	if (f)
+		pkfile = gaim_request_field_string_get_value(f);
+	f = gaim_request_fields_get_field(fields, "prfile");
+	if (f)
+		prfile = gaim_request_field_string_get_value(f);
+
+	f = gaim_request_fields_get_field(fields, "un");
+	if (f)
+		un = gaim_request_field_string_get_value(f);
+	f = gaim_request_fields_get_field(fields, "hn");
+	if (f)
+		hn = gaim_request_field_string_get_value(f);
+	f = gaim_request_fields_get_field(fields, "rn");
+	if (f)
+		rn = gaim_request_field_string_get_value(f);
+	f = gaim_request_fields_get_field(fields, "e");
+	if (f)
+		e = gaim_request_field_string_get_value(f);
+	f = gaim_request_fields_get_field(fields, "o");
+	if (f)
+		o = gaim_request_field_string_get_value(f);
+	f = gaim_request_fields_get_field(fields, "c");
+	if (f)
+		c = gaim_request_field_string_get_value(f);
+
+	identifier = silc_pkcs_encode_identifier((char *)un, (char *)hn,
+						 (char *)rn, (char *)e, (char *)o, (char *)c);
+
+	/* Create the key pair */
+	if (!silc_create_key_pair(SILCGAIM_DEF_PKCS, keylen, pkfile, prfile,
+				  identifier, pass1, NULL, &public_key, NULL,
+				  FALSE)) {
+		gaim_notify_error(
+		     gc, _("Create New SILC Key Pair"), _("Key Pair Generation failed"), NULL);
+		return;
+	}
+
+	silcgaim_show_public_key(sg, NULL, public_key, NULL, NULL);
+
+	silc_pkcs_public_key_free(public_key);
+	silc_free(identifier);
+}
+
+static void
+silcgaim_create_keypair(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	SilcGaim sg = gc->proto_data;
+	GaimRequestFields *fields;
+	GaimRequestFieldGroup *g;
+	GaimRequestField *f;
+	const char *username, *realname;
+	char *hostname, **u;
+	char tmp[256], pkd[256], pkd2[256], prd[256], prd2[256];
+
+	username = gaim_account_get_username(sg->account);
+	u = g_strsplit(username, "@", 2);
+	username = u[0];
+	realname = gaim_account_get_user_info(sg->account);
+	hostname = silc_net_localhost();
+	g_snprintf(tmp, sizeof(tmp), "%s@%s", username, hostname);
+
+	g_snprintf(pkd2, sizeof(pkd2), "%s" G_DIR_SEPARATOR_S"public_key.pub", silcgaim_silcdir());
+	g_snprintf(prd2, sizeof(prd2), "%s" G_DIR_SEPARATOR_S"private_key.prv", silcgaim_silcdir());
+	g_snprintf(pkd, sizeof(pkd) - 1, "%s",
+		   gaim_account_get_string(gc->account, "public-key", pkd2));
+	g_snprintf(prd, sizeof(prd) - 1, "%s",
+		   gaim_account_get_string(gc->account, "private-key", prd2));
+
+	fields = gaim_request_fields_new();
+
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_string_new("key", _("Key length"), "2048", FALSE);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_string_new("pkfile", _("Public key file"), pkd, FALSE);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_string_new("prfile", _("Private key file"), prd, FALSE);
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_fields_add_group(fields, g);
+
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_string_new("un", _("Username"), username ? username : "", FALSE);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_string_new("hn", _("Hostname"), hostname ? hostname : "", FALSE);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_string_new("rn", _("Real name"), realname ? realname : "", FALSE);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_string_new("e", _("E-mail"), tmp, FALSE);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_string_new("o", _("Organization"), "", FALSE);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_string_new("c", _("Country"), "", FALSE);
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_fields_add_group(fields, g);
+
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_string_new("pass1", _("Passphrase"), "", FALSE);
+	gaim_request_field_string_set_masked(f, TRUE);
+	gaim_request_field_group_add_field(g, f);
+	f = gaim_request_field_string_new("pass2", _("Passphrase (retype)"), "", FALSE);
+	gaim_request_field_string_set_masked(f, TRUE);
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_fields_add_group(fields, g);
+
+	gaim_request_fields(gc, _("Create New SILC Key Pair"),
+			    _("Create New SILC Key Pair"), NULL, fields,
+			    _("Generate Key Pair"), G_CALLBACK(silcgaim_create_keypair_cb),
+			    _("Cancel"), G_CALLBACK(silcgaim_create_keypair_cancel), gc);
+
+	g_strfreev(u);
+	silc_free(hostname);
+}
+
+static void
+silcgaim_change_pass(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	gaim_account_request_change_password(gaim_connection_get_account(gc));
+}
+
+static void
+silcgaim_change_passwd(GaimConnection *gc, const char *old, const char *new)
+{
+        char prd[256];
+	g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.pub", silcgaim_silcdir());
+	silc_change_private_key_passphrase(gaim_account_get_string(gc->account,
+								   "private-key",
+								   prd), old, new);
+}
+
+static void
+silcgaim_show_set_info(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	gaim_account_request_change_user_info(gaim_connection_get_account(gc));
+}
+
+static void
+silcgaim_set_info(GaimConnection *gc, const char *text)
+{
+}
+
+static GList *
+silcgaim_actions(GaimPlugin *plugin, gpointer context)
+{
+	GaimConnection *gc = context;
+	GList *list = NULL;
+	GaimPluginAction *act;
+
+	if (!gaim_account_get_bool(gc->account, "reject-attrs", FALSE)) {
+		act = gaim_plugin_action_new(_("Online Status"),
+				silcgaim_attrs);
+		list = g_list_append(list, act);
+	}
+
+	act = gaim_plugin_action_new(_("Detach From Server"),
+			silcgaim_detach);
+	list = g_list_append(list, act);
+
+	act = gaim_plugin_action_new(_("View Message of the Day"),
+			silcgaim_view_motd);
+	list = g_list_append(list, act);
+
+	act = gaim_plugin_action_new(_("Create SILC Key Pair..."),
+			silcgaim_create_keypair);
+	list = g_list_append(list, act);
+
+	act = gaim_plugin_action_new(_("Change Password..."),
+			silcgaim_change_pass);
+	list = g_list_append(list, act);
+
+	act = gaim_plugin_action_new(_("Set User Info..."),
+			silcgaim_show_set_info);
+	list = g_list_append(list, act);
+
+	return list;
+}
+
+
+/******************************* IM Routines *********************************/
+
+typedef struct {
+	char *nick;
+	char *message;
+	SilcUInt32 message_len;
+	SilcMessageFlags flags;
+	GaimMessageFlags gflags;
+} *SilcGaimIM;
+
+static void
+silcgaim_send_im_resolved(SilcClient client,
+			  SilcClientConnection conn,
+			  SilcClientEntry *clients,
+			  SilcUInt32 clients_count,
+			  void *context)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	SilcGaimIM im = context;
+	GaimConversation *convo;
+	char tmp[256], *nickname = NULL;
+	SilcClientEntry client_entry;
+#ifdef HAVE_SILCMIME_H
+	SilcDList list;
+#endif
+
+	convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, im->nick,
+							sg->account);
+	if (!convo)
+		return;
+
+	if (!clients)
+		goto err;
+
+	if (clients_count > 1) {
+		silc_parse_userfqdn(im->nick, &nickname, NULL);
+
+		/* Find the correct one. The im->nick might be a formatted nick
+		   so this will find the correct one. */
+		clients = silc_client_get_clients_local(client, conn,
+							nickname, im->nick,
+							&clients_count);
+		if (!clients)
+			goto err;
+		client_entry = clients[0];
+		silc_free(clients);
+	} else {
+		client_entry = clients[0];
+	}
+
+#ifdef HAVE_SILCMIME_H
+	/* Check for images */
+	if (im->gflags & GAIM_MESSAGE_IMAGES) {
+		list = silcgaim_image_message(im->message, (SilcUInt32 *)&im->flags);
+		if (list) {
+			/* Send one or more MIME message.  If more than one, they
+			   are MIME fragments due to over large message */
+			SilcBuffer buf;
+
+			silc_dlist_start(list);
+			while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
+				silc_client_send_private_message(client, conn,
+								 client_entry, im->flags,
+								 buf->data, buf->len,
+								 TRUE);
+			silc_mime_partial_free(list);
+			gaim_conv_im_write(GAIM_CONV_IM(convo), conn->local_entry->nickname,
+				   im->message, 0, time(NULL));
+			goto out;
+		}
+	}
+#endif
+
+	/* Send the message */
+	silc_client_send_private_message(client, conn, client_entry, im->flags,
+					 (unsigned char *)im->message, im->message_len, TRUE);
+	gaim_conv_im_write(GAIM_CONV_IM(convo), conn->local_entry->nickname,
+			   im->message, 0, time(NULL));
+	goto out;
+
+ err:
+	g_snprintf(tmp, sizeof(tmp),
+		   _("User <I>%s</I> is not present in the network"), im->nick);
+	gaim_conversation_write(convo, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL));
+
+ out:
+	g_free(im->nick);
+	g_free(im->message);
+	silc_free(im);
+	silc_free(nickname);
+}
+
+static int
+silcgaim_send_im(GaimConnection *gc, const char *who, const char *message,
+		 GaimMessageFlags flags)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count, mflags;
+	char *nickname, *msg, *tmp;
+	int ret = 0;
+	gboolean sign = gaim_account_get_bool(sg->account, "sign-verify", FALSE);
+#ifdef HAVE_SILCMIME_H
+	SilcDList list;
+#endif
+
+	if (!who || !message)
+		return 0;
+
+	mflags = SILC_MESSAGE_FLAG_UTF8;
+
+	tmp = msg = gaim_unescape_html(message);
+
+	if (!g_ascii_strncasecmp(msg, "/me ", 4)) {
+		msg += 4;
+		if (!*msg) {
+			g_free(tmp);
+			return 0;
+		}
+		mflags |= SILC_MESSAGE_FLAG_ACTION;
+	} else if (strlen(msg) > 1 && msg[0] == '/') {
+		if (!silc_client_command_call(client, conn, msg + 1))
+			gaim_notify_error(gc, _("Call Command"), _("Cannot call command"),
+					_("Unknown command"));
+		g_free(tmp);
+		return 0;
+	}
+
+
+	if (!silc_parse_userfqdn(who, &nickname, NULL)) {
+		g_free(tmp);
+		return 0;
+	}
+
+	if (sign)
+		mflags |= SILC_MESSAGE_FLAG_SIGNED;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(client, conn, nickname, who,
+						&clients_count);
+	if (!clients) {
+		/* Resolve unknown user */
+		SilcGaimIM im = silc_calloc(1, sizeof(*im));
+		if (!im) {
+			g_free(tmp);
+			return 0;
+		}
+		im->nick = g_strdup(who);
+		im->message = g_strdup(message);
+		im->message_len = strlen(im->message);
+		im->flags = mflags;
+		im->gflags = flags;
+		silc_client_get_clients(client, conn, nickname, NULL,
+					silcgaim_send_im_resolved, im);
+		silc_free(nickname);
+		g_free(tmp);
+		return 0;
+	}
+
+#ifdef HAVE_SILCMIME_H
+	/* Check for images */
+	if (flags & GAIM_MESSAGE_IMAGES) {
+		list = silcgaim_image_message(message, &mflags);
+		if (list) {
+			/* Send one or more MIME message.  If more than one, they
+			   are MIME fragments due to over large message */
+			SilcBuffer buf;
+
+			silc_dlist_start(list);
+			while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
+				ret =
+			 	silc_client_send_private_message(client, conn,
+								 clients[0], mflags,
+								 buf->data, buf->len,
+								 TRUE);
+			silc_mime_partial_free(list);
+			g_free(tmp);
+			silc_free(nickname);
+			silc_free(clients);
+			return ret;
+		}
+	}
+#endif
+
+	/* Send private message directly */
+	ret = silc_client_send_private_message(client, conn, clients[0],
+					       mflags,
+					       (unsigned char *)msg,
+					       strlen(msg), TRUE);
+
+	g_free(tmp);
+	silc_free(nickname);
+	silc_free(clients);
+	return ret;
+}
+
+
+static GList *silcgaim_blist_node_menu(GaimBlistNode *node) {
+	/* split this single menu building function back into the two
+	   original: one for buddies and one for chats */
+
+	if(GAIM_BLIST_NODE_IS_CHAT(node)) {
+		return silcgaim_chat_menu((GaimChat *) node);
+	} else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
+		return silcgaim_buddy_menu((GaimBuddy *) node);
+	} else {
+		g_return_val_if_reached(NULL);
+	}
+}
+
+/********************************* Commands **********************************/
+
+static GaimCmdRet silcgaim_cmd_chat_part(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	GaimConversation *convo = conv;
+	int id = 0;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	if(args && args[0])
+		convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, args[0],
+									gc->account);
+
+	if (convo != NULL)
+		id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo));
+
+	if (id == 0)
+		return GAIM_CMD_RET_FAILED;
+
+	silcgaim_chat_leave(gc, id);
+
+	return GAIM_CMD_RET_OK;
+
+}
+
+static GaimCmdRet silcgaim_cmd_chat_topic(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	int id = 0;
+	char *buf, *tmp, *tmp2;
+	const char *topic;
+
+	gc = gaim_conversation_get_gc(conv);
+	id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv));
+
+	if (gc == NULL || id == 0)
+		return GAIM_CMD_RET_FAILED;
+
+	if (!args || !args[0]) {
+		topic = gaim_conv_chat_get_topic (GAIM_CONV_CHAT(conv));
+		if (topic) {
+			tmp = g_markup_escape_text(topic, -1);
+			tmp2 = gaim_markup_linkify(tmp);
+			buf = g_strdup_printf(_("current topic is: %s"), tmp2);
+			g_free(tmp);
+			g_free(tmp2);
+		} else
+			buf = g_strdup(_("No topic is set"));
+		gaim_conv_chat_write(GAIM_CONV_CHAT(conv), gc->account->username, buf,
+							 GAIM_MESSAGE_SYSTEM|GAIM_MESSAGE_NO_LOG, time(NULL));
+		g_free(buf);
+
+	}
+
+	if (args && args[0] && (strlen(args[0]) > 255)) {
+		*error = g_strdup(_("Topic too long"));
+		return GAIM_CMD_RET_FAILED;
+	}
+
+	silcgaim_chat_set_topic(gc, id, args ? args[0] : NULL);
+
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_chat_join(GaimConversation *conv,
+        const char *cmd, char **args, char **error, void *data)
+{
+	GHashTable *comp;
+
+	if(!args || !args[0])
+		return GAIM_CMD_RET_FAILED;
+
+	comp = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+	g_hash_table_replace(comp, "channel", args[0]);
+	if(args[1])
+		g_hash_table_replace(comp, "passphrase", args[1]);
+
+	silcgaim_chat_join(gaim_conversation_get_gc(conv), comp);
+
+	g_hash_table_destroy(comp);
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_chat_list(GaimConversation *conv,
+        const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	gc = gaim_conversation_get_gc(conv);
+	gaim_roomlist_show_with_account(gaim_connection_get_account(gc));
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_whois(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	silcgaim_get_info(gc, args[0]);
+
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_msg(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	int ret;
+	GaimConnection *gc;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	ret = silcgaim_send_im(gc, args[0], args[1], GAIM_MESSAGE_SEND);
+
+	if (ret)
+		return GAIM_CMD_RET_OK;
+	else
+		return GAIM_CMD_RET_FAILED;
+}
+
+static GaimCmdRet silcgaim_cmd_query(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	int ret = 1;
+	GaimConversation *convo;
+	GaimConnection *gc;
+	GaimAccount *account;
+
+	if (!args || !args[0]) {
+		*error = g_strdup(_("You must specify a nick"));
+		return GAIM_CMD_RET_FAILED;
+	}
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	account = gaim_connection_get_account(gc);
+
+	convo = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, args[0]);
+
+	if (args[1]) {
+		ret = silcgaim_send_im(gc, args[0], args[1], GAIM_MESSAGE_SEND);
+		gaim_conv_im_write(GAIM_CONV_IM(convo), gaim_connection_get_display_name(gc),
+				args[1], GAIM_MESSAGE_SEND, time(NULL));
+	}
+
+	if (ret)
+		return GAIM_CMD_RET_OK;
+	else
+		return GAIM_CMD_RET_FAILED;
+}
+
+static GaimCmdRet silcgaim_cmd_motd(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	SilcGaim sg;
+	char *tmp;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	if (!sg->motd) {
+		*error = g_strdup(_("There is no Message of the Day associated with this connection"));
+		return GAIM_CMD_RET_FAILED;
+	}
+
+	tmp = g_markup_escape_text(sg->motd, -1);
+	gaim_notify_formatted(gc, NULL, _("Message of the Day"), NULL,
+			tmp, NULL, NULL);
+	g_free(tmp);
+
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_detach(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	SilcGaim sg;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	silc_client_command_call(sg->client, sg->conn, "DETACH");
+	sg->detaching = TRUE;
+
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_cmode(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	SilcGaim sg;
+	SilcChannelEntry channel;
+	char *silccmd, *silcargs, *msg, tmp[256];
+	const char *chname;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL || !args || gc->proto_data == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (args[0])
+		chname = args[0];
+	else
+		chname = gaim_conversation_get_name(conv);
+
+	if (!args[1]) {
+		channel = silc_client_get_channel(sg->client, sg->conn,
+										  (char *)chname);
+		if (!channel) {
+			*error = g_strdup_printf(_("channel %s not found"), chname);
+			return GAIM_CMD_RET_FAILED;
+		}
+		if (channel->mode) {
+			silcgaim_get_chmode_string(channel->mode, tmp, sizeof(tmp));
+			msg = g_strdup_printf(_("channel modes for %s: %s"), chname, tmp);
+		} else {
+			msg = g_strdup_printf(_("no channel modes are set on %s"), chname);
+		}
+		gaim_conv_chat_write(GAIM_CONV_CHAT(conv), "",
+							 msg, GAIM_MESSAGE_SYSTEM|GAIM_MESSAGE_NO_LOG, time(NULL));
+		g_free(msg);
+		return GAIM_CMD_RET_OK;
+	}
+
+	silcargs = g_strjoinv(" ", args);
+	silccmd = g_strconcat(cmd, " ", args ? silcargs : NULL, NULL);
+	g_free(silcargs);
+	if (!silc_client_command_call(sg->client, sg->conn, silccmd)) {
+		g_free(silccmd);
+		*error = g_strdup_printf(_("Failed to set cmodes for %s"), args[0]);
+		return GAIM_CMD_RET_FAILED;
+	}
+	g_free(silccmd);
+
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_generic(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	SilcGaim sg;
+	char *silccmd, *silcargs;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	silcargs = g_strjoinv(" ", args);
+	silccmd = g_strconcat(cmd, " ", args ? silcargs : NULL, NULL);
+	g_free(silcargs);
+	if (!silc_client_command_call(sg->client, sg->conn, silccmd)) {
+		g_free(silccmd);
+		*error = g_strdup_printf(_("Unknown command: %s, (may be a Gaim bug)"), cmd);
+		return GAIM_CMD_RET_FAILED;
+	}
+	g_free(silccmd);
+
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_quit(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	SilcGaim sg;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	silc_client_command_call(sg->client, sg->conn, NULL,
+				 "QUIT", (args && args[0]) ? args[0] : "Download Gaim: " GAIM_WEBSITE, NULL);
+
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_call(GaimConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	GaimConnection *gc;
+	SilcGaim sg;
+
+	gc = gaim_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	if (!silc_client_command_call(sg->client, sg->conn, args[0])) {
+		*error = g_strdup_printf(_("Unknown command: %s"), args[0]);
+		return GAIM_CMD_RET_FAILED;
+	}
+
+	return GAIM_CMD_RET_OK;
+}
+
+
+/************************** Plugin Initialization ****************************/
+
+static void
+silcgaim_register_commands(void)
+{
+	gaim_cmd_register("part", "w", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
+			GAIM_CMD_FLAG_PRPL_ONLY | GAIM_CMD_FLAG_ALLOW_WRONG_ARGS,
+			"prpl-silc", silcgaim_cmd_chat_part, _("part [channel]:  Leave the chat"), NULL);
+	gaim_cmd_register("leave", "w", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
+			GAIM_CMD_FLAG_PRPL_ONLY | GAIM_CMD_FLAG_ALLOW_WRONG_ARGS,
+			"prpl-silc", silcgaim_cmd_chat_part, _("leave [channel]:  Leave the chat"), NULL);
+	gaim_cmd_register("topic", "s", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc",
+			silcgaim_cmd_chat_topic, _("topic [&lt;new topic&gt;]:  View or change the topic"), NULL);
+	gaim_cmd_register("join", "ws", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
+			GAIM_CMD_FLAG_PRPL_ONLY | GAIM_CMD_FLAG_ALLOW_WRONG_ARGS,
+			"prpl-silc", silcgaim_cmd_chat_join,
+			_("join &lt;channel&gt; [&lt;password&gt;]:  Join a chat on this network"), NULL);
+	gaim_cmd_register("list", "", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc",
+			silcgaim_cmd_chat_list, _("list:  List channels on this network"), NULL);
+	gaim_cmd_register("whois", "w", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc",
+			silcgaim_cmd_whois, _("whois &lt;nick&gt;:  View nick's information"), NULL);
+	gaim_cmd_register("msg", "ws", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_msg,
+			_("msg &lt;nick&gt; &lt;message&gt;:  Send a private message to a user"), NULL);
+	gaim_cmd_register("query", "ws", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_query,
+			_("query &lt;nick&gt; [&lt;message&gt;]:  Send a private message to a user"), NULL);
+	gaim_cmd_register("motd", "", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_motd,
+			_("motd:  View the server's Message Of The Day"), NULL);
+	gaim_cmd_register("detach", "", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_detach,
+			_("detach:  Detach this session"), NULL);
+	gaim_cmd_register("quit", "s", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_quit,
+			_("quit [message]:  Disconnect from the server, with an optional message"), NULL);
+	gaim_cmd_register("call", "s", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_call,
+			_("call &lt;command&gt;:  Call any silc client command"), NULL);
+	/* These below just get passed through for the silc client library to deal
+	 * with */
+	gaim_cmd_register("kill", "ws", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+			_("kill &lt;nick&gt; [-pubkey|&lt;reason&gt;]:  Kill nick"), NULL);
+	gaim_cmd_register("nick", "w", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_generic,
+			_("nick &lt;newnick&gt;:  Change your nickname"), NULL);
+	gaim_cmd_register("whowas", "ww", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+			_("whowas &lt;nick&gt;:  View nick's information"), NULL);
+	gaim_cmd_register("cmode", "wws", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_cmode,
+			_("cmode &lt;channel&gt; [+|-&lt;modes&gt;] [arguments]:  Change or display channel modes"), NULL);
+	gaim_cmd_register("cumode", "wws", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+			_("cumode &lt;channel&gt; +|-&lt;modes&gt; &lt;nick&gt;:  Change nick's modes on channel"), NULL);
+	gaim_cmd_register("umode", "w", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_generic,
+			_("umode &lt;usermodes&gt;:  Set your modes in the network"), NULL);
+	gaim_cmd_register("oper", "s", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_generic,
+			_("oper &lt;nick&gt; [-pubkey]:  Get server operator privileges"), NULL);
+	gaim_cmd_register("invite", "ws", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+			_("invite &lt;channel&gt; [-|+]&lt;nick&gt;:  invite nick or add/remove from channel invite list"), NULL);
+	gaim_cmd_register("kick", "wws", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+			_("kick &lt;channel&gt; &lt;nick&gt; [comment]:  Kick client from channel"), NULL);
+	gaim_cmd_register("info", "w", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+			_("info [server]:  View server administrative details"), NULL);
+	gaim_cmd_register("ban", "ww", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+			_("ban [&lt;channel&gt; +|-&lt;nick&gt;]:  Ban client from channel"), NULL);
+	gaim_cmd_register("getkey", "w", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_generic,
+			_("getkey &lt;nick|server&gt;:  Retrieve client's or server's public key"), NULL);
+	gaim_cmd_register("stats", "", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_generic,
+			_("stats:  View server and network statistics"), NULL);
+	gaim_cmd_register("ping", "", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_generic,
+			_("ping:  Send PING to the connected server"), NULL);
+#if 0 /* Gaim doesn't handle these yet */
+	gaim_cmd_register("users", "w", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcgaim_cmd_users,
+			_("users &lt;channel&gt;:  List users in channel"));
+	gaim_cmd_register("names", "ww", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_names,
+			_("names [-count|-ops|-halfops|-voices|-normal] &lt;channel(s)&gt;:  List specific users in channel(s)"));
+#endif
+}
+
+static GaimWhiteboardPrplOps silcgaim_wb_ops =
+{
+	silcgaim_wb_start,
+	silcgaim_wb_end,
+	silcgaim_wb_get_dimensions,
+	silcgaim_wb_set_dimensions,
+	silcgaim_wb_get_brush,
+	silcgaim_wb_set_brush,
+	silcgaim_wb_send,
+	silcgaim_wb_clear,
+};
+
+static GaimPluginProtocolInfo prpl_info =
+{
+#ifdef HAVE_SILCMIME_H
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
+	OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE,
+#else
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
+	OPT_PROTO_PASSWORD_OPTIONAL,
+#endif
+	NULL,						/* user_splits */
+	NULL,						/* protocol_options */
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	{"jpeg,gif,png,bmp", 0, 0, 96, 96, 0, GAIM_ICON_SCALE_DISPLAY}, /* icon_spec */
+#else
+	NO_BUDDY_ICONS,
+#endif
+	silcgaim_list_icon,			/* list_icon */
+	silcgaim_list_emblems,		/* list_emblems */
+	silcgaim_status_text,		/* status_text */
+	silcgaim_tooltip_text,		/* tooltip_text */
+	silcgaim_away_states,		/* away_states */
+	silcgaim_blist_node_menu,	/* blist_node_menu */
+	silcgaim_chat_info,			/* chat_info */
+	silcgaim_chat_info_defaults,/* chat_info_defaults */
+	silcgaim_login,				/* login */
+	silcgaim_close,				/* close */
+	silcgaim_send_im,			/* send_im */
+	silcgaim_set_info,			/* set_info */
+	NULL,						/* send_typing */
+	silcgaim_get_info,			/* get_info */
+	silcgaim_set_status,		/* set_status */
+	silcgaim_idle_set,			/* set_idle */
+	silcgaim_change_passwd,		/* change_passwd */
+	silcgaim_add_buddy,			/* add_buddy */
+	NULL,						/* add_buddies */
+	silcgaim_remove_buddy,		/* remove_buddy */
+	NULL,						/* remove_buddies */
+	NULL,						/* add_permit */
+	NULL,						/* add_deny */
+	NULL,						/* rem_permit */
+	NULL,						/* rem_deny */
+	NULL,						/* set_permit_deny */
+	silcgaim_chat_join,			/* join_chat */
+	NULL,						/* reject_chat */
+	silcgaim_get_chat_name,		/* get_chat_name */
+	silcgaim_chat_invite,		/* chat_invite */
+	silcgaim_chat_leave,		/* chat_leave */
+	NULL,						/* chat_whisper */
+	silcgaim_chat_send,			/* chat_send */
+	silcgaim_keepalive,			/* keepalive */
+	NULL,						/* register_user */
+	NULL,						/* get_cb_info */
+	NULL,						/* get_cb_away */
+	NULL,						/* alias_buddy */
+	NULL,						/* group_buddy */
+	NULL,						/* rename_group */
+	NULL,						/* buddy_free */
+	NULL,						/* convo_closed */
+	NULL,						/* normalize */
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	silcgaim_buddy_set_icon,			/* set_buddy_icon */
+#else
+	NULL,
+#endif
+	NULL,						/* remove_group */
+	NULL,						/* get_cb_real_name */
+	silcgaim_chat_set_topic,	/* set_chat_topic */
+	NULL,						/* find_blist_chat */
+	silcgaim_roomlist_get_list,	/* roomlist_get_list */
+	silcgaim_roomlist_cancel,	/* roomlist_cancel */
+	NULL,						/* roomlist_expand_category */
+	NULL,						/* can_receive_file */
+	silcgaim_ftp_send_file,		/* send_file */
+	silcgaim_ftp_new_xfer,		/* new_xfer */
+	NULL,						/* offline_message */
+	&silcgaim_wb_ops,			/* whiteboard_prpl_ops */
+	NULL,                       /* send_raw */
+	NULL,                       /* roomlist_room_serialize */
+};
+
+static GaimPluginInfo info =
+{
+	GAIM_PLUGIN_MAGIC,
+	GAIM_MAJOR_VERSION,
+	GAIM_MINOR_VERSION,
+	GAIM_PLUGIN_PROTOCOL,                             /**< type           */
+	NULL,                                             /**< ui_requirement */
+	0,                                                /**< flags          */
+	NULL,                                             /**< dependencies   */
+	GAIM_PRIORITY_DEFAULT,                            /**< priority       */
+
+	"prpl-silc",                                      /**< id             */
+	"SILC",                                           /**< name           */
+	"1.0",                                            /**< version        */
+	/**  summary        */
+	N_("SILC Protocol Plugin"),
+	/**  description    */
+	N_("Secure Internet Live Conferencing (SILC) Protocol"),
+	"Pekka Riikonen",                                 /**< author         */
+	"http://silcnet.org/",                            /**< homepage       */
+
+	NULL,                                             /**< load           */
+	NULL,                                             /**< unload         */
+	NULL,                                             /**< destroy        */
+
+	NULL,                                             /**< ui_info        */
+	&prpl_info,                                       /**< extra_info     */
+	NULL,                                             /**< prefs_info     */
+	silcgaim_actions
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+	GaimAccountOption *option;
+	GaimAccountUserSplit *split;
+	char tmp[256];
+	int i;
+	GaimKeyValuePair *kvp;
+	GList *list = NULL;
+
+	silc_plugin = plugin;
+
+	split = gaim_account_user_split_new(_("Network"), "silcnet.org", '@');
+	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+
+	/* Account options */
+	option = gaim_account_option_string_new(_("Connect server"),
+						"server",
+						"silc.silcnet.org");
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = gaim_account_option_int_new(_("Port"), "port", 706);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	g_snprintf(tmp, sizeof(tmp), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcgaim_silcdir());
+	option = gaim_account_option_string_new(_("Public Key file"),
+						"public-key", tmp);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	g_snprintf(tmp, sizeof(tmp), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcgaim_silcdir());
+	option = gaim_account_option_string_new(_("Private Key file"),
+						"private-key", tmp);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	for (i = 0; silc_default_ciphers[i].name; i++) {
+		kvp = g_new0(GaimKeyValuePair, 1);
+		kvp->key = g_strdup(silc_default_ciphers[i].name);
+		kvp->value = g_strdup(silc_default_ciphers[i].name);
+		list = g_list_append(list, kvp);
+	}
+	option = gaim_account_option_list_new(_("Cipher"), "cipher", list);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	list = NULL;
+	for (i = 0; silc_default_hmacs[i].name; i++) {
+		kvp = g_new0(GaimKeyValuePair, 1);
+		kvp->key = g_strdup(silc_default_hmacs[i].name);
+		kvp->value = g_strdup(silc_default_hmacs[i].name);
+		list = g_list_append(list, kvp);
+	}
+	option = gaim_account_option_list_new(_("HMAC"), "hmac", list);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = gaim_account_option_bool_new(_("Public key authentication"),
+					      "pubkey-auth", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = gaim_account_option_bool_new(_("Reject watching by other users"),
+					      "reject-watch", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = gaim_account_option_bool_new(_("Block invites"),
+					      "block-invites", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = gaim_account_option_bool_new(_("Block IMs without Key Exchange"),
+					      "block-ims", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = gaim_account_option_bool_new(_("Reject online status attribute requests"),
+					      "reject-attrs", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = gaim_account_option_bool_new(_("Block messages to whiteboard"),
+					      "block-wb", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = gaim_account_option_bool_new(_("Automatically open whiteboard"),
+					      "open-wb", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = gaim_account_option_bool_new(_("Digitally sign and verify all messages"),
+					      "sign-verify", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	gaim_prefs_remove("/plugins/prpl/silc");
+
+	silcgaim_register_commands();
+
+#ifdef _WIN32
+	silc_net_win32_init();
+#endif
+}
+
+GAIM_INIT_PLUGIN(silc, init_plugin, info);