Mercurial > pidgin
diff src/protocols/silc/chat.c @ 8849:50d0f76639e7
[gaim-migrate @ 9616]
Let there be SILC.
committer: Tailor Script <tailor@pidgin.im>
author | Ethan Blanton <elb@pidgin.im> |
---|---|
date | Sat, 01 May 2004 19:34:44 +0000 |
parents | |
children | c8cd436b5840 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/silc/chat.c Sat May 01 19:34:44 2004 +0000 @@ -0,0 +1,1294 @@ +/* + + silcgaim_chat.c + + Author: Pekka Riikonen <priikone@silcnet.org> + + Copyright (C) 2004 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + +*/ + +#include "silcincludes.h" +#include "silcclient.h" +#include "silcgaim.h" + +/***************************** Channel Routines ******************************/ + +GList *silcgaim_chat_info(GaimConnection *gc) +{ + GList *ci = NULL; + struct proto_chat_entry *pce; + + pce = g_new0(struct proto_chat_entry, 1); + pce->label = _("_Channel:"); + pce->identifier = "channel"; + ci = g_list_append(ci, pce); + + pce = g_new0(struct proto_chat_entry, 1); + pce->label = _("_Passphrase:"); + pce->identifier = "passphrase"; + pce->secret = TRUE; + ci = g_list_append(ci, pce); + + return ci; +} + +static void +silcgaim_chat_getinfo(GaimConnection *gc, GHashTable *components); + +static void +silcgaim_chat_getinfo_res(SilcClient client, + SilcClientConnection conn, + SilcChannelEntry *channels, + SilcUInt32 channels_count, + void *context) +{ + GHashTable *components = context; + GaimConnection *gc = client->application; + const char *chname; + char tmp[256]; + + chname = g_hash_table_lookup(components, "channel"); + if (!chname) + return; + + if (!channels) { + g_snprintf(tmp, sizeof(tmp), + _("Channel %s does not exist in the network"), chname); + gaim_notify_error(gc, _("Channel Information"), + _("Cannot get channel information"), tmp); + return; + } + + silcgaim_chat_getinfo(gc, components); +} + +static void +silcgaim_chat_getinfo(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + const char *chname; + char *buf, tmp[256]; + GString *s; + SilcChannelEntry channel; + SilcHashTableList htl; + SilcChannelUser chu; + + if (!components) + return; + + chname = g_hash_table_lookup(components, "channel"); + if (!chname) + return; + channel = silc_client_get_channel(sg->client, sg->conn, + (char *)chname); + if (!channel) { + silc_client_get_channel_resolve(sg->client, sg->conn, + (char *)chname, + silcgaim_chat_getinfo_res, + components); + return; + } + + s = g_string_new(""); + g_string_append_printf(s, "Channel Name:\t\t%s\n", channel->channel_name); + if (channel->user_list && silc_hash_table_count(channel->user_list)) + g_string_append_printf(s, "User Count:\t\t%d\n", + (int)silc_hash_table_count(channel->user_list)); + + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) { + g_string_append_printf(s, "Channel Founder:\t%s\n", + chu->client->nickname); + break; + } + } + silc_hash_table_list_reset(&htl); + + if (channel->channel_key) + g_string_append_printf(s, "Channel Cipher:\t\t%s\n", + silc_cipher_get_name(channel->channel_key)); + if (channel->hmac) + g_string_append_printf(s, "Channel HMAC:\t\t%s\n", + silc_hmac_get_name(channel->hmac)); + + if (channel->topic) + g_string_append_printf(s, "\nChannel Topic:\n\%s\n", channel->topic); + + if (channel->mode) { + g_string_append_printf(s, "\nChannel Modes:\n"); + silcgaim_get_chmode_string(channel->mode, tmp, sizeof(tmp)); + g_string_append_printf(s, tmp); + g_string_append_printf(s, "\n"); + } + + if (channel->founder_key) { + char *fingerprint, *babbleprint; + unsigned char *pk; + SilcUInt32 pk_len; + pk = silc_pkcs_public_key_encode(channel->founder_key, &pk_len); + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + babbleprint = silc_hash_babbleprint(NULL, pk, pk_len); + + g_string_append_printf(s, "\nFounder Key Fingerprint:\n%s\n\n", fingerprint); + g_string_append_printf(s, "Founder Key Babbleprint:\n%s", babbleprint); + + silc_free(fingerprint); + silc_free(babbleprint); + silc_free(pk); + } + + buf = g_string_free(s, FALSE); + gaim_notify_message(NULL, GAIM_NOTIFY_MSG_INFO, + _("Channel Information"), + _("Channel Information"), + buf, NULL, NULL); + g_free(buf); +} + + +#if 0 /* XXX For now these are not implemented. We need better + listview dialog from Gaim for these. */ +/************************** Channel Invite List ******************************/ + +static void +silcgaim_chat_invitelist(GaimConnection *gc, GHashTable *components) +{ + +} + + +/**************************** Channel Ban List *******************************/ + +static void +silcgaim_chat_banlist(GaimConnection *gc, GHashTable *components) +{ + +} +#endif + + +/************************* Channel Authentication ****************************/ + +typedef struct { + SilcGaim sg; + SilcChannelEntry channel; + GaimChat *c; + SilcBuffer pubkeys; +} *SilcGaimChauth; + +static void +silcgaim_chat_chpk_add(void *user_data, const char *name) +{ + SilcGaimChauth sgc = (SilcGaimChauth)user_data; + SilcGaim sg = sgc->sg; + SilcClient client = sg->client; + SilcClientConnection conn = sg->conn; + SilcPublicKey public_key; + SilcBuffer chpks, pk, chidp; + unsigned char mode[4]; + SilcUInt32 m; + + /* Load the public key */ + if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) && + !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) { + silcgaim_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys); + silc_buffer_free(sgc->pubkeys); + silc_free(sgc); + gaim_notify_error(client->application, + _("Add Channel Public Key"), + _("Could not load public key"), NULL); + return; + } + + pk = silc_pkcs_public_key_payload_encode(public_key); + chpks = silc_buffer_alloc_size(2); + SILC_PUT16_MSB(1, chpks->head); + chpks = silc_argument_payload_encode_one(chpks, pk->data, + pk->len, 0x00); + silc_buffer_free(pk); + + m = sgc->channel->mode; + m |= SILC_CHANNEL_MODE_CHANNEL_AUTH; + + /* Send CMODE */ + SILC_PUT32_MSB(m, mode); + chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL); + silc_client_command_send(client, conn, SILC_COMMAND_CMODE, + ++conn->cmd_ident, 3, + 1, chidp->data, chidp->len, + 2, mode, sizeof(mode), + 9, chpks->data, chpks->len); + silc_buffer_free(chpks); + silc_buffer_free(chidp); + silc_buffer_free(sgc->pubkeys); + silc_free(sgc); +} + +static void +silcgaim_chat_chpk_cancel(void *user_data, const char *name) +{ + SilcGaimChauth sgc = (SilcGaimChauth)user_data; + silcgaim_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys); + silc_buffer_free(sgc->pubkeys); + silc_free(sgc); +} + +static void +silcgaim_chat_chpk_cb(SilcGaimChauth sgc, GaimRequestFields *fields) +{ + SilcGaim sg = sgc->sg; + SilcClient client = sg->client; + SilcClientConnection conn = sg->conn; + GaimRequestField *f; + const GList *list; + SilcPublicKey public_key; + SilcBuffer chpks, pk, chidp; + SilcUInt16 c = 0, ct; + unsigned char mode[4]; + SilcUInt32 m; + + f = gaim_request_fields_get_field(fields, "list"); + if (!gaim_request_field_list_get_selected(f)) { + /* Add new public key */ + gaim_request_file(NULL, _("Open Public Key..."), _(""), + G_CALLBACK(silcgaim_chat_chpk_add), + G_CALLBACK(silcgaim_chat_chpk_cancel), sgc); + return; + } + + list = gaim_request_field_list_get_items(f); + chpks = silc_buffer_alloc_size(2); + + for (ct = 0; list; list = list->next, ct++) { + public_key = gaim_request_field_list_get_data(f, list->data); + if (gaim_request_field_list_is_selected(f, list->data)) { + /* Delete this public key */ + pk = silc_pkcs_public_key_payload_encode(public_key); + chpks = silc_argument_payload_encode_one(chpks, pk->data, + pk->len, 0x01); + silc_buffer_free(pk); + c++; + } + silc_pkcs_public_key_free(public_key); + } + if (!c) { + silc_buffer_free(chpks); + return; + } + SILC_PUT16_MSB(c, chpks->head); + + m = sgc->channel->mode; + if (ct == c) + m &= ~SILC_CHANNEL_MODE_CHANNEL_AUTH; + + /* Send CMODE */ + SILC_PUT32_MSB(m, mode); + chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL); + silc_client_command_send(client, conn, SILC_COMMAND_CMODE, + ++conn->cmd_ident, 3, + 1, chidp->data, chidp->len, + 2, mode, sizeof(mode), + 9, chpks->data, chpks->len); + silc_buffer_free(chpks); + silc_buffer_free(chidp); + silc_buffer_free(sgc->pubkeys); + silc_free(sgc); +} + +static void +silcgaim_chat_chauth_ok(SilcGaimChauth sgc, GaimRequestFields *fields) +{ + SilcGaim sg = sgc->sg; + GaimRequestField *f; + const char *curpass, *val; + int set; + + f = gaim_request_fields_get_field(fields, "passphrase"); + val = gaim_request_field_string_get_value(f); + curpass = gaim_blist_node_get_string((GaimBlistNode *)sgc->c, "passphrase"); + + if (!val && curpass) + set = 0; + else if (val && !curpass) + set = 1; + else if (val && curpass && strcmp(val, curpass)) + set = 1; + else + set = -1; + + if (set == 1) { + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + sgc->channel->channel_name, "+a", val, NULL); + gaim_blist_node_set_string((GaimBlistNode *)sgc->c, "passphrase", val); + gaim_blist_save(); + } else if (set == 0) { + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + sgc->channel->channel_name, "-a", NULL); + gaim_blist_node_remove_setting((GaimBlistNode *)sgc->c, "passphrase"); + gaim_blist_save(); + } + + silc_buffer_free(sgc->pubkeys); + silc_free(sgc); +} + +void silcgaim_chat_chauth_show(SilcGaim sg, SilcChannelEntry channel, + SilcBuffer channel_pubkeys) +{ + SilcUInt16 argc; + SilcArgumentPayload chpks; + unsigned char *pk; + SilcUInt32 pk_len, type; + char *fingerprint, *babbleprint; + SilcPublicKey pubkey; + SilcPublicKeyIdentifier ident; + char tmp2[1024], t[512]; + GaimRequestFields *fields; + GaimRequestFieldGroup *g; + GaimRequestField *f; + SilcGaimChauth sgc; + const char *curpass = NULL; + + sgc = silc_calloc(1, sizeof(*sgc)); + if (!sgc) + return; + sgc->sg = sg; + sgc->channel = channel; + + fields = gaim_request_fields_new(); + + if (sgc->c) + curpass = gaim_blist_node_get_string((GaimBlistNode *)sgc->c, "passphrase"); + + g = gaim_request_field_group_new(NULL); + f = gaim_request_field_string_new("passphrase", _("Channel Passphrase"), + curpass, FALSE); + gaim_request_field_string_set_masked(f, TRUE); + gaim_request_field_group_add_field(g, f); + gaim_request_fields_add_group(fields, g); + + g = gaim_request_field_group_new(NULL); + f = gaim_request_field_label_new("l1", _("Channel Public Keys List")); + gaim_request_field_group_add_field(g, f); + gaim_request_fields_add_group(fields, g); + + g_snprintf(t, sizeof(t), + _("Channel authentication is used to secure the channel from " + "unauthorized access. The authentication may be based on " + "passphrase and digital signatures. If passphrase is set, it " + "is required to be able to join. If channel public keys are set " + "then only users whose public keys are listed are able to join.")); + + if (!channel_pubkeys) { + f = gaim_request_field_list_new("list", NULL); + gaim_request_field_group_add_field(g, f); + gaim_request_fields(NULL, _("Channel Authentication"), + _("Channel Authentication"), t, fields, + "Add / Remove", G_CALLBACK(silcgaim_chat_chpk_cb), + "OK", G_CALLBACK(silcgaim_chat_chauth_ok), sgc); + return; + } + sgc->pubkeys = silc_buffer_copy(channel_pubkeys); + + g = gaim_request_field_group_new(NULL); + f = gaim_request_field_list_new("list", NULL); + gaim_request_field_group_add_field(g, f); + gaim_request_fields_add_group(fields, g); + + SILC_GET16_MSB(argc, channel_pubkeys->data); + chpks = silc_argument_payload_parse(channel_pubkeys->data + 2, + channel_pubkeys->len - 2, argc); + if (!chpks) + return; + + pk = silc_argument_get_first_arg(chpks, &type, &pk_len); + while (pk) { + fingerprint = silc_hash_fingerprint(NULL, pk + 4, pk_len - 4); + babbleprint = silc_hash_babbleprint(NULL, pk + 4, pk_len - 4); + silc_pkcs_public_key_payload_decode(pk, pk_len, &pubkey); + ident = silc_pkcs_decode_identifier(pubkey->identifier); + + g_snprintf(tmp2, sizeof(tmp2), "%s\n %s\n %s", + ident->realname ? ident->realname : ident->username ? + ident->username : "", fingerprint, babbleprint); + gaim_request_field_list_add(f, tmp2, pubkey); + + silc_free(fingerprint); + silc_free(babbleprint); + silc_pkcs_free_identifier(ident); + pk = silc_argument_get_next_arg(chpks, &type, &pk_len); + } + + gaim_request_field_list_set_multi_select(f, FALSE); + gaim_request_fields(NULL, _("Channel Authentication"), + _("Channel Authentication"), t, fields, + "Add / Remove", G_CALLBACK(silcgaim_chat_chpk_cb), + "OK", G_CALLBACK(silcgaim_chat_chauth_ok), sgc); + + silc_argument_payload_free(chpks); +} + +static void +silcgaim_chat_chauth(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + g_hash_table_lookup(components, "channel"), + "+C", NULL); +} + + +/************************** Channel Private Groups **************************/ + +/* Private groups are "virtual" channels. They are groups inside a channel. + This is implemented by using channel private keys. By knowing a channel + private key user becomes part of that group and is able to talk on that + group. Other users, on the same channel, won't be able to see the + messages of that group. It is possible to have multiple groups inside + a channel - and thus having multiple private keys on the channel. */ + +typedef struct { + SilcGaim sg; + GaimChat *c; + const char *channel; +} *SilcGaimCharPrv; + +static void +silcgaim_chat_prv_add(SilcGaimCharPrv p, GaimRequestFields *fields) +{ + SilcGaim sg = p->sg; + char tmp[512]; + GaimRequestField *f; + const char *name, *passphrase, *alias; + GHashTable *comp; + GaimGroup *g; + GaimChat *cn; + + f = gaim_request_fields_get_field(fields, "name"); + name = gaim_request_field_string_get_value(f); + if (!name) { + silc_free(p); + return; + } + f = gaim_request_fields_get_field(fields, "passphrase"); + passphrase = gaim_request_field_string_get_value(f); + f = gaim_request_fields_get_field(fields, "alias"); + alias = gaim_request_field_string_get_value(f); + + /* Add private group to buddy list */ + g_snprintf(tmp, sizeof(tmp), "%s [Private Group]", name); + comp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_replace(comp, g_strdup("channel"), g_strdup(tmp)); + g_hash_table_replace(comp, g_strdup("passphrase"), g_strdup(passphrase)); + + cn = gaim_chat_new(sg->account, alias, comp); + g = (GaimGroup *)p->c->node.parent; + gaim_blist_add_chat(cn, g, (GaimBlistNode *)p->c); + + /* Associate to a real channel */ + gaim_blist_node_set_string((GaimBlistNode *)cn, "parentch", p->channel); + gaim_blist_save(); + + /* Join the group */ + silcgaim_chat_join(sg->gc, comp); + + silc_free(p); +} + +static void +silcgaim_chat_prv_cancel(SilcGaimCharPrv p, GaimRequestFields *fields) +{ + silc_free(p); +} + +static void +silcgaim_chat_prv(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + SilcGaimCharPrv p; + GaimRequestFields *fields; + GaimRequestFieldGroup *g; + GaimRequestField *f; + char tmp[512]; + + p = silc_calloc(1, sizeof(*p)); + if (!p) + return; + p->sg = sg; + + p->channel = g_hash_table_lookup(components, "channel"); + p->c = gaim_blist_find_chat(sg->account, p->channel); + + fields = gaim_request_fields_new(); + + g = gaim_request_field_group_new(NULL); + f = gaim_request_field_string_new("name", _("Group Name"), + NULL, FALSE); + gaim_request_field_group_add_field(g, f); + + f = gaim_request_field_string_new("passphrase", _("Passphrase"), + NULL, FALSE); + gaim_request_field_string_set_masked(f, TRUE); + gaim_request_field_group_add_field(g, f); + + f = gaim_request_field_string_new("alias", _("Alias"), + NULL, FALSE); + gaim_request_field_group_add_field(g, f); + gaim_request_fields_add_group(fields, g); + + g_snprintf(tmp, sizeof(tmp), + _("Please enter the %s channel private group name and passphrase."), + p->channel); + gaim_request_fields(NULL, _("Add Channel Private Group"), NULL, tmp, fields, + "Add", G_CALLBACK(silcgaim_chat_prv_add), + "Cancel", G_CALLBACK(silcgaim_chat_prv_cancel), p); +} + + +/****************************** Channel Modes ********************************/ + +static void +silcgaim_chat_permanent_reset(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + g_hash_table_lookup(components, "channel"), + "-f", NULL); +} + +static void +silcgaim_chat_permanent(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + const char *channel; + + if (!sg->conn) + return; + + /* XXX we should have ability to define which founder + key to use. Now we use the user's own public key + (default key). */ + + /* Call CMODE */ + channel = g_hash_table_lookup(components, "channel"); + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", channel, + "+f", NULL); +} + +typedef struct { + SilcGaim sg; + const char *channel; +} *SilcGaimChatInput; + +static void +silcgaim_chat_ulimit_cb(SilcGaimChatInput s, const char *limit) +{ + SilcChannelEntry channel; + int ulimit = 0; + + channel = silc_client_get_channel(s->sg->client, s->sg->conn, + (char *)s->channel); + if (!channel) + return; + if (limit) + ulimit = atoi(limit); + + if (!limit || !(*limit) || *limit == '0') { + if (limit && ulimit == channel->user_limit) { + silc_free(s); + return; + } + silc_client_command_call(s->sg->client, s->sg->conn, NULL, "CMODE", + s->channel, "-l", NULL); + + silc_free(s); + return; + } + + if (ulimit == channel->user_limit) { + silc_free(s); + return; + } + + /* Call CMODE */ + silc_client_command_call(s->sg->client, s->sg->conn, NULL, "CMODE", + s->channel, "+l", limit, NULL); + + silc_free(s); +} + +static void +silcgaim_chat_ulimit(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + SilcGaimChatInput s; + SilcChannelEntry channel; + const char *ch; + char tmp[32]; + + if (!sg->conn) + return; + + ch = g_strdup(g_hash_table_lookup(components, "channel")); + channel = silc_client_get_channel(sg->client, sg->conn, (char *)ch); + if (!channel) + return; + + s = silc_calloc(1, sizeof(*s)); + if (!s) + return; + s->channel = ch; + s->sg = sg; + g_snprintf(tmp, sizeof(tmp), "%d", (int)channel->user_limit); + gaim_request_input(NULL, _("User Limit"), NULL, + _("Set user limit on channel. Set to zero to reset user limit."), + tmp, FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(silcgaim_chat_ulimit_cb), + _("Cancel"), G_CALLBACK(silcgaim_chat_ulimit_cb), s); +} + +static void +silcgaim_chat_resettopic(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + g_hash_table_lookup(components, "channel"), + "-t", NULL); +} + +static void +silcgaim_chat_settopic(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + g_hash_table_lookup(components, "channel"), + "+t", NULL); +} + +static void +silcgaim_chat_resetprivate(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + g_hash_table_lookup(components, "channel"), + "-p", NULL); +} + +static void +silcgaim_chat_setprivate(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + g_hash_table_lookup(components, "channel"), + "+p", NULL); +} + +static void +silcgaim_chat_resetsecret(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + g_hash_table_lookup(components, "channel"), + "-s", NULL); +} + +static void +silcgaim_chat_setsecret(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", + g_hash_table_lookup(components, "channel"), + "+s", NULL); +} + +GList *silcgaim_chat_menu(GaimConnection *gc, GHashTable *components) +{ + SilcGaim sg = gc->proto_data; + SilcClientConnection conn = sg->conn; + GList *m = NULL; + struct proto_chat_menu *pcm; + const char *chname = NULL; + SilcChannelEntry channel = NULL; + SilcChannelUser chu = NULL; + SilcUInt32 mode = 0; + + if (components) + chname = g_hash_table_lookup(components, "channel"); + if (chname) + channel = silc_client_get_channel(sg->client, sg->conn, + (char *)chname); + if (channel) { + chu = silc_client_on_channel(channel, conn->local_entry); + if (chu) + mode = chu->mode; + } + + if (strstr(chname, "[Private Group]")) + return NULL; + + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Get Info"); + pcm->callback = silcgaim_chat_getinfo; + pcm->gc = gc; + m = g_list_append(m, pcm); + +#if 0 /* XXX For now these are not implemented. We need better + listview dialog from Gaim for these. */ + if (mode & SILC_CHANNEL_UMODE_CHANOP) { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Invite List"); + pcm->callback = silcgaim_chat_invitelist; + pcm->gc = gc; + m = g_list_append(m, pcm); + + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Ban List"); + pcm->callback = silcgaim_chat_banlist; + pcm->gc = gc; + m = g_list_append(m, pcm); + } +#endif + + if (chu) { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Add Private Group"); + pcm->callback = silcgaim_chat_prv; + pcm->gc = gc; + m = g_list_append(m, pcm); + } + + if (mode & SILC_CHANNEL_UMODE_CHANFO) { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Channel Authentication"); + pcm->callback = silcgaim_chat_chauth; + pcm->gc = gc; + m = g_list_append(m, pcm); + + if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Reset Permanent"); + pcm->callback = silcgaim_chat_permanent_reset; + pcm->gc = gc; + m = g_list_append(m, pcm); + } else { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Set Permanent"); + pcm->callback = silcgaim_chat_permanent; + pcm->gc = gc; + m = g_list_append(m, pcm); + } + } + + if (mode & SILC_CHANNEL_UMODE_CHANOP) { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Set User Limit"); + pcm->callback = silcgaim_chat_ulimit; + pcm->gc = gc; + m = g_list_append(m, pcm); + + if (channel->mode & SILC_CHANNEL_MODE_TOPIC) { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Reset Topic Restriction"); + pcm->callback = silcgaim_chat_resettopic; + pcm->gc = gc; + m = g_list_append(m, pcm); + } else { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Set Topic Restriction"); + pcm->callback = silcgaim_chat_settopic; + pcm->gc = gc; + m = g_list_append(m, pcm); + } + + if (channel->mode & SILC_CHANNEL_MODE_PRIVATE) { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Reset Private Channel"); + pcm->callback = silcgaim_chat_resetprivate; + pcm->gc = gc; + m = g_list_append(m, pcm); + } else { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Set Private Channel"); + pcm->callback = silcgaim_chat_setprivate; + pcm->gc = gc; + m = g_list_append(m, pcm); + } + + if (channel->mode & SILC_CHANNEL_MODE_SECRET) { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Reset Secret Channel"); + pcm->callback = silcgaim_chat_resetsecret; + pcm->gc = gc; + m = g_list_append(m, pcm); + } else { + pcm = g_new0(struct proto_chat_menu, 1); + pcm->label = _("Set Secret Channel"); + pcm->callback = silcgaim_chat_setsecret; + pcm->gc = gc; + m = g_list_append(m, pcm); + } + } + + return m; +} + + +/******************************* Joining Etc. ********************************/ + +void silcgaim_chat_join_done(SilcClient client, + SilcClientConnection conn, + SilcClientEntry *clients, + SilcUInt32 clients_count, + void *context) +{ + GaimConnection *gc = client->application; + SilcGaim sg = gc->proto_data; + SilcChannelEntry channel = context; + GaimConversation *convo; + SilcUInt32 retry = SILC_PTR_TO_32(channel->context); + SilcHashTableList htl; + SilcChannelUser chu; + GList *users = NULL; + char tmp[256]; + + if (!clients && retry < 1) { + /* Resolving users failed, try again. */ + channel->context = SILC_32_TO_PTR(retry + 1); + silc_client_get_clients_by_channel(client, conn, channel, + silcgaim_chat_join_done, channel); + return; + } + + /* Add channel to Gaim */ + channel->context = SILC_32_TO_PTR(++sg->channel_ids); + serv_got_joined_chat(gc, sg->channel_ids, channel->channel_name); + convo = gaim_find_conversation_with_account(channel->channel_name, + sg->account); + if (!convo) + return; + + /* Add all users to channel */ + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + if (!chu->client->nickname) + continue; + chu->context = SILC_32_TO_PTR(sg->channel_ids); + +#if 0 /* XXX don't append mode char to nick because Gaim doesn't + give a way to change it afterwards when mode changes. */ + tmp2 = silc_client_chumode_char(chu->mode); + if (tmp2) + g_snprintf(tmp, sizeof(tmp), _("%s%s"), tmp2, + chu->client->nickname); + else + g_snprintf(tmp, sizeof(tmp), _("%s"), + chu->client->nickname); + silc_free(tmp2); + + users = g_list_append(users, g_strdup(tmp)); +#else + users = g_list_append(users, g_strdup(chu->client->nickname)); +#endif + + if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) { + if (chu->client == conn->local_entry) + g_snprintf(tmp, sizeof(tmp), + _("You are channel founder on <I>%s</I>"), + channel->channel_name); + else + g_snprintf(tmp, sizeof(tmp), + _("Channel founder on <I>%s</I> is <I>%s</I>"), + channel->channel_name, chu->client->nickname); + + gaim_conversation_write(convo, NULL, tmp, + GAIM_MESSAGE_SYSTEM, time(NULL)); + + } + } + silc_hash_table_list_reset(&htl); + + gaim_conv_chat_add_users(GAIM_CONV_CHAT(convo), users); + g_list_free(users); + + /* Set topic */ + if (channel->topic) + gaim_conv_chat_set_topic(GAIM_CONV_CHAT(convo), NULL, channel->topic); +} + +void silcgaim_chat_join(GaimConnection *gc, GHashTable *data) +{ + SilcGaim sg = gc->proto_data; + SilcClient client = sg->client; + SilcClientConnection conn = sg->conn; + const char *channel, *passphrase, *parentch; + + if (!conn) + return; + + channel = g_hash_table_lookup(data, "channel"); + passphrase = g_hash_table_lookup(data, "passphrase"); + + /* Check if we are joining a private group. Handle it + purely locally as it's not a real channel */ + if (strstr(channel, "[Private Group]")) { + SilcChannelEntry channel_entry; + SilcChannelPrivateKey key; + GaimChat *c; + SilcGaimPrvgrp grp; + + c = gaim_blist_find_chat(sg->account, channel); + parentch = gaim_blist_node_get_string((GaimBlistNode *)c, "parentch"); + if (!parentch) + return; + + channel_entry = silc_client_get_channel(sg->client, sg->conn, + (char *)parentch); + if (!channel_entry || + !silc_client_on_channel(channel_entry, sg->conn->local_entry)) { + char tmp[512]; + g_snprintf(tmp, sizeof(tmp), + _("You have to join the %s channel before you are " + "able to join the private group"), parentch); + gaim_notify_error(gc, _("Join Private Group"), + _("Cannot join private group"), tmp); + return; + } + + /* Add channel private key */ + if (!silc_client_add_channel_private_key(client, conn, + channel_entry, channel, + NULL, NULL, + (unsigned char *)passphrase, + strlen(passphrase), &key)) + return; + + /* Join the group */ + grp = silc_calloc(1, sizeof(*grp)); + if (!grp) + return; + grp->id = ++sg->channel_ids + SILCGAIM_PRVGRP; + grp->chid = SILC_PTR_TO_32(channel_entry->context); + grp->parentch = parentch; + grp->channel = channel; + grp->key = key; + sg->grps = g_list_append(sg->grps, grp); + serv_got_joined_chat(gc, grp->id, channel); + return; + } + + /* XXX We should have other properties here as well: + 1. whether to try to authenticate to the channel + 1a. with default key, + 1b. with specific key. + 2. whether to try to authenticate to become founder. + 2a. with default key, + 2b. with specific key. + + Since now such variety is not possible in the join dialog + we always use -founder and -auth options, which try to + do both 1 and 2 with default keys. */ + + /* Call JOIN */ + if (passphrase) + silc_client_command_call(client, conn, NULL, "JOIN", + channel, passphrase, "-auth", "-founder", NULL); + else + silc_client_command_call(client, conn, NULL, "JOIN", + channel, "-auth", "-founder", NULL); +} + +void silcgaim_chat_invite(GaimConnection *gc, int id, const char *msg, + const char *name) +{ + SilcGaim sg = gc->proto_data; + SilcClient client = sg->client; + SilcClientConnection conn = sg->conn; + SilcHashTableList htl; + SilcChannelUser chu; + gboolean found = FALSE; + + if (!conn) + return; + + /* See if we are inviting on a private group. Invite + to the actual channel */ + if (id > SILCGAIM_PRVGRP) { + GList *l; + SilcGaimPrvgrp prv; + + for (l = sg->grps; l; l = l->next) + if (((SilcGaimPrvgrp)l->data)->id == id) + break; + if (!l) + return; + prv = l->data; + id = prv->chid; + } + + /* Find channel by id */ + silc_hash_table_list(conn->local_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + if (SILC_PTR_TO_32(chu->channel->context) == id ) { + found = TRUE; + break; + } + } + silc_hash_table_list_reset(&htl); + if (!found) + return; + + /* Call INVITE */ + silc_client_command_call(client, conn, NULL, "INVITE", + chu->channel->channel_name, + name); +} + +void silcgaim_chat_leave(GaimConnection *gc, int id) +{ + SilcGaim sg = gc->proto_data; + SilcClient client = sg->client; + SilcClientConnection conn = sg->conn; + SilcHashTableList htl; + SilcChannelUser chu; + gboolean found = FALSE; + GList *l; + SilcGaimPrvgrp prv; + + if (!conn) + return; + + /* See if we are leaving a private group */ + if (id > SILCGAIM_PRVGRP) { + SilcChannelEntry channel; + + for (l = sg->grps; l; l = l->next) + if (((SilcGaimPrvgrp)l->data)->id == id) + break; + if (!l) + return; + prv = l->data; + channel = silc_client_get_channel(sg->client, sg->conn, + (char *)prv->parentch); + if (!channel) + return; + silc_client_del_channel_private_key(client, conn, + channel, prv->key); + silc_free(prv); + sg->grps = g_list_remove(sg->grps, prv); + serv_got_chat_left(gc, id); + return; + } + + /* Find channel by id */ + silc_hash_table_list(conn->local_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + if (SILC_PTR_TO_32(chu->channel->context) == id ) { + found = TRUE; + break; + } + } + silc_hash_table_list_reset(&htl); + if (!found) + return; + + /* Call LEAVE */ + silc_client_command_call(client, conn, NULL, "LEAVE", + chu->channel->channel_name, NULL); + + serv_got_chat_left(gc, id); + + /* Leave from private groups on this channel as well */ + for (l = sg->grps; l; l = l->next) + if (((SilcGaimPrvgrp)l->data)->chid == id) { + prv = l->data; + silc_client_del_channel_private_key(client, conn, + chu->channel, + prv->key); + serv_got_chat_left(gc, prv->id); + silc_free(prv); + sg->grps = g_list_remove(sg->grps, prv); + if (!sg->grps) + break; + } +} + +int silcgaim_chat_send(GaimConnection *gc, int id, const char *msg) +{ + SilcGaim sg = gc->proto_data; + SilcClient client = sg->client; + SilcClientConnection conn = sg->conn; + SilcHashTableList htl; + SilcChannelUser chu; + SilcChannelEntry channel = NULL; + SilcChannelPrivateKey key = NULL; + SilcUInt32 flags; + int ret; + gboolean found = FALSE; + gboolean sign = gaim_prefs_get_bool("/plugins/prpl/silc/sign_chat"); + + if (!msg || !conn) + return 0; + + /* See if command */ + if (strlen(msg) > 1 && msg[0] == '/') { + if (!silc_client_command_call(client, conn, msg + 1)) + gaim_notify_error(gc, ("Call Command"), _("Cannot call command"), + _("Unknown command")); + return 0; + } + + flags = SILC_MESSAGE_FLAG_UTF8; + if (sign) + flags |= SILC_MESSAGE_FLAG_SIGNED; + + /* Get the channel private key if we are sending on + private group */ + if (id > SILCGAIM_PRVGRP) { + GList *l; + SilcGaimPrvgrp prv; + + for (l = sg->grps; l; l = l->next) + if (((SilcGaimPrvgrp)l->data)->id == id) + break; + if (!l) + return 0; + prv = l->data; + channel = silc_client_get_channel(sg->client, sg->conn, + (char *)prv->parentch); + if (!channel) + return 0; + key = prv->key; + } + + if (!channel) { + /* Find channel by id */ + silc_hash_table_list(conn->local_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + if (SILC_PTR_TO_32(chu->channel->context) == id ) { + found = TRUE; + break; + } + } + silc_hash_table_list_reset(&htl); + if (!found) + return 0; + channel = chu->channel; + } + + /* Send channel message */ + ret = silc_client_send_channel_message(client, conn, channel, key, + flags, (unsigned char *)msg, + strlen(msg), TRUE); + if (ret) + serv_got_chat_in(gc, id, gaim_connection_get_display_name(gc), 0, msg, + time(NULL)); + + return ret; +} + +void silcgaim_chat_set_topic(GaimConnection *gc, int id, const char *topic) +{ + SilcGaim sg = gc->proto_data; + SilcClient client = sg->client; + SilcClientConnection conn = sg->conn; + SilcHashTableList htl; + SilcChannelUser chu; + gboolean found = FALSE; + + if (!topic || !conn) + return; + + /* See if setting topic on private group. Set it + on the actual channel */ + if (id > SILCGAIM_PRVGRP) { + GList *l; + SilcGaimPrvgrp prv; + + for (l = sg->grps; l; l = l->next) + if (((SilcGaimPrvgrp)l->data)->id == id) + break; + if (!l) + return; + prv = l->data; + id = prv->chid; + } + + /* Find channel by id */ + silc_hash_table_list(conn->local_entry->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chu)) { + if (SILC_PTR_TO_32(chu->channel->context) == id ) { + found = TRUE; + break; + } + } + silc_hash_table_list_reset(&htl); + if (!found) + return; + + /* Call TOPIC */ + silc_client_command_call(client, conn, NULL, "TOPIC", + chu->channel->channel_name, topic, NULL); +} + +GaimRoomlist *silcgaim_roomlist_get_list(GaimConnection *gc) +{ + SilcGaim sg = gc->proto_data; + SilcClient client = sg->client; + SilcClientConnection conn = sg->conn; + GList *fields = NULL; + GaimRoomlistField *f; + + if (!conn) + return NULL; + + if (sg->roomlist) + gaim_roomlist_unref(sg->roomlist); + + sg->roomlist_canceled = FALSE; + + sg->roomlist = gaim_roomlist_new(gaim_connection_get_account(gc)); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "channel", TRUE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_INT, + _("Users"), "users", FALSE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, + _("Topic"), "topic", FALSE); + fields = g_list_append(fields, f); + gaim_roomlist_set_fields(sg->roomlist, fields); + + /* Call LIST */ + silc_client_command_call(client, conn, "LIST"); + + gaim_roomlist_set_in_progress(sg->roomlist, TRUE); + + return sg->roomlist; +} + +void silcgaim_roomlist_cancel(GaimRoomlist *list) +{ + GaimConnection *gc = gaim_account_get_connection(list->account); + SilcGaim sg; + + if (!gc) + return; + sg = gc->proto_data; + + gaim_roomlist_set_in_progress(list, FALSE); + if (sg->roomlist == list) { + gaim_roomlist_unref(sg->roomlist); + sg->roomlist = NULL; + sg->roomlist_canceled = TRUE; + } +}