changeset 8849:50d0f76639e7

[gaim-migrate @ 9616] Let there be SILC. committer: Tailor Script <tailor@pidgin.im>
author Ethan Blanton <elb@pidgin.im>
date Sat, 01 May 2004 19:34:44 +0000
parents 56e6b9bdcdbc
children 23bdb4cc0cad
files ChangeLog configure.ac src/protocols/Makefile.am src/protocols/silc/.cvsignore src/protocols/silc/Makefile.am src/protocols/silc/README src/protocols/silc/TODO src/protocols/silc/buddy.c src/protocols/silc/chat.c src/protocols/silc/ft.c src/protocols/silc/ops.c src/protocols/silc/pk.c src/protocols/silc/silc.c src/protocols/silc/silcgaim.h src/protocols/silc/util.c
diffstat 15 files changed, 6880 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat May 01 15:20:25 2004 +0000
+++ b/ChangeLog	Sat May 01 19:34:44 2004 +0000
@@ -2,6 +2,8 @@
 
 version 0.78cvs
 	New Features:
+        * Support for the SILC (http://www.silcnet.org/) protocol from
+          Pekka Riikonen.
 	* Option to suppress the disconnect notification when using
 	  the autoreconnect plugin (Christopher (siege) O'Brien)
 	* Added support for dragging buddies from the buddy list into the
--- a/configure.ac	Sat May 01 15:20:25 2004 +0000
+++ b/configure.ac	Sat May 01 19:34:44 2004 +0000
@@ -1,5 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 AC_INIT([gaim], [0.78cvs], [gaim-devel@lists.sourceforge.net])
+AC_CANONICAL_SYSTEM
 AM_CONFIG_HEADER(config.h)
 AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
 
@@ -102,7 +103,7 @@
 fi
 
 if test "x$STATIC_PRPLS" = "xall" ; then
-	STATIC_PRPLS="gg irc jabber msn napster novell oscar yahoo zephyr"
+	STATIC_PRPLS="gg irc jabber msn napster novell oscar silc yahoo zephyr"
 fi
 AC_SUBST(STATIC_PRPLS)
 STATIC_LINK_LIBS=
@@ -121,6 +122,7 @@
 		novell)		static_novell=yes ;;
 		oscar)		static_oscar=yes ;;
 		rendezvous)	static_rendezvous=yes ;;
+		silc)		static_silc=yes ;;
 		toc)		static_toc=yes ;;
 		trepia)		static_trepia=yes ;;
 		yahoo)		static_yahoo=yes ;;
@@ -136,6 +138,7 @@
 AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes")
 AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes")
 AM_CONDITIONAL(STATIC_RENDEZVOUS, test "x$static_rendezvous" = "xyes")
+AM_CONDITIONAL(STATIC_SILC, test "x$static_silc" = "xyes")
 AM_CONDITIONAL(STATIC_TOC, test "x$static_toc" = "xyes")
 AM_CONDITIONAL(STATIC_TREPIA, test "x$static_trepia" = "xyes")
 AM_CONDITIONAL(STATIC_YAHOO, test "x$static_yahoo" = "xyes")
