diff libpurple/protocols/silc/buddy.c @ 15373: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 32c366eeeb99
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc/buddy.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,1739 @@
+/*
+
+  silcgaim_buddy.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 "silcgaim.h"
+#include "wb.h"
+
+/***************************** Key Agreement *********************************/
+
+static void
+silcgaim_buddy_keyagr(GaimBlistNode *node, gpointer data);
+
+static void
+silcgaim_buddy_keyagr_do(GaimConnection *gc, const char *name,
+			 			 gboolean force_local);
+
+typedef struct {
+	char *nick;
+	GaimConnection *gc;
+} *SilcGaimResolve;
+
+static void
+silcgaim_buddy_keyagr_resolved(SilcClient client,
+			       SilcClientConnection conn,
+			       SilcClientEntry *clients,
+			       SilcUInt32 clients_count,
+			       void *context)
+{
+	GaimConnection *gc = client->application;
+	SilcGaimResolve r = context;
+	char tmp[256];
+
+	if (!clients) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("User %s is not present in the network"), r->nick);
+		gaim_notify_error(gc, _("Key Agreement"),
+				  _("Cannot perform the key agreement"), tmp);
+		silc_free(r->nick);
+		silc_free(r);
+		return;
+	}
+
+	silcgaim_buddy_keyagr_do(gc, r->nick, FALSE);
+	silc_free(r->nick);
+	silc_free(r);
+}
+
+typedef struct {
+	gboolean responder;
+} *SilcGaimKeyAgr;
+
+static void
+silcgaim_buddy_keyagr_cb(SilcClient client,
+			 SilcClientConnection conn,
+			 SilcClientEntry client_entry,
+			 SilcKeyAgreementStatus status,
+			 SilcSKEKeyMaterial *key,
+			 void *context)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	SilcGaimKeyAgr a = context;
+
+	if (!sg->conn)
+		return;
+
+	switch (status) {
+	case SILC_KEY_AGREEMENT_OK:
+		{
+			GaimConversation *convo;
+			char tmp[128];
+
+			/* Set the private key for this client */
+			silc_client_del_private_message_key(client, conn, client_entry);
+			silc_client_add_private_message_key_ske(client, conn, client_entry,
+								NULL, NULL, key, a->responder);
+			silc_ske_free_key_material(key);
+
+			
+			/* Open IM window */
+			convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,
+									client_entry->nickname, sg->account);
+			if (convo) {
+				/* we don't have windows in the core anymore...but we may want to
+				 * provide some method for asking the UI to show the window
+				gaim_conv_window_show(gaim_conversation_get_window(convo));
+				 */
+			} else {
+				convo = gaim_conversation_new(GAIM_CONV_TYPE_IM, sg->account,
+							      client_entry->nickname);
+			}
+			g_snprintf(tmp, sizeof(tmp), "%s [private key]", client_entry->nickname);
+			gaim_conversation_set_title(convo, tmp);
+		}
+		break;
+
+	case SILC_KEY_AGREEMENT_ERROR:
+		gaim_notify_error(gc, _("Key Agreement"),
+				  _("Error occurred during key agreement"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_FAILURE:
+		gaim_notify_error(gc, _("Key Agreement"), _("Key Agreement failed"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_TIMEOUT:
+		gaim_notify_error(gc, _("Key Agreement"),
+				  _("Timeout during key agreement"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_ABORTED:
+		gaim_notify_error(gc, _("Key Agreement"),
+				  _("Key agreement was aborted"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_ALREADY_STARTED:
+		gaim_notify_error(gc, _("Key Agreement"),
+				  _("Key agreement is already started"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_SELF_DENIED:
+		gaim_notify_error(gc, _("Key Agreement"),
+				  _("Key agreement cannot be started with yourself"),
+				  NULL);
+		break;
+
+	default:
+		break;
+	}
+
+	silc_free(a);
+}
+
+static void
+silcgaim_buddy_keyagr_do(GaimConnection *gc, const char *name,
+			 gboolean force_local)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+	char *local_ip = NULL, *remote_ip = NULL;
+	gboolean local = TRUE;
+	char *nickname;
+	SilcGaimKeyAgr a;
+
+	if (!sg->conn || !name)
+		return;
+
+	if (!silc_parse_userfqdn(name, &nickname, NULL))
+		return;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(sg->client, sg->conn, nickname, name,
+						&clients_count);
+	if (!clients) {
+		/* Resolve unknown user */
+		SilcGaimResolve r = silc_calloc(1, sizeof(*r));
+		if (!r)
+			return;
+		r->nick = g_strdup(name);
+		r->gc = gc;
+		silc_client_get_clients(sg->client, sg->conn, nickname, NULL,
+					silcgaim_buddy_keyagr_resolved, r);
+		silc_free(nickname);
+		return;
+	}
+
+	/* Resolve the local IP from the outgoing socket connection.  We resolve
+	   it to check whether we have a private range IP address or public IP
+	   address.  If we have public then we will assume that we are not behind
+	   NAT and will provide automatically the point of connection to the
+	   agreement.  If we have private range address we assume that we are
+	   behind NAT and we let the responder provide the point of connection.
+
+	   The algorithm also checks the remote IP address of server connection.
+	   If it is private range address and we have private range address we
+	   assume that we are chatting in LAN and will provide the point of
+	   connection.
+
+	   Naturally this algorithm does not always get things right. */
+
+	if (silc_net_check_local_by_sock(sg->conn->sock->sock, NULL, &local_ip)) {
+		/* Check if the IP is private */
+		if (!force_local && silcgaim_ip_is_private(local_ip)) {
+			local = FALSE;
+
+			/* Local IP is private, resolve the remote server IP to see whether
+			   we are talking to Internet or just on LAN. */
+			if (silc_net_check_host_by_sock(sg->conn->sock->sock, NULL,
+							&remote_ip))
+				if (silcgaim_ip_is_private(remote_ip))
+					/* We assume we are in LAN.  Let's provide
+					   the connection point. */
+					local = TRUE;
+		}
+	}
+
+	if (force_local)
+		local = TRUE;
+
+	if (local && !local_ip)
+		local_ip = silc_net_localip();
+
+	a = silc_calloc(1, sizeof(*a));
+	if (!a)
+		return;
+	a->responder = local;
+
+	/* Send the key agreement request */
+	silc_client_send_key_agreement(sg->client, sg->conn, clients[0],
+				       local ? local_ip : NULL, NULL, 0, 60,
+				       silcgaim_buddy_keyagr_cb, a);
+
+	silc_free(local_ip);
+	silc_free(remote_ip);
+	silc_free(clients);
+}
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	SilcClientID client_id;
+	char *hostname;
+	SilcUInt16 port;
+} *SilcGaimKeyAgrAsk;
+
+static void
+silcgaim_buddy_keyagr_request_cb(SilcGaimKeyAgrAsk a, gint id)
+{
+	SilcGaimKeyAgr ai;
+	SilcClientEntry client_entry;
+
+	if (id != 1)
+		goto out;
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(a->client, a->conn,
+						    &a->client_id);
+	if (!client_entry) {
+		gaim_notify_error(a->client->application, _("Key Agreement"),
+				  _("The remote user is not present in the network any more"),
+				  NULL);
+		goto out;
+	}
+
+	/* If the hostname was provided by the requestor perform the key agreement
+	   now.  Otherwise, we will send him a request to connect to us. */
+	if (a->hostname) {
+		ai = silc_calloc(1, sizeof(*ai));
+		if (!ai)
+			goto out;
+		ai->responder = FALSE;
+		silc_client_perform_key_agreement(a->client, a->conn, client_entry,
+						  a->hostname, a->port,
+						  silcgaim_buddy_keyagr_cb, ai);
+	} else {
+		/* Send request.  Force us as the point of connection since requestor
+		   did not provide the point of connection. */
+		silcgaim_buddy_keyagr_do(a->client->application,
+					 client_entry->nickname, TRUE);
+	}
+
+ out:
+	silc_free(a->hostname);
+	silc_free(a);
+}
+
+void silcgaim_buddy_keyagr_request(SilcClient client,
+				   SilcClientConnection conn,
+				   SilcClientEntry client_entry,
+				   const char *hostname, SilcUInt16 port)
+{
+	char tmp[128], tmp2[128];
+	SilcGaimKeyAgrAsk a;
+
+	g_snprintf(tmp, sizeof(tmp),
+		   _("Key agreement request received from %s. Would you like to "
+		     "perform the key agreement?"), client_entry->nickname);
+	if (hostname)
+		g_snprintf(tmp2, sizeof(tmp2),
+			   _("The remote user is waiting key agreement on:\n"
+			     "Remote host: %s\nRemote port: %d"), hostname, port);
+
+	a = silc_calloc(1, sizeof(*a));
+	if (!a)
+		return;
+	a->client = client;
+	a->conn = conn;
+	a->client_id = *client_entry->id;
+	if (hostname)
+		a->hostname = strdup(hostname);
+	a->port = port;
+
+	gaim_request_action(client->application, _("Key Agreement Request"), tmp,
+			    hostname ? tmp2 : NULL, 1, a, 2,
+			    _("Yes"), G_CALLBACK(silcgaim_buddy_keyagr_request_cb),
+			    _("No"), G_CALLBACK(silcgaim_buddy_keyagr_request_cb));
+}
+
+static void
+silcgaim_buddy_keyagr(GaimBlistNode *node, gpointer data)
+{
+	GaimBuddy *buddy;
+
+	buddy = (GaimBuddy *)node;
+	silcgaim_buddy_keyagr_do(buddy->account->gc, buddy->name, FALSE);
+}
+
+
+/**************************** Static IM Key **********************************/
+
+static void
+silcgaim_buddy_resetkey(GaimBlistNode *node, gpointer data)
+{
+	GaimBuddy *b;
+	GaimConnection *gc;
+        SilcGaim sg;
+	char *nickname;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+
+	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+	b = (GaimBuddy *) node;
+	gc = gaim_account_get_connection(b->account);
+	sg = gc->proto_data;
+
+	if (!silc_parse_userfqdn(b->name, &nickname, NULL))
+		return;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(sg->client, sg->conn,
+						nickname, b->name,
+						&clients_count);
+	if (!clients) {
+		silc_free(nickname);
+		return;
+	}
+
+	clients[0]->prv_resp = FALSE;
+	silc_client_del_private_message_key(sg->client, sg->conn,
+					    clients[0]);
+	silc_free(clients);
+	silc_free(nickname);
+}
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	SilcClientID client_id;
+} *SilcGaimPrivkey;
+
+static void
+silcgaim_buddy_privkey(GaimConnection *gc, const char *name);
+
+static void
+silcgaim_buddy_privkey_cb(SilcGaimPrivkey p, const char *passphrase)
+{
+	SilcClientEntry client_entry;
+
+        if (!passphrase || !(*passphrase)) {
+                silc_free(p);
+                return;
+        }
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(p->client, p->conn,
+						    &p->client_id);
+	if (!client_entry) {
+		gaim_notify_error(p->client->application, _("IM With Password"),
+				  _("The remote user is not present in the network any more"),
+				  NULL);
+		silc_free(p);
+		return;
+	}
+
+	/* Set the private message key */
+	silc_client_del_private_message_key(p->client, p->conn,
+					    client_entry);
+	silc_client_add_private_message_key(p->client, p->conn,
+					    client_entry, NULL, NULL,
+					    (unsigned char *)passphrase,
+					    strlen(passphrase), FALSE,
+					    client_entry->prv_resp);
+	if (!client_entry->prv_resp)
+		silc_client_send_private_message_key_request(p->client,
+							     p->conn,
+							     client_entry);
+        silc_free(p);
+}
+
+static void
+silcgaim_buddy_privkey_resolved(SilcClient client,
+				SilcClientConnection conn,
+				SilcClientEntry *clients,
+				SilcUInt32 clients_count,
+				void *context)
+{
+	char tmp[256];
+
+	if (!clients) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("User %s is not present in the network"),
+			   (const char *)context);
+		gaim_notify_error(client->application, _("IM With Password"),
+				  _("Cannot set IM key"), tmp);
+		g_free(context);
+		return;
+	}
+
+	silcgaim_buddy_privkey(client->application, context);
+	silc_free(context);
+}
+
+static void
+silcgaim_buddy_privkey(GaimConnection *gc, const char *name)
+{
+        SilcGaim sg = gc->proto_data;
+	char *nickname;
+	SilcGaimPrivkey p;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+
+	if (!name)
+		return;
+	if (!silc_parse_userfqdn(name, &nickname, NULL))
+		return;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(sg->client, sg->conn,
+						nickname, name,
+						&clients_count);
+	if (!clients) {
+		silc_client_get_clients(sg->client, sg->conn, nickname, NULL,
+					silcgaim_buddy_privkey_resolved,
+					g_strdup(name));
+		silc_free(nickname);
+		return;
+	}
+
+	p = silc_calloc(1, sizeof(*p));
+	if (!p)
+		return;
+	p->client = sg->client;
+	p->conn = sg->conn;
+	p->client_id = *clients[0]->id;
+        gaim_request_input(gc, _("IM With Password"), NULL,
+                           _("Set IM Password"), NULL, FALSE, TRUE, NULL,
+                           _("OK"), G_CALLBACK(silcgaim_buddy_privkey_cb),
+                           _("Cancel"), G_CALLBACK(silcgaim_buddy_privkey_cb),
+			   p);
+
+	silc_free(clients);
+	silc_free(nickname);
+}
+
+static void
+silcgaim_buddy_privkey_menu(GaimBlistNode *node, gpointer data)
+{
+	GaimBuddy *buddy;
+	GaimConnection *gc;
+
+	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (GaimBuddy *) node;
+	gc = gaim_account_get_connection(buddy->account);
+
+	silcgaim_buddy_privkey(gc, buddy->name);
+}
+
+
+/**************************** Get Public Key *********************************/
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	SilcClientID client_id;
+} *SilcGaimBuddyGetkey;
+
+static void
+silcgaim_buddy_getkey(GaimConnection *gc, const char *name);
+
+static void
+silcgaim_buddy_getkey_cb(SilcGaimBuddyGetkey g,
+			 SilcClientCommandReplyContext cmd)
+{
+	SilcClientEntry client_entry;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(g->client, g->conn,
+						    &g->client_id);
+	if (!client_entry) {
+		gaim_notify_error(g->client->application, _("Get Public Key"),
+				  _("The remote user is not present in the network any more"),
+				  NULL);
+		silc_free(g);
+		return;
+	}
+
+	if (!client_entry->public_key) {
+		silc_free(g);
+		return;
+	}
+
+	/* Now verify the public key */
+	pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+	silcgaim_verify_public_key(g->client, g->conn, client_entry->nickname,
+				   SILC_SOCKET_TYPE_CLIENT,
+				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+				   NULL, NULL);
+	silc_free(pk);
+	silc_free(g);
+}
+
+static void
+silcgaim_buddy_getkey_resolved(SilcClient client,
+			       SilcClientConnection conn,
+			       SilcClientEntry *clients,
+			       SilcUInt32 clients_count,
+			       void *context)
+{
+	char tmp[256];
+
+	if (!clients) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("User %s is not present in the network"),
+			   (const char *)context);
+		gaim_notify_error(client->application, _("Get Public Key"),
+				  _("Cannot fetch the public key"), tmp);
+		g_free(context);
+		return;
+	}
+
+	silcgaim_buddy_getkey(client->application, context);
+	silc_free(context);
+}
+
+static void
+silcgaim_buddy_getkey(GaimConnection *gc, const char *name)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+	SilcGaimBuddyGetkey g;
+	char *nickname;
+
+	if (!name)
+		return;
+
+	if (!silc_parse_userfqdn(name, &nickname, NULL))
+		return;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(client, conn, nickname, name,
+						&clients_count);
+	if (!clients) {
+		silc_client_get_clients(client, conn, nickname, NULL,
+					silcgaim_buddy_getkey_resolved,
+					g_strdup(name));
+		silc_free(nickname);
+		return;
+	}
+
+	/* Call GETKEY */
+	g = silc_calloc(1, sizeof(*g));
+	if (!g)
+		return;
+	g->client = client;
+	g->conn = conn;
+	g->client_id = *clients[0]->id;
+	silc_client_command_call(client, conn, NULL, "GETKEY",
+				 clients[0]->nickname, NULL);
+	silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+				    conn->cmd_ident,
+				    (SilcCommandCb)silcgaim_buddy_getkey_cb, g);
+	silc_free(clients);
+	silc_free(nickname);
+}
+
+static void
+silcgaim_buddy_getkey_menu(GaimBlistNode *node, gpointer data)
+{
+	GaimBuddy *buddy;
+	GaimConnection *gc;
+
+	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (GaimBuddy *) node;
+	gc = gaim_account_get_connection(buddy->account);
+
+	silcgaim_buddy_getkey(gc, buddy->name);
+}
+
+static void
+silcgaim_buddy_showkey(GaimBlistNode *node, gpointer data)
+{
+	GaimBuddy *b;
+	GaimConnection *gc;
+	SilcGaim sg;
+	SilcPublicKey public_key;
+	const char *pkfile;
+
+	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+	b = (GaimBuddy *) node;
+	gc = gaim_account_get_connection(b->account);
+	sg = gc->proto_data;
+
+	pkfile = gaim_blist_node_get_string(node, "public-key");
+	if (!silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_PEM) &&
+	    !silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_BIN)) {
+		gaim_notify_error(gc,
+				  _("Show Public Key"),
+				  _("Could not load public key"), NULL);
+		return;
+	}
+
+	silcgaim_show_public_key(sg, b->name, public_key, NULL, NULL);
+	silc_pkcs_public_key_free(public_key);
+}
+
+
+/**************************** Buddy routines *********************************/
+
+/* The buddies are implemented by using the WHOIS and WATCH commands that
+   can be used to search users by their public key.  Since nicknames aren't
+   unique in SILC we cannot trust the buddy list using their nickname.  We
+   associate public keys to buddies and use those to search and watch
+   in the network.
+
+   The problem is that Gaim does not return GaimBuddy contexts to the
+   callbacks but the buddy names.  Naturally, this is not going to work
+   with SILC.  But, for now, we have to do what we can... */
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	SilcClientID client_id;
+	GaimBuddy *b;
+	unsigned char *offline_pk;
+	SilcUInt32 offline_pk_len;
+	unsigned int offline        : 1;
+	unsigned int pubkey_search  : 1;
+	unsigned int init           : 1;
+} *SilcGaimBuddyRes;
+
+static void
+silcgaim_add_buddy_ask_pk_cb(SilcGaimBuddyRes r, gint id);
+static void
+silcgaim_add_buddy_resolved(SilcClient client,
+			    SilcClientConnection conn,
+			    SilcClientEntry *clients,
+			    SilcUInt32 clients_count,
+			    void *context);
+
+void silcgaim_get_info(GaimConnection *gc, const char *who)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry client_entry;
+	GaimBuddy *b;
+	const char *filename, *nick = who;
+	char tmp[256];
+
+	if (!who)
+		return;
+	if (strlen(who) > 1 && who[0] == '@')
+		nick = who + 1;
+	if (strlen(who) > 1 && who[0] == '*')
+		nick = who + 1;
+	if (strlen(who) > 2 && who[0] == '*' && who[1] == '@')
+		nick = who + 2;
+
+	b = gaim_find_buddy(gc->account, nick);
+	if (b) {
+		/* See if we have this buddy's public key.  If we do use that
+		   to search the details. */
+		filename = gaim_blist_node_get_string((GaimBlistNode *)b, "public-key");
+		if (filename) {
+			/* Call WHOIS.  The user info is displayed in the WHOIS
+			   command reply. */
+			silc_client_command_call(client, conn, NULL, "WHOIS",
+						 "-details", "-pubkey", filename, NULL);
+			return;
+		}
+
+		if (!b->proto_data) {
+			g_snprintf(tmp, sizeof(tmp),
+				   _("User %s is not present in the network"), b->name);
+			gaim_notify_error(gc, _("User Information"),
+					  _("Cannot get user information"), tmp);
+			return;
+		}
+
+		client_entry = silc_client_get_client_by_id(client, conn, b->proto_data);
+		if (client_entry) {
+			/* Call WHOIS.  The user info is displayed in the WHOIS
+			   command reply. */
+			silc_client_command_call(client, conn, NULL, "WHOIS",
+						 client_entry->nickname, "-details", NULL);
+		}
+	} else {
+		/* Call WHOIS just with nickname. */
+		silc_client_command_call(client, conn, NULL, "WHOIS", nick, NULL);
+	}
+}
+
+static void
+silcgaim_add_buddy_pk_no(SilcGaimBuddyRes r)
+{
+	char tmp[512];
+	g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not trusted"),
+		   r->b->name);
+	gaim_notify_error(r->client->application, _("Add Buddy"), tmp,
+			  _("You cannot receive buddy notifications until you "
+			    "import his/her public key.  You can use the Get Public Key "
+			    "command to get the public key."));
+	gaim_prpl_got_user_status(gaim_buddy_get_account(r->b), gaim_buddy_get_name(r->b), SILCGAIM_STATUS_ID_OFFLINE, NULL);
+}
+
+static void
+silcgaim_add_buddy_save(bool success, void *context)
+{
+	SilcGaimBuddyRes r = context;
+	GaimBuddy *b = r->b;
+	SilcClient client = r->client;
+	SilcClientEntry client_entry;
+	SilcAttributePayload attr;
+	SilcAttribute attribute;
+	SilcVCardStruct vcard;
+	SilcAttributeObjMime message, extension;
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	SilcAttributeObjMime usericon;
+#endif
+	SilcAttributeObjPk serverpk, usersign, serversign;
+	gboolean usign_success = TRUE, ssign_success = TRUE;
+	char filename[512], filename2[512], *fingerprint = NULL, *tmp;
+	SilcUInt32 len;
+	int i;
+
+	if (!success) {
+		/* The user did not trust the public key. */
+		silcgaim_add_buddy_pk_no(r);
+		silc_free(r);
+		return;
+	}
+
+	if (r->offline) {
+		/* User is offline.  Associate the imported public key with
+		   this user. */
+		fingerprint = silc_hash_fingerprint(NULL, r->offline_pk,
+						    r->offline_pk_len);
+		for (i = 0; i < strlen(fingerprint); i++)
+			if (fingerprint[i] == ' ')
+				fingerprint[i] = '_';
+		g_snprintf(filename, sizeof(filename) - 1,
+			   "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub",
+			   silcgaim_silcdir(), fingerprint);
+		gaim_blist_node_set_string((GaimBlistNode *)b, "public-key", filename);
+		gaim_prpl_got_user_status(gaim_buddy_get_account(r->b), gaim_buddy_get_name(r->b), SILCGAIM_STATUS_ID_OFFLINE, NULL);
+		silc_free(fingerprint);
+		silc_free(r->offline_pk);
+		silc_free(r);
+		return;
+	}
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(r->client, r->conn,
+						    &r->client_id);
+	if (!client_entry) {
+		silc_free(r);
+		return;
+	}
+
+	memset(&vcard, 0, sizeof(vcard));
+	memset(&message, 0, sizeof(message));
+	memset(&extension, 0, sizeof(extension));
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	memset(&usericon, 0, sizeof(usericon));
+#endif
+	memset(&serverpk, 0, sizeof(serverpk));
+	memset(&usersign, 0, sizeof(usersign));
+	memset(&serversign, 0, sizeof(serversign));
+
+	/* Now that we have the public key and we trust it now we
+	   save the attributes of the buddy and update its status. */
+
+	if (client_entry->attrs) {
+		silc_dlist_start(client_entry->attrs);
+		while ((attr = silc_dlist_get(client_entry->attrs))
+		       != SILC_LIST_END) {
+			attribute = silc_attribute_get_attribute(attr);
+
+			switch (attribute) {
+			case SILC_ATTRIBUTE_USER_INFO:
+				if (!silc_attribute_get_object(attr, (void *)&vcard,
+							       sizeof(vcard)))
+					continue;
+				break;
+
+			case SILC_ATTRIBUTE_STATUS_MESSAGE:
+				if (!silc_attribute_get_object(attr, (void *)&message,
+							       sizeof(message)))
+					continue;
+				break;
+
+			case SILC_ATTRIBUTE_EXTENSION:
+				if (!silc_attribute_get_object(attr, (void *)&extension,
+							       sizeof(extension)))
+					continue;
+				break;
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+			case SILC_ATTRIBUTE_USER_ICON:
+				if (!silc_attribute_get_object(attr, (void *)&usericon,
+							       sizeof(usericon)))
+					continue;
+				break;
+#endif
+
+			case SILC_ATTRIBUTE_SERVER_PUBLIC_KEY:
+				if (serverpk.type)
+					continue;
+				if (!silc_attribute_get_object(attr, (void *)&serverpk,
+							       sizeof(serverpk)))
+					continue;
+				break;
+
+			case SILC_ATTRIBUTE_USER_DIGITAL_SIGNATURE:
+				if (usersign.data)
+					continue;
+				if (!silc_attribute_get_object(attr, (void *)&usersign,
+							       sizeof(usersign)))
+					continue;
+				break;
+
+			case SILC_ATTRIBUTE_SERVER_DIGITAL_SIGNATURE:
+				if (serversign.data)
+					continue;
+				if (!silc_attribute_get_object(attr, (void *)&serversign,
+							       sizeof(serversign)))
+					continue;
+				break;
+
+			default:
+				break;
+			}
+		}
+	}
+
+	/* Verify the attribute signatures */
+
+	if (usersign.data) {
+		SilcPKCS pkcs;
+		unsigned char *verifyd;
+		SilcUInt32 verify_len;
+
+		silc_pkcs_alloc((unsigned char*)"rsa", &pkcs);
+		verifyd = silc_attribute_get_verify_data(client_entry->attrs,
+							 FALSE, &verify_len);
+		if (verifyd && silc_pkcs_public_key_set(pkcs, client_entry->public_key)){
+			if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash,
+							usersign.data,
+							usersign.data_len,
+							verifyd, verify_len))
+				usign_success = FALSE;
+		}
+		silc_free(verifyd);
+	}
+
+	if (serversign.data && !strcmp(serverpk.type, "silc-rsa")) {
+		SilcPublicKey public_key;
+		SilcPKCS pkcs;
+		unsigned char *verifyd;
+		SilcUInt32 verify_len;
+
+		if (silc_pkcs_public_key_decode(serverpk.data, serverpk.data_len,
+						&public_key)) {
+			silc_pkcs_alloc((unsigned char *)"rsa", &pkcs);
+			verifyd = silc_attribute_get_verify_data(client_entry->attrs,
+								 TRUE, &verify_len);
+			if (verifyd && silc_pkcs_public_key_set(pkcs, public_key)) {
+				if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash,
+							       serversign.data,
+							       serversign.data_len,
+							       verifyd, verify_len))
+					ssign_success = FALSE;
+			}
+			silc_pkcs_public_key_free(public_key);
+			silc_free(verifyd);
+		}
+	}
+
+	fingerprint = silc_fingerprint(client_entry->fingerprint,
+				       client_entry->fingerprint_len);
+	for (i = 0; i < strlen(fingerprint); i++)
+		if (fingerprint[i] == ' ')
+			fingerprint[i] = '_';
+
+	if (usign_success || ssign_success) {
+		struct passwd *pw;
+		struct stat st;
+
+		memset(filename2, 0, sizeof(filename2));
+
+		/* Filename for dir */
+		tmp = fingerprint + strlen(fingerprint) - 9;
+		g_snprintf(filename, sizeof(filename) - 1,
+			   "%s" G_DIR_SEPARATOR_S "friends" G_DIR_SEPARATOR_S "%s",
+			   silcgaim_silcdir(), tmp);
+
+		pw = getpwuid(getuid());
+		if (!pw)
+			return;
+
+		/* Create dir if it doesn't exist */
+		if ((g_stat(filename, &st)) == -1) {
+			if (errno == ENOENT) {
+				if (pw->pw_uid == geteuid())
+					g_mkdir(filename, 0755);
+			}
+		}
+
+		/* Save VCard */
+		g_snprintf(filename2, sizeof(filename2) - 1,
+			   "%s" G_DIR_SEPARATOR_S "vcard", filename);
+		if (vcard.full_name) {
+			tmp = (char *)silc_vcard_encode(&vcard, &len);
+			silc_file_writefile(filename2, tmp, len);
+			silc_free(tmp);
+		}
+
+		/* Save status message */
+		if (message.mime) {
+			memset(filename2, 0, sizeof(filename2));
+			g_snprintf(filename2, sizeof(filename2) - 1,
+				   "%s" G_DIR_SEPARATOR_S "status_message.mime",
+				   filename);
+			silc_file_writefile(filename2, (char *)message.mime,
+					    message.mime_len);
+		}
+
+		/* Save extension data */
+		if (extension.mime) {
+			memset(filename2, 0, sizeof(filename2));
+			g_snprintf(filename2, sizeof(filename2) - 1,
+				   "%s" G_DIR_SEPARATOR_S "extension.mime",
+				   filename);
+			silc_file_writefile(filename2, (char *)extension.mime,
+					    extension.mime_len);
+		}
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+		/* Save user icon */
+		if (usericon.mime) {
+			SilcMime m = silc_mime_decode(usericon.mime,
+						      usericon.mime_len);
+			if (m) {
+				const char *type = silc_mime_get_field(m, "Content-Type");
+				if (!strcmp(type, "image/jpeg") ||
+				    !strcmp(type, "image/gif") ||
+				    !strcmp(type, "image/bmp") ||
+				    !strcmp(type, "image/png")) {
+					const unsigned char *data;
+					SilcUInt32 data_len;
+					data = silc_mime_get_data(m, &data_len);
+					if (data)
+						gaim_buddy_icons_set_for_user(gaim_buddy_get_account(r->b), gaim_buddy_get_name(r->b), (void *)data, data_len);
+				}
+				silc_mime_free(m);
+			}
+		}
+#endif
+	}
+
+	/* Save the public key path to buddy properties, as it is used
+	   to identify the buddy in the network (and not the nickname). */
+	memset(filename, 0, sizeof(filename));
+	g_snprintf(filename, sizeof(filename) - 1,
+		   "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub",
+		   silcgaim_silcdir(), fingerprint);
+	gaim_blist_node_set_string((GaimBlistNode *)b, "public-key", filename);
+
+	/* Update online status */
+	gaim_prpl_got_user_status(gaim_buddy_get_account(r->b), gaim_buddy_get_name(r->b), SILCGAIM_STATUS_ID_AVAILABLE, NULL);
+
+	/* Finally, start watching this user so we receive its status
+	   changes from the server */
+	g_snprintf(filename2, sizeof(filename2) - 1, "+%s", filename);
+	silc_client_command_call(r->client, r->conn, NULL, "WATCH", "-pubkey",
+				 filename2, NULL);
+
+	silc_free(fingerprint);
+	silc_free(r);
+}
+
+static void
+silcgaim_add_buddy_ask_import(void *user_data, const char *name)
+{
+	SilcGaimBuddyRes r = (SilcGaimBuddyRes)user_data;
+	SilcPublicKey public_key;
+
+	/* 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)) {
+		silcgaim_add_buddy_ask_pk_cb(r, 0);
+		gaim_notify_error(r->client->application,
+				  _("Add Buddy"), _("Could not load public key"), NULL);
+		return;
+	}
+
+	/* Now verify the public key */
+	r->offline_pk = silc_pkcs_public_key_encode(public_key, &r->offline_pk_len);
+	silcgaim_verify_public_key(r->client, r->conn, r->b->name,
+				   SILC_SOCKET_TYPE_CLIENT,
+				   r->offline_pk, r->offline_pk_len,
+				   SILC_SKE_PK_TYPE_SILC,
+				   silcgaim_add_buddy_save, r);
+}
+
+static void
+silcgaim_add_buddy_ask_pk_cancel(void *user_data, const char *name)
+{
+	SilcGaimBuddyRes r = (SilcGaimBuddyRes)user_data;
+
+	/* The user did not import public key.  The buddy is unusable. */
+	silcgaim_add_buddy_pk_no(r);
+	silc_free(r);
+}
+
+static void
+silcgaim_add_buddy_ask_pk_cb(SilcGaimBuddyRes r, gint id)
+{
+	if (id != 0) {
+		/* The user did not import public key.  The buddy is unusable. */
+		silcgaim_add_buddy_pk_no(r);
+		silc_free(r);
+		return;
+	}
+
+	/* Open file selector to select the public key. */
+	gaim_request_file(r->client->application, _("Open..."), NULL, FALSE,
+			  G_CALLBACK(silcgaim_add_buddy_ask_import),
+			  G_CALLBACK(silcgaim_add_buddy_ask_pk_cancel), r);
+}
+
+static void
+silcgaim_add_buddy_ask_pk(SilcGaimBuddyRes r)
+{
+	char tmp[512];
+	g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not present in the network"),
+		   r->b->name);
+	gaim_request_action(r->client->application, _("Add Buddy"), tmp,
+			    _("To add the buddy you must import his/her public key. "
+			      "Press Import to import a public key."), 0, r, 2,
+			    _("Cancel"), G_CALLBACK(silcgaim_add_buddy_ask_pk_cb),
+			    _("_Import..."), G_CALLBACK(silcgaim_add_buddy_ask_pk_cb));
+}
+
+static void
+silcgaim_add_buddy_getkey_cb(SilcGaimBuddyRes r,
+			     SilcClientCommandReplyContext cmd)
+{
+	SilcClientEntry client_entry;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(r->client, r->conn,
+						    &r->client_id);
+	if (!client_entry || !client_entry->public_key) {
+		/* The buddy is offline/nonexistent. We will require user
+		   to associate a public key with the buddy or the buddy
+		   cannot be added. */
+		r->offline = TRUE;
+		silcgaim_add_buddy_ask_pk(r);
+		return;
+	}
+
+	/* Now verify the public key */
+	pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+	silcgaim_verify_public_key(r->client, r->conn, client_entry->nickname,
+				   SILC_SOCKET_TYPE_CLIENT,
+				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+				   silcgaim_add_buddy_save, r);
+	silc_free(pk);
+}
+
+static void
+silcgaim_add_buddy_select_cb(SilcGaimBuddyRes r, GaimRequestFields *fields)
+{
+	GaimRequestField *f;
+	const GList *list;
+	SilcClientEntry client_entry;
+
+	f = gaim_request_fields_get_field(fields, "list");
+	list = gaim_request_field_list_get_selected(f);
+	if (!list) {
+		/* The user did not select any user. */
+		silcgaim_add_buddy_pk_no(r);
+		silc_free(r);
+		return;
+	}
+
+	client_entry = gaim_request_field_list_get_data(f, list->data);
+	silcgaim_add_buddy_resolved(r->client, r->conn, &client_entry, 1, r);
+}
+
+static void
+silcgaim_add_buddy_select_cancel(SilcGaimBuddyRes r, GaimRequestFields *fields)
+{
+	/* The user did not select any user. */
+	silcgaim_add_buddy_pk_no(r);
+	silc_free(r);
+}
+
+static void
+silcgaim_add_buddy_select(SilcGaimBuddyRes r,
+			  SilcClientEntry *clients,
+			  SilcUInt32 clients_count)
+{
+	GaimRequestFields *fields;
+	GaimRequestFieldGroup *g;
+	GaimRequestField *f;
+	char tmp[512], tmp2[128];
+	int i;
+	char *fingerprint;
+
+	fields = gaim_request_fields_new();
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_list_new("list", NULL);
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_field_list_set_multi_select(f, FALSE);
+	gaim_request_fields_add_group(fields, g);
+
+	for (i = 0; i < clients_count; i++) {
+		fingerprint = NULL;
+		if (clients[i]->fingerprint) {
+			fingerprint = silc_fingerprint(clients[i]->fingerprint,
+						       clients[i]->fingerprint_len);
+			g_snprintf(tmp2, sizeof(tmp2), "\n%s", fingerprint);
+		}
+		g_snprintf(tmp, sizeof(tmp), "%s - %s (%s@%s)%s",
+			   clients[i]->realname, clients[i]->nickname,
+			   clients[i]->username, clients[i]->hostname ?
+			   clients[i]->hostname : "",
+			   fingerprint ? tmp2 : "");
+		gaim_request_field_list_add(f, tmp, clients[i]);
+		silc_free(fingerprint);
+	}
+
+	gaim_request_fields(r->client->application, _("Add Buddy"),
+				_("Select correct user"),
+				r->pubkey_search
+					? _("More than one user was found with the same public key. Select "
+						"the correct user from the list to add to the buddy list.")
+					: _("More than one user was found with the same name. Select "
+						"the correct user from the list to add to the buddy list."),
+				fields,
+				_("OK"), G_CALLBACK(silcgaim_add_buddy_select_cb),
+				_("Cancel"), G_CALLBACK(silcgaim_add_buddy_select_cancel), r);
+}
+
+static void
+silcgaim_add_buddy_resolved(SilcClient client,
+			    SilcClientConnection conn,
+			    SilcClientEntry *clients,
+			    SilcUInt32 clients_count,
+			    void *context)
+{
+	SilcGaimBuddyRes r = context;
+	GaimBuddy *b = r->b;
+	SilcAttributePayload pub;
+	SilcAttributeObjPk userpk;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
+	const char *filename;
+
+	filename = gaim_blist_node_get_string((GaimBlistNode *)b, "public-key");
+
+	/* If the buddy is offline/nonexistent, we will require user
+	   to associate a public key with the buddy or the buddy
+	   cannot be added. */
+	if (!clients_count) {
+		if (r->init) {
+			silc_free(r);
+			return;
+		}
+
+		r->offline = TRUE;
+		/* If the user has already associated a public key, try loading it
+		 * before prompting the user to load it again */
+		if (filename != NULL)
+			silcgaim_add_buddy_ask_import(r, filename);
+		else
+			silcgaim_add_buddy_ask_pk(r);
+		return;
+	}
+
+	/* If more than one client was found with nickname, we need to verify
+	   from user which one is the correct. */
+	if (clients_count > 1 && !r->pubkey_search) {
+		if (r->init) {
+			silc_free(r);
+			return;
+		}
+
+		silcgaim_add_buddy_select(r, clients, clients_count);
+		return;
+	}
+
+	/* If we searched using public keys and more than one entry was found
+	   the same person is logged on multiple times. */
+	if (clients_count > 1 && r->pubkey_search && b->name) {
+		if (r->init) {
+			/* Find the entry that closest matches to the
+			   buddy nickname. */
+			int i;
+			for (i = 0; i < clients_count; i++) {
+				if (!strncasecmp(b->name, clients[i]->nickname,
+						 strlen(b->name))) {
+					clients[0] = clients[i];
+					break;
+				}
+			}
+		} else {
+			/* Verify from user which one is correct */
+			silcgaim_add_buddy_select(r, clients, clients_count);
+			return;
+		}
+	}
+
+	/* The client was found.  Now get its public key and verify
+	   that before adding the buddy. */
+	memset(&userpk, 0, sizeof(userpk));
+	b->proto_data = silc_memdup(clients[0]->id, sizeof(*clients[0]->id));
+	r->client_id = *clients[0]->id;
+
+	/* Get the public key from attributes, if not present then
+	   resolve it with GETKEY unless we have it cached already. */
+	if (clients[0]->attrs && !clients[0]->public_key) {
+		pub = silcgaim_get_attr(clients[0]->attrs,
+					SILC_ATTRIBUTE_USER_PUBLIC_KEY);
+		if (!pub || !silc_attribute_get_object(pub, (void *)&userpk,
+						       sizeof(userpk))) {
+			/* Get public key with GETKEY */
+			silc_client_command_call(client, conn, NULL,
+						 "GETKEY", clients[0]->nickname, NULL);
+			silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+						    conn->cmd_ident,
+						    (SilcCommandCb)silcgaim_add_buddy_getkey_cb,
+						    r);
+			return;
+		}
+		if (!silc_pkcs_public_key_decode(userpk.data, userpk.data_len,
+						 &clients[0]->public_key))
+			return;
+		silc_free(userpk.data);
+	} else if (filename && !clients[0]->public_key) {
+		if (!silc_pkcs_load_public_key(filename, &clients[0]->public_key,
+					       SILC_PKCS_FILE_PEM) &&
+		    !silc_pkcs_load_public_key(filename, &clients[0]->public_key,
+					       SILC_PKCS_FILE_BIN)) {
+			/* Get public key with GETKEY */
+			silc_client_command_call(client, conn, NULL,
+						 "GETKEY", clients[0]->nickname, NULL);
+			silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+						    conn->cmd_ident,
+						    (SilcCommandCb)silcgaim_add_buddy_getkey_cb,
+						    r);
+			return;
+		}
+	} else if (!clients[0]->public_key) {
+		/* Get public key with GETKEY */
+		silc_client_command_call(client, conn, NULL,
+					 "GETKEY", clients[0]->nickname, NULL);
+		silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+					    conn->cmd_ident,
+					    (SilcCommandCb)silcgaim_add_buddy_getkey_cb,
+					    r);
+		return;
+	}
+
+	/* We have the public key, verify it. */
+	pk = silc_pkcs_public_key_encode(clients[0]->public_key, &pk_len);
+	silcgaim_verify_public_key(client, conn, clients[0]->nickname,
+				   SILC_SOCKET_TYPE_CLIENT,
+				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+				   silcgaim_add_buddy_save, r);
+	silc_free(pk);
+}
+
+static void
+silcgaim_add_buddy_i(GaimConnection *gc, GaimBuddy *b, gboolean init)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcGaimBuddyRes r;
+	SilcBuffer attrs;
+	const char *filename, *name = b->name;
+
+	r = silc_calloc(1, sizeof(*r));
+	if (!r)
+		return;
+	r->client = client;
+	r->conn = conn;
+	r->b = b;
+	r->init = init;
+
+	/* See if we have this buddy's public key.  If we do use that
+	   to search the details. */
+	filename = gaim_blist_node_get_string((GaimBlistNode *)b, "public-key");
+	if (filename) {
+		SilcPublicKey public_key;
+		SilcAttributeObjPk userpk;
+
+		if (!silc_pkcs_load_public_key(filename, &public_key,
+					       SILC_PKCS_FILE_PEM) &&
+		    !silc_pkcs_load_public_key(filename, &public_key,
+					       SILC_PKCS_FILE_BIN))
+			return;
+
+		/* Get all attributes, and use the public key to search user */
+		name = NULL;
+		attrs = silc_client_attributes_request(SILC_ATTRIBUTE_USER_INFO,
+						       SILC_ATTRIBUTE_SERVICE,
+						       SILC_ATTRIBUTE_STATUS_MOOD,
+						       SILC_ATTRIBUTE_STATUS_FREETEXT,
+						       SILC_ATTRIBUTE_STATUS_MESSAGE,
+						       SILC_ATTRIBUTE_PREFERRED_LANGUAGE,
+						       SILC_ATTRIBUTE_PREFERRED_CONTACT,
+						       SILC_ATTRIBUTE_TIMEZONE,
+						       SILC_ATTRIBUTE_GEOLOCATION,
+#ifdef SILC_ATTRIBUTE_USER_ICON
+						       SILC_ATTRIBUTE_USER_ICON,
+#endif
+						       SILC_ATTRIBUTE_DEVICE_INFO, 0);
+		userpk.type = "silc-rsa";
+		userpk.data = silc_pkcs_public_key_encode(public_key, &userpk.data_len);
+		attrs = silc_attribute_payload_encode(attrs,
+						      SILC_ATTRIBUTE_USER_PUBLIC_KEY,
+						      SILC_ATTRIBUTE_FLAG_VALID,
+						      &userpk, sizeof(userpk));
+		silc_free(userpk.data);
+		silc_pkcs_public_key_free(public_key);
+		r->pubkey_search = TRUE;
+	} else {
+		/* Get all attributes */
+		attrs = silc_client_attributes_request(0);
+	}
+
+	/* Resolve */
+	silc_client_get_clients_whois(client, conn, name, NULL, attrs,
+				      silcgaim_add_buddy_resolved, r);
+	silc_buffer_free(attrs);
+}
+
+void silcgaim_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+	silcgaim_add_buddy_i(gc, buddy, FALSE);
+}
+
+void silcgaim_send_buddylist(GaimConnection *gc)
+{
+	GaimBuddyList *blist;
+	GaimBlistNode *gnode, *cnode, *bnode;
+	GaimBuddy *buddy;
+	GaimAccount *account;
+
+	account = gaim_connection_get_account(gc);
+
+	if ((blist = gaim_get_blist()) != NULL)
+	{
+		for (gnode = blist->root; gnode != NULL; gnode = gnode->next)
+		{
+			if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+				continue;
+			for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
+			{
+				if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
+					continue;
+				for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
+				{
+					if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
+						continue;
+					buddy = (GaimBuddy *)bnode;
+					if (gaim_buddy_get_account(buddy) == account)
+						silcgaim_add_buddy_i(gc, buddy, TRUE);
+				}
+			}
+		}
+	}
+}
+
+void silcgaim_remove_buddy(GaimConnection *gc, GaimBuddy *buddy,
+			   GaimGroup *group)
+{
+	silc_free(buddy->proto_data);
+}
+
+void silcgaim_idle_set(GaimConnection *gc, int idle)
+
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcAttributeObjService service;
+	const char *server;
+	int port;
+
+	server = gaim_account_get_string(sg->account, "server",
+					 "silc.silcnet.org");
+	port = gaim_account_get_int(sg->account, "port", 706),
+
+	memset(&service, 0, sizeof(service));
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_SERVICE, NULL);
+	service.port = port;
+	g_snprintf(service.address, sizeof(service.address), "%s", server);
+	service.idle = idle;
+	silc_client_attribute_add(client, conn, SILC_ATTRIBUTE_SERVICE,
+				  &service, sizeof(service));
+}
+
+char *silcgaim_status_text(GaimBuddy *b)
+{
+	SilcGaim sg = b->account->gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientID *client_id = b->proto_data;
+	SilcClientEntry client_entry;
+	SilcAttributePayload attr;
+	SilcAttributeMood mood = 0;
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(client, conn, client_id);
+	if (!client_entry)
+		return NULL;
+
+	/* If user is online, we show the mood status, if available.
+	   If user is offline or away that status is indicated. */
+
+	if (client_entry->mode & SILC_UMODE_DETACHED)
+		return g_strdup(_("Detached"));
+	if (client_entry->mode & SILC_UMODE_GONE)
+		return g_strdup(_("Away"));
+	if (client_entry->mode & SILC_UMODE_INDISPOSED)
+		return g_strdup(_("Indisposed"));
+	if (client_entry->mode & SILC_UMODE_BUSY)
+		return g_strdup(_("Busy"));
+	if (client_entry->mode & SILC_UMODE_PAGE)
+		return g_strdup(_("Wake Me Up"));
+	if (client_entry->mode & SILC_UMODE_HYPER)
+		return g_strdup(_("Hyper Active"));
+	if (client_entry->mode & SILC_UMODE_ROBOT)
+		return g_strdup(_("Robot"));
+
+	attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_STATUS_MOOD);
+	if (attr && silc_attribute_get_object(attr, &mood, sizeof(mood))) {
+		/* The mood is a bit mask, so we could show multiple moods,
+		   but let's show only one for now. */
+		if (mood & SILC_ATTRIBUTE_MOOD_HAPPY)
+			return g_strdup(_("Happy"));
+		if (mood & SILC_ATTRIBUTE_MOOD_SAD)
+			return g_strdup(_("Sad"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ANGRY)
+			return g_strdup(_("Angry"));
+		if (mood & SILC_ATTRIBUTE_MOOD_JEALOUS)
+			return g_strdup(_("Jealous"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ASHAMED)
+			return g_strdup(_("Ashamed"));
+		if (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE)
+			return g_strdup(_("Invincible"));
+		if (mood & SILC_ATTRIBUTE_MOOD_INLOVE)
+			return g_strdup(_("In Love"));
+		if (mood & SILC_ATTRIBUTE_MOOD_SLEEPY)
+			return g_strdup(_("Sleepy"));
+		if (mood & SILC_ATTRIBUTE_MOOD_BORED)
+			return g_strdup(_("Bored"));
+		if (mood & SILC_ATTRIBUTE_MOOD_EXCITED)
+			return g_strdup(_("Excited"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS)
+			return g_strdup(_("Anxious"));
+	}
+
+	return NULL;
+}
+
+void silcgaim_tooltip_text(GaimBuddy *b, GaimNotifyUserInfo *user_info, gboolean full)
+{
+	SilcGaim sg = b->account->gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientID *client_id = b->proto_data;
+	SilcClientEntry client_entry;
+	char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr;
+	char tmp[256];
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(client, conn, client_id);
+	if (!client_entry)
+		return;
+
+	if (client_entry->nickname)
+		gaim_notify_user_info_add_pair(user_info, _("Nickname"),
+					       client_entry->nickname);
+	if (client_entry->username && client_entry->hostname) {
+		g_snprintf(tmp, sizeof(tmp), "%s@%s", client_entry->username, client_entry->hostname);
+		gaim_notify_user_info_add_pair(user_info, _("Username"), tmp);
+	}
+	if (client_entry->mode) {
+		memset(tmp, 0, sizeof(tmp));
+		silcgaim_get_umode_string(client_entry->mode,
+					  tmp, sizeof(tmp) - strlen(tmp));
+		gaim_notify_user_info_add_pair(user_info, _("User Modes"), tmp);
+	}
+
+	silcgaim_parse_attrs(client_entry->attrs, &moodstr, &statusstr, &contactstr, &langstr, &devicestr, &tzstr, &geostr);
+
+	if (statusstr) {
+		gaim_notify_user_info_add_pair(user_info, _("Message"), statusstr);
+		g_free(statusstr);
+	}
+
+	if (full) {
+		if (moodstr) {
+			gaim_notify_user_info_add_pair(user_info, _("Mood"), moodstr);
+			g_free(moodstr);
+		}
+
+		if (contactstr) {
+			gaim_notify_user_info_add_pair(user_info, _("Preferred Contact"), contactstr);
+			g_free(contactstr);
+		}
+
+		if (langstr) {
+			gaim_notify_user_info_add_pair(user_info, _("Preferred Language"), langstr);
+			g_free(langstr);
+		}
+
+		if (devicestr) {
+			gaim_notify_user_info_add_pair(user_info, _("Device"), devicestr);
+			g_free(devicestr);
+		}
+
+		if (tzstr) {
+			gaim_notify_user_info_add_pair(user_info, _("Timezone"), tzstr);
+			g_free(tzstr);
+		}
+
+		if (geostr) {
+			gaim_notify_user_info_add_pair(user_info, _("Geolocation"), geostr);
+			g_free(geostr);
+		}
+	}
+}
+
+static void
+silcgaim_buddy_kill(GaimBlistNode *node, gpointer data)
+{
+	GaimBuddy *b;
+	GaimConnection *gc;
+	SilcGaim sg;
+
+	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+	b = (GaimBuddy *) node;
+	gc = gaim_account_get_connection(b->account);
+	sg = gc->proto_data;
+
+	/* Call KILL */
+	silc_client_command_call(sg->client, sg->conn, NULL, "KILL",
+				 b->name, "Killed by operator", NULL);
+}
+
+typedef struct {
+	SilcGaim sg;
+	SilcClientEntry client_entry;
+} *SilcGaimBuddyWb;
+
+static void
+silcgaim_buddy_wb(GaimBlistNode *node, gpointer data)
+{
+	SilcGaimBuddyWb wb = data;
+	silcgaim_wb_init(wb->sg, wb->client_entry);
+	silc_free(wb);
+}
+
+GList *silcgaim_buddy_menu(GaimBuddy *buddy)
+{
+	GaimConnection *gc = gaim_account_get_connection(buddy->account);
+	SilcGaim sg = gc->proto_data;
+	SilcClientConnection conn = sg->conn;
+	const char *pkfile = NULL;
+	SilcClientEntry client_entry = NULL;
+	GaimMenuAction *act;
+	GList *m = NULL;
+	SilcGaimBuddyWb wb;
+
+	pkfile = gaim_blist_node_get_string((GaimBlistNode *) buddy, "public-key");
+	client_entry = silc_client_get_client_by_id(sg->client,
+						    sg->conn,
+						    buddy->proto_data);
+
+	if (client_entry && client_entry->send_key) {
+		act = gaim_menu_action_new(_("Reset IM Key"),
+		                           GAIM_CALLBACK(silcgaim_buddy_resetkey),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+	} else {
+		act = gaim_menu_action_new(_("IM with Key Exchange"),
+		                           GAIM_CALLBACK(silcgaim_buddy_keyagr),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		act = gaim_menu_action_new(_("IM with Password"),
+		                           GAIM_CALLBACK(silcgaim_buddy_privkey_menu),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	if (pkfile) {
+		act = gaim_menu_action_new(_("Show Public Key"),
+		                           GAIM_CALLBACK(silcgaim_buddy_showkey),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+	} else {
+		act = gaim_menu_action_new(_("Get Public Key..."),
+		                           GAIM_CALLBACK(silcgaim_buddy_getkey_menu),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	if (conn && conn->local_entry->mode & SILC_UMODE_ROUTER_OPERATOR) {
+		act = gaim_menu_action_new(_("Kill User"),
+		                           GAIM_CALLBACK(silcgaim_buddy_kill),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	if (client_entry) {
+		wb = silc_calloc(1, sizeof(*wb));
+		wb->sg = sg;
+		wb->client_entry = client_entry;
+		act = gaim_menu_action_new(_("Draw On Whiteboard"),
+		                           GAIM_CALLBACK(silcgaim_buddy_wb),
+		                           (void *)wb, NULL);
+		m = g_list_append(m, act);
+	}
+	return m;
+}
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+void silcgaim_buddy_set_icon(GaimConnection *gc, const char *iconfile)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcMime mime;
+	GaimBuddyIcon ic;
+	char type[32];
+	unsigned char *icon;
+	const char *t;
+	struct stat st;
+	FILE *fp;
+	SilcAttributeObjMime obj;
+
+	/* Remove */
+	if (!iconfile) {
+		silc_client_attribute_del(client, conn,
+					  SILC_ATTRIBUTE_USER_ICON, NULL);
+		return;
+	}
+
+	/* Add */
+	if (g_stat(iconfile, &st) < 0)
+		return;
+	fp = g_fopen(iconfile, "rb");
+	if (!fp)
+		return;
+	ic.data = g_malloc(st.st_size);
+	if (!ic.data)
+		return;
+	ic.len = fread(ic.data, 1, st.st_size, fp);
+	fclose(fp);
+
+	mime = silc_mime_alloc();
+	if (!mime) {
+		g_free(ic.data);
+		return;
+	}
+
+	t = gaim_buddy_icon_get_type((const GaimBuddyIcon *)&ic);
+	if (!t) {
+		g_free(ic.data);
+		silc_mime_free(mime);
+		return;
+	}
+	if (!strcmp(t, "jpg"))
+		t = "jpeg";
+	g_snprintf(type, sizeof(type), "image/%s", t);
+	silc_mime_add_field(mime, "Content-Type", type);
+	silc_mime_add_data(mime, ic.data, ic.len);
+
+	obj.mime = icon = silc_mime_encode(mime, &obj.mime_len);
+	if (obj.mime)
+		silc_client_attribute_add(client, conn, 
+					  SILC_ATTRIBUTE_USER_ICON, &obj, sizeof(obj));
+
+	silc_free(icon);
+	g_free(ic.data);
+	silc_mime_free(mime);
+}
+#endif