Mercurial > pidgin.yaz
diff libgaim/protocols/silc/ops.c @ 14192:60b1bc8dbf37
[gaim-migrate @ 16863]
Renamed 'core' to 'libgaim'
committer: Tailor Script <tailor@pidgin.im>
author | Evan Schoenberg <evan.s@dreskin.net> |
---|---|
date | Sat, 19 Aug 2006 01:50:10 +0000 |
parents | |
children | 9d3ff97e8105 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libgaim/protocols/silc/ops.c Sat Aug 19 01:50:10 2006 +0000 @@ -0,0 +1,2064 @@ +/* + + silcgaim_ops.c + + Author: Pekka Riikonen <priikone@silcnet.org> + + Copyright (C) 2004 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + +*/ + +#include "silcincludes.h" +#include "silcclient.h" +#include "silcgaim.h" +#include "imgstore.h" +#include "wb.h" + +static void +silc_channel_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcChannelEntry channel, + SilcMessagePayload payload, SilcChannelPrivateKey key, + SilcMessageFlags flags, const unsigned char *message, + SilcUInt32 message_len); +static void +silc_private_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcMessagePayload payload, + SilcMessageFlags flags, const unsigned char *message, + SilcUInt32 message_len); + +/* Message sent to the application by library. `conn' associates the + message to a specific connection. `conn', however, may be NULL. + The `type' indicates the type of the message sent by the library. + The application can for example filter the message according the + type. */ + +static void +silc_say(SilcClient client, SilcClientConnection conn, + SilcClientMessageType type, char *msg, ...) +{ + /* Nothing */ +} + +#ifdef HAVE_SILCMIME_H +/* Processes incoming MIME message. Can be private message or channel + message. */ + +static void +silcgaim_mime_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcChannelEntry channel, + SilcMessagePayload payload, SilcChannelPrivateKey key, + SilcMessageFlags flags, SilcMime mime, + gboolean recursive) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + const char *type; + const unsigned char *data; + SilcUInt32 data_len; + GaimMessageFlags cflags = 0; + GaimConversation *convo = NULL; + + if (!mime) + return; + + /* Check for fragmented MIME message */ + if (silc_mime_is_partial(mime)) { + if (!sg->mimeass) + sg->mimeass = silc_mime_assembler_alloc(); + + /* Defragment */ + mime = silc_mime_assemble(sg->mimeass, mime); + if (!mime) + /* More fragments to come */ + return; + + /* Process the complete message */ + silcgaim_mime_message(client, conn, sender, channel, + payload, key, flags, mime, FALSE); + return; + } + + /* Check for multipart message */ + if (silc_mime_is_multipart(mime)) { + SilcMime p; + const char *mtype; + SilcDList parts = silc_mime_get_multiparts(mime, &mtype); + + /* Only "mixed" type supported */ + if (strcmp(mtype, "mixed")) + goto out; + + silc_dlist_start(parts); + while ((p = silc_dlist_get(parts)) != SILC_LIST_END) { + /* Recursively process parts */ + silcgaim_mime_message(client, conn, sender, channel, + payload, key, flags, p, TRUE); + } + goto out; + } + + /* Get content type and MIME data */ + type = silc_mime_get_field(mime, "Content-Type"); + if (!type) + goto out; + data = silc_mime_get_data(mime, &data_len); + if (!data) + goto out; + + /* Process according to content type */ + + /* Plain text */ + if (strstr(type, "text/plain")) { + /* Default is UTF-8, don't check for other charsets */ + if (!strstr(type, "utf-8")) + goto out; + + if (channel) + silc_channel_message(client, conn, sender, channel, + payload, key, + SILC_MESSAGE_FLAG_UTF8, data, + data_len); + else + silc_private_message(client, conn, sender, payload, + SILC_MESSAGE_FLAG_UTF8, data, + data_len); + goto out; + } + + /* Image */ + if (strstr(type, "image/png") || + strstr(type, "image/jpeg") || + strstr(type, "image/gif") || + strstr(type, "image/tiff")) { + char tmp[32]; + int imgid; + + /* Get channel convo (if message is for channel) */ + if (key && channel) { + GList *l; + SilcGaimPrvgrp prv; + + for (l = sg->grps; l; l = l->next) + if (((SilcGaimPrvgrp)l->data)->key == key) { + prv = l->data; + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + prv->channel, sg->account); + break; + } + } + if (channel && !convo) + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (channel && !convo) + goto out; + + imgid = gaim_imgstore_add(data, data_len, ""); + if (imgid) { + cflags |= GAIM_MESSAGE_IMAGES | GAIM_MESSAGE_RECV; + g_snprintf(tmp, sizeof(tmp), "<IMG ID=\"%d\">", imgid); + + if (channel) + serv_got_chat_in(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)), + sender->nickname ? + sender->nickname : + "<unknown>", cflags, + tmp, time(NULL)); + else + serv_got_im(gc, sender->nickname ? + sender->nickname : "<unknown>", + tmp, cflags, time(NULL)); + + gaim_imgstore_unref(imgid); + cflags = 0; + } + goto out; + } + + /* Whiteboard message */ + if (strstr(type, "application/x-wb") && + !gaim_account_get_bool(sg->account, "block-wb", FALSE)) { + if (channel) + silcgaim_wb_receive_ch(client, conn, sender, channel, + payload, flags, data, data_len); + else + silcgaim_wb_receive(client, conn, sender, payload, + flags, data, data_len); + goto out; + } + + out: + if (!recursive) + silc_mime_free(mime); +} +#endif /* HAVE_SILCMIME_H */ + +/* Message for a channel. The `sender' is the sender of the message + The `channel' is the channel. The `message' is the message. Note + that `message' maybe NULL. The `flags' indicates message flags + and it is used to determine how the message can be interpreted + (like it may tell the message is multimedia message). */ + +static void +silc_channel_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcChannelEntry channel, + SilcMessagePayload payload, SilcChannelPrivateKey key, + SilcMessageFlags flags, const unsigned char *message, + SilcUInt32 message_len) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + GaimConversation *convo = NULL; + char *msg, *tmp; + + if (!message) + return; + + if (key) { + GList *l; + SilcGaimPrvgrp prv; + + for (l = sg->grps; l; l = l->next) + if (((SilcGaimPrvgrp)l->data)->key == key) { + prv = l->data; + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + prv->channel, sg->account); + break; + } + } + if (!convo) + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (!convo) + return; + + if (flags & SILC_MESSAGE_FLAG_SIGNED && + gaim_account_get_bool(sg->account, "sign-verify", FALSE)) { + /* XXX */ + } + + if (flags & SILC_MESSAGE_FLAG_DATA) { + /* Process MIME message */ +#ifdef HAVE_SILCMIME_H + SilcMime mime; + mime = silc_mime_decode(message, message_len); + silcgaim_mime_message(client, conn, sender, channel, payload, + key, flags, mime, FALSE); +#else + char type[128], enc[128]; + unsigned char *data; + SilcUInt32 data_len; + + memset(type, 0, sizeof(type)); + memset(enc, 0, sizeof(enc)); + + if (!silc_mime_parse(message, message_len, NULL, 0, + type, sizeof(type) - 1, enc, sizeof(enc) - 1, &data, + &data_len)) + return; + + if (!strcmp(type, "application/x-wb") && + !strcmp(enc, "binary") && + !gaim_account_get_bool(sg->account, "block-wb", FALSE)) + silcgaim_wb_receive_ch(client, conn, sender, channel, + payload, flags, data, data_len); +#endif + return; + } + + if (flags & SILC_MESSAGE_FLAG_ACTION) { + msg = g_strdup_printf("/me %s", + (const char *)message); + if (!msg) + return; + + tmp = g_markup_escape_text(msg, -1); + /* Send to Gaim */ + serv_got_chat_in(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)), + sender->nickname ? + sender->nickname : "<unknown>", 0, + tmp, time(NULL)); + g_free(tmp); + g_free(msg); + return; + } + + if (flags & SILC_MESSAGE_FLAG_NOTICE) { + msg = g_strdup_printf("(notice) <I>%s</I> %s", + sender->nickname ? + sender->nickname : "<unknown>", + (const char *)message); + if (!msg) + return; + + /* Send to Gaim */ + gaim_conversation_write(convo, NULL, (const char *)msg, + GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(msg); + return; + } + + if (flags & SILC_MESSAGE_FLAG_UTF8) { + tmp = g_markup_escape_text((const char *)message, -1); + /* Send to Gaim */ + serv_got_chat_in(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)), + sender->nickname ? + sender->nickname : "<unknown>", 0, + tmp, time(NULL)); + g_free(tmp); + } +} + + +/* Private message to the client. The `sender' is the sender of the + message. The message is `message'and maybe NULL. The `flags' + indicates message flags and it is used to determine how the message + can be interpreted (like it may tell the message is multimedia + message). */ + +static void +silc_private_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcMessagePayload payload, + SilcMessageFlags flags, const unsigned char *message, + SilcUInt32 message_len) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + GaimConversation *convo = NULL; + char *msg, *tmp; + + if (!message) + return; + + if (sender->nickname) + /* XXX - Should this be GAIM_CONV_TYPE_IM? */ + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_ANY, + sender->nickname, sg->account); + + if (flags & SILC_MESSAGE_FLAG_SIGNED && + gaim_account_get_bool(sg->account, "sign-verify", FALSE)) { + /* XXX */ + } + + if (flags & SILC_MESSAGE_FLAG_DATA) { +#ifdef HAVE_SILCMIME_H + /* Process MIME message */ + SilcMime mime; + mime = silc_mime_decode(message, message_len); + silcgaim_mime_message(client, conn, sender, NULL, payload, + NULL, flags, mime, FALSE); +#else + char type[128], enc[128]; + unsigned char *data; + SilcUInt32 data_len; + + memset(type, 0, sizeof(type)); + memset(enc, 0, sizeof(enc)); + + if (!silc_mime_parse(message, message_len, NULL, 0, + type, sizeof(type) - 1, enc, sizeof(enc) - 1, &data, + &data_len)) + return; + + if (!strcmp(type, "application/x-wb") && + !strcmp(enc, "binary") && + !gaim_account_get_bool(sg->account, "block-wb", FALSE)) + silcgaim_wb_receive(client, conn, sender, payload, + flags, data, data_len); +#endif + return; + } + + if (flags & SILC_MESSAGE_FLAG_ACTION && convo) { + msg = g_strdup_printf("/me %s", + (const char *)message); + if (!msg) + return; + + tmp = g_markup_escape_text(msg, -1); + /* Send to Gaim */ + serv_got_im(gc, sender->nickname ? + sender->nickname : "<unknown>", + tmp, 0, time(NULL)); + g_free(msg); + g_free(tmp); + return; + } + + if (flags & SILC_MESSAGE_FLAG_NOTICE && convo) { + msg = g_strdup_printf("(notice) <I>%s</I> %s", + sender->nickname ? + sender->nickname : "<unknown>", + (const char *)message); + if (!msg) + return; + + /* Send to Gaim */ + gaim_conversation_write(convo, NULL, (const char *)msg, + GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(msg); + return; + } + + if (flags & SILC_MESSAGE_FLAG_UTF8) { + tmp = g_markup_escape_text((const char *)message, -1); + /* Send to Gaim */ + serv_got_im(gc, sender->nickname ? + sender->nickname : "<unknown>", + tmp, 0, time(NULL)); + g_free(tmp); + } +} + + +/* Notify message to the client. The notify arguments are sent in the + same order as servers sends them. The arguments are same as received + from the server except for ID's. If ID is received application receives + the corresponding entry to the ID. For example, if Client ID is received + application receives SilcClientEntry. Also, if the notify type is + for channel the channel entry is sent to application (even if server + does not send it because client library gets the channel entry from + the Channel ID in the packet's header). */ + +static void +silc_notify(SilcClient client, SilcClientConnection conn, + SilcNotifyType type, ...) +{ + va_list va; + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + GaimConversation *convo; + SilcClientEntry client_entry, client_entry2; + SilcChannelEntry channel; + SilcServerEntry server_entry; + SilcIdType idtype; + void *entry; + SilcUInt32 mode; + SilcHashTableList htl; + SilcChannelUser chu; + char buf[512], buf2[512], *tmp, *name; + 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"), strdup(name)); + serv_got_chat_invite(gc, name, client_entry->nickname, NULL, components); + } + break; + + case SILC_NOTIFY_TYPE_JOIN: + client_entry = va_arg(va, SilcClientEntry); + channel = va_arg(va, SilcChannelEntry); + + /* If we joined channel, do nothing */ + if (client_entry == conn->local_entry) + break; + + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (!convo) + break; + + /* Join user to channel */ + g_snprintf(buf, sizeof(buf), "%s@%s", + client_entry->username, client_entry->hostname); + gaim_conv_chat_add_user(GAIM_CONV_CHAT(convo), + g_strdup(client_entry->nickname), buf, GAIM_CBFLAGS_NONE, TRUE); + + break; + + case SILC_NOTIFY_TYPE_LEAVE: + client_entry = va_arg(va, SilcClientEntry); + channel = va_arg(va, SilcChannelEntry); + + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (!convo) + break; + + /* Remove user from channel */ + gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo), + client_entry->nickname, NULL); + + break; + + case SILC_NOTIFY_TYPE_SIGNOFF: + client_entry = va_arg(va, SilcClientEntry); + tmp = va_arg(va, char *); + + if (!client_entry->nickname) + break; + + /* Remove from all channels */ + silc_hash_table_list(client_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + chu->channel->channel_name, sg->account); + if (!convo) + continue; + gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo), + client_entry->nickname, + tmp); + } + silc_hash_table_list_reset(&htl); + + break; + + case SILC_NOTIFY_TYPE_TOPIC_SET: + { + char *esc, *tmp2; + idtype = va_arg(va, int); + entry = va_arg(va, void *); + tmp = va_arg(va, char *); + channel = va_arg(va, SilcChannelEntry); + + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (!convo) + break; + + if (!tmp) + break; + + esc = g_markup_escape_text(tmp, -1); + tmp2 = gaim_markup_linkify(esc); + g_free(esc); + + if (idtype == SILC_ID_CLIENT) { + client_entry = (SilcClientEntry)entry; + g_snprintf(buf, sizeof(buf), + _("%s has changed the topic of <I>%s</I> to: %s"), + client_entry->nickname, channel->channel_name, tmp2); + gaim_conv_chat_write(GAIM_CONV_CHAT(convo), client_entry->nickname, + buf, GAIM_MESSAGE_SYSTEM, time(NULL)); + gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), + client_entry->nickname, tmp); + } else if (idtype == SILC_ID_SERVER) { + server_entry = (SilcServerEntry)entry; + g_snprintf(buf, sizeof(buf), + _("%s has changed the topic of <I>%s</I> to: %s"), + server_entry->server_name, channel->channel_name, tmp2); + gaim_conv_chat_write(GAIM_CONV_CHAT(convo), server_entry->server_name, + buf, GAIM_MESSAGE_SYSTEM, time(NULL)); + gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), + server_entry->server_name, tmp); + } else if (idtype == SILC_ID_CHANNEL) { + channel = (SilcChannelEntry)entry; + g_snprintf(buf, sizeof(buf), + _("%s has changed the topic of <I>%s</I> to: %s"), + channel->channel_name, channel->channel_name, tmp2); + gaim_conv_chat_write(GAIM_CONV_CHAT(convo), channel->channel_name, + buf, GAIM_MESSAGE_SYSTEM, time(NULL)); + gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), + channel->channel_name, tmp); + } else { + gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, tmp); + } + + g_free(tmp2); + + break; + + } + case SILC_NOTIFY_TYPE_NICK_CHANGE: + client_entry = va_arg(va, SilcClientEntry); + client_entry2 = va_arg(va, SilcClientEntry); + + if (!strcmp(client_entry->nickname, client_entry2->nickname)) + break; + + /* Change nick on all channels */ + silc_hash_table_list(client_entry2->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + chu->channel->channel_name, sg->account); + if (!convo) + continue; + if (gaim_conv_chat_find_user(GAIM_CONV_CHAT(convo), client_entry->nickname)) + gaim_conv_chat_rename_user(GAIM_CONV_CHAT(convo), + client_entry->nickname, + client_entry2->nickname); + } + silc_hash_table_list_reset(&htl); + + break; + + case SILC_NOTIFY_TYPE_CMODE_CHANGE: + idtype = va_arg(va, int); + entry = va_arg(va, void *); + mode = va_arg(va, SilcUInt32); + (void)va_arg(va, char *); + (void)va_arg(va, char *); + (void)va_arg(va, char *); + (void)va_arg(va, SilcPublicKey); + buffer = va_arg(va, SilcBuffer); + channel = va_arg(va, SilcChannelEntry); + + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (!convo) + break; + + if (idtype == SILC_ID_CLIENT) + name = ((SilcClientEntry)entry)->nickname; + else if (idtype == SILC_ID_SERVER) + name = ((SilcServerEntry)entry)->server_name; + else + name = ((SilcChannelEntry)entry)->channel_name; + if (!name) + break; + + if (mode) { + silcgaim_get_chmode_string(mode, buf2, sizeof(buf2)); + g_snprintf(buf, sizeof(buf), + _("<I>%s</I> set channel <I>%s</I> modes to: %s"), name, + channel->channel_name, buf2); + } else { + g_snprintf(buf, sizeof(buf), + _("<I>%s</I> removed all channel <I>%s</I> modes"), name, + channel->channel_name); + } + gaim_conv_chat_write(GAIM_CONV_CHAT(convo), channel->channel_name, + buf, GAIM_MESSAGE_SYSTEM, time(NULL)); + break; + + case SILC_NOTIFY_TYPE_CUMODE_CHANGE: + { + GaimConvChatBuddyFlags flags = GAIM_CBFLAGS_NONE; + idtype = va_arg(va, int); + entry = va_arg(va, void *); + mode = va_arg(va, SilcUInt32); + client_entry2 = va_arg(va, SilcClientEntry); + channel = va_arg(va, SilcChannelEntry); + + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (!convo) + break; + + if (idtype == SILC_ID_CLIENT) + name = ((SilcClientEntry)entry)->nickname; + else if (idtype == SILC_ID_SERVER) + name = ((SilcServerEntry)entry)->server_name; + else + name = ((SilcChannelEntry)entry)->channel_name; + if (!name) + break; + + if (mode) { + silcgaim_get_chumode_string(mode, buf2, sizeof(buf2)); + g_snprintf(buf, sizeof(buf), + _("<I>%s</I> set <I>%s's</I> modes to: %s"), name, + client_entry2->nickname, buf2); + if (mode & SILC_CHANNEL_UMODE_CHANFO) + flags |= GAIM_CBFLAGS_FOUNDER; + if (mode & SILC_CHANNEL_UMODE_CHANOP) + flags |= GAIM_CBFLAGS_OP; + } else { + g_snprintf(buf, sizeof(buf), + _("<I>%s</I> removed all <I>%s's</I> modes"), name, + client_entry2->nickname); + } + gaim_conv_chat_write(GAIM_CONV_CHAT(convo), channel->channel_name, + buf, GAIM_MESSAGE_SYSTEM, time(NULL)); + gaim_conv_chat_user_set_flags(GAIM_CONV_CHAT(convo), client_entry2->nickname, flags); + break; + } + + case SILC_NOTIFY_TYPE_MOTD: + tmp = va_arg(va, char *); + silc_free(sg->motd); + sg->motd = silc_memdup(tmp, strlen(tmp)); + break; + + case SILC_NOTIFY_TYPE_KICKED: + client_entry = va_arg(va, SilcClientEntry); + tmp = va_arg(va, char *); + client_entry2 = va_arg(va, SilcClientEntry); + channel = va_arg(va, SilcChannelEntry); + + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (!convo) + break; + + if (client_entry == conn->local_entry) { + /* Remove us from channel */ + g_snprintf(buf, sizeof(buf), + _("You have been kicked off <I>%s</I> by <I>%s</I> (%s)"), + channel->channel_name, client_entry2->nickname, + tmp ? tmp : ""); + gaim_conv_chat_write(GAIM_CONV_CHAT(convo), client_entry->nickname, + buf, GAIM_MESSAGE_SYSTEM, time(NULL)); + serv_got_chat_left(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo))); + } else { + /* Remove user from channel */ + g_snprintf(buf, sizeof(buf), _("Kicked by %s (%s)"), + client_entry2->nickname, tmp ? tmp : ""); + gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo), + client_entry->nickname, + buf); + } + + break; + + case SILC_NOTIFY_TYPE_KILLED: + client_entry = va_arg(va, SilcClientEntry); + tmp = va_arg(va, char *); + idtype = va_arg(va, int); + entry = va_arg(va, SilcClientEntry); + + if (!client_entry->nickname) + break; + + if (client_entry == conn->local_entry) { + if (idtype == SILC_ID_CLIENT) { + client_entry2 = (SilcClientEntry)entry; + g_snprintf(buf, sizeof(buf), + _("You have been killed by %s (%s)"), + client_entry2->nickname, tmp ? tmp : ""); + } else if (idtype == SILC_ID_SERVER) { + server_entry = (SilcServerEntry)entry; + g_snprintf(buf, sizeof(buf), + _("You have been killed by %s (%s)"), + server_entry->server_name, tmp ? tmp : ""); + } else if (idtype == SILC_ID_CHANNEL) { + channel = (SilcChannelEntry)entry; + g_snprintf(buf, sizeof(buf), + _("You have been killed by %s (%s)"), + channel->channel_name, tmp ? tmp : ""); + } + + /* Remove us from all channels */ + silc_hash_table_list(client_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + chu->channel->channel_name, sg->account); + if (!convo) + continue; + gaim_conv_chat_write(GAIM_CONV_CHAT(convo), client_entry->nickname, + buf, GAIM_MESSAGE_SYSTEM, time(NULL)); + serv_got_chat_left(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo))); + } + silc_hash_table_list_reset(&htl); + + } else { + if (idtype == SILC_ID_CLIENT) { + client_entry2 = (SilcClientEntry)entry; + g_snprintf(buf, sizeof(buf), + _("Killed by %s (%s)"), + client_entry2->nickname, tmp ? tmp : ""); + } else if (idtype == SILC_ID_SERVER) { + server_entry = (SilcServerEntry)entry; + g_snprintf(buf, sizeof(buf), + _("Killed by %s (%s)"), + server_entry->server_name, tmp ? tmp : ""); + } else if (idtype == SILC_ID_CHANNEL) { + channel = (SilcChannelEntry)entry; + g_snprintf(buf, sizeof(buf), + _("Killed by %s (%s)"), + channel->channel_name, tmp ? tmp : ""); + } + + /* Remove user from all channels */ + silc_hash_table_list(client_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + chu->channel->channel_name, sg->account); + if (!convo) + continue; + gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo), + client_entry->nickname, tmp); + } + silc_hash_table_list_reset(&htl); + } + + break; + + case SILC_NOTIFY_TYPE_CHANNEL_CHANGE: + break; + + case SILC_NOTIFY_TYPE_SERVER_SIGNOFF: + { + int i; + SilcClientEntry *clients; + SilcUInt32 clients_count; + + (void)va_arg(va, void *); + clients = va_arg(va, SilcClientEntry *); + clients_count = va_arg(va, SilcUInt32); + + for (i = 0; i < clients_count; i++) { + if (!clients[i]->nickname) + break; + + /* Remove from all channels */ + silc_hash_table_list(clients[i]->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + convo = + gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + chu->channel->channel_name, sg->account); + if (!convo) + continue; + gaim_conv_chat_remove_user(GAIM_CONV_CHAT(convo), + clients[i]->nickname, + _("Server signoff")); + } + silc_hash_table_list_reset(&htl); + } + } + break; + + case SILC_NOTIFY_TYPE_ERROR: + { + SilcStatus error = va_arg(va, int); + gaim_notify_error(gc, "Error Notify", + silc_get_status_message(error), + NULL); + } + break; + + case SILC_NOTIFY_TYPE_WATCH: + { + SilcPublicKey public_key; + unsigned char *pk; + SilcUInt32 pk_len; + char *fingerprint; + + client_entry = va_arg(va, SilcClientEntry); + (void)va_arg(va, char *); + mode = va_arg(va, SilcUInt32); + notify = va_arg(va, int); + public_key = va_arg(va, SilcPublicKey); + + b = NULL; + if (public_key) { + GaimBlistNode *gnode, *cnode, *bnode; + const char *f; + + pk = silc_pkcs_public_key_encode(public_key, &pk_len); + if (!pk) + break; + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + for (i = 0; i < strlen(fingerprint); i++) + if (fingerprint[i] == ' ') + fingerprint[i] = '_'; + g_snprintf(buf, sizeof(buf) - 1, + "%s" G_DIR_SEPARATOR_S "clientkeys" + G_DIR_SEPARATOR_S "clientkey_%s.pub", + silcgaim_silcdir(), fingerprint); + silc_free(fingerprint); + silc_free(pk); + + /* Find buddy by associated public key */ + for (gnode = gaim_get_blist()->root; gnode; + gnode = gnode->next) { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + for (cnode = gnode->child; cnode; cnode = cnode->next) { + if( !GAIM_BLIST_NODE_IS_CONTACT(cnode)) + continue; + for (bnode = cnode->child; bnode; + bnode = bnode->next) { + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + b = (GaimBuddy *)bnode; + if (b->account != gc->account) + continue; + f = gaim_blist_node_get_string(bnode, "public-key"); + if (!strcmp(f, buf)) + goto cont; + } + } + } + } + cont: + if (!b) { + /* Find buddy by nickname */ + b = gaim_find_buddy(sg->account, client_entry->nickname); + if (!b) { + gaim_debug_warning("silc", "WATCH for %s, unknown buddy", + client_entry->nickname); + break; + } + } + + silc_free(b->proto_data); + b->proto_data = silc_memdup(client_entry->id, + sizeof(*client_entry->id)); + if (notify == SILC_NOTIFY_TYPE_NICK_CHANGE) { + break; + } else if (notify == SILC_NOTIFY_TYPE_UMODE_CHANGE) { + /* See if client was away and is now present */ + if (!(mode & (SILC_UMODE_GONE | SILC_UMODE_INDISPOSED | + SILC_UMODE_BUSY | SILC_UMODE_PAGE | + SILC_UMODE_DETACHED)) && + (client_entry->mode & SILC_UMODE_GONE || + client_entry->mode & SILC_UMODE_INDISPOSED || + client_entry->mode & SILC_UMODE_BUSY || + client_entry->mode & SILC_UMODE_PAGE || + client_entry->mode & SILC_UMODE_DETACHED)) { + client_entry->mode = mode; + gaim_prpl_got_user_status(gaim_buddy_get_account(b), gaim_buddy_get_name(b), SILCGAIM_STATUS_ID_AVAILABLE, NULL); + } + else if ((mode & SILC_UMODE_GONE) || + (mode & SILC_UMODE_INDISPOSED) || + (mode & SILC_UMODE_BUSY) || + (mode & SILC_UMODE_PAGE) || + (mode & SILC_UMODE_DETACHED)) { + client_entry->mode = mode; + gaim_prpl_got_user_status(gaim_buddy_get_account(b), gaim_buddy_get_name(b), SILCGAIM_STATUS_ID_OFFLINE, NULL); + } + } else if (notify == SILC_NOTIFY_TYPE_SIGNOFF || + notify == SILC_NOTIFY_TYPE_SERVER_SIGNOFF || + notify == SILC_NOTIFY_TYPE_KILLED) { + client_entry->mode = mode; + gaim_prpl_got_user_status(gaim_buddy_get_account(b), gaim_buddy_get_name(b), SILCGAIM_STATUS_ID_OFFLINE, NULL); + } else if (notify == SILC_NOTIFY_TYPE_NONE) { + client_entry->mode = mode; + gaim_prpl_got_user_status(gaim_buddy_get_account(b), gaim_buddy_get_name(b), SILCGAIM_STATUS_ID_AVAILABLE, NULL); + } + } + break; + + default: + gaim_debug_info("silc", "Unhandled notification: %d\n", type); + break; + } + + va_end(va); +} + + +/* Command handler. This function is called always in the command function. + If error occurs it will be called as well. `conn' is the associated + client connection. `cmd_context' is the command context that was + originally sent to the command. `success' is FALSE if error occurred + during command. `command' is the command being processed. It must be + noted that this is not reply from server. This is merely called just + after application has called the command. Just to tell application + that the command really was processed. */ + +static void +silc_command(SilcClient client, SilcClientConnection conn, + SilcClientCommandContext cmd_context, bool success, + SilcCommand command, SilcStatus status) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + + switch (command) { + + case SILC_COMMAND_CMODE: + if (cmd_context->argc == 3 && + !strcmp((char *)cmd_context->argv[2], "+C")) + sg->chpk = TRUE; + else + sg->chpk = FALSE; + break; + + default: + break; + } +} + +#if 0 +static void +silcgaim_whois_more(SilcClientEntry client_entry, gint id) +{ + SilcAttributePayload attr; + SilcAttribute attribute; + char *buf; + GString *s; + SilcVCardStruct vcard; + int i; + + if (id != 0) + return; + + memset(&vcard, 0, sizeof(vcard)); + + s = g_string_new(""); + + silc_dlist_start(client_entry->attrs); + while ((attr = silc_dlist_get(client_entry->attrs)) != SILC_LIST_END) { + attribute = silc_attribute_get_attribute(attr); + switch (attribute) { + + case SILC_ATTRIBUTE_USER_INFO: + if (!silc_attribute_get_object(attr, (void *)&vcard, + sizeof(vcard))) + continue; + g_string_append_printf(s, "%s:\n\n", _("Personal Information")); + if (vcard.full_name) + g_string_append_printf(s, "%s:\t\t%s\n", + _("Full Name"), + vcard.full_name); + if (vcard.first_name) + g_string_append_printf(s, "%s:\t%s\n", + _("First Name"), + vcard.first_name); + if (vcard.middle_names) + g_string_append_printf(s, "%s:\t%s\n", + _("Middle Name"), + vcard.middle_names); + if (vcard.family_name) + g_string_append_printf(s, "%s:\t%s\n", + _("Family Name"), + vcard.family_name); + if (vcard.nickname) + g_string_append_printf(s, "%s:\t\t%s\n", + _("Nickname"), + vcard.nickname); + if (vcard.bday) + g_string_append_printf(s, "%s:\t\t%s\n", + _("Birth Day"), + vcard.bday); + if (vcard.title) + g_string_append_printf(s, "%s:\t\t%s\n", + _("Job Title"), + vcard.title); + if (vcard.role) + g_string_append_printf(s, "%s:\t\t%s\n", + _("Job Role"), + vcard.role); + if (vcard.org_name) + g_string_append_printf(s, "%s:\t%s\n", + _("Organization"), + vcard.org_name); + if (vcard.org_unit) + g_string_append_printf(s, "%s:\t\t%s\n", + _("Unit"), + vcard.org_unit); + if (vcard.url) + g_string_append_printf(s, "%s:\t%s\n", + _("Homepage"), + vcard.url); + if (vcard.label) + g_string_append_printf(s, "%s:\t%s\n", + _("Address"), + vcard.label); + for (i = 0; i < vcard.num_tels; i++) { + if (vcard.tels[i].telnum) + g_string_append_printf(s, "%s:\t\t\t%s\n", + _("Phone"), + vcard.tels[i].telnum); + } + for (i = 0; i < vcard.num_emails; i++) { + if (vcard.emails[i].address) + g_string_append_printf(s, "%s:\t\t%s\n", + _("E-Mail"), + vcard.emails[i].address); + } + if (vcard.note) + g_string_append_printf(s, "\n%s:\t\t%s\n", + _("Note"), + vcard.note); + break; + } + } + + buf = g_string_free(s, FALSE); + gaim_notify_info(NULL, _("User Information"), _("User Information"), + buf); + g_free(buf); +} +#endif + +/* Command reply handler. This function is called always in the command reply + function. If error occurs it will be called as well. Normal scenario + is that it will be called after the received command data has been parsed + and processed. The function is used to pass the received command data to + the application. + + `conn' is the associated client connection. `cmd_payload' is the command + payload data received from server and it can be ignored. It is provided + if the application would like to re-parse the received command data, + however, it must be noted that the data is parsed already by the library + thus the payload can be ignored. `success' is FALSE if error occurred. + In this case arguments are not sent to the application. The `status' is + the command reply status server returned. The `command' is the command + reply being processed. The function has variable argument list and each + command defines the number and type of arguments it passes to the + application (on error they are not sent). */ + +static void +silc_command_reply(SilcClient client, SilcClientConnection conn, + SilcCommandPayload cmd_payload, bool success, + SilcCommand command, SilcStatus status, ...) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + GaimConversation *convo; + va_list vp; + + va_start(vp, status); + + switch (command) { + case SILC_COMMAND_JOIN: + { + SilcChannelEntry channel_entry; + + if (!success) { + gaim_notify_error(gc, _("Join Chat"), _("Cannot join channel"), + silc_get_status_message(status)); + return; + } + + (void)va_arg(vp, char *); + channel_entry = va_arg(vp, SilcChannelEntry); + + /* Resolve users on channel */ + silc_client_get_clients_by_channel(client, conn, channel_entry, + silcgaim_chat_join_done, + channel_entry); + } + break; + + case SILC_COMMAND_LEAVE: + break; + + case SILC_COMMAND_USERS: + break; + + case SILC_COMMAND_WHOIS: + { + SilcUInt32 idle, mode; + SilcBuffer channels, user_modes; + SilcClientEntry client_entry; + char *buf, tmp[1024], *tmp2; + char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr; + 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(""); + tmp2 = g_markup_escape_text(client_entry->nickname, -1); + g_string_append_printf(s, "<b>%s:</b> %s", _("Nickname"), tmp2); + g_free(tmp2); + if (client_entry->realname) { + tmp2 = g_markup_escape_text(client_entry->realname, -1); + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Real Name"), tmp2); + g_free(tmp2); + } + if (client_entry->username) { + tmp2 = g_markup_escape_text(client_entry->username, -1); + if (client_entry->hostname) + g_string_append_printf(s, "<br><b>%s:</b> %s@%s", _("Username"), tmp2, client_entry->hostname); + else + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Username"), tmp2); + g_free(tmp2); + } + + if (client_entry->mode) { + g_string_append_printf(s, "<br><b>%s:</b> ", _("User Modes")); + memset(tmp, 0, sizeof(tmp)); + silcgaim_get_umode_string(client_entry->mode, + tmp, sizeof(tmp) - strlen(tmp)); + g_string_append_printf(s, "%s", tmp); + } + + silcgaim_parse_attrs(client_entry->attrs, &moodstr, &statusstr, &contactstr, &langstr, &devicestr, &tzstr, &geostr); + if (moodstr) { + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Mood"), moodstr); + g_free(moodstr); + } + + if (statusstr) { + tmp2 = g_markup_escape_text(statusstr, -1); + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Status Text"), tmp2); + g_free(statusstr); + g_free(tmp2); + } + + if (contactstr) { + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Preferred Contact"), contactstr); + g_free(contactstr); + } + + if (langstr) { + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Preferred Language"), langstr); + g_free(langstr); + } + + if (devicestr) { + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Device"), devicestr); + g_free(devicestr); + } + + if (tzstr) { + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Timezone"), tzstr); + g_free(tzstr); + } + + if (geostr) { + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Geolocation"), geostr); + g_free(geostr); + } + + if (client_entry->server) + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Server"), client_entry->server); + + if (channels && user_modes) { + SilcUInt32 *umodes; + SilcDList list = + silc_channel_payload_parse_list(channels->data, + channels->len); + if (list && silc_get_mode_list(user_modes, + silc_dlist_count(list), + &umodes)) { + SilcChannelPayload entry; + int i = 0; + + g_string_append_printf(s, "<br><b>%s:</b> ", _("Currently on")); + memset(tmp, 0, sizeof(tmp)); + silc_dlist_start(list); + while ((entry = silc_dlist_get(list)) + != SILC_LIST_END) { + SilcUInt32 name_len; + char *m = silc_client_chumode_char(umodes[i++]); + char *name = (char *)silc_channel_get_name(entry, &name_len); + if (m) + silc_strncat(tmp, sizeof(tmp) - 1, m, strlen(m)); + silc_strncat(tmp, sizeof(tmp) - 1, name, name_len); + silc_strncat(tmp, sizeof(tmp) - 1, " ", 1); + silc_free(m); + + } + tmp2 = g_markup_escape_text(tmp, -1); + g_string_append_printf(s, "%s", tmp2); + g_free(tmp2); + silc_free(umodes); + } + } + + if (client_entry->public_key) { + char *fingerprint, *babbleprint; + unsigned char *pk; + SilcUInt32 pk_len; + pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len); + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + babbleprint = silc_hash_babbleprint(NULL, pk, pk_len); + g_string_append_printf(s, "<br><b>%s:</b><br>%s", _("Public Key Fingerprint"), fingerprint); + g_string_append_printf(s, "<br><b>%s:</b><br>%s", _("Public Key Babbleprint"), 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(gc, _("User Information"), + _("User Information"), + buf, 1, client_entry, 2, + _("OK"), G_CALLBACK(silcgaim_whois_more), + _("_More..."), G_CALLBACK(silcgaim_whois_more)); + else +#endif + gaim_notify_userinfo(gc, client_entry->nickname, buf, NULL, NULL); + g_free(buf); + } + break; + + case SILC_COMMAND_WHOWAS: + { + SilcClientEntry client_entry; + char *buf, *nickname, *realname, *username, *tmp; + 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); + nickname = va_arg(vp, char *); + username = va_arg(vp, char *); + realname = va_arg(vp, char *); + if (!nickname) + break; + + s = g_string_new(""); + tmp = g_markup_escape_text(nickname, -1); + g_string_append_printf(s, "<b>%s:</b> %s", _("Nickname"), tmp); + g_free(tmp); + if (realname) { + tmp = g_markup_escape_text(realname, -1); + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Real Name"), tmp); + g_free(tmp); + } + if (username) { + tmp = g_markup_escape_text(username, -1); + if (client_entry && client_entry->hostname) + g_string_append_printf(s, "<br><b>%s:</b> %s@%s", _("Username"), tmp, client_entry->hostname); + else + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Username"), tmp); + g_free(tmp); + } + if (client_entry && client_entry->server) + g_string_append_printf(s, "<br><b>%s:</b> %s", _("Server"), client_entry->server); + + + if (client_entry && client_entry->public_key) { + char *fingerprint, *babbleprint; + unsigned char *pk; + SilcUInt32 pk_len; + pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len); + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + babbleprint = silc_hash_babbleprint(NULL, pk, pk_len); + g_string_append_printf(s, "<br><b>%s:</b><br>%s", _("Public Key Fingerprint"), fingerprint); + g_string_append_printf(s, "<br><b>%s:</b><br>%s", _("Public Key Babbleprint"), babbleprint); + silc_free(fingerprint); + silc_free(babbleprint); + silc_free(pk); + } + + buf = g_string_free(s, FALSE); + gaim_notify_userinfo(gc, nickname, buf, NULL, NULL); + 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(GAIM_CONV_TYPE_CHAT, + channel->channel_name, sg->account); + if (!convo) { + gaim_debug_error("silc", "Got a topic for %s, which doesn't exist\n", + channel->channel_name); + break; + } + + /* Set topic */ + if (channel->topic) + gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, channel->topic); + } + break; + + case SILC_COMMAND_NICK: + { + /* I don't think we should need to do this because the server should + * be sending a SILC_NOTIFY_TYPE_NICK_CHANGE when we change our own + * nick, but it isn't, so we deal with it here instead. Stu. */ + SilcClientEntry local_entry; + SilcHashTableList htl; + SilcChannelUser chu; + const char *oldnick; + + if (!success) { + gaim_notify_error(gc, _("Nick"), _("Failed to change nickname"), + silc_get_status_message(status)); + return; + } + + local_entry = va_arg(vp, SilcClientEntry); + + /* Change nick on all channels */ + silc_hash_table_list(local_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, + chu->channel->channel_name, sg->account); + if (!convo) + continue; + oldnick = gaim_conv_chat_get_nick(GAIM_CONV_CHAT(convo)); + if (strcmp(oldnick, gaim_normalize(gaim_conversation_get_account(convo), local_entry->nickname))) { + gaim_conv_chat_rename_user(GAIM_CONV_CHAT(convo), + oldnick, local_entry->nickname); + gaim_conv_chat_set_nick(GAIM_CONV_CHAT(convo), local_entry->nickname); + } + } + silc_hash_table_list_reset(&htl); + + gaim_connection_set_display_name(gc, local_entry->nickname); + } + break; + + case SILC_COMMAND_LIST: + { + char *topic, *name; + int usercount; + GaimRoomlistRoom *room; + + if (sg->roomlist_canceled) + break; + + if (!success) { + gaim_notify_error(gc, _("Error"), _("Error retrieving room list"), + silc_get_status_message(status)); + gaim_roomlist_set_in_progress(sg->roomlist, FALSE); + gaim_roomlist_unref(sg->roomlist); + sg->roomlist = NULL; + return; + } + + (void)va_arg(vp, SilcChannelEntry); + name = va_arg(vp, char *); + if (!name) { + gaim_notify_error(gc, _("Roomlist"), _("Cannot get room list"), + silc_get_status_message(status)); + gaim_roomlist_set_in_progress(sg->roomlist, FALSE); + gaim_roomlist_unref(sg->roomlist); + sg->roomlist = NULL; + return; + } + topic = va_arg(vp, char *); + usercount = va_arg(vp, int); + + room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, name, NULL); + gaim_roomlist_room_add_field(sg->roomlist, room, name); + gaim_roomlist_room_add_field(sg->roomlist, room, + SILC_32_TO_PTR(usercount)); + gaim_roomlist_room_add_field(sg->roomlist, room, + topic ? topic : ""); + gaim_roomlist_room_add(sg->roomlist, room); + + if (status == SILC_STATUS_LIST_END || + status == SILC_STATUS_OK) { + gaim_roomlist_set_in_progress(sg->roomlist, FALSE); + gaim_roomlist_unref(sg->roomlist); + sg->roomlist = NULL; + } + } + break; + + case SILC_COMMAND_GETKEY: + { + SilcPublicKey public_key; + + if (!success) { + gaim_notify_error(gc, _("Get Public Key"), + _("Cannot fetch the public key"), + silc_get_status_message(status)); + return; + } + + (void)va_arg(vp, SilcUInt32); + (void)va_arg(vp, void *); + public_key = va_arg(vp, SilcPublicKey); + + if (!public_key) + gaim_notify_error(gc, _("Get Public Key"), + _("Cannot fetch the public key"), + _("No public key was received")); + } + break; + + case SILC_COMMAND_INFO: + { + + 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(gc, NULL, _("Server Information"), tmp); + } + } + break; + + case SILC_COMMAND_STATS: + { + SilcUInt32 starttime, uptime, my_clients, my_channels, my_server_ops, + my_router_ops, cell_clients, cell_channels, cell_servers, + clients, channels, servers, routers, server_ops, router_ops; + SilcUInt32 buffer_length; + SilcBufferStruct buf; + + unsigned char *server_stats; + char *msg; + + if (!success) { + gaim_notify_error(gc, _("Server Statistics"), + _("Cannot get server statistics"), + silc_get_status_message(status)); + return; + } + + server_stats = va_arg(vp, unsigned char *); + buffer_length = va_arg(vp, SilcUInt32); + if (!server_stats || !buffer_length) { + gaim_notify_error(gc, _("Server Statistics"), + _("No server statistics available"), NULL); + break; + } + silc_buffer_set(&buf, server_stats, buffer_length); + silc_buffer_unformat(&buf, + SILC_STR_UI_INT(&starttime), + SILC_STR_UI_INT(&uptime), + SILC_STR_UI_INT(&my_clients), + SILC_STR_UI_INT(&my_channels), + SILC_STR_UI_INT(&my_server_ops), + SILC_STR_UI_INT(&my_router_ops), + SILC_STR_UI_INT(&cell_clients), + SILC_STR_UI_INT(&cell_channels), + SILC_STR_UI_INT(&cell_servers), + SILC_STR_UI_INT(&clients), + SILC_STR_UI_INT(&channels), + SILC_STR_UI_INT(&servers), + SILC_STR_UI_INT(&routers), + SILC_STR_UI_INT(&server_ops), + SILC_STR_UI_INT(&router_ops), + SILC_STR_END); + + msg = g_strdup_printf(_("Local server start time: %s\n" + "Local server uptime: %s\n" + "Local server clients: %d\n" + "Local server channels: %d\n" + "Local server operators: %d\n" + "Local router operators: %d\n" + "Local cell clients: %d\n" + "Local cell channels: %d\n" + "Local cell servers: %d\n" + "Total clients: %d\n" + "Total channels: %d\n" + "Total servers: %d\n" + "Total routers: %d\n" + "Total server operators: %d\n" + "Total router operators: %d\n"), + silc_get_time(starttime), + gaim_str_seconds_to_string((int)uptime), + (int)my_clients, (int)my_channels, (int)my_server_ops, (int)my_router_ops, + (int)cell_clients, (int)cell_channels, (int)cell_servers, + (int)clients, (int)channels, (int)servers, (int)routers, + (int)server_ops, (int)router_ops); + + gaim_notify_info(gc, NULL, + _("Network Statistics"), msg); + g_free(msg); + } + break; + + case SILC_COMMAND_PING: + { + if (!success) { + gaim_notify_error(gc, _("Ping"), _("Ping failed"), + silc_get_status_message(status)); + return; + } + + gaim_notify_info(gc, _("Ping"), _("Ping reply received from server"), + NULL); + } + break; + + case SILC_COMMAND_KILL: + if (!success) { + gaim_notify_error(gc, _("Kill User"), + _("Could not kill user"), + silc_get_status_message(status)); + return; + } + break; + + case SILC_COMMAND_CMODE: + { + SilcChannelEntry channel_entry; + SilcBuffer channel_pubkeys; + + if (!success) + return; + + channel_entry = va_arg(vp, SilcChannelEntry); + (void)va_arg(vp, SilcUInt32); + (void)va_arg(vp, SilcPublicKey); + channel_pubkeys = va_arg(vp, SilcBuffer); + + if (sg->chpk) + silcgaim_chat_chauth_show(sg, channel_entry, channel_pubkeys); + } + break; + + default: + if (success) + gaim_debug_info("silc", "Unhandled command: %d (succeeded)\n", command); + else + gaim_debug_info("silc", "Unhandled command: %d (failed: %s)\n", command, + silc_get_status_message(status)); + break; + } + + va_end(vp); +} + + +/* Called to indicate that connection was either successfully established + or connecting failed. This is also the first time application receives + the SilcClientConnection object which it should save somewhere. + If the `success' is FALSE the application must always call the function + silc_client_close_connection. */ + +static void +silc_connected(SilcClient client, SilcClientConnection conn, + SilcClientConnectionStatus status) +{ + GaimConnection *gc = client->application; + SilcGaim sg; + gboolean reject_watch, block_invites, block_ims; + + if (gc == NULL) { + silc_client_close_connection(client, conn); + return; + } + sg = gc->proto_data; + + switch (status) { + case SILC_CLIENT_CONN_SUCCESS: + case SILC_CLIENT_CONN_SUCCESS_RESUME: + gaim_connection_set_state(gc, GAIM_CONNECTED); + + /* Send the server our buddy list */ + silcgaim_send_buddylist(gc); + + g_unlink(silcgaim_session_file(gaim_account_get_username(sg->account))); + + /* Send any UMODEs configured for account */ + reject_watch = gaim_account_get_bool(sg->account, "reject-watch", FALSE); + block_invites = gaim_account_get_bool(sg->account, "block-invites", FALSE); + block_ims = gaim_account_get_bool(sg->account, "block-ims", FALSE); + if (reject_watch || block_invites || block_ims) { + char m[5]; + g_snprintf(m, sizeof(m), "+%s%s%s", + reject_watch ? "w" : "", + block_invites ? "I" : "", + block_ims ? "P" : ""); + silc_client_command_call(sg->client, sg->conn, NULL, + "UMODE", m, NULL); + } + + return; + break; + case SILC_CLIENT_CONN_ERROR: + gaim_connection_error(gc, _("Error during connecting to SILC Server")); + g_unlink(silcgaim_session_file(gaim_account_get_username(sg->account))); + break; + + case SILC_CLIENT_CONN_ERROR_KE: + gaim_connection_error(gc, _("Key Exchange failed")); + break; + + case SILC_CLIENT_CONN_ERROR_AUTH: + gaim_connection_error(gc, _("Authentication failed")); + break; + + case SILC_CLIENT_CONN_ERROR_RESUME: + gaim_connection_error(gc, + _("Resuming detached session failed. " + "Press Reconnect to create new connection.")); + g_unlink(silcgaim_session_file(gaim_account_get_username(sg->account))); + break; + + case SILC_CLIENT_CONN_ERROR_TIMEOUT: + gaim_connection_error(gc, _("Connection Timeout")); + break; + } + + /* Error */ + sg->conn = NULL; + silc_client_close_connection(client, conn); +} + + +/* Called to indicate that connection was disconnected to the server. + The `status' may tell the reason of the disconnection, and if the + `message' is non-NULL it may include the disconnection message + received from server. */ + +static void +silc_disconnected(SilcClient client, SilcClientConnection conn, + SilcStatus status, const char *message) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + + if (sg->resuming && !sg->detaching) + g_unlink(silcgaim_session_file(gaim_account_get_username(sg->account))); + + sg->conn = NULL; + + /* Close the connection */ + if (!sg->detaching) + gaim_connection_error(gc, _("Disconnected by server")); + else + /* TODO: Does this work correctly? Maybe we need to set wants_to_die? */ + gaim_account_disconnect(gaim_connection_get_account(gc)); +} + + +typedef struct { + SilcGetAuthMeth completion; + void *context; +} *SilcGaimGetAuthMethod; + +/* Callback called when we've received the authentication method information + from the server after we've requested it. */ + +static void silc_get_auth_method_callback(SilcClient client, + SilcClientConnection conn, + SilcAuthMethod auth_meth, + void *context) +{ + SilcGaimGetAuthMethod internal = context; + + switch (auth_meth) { + case SILC_AUTH_NONE: + /* No authentication required. */ + (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context); + break; + + case SILC_AUTH_PASSWORD: + /* By returning NULL here the library will ask the passphrase from us + by calling the silc_ask_passphrase. */ + (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context); + break; + + case SILC_AUTH_PUBLIC_KEY: + /* Do not get the authentication data now, the library will generate + it using our default key, if we do not provide it here. */ + (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context); + break; + } + + silc_free(internal); +} + +/* Find authentication method and authentication data by hostname and + port. The hostname may be IP address as well. When the authentication + method has been resolved the `completion' callback with the found + authentication method and authentication data is called. The `conn' + may be NULL. */ + +static void +silc_get_auth_method(SilcClient client, SilcClientConnection conn, + char *hostname, SilcUInt16 port, + SilcGetAuthMeth completion, void *context) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + SilcGaimGetAuthMethod internal; + const char *password; + + /* Progress */ + if (sg->resuming) + gaim_connection_update_progress(gc, _("Resuming session"), 4, 5); + else + gaim_connection_update_progress(gc, _("Authenticating connection"), 4, 5); + + /* Check configuration if we have this connection configured. If we + have then return that data immediately, as it's faster way. */ + if (gaim_account_get_bool(sg->account, "pubkey-auth", FALSE)) { + completion(TRUE, SILC_AUTH_PUBLIC_KEY, NULL, 0, context); + return; + } + password = gaim_connection_get_password(gc); + if (password && *password) { + completion(TRUE, SILC_AUTH_PASSWORD, (unsigned char *)password, strlen(password), context); + return; + } + + /* Resolve the authentication method from server, as we may not know it. */ + internal = silc_calloc(1, sizeof(*internal)); + if (!internal) + return; + internal->completion = completion; + internal->context = context; + silc_client_request_authentication_method(client, conn, + silc_get_auth_method_callback, + internal); +} + + +/* Verifies received public key. The `conn_type' indicates which entity + (server, client etc.) has sent the public key. If user decides to trust + the application may save the key as trusted public key for later + use. The `completion' must be called after the public key has been + verified. */ + +static void +silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + SilcUInt32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + + if (!sg->conn && (conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER)) { + /* Progress */ + if (sg->resuming) + gaim_connection_update_progress(gc, _("Resuming session"), 3, 5); + else + gaim_connection_update_progress(gc, _("Verifying server public key"), + 3, 5); + } + + /* Verify public key */ + silcgaim_verify_public_key(client, conn, NULL, conn_type, pk, + pk_len, pk_type, completion, context); +} + +typedef struct { + SilcAskPassphrase completion; + void *context; +} *SilcGaimAskPassphrase; + +static void +silc_ask_passphrase_cb(SilcGaimAskPassphrase internal, const char *passphrase) +{ + if (!passphrase || !(*passphrase)) + internal->completion(NULL, 0, internal->context); + else + internal->completion((unsigned char *)passphrase, + strlen(passphrase), internal->context); + silc_free(internal); +} + +/* Ask (interact, that is) a passphrase from user. The passphrase is + returned to the library by calling the `completion' callback with + the `context'. The returned passphrase SHOULD be in UTF-8 encoded, + if not then the library will attempt to encode. */ + +static void +silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context) +{ + SilcGaimAskPassphrase internal = silc_calloc(1, sizeof(*internal)); + + if (!internal) + return; + internal->completion = completion; + internal->context = context; + gaim_request_input(client->application, _("Passphrase"), NULL, + _("Passphrase required"), NULL, FALSE, TRUE, NULL, + _("OK"), G_CALLBACK(silc_ask_passphrase_cb), + _("Cancel"), G_CALLBACK(silc_ask_passphrase_cb), + internal); +} + + +/* Notifies application that failure packet was received. This is called + if there is some protocol active in the client. The `protocol' is the + protocol context. The `failure' is opaque pointer to the failure + indication. Note, that the `failure' is protocol dependant and + application must explicitly cast it to correct type. Usually `failure' + is 32 bit failure type (see protocol specs for all protocol failure + types). */ + +static void +silc_failure(SilcClient client, SilcClientConnection conn, + SilcProtocol protocol, void *failure) +{ + GaimConnection *gc = client->application; + char buf[128]; + + memset(buf, 0, sizeof(buf)); + + if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) { + SilcSKEStatus status = (SilcSKEStatus)SILC_PTR_TO_32(failure); + + if (status == SILC_SKE_STATUS_BAD_VERSION) + g_snprintf(buf, sizeof(buf), + _("Failure: Version mismatch, upgrade your client")); + if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY) + g_snprintf(buf, sizeof(buf), + _("Failure: Remote does not trust/support your public key")); + if (status == SILC_SKE_STATUS_UNKNOWN_GROUP) + g_snprintf(buf, sizeof(buf), + _("Failure: Remote does not support proposed KE group")); + if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER) + g_snprintf(buf, sizeof(buf), + _("Failure: Remote does not support proposed cipher")); + if (status == SILC_SKE_STATUS_UNKNOWN_PKCS) + g_snprintf(buf, sizeof(buf), + _("Failure: Remote does not support proposed PKCS")); + if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION) + g_snprintf(buf, sizeof(buf), + _("Failure: Remote does not support proposed hash function")); + if (status == SILC_SKE_STATUS_UNKNOWN_HMAC) + g_snprintf(buf, sizeof(buf), + _("Failure: Remote does not support proposed HMAC")); + if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE) + g_snprintf(buf, sizeof(buf), _("Failure: Incorrect signature")); + if (status == SILC_SKE_STATUS_INVALID_COOKIE) + g_snprintf(buf, sizeof(buf), _("Failure: Invalid cookie")); + + /* Show the error on the progress bar. A more generic error message + is going to be showed to user after this in the silc_connected. */ + gaim_connection_update_progress(gc, buf, 2, 5); + } + + if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) { + SilcUInt32 err = SILC_PTR_TO_32(failure); + + if (err == SILC_AUTH_FAILED) + g_snprintf(buf, sizeof(buf), _("Failure: Authentication failed")); + + /* Show the error on the progress bar. A more generic error message + is going to be showed to user after this in the silc_connected. */ + gaim_connection_update_progress(gc, buf, 4, 5); + } +} + +/* Asks whether the user would like to perform the key agreement protocol. + This is called after we have received an key agreement packet or an + reply to our key agreement packet. This returns TRUE if the user wants + the library to perform the key agreement protocol and FALSE if it is not + desired (application may start it later by calling the function + silc_client_perform_key_agreement). If TRUE is returned also the + `completion' and `context' arguments must be set by the application. */ + +static bool +silc_key_agreement(SilcClient client, SilcClientConnection conn, + SilcClientEntry client_entry, const char *hostname, + SilcUInt16 port, SilcKeyAgreementCallback *completion, + void **context) +{ + silcgaim_buddy_keyagr_request(client, conn, client_entry, hostname, port); + *completion = NULL; + *context = NULL; + return FALSE; +} + + +/* Notifies application that file transfer protocol session is being + requested by the remote client indicated by the `client_entry' from + the `hostname' and `port'. The `session_id' is the file transfer + session and it can be used to either accept or reject the file + transfer request, by calling the silc_client_file_receive or + silc_client_file_close, respectively. */ + +static void +silc_ftp(SilcClient client, SilcClientConnection conn, + SilcClientEntry client_entry, SilcUInt32 session_id, + const char *hostname, SilcUInt16 port) +{ + silcgaim_ftp_request(client, conn, client_entry, session_id, + hostname, port); +} + + +/* Delivers SILC session detachment data indicated by `detach_data' to the + application. If application has issued SILC_COMMAND_DETACH command + the client session in the SILC network is not quit. The client remains + in the network but is detached. The detachment data may be used later + to resume the session in the SILC Network. The appliation is + responsible of saving the `detach_data', to for example in a file. + + The detachment data can be given as argument to the functions + silc_client_connect_to_server, or silc_client_add_connection when + creating connection to remote server, inside SilcClientConnectionParams + structure. If it is provided the client library will attempt to resume + the session in the network. After the connection is created + successfully, the application is responsible of setting the user + interface for user into the same state it was before detaching (showing + same channels, channel modes, etc). It can do this by fetching the + information (like joined channels) from the client library. */ + +static void +silc_detach(SilcClient client, SilcClientConnection conn, + const unsigned char *detach_data, SilcUInt32 detach_data_len) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + const char *file; + + /* Save the detachment data to file. */ + file = silcgaim_session_file(gaim_account_get_username(sg->account)); + g_unlink(file); + silc_file_writefile(file, (char *)detach_data, detach_data_len); +} + +SilcClientOperations ops = { + silc_say, + silc_channel_message, + silc_private_message, + silc_notify, + silc_command, + silc_command_reply, + silc_connected, + silc_disconnected, + silc_get_auth_method, + silc_verify_public_key, + silc_ask_passphrase, + silc_failure, + silc_key_agreement, + silc_ftp, + silc_detach +};