@@ -146,7 +149,7 @@
 
 AC_ARG_WITH(dynamic_prpls,   [  --with-dynamic-prpls    specify which protocols to build dynamically],[DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`])
 if test "x$DYNAMIC_PRPLS" = "xall" ; then
-	DYNAMIC_PRPLS="gg irc jabber msn napster novell oscar yahoo zephyr"
+	DYNAMIC_PRPLS="gg irc jabber msn napster novell oscar silc yahoo zephyr"
 fi
 AC_SUBST(DYNAMIC_PRPLS)
 for i in $DYNAMIC_PRPLS ; do
@@ -159,6 +162,7 @@
 		novell)		dynamic_novell=yes ;;
 		oscar)		dynamic_oscar=yes ;;
 		rendezvous)	dynamic_rendezvous=yes ;;
+		silc)		dynamic_silc=yes ;;
 		toc)		dynamic_toc=yes ;;
 		trepia)		dynamic_trepia=yes ;;
 		yahoo)		dynamic_yahoo=yes ;;
@@ -174,6 +178,7 @@
 AM_CONDITIONAL(DYNAMIC_NOVELL, test "x$dynamic_novell" = "xyes")
 AM_CONDITIONAL(DYNAMIC_OSCAR, test "x$dynamic_oscar" = "xyes")
 AM_CONDITIONAL(DYNAMIC_RENDEZVOUS, test "x$dynamic_rendezvous" = "xyes")
+AM_CONDITIONAL(DYNAMIC_SILC, test "x$dynamic_silc" = "xyes")
 AM_CONDITIONAL(DYNAMIC_TOC, test "x$dynamic_toc" = "xyes")
 AM_CONDITIONAL(DYNAMIC_TREPIA, test "x$dynamic_trepia" = "xyes")
 AM_CONDITIONAL(DYNAMIC_YAHOO, test "x$dynamic_yahoo" = "xyes")
@@ -193,9 +198,39 @@
 AC_ARG_ENABLE(screensaver,   [  --disable-screensaver   compile without X screensaver extension],,enable_xss=yes)
 AC_ARG_ENABLE(sm,      [  --disable-sm            compile without X session management support],,enable_sm=yes)
 AC_ARG_WITH(krb4,      [  --with-krb4=PREFIX      Compile Zephyr plugin with Kerberos 4 support],kerberos="$withval",kerberos="no")
+AC_ARG_WITH(zephyr,    [  --with-zephyr=PREFIX    Compile Zephyr plugin against external libzephyr],zephyr="$withval",zephyr="no")
+AC_ARG_WITH(silc-libs, [  --with-silc-libs=DIR    Compile the SILC plugin against the SILC libs in DIR], [ac_silc_libs="$withval"])
+AC_ARG_WITH(silc-includes, [  --with-silc-includes=DIR
+                          Compile the SILC plugin against includes in DIR ],
+        [  ac_silc_includes="$withval" ])
+AM_CONDITIONAL(EXTERNAL_LIBZEPHYR, test "x$zephyr" != "xno")
 
-AC_ARG_WITH(zephyr,    [  --with-zephyr=PREFIX    Compile Zephyr plugin against external libzephyr],zephyr="$withval",zephyr="no")
-AM_CONDITIONAL(EXTERNAL_LIBZEPHYR, test "x$zephyr" != "xno")
+#
+# SILC Toolkit check for SILC Protocol Plugin
+#
+SILC_INCLUDES=""
+SILC_LIBS=""
+ac_silc_includes=""
+ac_silc_libs=""
+
+if test "$ac_silc_includes" != "" ; then
+SILC_INCLUDES="-I$ac_silc_includes"
+else
+static_silc=no
+dynamic_silc=no
+fi
+
+if test "$ac_silc_libs" != "" ; then
+        SILC_LIBS="-L$ac_silc_libs"
+else
+static_silc=no
+dynamic_silc=no
+fi
+SILC_LIBS="$SILC_LIBS -lsilc -lsilcclient -lpthread"
+AC_SUBST(SILC_INCLUDES)
+AC_SUBST(SILC_LIBS)
+AC_CHECK_HEADER(sys/utsname.h)
+AC_CHECK_FUNC(uname)
 
 if test "$enable_debug" = yes ; then
 	AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.])
@@ -1109,6 +1144,7 @@
 		   src/protocols/trepia/Makefile
 		   src/protocols/yahoo/Makefile
 		   src/protocols/zephyr/Makefile
+		   src/protocols/silc/Makefile
 		   gaim.spec
 		  ])
 
--- a/src/protocols/Makefile.am	Sat May 01 15:20:25 2004 +0000
+++ b/src/protocols/Makefile.am	Sat May 01 19:34:44 2004 +0000
@@ -1,3 +1,3 @@
-DIST_SUBDIRS = gg irc jabber msn napster novell oscar rendezvous toc trepia yahoo zephyr
+DIST_SUBDIRS = gg irc jabber msn napster novell oscar rendezvous toc trepia yahoo zephyr silc
 
 SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/.cvsignore	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,6 @@
+*.lo
+*.la
+.libs
+.deps
+Makefile
+Makefile.in
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/Makefile.am	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,35 @@
+EXTRA_DIST = README TODO
+
+pkgdir = $(libdir)/gaim
+
+SILCSOURCES = silc.c silcgaim.h buddy.c chat.c ft.c ops.c pk.c util.c
+
+AM_CFLAGS = $(st)
+
+libsilc_la_LDFLAGS = -module -avoid-version
+
+if STATIC_IRC
+
+st = -DGAIM_STATIC_PRPL $(SILC_INCLUDES)
+noinst_LIBRARIES = libsilc.a
+pkg_LTLIBRARIES =
+
+libsilc_a_SOURCES = $(SILCSOURCES)
+libsilc_a_CFLAGS  = $(AM_CFLAGS)
+libsilc_a_LIBADD  = $(SILC_LIBS)
+
+else
+
+st = $(SILC_INCLUDES)
+pkg_LTLIBRARIES = libsilc.la
+noinst_LIBRARIES =
+
+libsilc_la_SOURCES = $(SILCSOURCES)
+libsilc_la_LIBADD  = $(SILC_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src \
+	$(GLIB_CFLAGS) \
+	$(DEBUG_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/README	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,27 @@
+SILC Gaim Plugin
+================
+
+This is Gaim protocol plugin of the protocol called Secure Internet Live 
+Conferencing (SILC).  The implementation will use the SILC Toolkit, 
+freely available from the http://silcnet.org/ site, for the actual SILC 
+protocol implementation.
+
+To include the SILC into Gaim, one needs to first compile and install 
+the SILC Toolkit.  It is done as follows:
+
+	./configure --enable-shared --without-silcd --without-irssi
+	make
+	make install
+
+This will compile shared libraries of the SILC Toolkit.  If the --prefix 
+is not given to ./configure, the binaries are installed into the 
+/usr/local/silc directory.
+
+Once the Toolkit is installed one needs to tell for the Gaim ./configure
+script where the SILC Toolkit is located.  It is done as follows:
+
+	./configure --with-silc-libs=/path/to/silc/lib
+		    --with-silc-includes=/path/to/silc/include
+
+If the Toolkit cannot be located the SILC will not be compiled into the 
+Gaim.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/TODO	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,7 @@
+Features TODO (maybe)
+=====================
+
+Preferences
+	- Add joined channels to buddy list automatically (during
+	  session)
+	- Add joined channels to buddy list automatically permanently
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/buddy.c	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,1621 @@
+/*
+
+  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"
+
+/***************************** Key Agreement *********************************/
+
+static void
+silcgaim_buddy_keyagr(GaimConnection *gc, const char *name);
+
+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(gc, r->nick);
+	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(client_entry->nickname,
+								    sg->account);
+			if (convo)
+				gaim_conv_window_show(gaim_conversation_get_window(convo));
+			else
+				convo = gaim_conversation_new(GAIM_CONV_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(NULL, _("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(GaimConnection *gc, const char *name)
+{
+	silcgaim_buddy_keyagr_do(gc, name, FALSE);
+}
+
+
+/**************************** Static IM Key **********************************/
+
+static void
+silcgaim_buddy_resetkey(GaimConnection *gc, const char *name)
+{
+        SilcGaim sg = gc->proto_data;
+	char *nickname;
+	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_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(NULL, _("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);
+}
+
+
+/**************************** 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_showkey(GaimConnection *gc, const char *name)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcPublicKey public_key;
+	const char *pkfile;
+	GaimBuddy *b;
+
+	b = gaim_find_buddy(gc->account, name);
+	if (!b)
+		return;
+
+	pkfile = gaim_blist_node_get_string((GaimBlistNode *)b, "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, 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 untill you "
+			    "import its public key.  You can use the Get Public Key "
+			    "command to get the public key."));
+	gaim_blist_update_buddy_presence(r->b, GAIM_BUDDY_OFFLINE);
+}
+
+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;
+	SilcAttributeObjPk serverpk, usersign, serversign;
+	gboolean usign_success = TRUE, ssign_success = TRUE;
+	unsigned char filename[256], filename2[256], *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_blist_save();
+		gaim_blist_update_buddy_presence(r->b, GAIM_BUDDY_OFFLINE);
+		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));
+	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. */
+
+	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;
+
+		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("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("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 ((stat(filename, &st)) == -1) {
+			if (errno == ENOENT) {
+				if (pw->pw_uid == geteuid())
+					mkdir(filename, 0755);
+			}
+		}
+
+		/* Save VCard */
+		g_snprintf(filename2, sizeof(filename2) - 1,
+			   "%s" G_DIR_SEPARATOR_S "vcard", filename);
+		if (vcard.full_name) {
+			tmp = 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, 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, extension.mime,
+					    extension.mime_len);
+		}
+	}
+
+	/* 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);
+	gaim_blist_save();
+
+	/* Update online status on the buddy list */
+	gaim_blist_update_buddy_presence(b, GAIM_BUDDY_ONLINE);
+
+	/* 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(NULL, _("Open..."), NULL,
+			  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(NULL, _("Add Buddy"), tmp,
+			    _("To add the buddy you must import its 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];
+	int i;
+
+	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++) {
+		g_snprintf(tmp, sizeof(tmp), "%s - %s (%s@%s)",
+			   clients[i]->realname, clients[i]->nickname,
+			   clients[i]->username, clients[i]->hostname ?
+			   clients[i]->hostname : "");
+		gaim_request_field_list_add(f, tmp, clients[i]);
+	}
+
+	g_snprintf(tmp, sizeof(tmp),
+		   _("More than one users were found with the same %s. Select "
+		     "the correct user from the list to add to the buddy list."),
+		   r->pubkey_search ? "public key" : "name");
+	gaim_request_fields(NULL, _("Add Buddy"),
+			    _("Select correct user"), tmp, 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;
+
+	/* 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;
+		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;
+
+	filename = gaim_blist_node_get_string((GaimBlistNode *)b, "public-key");
+
+	/* 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,
+						       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, const char *name, GaimGroup *grp)
+{
+	GaimBuddy *b;
+
+	b = gaim_find_buddy_in_group(gc->account, name, grp);
+	if (!b)
+	  return;
+
+	silcgaim_add_buddy_i(gc, b, FALSE);
+}
+
+void silcgaim_add_buddies(GaimConnection *gc, GList *buddies)
+{
+	while (buddies) {
+		GaimBuddy *b;
+		b = gaim_find_buddy(gc->account, buddies->data);
+		if (!b)
+			continue;
+		silcgaim_add_buddy_i(gc, b, TRUE);
+		buddies = buddies->next;
+	}
+}
+
+void silcgaim_remove_buddy(GaimConnection *gc, const char *name,
+			   const char *group)
+{
+	GaimBuddy *b;
+	GaimGroup *g;
+
+	g = gaim_find_group(group);
+	b = gaim_find_buddy_in_group(gc->account, name, g);
+	if (!b)
+		return;
+
+	silc_free(b->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;
+}
+
+char *silcgaim_tooltip_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;
+	SilcAttributeContact contact;
+	SilcAttributeObjDevice device;
+	SilcAttributeObjGeo geo;
+	GString *s;
+	char *buf;
+	char tmp[256];
+
+	s = g_string_new("");
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(client, conn, client_id);
+	if (!client_entry)
+	  return NULL;
+
+	if (client_entry->nickname)
+	  g_string_append_printf(s, "<b>Nickname:</b> %s\n",
+				 client_entry->nickname);
+	if (client_entry->username && client_entry->hostname)
+	  g_string_append_printf(s, "<b>Username:</b> %s@%s\n",
+				 client_entry->username, client_entry->hostname);
+	if (client_entry->mode) {
+	  g_string_append_printf(s, "<b>Modes:</b> ");
+	  memset(tmp, 0, sizeof(tmp));
+	  silcgaim_get_umode_string(client_entry->mode,
+				    tmp, sizeof(tmp) - strlen(tmp));
+	  g_string_append_printf(s, "%s\n", tmp);
+	}
+
+	attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_STATUS_MOOD);
+	if (attr && silc_attribute_get_object(attr, &mood, sizeof(mood))) {
+		if (mood)
+			g_string_append_printf(s, "<b>Mood:</b> ");
+		if (mood & SILC_ATTRIBUTE_MOOD_HAPPY)
+			g_string_append_printf(s, "[Happy] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_SAD)
+			g_string_append_printf(s, "[Sad] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_ANGRY)
+			g_string_append_printf(s, "[Angry] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_JEALOUS)
+			g_string_append_printf(s, "[Jealous] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_ASHAMED)
+			g_string_append_printf(s, "[Ashamed] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE)
+			g_string_append_printf(s, "[Invincible] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_INLOVE)
+			g_string_append_printf(s, "[In Love] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_SLEEPY)
+			g_string_append_printf(s, "[Sleepy] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_BORED)
+			g_string_append_printf(s, "[Bored] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_EXCITED)
+			g_string_append_printf(s, "[Excited] ");
+		if (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS)
+			g_string_append_printf(s, "[Anxious] ");
+		if (mood)
+			g_string_append_printf(s, "\n");
+	}
+
+	attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_STATUS_FREETEXT);
+	memset(tmp, 0, sizeof(tmp));
+	if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+		g_string_append_printf(s, "<b>Status Text:</b> %s\n", tmp);
+
+	attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_PREFERRED_CONTACT);
+	if (attr && silc_attribute_get_object(attr, &contact, sizeof(contact))) {
+		if (contact)
+			g_string_append_printf(s, "<b>Preferred Contact:</b> ");
+		if (contact & SILC_ATTRIBUTE_CONTACT_CHAT)
+			g_string_append_printf(s, "[chat] ");
+		if (contact & SILC_ATTRIBUTE_CONTACT_EMAIL)
+			g_string_append_printf(s, "[email] ");
+		if (contact & SILC_ATTRIBUTE_CONTACT_CALL)
+			g_string_append_printf(s, "[phone] ");
+		if (contact & SILC_ATTRIBUTE_CONTACT_PAGE)
+			g_string_append_printf(s, "[paging] ");
+		if (contact & SILC_ATTRIBUTE_CONTACT_SMS)
+			g_string_append_printf(s, "[SMS] ");
+		if (contact & SILC_ATTRIBUTE_CONTACT_MMS)
+			g_string_append_printf(s, "[MMS] ");
+		if (contact & SILC_ATTRIBUTE_CONTACT_VIDEO)
+			g_string_append_printf(s, "[video conferencing] ");
+		g_string_append_printf(s, "\n");
+	}
+
+	attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_PREFERRED_LANGUAGE);
+	memset(tmp, 0, sizeof(tmp));
+	if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+		g_string_append_printf(s, "<b>Preferred Language:</b> %s\n", tmp);
+
+	attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_DEVICE_INFO);
+	memset(&device, 0, sizeof(device));
+	if (attr && silc_attribute_get_object(attr, &device, sizeof(device))) {
+		g_string_append_printf(s, "<b>Device:</b> ");
+		if (device.type == SILC_ATTRIBUTE_DEVICE_COMPUTER)
+		    g_string_append_printf(s, "Computer: ");
+		if (device.type == SILC_ATTRIBUTE_DEVICE_MOBILE_PHONE)
+		    g_string_append_printf(s, "Mobile Phone: ");
+		if (device.type == SILC_ATTRIBUTE_DEVICE_PDA)
+		    g_string_append_printf(s, "PDA: ");
+		if (device.type == SILC_ATTRIBUTE_DEVICE_TERMINAL)
+		    g_string_append_printf(s, "Terminal: ");
+		g_string_append_printf(s, "%s %s %s %s\n",
+				       device.manufacturer ? device.manufacturer : "",
+				       device.version ? device.version : "",
+				       device.model ? device.model : "",
+				       device.language ? device.language : "");
+	}
+
+	attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_TIMEZONE);
+	memset(tmp, 0, sizeof(tmp));
+	if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+		g_string_append_printf(s, "<b>Timezone:</b> %s\n", tmp);
+
+	attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_GEOLOCATION);
+	memset(&geo, 0, sizeof(geo));
+	if (attr && silc_attribute_get_object(attr, &geo, sizeof(geo)))
+		g_string_append_printf(s, "<b>Geolocation:</b> %s %s %s (%s)\n",
+				       geo.longitude ? geo.longitude : "",
+				       geo.latitude ? geo.latitude : "",
+				       geo.altitude ? geo.altitude : "",
+				       geo.accuracy ? geo.accuracy : "");
+
+	buf = g_string_free(s, FALSE);
+	return buf;
+}
+
+static void
+silcgaim_buddy_kill(GaimConnection *gc, const char *name)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+
+	/* Call KILL */
+	silc_client_command_call(client, conn, NULL, "KILL",
+				 name, "Killed by operator", NULL);
+}
+
+static void
+silcgaim_buddy_send_file(GaimConnection *gc, const char *name)
+{
+	silcgaim_ftp_send_file(gc, name);
+}
+
+GList *silcgaim_buddy_menu(GaimConnection *gc, const char *name)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClientConnection conn = sg->conn;
+	GList *m = NULL;
+	struct proto_buddy_menu *pbm;
+	GaimBuddy *b;
+	const char *pkfile = NULL;
+	SilcClientEntry client_entry = NULL;
+
+	b = gaim_find_buddy(gc->account, name);
+	if (b) {
+		pkfile = gaim_blist_node_get_string((GaimBlistNode *)b, "public-key");
+		client_entry = silc_client_get_client_by_id(sg->client,
+							    sg->conn,
+							    b->proto_data);
+	}
+
+	if (client_entry && client_entry->send_key) {
+		pbm = g_new0(struct proto_buddy_menu, 1);
+		pbm->label = _("Reset IM Key");
+		pbm->callback = silcgaim_buddy_resetkey;
+		pbm->gc = gc;
+		m = g_list_append(m, pbm);
+	} else {
+		pbm = g_new0(struct proto_buddy_menu, 1);
+		pbm->label = _("IM with Key Exchange");
+		pbm->callback = silcgaim_buddy_keyagr;
+		pbm->gc = gc;
+		m = g_list_append(m, pbm);
+
+		pbm = g_new0(struct proto_buddy_menu, 1);
+		pbm->label = _("IM with Password");
+		pbm->callback = silcgaim_buddy_privkey;
+		pbm->gc = gc;
+		m = g_list_append(m, pbm);
+	}
+
+	if (pkfile) {
+		pbm = g_new0(struct proto_buddy_menu, 1);
+		pbm->label = _("Show Public Key");
+		pbm->callback = silcgaim_buddy_showkey;
+		pbm->gc = gc;
+		m = g_list_append(m, pbm);
+	} else {
+		pbm = g_new0(struct proto_buddy_menu, 1);
+		pbm->label = _("Get Public Key...");
+		pbm->callback = silcgaim_buddy_getkey;
+		pbm->gc = gc;
+		m = g_list_append(m, pbm);
+	}
+
+	pbm = g_new0(struct proto_buddy_menu, 1);
+	pbm->label = _("Send File...");
+	pbm->callback = silcgaim_buddy_send_file;
+	pbm->gc = gc;
+	m = g_list_append(m, pbm);
+
+	if (conn && conn->local_entry->mode & SILC_UMODE_ROUTER_OPERATOR) {
+		pbm = g_new0(struct proto_buddy_menu, 1);
+		pbm->label = _("Kill User");
+		pbm->callback = silcgaim_buddy_kill;
+		pbm->gc = gc;
+		m = g_list_append(m, pbm);
+	}
+
+	return m;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/chat.c	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,1294 @@
+/*
+
+  silcgaim_chat.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcgaim.h"
+
+/***************************** Channel Routines ******************************/
+
+GList *silcgaim_chat_info(GaimConnection *gc)
+{
+	GList *ci = NULL;
+	struct proto_chat_entry *pce;
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Channel:");
+	pce->identifier = "channel";
+	ci = g_list_append(ci, pce);
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Passphrase:");
+	pce->identifier = "passphrase";
+	pce->secret = TRUE;
+	ci = g_list_append(ci, pce);
+
+	return ci;
+}
+
+static void
+silcgaim_chat_getinfo(GaimConnection *gc, GHashTable *components);
+
+static void
+silcgaim_chat_getinfo_res(SilcClient client,
+			  SilcClientConnection conn,
+			  SilcChannelEntry *channels,
+			  SilcUInt32 channels_count,
+			  void *context)
+{
+	GHashTable *components = context;
+	GaimConnection *gc = client->application;
+	const char *chname;
+	char tmp[256];
+
+	chname = g_hash_table_lookup(components, "channel");
+	if (!chname)
+		return;
+
+	if (!channels) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("Channel %s does not exist in the network"), chname);
+		gaim_notify_error(gc, _("Channel Information"),
+				  _("Cannot get channel information"), tmp);
+		return;
+	}
+
+	silcgaim_chat_getinfo(gc, components);
+}
+
+static void
+silcgaim_chat_getinfo(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	const char *chname;
+	char *buf, tmp[256];
+	GString *s;
+	SilcChannelEntry channel;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+
+	if (!components)
+		return;
+
+	chname = g_hash_table_lookup(components, "channel");
+	if (!chname)
+		return;
+	channel = silc_client_get_channel(sg->client, sg->conn,
+					  (char *)chname);
+	if (!channel) {
+		silc_client_get_channel_resolve(sg->client, sg->conn,
+						(char *)chname,
+						silcgaim_chat_getinfo_res,
+						components);
+		return;
+	}
+
+	s = g_string_new("");
+	g_string_append_printf(s, "Channel Name:\t\t%s\n", channel->channel_name);
+	if (channel->user_list && silc_hash_table_count(channel->user_list))
+		g_string_append_printf(s, "User Count:\t\t%d\n",
+				       (int)silc_hash_table_count(channel->user_list));
+
+	silc_hash_table_list(channel->user_list, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+			g_string_append_printf(s, "Channel Founder:\t%s\n",
+					       chu->client->nickname);
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+
+	if (channel->channel_key)
+		g_string_append_printf(s, "Channel Cipher:\t\t%s\n",
+				       silc_cipher_get_name(channel->channel_key));
+	if (channel->hmac)
+		g_string_append_printf(s, "Channel HMAC:\t\t%s\n",
+				       silc_hmac_get_name(channel->hmac));
+
+	if (channel->topic)
+		g_string_append_printf(s, "\nChannel Topic:\n\%s\n", channel->topic);
+
+	if (channel->mode) {
+		g_string_append_printf(s, "\nChannel Modes:\n");
+		silcgaim_get_chmode_string(channel->mode, tmp, sizeof(tmp));
+		g_string_append_printf(s, tmp);
+		g_string_append_printf(s, "\n");
+	}
+
+	if (channel->founder_key) {
+		char *fingerprint, *babbleprint;
+		unsigned char *pk;
+		SilcUInt32 pk_len;
+		pk = silc_pkcs_public_key_encode(channel->founder_key, &pk_len);
+		fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+		babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+
+		g_string_append_printf(s, "\nFounder Key Fingerprint:\n%s\n\n", fingerprint);
+		g_string_append_printf(s, "Founder Key Babbleprint:\n%s", babbleprint);
+
+		silc_free(fingerprint);
+		silc_free(babbleprint);
+		silc_free(pk);
+	}
+
+	buf = g_string_free(s, FALSE);
+	gaim_notify_message(NULL, GAIM_NOTIFY_MSG_INFO,
+			    _("Channel Information"),
+			    _("Channel Information"),
+			    buf, NULL, NULL);
+	g_free(buf);
+}
+
+
+#if 0   /* XXX For now these are not implemented.  We need better
+	   listview dialog from Gaim for these. */
+/************************** Channel Invite List ******************************/
+
+static void
+silcgaim_chat_invitelist(GaimConnection *gc, GHashTable *components)
+{
+
+}
+
+
+/**************************** Channel Ban List *******************************/
+
+static void
+silcgaim_chat_banlist(GaimConnection *gc, GHashTable *components)
+{
+
+}
+#endif
+
+
+/************************* Channel Authentication ****************************/
+
+typedef struct {
+	SilcGaim sg;
+	SilcChannelEntry channel;
+	GaimChat *c;
+	SilcBuffer pubkeys;
+} *SilcGaimChauth;
+
+static void
+silcgaim_chat_chpk_add(void *user_data, const char *name)
+{
+	SilcGaimChauth sgc = (SilcGaimChauth)user_data;
+	SilcGaim sg = sgc->sg;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcPublicKey public_key;
+	SilcBuffer chpks, pk, chidp;
+	unsigned char mode[4];
+	SilcUInt32 m;
+
+	/* Load the public key */
+	if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) &&
+	    !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) {
+		silcgaim_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys);
+		silc_buffer_free(sgc->pubkeys);
+		silc_free(sgc);
+		gaim_notify_error(client->application,
+				  _("Add Channel Public Key"),
+				  _("Could not load public key"), NULL);
+		return;
+	}
+
+	pk = silc_pkcs_public_key_payload_encode(public_key);
+	chpks = silc_buffer_alloc_size(2);
+	SILC_PUT16_MSB(1, chpks->head);
+	chpks = silc_argument_payload_encode_one(chpks, pk->data,
+						 pk->len, 0x00);
+	silc_buffer_free(pk);
+
+	m = sgc->channel->mode;
+	m |= SILC_CHANNEL_MODE_CHANNEL_AUTH;
+
+	/* Send CMODE */
+	SILC_PUT32_MSB(m, mode);
+	chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL);
+	silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
+				 ++conn->cmd_ident, 3,
+				 1, chidp->data, chidp->len,
+				 2, mode, sizeof(mode),
+				 9, chpks->data, chpks->len);
+	silc_buffer_free(chpks);
+	silc_buffer_free(chidp);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcgaim_chat_chpk_cancel(void *user_data, const char *name)
+{
+	SilcGaimChauth sgc = (SilcGaimChauth)user_data;
+	silcgaim_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcgaim_chat_chpk_cb(SilcGaimChauth sgc, GaimRequestFields *fields)
+{
+	SilcGaim sg = sgc->sg;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	GaimRequestField *f;
+	const GList *list;
+	SilcPublicKey public_key;
+	SilcBuffer chpks, pk, chidp;
+	SilcUInt16 c = 0, ct;
+	unsigned char mode[4];
+	SilcUInt32 m;
+
+	f = gaim_request_fields_get_field(fields, "list");
+	if (!gaim_request_field_list_get_selected(f)) {
+		/* Add new public key */
+		gaim_request_file(NULL, _("Open Public Key..."), _(""),
+				  G_CALLBACK(silcgaim_chat_chpk_add),
+				  G_CALLBACK(silcgaim_chat_chpk_cancel), sgc);
+		return;
+	}
+
+	list = gaim_request_field_list_get_items(f);
+	chpks = silc_buffer_alloc_size(2);
+
+	for (ct = 0; list; list = list->next, ct++) {
+		public_key = gaim_request_field_list_get_data(f, list->data);
+		if (gaim_request_field_list_is_selected(f, list->data)) {
+			/* Delete this public key */
+			pk = silc_pkcs_public_key_payload_encode(public_key);
+			chpks = silc_argument_payload_encode_one(chpks, pk->data,
+								 pk->len, 0x01);
+			silc_buffer_free(pk);
+			c++;
+		}
+		silc_pkcs_public_key_free(public_key);
+	}
+	if (!c) {
+		silc_buffer_free(chpks);
+		return;
+	}
+	SILC_PUT16_MSB(c, chpks->head);
+
+	m = sgc->channel->mode;
+	if (ct == c)
+		m &= ~SILC_CHANNEL_MODE_CHANNEL_AUTH;
+
+	/* Send CMODE */
+	SILC_PUT32_MSB(m, mode);
+	chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL);
+	silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
+				 ++conn->cmd_ident, 3,
+				 1, chidp->data, chidp->len,
+				 2, mode, sizeof(mode),
+				 9, chpks->data, chpks->len);
+	silc_buffer_free(chpks);
+	silc_buffer_free(chidp);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcgaim_chat_chauth_ok(SilcGaimChauth sgc, GaimRequestFields *fields)
+{
+	SilcGaim sg = sgc->sg;
+	GaimRequestField *f;
+	const char *curpass, *val;
+	int set;
+
+	f = gaim_request_fields_get_field(fields, "passphrase");
+	val = gaim_request_field_string_get_value(f);
+	curpass = gaim_blist_node_get_string((GaimBlistNode *)sgc->c, "passphrase");
+
+	if (!val && curpass)
+		set = 0;
+	else if (val && !curpass)
+		set = 1;
+	else if (val && curpass && strcmp(val, curpass))
+		set = 1;
+	else
+		set = -1;
+
+	if (set == 1) {
+		silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+					 sgc->channel->channel_name, "+a", val, NULL);
+		gaim_blist_node_set_string((GaimBlistNode *)sgc->c, "passphrase", val);
+		gaim_blist_save();
+	} else if (set == 0) {
+		silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+					 sgc->channel->channel_name, "-a", NULL);
+		gaim_blist_node_remove_setting((GaimBlistNode *)sgc->c, "passphrase");
+		gaim_blist_save();
+	}
+
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+void silcgaim_chat_chauth_show(SilcGaim sg, SilcChannelEntry channel,
+			       SilcBuffer channel_pubkeys)
+{
+	SilcUInt16 argc;
+	SilcArgumentPayload chpks;
+	unsigned char *pk;
+	SilcUInt32 pk_len, type;
+	char *fingerprint, *babbleprint;
+	SilcPublicKey pubkey;
+	SilcPublicKeyIdentifier ident;
+	char tmp2[1024], t[512];
+	GaimRequestFields *fields;
+	GaimRequestFieldGroup *g;
+	GaimRequestField *f;
+	SilcGaimChauth sgc;
+	const char *curpass = NULL;
+
+	sgc = silc_calloc(1, sizeof(*sgc));
+	if (!sgc)
+		return;
+	sgc->sg = sg;
+	sgc->channel = channel;
+
+	fields = gaim_request_fields_new();
+
+	if (sgc->c)
+	  curpass = gaim_blist_node_get_string((GaimBlistNode *)sgc->c, "passphrase");
+
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_string_new("passphrase", _("Channel Passphrase"),
+					  curpass, FALSE);
+	gaim_request_field_string_set_masked(f, TRUE);
+	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_label_new("l1", _("Channel Public Keys List"));
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_fields_add_group(fields, g);
+
+	g_snprintf(t, sizeof(t),
+		   _("Channel authentication is used to secure the channel from "
+		     "unauthorized access. The authentication may be based on "
+		     "passphrase and digital signatures. If passphrase is set, it "
+		     "is required to be able to join. If channel public keys are set "
+		     "then only users whose public keys are listed are able to join."));
+
+	if (!channel_pubkeys) {
+		f = gaim_request_field_list_new("list", NULL);
+		gaim_request_field_group_add_field(g, f);
+		gaim_request_fields(NULL, _("Channel Authentication"),
+				    _("Channel Authentication"), t, fields,
+				    "Add / Remove", G_CALLBACK(silcgaim_chat_chpk_cb),
+				    "OK", G_CALLBACK(silcgaim_chat_chauth_ok), sgc);
+		return;
+	}
+	sgc->pubkeys = silc_buffer_copy(channel_pubkeys);
+
+	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_fields_add_group(fields, g);
+
+	SILC_GET16_MSB(argc, channel_pubkeys->data);
+	chpks = silc_argument_payload_parse(channel_pubkeys->data + 2,
+					    channel_pubkeys->len - 2, argc);
+	if (!chpks)
+		return;
+
+	pk = silc_argument_get_first_arg(chpks, &type, &pk_len);
+	while (pk) {
+		fingerprint = silc_hash_fingerprint(NULL, pk + 4, pk_len - 4);
+		babbleprint = silc_hash_babbleprint(NULL, pk + 4, pk_len - 4);
+		silc_pkcs_public_key_payload_decode(pk, pk_len, &pubkey);
+		ident = silc_pkcs_decode_identifier(pubkey->identifier);
+
+		g_snprintf(tmp2, sizeof(tmp2), "%s\n  %s\n  %s",
+			   ident->realname ? ident->realname : ident->username ?
+			   ident->username : "", fingerprint, babbleprint);
+		gaim_request_field_list_add(f, tmp2, pubkey);
+
+		silc_free(fingerprint);
+		silc_free(babbleprint);
+		silc_pkcs_free_identifier(ident);
+		pk = silc_argument_get_next_arg(chpks, &type, &pk_len);
+	}
+
+	gaim_request_field_list_set_multi_select(f, FALSE);
+	gaim_request_fields(NULL, _("Channel Authentication"),
+			    _("Channel Authentication"), t, fields,
+			    "Add / Remove", G_CALLBACK(silcgaim_chat_chpk_cb),
+			    "OK", G_CALLBACK(silcgaim_chat_chauth_ok), sgc);
+
+	silc_argument_payload_free(chpks);
+}
+
+static void
+silcgaim_chat_chauth(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(components, "channel"),
+				 "+C", NULL);
+}
+
+
+/************************** Channel Private Groups **************************/
+
+/* Private groups are "virtual" channels.  They are groups inside a channel.
+   This is implemented by using channel private keys.  By knowing a channel
+   private key user becomes part of that group and is able to talk on that
+   group.  Other users, on the same channel, won't be able to see the
+   messages of that group.  It is possible to have multiple groups inside
+   a channel - and thus having multiple private keys on the channel. */
+
+typedef struct {
+	SilcGaim sg;
+	GaimChat *c;
+	const char *channel;
+} *SilcGaimCharPrv;
+
+static void
+silcgaim_chat_prv_add(SilcGaimCharPrv p, GaimRequestFields *fields)
+{
+	SilcGaim sg = p->sg;
+	char tmp[512];
+	GaimRequestField *f;
+	const char *name, *passphrase, *alias;
+	GHashTable *comp;
+	GaimGroup *g;
+	GaimChat *cn;
+
+	f = gaim_request_fields_get_field(fields, "name");
+	name = gaim_request_field_string_get_value(f);
+	if (!name) {
+		silc_free(p);
+		return;
+	}
+	f = gaim_request_fields_get_field(fields, "passphrase");
+	passphrase = gaim_request_field_string_get_value(f);
+	f = gaim_request_fields_get_field(fields, "alias");
+	alias = gaim_request_field_string_get_value(f);
+
+	/* Add private group to buddy list */
+	g_snprintf(tmp, sizeof(tmp), "%s [Private Group]", name);
+	comp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	g_hash_table_replace(comp, g_strdup("channel"), g_strdup(tmp));
+	g_hash_table_replace(comp, g_strdup("passphrase"), g_strdup(passphrase));
+
+	cn = gaim_chat_new(sg->account, alias, comp);
+	g = (GaimGroup *)p->c->node.parent;
+	gaim_blist_add_chat(cn, g, (GaimBlistNode *)p->c);
+
+	/* Associate to a real channel */
+	gaim_blist_node_set_string((GaimBlistNode *)cn, "parentch", p->channel);
+	gaim_blist_save();
+
+	/* Join the group */
+	silcgaim_chat_join(sg->gc, comp);
+
+	silc_free(p);
+}
+
+static void
+silcgaim_chat_prv_cancel(SilcGaimCharPrv p, GaimRequestFields *fields)
+{
+	silc_free(p);
+}
+
+static void
+silcgaim_chat_prv(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcGaimCharPrv p;
+	GaimRequestFields *fields;
+	GaimRequestFieldGroup *g;
+	GaimRequestField *f;
+	char tmp[512];
+
+	p = silc_calloc(1, sizeof(*p));
+	if (!p)
+		return;
+	p->sg = sg;
+
+	p->channel = g_hash_table_lookup(components, "channel");
+	p->c = gaim_blist_find_chat(sg->account, p->channel);
+
+	fields = gaim_request_fields_new();
+
+	g = gaim_request_field_group_new(NULL);
+	f = gaim_request_field_string_new("name", _("Group Name"),
+					  NULL, FALSE);
+	gaim_request_field_group_add_field(g, f);
+
+	f = gaim_request_field_string_new("passphrase", _("Passphrase"),
+					  NULL, FALSE);
+	gaim_request_field_string_set_masked(f, TRUE);
+	gaim_request_field_group_add_field(g, f);
+
+	f = gaim_request_field_string_new("alias", _("Alias"),
+					  NULL, FALSE);
+	gaim_request_field_group_add_field(g, f);
+	gaim_request_fields_add_group(fields, g);
+
+	g_snprintf(tmp, sizeof(tmp),
+		   _("Please enter the %s channel private group name and passphrase."),
+		   p->channel);
+	gaim_request_fields(NULL, _("Add Channel Private Group"), NULL, tmp, fields,
+			    "Add", G_CALLBACK(silcgaim_chat_prv_add),
+			    "Cancel", G_CALLBACK(silcgaim_chat_prv_cancel), p);
+}
+
+
+/****************************** Channel Modes ********************************/
+
+static void
+silcgaim_chat_permanent_reset(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(components, "channel"),
+				 "-f", NULL);
+}
+
+static void
+silcgaim_chat_permanent(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	const char *channel;
+
+	if (!sg->conn)
+		return;
+
+	/* XXX we should have ability to define which founder
+	   key to use.  Now we use the user's own public key
+	   (default key). */
+
+	/* Call CMODE */
+	channel = g_hash_table_lookup(components, "channel");
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", channel,
+				 "+f", NULL);
+}
+
+typedef struct {
+	SilcGaim sg;
+	const char *channel;
+} *SilcGaimChatInput;
+
+static void
+silcgaim_chat_ulimit_cb(SilcGaimChatInput s, const char *limit)
+{
+	SilcChannelEntry channel;
+	int ulimit = 0;
+
+	channel = silc_client_get_channel(s->sg->client, s->sg->conn,
+					  (char *)s->channel);
+	if (!channel)
+		return;
+	if (limit)
+		ulimit = atoi(limit);
+
+	if (!limit || !(*limit) || *limit == '0') {
+		if (limit && ulimit == channel->user_limit) {
+			silc_free(s);
+			return;
+		}
+		silc_client_command_call(s->sg->client, s->sg->conn, NULL, "CMODE",
+					 s->channel, "-l", NULL);
+
+		silc_free(s);
+		return;
+	}
+
+	if (ulimit == channel->user_limit) {
+		silc_free(s);
+		return;
+	}
+
+	/* Call CMODE */
+	silc_client_command_call(s->sg->client, s->sg->conn, NULL, "CMODE",
+				 s->channel, "+l", limit, NULL);
+
+	silc_free(s);
+}
+
+static void
+silcgaim_chat_ulimit(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcGaimChatInput s;
+	SilcChannelEntry channel;
+	const char *ch;
+	char tmp[32];
+
+	if (!sg->conn)
+		return;
+
+	ch = g_strdup(g_hash_table_lookup(components, "channel"));
+	channel = silc_client_get_channel(sg->client, sg->conn, (char *)ch);
+	if (!channel)
+		return;
+
+	s = silc_calloc(1, sizeof(*s));
+	if (!s)
+		return;
+	s->channel = ch;
+	s->sg = sg;
+	g_snprintf(tmp, sizeof(tmp), "%d", (int)channel->user_limit);
+	gaim_request_input(NULL, _("User Limit"), NULL,
+			   _("Set user limit on channel. Set to zero to reset user limit."),
+			   tmp, FALSE, FALSE, NULL,
+			   _("OK"), G_CALLBACK(silcgaim_chat_ulimit_cb),
+			   _("Cancel"), G_CALLBACK(silcgaim_chat_ulimit_cb), s);
+}
+
+static void
+silcgaim_chat_resettopic(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(components, "channel"),
+				 "-t", NULL);
+}
+
+static void
+silcgaim_chat_settopic(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(components, "channel"),
+				 "+t", NULL);
+}
+
+static void
+silcgaim_chat_resetprivate(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(components, "channel"),
+				 "-p", NULL);
+}
+
+static void
+silcgaim_chat_setprivate(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(components, "channel"),
+				 "+p", NULL);
+}
+
+static void
+silcgaim_chat_resetsecret(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(components, "channel"),
+				 "-s", NULL);
+}
+
+static void
+silcgaim_chat_setsecret(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(components, "channel"),
+				 "+s", NULL);
+}
+
+GList *silcgaim_chat_menu(GaimConnection *gc, GHashTable *components)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClientConnection conn = sg->conn;
+	GList *m = NULL;
+	struct proto_chat_menu *pcm;
+	const char *chname = NULL;
+	SilcChannelEntry channel = NULL;
+	SilcChannelUser chu = NULL;
+	SilcUInt32 mode = 0;
+
+	if (components)
+		chname = g_hash_table_lookup(components, "channel");
+	if (chname)
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)chname);
+	if (channel) {
+		chu = silc_client_on_channel(channel, conn->local_entry);
+		if (chu)
+			mode = chu->mode;
+	}
+
+	if (strstr(chname, "[Private Group]"))
+		return NULL;
+
+	pcm = g_new0(struct proto_chat_menu, 1);
+	pcm->label = _("Get Info");
+	pcm->callback = silcgaim_chat_getinfo;
+	pcm->gc = gc;
+	m = g_list_append(m, pcm);
+
+#if 0   /* XXX For now these are not implemented.  We need better
+	   listview dialog from Gaim for these. */
+	if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+		pcm = g_new0(struct proto_chat_menu, 1);
+		pcm->label = _("Invite List");
+		pcm->callback = silcgaim_chat_invitelist;
+		pcm->gc = gc;
+		m = g_list_append(m, pcm);
+
+		pcm = g_new0(struct proto_chat_menu, 1);
+		pcm->label = _("Ban List");
+		pcm->callback = silcgaim_chat_banlist;
+		pcm->gc = gc;
+		m = g_list_append(m, pcm);
+	}
+#endif
+
+	if (chu) {
+		pcm = g_new0(struct proto_chat_menu, 1);
+		pcm->label = _("Add Private Group");
+		pcm->callback = silcgaim_chat_prv;
+		pcm->gc = gc;
+		m = g_list_append(m, pcm);
+	}
+
+	if (mode & SILC_CHANNEL_UMODE_CHANFO) {
+		pcm = g_new0(struct proto_chat_menu, 1);
+		pcm->label = _("Channel Authentication");
+		pcm->callback = silcgaim_chat_chauth;
+		pcm->gc = gc;
+		m = g_list_append(m, pcm);
+
+		if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+			pcm = g_new0(struct proto_chat_menu, 1);
+			pcm->label = _("Reset Permanent");
+			pcm->callback = silcgaim_chat_permanent_reset;
+			pcm->gc = gc;
+			m = g_list_append(m, pcm);
+		} else {
+			pcm = g_new0(struct proto_chat_menu, 1);
+			pcm->label = _("Set Permanent");
+			pcm->callback = silcgaim_chat_permanent;
+			pcm->gc = gc;
+			m = g_list_append(m, pcm);
+		}
+	}
+
+	if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+		pcm = g_new0(struct proto_chat_menu, 1);
+		pcm->label = _("Set User Limit");
+		pcm->callback = silcgaim_chat_ulimit;
+		pcm->gc = gc;
+		m = g_list_append(m, pcm);
+
+		if (channel->mode & SILC_CHANNEL_MODE_TOPIC) {
+			pcm = g_new0(struct proto_chat_menu, 1);
+			pcm->label = _("Reset Topic Restriction");
+			pcm->callback = silcgaim_chat_resettopic;
+			pcm->gc = gc;
+			m = g_list_append(m, pcm);
+		} else {
+			pcm = g_new0(struct proto_chat_menu, 1);
+			pcm->label = _("Set Topic Restriction");
+			pcm->callback = silcgaim_chat_settopic;
+			pcm->gc = gc;
+			m = g_list_append(m, pcm);
+		}
+
+		if (channel->mode & SILC_CHANNEL_MODE_PRIVATE) {
+			pcm = g_new0(struct proto_chat_menu, 1);
+			pcm->label = _("Reset Private Channel");
+			pcm->callback = silcgaim_chat_resetprivate;
+			pcm->gc = gc;
+			m = g_list_append(m, pcm);
+		} else {
+			pcm = g_new0(struct proto_chat_menu, 1);
+			pcm->label = _("Set Private Channel");
+			pcm->callback = silcgaim_chat_setprivate;
+			pcm->gc = gc;
+			m = g_list_append(m, pcm);
+		}
+
+		if (channel->mode & SILC_CHANNEL_MODE_SECRET) {
+			pcm = g_new0(struct proto_chat_menu, 1);
+			pcm->label = _("Reset Secret Channel");
+			pcm->callback = silcgaim_chat_resetsecret;
+			pcm->gc = gc;
+			m = g_list_append(m, pcm);
+		} else {
+			pcm = g_new0(struct proto_chat_menu, 1);
+			pcm->label = _("Set Secret Channel");
+			pcm->callback = silcgaim_chat_setsecret;
+			pcm->gc = gc;
+			m = g_list_append(m, pcm);
+		}
+	}
+
+	return m;
+}
+
+
+/******************************* Joining Etc. ********************************/
+
+void silcgaim_chat_join_done(SilcClient client,
+			     SilcClientConnection conn,
+			     SilcClientEntry *clients,
+			     SilcUInt32 clients_count,
+			     void *context)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	SilcChannelEntry channel = context;
+	GaimConversation *convo;
+	SilcUInt32 retry = SILC_PTR_TO_32(channel->context);
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	GList *users = NULL;
+	char tmp[256];
+
+	if (!clients && retry < 1) {
+		/* Resolving users failed, try again. */
+		channel->context = SILC_32_TO_PTR(retry + 1);
+		silc_client_get_clients_by_channel(client, conn, channel,
+						   silcgaim_chat_join_done, channel);
+		return;
+	}
+
+	/* Add channel to Gaim */
+	channel->context = SILC_32_TO_PTR(++sg->channel_ids);
+	serv_got_joined_chat(gc, sg->channel_ids, channel->channel_name);
+	convo = gaim_find_conversation_with_account(channel->channel_name,
+						    sg->account);
+	if (!convo)
+		return;
+
+	/* Add all users to channel */
+	silc_hash_table_list(channel->user_list, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (!chu->client->nickname)
+			continue;
+		chu->context = SILC_32_TO_PTR(sg->channel_ids);
+
+#if 0   /* XXX don't append mode char to nick because Gaim doesn't
+	   give a way to change it afterwards when mode changes. */
+		tmp2 = silc_client_chumode_char(chu->mode);
+		if (tmp2)
+			g_snprintf(tmp, sizeof(tmp), _("%s%s"), tmp2,
+				   chu->client->nickname);
+		else
+			g_snprintf(tmp, sizeof(tmp), _("%s"),
+				   chu->client->nickname);
+		silc_free(tmp2);
+
+		users = g_list_append(users, g_strdup(tmp));
+#else
+		users = g_list_append(users, g_strdup(chu->client->nickname));
+#endif
+
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+			if (chu->client == conn->local_entry)
+				g_snprintf(tmp, sizeof(tmp),
+					   _("You are channel founder on <I>%s</I>"),
+					   channel->channel_name);
+			else
+				g_snprintf(tmp, sizeof(tmp),
+					   _("Channel founder on <I>%s</I> is <I>%s</I>"),
+					   channel->channel_name, chu->client->nickname);
+
+			gaim_conversation_write(convo, NULL, tmp,
+						GAIM_MESSAGE_SYSTEM, time(NULL));
+
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+
+	gaim_conv_chat_add_users(GAIM_CONV_CHAT(convo), users);
+	g_list_free(users);
+
+	/* Set topic */
+	if (channel->topic)
+		gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, channel->topic);
+}
+
+void silcgaim_chat_join(GaimConnection *gc, GHashTable *data)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	const char *channel, *passphrase, *parentch;
+
+	if (!conn)
+		return;
+
+	channel = g_hash_table_lookup(data, "channel");
+	passphrase = g_hash_table_lookup(data, "passphrase");
+
+	/* Check if we are joining a private group.  Handle it
+	   purely locally as it's not a real channel */
+	if (strstr(channel, "[Private Group]")) {
+		SilcChannelEntry channel_entry;
+		SilcChannelPrivateKey key;
+		GaimChat *c;
+		SilcGaimPrvgrp grp;
+
+		c = gaim_blist_find_chat(sg->account, channel);
+		parentch = gaim_blist_node_get_string((GaimBlistNode *)c, "parentch");
+		if (!parentch)
+			return;
+
+		channel_entry = silc_client_get_channel(sg->client, sg->conn,
+							(char *)parentch);
+		if (!channel_entry ||
+		    !silc_client_on_channel(channel_entry, sg->conn->local_entry)) {
+			char tmp[512];
+			g_snprintf(tmp, sizeof(tmp),
+				   _("You have to join the %s channel before you are "
+				     "able to join the private group"), parentch);
+			gaim_notify_error(gc, _("Join Private Group"),
+					  _("Cannot join private group"), tmp);
+			return;
+		}
+
+		/* Add channel private key */
+		if (!silc_client_add_channel_private_key(client, conn,
+							 channel_entry, channel,
+							 NULL, NULL,
+							 (unsigned char *)passphrase,
+							 strlen(passphrase), &key))
+			return;
+
+		/* Join the group */
+		grp = silc_calloc(1, sizeof(*grp));
+		if (!grp)
+			return;
+		grp->id = ++sg->channel_ids + SILCGAIM_PRVGRP;
+		grp->chid = SILC_PTR_TO_32(channel_entry->context);
+		grp->parentch = parentch;
+		grp->channel = channel;
+		grp->key = key;
+		sg->grps = g_list_append(sg->grps, grp);
+		serv_got_joined_chat(gc, grp->id, channel);
+		return;
+	}
+
+	/* XXX We should have other properties here as well:
+	   1. whether to try to authenticate to the channel
+	     1a. with default key,
+	     1b. with specific key.
+	   2. whether to try to authenticate to become founder.
+	     2a. with default key,
+	     2b. with specific key.
+
+	   Since now such variety is not possible in the join dialog
+	   we always use -founder and -auth options, which try to
+	   do both 1 and 2 with default keys. */
+
+	/* Call JOIN */
+	if (passphrase)
+		silc_client_command_call(client, conn, NULL, "JOIN",
+					 channel, passphrase, "-auth", "-founder", NULL);
+	else
+		silc_client_command_call(client, conn, NULL, "JOIN",
+					 channel, "-auth", "-founder", NULL);
+}
+
+void silcgaim_chat_invite(GaimConnection *gc, int id, const char *msg,
+			  const char *name)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+
+	if (!conn)
+		return;
+
+	/* See if we are inviting on a private group.  Invite
+	   to the actual channel */
+	if (id > SILCGAIM_PRVGRP) {
+		GList *l;
+		SilcGaimPrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcGaimPrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		id = prv->chid;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call INVITE */
+	silc_client_command_call(client, conn, NULL, "INVITE",
+				 chu->channel->channel_name,
+				 name);
+}
+
+void silcgaim_chat_leave(GaimConnection *gc, int id)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+	GList *l;
+	SilcGaimPrvgrp prv;
+
+	if (!conn)
+		return;
+
+	/* See if we are leaving a private group */
+	if (id > SILCGAIM_PRVGRP) {
+		SilcChannelEntry channel;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcGaimPrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)prv->parentch);
+		if (!channel)
+			return;
+		silc_client_del_channel_private_key(client, conn,
+						    channel, prv->key);
+		silc_free(prv);
+		sg->grps = g_list_remove(sg->grps, prv);
+		serv_got_chat_left(gc, id);
+		return;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call LEAVE */
+	silc_client_command_call(client, conn, NULL, "LEAVE",
+				 chu->channel->channel_name, NULL);
+
+	serv_got_chat_left(gc, id);
+
+	/* Leave from private groups on this channel as well */
+	for (l = sg->grps; l; l = l->next)
+		if (((SilcGaimPrvgrp)l->data)->chid == id) {
+			prv = l->data;
+			silc_client_del_channel_private_key(client, conn,
+							    chu->channel,
+							    prv->key);
+			serv_got_chat_left(gc, prv->id);
+			silc_free(prv);
+			sg->grps = g_list_remove(sg->grps, prv);
+			if (!sg->grps)
+				break;
+		}
+}
+
+int silcgaim_chat_send(GaimConnection *gc, int id, const char *msg)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	SilcChannelEntry channel = NULL;
+	SilcChannelPrivateKey key = NULL;
+	SilcUInt32 flags;
+	int ret;
+	gboolean found = FALSE;
+	gboolean sign = gaim_prefs_get_bool("/plugins/prpl/silc/sign_chat");
+
+	if (!msg || !conn)
+		return 0;
+
+	/* See if command */
+	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"));
+		return 0;
+	}
+
+	flags = SILC_MESSAGE_FLAG_UTF8;
+	if (sign)
+		flags |= SILC_MESSAGE_FLAG_SIGNED;
+
+	/* Get the channel private key if we are sending on
+	   private group */
+	if (id > SILCGAIM_PRVGRP) {
+		GList *l;
+		SilcGaimPrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcGaimPrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return 0;
+		prv = l->data;
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)prv->parentch);
+		if (!channel)
+			return 0;
+		key = prv->key;
+	}
+
+	if (!channel) {
+		/* Find channel by id */
+		silc_hash_table_list(conn->local_entry->channels, &htl);
+		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+			if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+				found = TRUE;
+				break;
+			}
+		}
+		silc_hash_table_list_reset(&htl);
+		if (!found)
+			return 0;
+		channel = chu->channel;
+	}
+
+	/* Send channel message */
+	ret = silc_client_send_channel_message(client, conn, channel, key,
+					       flags, (unsigned char *)msg,
+					       strlen(msg), TRUE);
+	if (ret)
+		serv_got_chat_in(gc, id, gaim_connection_get_display_name(gc), 0, msg,
+				 time(NULL));
+
+	return ret;
+}
+
+void silcgaim_chat_set_topic(GaimConnection *gc, int id, const char *topic)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+
+	if (!topic || !conn)
+		return;
+
+	/* See if setting topic on private group.  Set it
+	   on the actual channel */
+	if (id > SILCGAIM_PRVGRP) {
+		GList *l;
+		SilcGaimPrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcGaimPrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		id = prv->chid;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call TOPIC */
+	silc_client_command_call(client, conn, NULL, "TOPIC",
+				 chu->channel->channel_name, topic, NULL);
+}
+
+GaimRoomlist *silcgaim_roomlist_get_list(GaimConnection *gc)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	GList *fields = NULL;
+	GaimRoomlistField *f;
+
+	if (!conn)
+		return NULL;
+
+	if (sg->roomlist)
+		gaim_roomlist_unref(sg->roomlist);
+
+	sg->roomlist_canceled = FALSE;
+
+	sg->roomlist = gaim_roomlist_new(gaim_connection_get_account(gc));
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "channel", TRUE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_INT,
+				    _("Users"), "users", FALSE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING,
+				    _("Topic"), "topic", FALSE);
+	fields = g_list_append(fields, f);
+	gaim_roomlist_set_fields(sg->roomlist, fields);
+
+	/* Call LIST */
+	silc_client_command_call(client, conn, "LIST");
+
+	gaim_roomlist_set_in_progress(sg->roomlist, TRUE);
+
+	return sg->roomlist;
+}
+
+void silcgaim_roomlist_cancel(GaimRoomlist *list)
+{
+	GaimConnection *gc = gaim_account_get_connection(list->account);
+	SilcGaim sg;
+
+	if (!gc)
+		return;
+	sg = gc->proto_data;
+
+	gaim_roomlist_set_in_progress(list, FALSE);
+	if (sg->roomlist == list) {
+		gaim_roomlist_unref(sg->roomlist);
+		sg->roomlist = NULL;
+		sg->roomlist_canceled = TRUE;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/ft.c	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,407 @@
+/*
+
+  silcgaim_ft.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"
+
+/****************************** File Transfer ********************************/
+
+/* This implements the secure file transfer protocol (SFTP) using the SILC
+   SFTP library implementation.  The API we use from the SILC Toolkit is the
+   SILC Client file transfer API, as it provides a simple file transfer we
+   need in this case.  We could use the SILC SFTP API directly, but it would
+   be an overkill since we'd effectively re-implement the file transfer what
+   the SILC Client's file transfer API already provides.
+
+   From Gaim we do NOT use the FT API to do the transfer as it is very limiting.
+   In fact it does not suite to file transfers like SFTP at all.  For example,
+   it assumes that read operations are synchronous what they are not in SFTP.
+   It also assumes that the file transfer socket is to be handled by the Gaim
+   eventloop, and this naturally is something we don't want to do in case of
+   SILC Toolkit.  The FT API suites well to purely stream based file transfers
+   like HTTP GET and similar.
+
+   For this reason, we directly access the Gaim GKT FT API and hack the FT
+   API to merely provide the user interface experience and all the magic
+   is done in the SILC Toolkit.  Ie. we update the statistics information in
+   the FT API for user interface, and that's it.  A bit dirty but until the
+   FT API gets better this is the way to go.  Good thing that FT API allowed
+   us to do this. */
+
+typedef struct {
+	SilcGaim sg;
+	SilcClientEntry client_entry;
+	SilcUInt32 session_id;
+	char *hostname;
+	SilcUInt16 port;
+	GaimXfer *xfer;
+
+	SilcClientFileName completion;
+	void *completion_context;
+} *SilcGaimXfer;
+
+static void
+silcgaim_ftp_monitor(SilcClient client,
+		     SilcClientConnection conn,
+		     SilcClientMonitorStatus status,
+		     SilcClientFileError error,
+		     SilcUInt64 offset,
+		     SilcUInt64 filesize,
+		     SilcClientEntry client_entry,
+		     SilcUInt32 session_id,
+		     const char *filepath,
+		     void *context)
+{
+	SilcGaimXfer xfer = context;
+	GaimConnection *gc = xfer->sg->gc;
+	char tmp[256];
+
+	if (status == SILC_CLIENT_FILE_MONITOR_CLOSED) {
+		gaim_xfer_unref(xfer->xfer);
+		silc_free(xfer);
+		return;
+	}
+
+	if (status == SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT)
+		return;
+
+	if (status == SILC_CLIENT_FILE_MONITOR_ERROR) {
+		if (error == SILC_CLIENT_FILE_NO_SUCH_FILE) {
+			g_snprintf(tmp, sizeof(tmp), "No such file %s",
+				   filepath ? filepath : "[N/A]");
+			gaim_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"), tmp);
+		} else if (error == SILC_CLIENT_FILE_PERMISSION_DENIED) {
+			gaim_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"),
+					  _("Permission denied"));
+		} else if (error == SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED) {
+			gaim_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"),
+					  _("Key agreement failed"));
+		} else if (error == SILC_CLIENT_FILE_UNKNOWN_SESSION) {
+			gaim_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"),
+					  _("File transfer sessions does not exist"));
+		} else {
+			gaim_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"), NULL);
+		}
+		silc_client_file_close(client, conn, session_id);
+		gaim_xfer_unref(xfer->xfer);
+		silc_free(xfer);
+		return;
+	}
+
+	/* Update file transfer UI */
+	if (!offset && filesize)
+		gaim_xfer_set_size(xfer->xfer, filesize);
+	if (offset && filesize) {
+		xfer->xfer->bytes_sent = offset;
+		xfer->xfer->bytes_remaining = filesize - offset;
+	}
+	gaim_xfer_update_progress(xfer->xfer);
+
+	if (status == SILC_CLIENT_FILE_MONITOR_SEND ||
+	    status == SILC_CLIENT_FILE_MONITOR_RECEIVE) {
+		if (offset == filesize) {
+			/* Download finished */
+			gaim_xfer_set_completed(xfer->xfer, TRUE);
+			silc_client_file_close(client, conn, session_id);
+		}
+	}
+}
+
+static void
+silcgaim_ftp_cancel(GaimXfer *x)
+{
+	SilcGaimXfer xfer = x->data;
+	xfer->xfer->status = GAIM_XFER_STATUS_CANCEL_LOCAL;
+	gaim_xfer_update_progress(xfer->xfer);
+	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+}
+
+static void
+silcgaim_ftp_ask_name_cancel(GaimXfer *x)
+{
+	SilcGaimXfer xfer = x->data;
+
+	/* Cancel the transmission */
+	xfer->completion(NULL, xfer->completion_context);
+	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+}
+
+static void
+silcgaim_ftp_ask_name_ok(GaimXfer *x)
+{
+	SilcGaimXfer xfer = x->data;
+	const char *name;
+
+	name = gaim_xfer_get_local_filename(x);
+	unlink(name);
+	xfer->completion(name, xfer->completion_context);
+}
+
+static void
+silcgaim_ftp_ask_name(SilcClient client,
+		      SilcClientConnection conn,
+		      SilcUInt32 session_id,
+		      const char *remote_filename,
+		      SilcClientFileName completion,
+		      void *completion_context,
+		      void *context)
+{
+	SilcGaimXfer xfer = context;
+
+	xfer->completion = completion;
+	xfer->completion_context = completion_context;
+
+	gaim_xfer_set_init_fnc(xfer->xfer, silcgaim_ftp_ask_name_ok);
+	gaim_xfer_set_request_denied_fnc(xfer->xfer, silcgaim_ftp_ask_name_cancel);
+
+	/* Request to save the file */
+	gaim_xfer_set_filename(xfer->xfer, remote_filename);
+	gaim_xfer_request(xfer->xfer);
+}
+
+static void
+silcgaim_ftp_request_result(GaimXfer *x)
+{
+	SilcGaimXfer xfer = x->data;
+	SilcClientFileError status;
+	GaimConnection *gc = xfer->sg->gc;
+
+	if (gaim_xfer_get_status(x) != GAIM_XFER_STATUS_ACCEPTED)
+		return;
+
+	/* Start the file transfer */
+	gaim_xfer_add(xfer->xfer);
+	status = silc_client_file_receive(xfer->sg->client, xfer->sg->conn,
+					  silcgaim_ftp_monitor, xfer,
+					  NULL, xfer->session_id,
+					  silcgaim_ftp_ask_name, xfer);
+	switch (status) {
+	case SILC_CLIENT_FILE_OK:
+		return;
+		break;
+
+	case SILC_CLIENT_FILE_UNKNOWN_SESSION:
+		gaim_notify_error(gc, _("Secure File Transfer"),
+				  _("No file transfer session active"), NULL);
+		break;
+
+	case SILC_CLIENT_FILE_ALREADY_STARTED:
+		gaim_notify_error(gc, _("Secure File Transfer"),
+				  _("File transfer already started"), NULL);
+		break;
+
+	case SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED:
+		gaim_notify_error(gc, _("Secure File Transfer"),
+				  _("Could not perform key agreement for file transfer"),
+				  NULL);
+		break;
+
+	default:
+		gaim_notify_error(gc, _("Secure File Transfer"),
+				  _("Could not start the file transfer"), NULL);
+		break;
+	}
+
+	/* Error */
+	gaim_xfer_unref(xfer->xfer);
+	g_free(xfer->hostname);
+	silc_free(xfer);
+}
+
+static void
+silcgaim_ftp_request_denied(GaimXfer *x)
+{
+
+}
+
+void silcgaim_ftp_request(SilcClient client, SilcClientConnection conn,
+			  SilcClientEntry client_entry, SilcUInt32 session_id,
+			  const char *hostname, SilcUInt16 port)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	SilcGaimXfer xfer;
+
+	xfer = silc_calloc(1, sizeof(*xfer));
+	if (!xfer) {
+		silc_client_file_close(sg->client, sg->conn, xfer->session_id);
+		return;
+	}
+
+	xfer->sg = sg;
+	xfer->client_entry = client_entry;
+	xfer->session_id = session_id;
+	xfer->hostname = g_strdup(hostname);
+	xfer->port = port;
+	xfer->xfer = gaim_xfer_new(xfer->sg->account, GAIM_XFER_RECEIVE,
+				   xfer->client_entry->nickname);
+	if (!xfer->xfer) {
+		silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+		g_free(xfer->hostname);
+		silc_free(xfer);
+		return;
+	}
+	gaim_xfer_set_init_fnc(xfer->xfer, silcgaim_ftp_request_result);
+	gaim_xfer_set_request_denied_fnc(xfer->xfer, silcgaim_ftp_request_denied);
+	gaim_xfer_set_cancel_recv_fnc(xfer->xfer, silcgaim_ftp_cancel);
+	xfer->xfer->remote_ip = g_strdup(hostname);
+	xfer->xfer->remote_port = port;
+	xfer->xfer->data = xfer;
+
+	/* File transfer request */
+	gaim_xfer_request(xfer->xfer);
+}
+
+static void
+silcgaim_ftp_send_cancel(GaimXfer *x)
+{
+	SilcGaimXfer xfer = x->data;
+	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+	gaim_xfer_unref(xfer->xfer);
+	g_free(xfer->hostname);
+	silc_free(xfer);
+}
+
+static void
+silcgaim_ftp_send(GaimXfer *x)
+{
+	SilcGaimXfer xfer = x->data;
+	const char *name;
+	char *local_ip = NULL, *remote_ip = NULL;;
+	gboolean local = TRUE;
+
+	name = gaim_xfer_get_local_filename(x);
+
+	/* Do the same magic what we do with key agreement (see silcgaim_buddy.c)
+	   to see if we are behind NAT. */
+	if (silc_net_check_local_by_sock(xfer->sg->conn->sock->sock,
+					 NULL, &local_ip)) {
+		/* Check if the IP is private */
+		if (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(xfer->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 (local && !local_ip)
+		local_ip = silc_net_localip();
+
+	/* Send the file */
+	gaim_xfer_add(xfer->xfer);
+	silc_client_file_send(xfer->sg->client, xfer->sg->conn,
+			      silcgaim_ftp_monitor, xfer,
+			      local_ip, 0, !local, xfer->client_entry,
+			      name, &xfer->session_id);
+
+	silc_free(local_ip);
+	silc_free(remote_ip);
+}
+
+static void
+silcgaim_ftp_send_file_resolved(SilcClient client,
+				SilcClientConnection conn,
+				SilcClientEntry *clients,
+				SilcUInt32 clients_count,
+				void *context)
+{
+	GaimConnection *gc = client->application;
+	char tmp[256];
+
+	if (!clients) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("User %s is not present in the network"),
+			   (const char *)context);
+		gaim_notify_error(gc, _("Secure File Transfer"),
+				  _("Cannot send file"), tmp);
+		silc_free(context);
+		return;
+	}
+
+	silcgaim_ftp_send_file(client->application, (const char *)context);
+	silc_free(context);
+}
+
+void silcgaim_ftp_send_file(GaimConnection *gc, const char *name)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+	SilcGaimXfer xfer;
+	char *nickname;
+
+	if (!name)
+		return;
+
+	if (!silc_parse_userfqdn(name, &nickname, NULL))
+		return;
+
+#if 1
+	silc_debug = TRUE;
+	silc_log_set_debug_string("*client*,*ftp*");
+#endif
+
+	/* 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_ftp_send_file_resolved,
+					strdup(name));
+		silc_free(nickname);
+		return;
+	}
+
+	xfer = silc_calloc(1, sizeof(*xfer));
+	if (!xfer)
+		return;
+	xfer->sg = sg;
+	xfer->client_entry = clients[0];
+	xfer->xfer = gaim_xfer_new(xfer->sg->account, GAIM_XFER_SEND,
+				   xfer->client_entry->nickname);
+	if (!xfer->xfer) {
+		silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+		g_free(xfer->hostname);
+		silc_free(xfer);
+		return;
+	}
+	gaim_xfer_set_init_fnc(xfer->xfer, silcgaim_ftp_send);
+	gaim_xfer_set_request_denied_fnc(xfer->xfer, silcgaim_ftp_request_denied);
+	gaim_xfer_set_cancel_send_fnc(xfer->xfer, silcgaim_ftp_send_cancel);
+	xfer->xfer->data = xfer;
+
+	/* Choose file to send */
+	gaim_xfer_request(xfer->xfer);
+
+	silc_free(clients);
+	silc_free(nickname);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/ops.c	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,1543 @@
+/*
+
+  silcgaim_ops.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"
+
+/* Message sent to the application by library. `conn' associates the
+   message to a specific connection.  `conn', however, may be NULL.
+   The `type' indicates the type of the message sent by the library.
+   The application can for example filter the message according the
+   type. */
+
+static void
+silc_say(SilcClient client, SilcClientConnection conn,
+	 SilcClientMessageType type, char *msg, ...)
+{
+	/* Nothing */
+}
+
+
+/* Message for a channel. The `sender' is the sender of the message
+   The `channel' is the channel. The `message' is the message.  Note
+   that `message' maybe NULL.  The `flags' indicates message flags
+   and it is used to determine how the message can be interpreted
+   (like it may tell the message is multimedia message). */
+
+static void
+silc_channel_message(SilcClient client, SilcClientConnection conn,
+		     SilcClientEntry sender, SilcChannelEntry channel,
+		     SilcMessagePayload payload, SilcChannelPrivateKey key,
+		     SilcMessageFlags flags, const unsigned char *message,
+		     SilcUInt32 message_len)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	GaimConversation *convo = NULL;
+	char *msg;
+
+	if (!message)
+		return;
+
+	if (key) {
+		GList *l;
+		SilcGaimPrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcGaimPrvgrp)l->data)->key == key) {
+				prv = l->data;
+				convo = gaim_find_conversation_with_account(prv->channel,
+									    sg->account);
+				break;
+			}
+	}
+	if (!convo)
+		convo = gaim_find_conversation_with_account(channel->channel_name,
+							    sg->account);
+	if (!convo)
+		return;
+
+	if (flags & SILC_MESSAGE_FLAG_SIGNED &&
+	    gaim_prefs_get_bool("/plugins/prpl/silc/verify_chat")) {
+		/* XXX */
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_DATA) {
+		/* XXX */
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_ACTION) {
+		msg = g_strdup_printf("<I>%s</I> %s",
+				      sender->nickname ?
+				      sender->nickname : "<unknown>",
+				      (const char *)message);
+		if (!msg)
+			return;
+
+		/* Send to Gaim */
+		gaim_conversation_write(convo, NULL, (const char *)msg,
+					GAIM_MESSAGE_SYSTEM, time(NULL));
+		g_free(msg);
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_NOTICE) {
+		msg = g_strdup_printf("(notice) <I>%s</I> %s",
+				      sender->nickname ?
+				      sender->nickname : "<unknown>",
+				      (const char *)message);
+		if (!msg)
+			return;
+
+		/* Send to Gaim */
+		gaim_conversation_write(convo, NULL, (const char *)msg,
+					GAIM_MESSAGE_SYSTEM, time(NULL));
+		g_free(msg);
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_UTF8)
+		/* Send to Gaim */
+		serv_got_chat_in(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)),
+				 sender->nickname ?
+				 sender->nickname : "<unknown>", 0,
+				 (const char *)message, time(NULL));
+}
+
+
+/* Private message to the client. The `sender' is the sender of the
+   message. The message is `message'and maybe NULL.  The `flags'
+   indicates message flags  and it is used to determine how the message
+   can be interpreted (like it may tell the message is multimedia
+   message). */
+
+static void
+silc_private_message(SilcClient client, SilcClientConnection conn,
+		     SilcClientEntry sender, SilcMessagePayload payload,
+		     SilcMessageFlags flags, const unsigned char *message,
+		     SilcUInt32 message_len)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	GaimConversation *convo = NULL;
+	char *msg;
+
+	if (!message)
+		return;
+
+	if (sender->nickname)
+		convo = gaim_find_conversation_with_account(sender->nickname, sg->account);
+
+	if (flags & SILC_MESSAGE_FLAG_SIGNED &&
+	    gaim_prefs_get_bool("/plugins/prpl/silc/verify_im")) {
+		/* XXX */
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_DATA) {
+		/* XXX */
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_ACTION && convo) {
+		msg = g_strdup_printf("<I>%s</I> %s",
+				      sender->nickname ?
+				      sender->nickname : "<unknown>",
+				      (const char *)message);
+		if (!msg)
+			return;
+
+		/* Send to Gaim */
+		gaim_conversation_write(convo, NULL, (const char *)msg,
+					GAIM_MESSAGE_SYSTEM, time(NULL));
+		g_free(msg);
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_NOTICE && convo) {
+		msg = g_strdup_printf("(notice) <I>%s</I> %s",
+				      sender->nickname ?
+				      sender->nickname : "<unknown>",
+				      (const char *)message);
+		if (!msg)
+			return;
+
+		/* Send to Gaim */
+		gaim_conversation_write(convo, NULL, (const char *)msg,
+					GAIM_MESSAGE_SYSTEM, time(NULL));
+		g_free(msg);
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_UTF8)
+		/* Send to Gaim */
+		serv_got_im(gc, sender->nickname ?
+			    sender->nickname : "<unknown>",
+			    (const char *)message, 0, time(NULL));
+}
+
+
+/* Notify message to the client. The notify arguments are sent in the
+   same order as servers sends them. The arguments are same as received
+   from the server except for ID's.  If ID is received application receives
+   the corresponding entry to the ID. For example, if Client ID is received
+   application receives SilcClientEntry.  Also, if the notify type is
+   for channel the channel entry is sent to application (even if server
+   does not send it because client library gets the channel entry from
+   the Channel ID in the packet's header). */
+
+static void
+silc_notify(SilcClient client, SilcClientConnection conn,
+	    SilcNotifyType type, ...)
+{
+	va_list va;
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	GaimConversation *convo;
+	SilcClientEntry client_entry, client_entry2;
+	SilcChannelEntry channel;
+	SilcServerEntry server_entry;
+	SilcIdType idtype;
+	void *entry;
+	SilcUInt32 mode;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	char buf[512], buf2[512], *tmp, *name;
+	SilcBuffer buffer;
+	SilcNotifyType notify;
+	GaimBuddy *b;
+	int i;
+
+	va_start(va, type);
+	memset(buf, 0, sizeof(buf));
+
+	switch (type) {
+
+	case SILC_NOTIFY_TYPE_NONE:
+		break;
+
+	case SILC_NOTIFY_TYPE_INVITE:
+		{
+			GHashTable *components;
+			channel = va_arg(va, SilcChannelEntry);
+			name = va_arg(va, char *);
+			client_entry = va_arg(va, SilcClientEntry);
+
+			components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+			g_hash_table_insert(components, strdup("channel"), name);
+			serv_got_chat_invite(gc, name, client_entry->nickname, NULL, NULL);
+		}
+		break;
+
+	case SILC_NOTIFY_TYPE_JOIN:
+		client_entry = va_arg(va, SilcClientEntry);
+		channel = va_arg(va, SilcChannelEntry);
+
+		/* If we joined channel, do nothing */
+		if (client_entry == conn->local_entry)
+			break;
+
+		convo = gaim_find_conversation_with_account(channel->channel_name,
+							    sg->account);
+		if (!convo)
+			break;
+
+		/* Join user to channel */
+		g_snprintf(buf, sizeof(buf), _("%s@%s"),
+			   client_entry->username, client_entry->hostname);
+		gaim_conv_chat_add_user(GAIM_CONV_CHAT(convo),
+					g_strdup(client_entry->nickname), buf);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_LEAVE:
+		client_entry = va_arg(va, SilcClientEntry);
+		channel = va_arg(va, SilcChannelEntry);
+
+		convo = gaim_find_conversation_with_account(channel->channel_name,
+							    sg->account);
+		if (!convo)
+			break;
+
+		/* Remove user from channel */
+		gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo),
+					   client_entry->nickname, NULL);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_SIGNOFF:
+		client_entry = va_arg(va, SilcClientEntry);
+		tmp = va_arg(va, char *);
+
+		if (!client_entry->nickname)
+			break;
+
+		/* Remove from all channels */
+		silc_hash_table_list(client_entry->channels, &htl);
+		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+			convo = gaim_find_conversation_with_account(chu->channel->channel_name,
+								    sg->account);
+			if (!convo)
+				continue;
+			gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo),
+						   client_entry->nickname,
+						   tmp);
+		}
+		silc_hash_table_list_reset(&htl);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_TOPIC_SET:
+		idtype = va_arg(va, int);
+		entry = va_arg(va, void *);
+		tmp = va_arg(va, char *);
+		channel = va_arg(va, SilcChannelEntry);
+
+		convo = gaim_find_conversation_with_account(channel->channel_name,
+							    sg->account);
+		if (!convo)
+			break;
+
+		if (!tmp)
+			break;
+
+		if (idtype == SILC_ID_CLIENT) {
+			client_entry = (SilcClientEntry)entry;
+			g_snprintf(buf, sizeof(buf),
+				   _("%s has changed the topic of <I>%s</I> to: %s"),
+				   client_entry->nickname, channel->channel_name, tmp);
+			gaim_conv_chat_write(GAIM_CONV_CHAT(convo), client_entry->nickname,
+					     buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+		} else if (idtype == SILC_ID_SERVER) {
+			server_entry = (SilcServerEntry)entry;
+			g_snprintf(buf, sizeof(buf),
+				   _("%s has changed the topic of <I>%s</I> to: %s"),
+				   server_entry->server_name, channel->channel_name, tmp);
+			gaim_conv_chat_write(GAIM_CONV_CHAT(convo), server_entry->server_name,
+					     buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+		} else if (idtype == SILC_ID_CHANNEL) {
+			channel = (SilcChannelEntry)entry;
+			g_snprintf(buf, sizeof(buf),
+				   _("%s has changed the topic of <I>%s</I> to: %s"),
+				   channel->channel_name, channel->channel_name, tmp);
+			gaim_conv_chat_write(GAIM_CONV_CHAT(convo), channel->channel_name,
+					     buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+		}
+
+		gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, tmp);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_NICK_CHANGE:
+		client_entry = va_arg(va, SilcClientEntry);
+		client_entry2 = va_arg(va, SilcClientEntry);
+
+		if (!strcmp(client_entry->nickname, client_entry2->nickname))
+			break;
+
+		/* Change nick on all channels */
+		silc_hash_table_list(client_entry2->channels, &htl);
+		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+			convo = gaim_find_conversation_with_account(chu->channel->channel_name,
+								    sg->account);
+			if (!convo)
+				continue;
+			gaim_conv_chat_rename_user(GAIM_CONV_CHAT(convo),
+						   client_entry->nickname,
+						   client_entry2->nickname);
+		}
+		silc_hash_table_list_reset(&htl);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_CMODE_CHANGE:
+		idtype = va_arg(va, int);
+		entry = va_arg(va, void *);
+		mode = va_arg(va, SilcUInt32);
+		(void)va_arg(va, char *);
+		(void)va_arg(va, char *);
+		(void)va_arg(va, char *);
+		(void)va_arg(va, SilcPublicKey);
+		buffer = va_arg(va, SilcBuffer);
+		channel = va_arg(va, SilcChannelEntry);
+
+		convo = gaim_find_conversation_with_account(channel->channel_name,
+							    sg->account);
+		if (!convo)
+			break;
+
+		if (idtype == SILC_ID_CLIENT)
+			name = ((SilcClientEntry)entry)->nickname;
+		else if (idtype == SILC_ID_SERVER)
+			name = ((SilcServerEntry)entry)->server_name;
+		else
+			name = ((SilcChannelEntry)entry)->channel_name;
+		if (!name)
+			break;
+
+		if (mode) {
+			silcgaim_get_chmode_string(mode, buf2, sizeof(buf2));
+			g_snprintf(buf, sizeof(buf),
+				   _("<I>%s</I> set channel <I>%s</I> modes to: %s"), name,
+				   channel->channel_name, buf2);
+		} else {
+			g_snprintf(buf, sizeof(buf),
+				   _("<I>%s</I> removed all channel <I>%s</I> modes"), name,
+				   channel->channel_name);
+		}
+		gaim_conv_chat_write(GAIM_CONV_CHAT(convo), channel->channel_name,
+				     buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+		break;
+
+	case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
+		idtype = va_arg(va, int);
+		entry = va_arg(va, void *);
+		mode = va_arg(va, SilcUInt32);
+		client_entry2 = va_arg(va, SilcClientEntry);
+		channel = va_arg(va, SilcChannelEntry);
+
+		convo = gaim_find_conversation_with_account(channel->channel_name,
+							    sg->account);
+		if (!convo)
+			break;
+
+		if (idtype == SILC_ID_CLIENT)
+			name = ((SilcClientEntry)entry)->nickname;
+		else if (idtype == SILC_ID_SERVER)
+			name = ((SilcServerEntry)entry)->server_name;
+		else
+			name = ((SilcChannelEntry)entry)->channel_name;
+		if (!name)
+			break;
+
+		if (mode) {
+			silcgaim_get_chumode_string(mode, buf2, sizeof(buf2));
+			g_snprintf(buf, sizeof(buf),
+				   _("<I>%s</I> set <I>%s's</I> modes to: %s"), name,
+				   client_entry2->nickname, buf2);
+		} else {
+			g_snprintf(buf, sizeof(buf),
+				   _("<I>%s</I> removed all <I>%s's</I> modes"), name,
+				   client_entry2->nickname);
+		}
+		gaim_conv_chat_write(GAIM_CONV_CHAT(convo), channel->channel_name,
+				     buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+		break;
+
+	case SILC_NOTIFY_TYPE_MOTD:
+		tmp = va_arg(va, char *);
+		silc_free(sg->motd);
+		sg->motd = silc_memdup(tmp, strlen(tmp));
+		break;
+
+	case SILC_NOTIFY_TYPE_KICKED:
+		client_entry = va_arg(va, SilcClientEntry);
+		tmp = va_arg(va, char *);
+		client_entry2 = va_arg(va, SilcClientEntry);
+		channel = va_arg(va, SilcChannelEntry);
+
+		convo = gaim_find_conversation_with_account(channel->channel_name,
+							    sg->account);
+		if (!convo)
+			break;
+
+		if (client_entry == conn->local_entry) {
+			/* Remove us from channel */
+			g_snprintf(buf, sizeof(buf),
+				   _("You have been kicked off <I>%s</I> by <I>%s</I> (%s)"),
+				   channel->channel_name, client_entry2->nickname,
+				   tmp ? tmp : "");
+			gaim_conv_chat_write(GAIM_CONV_CHAT(convo), client_entry->nickname,
+					     buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+			serv_got_chat_left(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)));
+		} else {
+			/* Remove user from channel */
+			g_snprintf(buf, sizeof(buf), ("Kicked by %s (%s)"),
+				   client_entry2->nickname, tmp ? tmp : "");
+			gaim_conv_chat_rename_user(GAIM_CONV_CHAT(convo),
+						   client_entry->nickname,
+						   buf);
+		}
+
+		break;
+
+	case SILC_NOTIFY_TYPE_KILLED:
+		client_entry = va_arg(va, SilcClientEntry);
+		tmp = va_arg(va, char *);
+		idtype = va_arg(va, int);
+		entry = va_arg(va, SilcClientEntry);
+
+		if (!client_entry->nickname)
+			break;
+
+		if (client_entry == conn->local_entry) {
+			if (idtype == SILC_ID_CLIENT) {
+				client_entry2 = (SilcClientEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("You have been killed by %s (%s)"),
+					   client_entry2->nickname, tmp ? tmp : "");
+			} else if (idtype == SILC_ID_SERVER) {
+				server_entry = (SilcServerEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("You have been killed by %s (%s)"),
+					   server_entry->server_name, tmp ? tmp : "");
+			} else if (idtype == SILC_ID_CHANNEL) {
+				channel = (SilcChannelEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("You have been killed by %s (%s)"),
+					   channel->channel_name, tmp ? tmp : "");
+			}
+
+			/* Remove us from all channels */
+			silc_hash_table_list(client_entry->channels, &htl);
+			while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+				convo = gaim_find_conversation_with_account(chu->channel->channel_name,
+									    sg->account);
+				if (!convo)
+					continue;
+				gaim_conv_chat_write(GAIM_CONV_CHAT(convo), client_entry->nickname,
+						     buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+				serv_got_chat_left(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)));
+			}
+			silc_hash_table_list_reset(&htl);
+
+		} else {
+			if (idtype == SILC_ID_CLIENT) {
+				client_entry2 = (SilcClientEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("Killed by %s (%s)"),
+					   client_entry2->nickname, tmp ? tmp : "");
+			} else if (idtype == SILC_ID_SERVER) {
+				server_entry = (SilcServerEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("Killed by %s (%s)"),
+					   server_entry->server_name, tmp ? tmp : "");
+			} else if (idtype == SILC_ID_CHANNEL) {
+				channel = (SilcChannelEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("Killed by %s (%s)"),
+					   channel->channel_name, tmp ? tmp : "");
+			}
+
+			/* Remove user from all channels */
+			silc_hash_table_list(client_entry->channels, &htl);
+			while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+				convo = gaim_find_conversation_with_account(chu->channel->channel_name,
+									    sg->account);
+				if (!convo)
+					continue;
+				gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo),
+							   client_entry->nickname, tmp);
+			}
+			silc_hash_table_list_reset(&htl);
+		}
+
+		break;
+
+	case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
+		break;
+
+	case SILC_NOTIFY_TYPE_SERVER_SIGNOFF:
+		{
+			int i;
+			SilcClientEntry *clients;
+			SilcUInt32 clients_count;
+
+			(void)va_arg(va, void *);
+			clients = va_arg(va, SilcClientEntry *);
+			clients_count = va_arg(va, SilcUInt32);
+
+			for (i = 0; i < clients_count; i++) {
+				if (!clients[i]->nickname)
+					break;
+
+				/* Remove from all channels */
+				silc_hash_table_list(clients[i]->channels, &htl);
+				while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+					convo =
+						gaim_find_conversation_with_account(chu->channel->channel_name,
+										    sg->account);
+					if (!convo)
+						continue;
+					gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo),
+								   clients[i]->nickname,
+								   _("Server signoff"));
+				}
+				silc_hash_table_list_reset(&htl);
+			}
+		}
+		break;
+
+	case SILC_NOTIFY_TYPE_ERROR:
+		{
+			SilcStatus error = va_arg(va, int);
+			gaim_notify_error(gc, "Error Notify",
+					  silc_get_status_message(error),
+					  NULL);
+		}
+		break;
+
+	case SILC_NOTIFY_TYPE_WATCH:
+		{
+			SilcPublicKey public_key;
+			unsigned char *pk;
+			SilcUInt32 pk_len;
+			char *fingerprint;
+
+			client_entry = va_arg(va, SilcClientEntry);
+			(void)va_arg(va, char *);
+			mode = va_arg(va, SilcUInt32);
+			notify = va_arg(va, int);
+			public_key = va_arg(va, SilcPublicKey);
+
+			b = NULL;
+			if (public_key) {
+				GaimBlistNode *gnode, *cnode, *bnode;
+				const char *f;
+
+				pk = silc_pkcs_public_key_encode(public_key, &pk_len);
+				if (!pk)
+					break;
+				fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+				for (i = 0; i < strlen(fingerprint); i++)
+					if (fingerprint[i] == ' ')
+						fingerprint[i] = '_';
+				g_snprintf(buf, sizeof(buf) - 1,
+					   "%s" G_DIR_SEPARATOR_S "clientkeys"
+					   G_DIR_SEPARATOR_S "clientkey_%s.pub",
+					   silcgaim_silcdir(), fingerprint);
+				silc_free(fingerprint);
+				silc_free(pk);
+
+				/* Find buddy by associated public key */
+				for (gnode = gaim_get_blist()->root; gnode;
+				     gnode = gnode->next) {
+					if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+						continue;
+					for (cnode = gnode->child; cnode; cnode = cnode->next) {
+						if( !GAIM_BLIST_NODE_IS_CONTACT(cnode))
+							continue;
+						for (bnode = cnode->child; bnode;
+						     bnode = bnode->next) {
+							if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
+								continue;
+							b = (GaimBuddy *)bnode;
+							if (b->account != gc->account)
+								continue;
+							f = gaim_blist_node_get_string(bnode, "public-key");
+							if (!strcmp(f, buf))
+								goto cont;
+						}
+					}
+				}
+			}
+		cont:
+			if (!b) {
+				/* Find buddy by nickname */
+				b = gaim_find_buddy(sg->account, client_entry->nickname);
+				if (!b) {
+					fprintf(stderr, "WATCH for %s, unknown buddy",
+						client_entry->nickname);
+					break;
+				}
+			}
+
+			silc_free(b->proto_data);
+			b->proto_data = silc_memdup(client_entry->id,
+						    sizeof(*client_entry->id));
+			if (notify == SILC_NOTIFY_TYPE_NICK_CHANGE) {
+				break;
+			} else if (notify == SILC_NOTIFY_TYPE_UMODE_CHANGE) {
+				/* See if client was away and is now present */
+				if (!(mode & (SILC_UMODE_GONE | SILC_UMODE_INDISPOSED |
+					      SILC_UMODE_BUSY | SILC_UMODE_PAGE |
+					      SILC_UMODE_DETACHED)) &&
+				    (client_entry->mode & SILC_UMODE_GONE ||
+				     client_entry->mode & SILC_UMODE_INDISPOSED ||
+				     client_entry->mode & SILC_UMODE_BUSY ||
+				     client_entry->mode & SILC_UMODE_PAGE ||
+				     client_entry->mode & SILC_UMODE_DETACHED)) {
+					client_entry->mode = mode;
+					gaim_blist_update_buddy_presence(b, GAIM_BUDDY_ONLINE);
+				}
+				else if ((mode & SILC_UMODE_GONE) ||
+					 (mode & SILC_UMODE_INDISPOSED) ||
+					 (mode & SILC_UMODE_BUSY) ||
+					 (mode & SILC_UMODE_PAGE) ||
+					 (mode & SILC_UMODE_DETACHED)) {
+					client_entry->mode = mode;
+					gaim_blist_update_buddy_presence(b, GAIM_BUDDY_OFFLINE);
+				}
+			} else if (notify == SILC_NOTIFY_TYPE_SIGNOFF ||
+				   notify == SILC_NOTIFY_TYPE_SERVER_SIGNOFF ||
+				   notify == SILC_NOTIFY_TYPE_KILLED) {
+				client_entry->mode = mode;
+				gaim_blist_update_buddy_presence(b, GAIM_BUDDY_OFFLINE);
+			} else if (notify == SILC_NOTIFY_TYPE_NONE) {
+				client_entry->mode = mode;
+				gaim_blist_update_buddy_presence(b, GAIM_BUDDY_ONLINE);
+			}
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	va_end(va);
+}
+
+
+/* Command handler. This function is called always in the command function.
+   If error occurs it will be called as well. `conn' is the associated
+   client connection. `cmd_context' is the command context that was
+   originally sent to the command. `success' is FALSE if error occurred
+   during command. `command' is the command being processed. It must be
+   noted that this is not reply from server. This is merely called just
+   after application has called the command. Just to tell application
+   that the command really was processed. */
+
+static void
+silc_command(SilcClient client, SilcClientConnection conn,
+	     SilcClientCommandContext cmd_context, bool success,
+	     SilcCommand command, SilcStatus status)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+
+	switch (command) {
+
+	case SILC_COMMAND_CMODE:
+		if (cmd_context->argc == 3 &&
+		    !strcmp(cmd_context->argv[2], "+C"))
+			sg->chpk = TRUE;
+		else
+			sg->chpk = FALSE;
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+silcgaim_whois_more(SilcClientEntry client_entry, gint id)
+{
+	SilcAttributePayload attr;
+	SilcAttribute attribute;
+	char *buf;
+	GString *s;
+	SilcVCardStruct vcard;
+	int i;
+
+	if (id != 0)
+		return;
+
+	memset(&vcard, 0, sizeof(vcard));
+
+	s = g_string_new("");
+
+	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;
+			g_string_append_printf(s, _("Personal Information:\n\n"));
+			if (vcard.full_name)
+				g_string_append_printf(s, _("Full Name:\t\t%s\n"),
+						       vcard.full_name);
+			if (vcard.first_name)
+				g_string_append_printf(s, _("First Name:\t%s\n"),
+						       vcard.first_name);
+			if (vcard.middle_names)
+				g_string_append_printf(s, _("Middle Names:\t%s\n"),
+						       vcard.middle_names);
+			if (vcard.family_name)
+				g_string_append_printf(s, _("Family Name:\t%s\n"),
+						       vcard.family_name);
+			if (vcard.nickname)
+				g_string_append_printf(s, _("Nickname:\t\t%s\n"),
+						       vcard.nickname);
+			if (vcard.bday)
+				g_string_append_printf(s, _("Birth Day:\t\t%s\n"),
+						       vcard.bday);
+			if (vcard.title)
+				g_string_append_printf(s, _("Job Title:\t\t%s\n"),
+						       vcard.title);
+			if (vcard.role)
+				g_string_append_printf(s, _("Job Role:\t\t%s\n"),
+						       vcard.role);
+			if (vcard.org_name)
+				g_string_append_printf(s, _("Organization:\t%s\n"),
+						       vcard.org_name);
+			if (vcard.org_unit)
+				g_string_append_printf(s, _("Unit:\t\t%s\n"),
+						       vcard.org_unit);
+			if (vcard.url)
+				g_string_append_printf(s, _("Homepage:\t%s\n"),
+						       vcard.url);
+			if (vcard.label)
+				g_string_append_printf(s, _("Address:\t%s\n"),
+						       vcard.label);
+			for (i = 0; i < vcard.num_tels; i++) {
+				if (vcard.tels[i].telnum)
+					g_string_append_printf(s, _("Tel:\t\t\t%s\n"),
+							       vcard.tels[i].telnum);
+			}
+			for (i = 0; i < vcard.num_emails; i++) {
+				if (vcard.emails[i].address)
+					g_string_append_printf(s, _("EMail:\t\t%s\n"),
+							       vcard.emails[i].address);
+			}
+			if (vcard.note)
+				g_string_append_printf(s, _("\nNote:\t\t%s\n"),
+						       vcard.note);
+			break;
+		}
+	}
+
+	buf = g_string_free(s, FALSE);
+	gaim_notify_info(NULL, _("User Information"), _("User Information"),
+			 buf);
+	g_free(buf);
+}
+
+/* Command reply handler. This function is called always in the command reply
+   function. If error occurs it will be called as well. Normal scenario
+   is that it will be called after the received command data has been parsed
+   and processed. The function is used to pass the received command data to
+   the application.
+
+   `conn' is the associated client connection. `cmd_payload' is the command
+   payload data received from server and it can be ignored. It is provided
+   if the application would like to re-parse the received command data,
+   however, it must be noted that the data is parsed already by the library
+   thus the payload can be ignored. `success' is FALSE if error occurred.
+   In this case arguments are not sent to the application. The `status' is
+   the command reply status server returned. The `command' is the command
+   reply being processed. The function has variable argument list and each
+   command defines the number and type of arguments it passes to the
+   application (on error they are not sent). */
+
+static void
+silc_command_reply(SilcClient client, SilcClientConnection conn,
+		   SilcCommandPayload cmd_payload, bool success,
+		   SilcCommand command, SilcStatus status, ...)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	GaimConversation *convo;
+	va_list vp;
+
+	va_start(vp, status);
+
+	switch (command) {
+	case SILC_COMMAND_JOIN:
+		{
+			SilcChannelEntry channel_entry;
+
+			if (!success) {
+				gaim_notify_error(gc, _("Join Chat"), _("Cannot join channel"),
+						  silc_get_status_message(status));
+				return;
+			}
+
+			(void)va_arg(vp, char *);
+			channel_entry = va_arg(vp, SilcChannelEntry);
+
+			/* Resolve users on channel */
+			silc_client_get_clients_by_channel(client, conn, channel_entry,
+							   silcgaim_chat_join_done,
+							   channel_entry);
+		}
+		break;
+
+	case SILC_COMMAND_LEAVE:
+		break;
+
+	case SILC_COMMAND_USERS:
+		break;
+
+	case SILC_COMMAND_WHOIS:
+		{
+			SilcUInt32 idle, mode;
+			SilcBuffer channels, user_modes;
+			SilcClientEntry client_entry;
+			char *buf, tmp[1024];
+			GString *s;
+
+			if (!success) {
+				gaim_notify_error(gc, _("User Information"),
+						  _("Cannot get user information"),
+						  silc_get_status_message(status));
+				break;
+			}
+
+			client_entry = va_arg(vp, SilcClientEntry);
+			if (!client_entry->nickname)
+				break;
+			(void)va_arg(vp, char *);
+			(void)va_arg(vp, char *);
+			(void)va_arg(vp, char *);
+			channels = va_arg(vp, SilcBuffer);
+			mode = va_arg(vp, SilcUInt32);
+			idle = va_arg(vp, SilcUInt32);
+			(void)va_arg(vp, unsigned char *);
+			user_modes = va_arg(vp, SilcBuffer);
+
+			s = g_string_new("");
+			g_string_append_printf(s, _("Nickname:\t\t%s\n"), client_entry->nickname);
+			if (client_entry->realname)
+				g_string_append_printf(s, _("Real Name:\t%s\n"), client_entry->realname);
+			if (client_entry->username)
+				g_string_append_printf(s, _("Username:\t\t%s\n"), client_entry->username);
+			if (client_entry->hostname)
+				g_string_append_printf(s, _("Hostname:\t\t%s\n"), client_entry->hostname);
+			if (client_entry->server)
+				g_string_append_printf(s, _("Server:\t\t%s\n"), client_entry->server);
+
+			if (mode) {
+				memset(tmp, 0, sizeof(tmp));
+				silcgaim_get_umode_string(mode, tmp, sizeof(tmp) - 1);
+				g_string_append_printf(s, _("User Mode:\t%s\n"), tmp);
+			}
+
+			if (channels && user_modes) {
+				SilcUInt32 *umodes;
+				SilcDList list =
+					silc_channel_payload_parse_list(channels->data,
+									channels->len);
+				if (list && silc_get_mode_list(user_modes,
+							       silc_dlist_count(list),
+							       &umodes)) {
+					SilcChannelPayload entry;
+					int i = 0;
+
+					g_string_append_printf(s, _("\nChannels:\n"));
+					memset(tmp, 0, sizeof(tmp));
+					silc_dlist_start(list);
+					while ((entry = silc_dlist_get(list))
+					       != SILC_LIST_END) {
+						SilcUInt32 name_len;
+						char *m = silc_client_chumode_char(umodes[i++]);
+						char *name = silc_channel_get_name(entry, &name_len);
+						if (m)
+							silc_strncat(tmp, sizeof(tmp) - 1, m, strlen(m));
+						silc_strncat(tmp, sizeof(tmp) - 1, name, name_len);
+						silc_strncat(tmp, sizeof(tmp) - 1, "  ", 1);
+						silc_free(m);
+
+					}
+					g_string_append_printf(s, _("%s\n"), tmp);
+					silc_free(umodes);
+				}
+			}
+
+			if (client_entry->public_key) {
+				char *fingerprint, *babbleprint;
+				unsigned char *pk;
+				SilcUInt32 pk_len;
+				pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+				fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+				babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+				g_string_append_printf(s, _("\nPublic Key Fingerprint:\n%s\n\n"), fingerprint);
+				g_string_append_printf(s, _("Public Key Babbleprint:\n%s"), babbleprint);
+				silc_free(fingerprint);
+				silc_free(babbleprint);
+				silc_free(pk);
+			}
+
+			buf = g_string_free(s, FALSE);
+#if 0 /* XXX for now, let's not show attrs here */
+			if (client_entry->attrs)
+				gaim_request_action(NULL, _("User Information"),
+						    _("User Information"),
+						    buf, 1, client_entry, 2,
+						    _("OK"), G_CALLBACK(silcgaim_whois_more),
+						    _("More..."), G_CALLBACK(silcgaim_whois_more));
+			else
+#endif
+				gaim_notify_info(NULL, _("User Information"),
+						 _("User Information"), buf);
+			g_free(buf);
+		}
+		break;
+
+	case SILC_COMMAND_DETACH:
+		if (!success) {
+			gaim_notify_error(gc, _("Detach From Server"), _("Cannot detach"),
+					  silc_get_status_message(status));
+			return;
+		}
+		break;
+
+	case SILC_COMMAND_TOPIC:
+		{
+			SilcChannelEntry channel;
+
+			if (!success) {
+				gaim_notify_error(gc, _("Topic"), _("Cannot set topic"),
+						  silc_get_status_message(status));
+				return;
+			}
+
+			channel = va_arg(vp, SilcChannelEntry);
+
+			convo = gaim_find_conversation_with_account(channel->channel_name,
+								    sg->account);
+			if (!convo)
+				break;
+
+			/* Set topic */
+			if (channel->topic)
+				gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, channel->topic);
+		}
+		break;
+
+	case SILC_COMMAND_LIST:
+		{
+			char *topic, *name;
+			int usercount;
+			GaimRoomlistRoom *room;
+
+			if (sg->roomlist_canceled)
+				break;
+
+			if (!success) {
+				gaim_notify_error(gc, _("Roomlist"), _("Cannot get room list"),
+						  silc_get_status_message(status));
+				gaim_roomlist_set_in_progress(sg->roomlist, FALSE);
+				gaim_roomlist_unref(sg->roomlist);
+				sg->roomlist = NULL;
+				return;
+			}
+
+			(void)va_arg(vp, SilcChannelEntry);
+			name = va_arg(vp, char *);
+			topic = va_arg(vp, char *);
+			usercount = va_arg(vp, int);
+
+			room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, name, NULL);
+			gaim_roomlist_room_add_field(sg->roomlist, room, name);
+			gaim_roomlist_room_add_field(sg->roomlist, room,
+						     SILC_32_TO_PTR(usercount));
+			gaim_roomlist_room_add_field(sg->roomlist, room,
+						     topic ? topic : "");
+			gaim_roomlist_room_add(sg->roomlist, room);
+
+			if (status == SILC_STATUS_LIST_END ||
+			    status == SILC_STATUS_OK) {
+				gaim_roomlist_set_in_progress(sg->roomlist, FALSE);
+				gaim_roomlist_unref(sg->roomlist);
+				sg->roomlist = NULL;
+			}
+		}
+		break;
+
+	case SILC_COMMAND_GETKEY:
+		{
+			SilcPublicKey public_key;
+
+			if (!success) {
+				gaim_notify_error(gc, _("Get Public Key"),
+						  _("Cannot fetch the public key"),
+						  silc_get_status_message(status));
+				return;
+			}
+
+			(void)va_arg(vp, SilcUInt32);
+			(void)va_arg(vp, void *);
+			public_key = va_arg(vp, SilcPublicKey);
+
+			if (!public_key)
+				gaim_notify_error(gc, _("Get Public Key"),
+						  _("Cannot fetch the public key"),
+						  _("No public key was received"));
+		}
+		break;
+
+	case SILC_COMMAND_INFO:
+		{
+
+			SilcServerEntry server_entry;
+			char *server_name;
+			char *server_info;
+			char tmp[256];
+
+			if (!success) {
+				gaim_notify_error(gc, _("Server Information"),
+						  _("Cannot get server information"),
+						  silc_get_status_message(status));
+				return;
+			}
+
+			server_entry = va_arg(vp, SilcServerEntry);
+			server_name = va_arg(vp, char *);
+			server_info = va_arg(vp, char *);
+
+			if (server_name && server_info) {
+				g_snprintf(tmp, sizeof(tmp), "Server: %s\n%s",
+					   server_name, server_info);
+				gaim_notify_info(NULL, _("Server Information"),
+						 _("Server Information"), tmp);
+			}
+		}
+		break;
+
+	case SILC_COMMAND_KILL:
+		if (!success) {
+			gaim_notify_error(gc, _("Kill User"),
+					  _("Could not kill user"),
+					  silc_get_status_message(status));
+			return;
+		}
+		break;
+
+	case SILC_COMMAND_CMODE:
+		{
+			SilcChannelEntry channel_entry;
+			SilcBuffer channel_pubkeys;
+
+			if (!success)
+				return;
+
+			channel_entry = va_arg(vp, SilcChannelEntry);
+			(void)va_arg(vp, SilcUInt32);
+			(void)va_arg(vp, SilcPublicKey);
+			channel_pubkeys = va_arg(vp, SilcBuffer);
+
+			if (sg->chpk)
+				silcgaim_chat_chauth_show(sg, channel_entry, channel_pubkeys);
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	va_end(vp);
+}
+
+
+/* Called to indicate that connection was either successfully established
+   or connecting failed.  This is also the first time application receives
+   the SilcClientConnection objecet which it should save somewhere.
+   If the `success' is FALSE the application must always call the function
+   silc_client_close_connection. */
+
+static void
+silc_connected(SilcClient client, SilcClientConnection conn,
+	       SilcClientConnectionStatus status)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	gboolean reject_watch, block_invites, block_ims;
+
+	if (!gc) {
+		sg->conn = NULL;
+		silc_client_close_connection(client, conn);
+		return;
+	}
+
+	switch (status) {
+	case SILC_CLIENT_CONN_SUCCESS:
+	case SILC_CLIENT_CONN_SUCCESS_RESUME:
+		gaim_connection_set_state(gc, GAIM_CONNECTED);
+		serv_finish_login(gc);
+		unlink(silcgaim_session_file(gaim_account_get_username(sg->account)));
+
+		/* Send any UMODEs configured for account */
+		reject_watch = gaim_account_get_bool(sg->account, "reject-watch", FALSE);
+		block_invites = gaim_account_get_bool(sg->account, "block-invites", FALSE);
+		block_ims = gaim_account_get_bool(sg->account, "block-ims", FALSE);
+		if (reject_watch || block_invites || block_ims) {
+			char m[5];
+			g_snprintf(m, sizeof(m), "+%s%s%s",
+				   reject_watch ? "w" : "",
+				   block_invites ? "I" : "",
+				   block_ims ? "P" : "");
+			silc_client_command_call(sg->client, sg->conn, NULL,
+						 "UMODE", m, NULL);
+		}
+
+		return;
+		break;
+
+	case SILC_CLIENT_CONN_ERROR:
+		gaim_connection_error(gc, _("Error during connecting to SILC Server"));
+		unlink(silcgaim_session_file(gaim_account_get_username(sg->account)));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_KE:
+		gaim_connection_error(gc, _("Key Exchange failed"));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_AUTH:
+		gaim_connection_error(gc, _("Authentication failed"));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_RESUME:
+		gaim_connection_error(gc,
+				      _("Resuming detached session failed."
+					"Press Reconnect to create new connection."));
+		unlink(silcgaim_session_file(gaim_account_get_username(sg->account)));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_TIMEOUT:
+		gaim_connection_error(gc, _("Connection timeout"));
+		break;
+	}
+
+	/* Error */
+	sg->conn = NULL;
+	silc_client_close_connection(client, conn);
+}
+
+
+/* Called to indicate that connection was disconnected to the server.
+   The `status' may tell the reason of the disconnection, and if the
+   `message' is non-NULL it may include the disconnection message
+   received from server. */
+
+static void
+silc_disconnected(SilcClient client, SilcClientConnection conn,
+		  SilcStatus status, const char *message)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+
+	if (sg->resuming && !sg->detaching)
+		unlink(silcgaim_session_file(gaim_account_get_username(sg->account)));
+
+	sg->conn = NULL;
+
+	/* Close the connection */
+	if (!sg->detaching)
+		gaim_connection_error(gc, _("Disconnected by server"));
+	else
+		gaim_connection_destroy(gc);
+}
+
+
+typedef struct {
+	SilcGetAuthMeth completion;
+	void *context;
+} *SilcGaimGetAuthMethod;
+
+/* Callback called when we've received the authentication method information
+   from the server after we've requested it. */
+
+static void silc_get_auth_method_callback(SilcClient client,
+					  SilcClientConnection conn,
+					  SilcAuthMethod auth_meth,
+					  void *context)
+{
+	SilcGaimGetAuthMethod internal = context;
+
+	switch (auth_meth) {
+	case SILC_AUTH_NONE:
+		/* No authentication required. */
+		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+		break;
+
+	case SILC_AUTH_PASSWORD:
+		/* By returning NULL here the library will ask the passphrase from us
+		   by calling the silc_ask_passphrase. */
+		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+		break;
+
+	case SILC_AUTH_PUBLIC_KEY:
+		/* Do not get the authentication data now, the library will generate
+		   it using our default key, if we do not provide it here. */
+		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+		break;
+	}
+
+	silc_free(internal);
+}
+
+/* Find authentication method and authentication data by hostname and
+   port. The hostname may be IP address as well. When the authentication
+   method has been resolved the `completion' callback with the found
+   authentication method and authentication data is called. The `conn'
+   may be NULL. */
+
+static void
+silc_get_auth_method(SilcClient client, SilcClientConnection conn,
+		     char *hostname, SilcUInt16 port,
+		     SilcGetAuthMeth completion, void *context)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	SilcGaimGetAuthMethod internal;
+
+	/* Progress */
+	if (sg->resuming)
+		gaim_connection_update_progress(gc, _("Resuming session"), 4, 5);
+	else
+		gaim_connection_update_progress(gc, _("Authenticating connection"), 4, 5);
+
+	/* Check configuration if we have this connection configured.  If we
+	   have then return that data immediately, as it's faster way. */
+	if (gc->account->password && *gc->account->password) {
+		completion(TRUE, SILC_AUTH_PASSWORD, gc->account->password,
+			   strlen(gc->account->password), context);
+		return;
+	}
+	if (gaim_account_get_bool(sg->account, "pubkey-auth", FALSE)) {
+		completion(TRUE, SILC_AUTH_PUBLIC_KEY, NULL, 0, context);
+		return;
+	}
+
+	/* Resolve the authentication method from server, as we may not know it. */
+	internal = silc_calloc(1, sizeof(*internal));
+	if (!internal)
+		return;
+	internal->completion = completion;
+	internal->context = context;
+	silc_client_request_authentication_method(client, conn,
+						  silc_get_auth_method_callback,
+						  internal);
+}
+
+
+/* Verifies received public key. The `conn_type' indicates which entity
+   (server, client etc.) has sent the public key. If user decides to trust
+   the application may save the key as trusted public key for later
+   use. The `completion' must be called after the public key has been
+   verified. */
+
+static void
+silc_verify_public_key(SilcClient client, SilcClientConnection conn,
+		       SilcSocketType conn_type, unsigned char *pk,
+		       SilcUInt32 pk_len, SilcSKEPKType pk_type,
+		       SilcVerifyPublicKey completion, void *context)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+
+	if (!sg->conn && (conn_type == SILC_SOCKET_TYPE_SERVER ||
+			  conn_type == SILC_SOCKET_TYPE_ROUTER)) {
+		/* Progress */
+		if (sg->resuming)
+			gaim_connection_update_progress(gc, _("Resuming session"), 3, 5);
+		else
+			gaim_connection_update_progress(gc, _("Verifying server public key"),
+							3, 5);
+	}
+
+	/* Verify public key */
+	silcgaim_verify_public_key(client, conn, NULL, conn_type, pk,
+				   pk_len, pk_type, completion, context);
+}
+
+typedef struct {
+	SilcAskPassphrase completion;
+	void *context;
+} *SilcGaimAskPassphrase;
+
+static void
+silc_ask_passphrase_cb(SilcGaimAskPassphrase internal, const char *passphrase)
+{
+	if (!passphrase || !(*passphrase))
+		internal->completion(NULL, 0, internal->context);
+	else
+		internal->completion((unsigned char *)passphrase,
+				     strlen(passphrase), internal->context);
+	silc_free(internal);
+}
+
+/* Ask (interact, that is) a passphrase from user. The passphrase is
+   returned to the library by calling the `completion' callback with
+   the `context'. The returned passphrase SHOULD be in UTF-8 encoded,
+   if not then the library will attempt to encode. */
+
+static void
+silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
+		    SilcAskPassphrase completion, void *context)
+{
+	SilcGaimAskPassphrase internal = silc_calloc(1, sizeof(*internal));
+
+	if (!internal)
+		return;
+	internal->completion = completion;
+	internal->context = context;
+	gaim_request_input(NULL, _("Passphrase"), NULL,
+			   _("Passphrase required"), NULL, FALSE, TRUE, NULL,
+			   _("OK"), G_CALLBACK(silc_ask_passphrase_cb),
+			   _("Cancel"), G_CALLBACK(silc_ask_passphrase_cb),
+			   internal);
+}
+
+
+/* Notifies application that failure packet was received.  This is called
+   if there is some protocol active in the client.  The `protocol' is the
+   protocol context.  The `failure' is opaque pointer to the failure
+   indication.  Note, that the `failure' is protocol dependant and
+   application must explicitly cast it to correct type.  Usually `failure'
+   is 32 bit failure type (see protocol specs for all protocol failure
+   types). */
+
+static void
+silc_failure(SilcClient client, SilcClientConnection conn,
+	     SilcProtocol protocol, void *failure)
+{
+	GaimConnection *gc = client->application;
+	char buf[128];
+
+	memset(buf, 0, sizeof(buf));
+
+	if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) {
+		SilcSKEStatus status = (SilcSKEStatus)SILC_PTR_TO_32(failure);
+
+		if (status == SILC_SKE_STATUS_BAD_VERSION)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Version mismatch, upgrade your client"));
+		if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not trust/support your public key"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_GROUP)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed KE group"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed cipher"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_PKCS)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed PKCS"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed hash function"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_HMAC)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed HMAC"));
+		if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE)
+			g_snprintf(buf, sizeof(buf), _("Failure: Incorrect signature"));
+		if (status == SILC_SKE_STATUS_INVALID_COOKIE)
+			g_snprintf(buf, sizeof(buf), _("Failure: Invalid cookie"));
+
+		/* Show the error on the progress bar.  A more generic error message
+		   is going to be showed to user after this in the silc_connected. */
+		gaim_connection_update_progress(gc, buf, 2, 5);
+	}
+
+	if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) {
+		SilcUInt32 err = SILC_PTR_TO_32(failure);
+
+		if (err == SILC_AUTH_FAILED)
+			g_snprintf(buf, sizeof(buf), _("Failure: Authentication failed"));
+
+		/* Show the error on the progress bar.  A more generic error message
+		   is going to be showed to user after this in the silc_connected. */
+		gaim_connection_update_progress(gc, buf, 4, 5);
+	}
+}
+
+/* Asks whether the user would like to perform the key agreement protocol.
+   This is called after we have received an key agreement packet or an
+   reply to our key agreement packet. This returns TRUE if the user wants
+   the library to perform the key agreement protocol and FALSE if it is not
+   desired (application may start it later by calling the function
+   silc_client_perform_key_agreement). If TRUE is returned also the
+   `completion' and `context' arguments must be set by the application. */
+
+static bool
+silc_key_agreement(SilcClient client, SilcClientConnection conn,
+		   SilcClientEntry client_entry, const char *hostname,
+		   SilcUInt16 port, SilcKeyAgreementCallback *completion,
+		   void **context)
+{
+	silcgaim_buddy_keyagr_request(client, conn, client_entry, hostname, port);
+	*completion = NULL;
+	*context = NULL;
+	return FALSE;
+}
+
+
+/* Notifies application that file transfer protocol session is being
+   requested by the remote client indicated by the `client_entry' from
+   the `hostname' and `port'. The `session_id' is the file transfer
+   session and it can be used to either accept or reject the file
+   transfer request, by calling the silc_client_file_receive or
+   silc_client_file_close, respectively. */
+
+static void
+silc_ftp(SilcClient client, SilcClientConnection conn,
+	 SilcClientEntry client_entry, SilcUInt32 session_id,
+	 const char *hostname, SilcUInt16 port)
+{
+	silcgaim_ftp_request(client, conn, client_entry, session_id,
+			     hostname, port);
+}
+
+
+/* Delivers SILC session detachment data indicated by `detach_data' to the
+   application.  If application has issued SILC_COMMAND_DETACH command
+   the client session in the SILC network is not quit.  The client remains
+   in the network but is detached.  The detachment data may be used later
+   to resume the session in the SILC Network.  The appliation is
+   responsible of saving the `detach_data', to for example in a file.
+
+   The detachment data can be given as argument to the functions
+   silc_client_connect_to_server, or silc_client_add_connection when
+   creating connection to remote server, inside SilcClientConnectionParams
+   structure.  If it is provided the client library will attempt to resume
+   the session in the network.  After the connection is created
+   successfully, the application is responsible of setting the user
+   interface for user into the same state it was before detaching (showing
+   same channels, channel modes, etc).  It can do this by fetching the
+   information (like joined channels) from the client library. */
+
+static void
+silc_detach(SilcClient client, SilcClientConnection conn,
+	    const unsigned char *detach_data, SilcUInt32 detach_data_len)
+{
+	GaimConnection *gc = client->application;
+	SilcGaim sg = gc->proto_data;
+	const char *file;
+
+	/* Save the detachment data to file. */
+	file = silcgaim_session_file(gaim_account_get_username(sg->account));
+	unlink(file);
+	silc_file_writefile(file, detach_data, detach_data_len);
+}
+
+SilcClientOperations ops = {
+	silc_say,
+	silc_channel_message,
+	silc_private_message,
+	silc_notify,
+	silc_command,
+	silc_command_reply,
+	silc_connected,
+	silc_disconnected,
+	silc_get_auth_method,
+	silc_verify_public_key,
+	silc_ask_passphrase,
+	silc_failure,
+	silc_key_agreement,
+	silc_ftp,
+	silc_detach
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/pk.c	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,271 @@
+/*
+
+  silcgaim_pk.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"
+
+/************************* Public Key Verification ***************************/
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	char *filename;
+	char *entity;
+	char *entity_name;
+	char *fingerprint;
+	char *babbleprint;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
+	SilcSKEPKType pk_type;
+	SilcVerifyPublicKey completion;
+	void *context;
+	gboolean changed;
+} *PublicKeyVerify;
+
+static void silcgaim_verify_ask(const char *entity,
+				const char *fingerprint,
+				const char *babbleprint,
+				PublicKeyVerify verify);
+
+static void silcgaim_verify_cb(PublicKeyVerify verify, gint id)
+{
+	if (id != 2) {
+		if (verify->completion)
+			verify->completion(FALSE, verify->context);
+	} else {
+		if (verify->completion)
+			verify->completion(TRUE, verify->context);
+
+		/* Save the key for future checking */
+		silc_pkcs_save_public_key_data(verify->filename, verify->pk,
+					       verify->pk_len, SILC_PKCS_FILE_PEM);
+	}
+
+	silc_free(verify->filename);
+	silc_free(verify->entity);
+	silc_free(verify->entity_name);
+	silc_free(verify->fingerprint);
+	silc_free(verify->babbleprint);
+	silc_free(verify->pk);
+	silc_free(verify);
+}
+
+static void silcgaim_verify_details_cb(PublicKeyVerify verify)
+{
+	/* What a hack.  We have to display the accept dialog _again_
+	   because Gaim closes the dialog after you press the button.  Gaim
+	   should have option for the dialogs whether the buttons close them
+	   or not. */
+	silcgaim_verify_ask(verify->entity, verify->fingerprint,
+			    verify->babbleprint, verify);
+}
+
+static void silcgaim_verify_details(PublicKeyVerify verify, gint id)
+{
+	SilcPublicKey public_key;
+	GaimConnection *gc = verify->client->application;
+	SilcGaim sg = gc->proto_data;
+
+	silc_pkcs_public_key_decode(verify->pk, verify->pk_len,
+				    &public_key);
+	silcgaim_show_public_key(sg, verify->entity_name, public_key,
+				 G_CALLBACK(silcgaim_verify_details_cb),
+				 verify);
+	silc_pkcs_public_key_free(public_key);
+}
+
+static void silcgaim_verify_ask(const char *entity,
+				const char *fingerprint,
+				const char *babbleprint,
+				PublicKeyVerify verify)
+{
+	char tmp[256], tmp2[256];
+
+	if (verify->changed) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("Received %s's public key. Your local copy does not match this "
+			     "key. Would you still like to accept this public key?"),
+			   entity);
+	} else {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("Received %s's public key. Would you like to accept this "
+			     "public key?"), entity);
+	}
+	g_snprintf(tmp2, sizeof(tmp2),
+		   _("Fingerprint and babbleprint for the %s key are:\n\n"
+		     "%s\n%s\n"), entity, fingerprint, babbleprint);
+
+	gaim_request_action(NULL, _("Verify Public Key"), tmp, tmp2, 2, verify, 3,
+			    _("Yes"), G_CALLBACK(silcgaim_verify_cb),
+			    _("No"), G_CALLBACK(silcgaim_verify_cb),
+			    _("View..."), G_CALLBACK(silcgaim_verify_details));
+}
+
+void silcgaim_verify_public_key(SilcClient client, SilcClientConnection conn,
+				const char *name, SilcSocketType conn_type,
+				unsigned char *pk, SilcUInt32 pk_len,
+				SilcSKEPKType pk_type,
+				SilcVerifyPublicKey completion, void *context)
+{
+	GaimConnection *gc = client->application;
+	int i;
+	char file[256], filename[256], filename2[256], *ipf, *hostf = NULL;
+	char *fingerprint, *babbleprint;
+	struct passwd *pw;
+	struct stat st;
+	char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER ||
+			 conn_type == SILC_SOCKET_TYPE_ROUTER) ?
+			"server" : "client");
+	PublicKeyVerify verify;
+
+	if (pk_type != SILC_SKE_PK_TYPE_SILC) {
+		gaim_notify_error(gc, _("Verify Public Key"),
+				  _("Unsupported public key type"), NULL);
+		if (completion)
+			completion(FALSE, context);
+		return;
+	}
+
+	pw = getpwuid(getuid());
+	if (!pw) {
+		if (completion)
+			completion(FALSE, context);
+		return;
+	}
+
+	memset(filename, 0, sizeof(filename));
+	memset(filename2, 0, sizeof(filename2));
+	memset(file, 0, sizeof(file));
+
+	if (conn_type == SILC_SOCKET_TYPE_SERVER ||
+	    conn_type == SILC_SOCKET_TYPE_ROUTER) {
+		if (!name) {
+			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
+				   conn->sock->ip, conn->sock->port);
+			g_snprintf(filename, sizeof(filename) - 1,
+				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
+				   silcgaim_silcdir(), entity, file);
+
+			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
+				   conn->sock->hostname, conn->sock->port);
+			g_snprintf(filename2, sizeof(filename2) - 1,
+				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
+				   silcgaim_silcdir(), entity, file);
+
+			ipf = filename;
+			hostf = filename2;
+		} else {
+			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
+				   name, conn->sock->port);
+			g_snprintf(filename, sizeof(filename) - 1,
+				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
+				   silcgaim_silcdir(), entity, file);
+
+			ipf = filename;
+		}
+	} else {
+		/* Replace all whitespaces with `_'. */
+		fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+		for (i = 0; i < strlen(fingerprint); i++)
+			if (fingerprint[i] == ' ')
+				fingerprint[i] = '_';
+
+		g_snprintf(file, sizeof(file) - 1, "%skey_%s.pub", entity, fingerprint);
+		g_snprintf(filename, sizeof(filename) - 1,
+			   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
+			   silcgaim_silcdir(), entity, file);
+		silc_free(fingerprint);
+
+		ipf = filename;
+	}
+
+	verify = silc_calloc(1, sizeof(*verify));
+	if (!verify)
+		return;
+	verify->client = client;
+	verify->conn = conn;
+	verify->filename = strdup(ipf);
+	verify->entity = strdup(entity);
+	verify->entity_name = (conn_type != SILC_SOCKET_TYPE_CLIENT ?
+			       (name ? strdup(name) : strdup(conn->sock->hostname))
+			       : NULL);
+	verify->pk = silc_memdup(pk, pk_len);
+	verify->pk_len = pk_len;
+	verify->pk_type = pk_type;
+	verify->completion = completion;
+	verify->context = context;
+	fingerprint = verify->fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+	babbleprint = verify->babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+
+	/* Check whether this key already exists */
+	if (stat(ipf, &st) < 0 && (!hostf || stat(hostf, &st) < 0)) {
+		/* Key does not exist, ask user to verify the key and save it */
+		silcgaim_verify_ask(name ? name : entity,
+				    fingerprint, babbleprint, verify);
+		return;
+	} else {
+		/* The key already exists, verify it. */
+		SilcPublicKey public_key;
+		unsigned char *encpk;
+		SilcUInt32 encpk_len;
+
+		/* Load the key file, try for both IP filename and hostname filename */
+		if (!silc_pkcs_load_public_key(ipf, &public_key,
+					       SILC_PKCS_FILE_PEM) &&
+		    !silc_pkcs_load_public_key(ipf, &public_key,
+					       SILC_PKCS_FILE_BIN) &&
+		    (!hostf || (!silc_pkcs_load_public_key(hostf, &public_key,
+							   SILC_PKCS_FILE_PEM) &&
+				!silc_pkcs_load_public_key(hostf, &public_key,
+							   SILC_PKCS_FILE_BIN)))) {
+			silcgaim_verify_ask(name ? name : entity,
+					    fingerprint, babbleprint, verify);
+			return;
+		}
+
+		/* Encode the key data */
+		encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
+		if (!encpk) {
+			silcgaim_verify_ask(name ? name : entity,
+					    fingerprint, babbleprint, verify);
+			return;
+		}
+
+		/* Compare the keys */
+		if (memcmp(encpk, pk, encpk_len)) {
+			/* Ask user to verify the key and save it */
+			verify->changed = TRUE;
+			silcgaim_verify_ask(name ? name : entity,
+					    fingerprint, babbleprint, verify);
+			return;
+		}
+
+		/* Local copy matched */
+		if (completion)
+			completion(TRUE, context);
+		silc_free(verify->filename);
+		silc_free(verify->entity);
+		silc_free(verify->entity_name);
+		silc_free(verify->pk);
+		silc_free(verify->fingerprint);
+		silc_free(verify->babbleprint);
+		silc_free(verify);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/silc.c	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,1091 @@
+/*
+
+  silcgaim.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"
+
+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, char **se, char **sw,
+		      char **nw, char **ne)
+{
+}
+
+static GList *
+silcgaim_away_states(GaimConnection *gc)
+{
+	GList *st = NULL;
+
+	st = g_list_append(st, _("Online"));
+	st = g_list_append(st, _("Hyper Active"));
+	st = g_list_append(st, _("Away"));
+	st = g_list_append(st, _("Busy"));
+	st = g_list_append(st, _("Indisposed"));
+	st = g_list_append(st, _("Wake Me Up"));
+
+	return st;
+}
+
+static void
+silcgaim_set_away(GaimConnection *gc, const char *state, const char *msg)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcUInt32 mode;
+	SilcBuffer idp;
+	unsigned char mb[4];
+
+	if (!state)
+		return;
+	if (!sg->conn)
+		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 Active")))
+		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, _("Wake Me Up")))
+		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, GaimInputCondition cond)
+{
+	GaimConnection *gc = data;
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn;
+	GaimAccount *account = sg->account;
+	SilcClientConnectionParams params;
+	const char *dfile;
+
+	if (source < 0) {
+		gaim_connection_error(gc, _("Connection failed"));
+		return;
+	}
+	if (!g_list_find(gaim_connections_get_all(), gc)) {
+		close(source);
+		g_source_remove(sg->scheduler);
+		silc_client_stop(sg->client);
+		silc_client_free(sg->client);
+		silc_free(sg);
+		return;
+	}
+
+	/* Get session detachment data, if available */
+	memset(&params, 0, sizeof(params));
+	dfile = silcgaim_session_file(gaim_account_get_username(sg->account));
+	params.detach_data = 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 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));
+	}
+
+	silc_free(params.detach_data);
+}
+
+static void
+silcgaim_login(GaimAccount *account)
+{
+	SilcGaim sg;
+	SilcClient client;
+	SilcClientParams params;
+	GaimConnection *gc;
+
+	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)) {
+		client->username = strdup(gaim_account_get_username(account));
+	} else {
+		client->username = silc_get_username();
+		gaim_account_set_username(account, client->username);
+	}
+	if (gaim_account_get_user_info(account)) {
+		client->realname = strdup(gaim_account_get_user_info(account));
+	} else {
+		client->realname = silc_get_real_name();
+		gaim_account_set_user_info(account, client->realname);
+	}
+	client->hostname = silc_net_localhost();
+
+	gaim_connection_set_display_name(gc, client->username);
+
+	/* Init SILC client */
+	if (!silc_client_init(client)) {
+		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)) {
+		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 */
+	if (!silc_load_key_pair(gaim_prefs_get_string("/plugins/prpl/silc/pubkey"),
+				gaim_prefs_get_string("/plugins/prpl/silc/privkey"),
+				"", &client->pkcs, &client->public_key,
+				&client->private_key)) {
+		gaim_connection_error(gc, ("Could not load SILC key pair"));
+		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(account,
+			       gaim_account_get_string(account, "server",
+						       "silc.silcnet.org"),
+			       gaim_account_get_int(account, "port", 706),
+			       silcgaim_login_connected, gc)) {
+		gaim_connection_error(gc, ("Unable to create connection"));
+		return;
+	}
+
+	/* Schedule SILC using Glib's event loop */
+	sg->scheduler = g_timeout_add(5, (GSourceFunc)silcgaim_scheduler, sg);
+}
+
+static int
+silcgaim_close_final(gpointer *context)
+{
+	SilcGaim sg = (SilcGaim)context;
+	silc_client_stop(sg->client);
+	silc_client_free(sg->client);
+	silc_free(sg);
+	return 0;
+}
+
+static void
+silcgaim_close_convos(GaimConversation *convo)
+{
+	if (convo)
+		gaim_conversation_destroy(convo);
+}
+
+static void
+silcgaim_close(GaimConnection *gc)
+{
+	SilcGaim sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	/* Close all conversations */
+	gaim_conversation_foreach(silcgaim_close_convos);
+
+	/* Send QUIT */
+	silc_client_command_call(sg->client, sg->conn, NULL,
+				 "QUIT", "Leaving", 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_prefs_set_string("/plugins/prpl/silc/vcard", val);
+		gaim_prefs_sync();
+		tmp = silc_file_readfile(val, &tmp_len);
+		if (tmp) {
+			tmp[tmp_len] = 0;
+			if (silc_vcard_decode(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);
+	}
+
+#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(GaimConnection *gc)
+{
+	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", _("Email"), 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_prefs_get_string("/plugins/prpl/silc/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(NULL, _("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(GaimConnection *gc)
+{
+	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(GaimConnection *gc)
+{
+	SilcGaim sg;
+
+	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;
+	}
+
+	gaim_notify_formatted(gc, "Message of the Day", "Message of the Day", NULL,
+			      sg->motd, NULL, NULL);
+}
+
+static GList *
+silcgaim_actions(GaimConnection *gc)
+{
+	struct proto_actions_menu *pam;
+	GList *list = NULL;
+
+	if (!gaim_account_get_bool(gc->account, "reject-attrs", FALSE)) {
+		pam = g_new0(struct proto_actions_menu, 1);
+		pam->label = _("Online Status");
+		pam->callback = silcgaim_attrs;
+		pam->gc = gc;
+		list = g_list_append(list, pam);
+	}
+
+	pam = g_new0(struct proto_actions_menu, 1);
+	pam->label = _("Detach From Server");
+	pam->callback = silcgaim_detach;
+	pam->gc = gc;
+	list = g_list_append(list, pam);
+
+	pam = g_new0(struct proto_actions_menu, 1);
+	pam->label = _("View Message of the Day");
+	pam->callback = silcgaim_view_motd;
+	pam->gc = gc;
+	list = g_list_append(list, pam);
+
+	return list;
+}
+
+
+/******************************* IM Routines *********************************/
+
+typedef struct {
+	char *nick;
+	unsigned char *message;
+	SilcUInt32 message_len;
+	SilcMessageFlags flags;
+} *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;
+
+	convo = gaim_find_conversation_with_account(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];
+	}
+
+	/* Send the message */
+	silc_client_send_private_message(client, conn, client_entry, im->flags,
+					 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 *msg,
+		 GaimConvImFlags flags)
+{
+	SilcGaim sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count, mflags;
+	char *nickname;
+	int ret;
+	gboolean sign = gaim_prefs_get_bool("/plugins/prpl/silc/sign_im");
+
+	if (!who || !msg)
+		return 0;
+
+	/* See if command */
+	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"));
+		return 0;
+	}
+
+	if (!silc_parse_userfqdn(who, &nickname, NULL))
+		return 0;
+
+	mflags = SILC_MESSAGE_FLAG_UTF8;
+	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)
+			return 0;
+		im->nick = g_strdup(who);
+		im->message = g_strdup(msg);
+		im->message_len = strlen(im->message);
+		im->flags = mflags;
+		silc_client_get_clients(client, conn, nickname, NULL,
+					silcgaim_send_im_resolved, im);
+		silc_free(nickname);
+		return 0;
+	}
+
+	/* Send private message directly */
+	ret = silc_client_send_private_message(client, conn, clients[0],
+					       mflags, (char *)msg,
+					       strlen(msg), TRUE);
+
+	silc_free(nickname);
+	silc_free(clients);
+	return ret;
+}
+
+
+/************************** Plugin Initialization ****************************/
+
+static GaimPluginPrefFrame *
+silcgaim_pref_frame(GaimPlugin *plugin)
+{
+	GaimPluginPrefFrame *frame;
+	GaimPluginPref *ppref;
+
+	frame = gaim_plugin_pref_frame_new();
+
+	ppref = gaim_plugin_pref_new_with_label(_("Instant Messages"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label(
+			    "/plugins/prpl/silc/sign_im",
+			    _("Digitally sign all IM messages"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label(
+			    "/plugins/prpl/silc/verify_im",
+			    _("Verify all IM message signatures"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_label(_("Channel Messages"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label(
+			    "/plugins/prpl/silc/sign_chat",
+			    _("Digitally sign all channel messages"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label(
+			    "/plugins/prpl/silc/verify_chat",
+			    _("Verify all channel message signatures"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_label(_("Default SILC Key Pair"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label(
+			    "/plugins/prpl/silc/pubkey",
+			    _("SILC Public Key"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label(
+			    "/plugins/prpl/silc/privkey",
+			    _("SILC Private Key"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	return frame;
+}
+
+static GaimPluginUiInfo prefs_info =
+{
+	silcgaim_pref_frame,
+};
+
+static GaimPluginProtocolInfo prpl_info =
+{
+	GAIM_PRPL_API_VERSION,
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
+	OPT_PROTO_PASSWORD_OPTIONAL,
+	NULL,
+	NULL,
+	silcgaim_list_icon,
+	silcgaim_list_emblems,
+	silcgaim_status_text,
+	silcgaim_tooltip_text,
+	silcgaim_away_states,
+	silcgaim_actions,
+	silcgaim_buddy_menu,
+	silcgaim_chat_info,
+	silcgaim_login,
+	silcgaim_close,
+	silcgaim_send_im,
+	NULL,
+	NULL,
+	silcgaim_get_info,
+	silcgaim_set_away,
+	NULL,
+	NULL,
+	NULL,
+	silcgaim_idle_set,
+	NULL,
+	silcgaim_add_buddy,
+	silcgaim_add_buddies,
+	silcgaim_remove_buddy,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	silcgaim_chat_join,
+	NULL,
+	silcgaim_chat_invite,
+	silcgaim_chat_leave,
+	NULL,
+	silcgaim_chat_send,
+	silcgaim_keepalive,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	silcgaim_chat_set_topic,
+	NULL,
+	silcgaim_roomlist_get_list,
+	silcgaim_roomlist_cancel,
+	NULL,
+	silcgaim_chat_menu
+};
+
+static GaimPluginInfo info =
+{
+	GAIM_PLUGIN_API_VERSION,                          /**< api_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"),
+	N_("Pekka Riikonen"),                             /**< author         */
+	N_("http://silcnet.org/"),                        /**< homepage       */
+
+	NULL,                                             /**< load           */
+	NULL,                                             /**< unload         */
+	NULL,                                             /**< destroy        */
+
+	NULL,                                             /**< ui_info        */
+	&prpl_info,                                       /**< extra_info     */
+	&prefs_info                                       /**< prefs_info     */
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+	GaimAccountOption *option;
+	char tmp[256];
+
+	silc_plugin = plugin;
+
+	/* 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);
+
+	option = gaim_account_option_bool_new(_("Public key authentication"),
+					      "pubkey-auth", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+#if 0   /* XXX Public key auth interface with explicit key pair is
+	   broken in SILC Toolkit */
+	g_snprintf(tmp, sizeof(tmp), _("%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/private_key.prv"), silcgaim_silcdir());
+	option = gaim_account_option_string_new(_("Private Key File"),
+						"public-key", tmp);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+#endif
+
+	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);
+
+	/* Preferences */
+	gaim_prefs_add_none("/plugins/prpl/silc");
+	gaim_prefs_add_bool("/plugins/prpl/silc/sign_im", FALSE);
+	gaim_prefs_add_bool("/plugins/prpl/silc/verify_im", FALSE);
+	gaim_prefs_add_bool("/plugins/prpl/silc/sign_chat", FALSE);
+	gaim_prefs_add_bool("/plugins/prpl/silc/verify_chat", FALSE);
+	g_snprintf(tmp, sizeof(tmp), _("%s/public_key.pub"), silcgaim_silcdir());
+	gaim_prefs_add_string("/plugins/prpl/silc/pubkey", tmp);
+	g_snprintf(tmp, sizeof(tmp), _("%s/private_key.prv"), silcgaim_silcdir());
+	gaim_prefs_add_string("/plugins/prpl/silc/privkey", tmp);
+	gaim_prefs_add_string("/plugins/prpl/silc/vcard", "");
+}
+
+GAIM_INIT_PLUGIN(silc, init_plugin, info);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/silcgaim.h	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,131 @@
+/*
+
+  silcgaim.h
+
+  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.
+
+*/
+
+#ifndef SILCGAIM_H
+#define SILCGAIM_H
+
+/* Gaim includes */
+#include "internal.h"
+#include "account.h"
+#include "accountopt.h"
+#include "debug.h"
+#include "multi.h"
+#include "notify.h"
+#include "prpl.h"
+#include "request.h"
+#include "server.h"
+#include "util.h"
+#include "roomlist.h"
+#include "ft.h"
+
+/* Default public and private key file names */
+#define SILCGAIM_PUBLIC_KEY_NAME "public_key.pub"
+#define SILCGAIM_PRIVATE_KEY_NAME "private_key.prv"
+
+/* Default settings for creating key pair */
+#define SILCGAIM_DEF_PKCS "rsa"
+#define SILCGAIM_DEF_PKCS_LEN 2048
+
+#define SILCGAIM_PRVGRP 0x001fffff
+
+typedef struct {
+	unsigned long id;
+	const char *channel;
+	unsigned long chid;
+	const char *parentch;
+	SilcChannelPrivateKey key;
+} *SilcGaimPrvgrp;
+
+/* The SILC Gaim plugin context */
+typedef struct SilcGaimStruct {
+	SilcClient client;
+	SilcClientConnection conn;
+
+	guint scheduler;
+	GaimConnection *gc;
+	GaimAccount *account;
+	unsigned long channel_ids;
+	GList *grps;
+
+	char *motd;
+	GaimRoomlist *roomlist;
+
+	unsigned int detaching            : 1;
+	unsigned int resuming             : 1;
+	unsigned int roomlist_canceled    : 1;
+	unsigned int chpk                 : 1;
+} *SilcGaim;
+
+
+gboolean silcgaim_check_silc_dir(GaimConnection *gc);
+void silcgaim_chat_join_done(SilcClient client,
+			     SilcClientConnection conn,
+			     SilcClientEntry *clients,
+			     SilcUInt32 clients_count,
+			     void *context);
+const char *silcgaim_silcdir(void);
+const char *silcgaim_session_file(const char *account);
+void silcgaim_verify_public_key(SilcClient client, SilcClientConnection conn,
+				const char *name, SilcSocketType conn_type,
+				unsigned char *pk, SilcUInt32 pk_len,
+				SilcSKEPKType pk_type,
+				SilcVerifyPublicKey completion, void *context);
+GList *silcgaim_buddy_menu(GaimConnection *gc, const char *name);
+void silcgaim_add_buddy(GaimConnection *gc, const char *name, GaimGroup *grp);
+void silcgaim_add_buddies(GaimConnection *gc, GList *buddies);
+void silcgaim_remove_buddy(GaimConnection *gc, const char *name,
+			   const char *group);
+void silcgaim_buddy_keyagr_request(SilcClient client,
+				   SilcClientConnection conn,
+				   SilcClientEntry client_entry,
+				   const char *hostname, SilcUInt16 port);
+void silcgaim_idle_set(GaimConnection *gc, int idle);
+char *silcgaim_tooltip_text(GaimBuddy *b);
+char *silcgaim_status_text(GaimBuddy *b);
+gboolean silcgaim_ip_is_private(const char *ip);
+void silcgaim_ftp_send_file(GaimConnection *gc, const char *name);
+void silcgaim_ftp_request(SilcClient client, SilcClientConnection conn,
+			  SilcClientEntry client_entry, SilcUInt32 session_id,
+			  const char *hostname, SilcUInt16 port);
+void silcgaim_show_public_key(SilcGaim sg,
+			      const char *name, SilcPublicKey public_key,
+			      GCallback callback, void *context);
+void silcgaim_get_info(GaimConnection *gc, const char *who);
+SilcAttributePayload
+silcgaim_get_attr(SilcDList attrs, SilcAttribute attribute);
+void silcgaim_get_umode_string(SilcUInt32 mode, char *buf,
+			       SilcUInt32 buf_size);
+void silcgaim_get_chmode_string(SilcUInt32 mode, char *buf,
+				SilcUInt32 buf_size);
+void silcgaim_get_chumode_string(SilcUInt32 mode, char *buf,
+				 SilcUInt32 buf_size);
+GList *silcgaim_chat_info(GaimConnection *gc);
+GList *silcgaim_chat_menu(GaimConnection *gc, GHashTable *components);
+void silcgaim_chat_join(GaimConnection *gc, GHashTable *data);
+void silcgaim_chat_invite(GaimConnection *gc, int id, const char *msg,
+			  const char *name);
+void silcgaim_chat_leave(GaimConnection *gc, int id);
+int silcgaim_chat_send(GaimConnection *gc, int id, const char *msg);
+void silcgaim_chat_set_topic(GaimConnection *gc, int id, const char *topic);
+GaimRoomlist *silcgaim_roomlist_get_list(GaimConnection *gc);
+void silcgaim_roomlist_cancel(GaimRoomlist *list);
+void silcgaim_chat_chauth_show(SilcGaim sg, SilcChannelEntry channel,
+			       SilcBuffer channel_pubkeys);
+
+#endif /* SILCGAIM_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/silc/util.c	Sat May 01 19:34:44 2004 +0000
@@ -0,0 +1,404 @@
+/*
+
+  silcgaim_util.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"
+
+/**************************** Utility Routines *******************************/
+
+static char str[256], str2[256];
+
+const char *silcgaim_silcdir(void)
+{
+	const char *hd = gaim_home_dir();
+	memset(str, 0, sizeof(str));
+	g_snprintf(str, sizeof(str) - 1, "%s" G_DIR_SEPARATOR_S ".silc", hd ? hd : "/tmp");
+	return (const char *)str;
+}
+
+const char *silcgaim_session_file(const char *account)
+{
+	memset(str2, 0, sizeof(str2));
+	g_snprintf(str2, sizeof(str2) - 1, "%s" G_DIR_SEPARATOR_S "%s_session",
+		   silcgaim_silcdir(), account);
+	return (const char *)str2;
+}
+
+gboolean silcgaim_ip_is_private(const char *ip)
+{
+	if (silc_net_is_ip4(ip)) {
+		if (!strncmp(ip, "10.", 3)) {
+			return TRUE;
+		} else if (!strncmp(ip, "172.", 4) && strlen(ip) > 6) {
+			char tmp[3];
+			memset(tmp, 0, sizeof(tmp));
+			strncpy(tmp, ip + 4, 2);
+			int s = atoi(tmp);
+			if (s >= 16 && s <= 31)
+				return TRUE;
+		} else if (!strncmp(ip, "192.168.", 8)) {
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+/* This checks stats for various SILC files and directories. First it
+   checks if ~/.silc directory exist and is owned by the correct user. If
+   it doesn't exist, it will create the directory. After that it checks if
+   user's Public and Private key files exists and creates them if needed. */
+
+gboolean silcgaim_check_silc_dir(GaimConnection *gc)
+{
+	char filename[256], file_public_key[256], file_private_key[256];
+	char servfilename[256], clientfilename[256], friendsfilename[256];
+	struct stat st;
+	struct passwd *pw;
+
+	pw = getpwuid(getuid());
+	if (!pw) {
+		fprintf(stderr, "silc: %s\n", strerror(errno));
+		return FALSE;
+	}
+
+	g_snprintf(filename, sizeof(filename) - 1, "%s" G_DIR_SEPARATOR_S, silcgaim_silcdir());
+	g_snprintf(servfilename, sizeof(servfilename) - 1, "%s" G_DIR_SEPARATOR_S "serverkeys",
+		   silcgaim_silcdir());
+	g_snprintf(clientfilename, sizeof(clientfilename) - 1, "%s" G_DIR_SEPARATOR_S "clientkeys",
+		   silcgaim_silcdir());
+	g_snprintf(friendsfilename, sizeof(friendsfilename) - 1, "%s" G_DIR_SEPARATOR_S "friends",
+		   silcgaim_silcdir());
+
+	/*
+	 * Check ~/.silc directory
+	 */
+	if ((stat(filename, &st)) == -1) {
+		/* If dir doesn't exist */
+		if (errno == ENOENT) {
+			if (pw->pw_uid == geteuid()) {
+				if ((mkdir(filename, 0755)) == -1) {
+					fprintf(stderr, "Couldn't create `%s' directory\n", filename);
+					return FALSE;
+				}
+			} else {
+				fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n",
+					filename);
+				return FALSE;
+			}
+		} else {
+			fprintf(stderr, "%s\n", strerror(errno));
+			return FALSE;
+		}
+	} else {
+		/* Check the owner of the dir */
+		if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+			fprintf(stderr, "You don't seem to own `%s' directory\n",
+				filename);
+			return FALSE;
+		}
+	}
+
+	/*
+	 * Check ~./silc/serverkeys directory
+	 */
+	if ((stat(servfilename, &st)) == -1) {
+		/* If dir doesn't exist */
+		if (errno == ENOENT) {
+			if (pw->pw_uid == geteuid()) {
+				if ((mkdir(servfilename, 0755)) == -1) {
+					fprintf(stderr, "Couldn't create `%s' directory\n", servfilename);
+					return FALSE;
+				}
+			} else {
+				fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n",
+					servfilename);
+				return FALSE;
+			}
+		} else {
+			fprintf(stderr, "%s\n", strerror(errno));
+			return FALSE;
+		}
+	}
+
+	/*
+	 * Check ~./silc/clientkeys directory
+	 */
+	if ((stat(clientfilename, &st)) == -1) {
+		/* If dir doesn't exist */
+		if (errno == ENOENT) {
+			if (pw->pw_uid == geteuid()) {
+				if ((mkdir(clientfilename, 0755)) == -1) {
+					fprintf(stderr, "Couldn't create `%s' directory\n", clientfilename);
+					return FALSE;
+				}
+			} else {
+				fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n",
+					clientfilename);
+				return FALSE;
+			}
+		} else {
+			fprintf(stderr, "%s\n", strerror(errno));
+			return FALSE;
+		}
+	}
+
+	/*
+	 * Check ~./silc/friends directory
+	 */
+	if ((stat(friendsfilename, &st)) == -1) {
+		/* If dir doesn't exist */
+		if (errno == ENOENT) {
+			if (pw->pw_uid == geteuid()) {
+				if ((mkdir(friendsfilename, 0755)) == -1) {
+					fprintf(stderr, "Couldn't create `%s' directory\n", friendsfilename);
+					return FALSE;
+				}
+			} else {
+				fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n",
+					friendsfilename);
+				return FALSE;
+			}
+		} else {
+			fprintf(stderr, "%s\n", strerror(errno));
+			return FALSE;
+		}
+	}
+
+	/*
+	 * Check Public and Private keys
+	 */
+	g_snprintf(file_public_key, sizeof(file_public_key) - 1, "%s",
+		   gaim_prefs_get_string("/plugins/prpl/silc/pubkey"));
+	g_snprintf(file_private_key, sizeof(file_public_key) - 1, "%s",
+		   gaim_prefs_get_string("/plugins/prpl/silc/privkey"));
+
+	if ((stat(file_public_key, &st)) == -1) {
+		/* If file doesn't exist */
+		if (errno == ENOENT) {
+			gaim_connection_update_progress(gc, _("Creating SILC key pair..."), 1, 5);
+			silc_create_key_pair(SILCGAIM_DEF_PKCS,
+					     SILCGAIM_DEF_PKCS_LEN,
+					     file_public_key, file_private_key, NULL,
+					     "", NULL, NULL, NULL, FALSE);
+		} else {
+			fprintf(stderr, "%s\n", strerror(errno));
+			return FALSE;
+		}
+	}
+
+	/* Check the owner of the public key */
+	if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+		fprintf(stderr, "You don't seem to own your public key!?\n");
+		return FALSE;
+	}
+
+	if ((stat(file_private_key, &st)) == -1) {
+		/* If file doesn't exist */
+		if (errno == ENOENT) {
+			gaim_connection_update_progress(gc, _("Creating SILC key pair..."), 1, 5);
+			silc_create_key_pair(SILCGAIM_DEF_PKCS,
+					     SILCGAIM_DEF_PKCS_LEN,
+					     file_public_key, file_private_key, NULL,
+					     "", NULL, NULL, NULL, FALSE);
+		} else {
+			fprintf(stderr, "%s\n", strerror(errno));
+			return FALSE;
+		}
+	}
+
+	/* Check the owner of the private key */
+	if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+		fprintf(stderr, "You don't seem to own your private key!?\n");
+		return FALSE;
+	}
+
+	/* Check the permissions for the private key */
+	if ((st.st_mode & 0777) != 0600) {
+		fprintf(stderr, "Wrong permissions in your private key file `%s'!\n"
+			"Trying to change them ... ", file_private_key);
+		if ((chmod(file_private_key, 0600)) == -1) {
+			fprintf(stderr,
+				"Failed to change permissions for private key file!\n"
+				"Permissions for your private key file must be 0600.\n");
+			return FALSE;
+		}
+		fprintf(stderr, "Done.\n\n");
+	}
+
+	return TRUE;
+}
+
+void silcgaim_show_public_key(SilcGaim sg,
+			      const char *name, SilcPublicKey public_key,
+			      GCallback callback, void *context)
+{
+	SilcPublicKeyIdentifier ident;
+	SilcPKCS pkcs;
+	char *fingerprint, *babbleprint;
+	unsigned char *pk;
+	SilcUInt32 pk_len, key_len = 0;
+	GString *s;
+	char *buf;
+
+	ident = silc_pkcs_decode_identifier(public_key->identifier);
+	if (!ident)
+		return;
+
+	pk = silc_pkcs_public_key_encode(public_key, &pk_len);
+	fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+	babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+
+	if (silc_pkcs_alloc(public_key->name, &pkcs)) {
+		key_len = silc_pkcs_public_key_set(pkcs, public_key);
+		silc_pkcs_free(pkcs);
+	}
+
+	s = g_string_new("");
+	if (ident->realname)
+		g_string_append_printf(s, "Real Name: \t%s\n", ident->realname);
+	if (ident->username)
+		g_string_append_printf(s, "User Name: \t%s\n", ident->username);
+	if (ident->email)
+		g_string_append_printf(s, "EMail: \t\t%s\n", ident->email);
+	if (ident->host)
+		g_string_append_printf(s, "Host Name: \t%s\n", ident->host);
+	if (ident->org)
+		g_string_append_printf(s, "Organization: \t%s\n", ident->org);
+	if (ident->country)
+		g_string_append_printf(s, "Country: \t%s\n", ident->country);
+	g_string_append_printf(s, "Algorithm: \t\t%s\n", public_key->name);
+	g_string_append_printf(s, "Key Length: \t%d bits\n", (int)key_len);
+	g_string_append_printf(s, "\n");
+	g_string_append_printf(s, "Public Key Fingerprint:\n%s\n\n", fingerprint);
+	g_string_append_printf(s, "Public Key Babbleprint:\n%s", babbleprint);
+
+	buf = g_string_free(s, FALSE);
+
+	gaim_request_action(NULL, _("Public Key Information"),
+			    _("Public Key Information"),
+			    buf, 0, context, 1,
+			    _("Close"), callback);
+
+	g_free(buf);
+	silc_free(fingerprint);
+	silc_free(babbleprint);
+	silc_free(pk);
+	silc_pkcs_free_identifier(ident);
+}
+
+SilcAttributePayload
+silcgaim_get_attr(SilcDList attrs, SilcAttribute attribute)
+{
+	SilcAttributePayload attr = NULL;
+
+	if (!attrs)
+		return NULL;
+
+	silc_dlist_start(attrs);
+	while ((attr = silc_dlist_get(attrs)) != SILC_LIST_END)
+		if (attribute == silc_attribute_get_attribute(attr))
+			break;
+
+	return attr;
+}
+
+void silcgaim_get_umode_string(SilcUInt32 mode, char *buf,
+			       SilcUInt32 buf_size)
+{
+	memset(buf, 0, buf_size);
+	if ((mode & SILC_UMODE_SERVER_OPERATOR) ||
+	    (mode & SILC_UMODE_ROUTER_OPERATOR)) {
+		strcat(buf, (mode & SILC_UMODE_SERVER_OPERATOR) ?
+		       "[server operator] " :
+		       (mode & SILC_UMODE_ROUTER_OPERATOR) ?
+		       "[SILC operator] " : "[unknown mode] ");
+	}
+	if (mode & SILC_UMODE_GONE)
+		strcat(buf, "[away] ");
+	if (mode & SILC_UMODE_INDISPOSED)
+		strcat(buf, "[indisposed] ");
+	if (mode & SILC_UMODE_BUSY)
+		strcat(buf, "[busy] ");
+	if (mode & SILC_UMODE_PAGE)
+		strcat(buf, "[wake me up] ");
+	if (mode & SILC_UMODE_HYPER)
+		strcat(buf, "[hyperactive] ");
+	if (mode & SILC_UMODE_ROBOT)
+		strcat(buf, "[robot] ");
+	if (mode & SILC_UMODE_ANONYMOUS)
+		strcat(buf, "[anonymous] ");
+	if (mode & SILC_UMODE_BLOCK_PRIVMSG)
+		strcat(buf, "[blocks private messages] ");
+	if (mode & SILC_UMODE_DETACHED)
+		strcat(buf, "[detached] ");
+	if (mode & SILC_UMODE_REJECT_WATCHING)
+		strcat(buf, "[rejects watching] ");
+	if (mode & SILC_UMODE_BLOCK_INVITE)
+		strcat(buf, "[blocks invites] ");
+}
+
+void silcgaim_get_chmode_string(SilcUInt32 mode, char *buf,
+				SilcUInt32 buf_size)
+{
+	memset(buf, 0, buf_size);
+	if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)
+		strcat(buf, "[permanent] ");
+	if (mode & SILC_CHANNEL_MODE_PRIVATE)
+		strcat(buf, "[private] ");
+	if (mode & SILC_CHANNEL_MODE_SECRET)
+		strcat(buf, "[secret] ");
+	if (mode & SILC_CHANNEL_MODE_SECRET)
+		strcat(buf, "[secret] ");
+	if (mode & SILC_CHANNEL_MODE_PRIVKEY)
+		strcat(buf, "[private key] ");
+	if (mode & SILC_CHANNEL_MODE_INVITE)
+		strcat(buf, "[invite only] ");
+	if (mode & SILC_CHANNEL_MODE_TOPIC)
+		strcat(buf, "[topic restricted] ");
+	if (mode & SILC_CHANNEL_MODE_ULIMIT)
+		strcat(buf, "[user count limit] ");
+	if (mode & SILC_CHANNEL_MODE_PASSPHRASE)
+		strcat(buf, "[passphrase auth] ");
+	if (mode & SILC_CHANNEL_MODE_CHANNEL_AUTH)
+		strcat(buf, "[public key auth] ");
+	if (mode & SILC_CHANNEL_MODE_SILENCE_USERS)
+		strcat(buf, "[users silenced] ");
+	if (mode & SILC_CHANNEL_MODE_SILENCE_OPERS)
+		strcat(buf, "[operators silenced] ");
+}
+
+void silcgaim_get_chumode_string(SilcUInt32 mode, char *buf,
+				 SilcUInt32 buf_size)
+{
+	memset(buf, 0, buf_size);
+	if (mode & SILC_CHANNEL_UMODE_CHANFO)
+		strcat(buf, "[founder] ");
+	if (mode & SILC_CHANNEL_UMODE_CHANOP)
+		strcat(buf, "[operator] ");
+	if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES)
+		strcat(buf, "[blocks messages] ");
+	if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES_USERS)
+		strcat(buf, "[blocks user messages] ");
+	if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES_ROBOTS)
+		strcat(buf, "[blocks robot messages] ");
+	if (mode & SILC_CHANNEL_UMODE_QUIET)
+		strcat(buf, "[quieted] ");
+}