summaryrefslogtreecommitdiff
path: root/libpurple/protocols/silc
diff options
context:
space:
mode:
Diffstat (limited to 'libpurple/protocols/silc')
-rw-r--r--libpurple/protocols/silc/Makefile.am35
-rw-r--r--libpurple/protocols/silc/Makefile.mingw91
-rw-r--r--libpurple/protocols/silc/README31
-rw-r--r--libpurple/protocols/silc/TODO14
-rw-r--r--libpurple/protocols/silc/buddy.c1739
-rw-r--r--libpurple/protocols/silc/chat.c1451
-rw-r--r--libpurple/protocols/silc/ft.c412
-rw-r--r--libpurple/protocols/silc/ops.c2065
-rw-r--r--libpurple/protocols/silc/pk.c272
-rw-r--r--libpurple/protocols/silc/silc.c1920
-rw-r--r--libpurple/protocols/silc/silcgaim.h173
-rw-r--r--libpurple/protocols/silc/util.c773
-rw-r--r--libpurple/protocols/silc/wb.c516
-rw-r--r--libpurple/protocols/silc/wb.h49
14 files changed, 9541 insertions, 0 deletions
diff --git a/libpurple/protocols/silc/Makefile.am b/libpurple/protocols/silc/Makefile.am
new file mode 100644
index 0000000000..8a4755123c
--- /dev/null
+++ b/libpurple/protocols/silc/Makefile.am
@@ -0,0 +1,35 @@
+EXTRA_DIST = README TODO Makefile.mingw
+
+pkgdir = $(libdir)/gaim
+
+SILCSOURCES = silc.c silcgaim.h buddy.c chat.c ft.c ops.c pk.c util.c wb.c wb.h
+
+AM_CFLAGS = $(st)
+
+libsilcgaim_la_LDFLAGS = -module -avoid-version
+
+if STATIC_SILC
+
+st = -DGAIM_STATIC_PRPL $(SILC_CFLAGS)
+noinst_LIBRARIES = libsilcgaim.a
+pkg_LTLIBRARIES =
+
+libsilcgaim_a_SOURCES = $(SILCSOURCES)
+libsilcgaim_a_CFLAGS = $(AM_CFLAGS)
+libsilcgaim_a_LIBADD = $(SILC_LIBS)
+
+else
+
+st = $(SILC_CFLAGS)
+pkg_LTLIBRARIES = libsilcgaim.la
+noinst_LIBRARIES =
+
+libsilcgaim_la_SOURCES = $(SILCSOURCES)
+libsilcgaim_la_LIBADD = $(GLIB_LIBS) $(SILC_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/libgaim \
+ $(GLIB_CFLAGS) \
+ $(DEBUG_CFLAGS)
diff --git a/libpurple/protocols/silc/Makefile.mingw b/libpurple/protocols/silc/Makefile.mingw
new file mode 100644
index 0000000000..42fcc55025
--- /dev/null
+++ b/libpurple/protocols/silc/Makefile.mingw
@@ -0,0 +1,91 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libsilc protocol plugin
+#
+
+GAIM_TOP := ../../..
+include $(GAIM_TOP)/libgaim/win32/global.mak
+
+TARGET = libsilc
+NEEDED_DLLS = $(SILC_TOOLKIT)/lib/silc.dll \
+ $(SILC_TOOLKIT)/lib/silcclient.dll
+TYPE = PLUGIN
+
+# Static or Plugin...
+ifeq ($(TYPE),STATIC)
+ DEFINES += -DSTATIC
+ DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+ DLL_INSTALL_DIR = $(GAIM_INSTALL_PLUGINS_DIR)
+endif
+endif
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += -I. \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(GAIM_LIB_TOP) \
+ -I$(GAIM_LIB_TOP)/win32 \
+ -I$(GAIM_TOP) \
+ -I$(SILC_TOOLKIT)/include
+
+LIB_PATHS = -L$(GTK_TOP)/lib \
+ -L$(GAIM_LIB_TOP) \
+ -L$(SILC_TOOLKIT)/lib
+
+##
+## SOURCES, OBJECTS
+##
+C_SRC = silc.c \
+ buddy.c \
+ chat.c \
+ ft.c \
+ ops.c \
+ pk.c \
+ util.c \
+ wb.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = \
+ -lglib-2.0 \
+ -lws2_32 \
+ -lintl \
+ -lgaim \
+ -lsilc \
+ -lsilcclient
+
+include $(GAIM_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: all $(DLL_INSTALL_DIR) $(GAIM_INSTALL_DIR)
+ cp $(TARGET).dll $(DLL_INSTALL_DIR)
+ cp $(NEEDED_DLLS) $(GAIM_INSTALL_DIR)
+
+$(OBJECTS): $(GAIM_CONFIG_H)
+
+$(TARGET).dll: $(GAIM_LIBGAIM_DLL).a $(OBJECTS)
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x64000000 -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+ rm -f $(OBJECTS)
+ rm -f $(TARGET).dll
+
+include $(GAIM_COMMON_TARGETS)
diff --git a/libpurple/protocols/silc/README b/libpurple/protocols/silc/README
new file mode 100644
index 0000000000..c29087c6ac
--- /dev/null
+++ b/libpurple/protocols/silc/README
@@ -0,0 +1,31 @@
+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
+ 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 simply as:
+
+ ./configure
+
+if pkg-config is installed in your system. If it is isn't it's done as:
+
+ ./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.
diff --git a/libpurple/protocols/silc/TODO b/libpurple/protocols/silc/TODO
new file mode 100644
index 0000000000..b4b6c9ac5c
--- /dev/null
+++ b/libpurple/protocols/silc/TODO
@@ -0,0 +1,14 @@
+Features TODO (maybe)
+=====================
+
+Sending images
+ - Sending images to channel too, if Gaim allows it.
+
+Preferences
+ - Add joined channels to buddy list automatically (during
+ session)
+ - Add joined channels to buddy list automatically permanently
+
+Buddy icon
+ - After SILC Toolkit 1.0.2 buddy icon support can be added
+ (SILC_ATTERIBUTE_USER_ICON).
diff --git a/libpurple/protocols/silc/buddy.c b/libpurple/protocols/silc/buddy.c
new file mode 100644
index 0000000000..f9c208a6b1
--- /dev/null
+++ b/libpurple/protocols/silc/buddy.c
@@ -0,0 +1,1739 @@
+/*
+
+ silcgaim_buddy.c
+
+ Author: Pekka Riikonen <priikone@silcnet.org>
+
+ Copyright (C) 2004 Pekka Riikonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcgaim.h"
+#include "wb.h"
+
+/***************************** Key Agreement *********************************/
+
+static void
+silcgaim_buddy_keyagr(GaimBlistNode *node, gpointer data);
+
+static void
+silcgaim_buddy_keyagr_do(GaimConnection *gc, const char *name,
+ gboolean force_local);
+
+typedef struct {
+ char *nick;
+ GaimConnection *gc;
+} *SilcGaimResolve;
+
+static void
+silcgaim_buddy_keyagr_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count,
+ void *context)
+{
+ GaimConnection *gc = client->application;
+ SilcGaimResolve r = context;
+ char tmp[256];
+
+ if (!clients) {
+ g_snprintf(tmp, sizeof(tmp),
+ _("User %s is not present in the network"), r->nick);
+ gaim_notify_error(gc, _("Key Agreement"),
+ _("Cannot perform the key agreement"), tmp);
+ silc_free(r->nick);
+ silc_free(r);
+ return;
+ }
+
+ silcgaim_buddy_keyagr_do(gc, r->nick, FALSE);
+ silc_free(r->nick);
+ silc_free(r);
+}
+
+typedef struct {
+ gboolean responder;
+} *SilcGaimKeyAgr;
+
+static void
+silcgaim_buddy_keyagr_cb(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry,
+ SilcKeyAgreementStatus status,
+ SilcSKEKeyMaterial *key,
+ void *context)
+{
+ GaimConnection *gc = client->application;
+ SilcGaim sg = gc->proto_data;
+ SilcGaimKeyAgr a = context;
+
+ if (!sg->conn)
+ return;
+
+ switch (status) {
+ case SILC_KEY_AGREEMENT_OK:
+ {
+ GaimConversation *convo;
+ char tmp[128];
+
+ /* Set the private key for this client */
+ silc_client_del_private_message_key(client, conn, client_entry);
+ silc_client_add_private_message_key_ske(client, conn, client_entry,
+ NULL, NULL, key, a->responder);
+ silc_ske_free_key_material(key);
+
+
+ /* Open IM window */
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,
+ client_entry->nickname, sg->account);
+ if (convo) {
+ /* we don't have windows in the core anymore...but we may want to
+ * provide some method for asking the UI to show the window
+ gaim_conv_window_show(gaim_conversation_get_window(convo));
+ */
+ } else {
+ convo = gaim_conversation_new(GAIM_CONV_TYPE_IM, sg->account,
+ client_entry->nickname);
+ }
+ g_snprintf(tmp, sizeof(tmp), "%s [private key]", client_entry->nickname);
+ gaim_conversation_set_title(convo, tmp);
+ }
+ break;
+
+ case SILC_KEY_AGREEMENT_ERROR:
+ gaim_notify_error(gc, _("Key Agreement"),
+ _("Error occurred during key agreement"), NULL);
+ break;
+
+ case SILC_KEY_AGREEMENT_FAILURE:
+ gaim_notify_error(gc, _("Key Agreement"), _("Key Agreement failed"), NULL);
+ break;
+
+ case SILC_KEY_AGREEMENT_TIMEOUT:
+ gaim_notify_error(gc, _("Key Agreement"),
+ _("Timeout during key agreement"), NULL);
+ break;
+
+ case SILC_KEY_AGREEMENT_ABORTED:
+ gaim_notify_error(gc, _("Key Agreement"),
+ _("Key agreement was aborted"), NULL);
+ break;
+
+ case SILC_KEY_AGREEMENT_ALREADY_STARTED:
+ gaim_notify_error(gc, _("Key Agreement"),
+ _("Key agreement is already started"), NULL);
+ break;
+
+ case SILC_KEY_AGREEMENT_SELF_DENIED:
+ gaim_notify_error(gc, _("Key Agreement"),
+ _("Key agreement cannot be started with yourself"),
+ NULL);
+ break;
+
+ default:
+ break;
+ }
+
+ silc_free(a);
+}
+
+static void
+silcgaim_buddy_keyagr_do(GaimConnection *gc, const char *name,
+ gboolean force_local)
+{
+ SilcGaim sg = gc->proto_data;
+ SilcClientEntry *clients;
+ SilcUInt32 clients_count;
+ char *local_ip = NULL, *remote_ip = NULL;
+ gboolean local = TRUE;
+ char *nickname;
+ SilcGaimKeyAgr a;
+
+ if (!sg->conn || !name)
+ return;
+
+ if (!silc_parse_userfqdn(name, &nickname, NULL))
+ return;
+
+ /* Find client entry */
+ clients = silc_client_get_clients_local(sg->client, sg->conn, nickname, name,
+ &clients_count);
+ if (!clients) {
+ /* Resolve unknown user */
+ SilcGaimResolve r = silc_calloc(1, sizeof(*r));
+ if (!r)
+ return;
+ r->nick = g_strdup(name);
+ r->gc = gc;
+ silc_client_get_clients(sg->client, sg->conn, nickname, NULL,
+ silcgaim_buddy_keyagr_resolved, r);
+ silc_free(nickname);
+ return;
+ }
+
+ /* Resolve the local IP from the outgoing socket connection. We resolve
+ it to check whether we have a private range IP address or public IP
+ address. If we have public then we will assume that we are not behind
+ NAT and will provide automatically the point of connection to the
+ agreement. If we have private range address we assume that we are
+ behind NAT and we let the responder provide the point of connection.
+
+ The algorithm also checks the remote IP address of server connection.
+ If it is private range address and we have private range address we
+ assume that we are chatting in LAN and will provide the point of
+ connection.
+
+ Naturally this algorithm does not always get things right. */
+
+ if (silc_net_check_local_by_sock(sg->conn->sock->sock, NULL, &local_ip)) {
+ /* Check if the IP is private */
+ if (!force_local && silcgaim_ip_is_private(local_ip)) {
+ local = FALSE;
+
+ /* Local IP is private, resolve the remote server IP to see whether
+ we are talking to Internet or just on LAN. */
+ if (silc_net_check_host_by_sock(sg->conn->sock->sock, NULL,
+ &remote_ip))
+ if (silcgaim_ip_is_private(remote_ip))
+ /* We assume we are in LAN. Let's provide
+ the connection point. */
+ local = TRUE;
+ }
+ }
+
+ if (force_local)
+ local = TRUE;
+
+ if (local && !local_ip)
+ local_ip = silc_net_localip();
+
+ a = silc_calloc(1, sizeof(*a));
+ if (!a)
+ return;
+ a->responder = local;
+
+ /* Send the key agreement request */
+ silc_client_send_key_agreement(sg->client, sg->conn, clients[0],
+ local ? local_ip : NULL, NULL, 0, 60,
+ silcgaim_buddy_keyagr_cb, a);
+
+ silc_free(local_ip);
+ silc_free(remote_ip);
+ silc_free(clients);
+}
+
+typedef struct {
+ SilcClient client;
+ SilcClientConnection conn;
+ SilcClientID client_id;
+ char *hostname;
+ SilcUInt16 port;
+} *SilcGaimKeyAgrAsk;
+
+static void
+silcgaim_buddy_keyagr_request_cb(SilcGaimKeyAgrAsk a, gint id)
+{
+ SilcGaimKeyAgr ai;
+ SilcClientEntry client_entry;
+
+ if (id != 1)
+ goto out;
+
+ /* Get the client entry. */
+ client_entry = silc_client_get_client_by_id(a->client, a->conn,
+ &a->client_id);
+ if (!client_entry) {
+ gaim_notify_error(a->client->application, _("Key Agreement"),
+ _("The remote user is not present in the network any more"),
+ NULL);
+ goto out;
+ }
+
+ /* If the hostname was provided by the requestor perform the key agreement
+ now. Otherwise, we will send him a request to connect to us. */
+ if (a->hostname) {
+ ai = silc_calloc(1, sizeof(*ai));
+ if (!ai)
+ goto out;
+ ai->responder = FALSE;
+ silc_client_perform_key_agreement(a->client, a->conn, client_entry,
+ a->hostname, a->port,
+ silcgaim_buddy_keyagr_cb, ai);
+ } else {
+ /* Send request. Force us as the point of connection since requestor
+ did not provide the point of connection. */
+ silcgaim_buddy_keyagr_do(a->client->application,
+ client_entry->nickname, TRUE);
+ }
+
+ out:
+ silc_free(a->hostname);
+ silc_free(a);
+}
+
+void silcgaim_buddy_keyagr_request(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry,
+ const char *hostname, SilcUInt16 port)
+{
+ char tmp[128], tmp2[128];
+ SilcGaimKeyAgrAsk a;
+
+ g_snprintf(tmp, sizeof(tmp),
+ _("Key agreement request received from %s. Would you like to "
+ "perform the key agreement?"), client_entry->nickname);
+ if (hostname)
+ g_snprintf(tmp2, sizeof(tmp2),
+ _("The remote user is waiting key agreement on:\n"
+ "Remote host: %s\nRemote port: %d"), hostname, port);
+
+ a = silc_calloc(1, sizeof(*a));
+ if (!a)
+ return;
+ a->client = client;
+ a->conn = conn;
+ a->client_id = *client_entry->id;
+ if (hostname)
+ a->hostname = strdup(hostname);
+ a->port = port;
+
+ gaim_request_action(client->application, _("Key Agreement Request"), tmp,
+ hostname ? tmp2 : NULL, 1, a, 2,
+ _("Yes"), G_CALLBACK(silcgaim_buddy_keyagr_request_cb),
+ _("No"), G_CALLBACK(silcgaim_buddy_keyagr_request_cb));
+}
+
+static void
+silcgaim_buddy_keyagr(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+
+ buddy = (GaimBuddy *)node;
+ silcgaim_buddy_keyagr_do(buddy->account->gc, buddy->name, FALSE);
+}
+
+
+/**************************** Static IM Key **********************************/
+
+static void
+silcgaim_buddy_resetkey(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *b;
+ GaimConnection *gc;
+ SilcGaim sg;
+ char *nickname;
+ SilcClientEntry *clients;
+ SilcUInt32 clients_count;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ b = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(b->account);
+ sg = gc->proto_data;
+
+ if (!silc_parse_userfqdn(b->name, &nickname, NULL))
+ return;
+
+ /* Find client entry */
+ clients = silc_client_get_clients_local(sg->client, sg->conn,
+ nickname, b->name,
+ &clients_count);
+ if (!clients) {
+ silc_free(nickname);
+ return;
+ }
+
+ clients[0]->prv_resp = FALSE;
+ silc_client_del_private_message_key(sg->client, sg->conn,
+ clients[0]);
+ silc_free(clients);
+ silc_free(nickname);
+}
+
+typedef struct {
+ SilcClient client;
+ SilcClientConnection conn;
+ SilcClientID client_id;
+} *SilcGaimPrivkey;
+
+static void
+silcgaim_buddy_privkey(GaimConnection *gc, const char *name);
+
+static void
+silcgaim_buddy_privkey_cb(SilcGaimPrivkey p, const char *passphrase)
+{
+ SilcClientEntry client_entry;
+
+ if (!passphrase || !(*passphrase)) {
+ silc_free(p);
+ return;
+ }
+
+ /* Get the client entry. */
+ client_entry = silc_client_get_client_by_id(p->client, p->conn,
+ &p->client_id);
+ if (!client_entry) {
+ gaim_notify_error(p->client->application, _("IM With Password"),
+ _("The remote user is not present in the network any more"),
+ NULL);
+ silc_free(p);
+ return;
+ }
+
+ /* Set the private message key */
+ silc_client_del_private_message_key(p->client, p->conn,
+ client_entry);
+ silc_client_add_private_message_key(p->client, p->conn,
+ client_entry, NULL, NULL,
+ (unsigned char *)passphrase,
+ strlen(passphrase), FALSE,
+ client_entry->prv_resp);
+ if (!client_entry->prv_resp)
+ silc_client_send_private_message_key_request(p->client,
+ p->conn,
+ client_entry);
+ silc_free(p);
+}
+
+static void
+silcgaim_buddy_privkey_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count,
+ void *context)
+{
+ char tmp[256];
+
+ if (!clients) {
+ g_snprintf(tmp, sizeof(tmp),
+ _("User %s is not present in the network"),
+ (const char *)context);
+ gaim_notify_error(client->application, _("IM With Password"),
+ _("Cannot set IM key"), tmp);
+ g_free(context);
+ return;
+ }
+
+ silcgaim_buddy_privkey(client->application, context);
+ silc_free(context);
+}
+
+static void
+silcgaim_buddy_privkey(GaimConnection *gc, const char *name)
+{
+ SilcGaim sg = gc->proto_data;
+ char *nickname;
+ SilcGaimPrivkey p;
+ SilcClientEntry *clients;
+ SilcUInt32 clients_count;
+
+ if (!name)
+ return;
+ if (!silc_parse_userfqdn(name, &nickname, NULL))
+ return;
+
+ /* Find client entry */
+ clients = silc_client_get_clients_local(sg->client, sg->conn,
+ nickname, name,
+ &clients_count);
+ if (!clients) {
+ silc_client_get_clients(sg->client, sg->conn, nickname, NULL,
+ silcgaim_buddy_privkey_resolved,
+ g_strdup(name));
+ silc_free(nickname);
+ return;
+ }
+
+ p = silc_calloc(1, sizeof(*p));
+ if (!p)
+ return;
+ p->client = sg->client;
+ p->conn = sg->conn;
+ p->client_id = *clients[0]->id;
+ gaim_request_input(gc, _("IM With Password"), NULL,
+ _("Set IM Password"), NULL, FALSE, TRUE, NULL,
+ _("OK"), G_CALLBACK(silcgaim_buddy_privkey_cb),
+ _("Cancel"), G_CALLBACK(silcgaim_buddy_privkey_cb),
+ p);
+
+ silc_free(clients);
+ silc_free(nickname);
+}
+
+static void
+silcgaim_buddy_privkey_menu(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+
+ silcgaim_buddy_privkey(gc, buddy->name);
+}
+
+
+/**************************** Get Public Key *********************************/
+
+typedef struct {
+ SilcClient client;
+ SilcClientConnection conn;
+ SilcClientID client_id;
+} *SilcGaimBuddyGetkey;
+
+static void
+silcgaim_buddy_getkey(GaimConnection *gc, const char *name);
+
+static void
+silcgaim_buddy_getkey_cb(SilcGaimBuddyGetkey g,
+ SilcClientCommandReplyContext cmd)
+{
+ SilcClientEntry client_entry;
+ unsigned char *pk;
+ SilcUInt32 pk_len;
+
+ /* Get the client entry. */
+ client_entry = silc_client_get_client_by_id(g->client, g->conn,
+ &g->client_id);
+ if (!client_entry) {
+ gaim_notify_error(g->client->application, _("Get Public Key"),
+ _("The remote user is not present in the network any more"),
+ NULL);
+ silc_free(g);
+ return;
+ }
+
+ if (!client_entry->public_key) {
+ silc_free(g);
+ return;
+ }
+
+ /* Now verify the public key */
+ pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+ silcgaim_verify_public_key(g->client, g->conn, client_entry->nickname,
+ SILC_SOCKET_TYPE_CLIENT,
+ pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+ NULL, NULL);
+ silc_free(pk);
+ silc_free(g);
+}
+
+static void
+silcgaim_buddy_getkey_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count,
+ void *context)
+{
+ char tmp[256];
+
+ if (!clients) {
+ g_snprintf(tmp, sizeof(tmp),
+ _("User %s is not present in the network"),
+ (const char *)context);
+ gaim_notify_error(client->application, _("Get Public Key"),
+ _("Cannot fetch the public key"), tmp);
+ g_free(context);
+ return;
+ }
+
+ silcgaim_buddy_getkey(client->application, context);
+ silc_free(context);
+}
+
+static void
+silcgaim_buddy_getkey(GaimConnection *gc, const char *name)
+{
+ SilcGaim sg = gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ SilcClientEntry *clients;
+ SilcUInt32 clients_count;
+ SilcGaimBuddyGetkey g;
+ char *nickname;
+
+ if (!name)
+ return;
+
+ if (!silc_parse_userfqdn(name, &nickname, NULL))
+ return;
+
+ /* Find client entry */
+ clients = silc_client_get_clients_local(client, conn, nickname, name,
+ &clients_count);
+ if (!clients) {
+ silc_client_get_clients(client, conn, nickname, NULL,
+ silcgaim_buddy_getkey_resolved,
+ g_strdup(name));
+ silc_free(nickname);
+ return;
+ }
+
+ /* Call GETKEY */
+ g = silc_calloc(1, sizeof(*g));
+ if (!g)
+ return;
+ g->client = client;
+ g->conn = conn;
+ g->client_id = *clients[0]->id;
+ silc_client_command_call(client, conn, NULL, "GETKEY",
+ clients[0]->nickname, NULL);
+ silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+ conn->cmd_ident,
+ (SilcCommandCb)silcgaim_buddy_getkey_cb, g);
+ silc_free(clients);
+ silc_free(nickname);
+}
+
+static void
+silcgaim_buddy_getkey_menu(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *buddy;
+ GaimConnection *gc;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(buddy->account);
+
+ silcgaim_buddy_getkey(gc, buddy->name);
+}
+
+static void
+silcgaim_buddy_showkey(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *b;
+ GaimConnection *gc;
+ SilcGaim sg;
+ SilcPublicKey public_key;
+ const char *pkfile;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ b = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(b->account);
+ sg = gc->proto_data;
+
+ pkfile = gaim_blist_node_get_string(node, "public-key");
+ if (!silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_PEM) &&
+ !silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_BIN)) {
+ gaim_notify_error(gc,
+ _("Show Public Key"),
+ _("Could not load public key"), NULL);
+ return;
+ }
+
+ silcgaim_show_public_key(sg, b->name, public_key, NULL, NULL);
+ silc_pkcs_public_key_free(public_key);
+}
+
+
+/**************************** Buddy routines *********************************/
+
+/* The buddies are implemented by using the WHOIS and WATCH commands that
+ can be used to search users by their public key. Since nicknames aren't
+ unique in SILC we cannot trust the buddy list using their nickname. We
+ associate public keys to buddies and use those to search and watch
+ in the network.
+
+ The problem is that Gaim does not return GaimBuddy contexts to the
+ callbacks but the buddy names. Naturally, this is not going to work
+ with SILC. But, for now, we have to do what we can... */
+
+typedef struct {
+ SilcClient client;
+ SilcClientConnection conn;
+ SilcClientID client_id;
+ GaimBuddy *b;
+ unsigned char *offline_pk;
+ SilcUInt32 offline_pk_len;
+ unsigned int offline : 1;
+ unsigned int pubkey_search : 1;
+ unsigned int init : 1;
+} *SilcGaimBuddyRes;
+
+static void
+silcgaim_add_buddy_ask_pk_cb(SilcGaimBuddyRes r, gint id);
+static void
+silcgaim_add_buddy_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count,
+ void *context);
+
+void silcgaim_get_info(GaimConnection *gc, const char *who)
+{
+ SilcGaim sg = gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ SilcClientEntry client_entry;
+ GaimBuddy *b;
+ const char *filename, *nick = who;
+ char tmp[256];
+
+ if (!who)
+ return;
+ if (strlen(who) > 1 && who[0] == '@')
+ nick = who + 1;
+ if (strlen(who) > 1 && who[0] == '*')
+ nick = who + 1;
+ if (strlen(who) > 2 && who[0] == '*' && who[1] == '@')
+ nick = who + 2;
+
+ b = gaim_find_buddy(gc->account, nick);
+ if (b) {
+ /* See if we have this buddy's public key. If we do use that
+ to search the details. */
+ filename = gaim_blist_node_get_string((GaimBlistNode *)b, "public-key");
+ if (filename) {
+ /* Call WHOIS. The user info is displayed in the WHOIS
+ command reply. */
+ silc_client_command_call(client, conn, NULL, "WHOIS",
+ "-details", "-pubkey", filename, NULL);
+ return;
+ }
+
+ if (!b->proto_data) {
+ g_snprintf(tmp, sizeof(tmp),
+ _("User %s is not present in the network"), b->name);
+ gaim_notify_error(gc, _("User Information"),
+ _("Cannot get user information"), tmp);
+ return;
+ }
+
+ client_entry = silc_client_get_client_by_id(client, conn, b->proto_data);
+ if (client_entry) {
+ /* Call WHOIS. The user info is displayed in the WHOIS
+ command reply. */
+ silc_client_command_call(client, conn, NULL, "WHOIS",
+ client_entry->nickname, "-details", NULL);
+ }
+ } else {
+ /* Call WHOIS just with nickname. */
+ silc_client_command_call(client, conn, NULL, "WHOIS", nick, NULL);
+ }
+}
+
+static void
+silcgaim_add_buddy_pk_no(SilcGaimBuddyRes r)
+{
+ char tmp[512];
+ g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not trusted"),
+ r->b->name);
+ gaim_notify_error(r->client->application, _("Add Buddy"), tmp,
+ _("You cannot receive buddy notifications until you "
+ "import his/her public key. You can use the Get Public Key "
+ "command to get the public key."));
+ gaim_prpl_got_user_status(gaim_buddy_get_account(r->b), gaim_buddy_get_name(r->b), SILCGAIM_STATUS_ID_OFFLINE, NULL);
+}
+
+static void
+silcgaim_add_buddy_save(bool success, void *context)
+{
+ SilcGaimBuddyRes r = context;
+ GaimBuddy *b = r->b;
+ SilcClient client = r->client;
+ SilcClientEntry client_entry;
+ SilcAttributePayload attr;
+ SilcAttribute attribute;
+ SilcVCardStruct vcard;
+ SilcAttributeObjMime message, extension;
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ SilcAttributeObjMime usericon;
+#endif
+ SilcAttributeObjPk serverpk, usersign, serversign;
+ gboolean usign_success = TRUE, ssign_success = TRUE;
+ char filename[512], filename2[512], *fingerprint = NULL, *tmp;
+ SilcUInt32 len;
+ int i;
+
+ if (!success) {
+ /* The user did not trust the public key. */
+ silcgaim_add_buddy_pk_no(r);
+ silc_free(r);
+ return;
+ }
+
+ if (r->offline) {
+ /* User is offline. Associate the imported public key with
+ this user. */
+ fingerprint = silc_hash_fingerprint(NULL, r->offline_pk,
+ r->offline_pk_len);
+ for (i = 0; i < strlen(fingerprint); i++)
+ if (fingerprint[i] == ' ')
+ fingerprint[i] = '_';
+ g_snprintf(filename, sizeof(filename) - 1,
+ "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub",
+ silcgaim_silcdir(), fingerprint);
+ gaim_blist_node_set_string((GaimBlistNode *)b, "public-key", filename);
+ gaim_prpl_got_user_status(gaim_buddy_get_account(r->b), gaim_buddy_get_name(r->b), SILCGAIM_STATUS_ID_OFFLINE, NULL);
+ silc_free(fingerprint);
+ silc_free(r->offline_pk);
+ silc_free(r);
+ return;
+ }
+
+ /* Get the client entry. */
+ client_entry = silc_client_get_client_by_id(r->client, r->conn,
+ &r->client_id);
+ if (!client_entry) {
+ silc_free(r);
+ return;
+ }
+
+ memset(&vcard, 0, sizeof(vcard));
+ memset(&message, 0, sizeof(message));
+ memset(&extension, 0, sizeof(extension));
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ memset(&usericon, 0, sizeof(usericon));
+#endif
+ memset(&serverpk, 0, sizeof(serverpk));
+ memset(&usersign, 0, sizeof(usersign));
+ memset(&serversign, 0, sizeof(serversign));
+
+ /* Now that we have the public key and we trust it now we
+ save the attributes of the buddy and update its status. */
+
+ if (client_entry->attrs) {
+ silc_dlist_start(client_entry->attrs);
+ while ((attr = silc_dlist_get(client_entry->attrs))
+ != SILC_LIST_END) {
+ attribute = silc_attribute_get_attribute(attr);
+
+ switch (attribute) {
+ case SILC_ATTRIBUTE_USER_INFO:
+ if (!silc_attribute_get_object(attr, (void *)&vcard,
+ sizeof(vcard)))
+ continue;
+ break;
+
+ case SILC_ATTRIBUTE_STATUS_MESSAGE:
+ if (!silc_attribute_get_object(attr, (void *)&message,
+ sizeof(message)))
+ continue;
+ break;
+
+ case SILC_ATTRIBUTE_EXTENSION:
+ if (!silc_attribute_get_object(attr, (void *)&extension,
+ sizeof(extension)))
+ continue;
+ break;
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ case SILC_ATTRIBUTE_USER_ICON:
+ if (!silc_attribute_get_object(attr, (void *)&usericon,
+ sizeof(usericon)))
+ continue;
+ break;
+#endif
+
+ case SILC_ATTRIBUTE_SERVER_PUBLIC_KEY:
+ if (serverpk.type)
+ continue;
+ if (!silc_attribute_get_object(attr, (void *)&serverpk,
+ sizeof(serverpk)))
+ continue;
+ break;
+
+ case SILC_ATTRIBUTE_USER_DIGITAL_SIGNATURE:
+ if (usersign.data)
+ continue;
+ if (!silc_attribute_get_object(attr, (void *)&usersign,
+ sizeof(usersign)))
+ continue;
+ break;
+
+ case SILC_ATTRIBUTE_SERVER_DIGITAL_SIGNATURE:
+ if (serversign.data)
+ continue;
+ if (!silc_attribute_get_object(attr, (void *)&serversign,
+ sizeof(serversign)))
+ continue;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /* Verify the attribute signatures */
+
+ if (usersign.data) {
+ SilcPKCS pkcs;
+ unsigned char *verifyd;
+ SilcUInt32 verify_len;
+
+ silc_pkcs_alloc((unsigned char*)"rsa", &pkcs);
+ verifyd = silc_attribute_get_verify_data(client_entry->attrs,
+ FALSE, &verify_len);
+ if (verifyd && silc_pkcs_public_key_set(pkcs, client_entry->public_key)){
+ if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash,
+ usersign.data,
+ usersign.data_len,
+ verifyd, verify_len))
+ usign_success = FALSE;
+ }
+ silc_free(verifyd);
+ }
+
+ if (serversign.data && !strcmp(serverpk.type, "silc-rsa")) {
+ SilcPublicKey public_key;
+ SilcPKCS pkcs;
+ unsigned char *verifyd;
+ SilcUInt32 verify_len;
+
+ if (silc_pkcs_public_key_decode(serverpk.data, serverpk.data_len,
+ &public_key)) {
+ silc_pkcs_alloc((unsigned char *)"rsa", &pkcs);
+ verifyd = silc_attribute_get_verify_data(client_entry->attrs,
+ TRUE, &verify_len);
+ if (verifyd && silc_pkcs_public_key_set(pkcs, public_key)) {
+ if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash,
+ serversign.data,
+ serversign.data_len,
+ verifyd, verify_len))
+ ssign_success = FALSE;
+ }
+ silc_pkcs_public_key_free(public_key);
+ silc_free(verifyd);
+ }
+ }
+
+ fingerprint = silc_fingerprint(client_entry->fingerprint,
+ client_entry->fingerprint_len);
+ for (i = 0; i < strlen(fingerprint); i++)
+ if (fingerprint[i] == ' ')
+ fingerprint[i] = '_';
+
+ if (usign_success || ssign_success) {
+ struct passwd *pw;
+ struct stat st;
+
+ memset(filename2, 0, sizeof(filename2));
+
+ /* Filename for dir */
+ tmp = fingerprint + strlen(fingerprint) - 9;
+ g_snprintf(filename, sizeof(filename) - 1,
+ "%s" G_DIR_SEPARATOR_S "friends" G_DIR_SEPARATOR_S "%s",
+ silcgaim_silcdir(), tmp);
+
+ pw = getpwuid(getuid());
+ if (!pw)
+ return;
+
+ /* Create dir if it doesn't exist */
+ if ((g_stat(filename, &st)) == -1) {
+ if (errno == ENOENT) {
+ if (pw->pw_uid == geteuid())
+ g_mkdir(filename, 0755);
+ }
+ }
+
+ /* Save VCard */
+ g_snprintf(filename2, sizeof(filename2) - 1,
+ "%s" G_DIR_SEPARATOR_S "vcard", filename);
+ if (vcard.full_name) {
+ tmp = (char *)silc_vcard_encode(&vcard, &len);
+ silc_file_writefile(filename2, tmp, len);
+ silc_free(tmp);
+ }
+
+ /* Save status message */
+ if (message.mime) {
+ memset(filename2, 0, sizeof(filename2));
+ g_snprintf(filename2, sizeof(filename2) - 1,
+ "%s" G_DIR_SEPARATOR_S "status_message.mime",
+ filename);
+ silc_file_writefile(filename2, (char *)message.mime,
+ message.mime_len);
+ }
+
+ /* Save extension data */
+ if (extension.mime) {
+ memset(filename2, 0, sizeof(filename2));
+ g_snprintf(filename2, sizeof(filename2) - 1,
+ "%s" G_DIR_SEPARATOR_S "extension.mime",
+ filename);
+ silc_file_writefile(filename2, (char *)extension.mime,
+ extension.mime_len);
+ }
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ /* Save user icon */
+ if (usericon.mime) {
+ SilcMime m = silc_mime_decode(usericon.mime,
+ usericon.mime_len);
+ if (m) {
+ const char *type = silc_mime_get_field(m, "Content-Type");
+ if (!strcmp(type, "image/jpeg") ||
+ !strcmp(type, "image/gif") ||
+ !strcmp(type, "image/bmp") ||
+ !strcmp(type, "image/png")) {
+ const unsigned char *data;
+ SilcUInt32 data_len;
+ data = silc_mime_get_data(m, &data_len);
+ if (data)
+ gaim_buddy_icons_set_for_user(gaim_buddy_get_account(r->b), gaim_buddy_get_name(r->b), (void *)data, data_len);
+ }
+ silc_mime_free(m);
+ }
+ }
+#endif
+ }
+
+ /* Save the public key path to buddy properties, as it is used
+ to identify the buddy in the network (and not the nickname). */
+ memset(filename, 0, sizeof(filename));
+ g_snprintf(filename, sizeof(filename) - 1,
+ "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub",
+ silcgaim_silcdir(), fingerprint);
+ gaim_blist_node_set_string((GaimBlistNode *)b, "public-key", filename);
+
+ /* Update online status */
+ gaim_prpl_got_user_status(gaim_buddy_get_account(r->b), gaim_buddy_get_name(r->b), SILCGAIM_STATUS_ID_AVAILABLE, NULL);
+
+ /* Finally, start watching this user so we receive its status
+ changes from the server */
+ g_snprintf(filename2, sizeof(filename2) - 1, "+%s", filename);
+ silc_client_command_call(r->client, r->conn, NULL, "WATCH", "-pubkey",
+ filename2, NULL);
+
+ silc_free(fingerprint);
+ silc_free(r);
+}
+
+static void
+silcgaim_add_buddy_ask_import(void *user_data, const char *name)
+{
+ SilcGaimBuddyRes r = (SilcGaimBuddyRes)user_data;
+ SilcPublicKey public_key;
+
+ /* Load the public key */
+ if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) &&
+ !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) {
+ silcgaim_add_buddy_ask_pk_cb(r, 0);
+ gaim_notify_error(r->client->application,
+ _("Add Buddy"), _("Could not load public key"), NULL);
+ return;
+ }
+
+ /* Now verify the public key */
+ r->offline_pk = silc_pkcs_public_key_encode(public_key, &r->offline_pk_len);
+ silcgaim_verify_public_key(r->client, r->conn, r->b->name,
+ SILC_SOCKET_TYPE_CLIENT,
+ r->offline_pk, r->offline_pk_len,
+ SILC_SKE_PK_TYPE_SILC,
+ silcgaim_add_buddy_save, r);
+}
+
+static void
+silcgaim_add_buddy_ask_pk_cancel(void *user_data, const char *name)
+{
+ SilcGaimBuddyRes r = (SilcGaimBuddyRes)user_data;
+
+ /* The user did not import public key. The buddy is unusable. */
+ silcgaim_add_buddy_pk_no(r);
+ silc_free(r);
+}
+
+static void
+silcgaim_add_buddy_ask_pk_cb(SilcGaimBuddyRes r, gint id)
+{
+ if (id != 0) {
+ /* The user did not import public key. The buddy is unusable. */
+ silcgaim_add_buddy_pk_no(r);
+ silc_free(r);
+ return;
+ }
+
+ /* Open file selector to select the public key. */
+ gaim_request_file(r->client->application, _("Open..."), NULL, FALSE,
+ G_CALLBACK(silcgaim_add_buddy_ask_import),
+ G_CALLBACK(silcgaim_add_buddy_ask_pk_cancel), r);
+}
+
+static void
+silcgaim_add_buddy_ask_pk(SilcGaimBuddyRes r)
+{
+ char tmp[512];
+ g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not present in the network"),
+ r->b->name);
+ gaim_request_action(r->client->application, _("Add Buddy"), tmp,
+ _("To add the buddy you must import his/her public key. "
+ "Press Import to import a public key."), 0, r, 2,
+ _("Cancel"), G_CALLBACK(silcgaim_add_buddy_ask_pk_cb),
+ _("_Import..."), G_CALLBACK(silcgaim_add_buddy_ask_pk_cb));
+}
+
+static void
+silcgaim_add_buddy_getkey_cb(SilcGaimBuddyRes r,
+ SilcClientCommandReplyContext cmd)
+{
+ SilcClientEntry client_entry;
+ unsigned char *pk;
+ SilcUInt32 pk_len;
+
+ /* Get the client entry. */
+ client_entry = silc_client_get_client_by_id(r->client, r->conn,
+ &r->client_id);
+ if (!client_entry || !client_entry->public_key) {
+ /* The buddy is offline/nonexistent. We will require user
+ to associate a public key with the buddy or the buddy
+ cannot be added. */
+ r->offline = TRUE;
+ silcgaim_add_buddy_ask_pk(r);
+ return;
+ }
+
+ /* Now verify the public key */
+ pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+ silcgaim_verify_public_key(r->client, r->conn, client_entry->nickname,
+ SILC_SOCKET_TYPE_CLIENT,
+ pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+ silcgaim_add_buddy_save, r);
+ silc_free(pk);
+}
+
+static void
+silcgaim_add_buddy_select_cb(SilcGaimBuddyRes r, GaimRequestFields *fields)
+{
+ GaimRequestField *f;
+ const GList *list;
+ SilcClientEntry client_entry;
+
+ f = gaim_request_fields_get_field(fields, "list");
+ list = gaim_request_field_list_get_selected(f);
+ if (!list) {
+ /* The user did not select any user. */
+ silcgaim_add_buddy_pk_no(r);
+ silc_free(r);
+ return;
+ }
+
+ client_entry = gaim_request_field_list_get_data(f, list->data);
+ silcgaim_add_buddy_resolved(r->client, r->conn, &client_entry, 1, r);
+}
+
+static void
+silcgaim_add_buddy_select_cancel(SilcGaimBuddyRes r, GaimRequestFields *fields)
+{
+ /* The user did not select any user. */
+ silcgaim_add_buddy_pk_no(r);
+ silc_free(r);
+}
+
+static void
+silcgaim_add_buddy_select(SilcGaimBuddyRes r,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count)
+{
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *g;
+ GaimRequestField *f;
+ char tmp[512], tmp2[128];
+ int i;
+ char *fingerprint;
+
+ fields = gaim_request_fields_new();
+ g = gaim_request_field_group_new(NULL);
+ f = gaim_request_field_list_new("list", NULL);
+ gaim_request_field_group_add_field(g, f);
+ gaim_request_field_list_set_multi_select(f, FALSE);
+ gaim_request_fields_add_group(fields, g);
+
+ for (i = 0; i < clients_count; i++) {
+ fingerprint = NULL;
+ if (clients[i]->fingerprint) {
+ fingerprint = silc_fingerprint(clients[i]->fingerprint,
+ clients[i]->fingerprint_len);
+ g_snprintf(tmp2, sizeof(tmp2), "\n%s", fingerprint);
+ }
+ g_snprintf(tmp, sizeof(tmp), "%s - %s (%s@%s)%s",
+ clients[i]->realname, clients[i]->nickname,
+ clients[i]->username, clients[i]->hostname ?
+ clients[i]->hostname : "",
+ fingerprint ? tmp2 : "");
+ gaim_request_field_list_add(f, tmp, clients[i]);
+ silc_free(fingerprint);
+ }
+
+ gaim_request_fields(r->client->application, _("Add Buddy"),
+ _("Select correct user"),
+ r->pubkey_search
+ ? _("More than one user was found with the same public key. Select "
+ "the correct user from the list to add to the buddy list.")
+ : _("More than one user was found with the same name. Select "
+ "the correct user from the list to add to the buddy list."),
+ fields,
+ _("OK"), G_CALLBACK(silcgaim_add_buddy_select_cb),
+ _("Cancel"), G_CALLBACK(silcgaim_add_buddy_select_cancel), r);
+}
+
+static void
+silcgaim_add_buddy_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count,
+ void *context)
+{
+ SilcGaimBuddyRes r = context;
+ GaimBuddy *b = r->b;
+ SilcAttributePayload pub;
+ SilcAttributeObjPk userpk;
+ unsigned char *pk;
+ SilcUInt32 pk_len;
+ const char *filename;
+
+ filename = gaim_blist_node_get_string((GaimBlistNode *)b, "public-key");
+
+ /* If the buddy is offline/nonexistent, we will require user
+ to associate a public key with the buddy or the buddy
+ cannot be added. */
+ if (!clients_count) {
+ if (r->init) {
+ silc_free(r);
+ return;
+ }
+
+ r->offline = TRUE;
+ /* If the user has already associated a public key, try loading it
+ * before prompting the user to load it again */
+ if (filename != NULL)
+ silcgaim_add_buddy_ask_import(r, filename);
+ else
+ silcgaim_add_buddy_ask_pk(r);
+ return;
+ }
+
+ /* If more than one client was found with nickname, we need to verify
+ from user which one is the correct. */
+ if (clients_count > 1 && !r->pubkey_search) {
+ if (r->init) {
+ silc_free(r);
+ return;
+ }
+
+ silcgaim_add_buddy_select(r, clients, clients_count);
+ return;
+ }
+
+ /* If we searched using public keys and more than one entry was found
+ the same person is logged on multiple times. */
+ if (clients_count > 1 && r->pubkey_search && b->name) {
+ if (r->init) {
+ /* Find the entry that closest matches to the
+ buddy nickname. */
+ int i;
+ for (i = 0; i < clients_count; i++) {
+ if (!strncasecmp(b->name, clients[i]->nickname,
+ strlen(b->name))) {
+ clients[0] = clients[i];
+ break;
+ }
+ }
+ } else {
+ /* Verify from user which one is correct */
+ silcgaim_add_buddy_select(r, clients, clients_count);
+ return;
+ }
+ }
+
+ /* The client was found. Now get its public key and verify
+ that before adding the buddy. */
+ memset(&userpk, 0, sizeof(userpk));
+ b->proto_data = silc_memdup(clients[0]->id, sizeof(*clients[0]->id));
+ r->client_id = *clients[0]->id;
+
+ /* Get the public key from attributes, if not present then
+ resolve it with GETKEY unless we have it cached already. */
+ if (clients[0]->attrs && !clients[0]->public_key) {
+ pub = silcgaim_get_attr(clients[0]->attrs,
+ SILC_ATTRIBUTE_USER_PUBLIC_KEY);
+ if (!pub || !silc_attribute_get_object(pub, (void *)&userpk,
+ sizeof(userpk))) {
+ /* Get public key with GETKEY */
+ silc_client_command_call(client, conn, NULL,
+ "GETKEY", clients[0]->nickname, NULL);
+ silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+ conn->cmd_ident,
+ (SilcCommandCb)silcgaim_add_buddy_getkey_cb,
+ r);
+ return;
+ }
+ if (!silc_pkcs_public_key_decode(userpk.data, userpk.data_len,
+ &clients[0]->public_key))
+ return;
+ silc_free(userpk.data);
+ } else if (filename && !clients[0]->public_key) {
+ if (!silc_pkcs_load_public_key(filename, &clients[0]->public_key,
+ SILC_PKCS_FILE_PEM) &&
+ !silc_pkcs_load_public_key(filename, &clients[0]->public_key,
+ SILC_PKCS_FILE_BIN)) {
+ /* Get public key with GETKEY */
+ silc_client_command_call(client, conn, NULL,
+ "GETKEY", clients[0]->nickname, NULL);
+ silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+ conn->cmd_ident,
+ (SilcCommandCb)silcgaim_add_buddy_getkey_cb,
+ r);
+ return;
+ }
+ } else if (!clients[0]->public_key) {
+ /* Get public key with GETKEY */
+ silc_client_command_call(client, conn, NULL,
+ "GETKEY", clients[0]->nickname, NULL);
+ silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+ conn->cmd_ident,
+ (SilcCommandCb)silcgaim_add_buddy_getkey_cb,
+ r);
+ return;
+ }
+
+ /* We have the public key, verify it. */
+ pk = silc_pkcs_public_key_encode(clients[0]->public_key, &pk_len);
+ silcgaim_verify_public_key(client, conn, clients[0]->nickname,
+ SILC_SOCKET_TYPE_CLIENT,
+ pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+ silcgaim_add_buddy_save, r);
+ silc_free(pk);
+}
+
+static void
+silcgaim_add_buddy_i(GaimConnection *gc, GaimBuddy *b, gboolean init)
+{
+ SilcGaim sg = gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ SilcGaimBuddyRes r;
+ SilcBuffer attrs;
+ const char *filename, *name = b->name;
+
+ r = silc_calloc(1, sizeof(*r));
+ if (!r)
+ return;
+ r->client = client;
+ r->conn = conn;
+ r->b = b;
+ r->init = init;
+
+ /* See if we have this buddy's public key. If we do use that
+ to search the details. */
+ filename = gaim_blist_node_get_string((GaimBlistNode *)b, "public-key");
+ if (filename) {
+ SilcPublicKey public_key;
+ SilcAttributeObjPk userpk;
+
+ if (!silc_pkcs_load_public_key(filename, &public_key,
+ SILC_PKCS_FILE_PEM) &&
+ !silc_pkcs_load_public_key(filename, &public_key,
+ SILC_PKCS_FILE_BIN))
+ return;
+
+ /* Get all attributes, and use the public key to search user */
+ name = NULL;
+ attrs = silc_client_attributes_request(SILC_ATTRIBUTE_USER_INFO,
+ SILC_ATTRIBUTE_SERVICE,
+ SILC_ATTRIBUTE_STATUS_MOOD,
+ SILC_ATTRIBUTE_STATUS_FREETEXT,
+ SILC_ATTRIBUTE_STATUS_MESSAGE,
+ SILC_ATTRIBUTE_PREFERRED_LANGUAGE,
+ SILC_ATTRIBUTE_PREFERRED_CONTACT,
+ SILC_ATTRIBUTE_TIMEZONE,
+ SILC_ATTRIBUTE_GEOLOCATION,
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ SILC_ATTRIBUTE_USER_ICON,
+#endif
+ SILC_ATTRIBUTE_DEVICE_INFO, 0);
+ userpk.type = "silc-rsa";
+ userpk.data = silc_pkcs_public_key_encode(public_key, &userpk.data_len);
+ attrs = silc_attribute_payload_encode(attrs,
+ SILC_ATTRIBUTE_USER_PUBLIC_KEY,
+ SILC_ATTRIBUTE_FLAG_VALID,
+ &userpk, sizeof(userpk));
+ silc_free(userpk.data);
+ silc_pkcs_public_key_free(public_key);
+ r->pubkey_search = TRUE;
+ } else {
+ /* Get all attributes */
+ attrs = silc_client_attributes_request(0);
+ }
+
+ /* Resolve */
+ silc_client_get_clients_whois(client, conn, name, NULL, attrs,
+ silcgaim_add_buddy_resolved, r);
+ silc_buffer_free(attrs);
+}
+
+void silcgaim_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+ silcgaim_add_buddy_i(gc, buddy, FALSE);
+}
+
+void silcgaim_send_buddylist(GaimConnection *gc)
+{
+ GaimBuddyList *blist;
+ GaimBlistNode *gnode, *cnode, *bnode;
+ GaimBuddy *buddy;
+ GaimAccount *account;
+
+ account = gaim_connection_get_account(gc);
+
+ if ((blist = gaim_get_blist()) != NULL)
+ {
+ for (gnode = blist->root; gnode != NULL; gnode = gnode->next)
+ {
+ if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+ continue;
+ for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
+ {
+ if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
+ continue;
+ for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
+ {
+ if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
+ continue;
+ buddy = (GaimBuddy *)bnode;
+ if (gaim_buddy_get_account(buddy) == account)
+ silcgaim_add_buddy_i(gc, buddy, TRUE);
+ }
+ }
+ }
+ }
+}
+
+void silcgaim_remove_buddy(GaimConnection *gc, GaimBuddy *buddy,
+ GaimGroup *group)
+{
+ silc_free(buddy->proto_data);
+}
+
+void silcgaim_idle_set(GaimConnection *gc, int idle)
+
+{
+ SilcGaim sg = gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ SilcAttributeObjService service;
+ const char *server;
+ int port;
+
+ server = gaim_account_get_string(sg->account, "server",
+ "silc.silcnet.org");
+ port = gaim_account_get_int(sg->account, "port", 706),
+
+ memset(&service, 0, sizeof(service));
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_SERVICE, NULL);
+ service.port = port;
+ g_snprintf(service.address, sizeof(service.address), "%s", server);
+ service.idle = idle;
+ silc_client_attribute_add(client, conn, SILC_ATTRIBUTE_SERVICE,
+ &service, sizeof(service));
+}
+
+char *silcgaim_status_text(GaimBuddy *b)
+{
+ SilcGaim sg = b->account->gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ SilcClientID *client_id = b->proto_data;
+ SilcClientEntry client_entry;
+ SilcAttributePayload attr;
+ SilcAttributeMood mood = 0;
+
+ /* Get the client entry. */
+ client_entry = silc_client_get_client_by_id(client, conn, client_id);
+ if (!client_entry)
+ return NULL;
+
+ /* If user is online, we show the mood status, if available.
+ If user is offline or away that status is indicated. */
+
+ if (client_entry->mode & SILC_UMODE_DETACHED)
+ return g_strdup(_("Detached"));
+ if (client_entry->mode & SILC_UMODE_GONE)
+ return g_strdup(_("Away"));
+ if (client_entry->mode & SILC_UMODE_INDISPOSED)
+ return g_strdup(_("Indisposed"));
+ if (client_entry->mode & SILC_UMODE_BUSY)
+ return g_strdup(_("Busy"));
+ if (client_entry->mode & SILC_UMODE_PAGE)
+ return g_strdup(_("Wake Me Up"));
+ if (client_entry->mode & SILC_UMODE_HYPER)
+ return g_strdup(_("Hyper Active"));
+ if (client_entry->mode & SILC_UMODE_ROBOT)
+ return g_strdup(_("Robot"));
+
+ attr = silcgaim_get_attr(client_entry->attrs, SILC_ATTRIBUTE_STATUS_MOOD);
+ if (attr && silc_attribute_get_object(attr, &mood, sizeof(mood))) {
+ /* The mood is a bit mask, so we could show multiple moods,
+ but let's show only one for now. */
+ if (mood & SILC_ATTRIBUTE_MOOD_HAPPY)
+ return g_strdup(_("Happy"));
+ if (mood & SILC_ATTRIBUTE_MOOD_SAD)
+ return g_strdup(_("Sad"));
+ if (mood & SILC_ATTRIBUTE_MOOD_ANGRY)
+ return g_strdup(_("Angry"));
+ if (mood & SILC_ATTRIBUTE_MOOD_JEALOUS)
+ return g_strdup(_("Jealous"));
+ if (mood & SILC_ATTRIBUTE_MOOD_ASHAMED)
+ return g_strdup(_("Ashamed"));
+ if (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE)
+ return g_strdup(_("Invincible"));
+ if (mood & SILC_ATTRIBUTE_MOOD_INLOVE)
+ return g_strdup(_("In Love"));
+ if (mood & SILC_ATTRIBUTE_MOOD_SLEEPY)
+ return g_strdup(_("Sleepy"));
+ if (mood & SILC_ATTRIBUTE_MOOD_BORED)
+ return g_strdup(_("Bored"));
+ if (mood & SILC_ATTRIBUTE_MOOD_EXCITED)
+ return g_strdup(_("Excited"));
+ if (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS)
+ return g_strdup(_("Anxious"));
+ }
+
+ return NULL;
+}
+
+void silcgaim_tooltip_text(GaimBuddy *b, GaimNotifyUserInfo *user_info, gboolean full)
+{
+ SilcGaim sg = b->account->gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ SilcClientID *client_id = b->proto_data;
+ SilcClientEntry client_entry;
+ char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr;
+ char tmp[256];
+
+ /* Get the client entry. */
+ client_entry = silc_client_get_client_by_id(client, conn, client_id);
+ if (!client_entry)
+ return;
+
+ if (client_entry->nickname)
+ gaim_notify_user_info_add_pair(user_info, _("Nickname"),
+ client_entry->nickname);
+ if (client_entry->username && client_entry->hostname) {
+ g_snprintf(tmp, sizeof(tmp), "%s@%s", client_entry->username, client_entry->hostname);
+ gaim_notify_user_info_add_pair(user_info, _("Username"), tmp);
+ }
+ if (client_entry->mode) {
+ memset(tmp, 0, sizeof(tmp));
+ silcgaim_get_umode_string(client_entry->mode,
+ tmp, sizeof(tmp) - strlen(tmp));
+ gaim_notify_user_info_add_pair(user_info, _("User Modes"), tmp);
+ }
+
+ silcgaim_parse_attrs(client_entry->attrs, &moodstr, &statusstr, &contactstr, &langstr, &devicestr, &tzstr, &geostr);
+
+ if (statusstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Message"), statusstr);
+ g_free(statusstr);
+ }
+
+ if (full) {
+ if (moodstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Mood"), moodstr);
+ g_free(moodstr);
+ }
+
+ if (contactstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Preferred Contact"), contactstr);
+ g_free(contactstr);
+ }
+
+ if (langstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Preferred Language"), langstr);
+ g_free(langstr);
+ }
+
+ if (devicestr) {
+ gaim_notify_user_info_add_pair(user_info, _("Device"), devicestr);
+ g_free(devicestr);
+ }
+
+ if (tzstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Timezone"), tzstr);
+ g_free(tzstr);
+ }
+
+ if (geostr) {
+ gaim_notify_user_info_add_pair(user_info, _("Geolocation"), geostr);
+ g_free(geostr);
+ }
+ }
+}
+
+static void
+silcgaim_buddy_kill(GaimBlistNode *node, gpointer data)
+{
+ GaimBuddy *b;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+ b = (GaimBuddy *) node;
+ gc = gaim_account_get_connection(b->account);
+ sg = gc->proto_data;
+
+ /* Call KILL */
+ silc_client_command_call(sg->client, sg->conn, NULL, "KILL",
+ b->name, "Killed by operator", NULL);
+}
+
+typedef struct {
+ SilcGaim sg;
+ SilcClientEntry client_entry;
+} *SilcGaimBuddyWb;
+
+static void
+silcgaim_buddy_wb(GaimBlistNode *node, gpointer data)
+{
+ SilcGaimBuddyWb wb = data;
+ silcgaim_wb_init(wb->sg, wb->client_entry);
+ silc_free(wb);
+}
+
+GList *silcgaim_buddy_menu(GaimBuddy *buddy)
+{
+ GaimConnection *gc = gaim_account_get_connection(buddy->account);
+ SilcGaim sg = gc->proto_data;
+ SilcClientConnection conn = sg->conn;
+ const char *pkfile = NULL;
+ SilcClientEntry client_entry = NULL;
+ GaimMenuAction *act;
+ GList *m = NULL;
+ SilcGaimBuddyWb wb;
+
+ pkfile = gaim_blist_node_get_string((GaimBlistNode *) buddy, "public-key");
+ client_entry = silc_client_get_client_by_id(sg->client,
+ sg->conn,
+ buddy->proto_data);
+
+ if (client_entry && client_entry->send_key) {
+ act = gaim_menu_action_new(_("Reset IM Key"),
+ GAIM_CALLBACK(silcgaim_buddy_resetkey),
+ NULL, NULL);
+ m = g_list_append(m, act);
+
+ } else {
+ act = gaim_menu_action_new(_("IM with Key Exchange"),
+ GAIM_CALLBACK(silcgaim_buddy_keyagr),
+ NULL, NULL);
+ m = g_list_append(m, act);
+
+ act = gaim_menu_action_new(_("IM with Password"),
+ GAIM_CALLBACK(silcgaim_buddy_privkey_menu),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ if (pkfile) {
+ act = gaim_menu_action_new(_("Show Public Key"),
+ GAIM_CALLBACK(silcgaim_buddy_showkey),
+ NULL, NULL);
+ m = g_list_append(m, act);
+
+ } else {
+ act = gaim_menu_action_new(_("Get Public Key..."),
+ GAIM_CALLBACK(silcgaim_buddy_getkey_menu),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ if (conn && conn->local_entry->mode & SILC_UMODE_ROUTER_OPERATOR) {
+ act = gaim_menu_action_new(_("Kill User"),
+ GAIM_CALLBACK(silcgaim_buddy_kill),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ if (client_entry) {
+ wb = silc_calloc(1, sizeof(*wb));
+ wb->sg = sg;
+ wb->client_entry = client_entry;
+ act = gaim_menu_action_new(_("Draw On Whiteboard"),
+ GAIM_CALLBACK(silcgaim_buddy_wb),
+ (void *)wb, NULL);
+ m = g_list_append(m, act);
+ }
+ return m;
+}
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+void silcgaim_buddy_set_icon(GaimConnection *gc, const char *iconfile)
+{
+ SilcGaim sg = gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ SilcMime mime;
+ GaimBuddyIcon ic;
+ char type[32];
+ unsigned char *icon;
+ const char *t;
+ struct stat st;
+ FILE *fp;
+ SilcAttributeObjMime obj;
+
+ /* Remove */
+ if (!iconfile) {
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_USER_ICON, NULL);
+ return;
+ }
+
+ /* Add */
+ if (g_stat(iconfile, &st) < 0)
+ return;
+ fp = g_fopen(iconfile, "rb");
+ if (!fp)
+ return;
+ ic.data = g_malloc(st.st_size);
+ if (!ic.data)
+ return;
+ ic.len = fread(ic.data, 1, st.st_size, fp);
+ fclose(fp);
+
+ mime = silc_mime_alloc();
+ if (!mime) {
+ g_free(ic.data);
+ return;
+ }
+
+ t = gaim_buddy_icon_get_type((const GaimBuddyIcon *)&ic);
+ if (!t) {
+ g_free(ic.data);
+ silc_mime_free(mime);
+ return;
+ }
+ if (!strcmp(t, "jpg"))
+ t = "jpeg";
+ g_snprintf(type, sizeof(type), "image/%s", t);
+ silc_mime_add_field(mime, "Content-Type", type);
+ silc_mime_add_data(mime, ic.data, ic.len);
+
+ obj.mime = icon = silc_mime_encode(mime, &obj.mime_len);
+ if (obj.mime)
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_USER_ICON, &obj, sizeof(obj));
+
+ silc_free(icon);
+ g_free(ic.data);
+ silc_mime_free(mime);
+}
+#endif
diff --git a/libpurple/protocols/silc/chat.c b/libpurple/protocols/silc/chat.c
new file mode 100644
index 0000000000..6c555f30c9
--- /dev/null
+++ b/libpurple/protocols/silc/chat.c
@@ -0,0 +1,1451 @@
+/*
+
+ 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"
+#include "wb.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";
+ pce->required = TRUE;
+ ci = g_list_append(ci, pce);
+
+ pce = g_new0(struct proto_chat_entry, 1);
+ pce->label = _("_Passphrase:");
+ pce->identifier = "passphrase";
+ pce->secret = TRUE;
+ ci = g_list_append(ci, pce);
+
+ return ci;
+}
+
+GHashTable *silcgaim_chat_info_defaults(GaimConnection *gc, const char *chat_name)
+{
+ GHashTable *defaults;
+
+ defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+ if (chat_name != NULL)
+ g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
+
+ return defaults;
+}
+
+static void
+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], *tmp2;
+ GString *s;
+ SilcChannelEntry channel;
+ SilcHashTableList htl;
+ SilcChannelUser chu;
+
+ if (!components)
+ return;
+
+ chname = g_hash_table_lookup(components, "channel");
+ if (!chname)
+ return;
+ channel = silc_client_get_channel(sg->client, sg->conn,
+ (char *)chname);
+ if (!channel) {
+ silc_client_get_channel_resolve(sg->client, sg->conn,
+ (char *)chname,
+ silcgaim_chat_getinfo_res,
+ components);
+ return;
+ }
+
+ s = g_string_new("");
+ tmp2 = g_markup_escape_text(channel->channel_name, -1);
+ g_string_append_printf(s, _("<b>Channel Name:</b> %s"), tmp2);
+ g_free(tmp2);
+ if (channel->user_list && silc_hash_table_count(channel->user_list))
+ g_string_append_printf(s, _("<br><b>User Count:</b> %d"),
+ (int)silc_hash_table_count(channel->user_list));
+
+ silc_hash_table_list(channel->user_list, &htl);
+ while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+ if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+ tmp2 = g_markup_escape_text(chu->client->nickname, -1);
+ g_string_append_printf(s, _("<br><b>Channel Founder:</b> %s"),
+ tmp2);
+ g_free(tmp2);
+ break;
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+
+ if (channel->channel_key)
+ g_string_append_printf(s, _("<br><b>Channel Cipher:</b> %s"),
+ silc_cipher_get_name(channel->channel_key));
+ if (channel->hmac)
+ /* Definition of HMAC: http://en.wikipedia.org/wiki/HMAC */
+ g_string_append_printf(s, _("<br><b>Channel HMAC:</b> %s"),
+ silc_hmac_get_name(channel->hmac));
+
+ if (channel->topic) {
+ tmp2 = g_markup_escape_text(channel->topic, -1);
+ g_string_append_printf(s, _("<br><b>Channel Topic:</b><br>%s"), tmp2);
+ g_free(tmp2);
+ }
+
+ if (channel->mode) {
+ g_string_append_printf(s, _("<br><b>Channel Modes:</b> "));
+ silcgaim_get_chmode_string(channel->mode, tmp, sizeof(tmp));
+ g_string_append(s, tmp);
+ }
+
+ if (channel->founder_key) {
+ char *fingerprint, *babbleprint;
+ unsigned char *pk;
+ SilcUInt32 pk_len;
+ pk = silc_pkcs_public_key_encode(channel->founder_key, &pk_len);
+ fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+ babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+
+ g_string_append_printf(s, _("<br><b>Founder Key Fingerprint:</b><br>%s"), fingerprint);
+ g_string_append_printf(s, _("<br><b>Founder Key Babbleprint:</b><br>%s"), babbleprint);
+
+ silc_free(fingerprint);
+ silc_free(babbleprint);
+ silc_free(pk);
+ }
+
+ buf = g_string_free(s, FALSE);
+ gaim_notify_formatted(gc, NULL, _("Channel Information"), NULL, buf, NULL, NULL);
+ g_free(buf);
+}
+
+
+static void
+silcgaim_chat_getinfo_menu(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat = (GaimChat *)node;
+ silcgaim_chat_getinfo(chat->account->gc, chat->components);
+}
+
+
+#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(GaimBlistNode *node, gpointer data);
+{
+
+}
+
+
+/**************************** Channel Ban List *******************************/
+
+static void
+silcgaim_chat_banlist(GaimBlistNode *node, gpointer data);
+{
+
+}
+#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(sg->gc, _("Open Public Key..."), NULL, FALSE,
+ 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);
+ } 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");
+ }
+
+ 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(sg->gc, _("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(sg->gc, _("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(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+ g_hash_table_lookup(chat->components, "channel"),
+ "+C", NULL);
+}
+
+
+/************************** Channel Private Groups **************************/
+
+/* Private groups are "virtual" channels. They are groups inside a channel.
+ This is implemented by using channel private keys. By knowing a channel
+ private key user becomes part of that group and is able to talk on that
+ group. Other users, on the same channel, won't be able to see the
+ messages of that group. It is possible to have multiple groups inside
+ a channel - and thus having multiple private keys on the channel. */
+
+typedef struct {
+ 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);
+
+ /* 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(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ SilcGaimCharPrv p;
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *g;
+ GaimRequestField *f;
+ char tmp[512];
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ p = silc_calloc(1, sizeof(*p));
+ if (!p)
+ return;
+ p->sg = sg;
+
+ p->channel = g_hash_table_lookup(chat->components, "channel");
+ p->c = 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(gc, _("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(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+ g_hash_table_lookup(chat->components, "channel"),
+ "-f", NULL);
+}
+
+static void
+silcgaim_chat_permanent(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+ const char *channel;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ if (!sg->conn)
+ return;
+
+ /* XXX we should have ability to define which founder
+ key to use. Now we use the user's own public key
+ (default key). */
+
+ /* Call CMODE */
+ channel = g_hash_table_lookup(chat->components, "channel");
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", channel,
+ "+f", NULL);
+}
+
+typedef struct {
+ 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(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ SilcGaimChatInput s;
+ SilcChannelEntry channel;
+ const char *ch;
+ char tmp[32];
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ if (!sg->conn)
+ return;
+
+ ch = g_strdup(g_hash_table_lookup(chat->components, "channel"));
+ channel = silc_client_get_channel(sg->client, sg->conn, (char *)ch);
+ if (!channel)
+ return;
+
+ s = silc_calloc(1, sizeof(*s));
+ if (!s)
+ return;
+ s->channel = ch;
+ s->sg = sg;
+ g_snprintf(tmp, sizeof(tmp), "%d", (int)channel->user_limit);
+ gaim_request_input(gc, _("User Limit"), NULL,
+ _("Set user limit on channel. Set to zero to reset user limit."),
+ tmp, FALSE, FALSE, NULL,
+ _("OK"), G_CALLBACK(silcgaim_chat_ulimit_cb),
+ _("Cancel"), G_CALLBACK(silcgaim_chat_ulimit_cb), s);
+}
+
+static void
+silcgaim_chat_resettopic(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+ g_hash_table_lookup(chat->components, "channel"),
+ "-t", NULL);
+}
+
+static void
+silcgaim_chat_settopic(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+ g_hash_table_lookup(chat->components, "channel"),
+ "+t", NULL);
+}
+
+static void
+silcgaim_chat_resetprivate(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+ g_hash_table_lookup(chat->components, "channel"),
+ "-p", NULL);
+}
+
+static void
+silcgaim_chat_setprivate(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+ g_hash_table_lookup(chat->components, "channel"),
+ "+p", NULL);
+}
+
+static void
+silcgaim_chat_resetsecret(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+ g_hash_table_lookup(chat->components, "channel"),
+ "-s", NULL);
+}
+
+static void
+silcgaim_chat_setsecret(GaimBlistNode *node, gpointer data)
+{
+ GaimChat *chat;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+ chat = (GaimChat *) node;
+ gc = gaim_account_get_connection(chat->account);
+ sg = gc->proto_data;
+
+ silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+ g_hash_table_lookup(chat->components, "channel"),
+ "+s", NULL);
+}
+
+typedef struct {
+ SilcGaim sg;
+ SilcChannelEntry channel;
+} *SilcGaimChatWb;
+
+static void
+silcgaim_chat_wb(GaimBlistNode *node, gpointer data)
+{
+ SilcGaimChatWb wb = data;
+ silcgaim_wb_init_ch(wb->sg, wb->channel);
+ silc_free(wb);
+}
+
+GList *silcgaim_chat_menu(GaimChat *chat)
+{
+ GHashTable *components = chat->components;
+ GaimConnection *gc = gaim_account_get_connection(chat->account);
+ SilcGaim sg = gc->proto_data;
+ SilcClientConnection conn = sg->conn;
+ const char *chname = NULL;
+ SilcChannelEntry channel = NULL;
+ SilcChannelUser chu = NULL;
+ SilcUInt32 mode = 0;
+
+ GList *m = NULL;
+ GaimMenuAction *act;
+
+ if (components)
+ chname = g_hash_table_lookup(components, "channel");
+ if (chname)
+ channel = silc_client_get_channel(sg->client, sg->conn,
+ (char *)chname);
+ if (channel) {
+ chu = silc_client_on_channel(channel, conn->local_entry);
+ if (chu)
+ mode = chu->mode;
+ }
+
+ if (strstr(chname, "[Private Group]"))
+ return NULL;
+
+ act = gaim_menu_action_new(_("Get Info"),
+ GAIM_CALLBACK(silcgaim_chat_getinfo_menu),
+ NULL, NULL);
+ m = g_list_append(m, act);
+
+#if 0 /* XXX For now these are not implemented. We need better
+ listview dialog from Gaim for these. */
+ if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+ act = gaim_menu_action_new(_("Invite List"),
+ GAIM_CALLBACK(silcgaim_chat_invitelist),
+ NULL, NULL);
+ m = g_list_append(m, act);
+
+ act = gaim_menu_action_new(_("Ban List"),
+ GAIM_CALLBACK(silcgaim_chat_banlist),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+#endif
+
+ if (chu) {
+ act = gaim_menu_action_new(_("Add Private Group"),
+ GAIM_CALLBACK(silcgaim_chat_prv),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ if (mode & SILC_CHANNEL_UMODE_CHANFO) {
+ act = gaim_menu_action_new(_("Channel Authentication"),
+ GAIM_CALLBACK(silcgaim_chat_chauth),
+ NULL, NULL);
+ m = g_list_append(m, act);
+
+ if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+ act = gaim_menu_action_new(_("Reset Permanent"),
+ GAIM_CALLBACK(silcgaim_chat_permanent_reset),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ } else {
+ act = gaim_menu_action_new(_("Set Permanent"),
+ GAIM_CALLBACK(silcgaim_chat_permanent),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+ }
+
+ if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+ act = gaim_menu_action_new(_("Set User Limit"),
+ GAIM_CALLBACK(silcgaim_chat_ulimit),
+ NULL, NULL);
+ m = g_list_append(m, act);
+
+ if (channel->mode & SILC_CHANNEL_MODE_TOPIC) {
+ act = gaim_menu_action_new(_("Reset Topic Restriction"),
+ GAIM_CALLBACK(silcgaim_chat_resettopic),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ } else {
+ act = gaim_menu_action_new(_("Set Topic Restriction"),
+ GAIM_CALLBACK(silcgaim_chat_settopic),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ if (channel->mode & SILC_CHANNEL_MODE_PRIVATE) {
+ act = gaim_menu_action_new(_("Reset Private Channel"),
+ GAIM_CALLBACK(silcgaim_chat_resetprivate),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ } else {
+ act = gaim_menu_action_new(_("Set Private Channel"),
+ GAIM_CALLBACK(silcgaim_chat_setprivate),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
+ if (channel->mode & SILC_CHANNEL_MODE_SECRET) {
+ act = gaim_menu_action_new(_("Reset Secret Channel"),
+ GAIM_CALLBACK(silcgaim_chat_resetsecret),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ } else {
+ act = gaim_menu_action_new(_("Set Secret Channel"),
+ GAIM_CALLBACK(silcgaim_chat_setsecret),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+ }
+
+ if (channel) {
+ SilcGaimChatWb wb;
+ wb = silc_calloc(1, sizeof(*wb));
+ wb->sg = sg;
+ wb->channel = channel;
+ act = gaim_menu_action_new(_("Draw On Whiteboard"),
+ GAIM_CALLBACK(silcgaim_chat_wb),
+ (void *)wb, NULL);
+ m = g_list_append(m, act);
+ }
+
+ 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, *flags = NULL;
+ char tmp[256];
+
+ if (!clients && retry < 1) {
+ /* Resolving users failed, try again. */
+ channel->context = SILC_32_TO_PTR(retry + 1);
+ silc_client_get_clients_by_channel(client, conn, channel,
+ 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(GAIM_CONV_TYPE_CHAT,
+ channel->channel_name, sg->account);
+ if (!convo)
+ return;
+
+ /* Add all users to channel */
+ silc_hash_table_list(channel->user_list, &htl);
+ while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+ GaimConvChatBuddyFlags f = GAIM_CBFLAGS_NONE;
+ if (!chu->client->nickname)
+ continue;
+ chu->context = SILC_32_TO_PTR(sg->channel_ids);
+
+ if (chu->mode & SILC_CHANNEL_UMODE_CHANFO)
+ f |= GAIM_CBFLAGS_FOUNDER;
+ if (chu->mode & SILC_CHANNEL_UMODE_CHANOP)
+ f |= GAIM_CBFLAGS_OP;
+ users = g_list_append(users, g_strdup(chu->client->nickname));
+ flags = g_list_append(flags, GINT_TO_POINTER(f));
+
+ if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+ if (chu->client == conn->local_entry)
+ g_snprintf(tmp, sizeof(tmp),
+ _("You are channel founder on <I>%s</I>"),
+ channel->channel_name);
+ else
+ g_snprintf(tmp, sizeof(tmp),
+ _("Channel founder on <I>%s</I> is <I>%s</I>"),
+ channel->channel_name, chu->client->nickname);
+
+ 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, NULL, flags, FALSE);
+ g_list_free(users);
+ g_list_free(flags);
+
+ /* Set topic */
+ if (channel->topic)
+ gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, channel->topic);
+
+ /* Set nick */
+ gaim_conv_chat_set_nick(GAIM_CONV_CHAT(convo), conn->local_entry->nickname);
+}
+
+char *silcgaim_get_chat_name(GHashTable *data)
+{
+ return g_strdup(g_hash_table_lookup(data, "channel"));
+}
+
+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 != NULL) && (*passphrase != '\0'))
+ silc_client_command_call(client, conn, NULL, "JOIN",
+ channel, passphrase, "-auth", "-founder", NULL);
+ else
+ silc_client_command_call(client, conn, NULL, "JOIN",
+ channel, "-auth", "-founder", NULL);
+}
+
+void 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, NULL);
+}
+
+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, GaimMessageFlags msgflags)
+{
+ 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;
+ char *msg2, *tmp;
+ gboolean found = FALSE;
+ gboolean sign = gaim_account_get_bool(sg->account, "sign-verify", FALSE);
+
+ if (!msg || !conn)
+ return 0;
+
+ flags = SILC_MESSAGE_FLAG_UTF8;
+
+ tmp = msg2 = gaim_unescape_html(msg);
+
+ if (!g_ascii_strncasecmp(msg2, "/me ", 4))
+ {
+ msg2 += 4;
+ if (!*msg2) {
+ g_free(tmp);
+ return 0;
+ }
+ flags |= SILC_MESSAGE_FLAG_ACTION;
+ } else if (strlen(msg) > 1 && msg[0] == '/') {
+ if (!silc_client_command_call(client, conn, msg + 1))
+ gaim_notify_error(gc, _("Call Command"), _("Cannot call command"),
+ _("Unknown command"));
+ g_free(tmp);
+ return 0;
+ }
+
+
+ if (sign)
+ flags |= SILC_MESSAGE_FLAG_SIGNED;
+
+ /* Get the channel private key if we are sending on
+ private group */
+ if (id > SILCGAIM_PRVGRP) {
+ GList *l;
+ SilcGaimPrvgrp prv;
+
+ for (l = sg->grps; l; l = l->next)
+ if (((SilcGaimPrvgrp)l->data)->id == id)
+ break;
+ if (!l) {
+ g_free(tmp);
+ return 0;
+ }
+ prv = l->data;
+ channel = silc_client_get_channel(sg->client, sg->conn,
+ (char *)prv->parentch);
+ if (!channel) {
+ g_free(tmp);
+ return 0;
+ }
+ key = prv->key;
+ }
+
+ if (!channel) {
+ /* Find channel by id */
+ silc_hash_table_list(conn->local_entry->channels, &htl);
+ while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+ if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+ found = TRUE;
+ break;
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+ if (!found) {
+ g_free(tmp);
+ return 0;
+ }
+ channel = chu->channel;
+ }
+
+ /* Send channel message */
+ ret = silc_client_send_channel_message(client, conn, channel, key,
+ flags, (unsigned char *)msg2,
+ strlen(msg2), TRUE);
+ if (ret) {
+ serv_got_chat_in(gc, id, gaim_connection_get_display_name(gc), 0, msg,
+ time(NULL));
+ }
+ g_free(tmp);
+
+ 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 (!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;
+ }
+}
diff --git a/libpurple/protocols/silc/ft.c b/libpurple/protocols/silc/ft.c
new file mode 100644
index 0000000000..3a1c936025
--- /dev/null
+++ b/libpurple/protocols/silc/ft.c
@@ -0,0 +1,412 @@
+/*
+
+ 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 session 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);
+ g_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 */
+ 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, 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 */
+ 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, NULL);
+ silc_free(context);
+}
+
+GaimXfer *silcgaim_ftp_new_xfer(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;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ if (!silc_parse_userfqdn(name, &nickname, NULL))
+ return NULL;
+
+ /* 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 NULL;
+ }
+
+ xfer = silc_calloc(1, sizeof(*xfer));
+
+ g_return_val_if_fail(xfer != NULL, NULL);
+
+ 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 NULL;
+ }
+ 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;
+
+ silc_free(clients);
+ silc_free(nickname);
+
+ return xfer->xfer;
+}
+
+void silcgaim_ftp_send_file(GaimConnection *gc, const char *name, const char *file)
+{
+ GaimXfer *xfer = silcgaim_ftp_new_xfer(gc, name);
+
+ g_return_if_fail(xfer != NULL);
+
+ /* Choose file to send */
+ if (file)
+ gaim_xfer_request_accepted(xfer, file);
+ else
+ gaim_xfer_request(xfer);
+}
diff --git a/libpurple/protocols/silc/ops.c b/libpurple/protocols/silc/ops.c
new file mode 100644
index 0000000000..b7a8b5832a
--- /dev/null
+++ b/libpurple/protocols/silc/ops.c
@@ -0,0 +1,2065 @@
+/*
+
+ 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"
+#include "imgstore.h"
+#include "wb.h"
+
+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);
+static void
+silc_private_message(SilcClient client, SilcClientConnection conn,
+ SilcClientEntry sender, SilcMessagePayload payload,
+ SilcMessageFlags flags, const unsigned char *message,
+ SilcUInt32 message_len);
+
+/* 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 */
+}
+
+#ifdef HAVE_SILCMIME_H
+/* Processes incoming MIME message. Can be private message or channel
+ message. */
+
+static void
+silcgaim_mime_message(SilcClient client, SilcClientConnection conn,
+ SilcClientEntry sender, SilcChannelEntry channel,
+ SilcMessagePayload payload, SilcChannelPrivateKey key,
+ SilcMessageFlags flags, SilcMime mime,
+ gboolean recursive)
+{
+ GaimConnection *gc = client->application;
+ SilcGaim sg = gc->proto_data;
+ const char *type;
+ const unsigned char *data;
+ SilcUInt32 data_len;
+ GaimMessageFlags cflags = 0;
+ GaimConversation *convo = NULL;
+
+ if (!mime)
+ return;
+
+ /* Check for fragmented MIME message */
+ if (silc_mime_is_partial(mime)) {
+ if (!sg->mimeass)
+ sg->mimeass = silc_mime_assembler_alloc();
+
+ /* Defragment */
+ mime = silc_mime_assemble(sg->mimeass, mime);
+ if (!mime)
+ /* More fragments to come */
+ return;
+
+ /* Process the complete message */
+ silcgaim_mime_message(client, conn, sender, channel,
+ payload, key, flags, mime, FALSE);
+ return;
+ }
+
+ /* Check for multipart message */
+ if (silc_mime_is_multipart(mime)) {
+ SilcMime p;
+ const char *mtype;
+ SilcDList parts = silc_mime_get_multiparts(mime, &mtype);
+
+ /* Only "mixed" type supported */
+ if (strcmp(mtype, "mixed"))
+ goto out;
+
+ silc_dlist_start(parts);
+ while ((p = silc_dlist_get(parts)) != SILC_LIST_END) {
+ /* Recursively process parts */
+ silcgaim_mime_message(client, conn, sender, channel,
+ payload, key, flags, p, TRUE);
+ }
+ goto out;
+ }
+
+ /* Get content type and MIME data */
+ type = silc_mime_get_field(mime, "Content-Type");
+ if (!type)
+ goto out;
+ data = silc_mime_get_data(mime, &data_len);
+ if (!data)
+ goto out;
+
+ /* Process according to content type */
+
+ /* Plain text */
+ if (strstr(type, "text/plain")) {
+ /* Default is UTF-8, don't check for other charsets */
+ if (!strstr(type, "utf-8"))
+ goto out;
+
+ if (channel)
+ silc_channel_message(client, conn, sender, channel,
+ payload, key,
+ SILC_MESSAGE_FLAG_UTF8, data,
+ data_len);
+ else
+ silc_private_message(client, conn, sender, payload,
+ SILC_MESSAGE_FLAG_UTF8, data,
+ data_len);
+ goto out;
+ }
+
+ /* Image */
+ if (strstr(type, "image/png") ||
+ strstr(type, "image/jpeg") ||
+ strstr(type, "image/gif") ||
+ strstr(type, "image/tiff")) {
+ char tmp[32];
+ int imgid;
+
+ /* Get channel convo (if message is for channel) */
+ if (key && channel) {
+ 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(GAIM_CONV_TYPE_CHAT,
+ prv->channel, sg->account);
+ break;
+ }
+ }
+ if (channel && !convo)
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,
+ channel->channel_name, sg->account);
+ if (channel && !convo)
+ goto out;
+
+ imgid = gaim_imgstore_add(data, data_len, "");
+ if (imgid) {
+ cflags |= GAIM_MESSAGE_IMAGES | GAIM_MESSAGE_RECV;
+ g_snprintf(tmp, sizeof(tmp), "<IMG ID=\"%d\">", imgid);
+
+ if (channel)
+ serv_got_chat_in(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)),
+ sender->nickname ?
+ sender->nickname :
+ "<unknown>", cflags,
+ tmp, time(NULL));
+ else
+ serv_got_im(gc, sender->nickname ?
+ sender->nickname : "<unknown>",
+ tmp, cflags, time(NULL));
+
+ gaim_imgstore_unref(imgid);
+ cflags = 0;
+ }
+ goto out;
+ }
+
+ /* Whiteboard message */
+ if (strstr(type, "application/x-wb") &&
+ !gaim_account_get_bool(sg->account, "block-wb", FALSE)) {
+ if (channel)
+ silcgaim_wb_receive_ch(client, conn, sender, channel,
+ payload, flags, data, data_len);
+ else
+ silcgaim_wb_receive(client, conn, sender, payload,
+ flags, data, data_len);
+ goto out;
+ }
+
+ out:
+ if (!recursive)
+ silc_mime_free(mime);
+}
+#endif /* HAVE_SILCMIME_H */
+
+/* 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, *tmp;
+
+ 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(GAIM_CONV_TYPE_CHAT,
+ prv->channel, sg->account);
+ break;
+ }
+ }
+ if (!convo)
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,
+ channel->channel_name, sg->account);
+ if (!convo)
+ return;
+
+ if (flags & SILC_MESSAGE_FLAG_SIGNED &&
+ gaim_account_get_bool(sg->account, "sign-verify", FALSE)) {
+ /* XXX */
+ }
+
+ if (flags & SILC_MESSAGE_FLAG_DATA) {
+ /* Process MIME message */
+#ifdef HAVE_SILCMIME_H
+ SilcMime mime;
+ mime = silc_mime_decode(message, message_len);
+ silcgaim_mime_message(client, conn, sender, channel, payload,
+ key, flags, mime, FALSE);
+#else
+ char type[128], enc[128];
+ unsigned char *data;
+ SilcUInt32 data_len;
+
+ memset(type, 0, sizeof(type));
+ memset(enc, 0, sizeof(enc));
+
+ if (!silc_mime_parse(message, message_len, NULL, 0,
+ type, sizeof(type) - 1, enc, sizeof(enc) - 1, &data,
+ &data_len))
+ return;
+
+ if (!strcmp(type, "application/x-wb") &&
+ !strcmp(enc, "binary") &&
+ !gaim_account_get_bool(sg->account, "block-wb", FALSE))
+ silcgaim_wb_receive_ch(client, conn, sender, channel,
+ payload, flags, data, data_len);
+#endif
+ return;
+ }
+
+ if (flags & SILC_MESSAGE_FLAG_ACTION) {
+ msg = g_strdup_printf("/me %s",
+ (const char *)message);
+ if (!msg)
+ return;
+
+ tmp = g_markup_escape_text(msg, -1);
+ /* Send to Gaim */
+ serv_got_chat_in(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)),
+ sender->nickname ?
+ sender->nickname : "<unknown>", 0,
+ tmp, time(NULL));
+ g_free(tmp);
+ 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) {
+ tmp = g_markup_escape_text((const char *)message, -1);
+ /* Send to Gaim */
+ serv_got_chat_in(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)),
+ sender->nickname ?
+ sender->nickname : "<unknown>", 0,
+ tmp, time(NULL));
+ g_free(tmp);
+ }
+}
+
+
+/* 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, *tmp;
+
+ if (!message)
+ return;
+
+ if (sender->nickname)
+ /* XXX - Should this be GAIM_CONV_TYPE_IM? */
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_ANY,
+ sender->nickname, sg->account);
+
+ if (flags & SILC_MESSAGE_FLAG_SIGNED &&
+ gaim_account_get_bool(sg->account, "sign-verify", FALSE)) {
+ /* XXX */
+ }
+
+ if (flags & SILC_MESSAGE_FLAG_DATA) {
+#ifdef HAVE_SILCMIME_H
+ /* Process MIME message */
+ SilcMime mime;
+ mime = silc_mime_decode(message, message_len);
+ silcgaim_mime_message(client, conn, sender, NULL, payload,
+ NULL, flags, mime, FALSE);
+#else
+ char type[128], enc[128];
+ unsigned char *data;
+ SilcUInt32 data_len;
+
+ memset(type, 0, sizeof(type));
+ memset(enc, 0, sizeof(enc));
+
+ if (!silc_mime_parse(message, message_len, NULL, 0,
+ type, sizeof(type) - 1, enc, sizeof(enc) - 1, &data,
+ &data_len))
+ return;
+
+ if (!strcmp(type, "application/x-wb") &&
+ !strcmp(enc, "binary") &&
+ !gaim_account_get_bool(sg->account, "block-wb", FALSE))
+ silcgaim_wb_receive(client, conn, sender, payload,
+ flags, data, data_len);
+#endif
+ return;
+ }
+
+ if (flags & SILC_MESSAGE_FLAG_ACTION && convo) {
+ msg = g_strdup_printf("/me %s",
+ (const char *)message);
+ if (!msg)
+ return;
+
+ tmp = g_markup_escape_text(msg, -1);
+ /* Send to Gaim */
+ serv_got_im(gc, sender->nickname ?
+ sender->nickname : "<unknown>",
+ tmp, 0, time(NULL));
+ g_free(msg);
+ g_free(tmp);
+ 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) {
+ tmp = g_markup_escape_text((const char *)message, -1);
+ /* Send to Gaim */
+ serv_got_im(gc, sender->nickname ?
+ sender->nickname : "<unknown>",
+ tmp, 0, time(NULL));
+ g_free(tmp);
+ }
+}
+
+
+/* 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;
+ 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;
+ 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"), strdup(name));
+ serv_got_chat_invite(gc, name, client_entry->nickname, NULL, components);
+ }
+ 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(GAIM_CONV_TYPE_CHAT,
+ 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, GAIM_CBFLAGS_NONE, TRUE);
+
+ break;
+
+ case SILC_NOTIFY_TYPE_LEAVE:
+ client_entry = va_arg(va, SilcClientEntry);
+ channel = va_arg(va, SilcChannelEntry);
+
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,
+ 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(GAIM_CONV_TYPE_CHAT,
+ 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:
+ {
+ char *esc, *tmp2;
+ 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(GAIM_CONV_TYPE_CHAT,
+ channel->channel_name, sg->account);
+ if (!convo)
+ break;
+
+ if (!tmp)
+ break;
+
+ esc = g_markup_escape_text(tmp, -1);
+ tmp2 = gaim_markup_linkify(esc);
+ g_free(esc);
+
+ 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, tmp2);
+ gaim_conv_chat_write(GAIM_CONV_CHAT(convo), client_entry->nickname,
+ buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+ gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo),
+ client_entry->nickname, tmp);
+ } 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, tmp2);
+ gaim_conv_chat_write(GAIM_CONV_CHAT(convo), server_entry->server_name,
+ buf, GAIM_MESSAGE_SYSTEM, time(NULL));
+ gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo),
+ server_entry->server_name, tmp);
+ } 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, tmp2);
+ 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),
+ channel->channel_name, tmp);
+ } else {
+ gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, tmp);
+ }
+
+ g_free(tmp2);
+
+ 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(GAIM_CONV_TYPE_CHAT,
+ chu->channel->channel_name, sg->account);
+ if (!convo)
+ continue;
+ if (gaim_conv_chat_find_user(GAIM_CONV_CHAT(convo), client_entry->nickname))
+ 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);
+ (void)va_arg(va, SilcBuffer);
+ channel = va_arg(va, SilcChannelEntry);
+
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,
+ 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:
+ {
+ GaimConvChatBuddyFlags flags = GAIM_CBFLAGS_NONE;
+ 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(GAIM_CONV_TYPE_CHAT,
+ 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);
+ if (mode & SILC_CHANNEL_UMODE_CHANFO)
+ flags |= GAIM_CBFLAGS_FOUNDER;
+ if (mode & SILC_CHANNEL_UMODE_CHANOP)
+ flags |= GAIM_CBFLAGS_OP;
+ } 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));
+ gaim_conv_chat_user_set_flags(GAIM_CONV_CHAT(convo), client_entry2->nickname, flags);
+ 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(GAIM_CONV_TYPE_CHAT,
+ 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_remove_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(GAIM_CONV_TYPE_CHAT,
+ 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(GAIM_CONV_TYPE_CHAT,
+ 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(GAIM_CONV_TYPE_CHAT,
+ 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 (f && !strcmp(f, buf))
+ goto cont;
+ b = NULL;
+ }
+ }
+ }
+ }
+ cont:
+ if (!b) {
+ /* Find buddy by nickname */
+ b = gaim_find_buddy(sg->account, client_entry->nickname);
+ if (!b) {
+ gaim_debug_warning("silc", "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_prpl_got_user_status(gaim_buddy_get_account(b), gaim_buddy_get_name(b), SILCGAIM_STATUS_ID_AVAILABLE, NULL);
+ }
+ 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_prpl_got_user_status(gaim_buddy_get_account(b), gaim_buddy_get_name(b), SILCGAIM_STATUS_ID_OFFLINE, NULL);
+ }
+ } else if (notify == SILC_NOTIFY_TYPE_SIGNOFF ||
+ notify == SILC_NOTIFY_TYPE_SERVER_SIGNOFF ||
+ notify == SILC_NOTIFY_TYPE_KILLED) {
+ client_entry->mode = mode;
+ gaim_prpl_got_user_status(gaim_buddy_get_account(b), gaim_buddy_get_name(b), SILCGAIM_STATUS_ID_OFFLINE, NULL);
+ } else if (notify == SILC_NOTIFY_TYPE_NONE) {
+ client_entry->mode = mode;
+ gaim_prpl_got_user_status(gaim_buddy_get_account(b), gaim_buddy_get_name(b), SILCGAIM_STATUS_ID_AVAILABLE, NULL);
+ }
+ }
+ break;
+
+ default:
+ gaim_debug_info("silc", "Unhandled notification: %d\n", type);
+ 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((char *)cmd_context->argv[2], "+C"))
+ sg->chpk = TRUE;
+ else
+ sg->chpk = FALSE;
+ break;
+
+ default:
+ break;
+ }
+}
+
+#if 0
+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, "%s:\n\n", _("Personal Information"));
+ if (vcard.full_name)
+ g_string_append_printf(s, "%s:\t\t%s\n",
+ _("Full Name"),
+ vcard.full_name);
+ if (vcard.first_name)
+ g_string_append_printf(s, "%s:\t%s\n",
+ _("First Name"),
+ vcard.first_name);
+ if (vcard.middle_names)
+ g_string_append_printf(s, "%s:\t%s\n",
+ _("Middle Name"),
+ vcard.middle_names);
+ if (vcard.family_name)
+ g_string_append_printf(s, "%s:\t%s\n",
+ _("Family Name"),
+ vcard.family_name);
+ if (vcard.nickname)
+ g_string_append_printf(s, "%s:\t\t%s\n",
+ _("Nickname"),
+ vcard.nickname);
+ if (vcard.bday)
+ g_string_append_printf(s, "%s:\t\t%s\n",
+ _("Birth Day"),
+ vcard.bday);
+ if (vcard.title)
+ g_string_append_printf(s, "%s:\t\t%s\n",
+ _("Job Title"),
+ vcard.title);
+ if (vcard.role)
+ g_string_append_printf(s, "%s:\t\t%s\n",
+ _("Job Role"),
+ vcard.role);
+ if (vcard.org_name)
+ g_string_append_printf(s, "%s:\t%s\n",
+ _("Organization"),
+ vcard.org_name);
+ if (vcard.org_unit)
+ g_string_append_printf(s, "%s:\t\t%s\n",
+ _("Unit"),
+ vcard.org_unit);
+ if (vcard.url)
+ g_string_append_printf(s, "%s:\t%s\n",
+ _("Homepage"),
+ vcard.url);
+ if (vcard.label)
+ g_string_append_printf(s, "%s:\t%s\n",
+ _("Address"),
+ vcard.label);
+ for (i = 0; i < vcard.num_tels; i++) {
+ if (vcard.tels[i].telnum)
+ g_string_append_printf(s, "%s:\t\t\t%s\n",
+ _("Phone"),
+ vcard.tels[i].telnum);
+ }
+ for (i = 0; i < vcard.num_emails; i++) {
+ if (vcard.emails[i].address)
+ g_string_append_printf(s, "%s:\t\t%s\n",
+ _("E-Mail"),
+ vcard.emails[i].address);
+ }
+ if (vcard.note)
+ g_string_append_printf(s, "\n%s:\t\t%s\n",
+ _("Note"),
+ vcard.note);
+ break;
+ }
+ }
+
+ buf = g_string_free(s, FALSE);
+ gaim_notify_info(NULL, _("User Information"), _("User Information"),
+ buf);
+ g_free(buf);
+}
+#endif
+
+/* 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 tmp[1024], *tmp2;
+ char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr;
+ GaimNotifyUserInfo *user_info;
+
+ 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);
+
+ user_info = gaim_notify_user_info_new();
+ tmp2 = g_markup_escape_text(client_entry->nickname, -1);
+ gaim_notify_user_info_add_pair(user_info, _("Nickname"), tmp2);
+ g_free(tmp2);
+ if (client_entry->realname) {
+ tmp2 = g_markup_escape_text(client_entry->realname, -1);
+ gaim_notify_user_info_add_pair(user_info, _("Real Name"), tmp2);
+ g_free(tmp2);
+ }
+ if (client_entry->username) {
+ tmp2 = g_markup_escape_text(client_entry->username, -1);
+ if (client_entry->hostname) {
+ gchar *tmp3;
+ tmp3 = g_strdup_printf("%s@%s", tmp2, client_entry->hostname);
+ gaim_notify_user_info_add_pair(user_info, _("Username"), tmp3);
+ g_free(tmp3);
+ } else
+ gaim_notify_user_info_add_pair(user_info, _("Username"), tmp2);
+ g_free(tmp2);
+ }
+
+ if (client_entry->mode) {
+ memset(tmp, 0, sizeof(tmp));
+ silcgaim_get_umode_string(client_entry->mode,
+ tmp, sizeof(tmp) - strlen(tmp));
+ gaim_notify_user_info_add_pair(user_info, _("User Modes"), tmp);
+ }
+
+ silcgaim_parse_attrs(client_entry->attrs, &moodstr, &statusstr, &contactstr, &langstr, &devicestr, &tzstr, &geostr);
+ if (moodstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Mood"), moodstr);
+ g_free(moodstr);
+ }
+
+ if (statusstr) {
+ tmp2 = g_markup_escape_text(statusstr, -1);
+ gaim_notify_user_info_add_pair(user_info, _("Status Text"), tmp2);
+ g_free(statusstr);
+ g_free(tmp2);
+ }
+
+ if (contactstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Preferred Contact"), contactstr);
+ g_free(contactstr);
+ }
+
+ if (langstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Preferred Language"), langstr);
+ g_free(langstr);
+ }
+
+ if (devicestr) {
+ gaim_notify_user_info_add_pair(user_info, _("Device"), devicestr);
+ g_free(devicestr);
+ }
+
+ if (tzstr) {
+ gaim_notify_user_info_add_pair(user_info, _("Timezone"), tzstr);
+ g_free(tzstr);
+ }
+
+ if (geostr) {
+ gaim_notify_user_info_add_pair(user_info, _("Geolocation"), geostr);
+ g_free(geostr);
+ }
+
+ if (client_entry->server)
+ gaim_notify_user_info_add_pair(user_info, _("Server"), client_entry->server);
+
+ 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;
+
+ 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 = (char *)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);
+
+ }
+ tmp2 = g_markup_escape_text(tmp, -1);
+ gaim_notify_user_info_add_pair(user_info, _("Currently on"), tmp2);
+ g_free(tmp2);
+ 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);
+ gaim_notify_user_info_add_pair(user_info, _("Public Key Fingerprint"), fingerprint);
+ gaim_notify_user_info_add_pair(user_info, _("Public Key Babbleprint"), babbleprint);
+ silc_free(fingerprint);
+ silc_free(babbleprint);
+ silc_free(pk);
+ }
+
+#if 0 /* XXX for now, let's not show attrs here */
+ if (client_entry->attrs)
+ gaim_request_action(gc, _("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_userinfo(gc, client_entry->nickname, user_info, NULL, NULL);
+ gaim_notify_user_info_destroy(user_info);
+ }
+ break;
+
+ case SILC_COMMAND_WHOWAS:
+ {
+ SilcClientEntry client_entry;
+ char *nickname, *realname, *username, *tmp;
+ GaimNotifyUserInfo *user_info;
+
+ if (!success) {
+ gaim_notify_error(gc, _("User Information"),
+ _("Cannot get user information"),
+ silc_get_status_message(status));
+ break;
+ }
+
+ client_entry = va_arg(vp, SilcClientEntry);
+ nickname = va_arg(vp, char *);
+ username = va_arg(vp, char *);
+ realname = va_arg(vp, char *);
+ if (!nickname)
+ break;
+
+ user_info = gaim_notify_user_info_new();
+ tmp = g_markup_escape_text(nickname, -1);
+ gaim_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
+ g_free(tmp);
+ if (realname) {
+ tmp = g_markup_escape_text(realname, -1);
+ gaim_notify_user_info_add_pair(user_info, _("Real Name"), tmp);
+ g_free(tmp);
+ }
+ if (username) {
+ tmp = g_markup_escape_text(username, -1);
+ if (client_entry && client_entry->hostname) {
+ gchar *tmp3;
+ tmp3 = g_strdup_printf("%s@%s", tmp, client_entry->hostname);
+ gaim_notify_user_info_add_pair(user_info, _("Username"), tmp3);
+ g_free(tmp3);
+ } else
+ gaim_notify_user_info_add_pair(user_info, _("Username"), tmp);
+ g_free(tmp);
+ }
+ if (client_entry && client_entry->server)
+ gaim_notify_user_info_add_pair(user_info, _("Server"), client_entry->server);
+
+
+ if (client_entry && 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);
+ gaim_notify_user_info_add_pair(user_info, _("Public Key Fingerprint"), fingerprint);
+ gaim_notify_user_info_add_pair(user_info, _("Public Key Babbleprint"), babbleprint);
+ silc_free(fingerprint);
+ silc_free(babbleprint);
+ silc_free(pk);
+ }
+
+ gaim_notify_userinfo(gc, nickname, user_info, NULL, NULL);
+ gaim_notify_user_info_destroy(user_info);
+ }
+ 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(GAIM_CONV_TYPE_CHAT,
+ channel->channel_name, sg->account);
+ if (!convo) {
+ gaim_debug_error("silc", "Got a topic for %s, which doesn't exist\n",
+ channel->channel_name);
+ break;
+ }
+
+ /* Set topic */
+ if (channel->topic)
+ gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, channel->topic);
+ }
+ break;
+
+ case SILC_COMMAND_NICK:
+ {
+ /* I don't think we should need to do this because the server should
+ * be sending a SILC_NOTIFY_TYPE_NICK_CHANGE when we change our own
+ * nick, but it isn't, so we deal with it here instead. Stu. */
+ SilcClientEntry local_entry;
+ SilcHashTableList htl;
+ SilcChannelUser chu;
+ const char *oldnick;
+
+ if (!success) {
+ gaim_notify_error(gc, _("Nick"), _("Failed to change nickname"),
+ silc_get_status_message(status));
+ return;
+ }
+
+ local_entry = va_arg(vp, SilcClientEntry);
+
+ /* Change nick on all channels */
+ silc_hash_table_list(local_entry->channels, &htl);
+ while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,
+ chu->channel->channel_name, sg->account);
+ if (!convo)
+ continue;
+ oldnick = gaim_conv_chat_get_nick(GAIM_CONV_CHAT(convo));
+ if (strcmp(oldnick, gaim_normalize(gaim_conversation_get_account(convo), local_entry->nickname))) {
+ gaim_conv_chat_rename_user(GAIM_CONV_CHAT(convo),
+ oldnick, local_entry->nickname);
+ gaim_conv_chat_set_nick(GAIM_CONV_CHAT(convo), local_entry->nickname);
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+
+ gaim_connection_set_display_name(gc, local_entry->nickname);
+ }
+ break;
+
+ case SILC_COMMAND_LIST:
+ {
+ char *topic, *name;
+ int usercount;
+ GaimRoomlistRoom *room;
+
+ if (sg->roomlist_canceled)
+ break;
+
+ if (!success) {
+ gaim_notify_error(gc, _("Error"), _("Error retrieving 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 *);
+ if (!name) {
+ 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;
+ }
+ 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:
+ {
+
+ 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;
+ }
+
+ (void)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(gc, NULL, _("Server Information"), tmp);
+ }
+ }
+ break;
+
+ case SILC_COMMAND_STATS:
+ {
+ SilcUInt32 starttime, uptime, my_clients, my_channels, my_server_ops,
+ my_router_ops, cell_clients, cell_channels, cell_servers,
+ clients, channels, servers, routers, server_ops, router_ops;
+ SilcUInt32 buffer_length;
+ SilcBufferStruct buf;
+
+ unsigned char *server_stats;
+ char *msg;
+
+ if (!success) {
+ gaim_notify_error(gc, _("Server Statistics"),
+ _("Cannot get server statistics"),
+ silc_get_status_message(status));
+ return;
+ }
+
+ server_stats = va_arg(vp, unsigned char *);
+ buffer_length = va_arg(vp, SilcUInt32);
+ if (!server_stats || !buffer_length) {
+ gaim_notify_error(gc, _("Server Statistics"),
+ _("No server statistics available"), NULL);
+ break;
+ }
+ silc_buffer_set(&buf, server_stats, buffer_length);
+ silc_buffer_unformat(&buf,
+ SILC_STR_UI_INT(&starttime),
+ SILC_STR_UI_INT(&uptime),
+ SILC_STR_UI_INT(&my_clients),
+ SILC_STR_UI_INT(&my_channels),
+ SILC_STR_UI_INT(&my_server_ops),
+ SILC_STR_UI_INT(&my_router_ops),
+ SILC_STR_UI_INT(&cell_clients),
+ SILC_STR_UI_INT(&cell_channels),
+ SILC_STR_UI_INT(&cell_servers),
+ SILC_STR_UI_INT(&clients),
+ SILC_STR_UI_INT(&channels),
+ SILC_STR_UI_INT(&servers),
+ SILC_STR_UI_INT(&routers),
+ SILC_STR_UI_INT(&server_ops),
+ SILC_STR_UI_INT(&router_ops),
+ SILC_STR_END);
+
+ msg = g_strdup_printf(_("Local server start time: %s\n"
+ "Local server uptime: %s\n"
+ "Local server clients: %d\n"
+ "Local server channels: %d\n"
+ "Local server operators: %d\n"
+ "Local router operators: %d\n"
+ "Local cell clients: %d\n"
+ "Local cell channels: %d\n"
+ "Local cell servers: %d\n"
+ "Total clients: %d\n"
+ "Total channels: %d\n"
+ "Total servers: %d\n"
+ "Total routers: %d\n"
+ "Total server operators: %d\n"
+ "Total router operators: %d\n"),
+ silc_get_time(starttime),
+ gaim_str_seconds_to_string((int)uptime),
+ (int)my_clients, (int)my_channels, (int)my_server_ops, (int)my_router_ops,
+ (int)cell_clients, (int)cell_channels, (int)cell_servers,
+ (int)clients, (int)channels, (int)servers, (int)routers,
+ (int)server_ops, (int)router_ops);
+
+ gaim_notify_info(gc, NULL,
+ _("Network Statistics"), msg);
+ g_free(msg);
+ }
+ break;
+
+ case SILC_COMMAND_PING:
+ {
+ if (!success) {
+ gaim_notify_error(gc, _("Ping"), _("Ping failed"),
+ silc_get_status_message(status));
+ return;
+ }
+
+ gaim_notify_info(gc, _("Ping"), _("Ping reply received from server"),
+ NULL);
+ }
+ 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:
+ if (success)
+ gaim_debug_info("silc", "Unhandled command: %d (succeeded)\n", command);
+ else
+ gaim_debug_info("silc", "Unhandled command: %d (failed: %s)\n", command,
+ silc_get_status_message(status));
+ 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 object 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;
+ gboolean reject_watch, block_invites, block_ims;
+
+ if (gc == NULL) {
+ silc_client_close_connection(client, conn);
+ return;
+ }
+ sg = gc->proto_data;
+
+ switch (status) {
+ case SILC_CLIENT_CONN_SUCCESS:
+ case SILC_CLIENT_CONN_SUCCESS_RESUME:
+ gaim_connection_set_state(gc, GAIM_CONNECTED);
+
+ /* Send the server our buddy list */
+ silcgaim_send_buddylist(gc);
+
+ g_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"));
+ g_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."));
+ g_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)
+ g_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
+ /* TODO: Does this work correctly? Maybe we need to set wants_to_die? */
+ gaim_account_disconnect(gaim_connection_get_account(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;
+ const char *password;
+
+ /* 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 (gaim_account_get_bool(sg->account, "pubkey-auth", FALSE)) {
+ completion(TRUE, SILC_AUTH_PUBLIC_KEY, NULL, 0, context);
+ return;
+ }
+ password = gaim_connection_get_password(gc);
+ if (password && *password) {
+ completion(TRUE, SILC_AUTH_PASSWORD, (unsigned char *)password, strlen(password), 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(client->application, _("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));
+ g_unlink(file);
+ silc_file_writefile(file, (char *)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
+};
diff --git a/libpurple/protocols/silc/pk.c b/libpurple/protocols/silc/pk.c
new file mode 100644
index 0000000000..b628c82fe4
--- /dev/null
+++ b/libpurple/protocols/silc/pk.c
@@ -0,0 +1,272 @@
+/*
+
+ 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(verify->client->application, _("Verify Public Key"), tmp, tmp2,
+ GAIM_DEFAULT_ACTION_NONE, 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 (g_stat(ipf, &st) < 0 && (!hostf || g_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);
+ }
+}
diff --git a/libpurple/protocols/silc/silc.c b/libpurple/protocols/silc/silc.c
new file mode 100644
index 0000000000..cb3f6c409e
--- /dev/null
+++ b/libpurple/protocols/silc/silc.c
@@ -0,0 +1,1920 @@
+/*
+
+ silcgaim.c
+
+ Author: Pekka Riikonen <priikone@silcnet.org>
+
+ Copyright (C) 2004 - 2005 Pekka Riikonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcgaim.h"
+#include "version.h"
+#include "wb.h"
+
+extern SilcClientOperations ops;
+static GaimPlugin *silc_plugin = NULL;
+
+static const char *
+silcgaim_list_icon(GaimAccount *a, GaimBuddy *b)
+{
+ return (const char *)"silc";
+}
+
+static void
+silcgaim_list_emblems(GaimBuddy *b, const char **se, const char **sw,
+ const char **nw, const char **ne)
+{
+}
+
+static GList *
+silcgaim_away_states(GaimAccount *account)
+{
+ GaimStatusType *type;
+ GList *types = NULL;
+
+ type = gaim_status_type_new_full(GAIM_STATUS_AVAILABLE, SILCGAIM_STATUS_ID_AVAILABLE, NULL, FALSE, TRUE, FALSE);
+ types = g_list_append(types, type);
+ type = gaim_status_type_new_full(GAIM_STATUS_AVAILABLE, SILCGAIM_STATUS_ID_HYPER, _("Hyper Active"), FALSE, TRUE, FALSE);
+ types = g_list_append(types, type);
+ type = gaim_status_type_new_full(GAIM_STATUS_AWAY, SILCGAIM_STATUS_ID_AWAY, NULL, FALSE, TRUE, FALSE);
+ types = g_list_append(types, type);
+ type = gaim_status_type_new_full(GAIM_STATUS_UNAVAILABLE, SILCGAIM_STATUS_ID_BUSY, _("Busy"), FALSE, TRUE, FALSE);
+ types = g_list_append(types, type);
+ type = gaim_status_type_new_full(GAIM_STATUS_AWAY, SILCGAIM_STATUS_ID_INDISPOSED, _("Indisposed"), FALSE, TRUE, FALSE);
+ types = g_list_append(types, type);
+ type = gaim_status_type_new_full(GAIM_STATUS_AWAY, SILCGAIM_STATUS_ID_PAGE, _("Wake Me Up"), FALSE, TRUE, FALSE);
+ types = g_list_append(types, type);
+ type = gaim_status_type_new_full(GAIM_STATUS_OFFLINE, SILCGAIM_STATUS_ID_OFFLINE, NULL, FALSE, TRUE, FALSE);
+ types = g_list_append(types, type);
+
+ return types;
+}
+
+static void
+silcgaim_set_status(GaimAccount *account, GaimStatus *status)
+{
+ GaimConnection *gc = gaim_account_get_connection(account);
+ SilcGaim sg = NULL;
+ SilcUInt32 mode;
+ SilcBuffer idp;
+ unsigned char mb[4];
+ const char *state;
+
+ if (gc != NULL)
+ sg = gc->proto_data;
+
+ if (status == NULL)
+ return;
+
+ state = gaim_status_get_id(status);
+
+ if (state == NULL)
+ return;
+
+ if ((sg == NULL) || (sg->conn == NULL))
+ return;
+
+ mode = sg->conn->local_entry->mode;
+ mode &= ~(SILC_UMODE_GONE |
+ SILC_UMODE_HYPER |
+ SILC_UMODE_BUSY |
+ SILC_UMODE_INDISPOSED |
+ SILC_UMODE_PAGE);
+
+ if (!strcmp(state, "hyper"))
+ mode |= SILC_UMODE_HYPER;
+ else if (!strcmp(state, "away"))
+ mode |= SILC_UMODE_GONE;
+ else if (!strcmp(state, "busy"))
+ mode |= SILC_UMODE_BUSY;
+ else if (!strcmp(state, "indisposed"))
+ mode |= SILC_UMODE_INDISPOSED;
+ else if (!strcmp(state, "page"))
+ mode |= SILC_UMODE_PAGE;
+
+ /* Send UMODE */
+ idp = silc_id_payload_encode(sg->conn->local_id, SILC_ID_CLIENT);
+ SILC_PUT32_MSB(mode, mb);
+ silc_client_command_send(sg->client, sg->conn, SILC_COMMAND_UMODE,
+ ++sg->conn->cmd_ident, 2,
+ 1, idp->data, idp->len,
+ 2, mb, sizeof(mb));
+ silc_buffer_free(idp);
+}
+
+
+/*************************** Connection Routines *****************************/
+
+static void
+silcgaim_keepalive(GaimConnection *gc)
+{
+ SilcGaim sg = gc->proto_data;
+ silc_client_send_packet(sg->client, sg->conn, SILC_PACKET_HEARTBEAT,
+ NULL, 0);
+}
+
+static int
+silcgaim_scheduler(gpointer *context)
+{
+ SilcGaim sg = (SilcGaim)context;
+ silc_client_run_one(sg->client);
+ return 1;
+}
+
+static void
+silcgaim_nickname_parse(const char *nickname,
+ char **ret_nickname)
+{
+ silc_parse_userfqdn(nickname, ret_nickname, NULL);
+}
+
+static void
+silcgaim_login_connected(gpointer data, gint source, const gchar *error_message)
+{
+ GaimConnection *gc = data;
+ SilcGaim sg;
+ SilcClient client;
+ SilcClientConnection conn;
+ GaimAccount *account;
+ SilcClientConnectionParams params;
+ const char *dfile;
+
+ g_return_if_fail(gc != NULL);
+
+ sg = gc->proto_data;
+
+ if (source < 0) {
+ gaim_connection_error(gc, _("Connection failed"));
+ return;
+ }
+
+ client = sg->client;
+ account = sg->account;
+
+ /* Get session detachment data, if available */
+ memset(&params, 0, sizeof(params));
+ dfile = silcgaim_session_file(gaim_account_get_username(sg->account));
+ params.detach_data = (unsigned char *)silc_file_readfile(dfile, &params.detach_data_len);
+ if (params.detach_data)
+ params.detach_data[params.detach_data_len] = 0;
+
+ /* Add connection to SILC client library */
+ conn = silc_client_add_connection(
+ sg->client, &params,
+ (char *)gaim_account_get_string(account, "server",
+ "silc.silcnet.org"),
+ gaim_account_get_int(account, "port", 706), sg);
+ if (!conn) {
+ gaim_connection_error(gc, _("Cannot initialize SILC Client connection"));
+ gc->proto_data = NULL;
+ return;
+ }
+ sg->conn = conn;
+
+ /* Progress */
+ if (params.detach_data) {
+ gaim_connection_update_progress(gc, _("Resuming session"), 2, 5);
+ sg->resuming = TRUE;
+ } else {
+ gaim_connection_update_progress(gc, _("Performing key exchange"), 2, 5);
+ }
+
+ /* Perform SILC Key Exchange. The "silc_connected" will be called
+ eventually. */
+ silc_client_start_key_exchange(sg->client, sg->conn, source);
+
+ /* Set default attributes */
+ if (!gaim_account_get_bool(account, "reject-attrs", FALSE)) {
+ SilcUInt32 mask;
+ const char *tmp;
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ char *icon;
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+ struct utsname u;
+#endif
+
+ mask = SILC_ATTRIBUTE_MOOD_NORMAL;
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_STATUS_MOOD,
+ SILC_32_TO_PTR(mask),
+ sizeof(SilcUInt32));
+ mask = SILC_ATTRIBUTE_CONTACT_CHAT;
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_PREFERRED_CONTACT,
+ SILC_32_TO_PTR(mask),
+ sizeof(SilcUInt32));
+#ifdef HAVE_SYS_UTSNAME_H
+ if (!uname(&u)) {
+ SilcAttributeObjDevice dev;
+ memset(&dev, 0, sizeof(dev));
+ dev.type = SILC_ATTRIBUTE_DEVICE_COMPUTER;
+ dev.version = u.release;
+ dev.model = u.sysname;
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_DEVICE_INFO,
+ (void *)&dev, sizeof(dev));
+ }
+#endif
+#ifdef _WIN32
+ tmp = _tzname[0];
+#else
+ tmp = tzname[0];
+#endif
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_TIMEZONE,
+ (void *)tmp, strlen(tmp));
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ /* Set our buddy icon */
+ icon = gaim_buddy_icons_get_full_path(gaim_account_get_buddy_icon(account));
+ silcgaim_buddy_set_icon(gc, icon);
+ g_free(icon);
+#endif
+ }
+
+ silc_free(params.detach_data);
+}
+
+static void
+silcgaim_login(GaimAccount *account)
+{
+ SilcGaim sg;
+ SilcClient client;
+ SilcClientParams params;
+ GaimConnection *gc;
+ char pkd[256], prd[256];
+ const char *cipher, *hmac;
+ char *realname;
+ int i;
+
+ gc = account->gc;
+ if (!gc)
+ return;
+ gc->proto_data = NULL;
+
+ memset(&params, 0, sizeof(params));
+ strcat(params.nickname_format, "%n@%h%a");
+ params.nickname_parse = silcgaim_nickname_parse;
+ params.ignore_requested_attributes =
+ gaim_account_get_bool(account, "reject-attrs", FALSE);
+
+ /* Allocate SILC client */
+ client = silc_client_alloc(&ops, &params, gc, NULL);
+ if (!client) {
+ gaim_connection_error(gc, _("Out of memory"));
+ return;
+ }
+
+ /* Get username, real name and local hostname for SILC library */
+ if (gaim_account_get_username(account)) {
+ const char *u = gaim_account_get_username(account);
+ char **up = g_strsplit(u, "@", 2);
+ client->username = strdup(up[0]);
+ g_strfreev(up);
+ } else {
+ client->username = silc_get_username();
+ gaim_account_set_username(account, client->username);
+ }
+ realname = silc_get_real_name();
+ if (gaim_account_get_user_info(account)) {
+ client->realname = strdup(gaim_account_get_user_info(account));
+ free(realname);
+ } else if ((silc_get_real_name() != NULL) && (*realname != '\0')) {
+ client->realname = realname;
+ gaim_account_set_user_info(account, client->realname);
+ } else {
+ free(realname);
+ client->realname = strdup(_("Gaim User"));
+ }
+ client->hostname = silc_net_localhost();
+
+ gaim_connection_set_display_name(gc, client->username);
+
+ /* Register requested cipher and HMAC */
+ cipher = gaim_account_get_string(account, "cipher", SILC_DEFAULT_CIPHER);
+ for (i = 0; silc_default_ciphers[i].name; i++)
+ if (!strcmp(silc_default_ciphers[i].name, cipher)) {
+ silc_cipher_register(&(silc_default_ciphers[i]));
+ break;
+ }
+ hmac = gaim_account_get_string(account, "hmac", SILC_DEFAULT_HMAC);
+ for (i = 0; silc_default_hmacs[i].name; i++)
+ if (!strcmp(silc_default_hmacs[i].name, hmac)) {
+ silc_hmac_register(&(silc_default_hmacs[i]));
+ break;
+ }
+
+ /* Init SILC client */
+ if (!silc_client_init(client)) {
+ gc->wants_to_die = TRUE;
+ gaim_connection_error(gc, _("Cannot initialize SILC protocol"));
+ return;
+ }
+
+ /* Check the ~/.silc dir and create it, and new key pair if necessary. */
+ if (!silcgaim_check_silc_dir(gc)) {
+ gc->wants_to_die = TRUE;
+ gaim_connection_error(gc, _("Cannot find/access ~/.silc directory"));
+ return;
+ }
+
+ /* Progress */
+ gaim_connection_update_progress(gc, _("Connecting to SILC Server"), 1, 5);
+
+ /* Load SILC key pair */
+ g_snprintf(pkd, sizeof(pkd), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcgaim_silcdir());
+ g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcgaim_silcdir());
+ if (!silc_load_key_pair((char *)gaim_account_get_string(account, "public-key", pkd),
+ (char *)gaim_account_get_string(account, "private-key", prd),
+ (gc->password == NULL) ? "" : gc->password, &client->pkcs,
+ &client->public_key, &client->private_key)) {
+ g_snprintf(pkd, sizeof(pkd), _("Could not load SILC key pair: %s"), strerror(errno));
+ gaim_connection_error(gc, pkd);
+ return;
+ }
+
+ sg = silc_calloc(1, sizeof(*sg));
+ if (!sg)
+ return;
+ memset(sg, 0, sizeof(*sg));
+ sg->client = client;
+ sg->gc = gc;
+ sg->account = account;
+ gc->proto_data = sg;
+
+ /* Connect to the SILC server */
+ if (gaim_proxy_connect(gc, account,
+ gaim_account_get_string(account, "server",
+ "silc.silcnet.org"),
+ gaim_account_get_int(account, "port", 706),
+ silcgaim_login_connected, gc) == NULL)
+ {
+ gaim_connection_error(gc, _("Unable to create connection"));
+ return;
+ }
+
+ /* Schedule SILC using Glib's event loop */
+#ifndef _WIN32
+ sg->scheduler = g_timeout_add(5, (GSourceFunc)silcgaim_scheduler, sg);
+#else
+ sg->scheduler = g_timeout_add(300, (GSourceFunc)silcgaim_scheduler, sg);
+#endif
+}
+
+static int
+silcgaim_close_final(gpointer *context)
+{
+ SilcGaim sg = (SilcGaim)context;
+ silc_client_stop(sg->client);
+ silc_client_free(sg->client);
+#ifdef HAVE_SILCMIME_H
+ if (sg->mimeass)
+ silc_mime_assembler_free(sg->mimeass);
+#endif
+ silc_free(sg);
+ return 0;
+}
+
+static void
+silcgaim_close(GaimConnection *gc)
+{
+ SilcGaim sg = gc->proto_data;
+
+ g_return_if_fail(sg != NULL);
+
+ /* Send QUIT */
+ silc_client_command_call(sg->client, sg->conn, NULL,
+ "QUIT", "Download Gaim: " GAIM_WEBSITE, NULL);
+
+ if (sg->conn)
+ silc_client_close_connection(sg->client, sg->conn);
+
+ g_source_remove(sg->scheduler);
+ g_timeout_add(1, (GSourceFunc)silcgaim_close_final, sg);
+}
+
+
+/****************************** Protocol Actions *****************************/
+
+static void
+silcgaim_attrs_cancel(GaimConnection *gc, GaimRequestFields *fields)
+{
+ /* Nothing */
+}
+
+static void
+silcgaim_attrs_cb(GaimConnection *gc, GaimRequestFields *fields)
+{
+ SilcGaim sg = gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ GaimRequestField *f;
+ char *tmp;
+ SilcUInt32 tmp_len, mask;
+ SilcAttributeObjService service;
+ SilcAttributeObjDevice dev;
+ SilcVCardStruct vcard;
+ const char *val;
+
+ sg = gc->proto_data;
+ if (!sg)
+ return;
+
+ memset(&service, 0, sizeof(service));
+ memset(&dev, 0, sizeof(dev));
+ memset(&vcard, 0, sizeof(vcard));
+
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_USER_INFO, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_SERVICE, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_STATUS_MOOD, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_STATUS_FREETEXT, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_STATUS_MESSAGE, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_PREFERRED_LANGUAGE, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_PREFERRED_CONTACT, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_TIMEZONE, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_GEOLOCATION, NULL);
+ silc_client_attribute_del(client, conn,
+ SILC_ATTRIBUTE_DEVICE_INFO, NULL);
+
+ /* Set mood */
+ mask = 0;
+ f = gaim_request_fields_get_field(fields, "mood_normal");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_NORMAL;
+ f = gaim_request_fields_get_field(fields, "mood_happy");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_HAPPY;
+ f = gaim_request_fields_get_field(fields, "mood_sad");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_SAD;
+ f = gaim_request_fields_get_field(fields, "mood_angry");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_ANGRY;
+ f = gaim_request_fields_get_field(fields, "mood_jealous");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_JEALOUS;
+ f = gaim_request_fields_get_field(fields, "mood_ashamed");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_ASHAMED;
+ f = gaim_request_fields_get_field(fields, "mood_invincible");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_INVINCIBLE;
+ f = gaim_request_fields_get_field(fields, "mood_inlove");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_INLOVE;
+ f = gaim_request_fields_get_field(fields, "mood_sleepy");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_SLEEPY;
+ f = gaim_request_fields_get_field(fields, "mood_bored");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_BORED;
+ f = gaim_request_fields_get_field(fields, "mood_excited");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_EXCITED;
+ f = gaim_request_fields_get_field(fields, "mood_anxious");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_MOOD_ANXIOUS;
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_STATUS_MOOD,
+ SILC_32_TO_PTR(mask),
+ sizeof(SilcUInt32));
+
+ /* Set preferred contact */
+ mask = 0;
+ f = gaim_request_fields_get_field(fields, "contact_chat");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_CONTACT_CHAT;
+ f = gaim_request_fields_get_field(fields, "contact_email");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_CONTACT_EMAIL;
+ f = gaim_request_fields_get_field(fields, "contact_call");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_CONTACT_CALL;
+ f = gaim_request_fields_get_field(fields, "contact_sms");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_CONTACT_SMS;
+ f = gaim_request_fields_get_field(fields, "contact_mms");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_CONTACT_MMS;
+ f = gaim_request_fields_get_field(fields, "contact_video");
+ if (f && gaim_request_field_bool_get_value(f))
+ mask |= SILC_ATTRIBUTE_CONTACT_VIDEO;
+ if (mask)
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_PREFERRED_CONTACT,
+ SILC_32_TO_PTR(mask),
+ sizeof(SilcUInt32));
+
+ /* Set status text */
+ val = NULL;
+ f = gaim_request_fields_get_field(fields, "status_text");
+ if (f)
+ val = gaim_request_field_string_get_value(f);
+ if (val && *val)
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_STATUS_FREETEXT,
+ (void *)val, strlen(val));
+
+ /* Set vcard */
+ val = NULL;
+ f = gaim_request_fields_get_field(fields, "vcard");
+ if (f)
+ val = gaim_request_field_string_get_value(f);
+ if (val && *val) {
+ gaim_account_set_string(sg->account, "vcard", val);
+ tmp = silc_file_readfile(val, &tmp_len);
+ if (tmp) {
+ tmp[tmp_len] = 0;
+ if (silc_vcard_decode((unsigned char *)tmp, tmp_len, &vcard))
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_USER_INFO,
+ (void *)&vcard,
+ sizeof(vcard));
+ }
+ silc_vcard_free(&vcard);
+ silc_free(tmp);
+ } else {
+ gaim_account_set_string(sg->account, "vcard", "");
+ }
+
+#ifdef HAVE_SYS_UTSNAME_H
+ /* Set device info */
+ f = gaim_request_fields_get_field(fields, "device");
+ if (f && gaim_request_field_bool_get_value(f)) {
+ struct utsname u;
+ if (!uname(&u)) {
+ dev.type = SILC_ATTRIBUTE_DEVICE_COMPUTER;
+ dev.version = u.release;
+ dev.model = u.sysname;
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_DEVICE_INFO,
+ (void *)&dev, sizeof(dev));
+ }
+ }
+#endif
+
+ /* Set timezone */
+ val = NULL;
+ f = gaim_request_fields_get_field(fields, "timezone");
+ if (f)
+ val = gaim_request_field_string_get_value(f);
+ if (val && *val)
+ silc_client_attribute_add(client, conn,
+ SILC_ATTRIBUTE_TIMEZONE,
+ (void *)val, strlen(val));
+}
+
+static void
+silcgaim_attrs(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ SilcGaim sg = gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *g;
+ GaimRequestField *f;
+ SilcHashTable attrs;
+ SilcAttributePayload attr;
+ gboolean mnormal = TRUE, mhappy = FALSE, msad = FALSE,
+ mangry = FALSE, mjealous = FALSE, mashamed = FALSE,
+ minvincible = FALSE, minlove = FALSE, msleepy = FALSE,
+ mbored = FALSE, mexcited = FALSE, manxious = FALSE;
+ gboolean cemail = FALSE, ccall = FALSE, csms = FALSE,
+ cmms = FALSE, cchat = TRUE, cvideo = FALSE;
+ gboolean device = TRUE;
+ char status[1024];
+
+ sg = gc->proto_data;
+ if (!sg)
+ return;
+
+ memset(status, 0, sizeof(status));
+
+ attrs = silc_client_attributes_get(client, conn);
+ if (attrs) {
+ if (silc_hash_table_find(attrs,
+ SILC_32_TO_PTR(SILC_ATTRIBUTE_STATUS_MOOD),
+ NULL, (void *)&attr)) {
+ SilcUInt32 mood = 0;
+ silc_attribute_get_object(attr, &mood, sizeof(mood));
+ mnormal = !mood;
+ mhappy = (mood & SILC_ATTRIBUTE_MOOD_HAPPY);
+ msad = (mood & SILC_ATTRIBUTE_MOOD_SAD);
+ mangry = (mood & SILC_ATTRIBUTE_MOOD_ANGRY);
+ mjealous = (mood & SILC_ATTRIBUTE_MOOD_JEALOUS);
+ mashamed = (mood & SILC_ATTRIBUTE_MOOD_ASHAMED);
+ minvincible = (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE);
+ minlove = (mood & SILC_ATTRIBUTE_MOOD_INLOVE);
+ msleepy = (mood & SILC_ATTRIBUTE_MOOD_SLEEPY);
+ mbored = (mood & SILC_ATTRIBUTE_MOOD_BORED);
+ mexcited = (mood & SILC_ATTRIBUTE_MOOD_EXCITED);
+ manxious = (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS);
+ }
+
+ if (silc_hash_table_find(attrs,
+ SILC_32_TO_PTR(SILC_ATTRIBUTE_PREFERRED_CONTACT),
+ NULL, (void *)&attr)) {
+ SilcUInt32 contact = 0;
+ silc_attribute_get_object(attr, &contact, sizeof(contact));
+ cemail = (contact & SILC_ATTRIBUTE_CONTACT_EMAIL);
+ ccall = (contact & SILC_ATTRIBUTE_CONTACT_CALL);
+ csms = (contact & SILC_ATTRIBUTE_CONTACT_SMS);
+ cmms = (contact & SILC_ATTRIBUTE_CONTACT_MMS);
+ cchat = (contact & SILC_ATTRIBUTE_CONTACT_CHAT);
+ cvideo = (contact & SILC_ATTRIBUTE_CONTACT_VIDEO);
+ }
+
+ if (silc_hash_table_find(attrs,
+ SILC_32_TO_PTR(SILC_ATTRIBUTE_STATUS_FREETEXT),
+ NULL, (void *)&attr))
+ silc_attribute_get_object(attr, &status, sizeof(status));
+
+ if (!silc_hash_table_find(attrs,
+ SILC_32_TO_PTR(SILC_ATTRIBUTE_DEVICE_INFO),
+ NULL, (void *)&attr))
+ device = FALSE;
+ }
+
+ fields = gaim_request_fields_new();
+
+ g = gaim_request_field_group_new(NULL);
+ f = gaim_request_field_label_new("l3", _("Your Current Mood"));
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_normal", _("Normal"), mnormal);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_happy", _("Happy"), mhappy);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_sad", _("Sad"), msad);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_angry", _("Angry"), mangry);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_jealous", _("Jealous"), mjealous);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_ashamed", _("Ashamed"), mashamed);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_invincible", _("Invincible"), minvincible);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_inlove", _("In love"), minlove);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_sleepy", _("Sleepy"), msleepy);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_bored", _("Bored"), mbored);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_excited", _("Excited"), mexcited);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("mood_anxious", _("Anxious"), manxious);
+ gaim_request_field_group_add_field(g, f);
+
+ f = gaim_request_field_label_new("l4", _("\nYour Preferred Contact Methods"));
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("contact_chat", _("Chat"), cchat);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("contact_email", _("E-mail"), cemail);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("contact_call", _("Phone"), ccall);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("contact_sms", _("SMS"), csms);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("contact_mms", _("MMS"), cmms);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("contact_video", _("Video conferencing"), cvideo);
+ gaim_request_field_group_add_field(g, f);
+ gaim_request_fields_add_group(fields, g);
+
+ g = gaim_request_field_group_new(NULL);
+ f = gaim_request_field_string_new("status_text", _("Your Current Status"),
+ status[0] ? status : NULL, TRUE);
+ gaim_request_field_group_add_field(g, f);
+ gaim_request_fields_add_group(fields, g);
+
+ g = gaim_request_field_group_new(NULL);
+#if 0
+ f = gaim_request_field_label_new("l2", _("Online Services"));
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_bool_new("services",
+ _("Let others see what services you are using"),
+ TRUE);
+ gaim_request_field_group_add_field(g, f);
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+ f = gaim_request_field_bool_new("device",
+ _("Let others see what computer you are using"),
+ device);
+ gaim_request_field_group_add_field(g, f);
+#endif
+ gaim_request_fields_add_group(fields, g);
+
+ g = gaim_request_field_group_new(NULL);
+ f = gaim_request_field_string_new("vcard", _("Your VCard File"),
+ gaim_account_get_string(sg->account, "vcard", ""),
+ FALSE);
+ gaim_request_field_group_add_field(g, f);
+#ifdef _WIN32
+ f = gaim_request_field_string_new("timezone", _("Timezone"), _tzname[0], FALSE);
+#else
+ f = gaim_request_field_string_new("timezone", _("Timezone"), tzname[0], FALSE);
+#endif
+ gaim_request_field_group_add_field(g, f);
+ gaim_request_fields_add_group(fields, g);
+
+ gaim_request_fields(gc, _("User Online Status Attributes"),
+ _("User Online Status Attributes"),
+ _("You can let other users see your online status information "
+ "and your personal information. Please fill the information "
+ "you would like other users to see about yourself."),
+ fields,
+ _("OK"), G_CALLBACK(silcgaim_attrs_cb),
+ _("Cancel"), G_CALLBACK(silcgaim_attrs_cancel), gc);
+}
+
+static void
+silcgaim_detach(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ SilcGaim sg;
+
+ if (!gc)
+ return;
+ sg = gc->proto_data;
+ if (!sg)
+ return;
+
+ /* Call DETACH */
+ silc_client_command_call(sg->client, sg->conn, "DETACH");
+ sg->detaching = TRUE;
+}
+
+static void
+silcgaim_view_motd(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ SilcGaim sg;
+ char *tmp;
+
+ if (!gc)
+ return;
+ sg = gc->proto_data;
+ if (!sg)
+ return;
+
+ if (!sg->motd) {
+ gaim_notify_error(
+ gc, _("Message of the Day"), _("No Message of the Day available"),
+ _("There is no Message of the Day associated with this connection"));
+ return;
+ }
+
+ tmp = g_markup_escape_text(sg->motd, -1);
+ gaim_notify_formatted(gc, NULL, _("Message of the Day"), NULL,
+ tmp, NULL, NULL);
+ g_free(tmp);
+}
+
+static void
+silcgaim_create_keypair_cancel(GaimConnection *gc, GaimRequestFields *fields)
+{
+ /* Nothing */
+}
+
+static void
+silcgaim_create_keypair_cb(GaimConnection *gc, GaimRequestFields *fields)
+{
+ SilcGaim sg = gc->proto_data;
+ GaimRequestField *f;
+ const char *val, *pkfile = NULL, *prfile = NULL;
+ const char *pass1 = NULL, *pass2 = NULL, *un = NULL, *hn = NULL;
+ const char *rn = NULL, *e = NULL, *o = NULL, *c = NULL;
+ char *identifier;
+ int keylen = SILCGAIM_DEF_PKCS_LEN;
+ SilcPublicKey public_key;
+
+ sg = gc->proto_data;
+ if (!sg)
+ return;
+
+ val = NULL;
+ f = gaim_request_fields_get_field(fields, "pass1");
+ if (f)
+ val = gaim_request_field_string_get_value(f);
+ if (val && *val)
+ pass1 = val;
+ else
+ pass1 = "";
+ val = NULL;
+ f = gaim_request_fields_get_field(fields, "pass2");
+ if (f)
+ val = gaim_request_field_string_get_value(f);
+ if (val && *val)
+ pass2 = val;
+ else
+ pass2 = "";
+
+ if (strcmp(pass1, pass2)) {
+ gaim_notify_error(
+ gc, _("Create New SILC Key Pair"), _("Passphrases do not match"), NULL);
+ return;
+ }
+
+ val = NULL;
+ f = gaim_request_fields_get_field(fields, "key");
+ if (f)
+ val = gaim_request_field_string_get_value(f);
+ if (val && *val)
+ keylen = atoi(val);
+ f = gaim_request_fields_get_field(fields, "pkfile");
+ if (f)
+ pkfile = gaim_request_field_string_get_value(f);
+ f = gaim_request_fields_get_field(fields, "prfile");
+ if (f)
+ prfile = gaim_request_field_string_get_value(f);
+
+ f = gaim_request_fields_get_field(fields, "un");
+ if (f)
+ un = gaim_request_field_string_get_value(f);
+ f = gaim_request_fields_get_field(fields, "hn");
+ if (f)
+ hn = gaim_request_field_string_get_value(f);
+ f = gaim_request_fields_get_field(fields, "rn");
+ if (f)
+ rn = gaim_request_field_string_get_value(f);
+ f = gaim_request_fields_get_field(fields, "e");
+ if (f)
+ e = gaim_request_field_string_get_value(f);
+ f = gaim_request_fields_get_field(fields, "o");
+ if (f)
+ o = gaim_request_field_string_get_value(f);
+ f = gaim_request_fields_get_field(fields, "c");
+ if (f)
+ c = gaim_request_field_string_get_value(f);
+
+ identifier = silc_pkcs_encode_identifier((char *)un, (char *)hn,
+ (char *)rn, (char *)e, (char *)o, (char *)c);
+
+ /* Create the key pair */
+ if (!silc_create_key_pair(SILCGAIM_DEF_PKCS, keylen, pkfile, prfile,
+ identifier, pass1, NULL, &public_key, NULL,
+ FALSE)) {
+ gaim_notify_error(
+ gc, _("Create New SILC Key Pair"), _("Key Pair Generation failed"), NULL);
+ return;
+ }
+
+ silcgaim_show_public_key(sg, NULL, public_key, NULL, NULL);
+
+ silc_pkcs_public_key_free(public_key);
+ silc_free(identifier);
+}
+
+static void
+silcgaim_create_keypair(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ SilcGaim sg = gc->proto_data;
+ GaimRequestFields *fields;
+ GaimRequestFieldGroup *g;
+ GaimRequestField *f;
+ const char *username, *realname;
+ char *hostname, **u;
+ char tmp[256], pkd[256], pkd2[256], prd[256], prd2[256];
+
+ username = gaim_account_get_username(sg->account);
+ u = g_strsplit(username, "@", 2);
+ username = u[0];
+ realname = gaim_account_get_user_info(sg->account);
+ hostname = silc_net_localhost();
+ g_snprintf(tmp, sizeof(tmp), "%s@%s", username, hostname);
+
+ g_snprintf(pkd2, sizeof(pkd2), "%s" G_DIR_SEPARATOR_S"public_key.pub", silcgaim_silcdir());
+ g_snprintf(prd2, sizeof(prd2), "%s" G_DIR_SEPARATOR_S"private_key.prv", silcgaim_silcdir());
+ g_snprintf(pkd, sizeof(pkd) - 1, "%s",
+ gaim_account_get_string(gc->account, "public-key", pkd2));
+ g_snprintf(prd, sizeof(prd) - 1, "%s",
+ gaim_account_get_string(gc->account, "private-key", prd2));
+
+ fields = gaim_request_fields_new();
+
+ g = gaim_request_field_group_new(NULL);
+ f = gaim_request_field_string_new("key", _("Key length"), "2048", FALSE);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_string_new("pkfile", _("Public key file"), pkd, FALSE);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_string_new("prfile", _("Private key file"), prd, FALSE);
+ gaim_request_field_group_add_field(g, f);
+ gaim_request_fields_add_group(fields, g);
+
+ g = gaim_request_field_group_new(NULL);
+ f = gaim_request_field_string_new("un", _("Username"), username ? username : "", FALSE);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_string_new("hn", _("Hostname"), hostname ? hostname : "", FALSE);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_string_new("rn", _("Real name"), realname ? realname : "", FALSE);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_string_new("e", _("E-mail"), tmp, FALSE);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_string_new("o", _("Organization"), "", FALSE);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_string_new("c", _("Country"), "", FALSE);
+ gaim_request_field_group_add_field(g, f);
+ gaim_request_fields_add_group(fields, g);
+
+ g = gaim_request_field_group_new(NULL);
+ f = gaim_request_field_string_new("pass1", _("Passphrase"), "", FALSE);
+ gaim_request_field_string_set_masked(f, TRUE);
+ gaim_request_field_group_add_field(g, f);
+ f = gaim_request_field_string_new("pass2", _("Passphrase (retype)"), "", FALSE);
+ gaim_request_field_string_set_masked(f, TRUE);
+ gaim_request_field_group_add_field(g, f);
+ gaim_request_fields_add_group(fields, g);
+
+ gaim_request_fields(gc, _("Create New SILC Key Pair"),
+ _("Create New SILC Key Pair"), NULL, fields,
+ _("Generate Key Pair"), G_CALLBACK(silcgaim_create_keypair_cb),
+ _("Cancel"), G_CALLBACK(silcgaim_create_keypair_cancel), gc);
+
+ g_strfreev(u);
+ silc_free(hostname);
+}
+
+static void
+silcgaim_change_pass(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ gaim_account_request_change_password(gaim_connection_get_account(gc));
+}
+
+static void
+silcgaim_change_passwd(GaimConnection *gc, const char *old, const char *new)
+{
+ char prd[256];
+ g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.pub", silcgaim_silcdir());
+ silc_change_private_key_passphrase(gaim_account_get_string(gc->account,
+ "private-key",
+ prd), old, new);
+}
+
+static void
+silcgaim_show_set_info(GaimPluginAction *action)
+{
+ GaimConnection *gc = (GaimConnection *) action->context;
+ gaim_account_request_change_user_info(gaim_connection_get_account(gc));
+}
+
+static void
+silcgaim_set_info(GaimConnection *gc, const char *text)
+{
+}
+
+static GList *
+silcgaim_actions(GaimPlugin *plugin, gpointer context)
+{
+ GaimConnection *gc = context;
+ GList *list = NULL;
+ GaimPluginAction *act;
+
+ if (!gaim_account_get_bool(gc->account, "reject-attrs", FALSE)) {
+ act = gaim_plugin_action_new(_("Online Status"),
+ silcgaim_attrs);
+ list = g_list_append(list, act);
+ }
+
+ act = gaim_plugin_action_new(_("Detach From Server"),
+ silcgaim_detach);
+ list = g_list_append(list, act);
+
+ act = gaim_plugin_action_new(_("View Message of the Day"),
+ silcgaim_view_motd);
+ list = g_list_append(list, act);
+
+ act = gaim_plugin_action_new(_("Create SILC Key Pair..."),
+ silcgaim_create_keypair);
+ list = g_list_append(list, act);
+
+ act = gaim_plugin_action_new(_("Change Password..."),
+ silcgaim_change_pass);
+ list = g_list_append(list, act);
+
+ act = gaim_plugin_action_new(_("Set User Info..."),
+ silcgaim_show_set_info);
+ list = g_list_append(list, act);
+
+ return list;
+}
+
+
+/******************************* IM Routines *********************************/
+
+typedef struct {
+ char *nick;
+ char *message;
+ SilcUInt32 message_len;
+ SilcMessageFlags flags;
+ GaimMessageFlags gflags;
+} *SilcGaimIM;
+
+static void
+silcgaim_send_im_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count,
+ void *context)
+{
+ GaimConnection *gc = client->application;
+ SilcGaim sg = gc->proto_data;
+ SilcGaimIM im = context;
+ GaimConversation *convo;
+ char tmp[256], *nickname = NULL;
+ SilcClientEntry client_entry;
+#ifdef HAVE_SILCMIME_H
+ SilcDList list;
+#endif
+
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, im->nick,
+ sg->account);
+ if (!convo)
+ return;
+
+ if (!clients)
+ goto err;
+
+ if (clients_count > 1) {
+ silc_parse_userfqdn(im->nick, &nickname, NULL);
+
+ /* Find the correct one. The im->nick might be a formatted nick
+ so this will find the correct one. */
+ clients = silc_client_get_clients_local(client, conn,
+ nickname, im->nick,
+ &clients_count);
+ if (!clients)
+ goto err;
+ client_entry = clients[0];
+ silc_free(clients);
+ } else {
+ client_entry = clients[0];
+ }
+
+#ifdef HAVE_SILCMIME_H
+ /* Check for images */
+ if (im->gflags & GAIM_MESSAGE_IMAGES) {
+ list = silcgaim_image_message(im->message, (SilcUInt32 *)&im->flags);
+ if (list) {
+ /* Send one or more MIME message. If more than one, they
+ are MIME fragments due to over large message */
+ SilcBuffer buf;
+
+ silc_dlist_start(list);
+ while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
+ silc_client_send_private_message(client, conn,
+ client_entry, im->flags,
+ buf->data, buf->len,
+ TRUE);
+ silc_mime_partial_free(list);
+ gaim_conv_im_write(GAIM_CONV_IM(convo), conn->local_entry->nickname,
+ im->message, 0, time(NULL));
+ goto out;
+ }
+ }
+#endif
+
+ /* Send the message */
+ silc_client_send_private_message(client, conn, client_entry, im->flags,
+ (unsigned char *)im->message, im->message_len, TRUE);
+ gaim_conv_im_write(GAIM_CONV_IM(convo), conn->local_entry->nickname,
+ im->message, 0, time(NULL));
+ goto out;
+
+ err:
+ g_snprintf(tmp, sizeof(tmp),
+ _("User <I>%s</I> is not present in the network"), im->nick);
+ gaim_conversation_write(convo, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL));
+
+ out:
+ g_free(im->nick);
+ g_free(im->message);
+ silc_free(im);
+ silc_free(nickname);
+}
+
+static int
+silcgaim_send_im(GaimConnection *gc, const char *who, const char *message,
+ GaimMessageFlags flags)
+{
+ SilcGaim sg = gc->proto_data;
+ SilcClient client = sg->client;
+ SilcClientConnection conn = sg->conn;
+ SilcClientEntry *clients;
+ SilcUInt32 clients_count, mflags;
+ char *nickname, *msg, *tmp;
+ int ret = 0;
+ gboolean sign = gaim_account_get_bool(sg->account, "sign-verify", FALSE);
+#ifdef HAVE_SILCMIME_H
+ SilcDList list;
+#endif
+
+ if (!who || !message)
+ return 0;
+
+ mflags = SILC_MESSAGE_FLAG_UTF8;
+
+ tmp = msg = gaim_unescape_html(message);
+
+ if (!g_ascii_strncasecmp(msg, "/me ", 4)) {
+ msg += 4;
+ if (!*msg) {
+ g_free(tmp);
+ return 0;
+ }
+ mflags |= SILC_MESSAGE_FLAG_ACTION;
+ } else if (strlen(msg) > 1 && msg[0] == '/') {
+ if (!silc_client_command_call(client, conn, msg + 1))
+ gaim_notify_error(gc, _("Call Command"), _("Cannot call command"),
+ _("Unknown command"));
+ g_free(tmp);
+ return 0;
+ }
+
+
+ if (!silc_parse_userfqdn(who, &nickname, NULL)) {
+ g_free(tmp);
+ return 0;
+ }
+
+ if (sign)
+ mflags |= SILC_MESSAGE_FLAG_SIGNED;
+
+ /* Find client entry */
+ clients = silc_client_get_clients_local(client, conn, nickname, who,
+ &clients_count);
+ if (!clients) {
+ /* Resolve unknown user */
+ SilcGaimIM im = silc_calloc(1, sizeof(*im));
+ if (!im) {
+ g_free(tmp);
+ return 0;
+ }
+ im->nick = g_strdup(who);
+ im->message = g_strdup(message);
+ im->message_len = strlen(im->message);
+ im->flags = mflags;
+ im->gflags = flags;
+ silc_client_get_clients(client, conn, nickname, NULL,
+ silcgaim_send_im_resolved, im);
+ silc_free(nickname);
+ g_free(tmp);
+ return 0;
+ }
+
+#ifdef HAVE_SILCMIME_H
+ /* Check for images */
+ if (flags & GAIM_MESSAGE_IMAGES) {
+ list = silcgaim_image_message(message, &mflags);
+ if (list) {
+ /* Send one or more MIME message. If more than one, they
+ are MIME fragments due to over large message */
+ SilcBuffer buf;
+
+ silc_dlist_start(list);
+ while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
+ ret =
+ silc_client_send_private_message(client, conn,
+ clients[0], mflags,
+ buf->data, buf->len,
+ TRUE);
+ silc_mime_partial_free(list);
+ g_free(tmp);
+ silc_free(nickname);
+ silc_free(clients);
+ return ret;
+ }
+ }
+#endif
+
+ /* Send private message directly */
+ ret = silc_client_send_private_message(client, conn, clients[0],
+ mflags,
+ (unsigned char *)msg,
+ strlen(msg), TRUE);
+
+ g_free(tmp);
+ silc_free(nickname);
+ silc_free(clients);
+ return ret;
+}
+
+
+static GList *silcgaim_blist_node_menu(GaimBlistNode *node) {
+ /* split this single menu building function back into the two
+ original: one for buddies and one for chats */
+
+ if(GAIM_BLIST_NODE_IS_CHAT(node)) {
+ return silcgaim_chat_menu((GaimChat *) node);
+ } else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
+ return silcgaim_buddy_menu((GaimBuddy *) node);
+ } else {
+ g_return_val_if_reached(NULL);
+ }
+}
+
+/********************************* Commands **********************************/
+
+static GaimCmdRet silcgaim_cmd_chat_part(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ GaimConversation *convo = conv;
+ int id = 0;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ if(args && args[0])
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, args[0],
+ gc->account);
+
+ if (convo != NULL)
+ id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo));
+
+ if (id == 0)
+ return GAIM_CMD_RET_FAILED;
+
+ silcgaim_chat_leave(gc, id);
+
+ return GAIM_CMD_RET_OK;
+
+}
+
+static GaimCmdRet silcgaim_cmd_chat_topic(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ int id = 0;
+ char *buf, *tmp, *tmp2;
+ const char *topic;
+
+ gc = gaim_conversation_get_gc(conv);
+ id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv));
+
+ if (gc == NULL || id == 0)
+ return GAIM_CMD_RET_FAILED;
+
+ if (!args || !args[0]) {
+ topic = gaim_conv_chat_get_topic (GAIM_CONV_CHAT(conv));
+ if (topic) {
+ tmp = g_markup_escape_text(topic, -1);
+ tmp2 = gaim_markup_linkify(tmp);
+ buf = g_strdup_printf(_("current topic is: %s"), tmp2);
+ g_free(tmp);
+ g_free(tmp2);
+ } else
+ buf = g_strdup(_("No topic is set"));
+ gaim_conv_chat_write(GAIM_CONV_CHAT(conv), gc->account->username, buf,
+ GAIM_MESSAGE_SYSTEM|GAIM_MESSAGE_NO_LOG, time(NULL));
+ g_free(buf);
+
+ }
+
+ if (args && args[0] && (strlen(args[0]) > 255)) {
+ *error = g_strdup(_("Topic too long"));
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ silcgaim_chat_set_topic(gc, id, args ? args[0] : NULL);
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_chat_join(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GHashTable *comp;
+
+ if(!args || !args[0])
+ return GAIM_CMD_RET_FAILED;
+
+ comp = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+ g_hash_table_replace(comp, "channel", args[0]);
+ if(args[1])
+ g_hash_table_replace(comp, "passphrase", args[1]);
+
+ silcgaim_chat_join(gaim_conversation_get_gc(conv), comp);
+
+ g_hash_table_destroy(comp);
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_chat_list(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ gc = gaim_conversation_get_gc(conv);
+ gaim_roomlist_show_with_account(gaim_connection_get_account(gc));
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_whois(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ silcgaim_get_info(gc, args[0]);
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_msg(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ int ret;
+ GaimConnection *gc;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ ret = silcgaim_send_im(gc, args[0], args[1], GAIM_MESSAGE_SEND);
+
+ if (ret)
+ return GAIM_CMD_RET_OK;
+ else
+ return GAIM_CMD_RET_FAILED;
+}
+
+static GaimCmdRet silcgaim_cmd_query(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ int ret = 1;
+ GaimConversation *convo;
+ GaimConnection *gc;
+ GaimAccount *account;
+
+ if (!args || !args[0]) {
+ *error = g_strdup(_("You must specify a nick"));
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ account = gaim_connection_get_account(gc);
+
+ convo = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, args[0]);
+
+ if (args[1]) {
+ ret = silcgaim_send_im(gc, args[0], args[1], GAIM_MESSAGE_SEND);
+ gaim_conv_im_write(GAIM_CONV_IM(convo), gaim_connection_get_display_name(gc),
+ args[1], GAIM_MESSAGE_SEND, time(NULL));
+ }
+
+ if (ret)
+ return GAIM_CMD_RET_OK;
+ else
+ return GAIM_CMD_RET_FAILED;
+}
+
+static GaimCmdRet silcgaim_cmd_motd(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ SilcGaim sg;
+ char *tmp;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ sg = gc->proto_data;
+
+ if (sg == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ if (!sg->motd) {
+ *error = g_strdup(_("There is no Message of the Day associated with this connection"));
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ tmp = g_markup_escape_text(sg->motd, -1);
+ gaim_notify_formatted(gc, NULL, _("Message of the Day"), NULL,
+ tmp, NULL, NULL);
+ g_free(tmp);
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_detach(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ sg = gc->proto_data;
+
+ if (sg == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ silc_client_command_call(sg->client, sg->conn, "DETACH");
+ sg->detaching = TRUE;
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_cmode(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ SilcGaim sg;
+ SilcChannelEntry channel;
+ char *silccmd, *silcargs, *msg, tmp[256];
+ const char *chname;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL || !args || gc->proto_data == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ sg = gc->proto_data;
+
+ if (args[0])
+ chname = args[0];
+ else
+ chname = gaim_conversation_get_name(conv);
+
+ if (!args[1]) {
+ channel = silc_client_get_channel(sg->client, sg->conn,
+ (char *)chname);
+ if (!channel) {
+ *error = g_strdup_printf(_("channel %s not found"), chname);
+ return GAIM_CMD_RET_FAILED;
+ }
+ if (channel->mode) {
+ silcgaim_get_chmode_string(channel->mode, tmp, sizeof(tmp));
+ msg = g_strdup_printf(_("channel modes for %s: %s"), chname, tmp);
+ } else {
+ msg = g_strdup_printf(_("no channel modes are set on %s"), chname);
+ }
+ gaim_conv_chat_write(GAIM_CONV_CHAT(conv), "",
+ msg, GAIM_MESSAGE_SYSTEM|GAIM_MESSAGE_NO_LOG, time(NULL));
+ g_free(msg);
+ return GAIM_CMD_RET_OK;
+ }
+
+ silcargs = g_strjoinv(" ", args);
+ silccmd = g_strconcat(cmd, " ", args ? silcargs : NULL, NULL);
+ g_free(silcargs);
+ if (!silc_client_command_call(sg->client, sg->conn, silccmd)) {
+ g_free(silccmd);
+ *error = g_strdup_printf(_("Failed to set cmodes for %s"), args[0]);
+ return GAIM_CMD_RET_FAILED;
+ }
+ g_free(silccmd);
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_generic(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ SilcGaim sg;
+ char *silccmd, *silcargs;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ sg = gc->proto_data;
+
+ if (sg == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ silcargs = g_strjoinv(" ", args);
+ silccmd = g_strconcat(cmd, " ", args ? silcargs : NULL, NULL);
+ g_free(silcargs);
+ if (!silc_client_command_call(sg->client, sg->conn, silccmd)) {
+ g_free(silccmd);
+ *error = g_strdup_printf(_("Unknown command: %s, (may be a Gaim bug)"), cmd);
+ return GAIM_CMD_RET_FAILED;
+ }
+ g_free(silccmd);
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_quit(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ sg = gc->proto_data;
+
+ if (sg == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ silc_client_command_call(sg->client, sg->conn, NULL,
+ "QUIT", (args && args[0]) ? args[0] : "Download Gaim: " GAIM_WEBSITE, NULL);
+
+ return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet silcgaim_cmd_call(GaimConversation *conv,
+ const char *cmd, char **args, char **error, void *data)
+{
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ gc = gaim_conversation_get_gc(conv);
+
+ if (gc == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ sg = gc->proto_data;
+
+ if (sg == NULL)
+ return GAIM_CMD_RET_FAILED;
+
+ if (!silc_client_command_call(sg->client, sg->conn, args[0])) {
+ *error = g_strdup_printf(_("Unknown command: %s"), args[0]);
+ return GAIM_CMD_RET_FAILED;
+ }
+
+ return GAIM_CMD_RET_OK;
+}
+
+
+/************************** Plugin Initialization ****************************/
+
+static void
+silcgaim_register_commands(void)
+{
+ gaim_cmd_register("part", "w", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
+ GAIM_CMD_FLAG_PRPL_ONLY | GAIM_CMD_FLAG_ALLOW_WRONG_ARGS,
+ "prpl-silc", silcgaim_cmd_chat_part, _("part [channel]: Leave the chat"), NULL);
+ gaim_cmd_register("leave", "w", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
+ GAIM_CMD_FLAG_PRPL_ONLY | GAIM_CMD_FLAG_ALLOW_WRONG_ARGS,
+ "prpl-silc", silcgaim_cmd_chat_part, _("leave [channel]: Leave the chat"), NULL);
+ gaim_cmd_register("topic", "s", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc",
+ silcgaim_cmd_chat_topic, _("topic [&lt;new topic&gt;]: View or change the topic"), NULL);
+ gaim_cmd_register("join", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
+ GAIM_CMD_FLAG_PRPL_ONLY | GAIM_CMD_FLAG_ALLOW_WRONG_ARGS,
+ "prpl-silc", silcgaim_cmd_chat_join,
+ _("join &lt;channel&gt; [&lt;password&gt;]: Join a chat on this network"), NULL);
+ gaim_cmd_register("list", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc",
+ silcgaim_cmd_chat_list, _("list: List channels on this network"), NULL);
+ gaim_cmd_register("whois", "w", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc",
+ silcgaim_cmd_whois, _("whois &lt;nick&gt;: View nick's information"), NULL);
+ gaim_cmd_register("msg", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_msg,
+ _("msg &lt;nick&gt; &lt;message&gt;: Send a private message to a user"), NULL);
+ gaim_cmd_register("query", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_query,
+ _("query &lt;nick&gt; [&lt;message&gt;]: Send a private message to a user"), NULL);
+ gaim_cmd_register("motd", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_motd,
+ _("motd: View the server's Message Of The Day"), NULL);
+ gaim_cmd_register("detach", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_detach,
+ _("detach: Detach this session"), NULL);
+ gaim_cmd_register("quit", "s", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_quit,
+ _("quit [message]: Disconnect from the server, with an optional message"), NULL);
+ gaim_cmd_register("call", "s", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_call,
+ _("call &lt;command&gt;: Call any silc client command"), NULL);
+ /* These below just get passed through for the silc client library to deal
+ * with */
+ gaim_cmd_register("kill", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+ _("kill &lt;nick&gt; [-pubkey|&lt;reason&gt;]: Kill nick"), NULL);
+ gaim_cmd_register("nick", "w", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_generic,
+ _("nick &lt;newnick&gt;: Change your nickname"), NULL);
+ gaim_cmd_register("whowas", "ww", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+ _("whowas &lt;nick&gt;: View nick's information"), NULL);
+ gaim_cmd_register("cmode", "wws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_cmode,
+ _("cmode &lt;channel&gt; [+|-&lt;modes&gt;] [arguments]: Change or display channel modes"), NULL);
+ gaim_cmd_register("cumode", "wws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+ _("cumode &lt;channel&gt; +|-&lt;modes&gt; &lt;nick&gt;: Change nick's modes on channel"), NULL);
+ gaim_cmd_register("umode", "w", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_generic,
+ _("umode &lt;usermodes&gt;: Set your modes in the network"), NULL);
+ gaim_cmd_register("oper", "s", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_generic,
+ _("oper &lt;nick&gt; [-pubkey]: Get server operator privileges"), NULL);
+ gaim_cmd_register("invite", "ws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+ _("invite &lt;channel&gt; [-|+]&lt;nick&gt;: invite nick or add/remove from channel invite list"), NULL);
+ gaim_cmd_register("kick", "wws", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+ _("kick &lt;channel&gt; &lt;nick&gt; [comment]: Kick client from channel"), NULL);
+ gaim_cmd_register("info", "w", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+ _("info [server]: View server administrative details"), NULL);
+ gaim_cmd_register("ban", "ww", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_generic,
+ _("ban [&lt;channel&gt; +|-&lt;nick&gt;]: Ban client from channel"), NULL);
+ gaim_cmd_register("getkey", "w", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_generic,
+ _("getkey &lt;nick|server&gt;: Retrieve client's or server's public key"), NULL);
+ gaim_cmd_register("stats", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_generic,
+ _("stats: View server and network statistics"), NULL);
+ gaim_cmd_register("ping", "", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_generic,
+ _("ping: Send PING to the connected server"), NULL);
+#if 0 /* Gaim doesn't handle these yet */
+ gaim_cmd_register("users", "w", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
+ "prpl-silc", silcgaim_cmd_users,
+ _("users &lt;channel&gt;: List users in channel"));
+ gaim_cmd_register("names", "ww", GAIM_CMD_P_PRPL,
+ GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+ GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcgaim_cmd_names,
+ _("names [-count|-ops|-halfops|-voices|-normal] &lt;channel(s)&gt;: List specific users in channel(s)"));
+#endif
+}
+
+static GaimWhiteboardPrplOps silcgaim_wb_ops =
+{
+ silcgaim_wb_start,
+ silcgaim_wb_end,
+ silcgaim_wb_get_dimensions,
+ silcgaim_wb_set_dimensions,
+ silcgaim_wb_get_brush,
+ silcgaim_wb_set_brush,
+ silcgaim_wb_send,
+ silcgaim_wb_clear,
+};
+
+static GaimPluginProtocolInfo prpl_info =
+{
+#ifdef HAVE_SILCMIME_H
+ OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
+ OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE,
+#else
+ OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
+ OPT_PROTO_PASSWORD_OPTIONAL,
+#endif
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ {"jpeg,gif,png,bmp", 0, 0, 96, 96, 0, GAIM_ICON_SCALE_DISPLAY}, /* icon_spec */
+#else
+ NO_BUDDY_ICONS,
+#endif
+ silcgaim_list_icon, /* list_icon */
+ silcgaim_list_emblems, /* list_emblems */
+ silcgaim_status_text, /* status_text */
+ silcgaim_tooltip_text, /* tooltip_text */
+ silcgaim_away_states, /* away_states */
+ silcgaim_blist_node_menu, /* blist_node_menu */
+ silcgaim_chat_info, /* chat_info */
+ silcgaim_chat_info_defaults,/* chat_info_defaults */
+ silcgaim_login, /* login */
+ silcgaim_close, /* close */
+ silcgaim_send_im, /* send_im */
+ silcgaim_set_info, /* set_info */
+ NULL, /* send_typing */
+ silcgaim_get_info, /* get_info */
+ silcgaim_set_status, /* set_status */
+ silcgaim_idle_set, /* set_idle */
+ silcgaim_change_passwd, /* change_passwd */
+ silcgaim_add_buddy, /* add_buddy */
+ NULL, /* add_buddies */
+ silcgaim_remove_buddy, /* remove_buddy */
+ NULL, /* remove_buddies */
+ NULL, /* add_permit */
+ NULL, /* add_deny */
+ NULL, /* rem_permit */
+ NULL, /* rem_deny */
+ NULL, /* set_permit_deny */
+ silcgaim_chat_join, /* join_chat */
+ NULL, /* reject_chat */
+ silcgaim_get_chat_name, /* get_chat_name */
+ silcgaim_chat_invite, /* chat_invite */
+ silcgaim_chat_leave, /* chat_leave */
+ NULL, /* chat_whisper */
+ silcgaim_chat_send, /* chat_send */
+ silcgaim_keepalive, /* keepalive */
+ NULL, /* register_user */
+ NULL, /* get_cb_info */
+ NULL, /* get_cb_away */
+ NULL, /* alias_buddy */
+ NULL, /* group_buddy */
+ NULL, /* rename_group */
+ NULL, /* buddy_free */
+ NULL, /* convo_closed */
+ NULL, /* normalize */
+#ifdef SILC_ATTRIBUTE_USER_ICON
+ silcgaim_buddy_set_icon, /* set_buddy_icon */
+#else
+ NULL,
+#endif
+ NULL, /* remove_group */
+ NULL, /* get_cb_real_name */
+ silcgaim_chat_set_topic, /* set_chat_topic */
+ NULL, /* find_blist_chat */
+ silcgaim_roomlist_get_list, /* roomlist_get_list */
+ silcgaim_roomlist_cancel, /* roomlist_cancel */
+ NULL, /* roomlist_expand_category */
+ NULL, /* can_receive_file */
+ silcgaim_ftp_send_file, /* send_file */
+ silcgaim_ftp_new_xfer, /* new_xfer */
+ NULL, /* offline_message */
+ &silcgaim_wb_ops, /* whiteboard_prpl_ops */
+ NULL, /* send_raw */
+ NULL, /* roomlist_room_serialize */
+};
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_PROTOCOL, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ "prpl-silc", /**< id */
+ "SILC", /**< name */
+ "1.0", /**< version */
+ /** summary */
+ N_("SILC Protocol Plugin"),
+ /** description */
+ N_("Secure Internet Live Conferencing (SILC) Protocol"),
+ "Pekka Riikonen", /**< author */
+ "http://silcnet.org/", /**< homepage */
+
+ NULL, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ &prpl_info, /**< extra_info */
+ NULL, /**< prefs_info */
+ silcgaim_actions
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ GaimAccountOption *option;
+ GaimAccountUserSplit *split;
+ char tmp[256];
+ int i;
+ GaimKeyValuePair *kvp;
+ GList *list = NULL;
+
+ silc_plugin = plugin;
+
+ split = gaim_account_user_split_new(_("Network"), "silcnet.org", '@');
+ prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+
+ /* Account options */
+ option = gaim_account_option_string_new(_("Connect server"),
+ "server",
+ "silc.silcnet.org");
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ option = gaim_account_option_int_new(_("Port"), "port", 706);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ g_snprintf(tmp, sizeof(tmp), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcgaim_silcdir());
+ option = gaim_account_option_string_new(_("Public Key file"),
+ "public-key", tmp);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ g_snprintf(tmp, sizeof(tmp), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcgaim_silcdir());
+ option = gaim_account_option_string_new(_("Private Key file"),
+ "private-key", tmp);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ for (i = 0; silc_default_ciphers[i].name; i++) {
+ kvp = g_new0(GaimKeyValuePair, 1);
+ kvp->key = g_strdup(silc_default_ciphers[i].name);
+ kvp->value = g_strdup(silc_default_ciphers[i].name);
+ list = g_list_append(list, kvp);
+ }
+ option = gaim_account_option_list_new(_("Cipher"), "cipher", list);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ list = NULL;
+ for (i = 0; silc_default_hmacs[i].name; i++) {
+ kvp = g_new0(GaimKeyValuePair, 1);
+ kvp->key = g_strdup(silc_default_hmacs[i].name);
+ kvp->value = g_strdup(silc_default_hmacs[i].name);
+ list = g_list_append(list, kvp);
+ }
+ option = gaim_account_option_list_new(_("HMAC"), "hmac", list);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ option = gaim_account_option_bool_new(_("Public key authentication"),
+ "pubkey-auth", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ option = gaim_account_option_bool_new(_("Reject watching by other users"),
+ "reject-watch", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ option = gaim_account_option_bool_new(_("Block invites"),
+ "block-invites", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ option = gaim_account_option_bool_new(_("Block IMs without Key Exchange"),
+ "block-ims", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ option = gaim_account_option_bool_new(_("Reject online status attribute requests"),
+ "reject-attrs", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ option = gaim_account_option_bool_new(_("Block messages to whiteboard"),
+ "block-wb", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ option = gaim_account_option_bool_new(_("Automatically open whiteboard"),
+ "open-wb", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+ option = gaim_account_option_bool_new(_("Digitally sign and verify all messages"),
+ "sign-verify", FALSE);
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+ gaim_prefs_remove("/plugins/prpl/silc");
+
+ silcgaim_register_commands();
+
+#ifdef _WIN32
+ silc_net_win32_init();
+#endif
+}
+
+GAIM_INIT_PLUGIN(silc, init_plugin, info);
diff --git a/libpurple/protocols/silc/silcgaim.h b/libpurple/protocols/silc/silcgaim.h
new file mode 100644
index 0000000000..dc95699640
--- /dev/null
+++ b/libpurple/protocols/silc/silcgaim.h
@@ -0,0 +1,173 @@
+/*
+
+ 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 "cmds.h"
+#include "conversation.h"
+#include "debug.h"
+#include "ft.h"
+#include "notify.h"
+#include "prpl.h"
+#include "request.h"
+#include "roomlist.h"
+#include "server.h"
+#include "util.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
+
+/* Status IDs */
+#define SILCGAIM_STATUS_ID_OFFLINE "offline"
+#define SILCGAIM_STATUS_ID_AVAILABLE "available"
+#define SILCGAIM_STATUS_ID_HYPER "hyper"
+#define SILCGAIM_STATUS_ID_AWAY "away"
+#define SILCGAIM_STATUS_ID_BUSY "busy"
+#define SILCGAIM_STATUS_ID_INDISPOSED "indisposed"
+#define SILCGAIM_STATUS_ID_PAGE "page"
+
+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;
+#ifdef HAVE_SILCMIME_H
+ SilcMimeAssembler mimeass;
+#endif
+ 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(GaimBuddy *buddy);
+void silcgaim_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group);
+void silcgaim_send_buddylist(GaimConnection *gc);
+void silcgaim_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *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);
+void silcgaim_tooltip_text(GaimBuddy *b, GaimNotifyUserInfo *user_info, gboolean full);
+char *silcgaim_status_text(GaimBuddy *b);
+gboolean silcgaim_ip_is_private(const char *ip);
+void silcgaim_ftp_send_file(GaimConnection *gc, const char *name, const char *file);
+GaimXfer *silcgaim_ftp_new_xfer(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);
+GHashTable *silcgaim_chat_info_defaults(GaimConnection *gc, const char *chat_name);
+GList *silcgaim_chat_menu(GaimChat *);
+void silcgaim_chat_join(GaimConnection *gc, GHashTable *data);
+char *silcgaim_get_chat_name(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, GaimMessageFlags flags);
+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);
+void silcgaim_parse_attrs(SilcDList attrs, char **moodstr, char **statusstr,
+ char **contactstr, char **langstr, char **devicestr,
+ char **tzstr, char **geostr);
+#ifdef SILC_ATTRIBUTE_USER_ICON
+void silcgaim_buddy_set_icon(GaimConnection *gc, const char *iconfile);
+#endif
+#ifdef HAVE_SILCMIME_H
+char *silcgaim_file2mime(const char *filename);
+SilcDList silcgaim_image_message(const char *msg, SilcUInt32 *mflags);
+#endif
+
+#ifdef _WIN32
+typedef int uid_t;
+
+struct passwd {
+ char *pw_name; /* user name */
+ char *pw_passwd; /* user password */
+ int pw_uid; /* user id */
+ int pw_gid; /* group id */
+ char *pw_gecos; /* real name */
+ char *pw_dir; /* home directory */
+ char *pw_shell; /* shell program */
+};
+
+struct passwd *getpwuid(int uid);
+int getuid(void);
+int geteuid(void);
+#endif
+
+#endif /* SILCGAIM_H */
diff --git a/libpurple/protocols/silc/util.c b/libpurple/protocols/silc/util.c
new file mode 100644
index 0000000000..82b2aa8fc1
--- /dev/null
+++ b/libpurple/protocols/silc/util.c
@@ -0,0 +1,773 @@
+/*
+
+ silcgaim_util.c
+
+ Author: Pekka Riikonen <priikone@silcnet.org>
+
+ Copyright (C) 2004 - 2005 Pekka Riikonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcgaim.h"
+#include "imgstore.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];
+ int s;
+ memset(tmp, 0, sizeof(tmp));
+ strncpy(tmp, ip + 4, 2);
+ 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];
+ char pkd[256], prd[256];
+ struct stat st;
+ struct passwd *pw;
+ int fd;
+
+ pw = getpwuid(getuid());
+ if (!pw) {
+ gaim_debug_error("silc", "silc: %s\n", strerror(errno));
+ return FALSE;
+ }
+
+ g_snprintf(filename, sizeof(filename) - 1, "%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 ((g_stat(filename, &st)) == -1) {
+ /* If dir doesn't exist */
+ if (errno == ENOENT) {
+ if (pw->pw_uid == geteuid()) {
+ if ((g_mkdir(filename, 0755)) == -1) {
+ gaim_debug_error("silc", "Couldn't create '%s' directory\n", filename);
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't create '%s' directory due to a wrong uid!\n",
+ filename);
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't stat '%s' directory, error: %s\n", filename, strerror(errno));
+ return FALSE;
+ }
+ } else {
+#ifndef _WIN32
+ /* Check the owner of the dir */
+ if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+ gaim_debug_error("silc", "You don't seem to own '%s' directory\n",
+ filename);
+ return FALSE;
+ }
+#endif
+ }
+
+ /*
+ * Check ~./silc/serverkeys directory
+ */
+ if ((g_stat(servfilename, &st)) == -1) {
+ /* If dir doesn't exist */
+ if (errno == ENOENT) {
+ if (pw->pw_uid == geteuid()) {
+ if ((g_mkdir(servfilename, 0755)) == -1) {
+ gaim_debug_error("silc", "Couldn't create '%s' directory\n", servfilename);
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't create '%s' directory due to a wrong uid!\n",
+ servfilename);
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't stat '%s' directory, error: %s\n",
+ servfilename, strerror(errno));
+ return FALSE;
+ }
+ }
+
+ /*
+ * Check ~./silc/clientkeys directory
+ */
+ if ((g_stat(clientfilename, &st)) == -1) {
+ /* If dir doesn't exist */
+ if (errno == ENOENT) {
+ if (pw->pw_uid == geteuid()) {
+ if ((g_mkdir(clientfilename, 0755)) == -1) {
+ gaim_debug_error("silc", "Couldn't create '%s' directory\n", clientfilename);
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't create '%s' directory due to a wrong uid!\n",
+ clientfilename);
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't stat '%s' directory, error: %s\n",
+ clientfilename, strerror(errno));
+ return FALSE;
+ }
+ }
+
+ /*
+ * Check ~./silc/friends directory
+ */
+ if ((g_stat(friendsfilename, &st)) == -1) {
+ /* If dir doesn't exist */
+ if (errno == ENOENT) {
+ if (pw->pw_uid == geteuid()) {
+ if ((g_mkdir(friendsfilename, 0755)) == -1) {
+ gaim_debug_error("silc", "Couldn't create '%s' directory\n", friendsfilename);
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't create '%s' directory due to a wrong uid!\n",
+ friendsfilename);
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't stat '%s' directory, error: %s\n",
+ friendsfilename, strerror(errno));
+ return FALSE;
+ }
+ }
+
+ /*
+ * Check Public and Private keys
+ */
+ g_snprintf(pkd, sizeof(pkd), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcgaim_silcdir());
+ g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcgaim_silcdir());
+ g_snprintf(file_public_key, sizeof(file_public_key) - 1, "%s",
+ gaim_account_get_string(gc->account, "public-key", pkd));
+ g_snprintf(file_private_key, sizeof(file_public_key) - 1, "%s",
+ gaim_account_get_string(gc->account, "private-key", prd));
+
+ if ((g_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);
+ if (!silc_create_key_pair(SILCGAIM_DEF_PKCS,
+ SILCGAIM_DEF_PKCS_LEN,
+ file_public_key, file_private_key, NULL,
+ (gc->password == NULL) ? "" : gc->password,
+ NULL, NULL, NULL, FALSE)) {
+ gaim_debug_error("silc", "Couldn't create key pair\n");
+ return FALSE;
+ }
+
+ if ((g_stat(file_public_key, &st)) == -1) {
+ gaim_debug_error("silc", "Couldn't stat '%s' public key, error: %s\n",
+ file_public_key, strerror(errno));
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't stat '%s' public key, error: %s\n",
+ file_public_key, strerror(errno));
+ return FALSE;
+ }
+ }
+
+#ifndef _WIN32
+ /* Check the owner of the public key */
+ if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+ gaim_debug_error("silc", "You don't seem to own your public key!?\n");
+ return FALSE;
+ }
+#endif
+
+ if ((fd = g_open(file_private_key, O_RDONLY, 0)) != -1) {
+ if ((fstat(fd, &st)) == -1) {
+ gaim_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
+ file_private_key, strerror(errno));
+ close(fd);
+ return FALSE;
+ }
+ } else if ((g_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);
+ if (!silc_create_key_pair(SILCGAIM_DEF_PKCS,
+ SILCGAIM_DEF_PKCS_LEN,
+ file_public_key, file_private_key, NULL,
+ (gc->password == NULL) ? "" : gc->password,
+ NULL, NULL, NULL, FALSE)) {
+ gaim_debug_error("silc", "Couldn't create key pair\n");
+ return FALSE;
+ }
+
+ if ((fd = g_open(file_private_key, O_RDONLY, 0)) != -1) {
+ if ((fstat(fd, &st)) == -1) {
+ gaim_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
+ file_private_key, strerror(errno));
+ close(fd);
+ return FALSE;
+ }
+ }
+ /* This shouldn't really happen because silc_create_key_pair()
+ * will set the permissions */
+ else if ((g_stat(file_private_key, &st)) == -1) {
+ gaim_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
+ file_private_key, strerror(errno));
+ return FALSE;
+ }
+ } else {
+ gaim_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
+ file_private_key, strerror(errno));
+ return FALSE;
+ }
+ }
+
+#ifndef _WIN32
+ /* Check the owner of the private key */
+ if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+ gaim_debug_error("silc", "You don't seem to own your private key!?\n");
+ if (fd != -1)
+ close(fd);
+ return FALSE;
+ }
+
+ /* Check the permissions for the private key */
+ if ((st.st_mode & 0777) != 0600) {
+ gaim_debug_warning("silc", "Wrong permissions in your private key file `%s'!\n"
+ "Trying to change them ...\n", file_private_key);
+ if ((fd == -1) || (fchmod(fd, S_IRUSR | S_IWUSR)) == -1) {
+ gaim_debug_error("silc",
+ "Failed to change permissions for private key file!\n"
+ "Permissions for your private key file must be 0600.\n");
+ if (fd != -1)
+ close(fd);
+ return FALSE;
+ }
+ gaim_debug_warning("silc", "Done.\n\n");
+ }
+#endif
+
+ if (fd != -1)
+ close(fd);
+
+ return TRUE;
+}
+
+#ifdef _WIN32
+struct passwd *getpwuid(uid_t uid) {
+ struct passwd *pwd = calloc(1, sizeof(struct passwd));
+ return pwd;
+}
+
+uid_t getuid() {
+ return 0;
+}
+
+uid_t geteuid() {
+ return 0;
+}
+#endif
+
+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((unsigned char *)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)
+ /* Hint for translators: Please check the tabulator width here and in
+ the next strings (short strings: 2 tabs, longer strings 1 tab,
+ sum: 3 tabs or 24 characters) */
+ 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, _("E-Mail: \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%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(sg->gc, _("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] ");
+}
+
+void
+silcgaim_parse_attrs(SilcDList attrs, char **moodstr, char **statusstr,
+ char **contactstr, char **langstr, char **devicestr,
+ char **tzstr, char **geostr)
+{
+ SilcAttributePayload attr;
+ SilcAttributeMood mood = 0;
+ SilcAttributeContact contact;
+ SilcAttributeObjDevice device;
+ SilcAttributeObjGeo geo;
+
+ char tmp[1024];
+ GString *s;
+
+ *moodstr = NULL;
+ *statusstr = NULL;
+ *contactstr = NULL;
+ *langstr = NULL;
+ *devicestr = NULL;
+ *tzstr = NULL;
+ *geostr = NULL;
+
+ if (!attrs)
+ return;
+
+ s = g_string_new("");
+ attr = silcgaim_get_attr(attrs, SILC_ATTRIBUTE_STATUS_MOOD);
+ if (attr && silc_attribute_get_object(attr, &mood, sizeof(mood))) {
+ if (mood & SILC_ATTRIBUTE_MOOD_HAPPY)
+ g_string_append_printf(s, "[%s] ", _("Happy"));
+ if (mood & SILC_ATTRIBUTE_MOOD_SAD)
+ g_string_append_printf(s, "[%s] ", _("Sad"));
+ if (mood & SILC_ATTRIBUTE_MOOD_ANGRY)
+ g_string_append_printf(s, "[%s] ", _("Angry"));
+ if (mood & SILC_ATTRIBUTE_MOOD_JEALOUS)
+ g_string_append_printf(s, "[%s] ", _("Jealous"));
+ if (mood & SILC_ATTRIBUTE_MOOD_ASHAMED)
+ g_string_append_printf(s, "[%s] ", _("Ashamed"));
+ if (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE)
+ g_string_append_printf(s, "[%s] ", _("Invincible"));
+ if (mood & SILC_ATTRIBUTE_MOOD_INLOVE)
+ g_string_append_printf(s, "[%s] ", _("In Love"));
+ if (mood & SILC_ATTRIBUTE_MOOD_SLEEPY)
+ g_string_append_printf(s, "[%s] ", _("Sleepy"));
+ if (mood & SILC_ATTRIBUTE_MOOD_BORED)
+ g_string_append_printf(s, "[%s] ", _("Bored"));
+ if (mood & SILC_ATTRIBUTE_MOOD_EXCITED)
+ g_string_append_printf(s, "[%s] ", _("Excited"));
+ if (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS)
+ g_string_append_printf(s, "[%s] ", _("Anxious"));
+ }
+ if (strlen(s->str)) {
+ *moodstr = s->str;
+ g_string_free(s, FALSE);
+ } else
+ g_string_free(s, TRUE);
+
+ attr = silcgaim_get_attr(attrs, SILC_ATTRIBUTE_STATUS_FREETEXT);
+ memset(tmp, 0, sizeof(tmp));
+ if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+ *statusstr = g_strdup(tmp);
+
+ s = g_string_new("");
+ attr = silcgaim_get_attr(attrs, SILC_ATTRIBUTE_PREFERRED_CONTACT);
+ if (attr && silc_attribute_get_object(attr, &contact, sizeof(contact))) {
+ if (contact & SILC_ATTRIBUTE_CONTACT_CHAT)
+ g_string_append_printf(s, "[%s] ", _("Chat"));
+ if (contact & SILC_ATTRIBUTE_CONTACT_EMAIL)
+ g_string_append_printf(s, "[%s] ", _("E-Mail"));
+ if (contact & SILC_ATTRIBUTE_CONTACT_CALL)
+ g_string_append_printf(s, "[%s] ", _("Phone"));
+ if (contact & SILC_ATTRIBUTE_CONTACT_PAGE)
+ g_string_append_printf(s, "[%s] ", _("Paging"));
+ if (contact & SILC_ATTRIBUTE_CONTACT_SMS)
+ g_string_append_printf(s, "[%s] ", _("SMS"));
+ if (contact & SILC_ATTRIBUTE_CONTACT_MMS)
+ g_string_append_printf(s, "[%s] ", _("MMS"));
+ if (contact & SILC_ATTRIBUTE_CONTACT_VIDEO)
+ g_string_append_printf(s, "[%s] ", _("Video Conferencing"));
+ }
+ if (strlen(s->str)) {
+ *contactstr = s->str;
+ g_string_free(s, FALSE);
+ } else
+ g_string_free(s, TRUE);
+
+ attr = silcgaim_get_attr(attrs, SILC_ATTRIBUTE_PREFERRED_LANGUAGE);
+ memset(tmp, 0, sizeof(tmp));
+ if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+ *langstr = g_strdup(tmp);
+
+ s = g_string_new("");
+ attr = silcgaim_get_attr(attrs, SILC_ATTRIBUTE_DEVICE_INFO);
+ memset(&device, 0, sizeof(device));
+ if (attr && silc_attribute_get_object(attr, &device, sizeof(device))) {
+ if (device.type == SILC_ATTRIBUTE_DEVICE_COMPUTER)
+ g_string_append_printf(s, "%s: ", _("Computer"));
+ if (device.type == SILC_ATTRIBUTE_DEVICE_MOBILE_PHONE)
+ g_string_append_printf(s, "%s: ", _("Mobile Phone"));
+ if (device.type == SILC_ATTRIBUTE_DEVICE_PDA)
+ g_string_append_printf(s, "%s: ", _("PDA"));
+ if (device.type == SILC_ATTRIBUTE_DEVICE_TERMINAL)
+ g_string_append_printf(s, "%s: ", _("Terminal"));
+ g_string_append_printf(s, "%s %s %s %s",
+ device.manufacturer ? device.manufacturer : "",
+ device.version ? device.version : "",
+ device.model ? device.model : "",
+ device.language ? device.language : "");
+ }
+ if (strlen(s->str)) {
+ *devicestr = s->str;
+ g_string_free(s, FALSE);
+ } else
+ g_string_free(s, TRUE);
+
+ attr = silcgaim_get_attr(attrs, SILC_ATTRIBUTE_TIMEZONE);
+ memset(tmp, 0, sizeof(tmp));
+ if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+ *tzstr = g_strdup(tmp);
+
+ attr = silcgaim_get_attr(attrs, SILC_ATTRIBUTE_GEOLOCATION);
+ memset(&geo, 0, sizeof(geo));
+ if (attr && silc_attribute_get_object(attr, &geo, sizeof(geo)))
+ *geostr = g_strdup_printf("%s %s %s (%s)",
+ geo.longitude ? geo.longitude : "",
+ geo.latitude ? geo.latitude : "",
+ geo.altitude ? geo.altitude : "",
+ geo.accuracy ? geo.accuracy : "");
+}
+
+#ifdef HAVE_SILCMIME_H
+/* Returns MIME type of filetype */
+
+char *silcgaim_file2mime(const char *filename)
+{
+ const char *ct;
+
+ ct = strrchr(filename, '.');
+ if (!ct)
+ return NULL;
+ else if (!strcasecmp(".png", ct))
+ return strdup("image/png");
+ else if (!strcasecmp(".jpg", ct))
+ return strdup("image/jpeg");
+ else if (!strcasecmp(".jpeg", ct))
+ return strdup("image/jpeg");
+ else if (!strcasecmp(".gif", ct))
+ return strdup("image/gif");
+ else if (!strcasecmp(".tiff", ct))
+ return strdup("image/tiff");
+
+ return NULL;
+}
+
+/* Checks if message has images, and assembles MIME message if it has.
+ If only one image is present, creates simple MIME image message. If
+ there are multiple images and/or text with images multipart MIME
+ message is created. */
+
+SilcDList silcgaim_image_message(const char *msg, SilcUInt32 *mflags)
+{
+ SilcMime mime = NULL, p;
+ SilcDList list, parts = NULL;
+ const char *start, *end, *last;
+ GData *attribs;
+ char *type;
+ gboolean images = FALSE;
+
+ last = msg;
+ while (last && *last && gaim_markup_find_tag("img", last, &start,
+ &end, &attribs)) {
+ GaimStoredImage *image = NULL;
+ const char *id;
+
+ /* Check if there is text before image */
+ if (start - last) {
+ char *text, *tmp;
+ p = silc_mime_alloc();
+
+ /* Add content type */
+ silc_mime_add_field(p, "Content-Type",
+ "text/plain; charset=utf-8");
+
+ tmp = g_strndup(last, start - last);
+ text = gaim_unescape_html(tmp);
+ g_free(tmp);
+ /* Add text */
+ silc_mime_add_data(p, text, strlen(text));
+ g_free(text);
+
+ if (!parts)
+ parts = silc_dlist_init();
+ silc_dlist_add(parts, p);
+ }
+
+ id = g_datalist_get_data(&attribs, "id");
+ if (id && (image = gaim_imgstore_get(atoi(id)))) {
+ unsigned long imglen = gaim_imgstore_get_size(image);
+ gpointer img = gaim_imgstore_get_data(image);
+
+ p = silc_mime_alloc();
+
+ /* Add content type */
+ type = silcgaim_file2mime(gaim_imgstore_get_filename(image));
+ if (!type) {
+ g_datalist_clear(&attribs);
+ last = end + 1;
+ continue;
+ }
+ silc_mime_add_field(p, "Content-Type", type);
+ silc_free(type);
+
+ /* Add content transfer encoding */
+ silc_mime_add_field(p, "Content-Transfer-Encoding", "binary");
+
+ /* Add image data */
+ silc_mime_add_data(p, img, imglen);
+
+ if (!parts)
+ parts = silc_dlist_init();
+ silc_dlist_add(parts, p);
+ images = TRUE;
+ }
+
+ g_datalist_clear(&attribs);
+
+ /* Continue after tag */
+ last = end + 1;
+ }
+
+ /* Check for text after the image(s) */
+ if (images && last && *last) {
+ char *tmp = gaim_unescape_html(last);
+ p = silc_mime_alloc();
+
+ /* Add content type */
+ silc_mime_add_field(p, "Content-Type",
+ "text/plain; charset=utf-8");
+
+ /* Add text */
+ silc_mime_add_data(p, tmp, strlen(tmp));
+ g_free(tmp);
+
+ if (!parts)
+ parts = silc_dlist_init();
+ silc_dlist_add(parts, p);
+ }
+
+ /* If there weren't any images, don't return anything. */
+ if (!images) {
+ if (parts)
+ silc_dlist_uninit(parts);
+ return NULL;
+ }
+
+ if (silc_dlist_count(parts) > 1) {
+ /* Multipart MIME message */
+ char b[32];
+ mime = silc_mime_alloc();
+ silc_mime_add_field(mime, "MIME-Version", "1.0");
+ g_snprintf(b, sizeof(b), "b%4X%4X",
+ (unsigned int)time(NULL),
+ silc_dlist_count(parts));
+ silc_mime_set_multipart(mime, "mixed", b);
+ silc_dlist_start(parts);
+ while ((p = silc_dlist_get(parts)) != SILC_LIST_END)
+ silc_mime_add_multipart(mime, p);
+ } else {
+ /* Simple MIME message */
+ silc_dlist_start(parts);
+ mime = silc_dlist_get(parts);
+ silc_mime_add_field(mime, "MIME-Version", "1.0");
+ }
+
+ *mflags &= ~SILC_MESSAGE_FLAG_UTF8;
+ *mflags |= SILC_MESSAGE_FLAG_DATA;
+
+ /* Encode message. Fragment if it is too large */
+ list = silc_mime_encode_partial(mime, 0xfc00);
+
+ silc_dlist_uninit(parts);
+
+ /* Added multiparts gets freed here */
+ silc_mime_free(mime);
+
+ return list;
+}
+
+#endif /* HAVE_SILCMIME_H */
diff --git a/libpurple/protocols/silc/wb.c b/libpurple/protocols/silc/wb.c
new file mode 100644
index 0000000000..bca52a11d5
--- /dev/null
+++ b/libpurple/protocols/silc/wb.c
@@ -0,0 +1,516 @@
+/*
+
+ wb.c
+
+ Author: Pekka Riikonen <priikone@silcnet.org>
+
+ Copyright (C) 2005 Pekka Riikonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcgaim.h"
+#include "wb.h"
+
+/*
+ SILC Whiteboard packet:
+
+ 1 byte command
+ 2 bytes width
+ 2 bytes height
+ 4 bytes brush color
+ 2 bytes brush size
+ n bytes data
+
+ Data:
+
+ 4 bytes x
+ 4 bytes y
+
+ Commands:
+
+ 0x01 draw
+ 0x02 clear
+
+ MIME:
+
+ MIME-Version: 1.0
+ Content-Type: application/x-wb
+ Content-Transfer-Encoding: binary
+
+*/
+
+#define SILCGAIM_WB_MIME "MIME-Version: 1.0\r\nContent-Type: application/x-wb\r\nContent-Transfer-Encoding: binary\r\n\r\n"
+#define SILCGAIM_WB_HEADER strlen(SILCGAIM_WB_MIME) + 11
+
+#define SILCGAIM_WB_WIDTH 500
+#define SILCGAIM_WB_HEIGHT 400
+#define SILCGAIM_WB_WIDTH_MAX 1024
+#define SILCGAIM_WB_HEIGHT_MAX 1024
+
+/* Commands */
+typedef enum {
+ SILCGAIM_WB_DRAW = 0x01,
+ SILCGAIM_WB_CLEAR = 0x02,
+} SilcGaimWbCommand;
+
+/* Brush size */
+typedef enum {
+ SILCGAIM_WB_BRUSH_SMALL = 2,
+ SILCGAIM_WB_BRUSH_MEDIUM = 5,
+ SILCGAIM_WB_BRUSH_LARGE = 10,
+} SilcGaimWbBrushSize;
+
+/* Brush color (XXX Gaim should provide default colors) */
+typedef enum {
+ SILCGAIM_WB_COLOR_BLACK = 0,
+ SILCGAIM_WB_COLOR_RED = 13369344,
+ SILCGAIM_WB_COLOR_GREEN = 52224,
+ SILCGAIM_WB_COLOR_BLUE = 204,
+ SILCGAIM_WB_COLOR_YELLOW = 15658496,
+ SILCGAIM_WB_COLOR_ORANGE = 16737792,
+ SILCGAIM_WB_COLOR_CYAN = 52428,
+ SILCGAIM_WB_COLOR_VIOLET = 5381277,
+ SILCGAIM_WB_COLOR_PURPLE = 13369548,
+ SILCGAIM_WB_COLOR_TAN = 12093547,
+ SILCGAIM_WB_COLOR_BROWN = 5256485,
+ SILCGAIM_WB_COLOR_GREY = 11184810,
+ SILCGAIM_WB_COLOR_WHITE = 16777215,
+} SilcGaimWbColor;
+
+typedef struct {
+ int type; /* 0 = buddy, 1 = channel */
+ union {
+ SilcClientEntry client;
+ SilcChannelEntry channel;
+ } u;
+ int width;
+ int height;
+ int brush_size;
+ int brush_color;
+} *SilcGaimWb;
+
+/* Initialize whiteboard */
+
+GaimWhiteboard *silcgaim_wb_init(SilcGaim sg, SilcClientEntry client_entry)
+{
+ SilcClientConnection conn;
+ GaimWhiteboard *wb;
+ SilcGaimWb wbs;
+
+ conn = sg->conn;
+ wb = gaim_whiteboard_get_session(sg->account, client_entry->nickname);
+ if (!wb)
+ wb = gaim_whiteboard_create(sg->account, client_entry->nickname, 0);
+ if (!wb)
+ return NULL;
+
+ if (!wb->proto_data) {
+ wbs = silc_calloc(1, sizeof(*wbs));
+ if (!wbs)
+ return NULL;
+ wbs->type = 0;
+ wbs->u.client = client_entry;
+ wbs->width = SILCGAIM_WB_WIDTH;
+ wbs->height = SILCGAIM_WB_HEIGHT;
+ wbs->brush_size = SILCGAIM_WB_BRUSH_SMALL;
+ wbs->brush_color = SILCGAIM_WB_COLOR_BLACK;
+ wb->proto_data = wbs;
+
+ /* Start the whiteboard */
+ gaim_whiteboard_start(wb);
+ gaim_whiteboard_clear(wb);
+ }
+
+ return wb;
+}
+
+GaimWhiteboard *silcgaim_wb_init_ch(SilcGaim sg, SilcChannelEntry channel)
+{
+ GaimWhiteboard *wb;
+ SilcGaimWb wbs;
+
+ wb = gaim_whiteboard_get_session(sg->account, channel->channel_name);
+ if (!wb)
+ wb = gaim_whiteboard_create(sg->account, channel->channel_name, 0);
+ if (!wb)
+ return NULL;
+
+ if (!wb->proto_data) {
+ wbs = silc_calloc(1, sizeof(*wbs));
+ if (!wbs)
+ return NULL;
+ wbs->type = 1;
+ wbs->u.channel = channel;
+ wbs->width = SILCGAIM_WB_WIDTH;
+ wbs->height = SILCGAIM_WB_HEIGHT;
+ wbs->brush_size = SILCGAIM_WB_BRUSH_SMALL;
+ wbs->brush_color = SILCGAIM_WB_COLOR_BLACK;
+ wb->proto_data = wbs;
+
+ /* Start the whiteboard */
+ gaim_whiteboard_start(wb);
+ gaim_whiteboard_clear(wb);
+ }
+
+ return wb;
+}
+
+static void
+silcgaim_wb_parse(SilcGaimWb wbs, GaimWhiteboard *wb,
+ unsigned char *message, SilcUInt32 message_len)
+{
+ SilcUInt8 command;
+ SilcUInt16 width, height, brush_size;
+ SilcUInt32 brush_color, x, y, dx, dy;
+ SilcBufferStruct buf;
+ int ret;
+
+ /* Parse the packet */
+ silc_buffer_set(&buf, message, message_len);
+ ret = silc_buffer_unformat(&buf,
+ SILC_STR_UI_CHAR(&command),
+ SILC_STR_UI_SHORT(&width),
+ SILC_STR_UI_SHORT(&height),
+ SILC_STR_UI_INT(&brush_color),
+ SILC_STR_UI_SHORT(&brush_size),
+ SILC_STR_END);
+ if (ret < 0)
+ return;
+ silc_buffer_pull(&buf, ret);
+
+ /* Update whiteboard if its dimensions changed */
+ if (width != wbs->width || height != wbs->height)
+ silcgaim_wb_set_dimensions(wb, height, width);
+
+ if (command == SILCGAIM_WB_DRAW) {
+ /* Parse data and draw it */
+ ret = silc_buffer_unformat(&buf,
+ SILC_STR_UI_INT(&dx),
+ SILC_STR_UI_INT(&dy),
+ SILC_STR_END);
+ if (ret < 0)
+ return;
+ silc_buffer_pull(&buf, 8);
+ x = dx;
+ y = dy;
+ while (buf.len > 0) {
+ ret = silc_buffer_unformat(&buf,
+ SILC_STR_UI_INT(&dx),
+ SILC_STR_UI_INT(&dy),
+ SILC_STR_END);
+ if (ret < 0)
+ return;
+ silc_buffer_pull(&buf, 8);
+
+ gaim_whiteboard_draw_line(wb, x, y, x + dx, y + dy,
+ brush_color, brush_size);
+ x += dx;
+ y += dy;
+ }
+ }
+
+ if (command == SILCGAIM_WB_CLEAR)
+ gaim_whiteboard_clear(wb);
+}
+
+typedef struct {
+ unsigned char *message;
+ SilcUInt32 message_len;
+ SilcGaim sg;
+ SilcClientEntry sender;
+ SilcChannelEntry channel;
+} *SilcGaimWbRequest;
+
+static void
+silcgaim_wb_request_cb(SilcGaimWbRequest req, gint id)
+{
+ GaimWhiteboard *wb;
+
+ if (id != 1)
+ goto out;
+
+ if (!req->channel)
+ wb = silcgaim_wb_init(req->sg, req->sender);
+ else
+ wb = silcgaim_wb_init_ch(req->sg, req->channel);
+
+ silcgaim_wb_parse(wb->proto_data, wb, req->message, req->message_len);
+
+ out:
+ silc_free(req->message);
+ silc_free(req);
+}
+
+static void
+silcgaim_wb_request(SilcClient client, const unsigned char *message,
+ SilcUInt32 message_len, SilcClientEntry sender,
+ SilcChannelEntry channel)
+{
+ char tmp[128];
+ SilcGaimWbRequest req;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ gc = client->application;
+ sg = gc->proto_data;
+
+ /* Open whiteboard automatically if requested */
+ if (gaim_account_get_bool(sg->account, "open-wb", FALSE)) {
+ GaimWhiteboard *wb;
+
+ if (!channel)
+ wb = silcgaim_wb_init(sg, sender);
+ else
+ wb = silcgaim_wb_init_ch(sg, channel);
+
+ silcgaim_wb_parse(wb->proto_data, wb, (unsigned char *)message,
+ message_len);
+ return;
+ }
+
+ if (!channel) {
+ g_snprintf(tmp, sizeof(tmp),
+ _("%s sent message to whiteboard. Would you like "
+ "to open the whiteboard?"), sender->nickname);
+ } else {
+ g_snprintf(tmp, sizeof(tmp),
+ _("%s sent message to whiteboard on %s channel. "
+ "Would you like to open the whiteboard?"),
+ sender->nickname, channel->channel_name);
+ }
+
+ req = silc_calloc(1, sizeof(*req));
+ if (!req)
+ return;
+ req->message = silc_memdup(message, message_len);
+ req->message_len = message_len;
+ req->sender = sender;
+ req->channel = channel;
+ req->sg = sg;
+
+ gaim_request_action(gc, _("Whiteboard"), tmp, NULL, 1, req, 2,
+ _("Yes"), G_CALLBACK(silcgaim_wb_request_cb),
+ _("No"), G_CALLBACK(silcgaim_wb_request_cb));
+}
+
+/* Process incoming whiteboard message */
+
+void silcgaim_wb_receive(SilcClient client, SilcClientConnection conn,
+ SilcClientEntry sender, SilcMessagePayload payload,
+ SilcMessageFlags flags, const unsigned char *message,
+ SilcUInt32 message_len)
+{
+ SilcGaim sg;
+ GaimConnection *gc;
+ GaimWhiteboard *wb;
+ SilcGaimWb wbs;
+
+ gc = client->application;
+ sg = gc->proto_data;
+
+ wb = gaim_whiteboard_get_session(sg->account, sender->nickname);
+ if (!wb) {
+ /* Ask user if they want to open the whiteboard */
+ silcgaim_wb_request(client, message, message_len,
+ sender, NULL);
+ return;
+ }
+
+ wbs = wb->proto_data;
+ silcgaim_wb_parse(wbs, wb, (unsigned char *)message, message_len);
+}
+
+/* Process incoming whiteboard message on channel */
+
+void silcgaim_wb_receive_ch(SilcClient client, SilcClientConnection conn,
+ SilcClientEntry sender, SilcChannelEntry channel,
+ SilcMessagePayload payload,
+ SilcMessageFlags flags,
+ const unsigned char *message,
+ SilcUInt32 message_len)
+{
+ SilcGaim sg;
+ GaimConnection *gc;
+ GaimWhiteboard *wb;
+ SilcGaimWb wbs;
+
+ gc = client->application;
+ sg = gc->proto_data;
+
+ wb = gaim_whiteboard_get_session(sg->account, channel->channel_name);
+ if (!wb) {
+ /* Ask user if they want to open the whiteboard */
+ silcgaim_wb_request(client, message, message_len,
+ sender, channel);
+ return;
+ }
+
+ wbs = wb->proto_data;
+ silcgaim_wb_parse(wbs, wb, (unsigned char *)message, message_len);
+}
+
+/* Send whiteboard message */
+
+void silcgaim_wb_send(GaimWhiteboard *wb, GList *draw_list)
+{
+ SilcGaimWb wbs = wb->proto_data;
+ SilcBuffer packet;
+ GList *list;
+ int len;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ g_return_if_fail(draw_list);
+ gc = gaim_account_get_connection(wb->account);
+ g_return_if_fail(gc);
+ sg = gc->proto_data;
+ g_return_if_fail(sg);
+
+ len = SILCGAIM_WB_HEADER;
+ for (list = draw_list; list; list = list->next)
+ len += 4;
+
+ packet = silc_buffer_alloc_size(len);
+ if (!packet)
+ return;
+
+ /* Assmeble packet */
+ silc_buffer_format(packet,
+ SILC_STR_UI32_STRING(SILCGAIM_WB_MIME),
+ SILC_STR_UI_CHAR(SILCGAIM_WB_DRAW),
+ SILC_STR_UI_SHORT(wbs->width),
+ SILC_STR_UI_SHORT(wbs->height),
+ SILC_STR_UI_INT(wbs->brush_color),
+ SILC_STR_UI_SHORT(wbs->brush_size),
+ SILC_STR_END);
+ silc_buffer_pull(packet, SILCGAIM_WB_HEADER);
+ for (list = draw_list; list; list = list->next) {
+ silc_buffer_format(packet,
+ SILC_STR_UI_INT(GPOINTER_TO_INT(list->data)),
+ SILC_STR_END);
+ silc_buffer_pull(packet, 4);
+ }
+
+ /* Send the message */
+ if (wbs->type == 0) {
+ /* Private message */
+ silc_client_send_private_message(sg->client, sg->conn,
+ wbs->u.client,
+ SILC_MESSAGE_FLAG_DATA,
+ packet->head, len, TRUE);
+ } else if (wbs->type == 1) {
+ /* Channel message. Channel private keys are not supported. */
+ silc_client_send_channel_message(sg->client, sg->conn,
+ wbs->u.channel, NULL,
+ SILC_MESSAGE_FLAG_DATA,
+ packet->head, len, TRUE);
+ }
+
+ silc_buffer_free(packet);
+}
+
+/* Gaim Whiteboard operations */
+
+void silcgaim_wb_start(GaimWhiteboard *wb)
+{
+ /* Nothing here. Everything is in initialization */
+}
+
+void silcgaim_wb_end(GaimWhiteboard *wb)
+{
+ silc_free(wb->proto_data);
+ wb->proto_data = NULL;
+}
+
+void silcgaim_wb_get_dimensions(GaimWhiteboard *wb, int *width, int *height)
+{
+ SilcGaimWb wbs = wb->proto_data;
+ *width = wbs->width;
+ *height = wbs->height;
+}
+
+void silcgaim_wb_set_dimensions(GaimWhiteboard *wb, int width, int height)
+{
+ SilcGaimWb wbs = wb->proto_data;
+ wbs->width = width > SILCGAIM_WB_WIDTH_MAX ? SILCGAIM_WB_WIDTH_MAX :
+ width;
+ wbs->height = height > SILCGAIM_WB_HEIGHT_MAX ? SILCGAIM_WB_HEIGHT_MAX :
+ height;
+
+ /* Update whiteboard */
+ gaim_whiteboard_set_dimensions(wb, wbs->width, wbs->height);
+}
+
+void silcgaim_wb_get_brush(GaimWhiteboard *wb, int *size, int *color)
+{
+ SilcGaimWb wbs = wb->proto_data;
+ *size = wbs->brush_size;
+ *color = wbs->brush_color;
+}
+
+void silcgaim_wb_set_brush(GaimWhiteboard *wb, int size, int color)
+{
+ SilcGaimWb wbs = wb->proto_data;
+ wbs->brush_size = size;
+ wbs->brush_color = color;
+
+ /* Update whiteboard */
+ gaim_whiteboard_set_brush(wb, size, color);
+}
+
+void silcgaim_wb_clear(GaimWhiteboard *wb)
+{
+ SilcGaimWb wbs = wb->proto_data;
+ SilcBuffer packet;
+ int len;
+ GaimConnection *gc;
+ SilcGaim sg;
+
+ gc = gaim_account_get_connection(wb->account);
+ g_return_if_fail(gc);
+ sg = gc->proto_data;
+ g_return_if_fail(sg);
+
+ len = SILCGAIM_WB_HEADER;
+ packet = silc_buffer_alloc_size(len);
+ if (!packet)
+ return;
+
+ /* Assmeble packet */
+ silc_buffer_format(packet,
+ SILC_STR_UI32_STRING(SILCGAIM_WB_MIME),
+ SILC_STR_UI_CHAR(SILCGAIM_WB_CLEAR),
+ SILC_STR_UI_SHORT(wbs->width),
+ SILC_STR_UI_SHORT(wbs->height),
+ SILC_STR_UI_INT(wbs->brush_color),
+ SILC_STR_UI_SHORT(wbs->brush_size),
+ SILC_STR_END);
+
+ /* Send the message */
+ if (wbs->type == 0) {
+ /* Private message */
+ silc_client_send_private_message(sg->client, sg->conn,
+ wbs->u.client,
+ SILC_MESSAGE_FLAG_DATA,
+ packet->head, len, TRUE);
+ } else if (wbs->type == 1) {
+ /* Channel message */
+ silc_client_send_channel_message(sg->client, sg->conn,
+ wbs->u.channel, NULL,
+ SILC_MESSAGE_FLAG_DATA,
+ packet->head, len, TRUE);
+ }
+
+ silc_buffer_free(packet);
+}
diff --git a/libpurple/protocols/silc/wb.h b/libpurple/protocols/silc/wb.h
new file mode 100644
index 0000000000..4cbf5e6405
--- /dev/null
+++ b/libpurple/protocols/silc/wb.h
@@ -0,0 +1,49 @@
+/*
+
+ silcgaim.h
+
+ Author: Pekka Riikonen <priikone@silcnet.org>
+
+ Copyright (C) 2005 Pekka Riikonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+*/
+
+#ifndef SILCGAIM_WB_H
+#define SILCGAIM_WB_H
+
+#include "silcgaim.h"
+#include "whiteboard.h"
+
+GaimWhiteboard *
+silcgaim_wb_init(SilcGaim sg, SilcClientEntry client_entry);
+GaimWhiteboard *
+silcgaim_wb_init_ch(SilcGaim sg, SilcChannelEntry channel);
+void silcgaim_wb_receive(SilcClient client, SilcClientConnection conn,
+ SilcClientEntry sender, SilcMessagePayload payload,
+ SilcMessageFlags flags, const unsigned char *message,
+ SilcUInt32 message_len);
+void silcgaim_wb_receive_ch(SilcClient client, SilcClientConnection conn,
+ SilcClientEntry sender, SilcChannelEntry channel,
+ SilcMessagePayload payload,
+ SilcMessageFlags flags,
+ const unsigned char *message,
+ SilcUInt32 message_len);
+void silcgaim_wb_start(GaimWhiteboard *wb);
+void silcgaim_wb_end(GaimWhiteboard *wb);
+void silcgaim_wb_get_dimensions(GaimWhiteboard *wb, int *width, int *height);
+void silcgaim_wb_set_dimensions(GaimWhiteboard *wb, int width, int height);
+void silcgaim_wb_get_brush(GaimWhiteboard *wb, int *size, int *color);
+void silcgaim_wb_set_brush(GaimWhiteboard *wb, int size, int color);
+void silcgaim_wb_send(GaimWhiteboard *wb, GList *draw_list);
+void silcgaim_wb_clear(GaimWhiteboard *wb);
+
+#endif /* SILCGAIM_WB_H */