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