client_channel.c
- Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
+ Author: Pekka Riikonen <priikone@silcnet.org>
- Copyright (C) 1997 - 2001 Pekka Riikonen
+ Copyright (C) 1997 - 2007 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; either version 2 of the License, or
- (at your option) any later version.
-
+ 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
*/
/* $Id$ */
-/* This file includes channel message sending and receiving routines,
- channel key receiving and setting, and channel private key handling
- routines. */
-#include "clientlibincludes.h"
+#include "silc.h"
+#include "silcclient.h"
#include "client_internal.h"
-/* Sends packet to the `channel'. Packet to channel is always encrypted
- differently from "normal" packets. SILC header of the packet is
- encrypted with the next receiver's key and the rest of the packet is
- encrypted with the channel specific key. Padding and HMAC is computed
- with the next receiver's key. The `data' is the channel message. If
- the `force_send' is TRUE then the packet is sent immediately. */
+/************************** Channel Message Send ****************************/
-void silc_client_send_channel_message(SilcClient client,
- SilcClientConnection conn,
- SilcChannelEntry channel,
- unsigned char *data,
- unsigned int data_len,
- int force_send)
+/* Sends channel message to `channel'. */
+
+SilcBool silc_client_send_channel_message(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel,
+ SilcChannelPrivateKey key,
+ SilcMessageFlags flags,
+ SilcHash hash,
+ unsigned char *data,
+ SilcUInt32 data_len)
{
- int i;
- SilcSocketConnection sock = conn->sock;
- SilcBuffer payload;
- SilcPacketContext packetdata;
+ SilcChannelUser chu;
+ SilcBuffer buffer;
SilcCipher cipher;
SilcHmac hmac;
- unsigned char *id_string;
- unsigned int iv_len;
+ SilcBool ret;
+ SilcID sid, rid;
+
+ SILC_LOG_DEBUG(("Sending channel message"));
+
+ if (silc_unlikely(!client || !conn || !channel))
+ return FALSE;
+ if (silc_unlikely(flags & SILC_MESSAGE_FLAG_SIGNED && !hash))
+ return FALSE;
+ if (silc_unlikely(conn->internal->disconnected))
+ return FALSE;
+
+ chu = silc_client_on_channel(channel, conn->local_entry);
+ if (silc_unlikely(!chu)) {
+ client->internal->ops->say(conn->client, conn,
+ SILC_CLIENT_MESSAGE_AUDIT,
+ "Cannot talk to channel: not joined");
+ return FALSE;
+ }
+
+ /* Check if it is allowed to send messages to this channel by us. */
+ if (silc_unlikely(channel->mode & SILC_CHANNEL_MODE_SILENCE_USERS &&
+ !chu->mode))
+ return FALSE;
+ if (silc_unlikely(channel->mode & SILC_CHANNEL_MODE_SILENCE_OPERS &&
+ chu->mode & SILC_CHANNEL_UMODE_CHANOP &&
+ !(chu->mode & SILC_CHANNEL_UMODE_CHANFO)))
+ return FALSE;
+ if (silc_unlikely(chu->mode & SILC_CHANNEL_UMODE_QUIET))
+ return FALSE;
+
+ /* Take the key to be used */
+ if (channel->internal.private_keys) {
+ if (key) {
+ /* Use key application specified */
+ cipher = key->send_key;
+ hmac = key->hmac;
+ } else if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY &&
+ channel->internal.curr_key) {
+ /* Use current private key */
+ cipher = channel->internal.curr_key->send_key;
+ hmac = channel->internal.curr_key->hmac;
+ } else if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY &&
+ !channel->internal.curr_key &&
+ channel->internal.private_keys) {
+ /* Use just some private key since we don't know what to use
+ and private keys are set. */
+ silc_dlist_start(channel->internal.private_keys);
+ key = silc_dlist_get(channel->internal.private_keys);
+ cipher = key->send_key;
+ hmac = key->hmac;
+
+ /* Use this key as current private key */
+ channel->internal.curr_key = key;
+ } else {
+ /* Use normal channel key generated by the server */
+ cipher = channel->internal.send_key;
+ hmac = channel->internal.hmac;
+ }
+ } else {
+ /* Use normal channel key generated by the server */
+ cipher = channel->internal.send_key;
+ hmac = channel->internal.hmac;
+ }
- SILC_LOG_DEBUG(("Sending packet to channel"));
+ if (silc_unlikely(!cipher || !hmac)) {
+ SILC_LOG_ERROR(("No cipher and HMAC for channel"));
+ return FALSE;
+ }
- if (!channel || !channel->key || !channel->hmac) {
- client->ops->say(client, conn,
- "Cannot talk to channel: key does not exist");
- return;
+ /* Encode the message payload. This also encrypts the message payload. */
+ sid.type = SILC_ID_CLIENT;
+ sid.u.client_id = chu->client->id;
+ rid.type = SILC_ID_CHANNEL;
+ rid.u.channel_id = chu->channel->id;
+ buffer = silc_message_payload_encode(flags, data, data_len, TRUE, FALSE,
+ cipher, hmac, client->rng, NULL,
+ conn->private_key, hash, &sid, &rid,
+ NULL);
+ if (silc_unlikely(!buffer)) {
+ SILC_LOG_ERROR(("Error encoding channel message"));
+ return FALSE;
}
- /* Generate IV */
- iv_len = silc_cipher_get_block_len(channel->channel_key);
- if (channel->iv[0] == '\0')
- for (i = 0; i < iv_len; i++) channel->iv[i] =
- silc_rng_get_byte(client->rng);
- else
- silc_hash_make(client->md5hash, channel->iv, iv_len, channel->iv);
-
- /* Encode the channel payload. This also encrypts the message payload. */
- payload = silc_channel_payload_encode(data_len, data, iv_len,
- channel->iv, channel->channel_key,
- channel->hmac, client->rng);
-
- /* Get data used in packet header encryption, keys and stuff. */
- cipher = conn->send_key;
- hmac = conn->hmac;
- id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL);
-
- /* Set the packet context pointers. The destination ID is always
- the Channel ID of the channel. Server and router will handle the
- distribution of the packet. */
- packetdata.flags = 0;
- packetdata.type = SILC_PACKET_CHANNEL_MESSAGE;
- packetdata.src_id = conn->local_id_data;
- packetdata.src_id_len = SILC_ID_CLIENT_LEN;
- packetdata.src_id_type = SILC_ID_CLIENT;
- packetdata.dst_id = id_string;
- packetdata.dst_id_len = SILC_ID_CHANNEL_LEN;
- packetdata.dst_id_type = SILC_ID_CHANNEL;
- packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len + packetdata.dst_id_len;
- packetdata.padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len +
- packetdata.dst_id_len));
-
- /* Prepare outgoing data buffer for packet sending */
- silc_packet_send_prepare(sock,
- SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len +
- packetdata.dst_id_len,
- packetdata.padlen,
- payload->len);
-
- packetdata.buffer = sock->outbuf;
-
- /* Put the channel message payload to the outgoing data buffer */
- silc_buffer_put(sock->outbuf, payload->data, payload->len);
-
- /* Create the outgoing packet */
- silc_packet_assemble(&packetdata);
-
- /* Encrypt the header and padding of the packet. This is encrypted
- with normal session key shared with our server. */
- silc_packet_encrypt(cipher, hmac, sock->outbuf, SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len + packetdata.dst_id_len +
- packetdata.padlen);
-
- SILC_LOG_HEXDUMP(("Packet to channel, len %d", sock->outbuf->len),
- sock->outbuf->data, sock->outbuf->len);
-
- /* Now actually send the packet */
- silc_client_packet_send_real(client, sock, force_send);
- silc_buffer_free(payload);
- silc_free(id_string);
+ /* Send the channel message */
+ ret = silc_packet_send_ext(conn->stream, SILC_PACKET_CHANNEL_MESSAGE, 0,
+ 0, NULL, SILC_ID_CHANNEL, &channel->id,
+ silc_buffer_datalen(buffer), NULL, NULL);
+
+ silc_buffer_free(buffer);
+ return ret;
}
-/* Process received message to a channel (or from a channel, really). This
- decrypts the channel message with channel specific key and parses the
- channel payload. Finally it displays the message on the screen. */
+/************************* Channel Message Receive **************************/
-void silc_client_channel_message(SilcClient client,
- SilcSocketConnection sock,
- SilcPacketContext *packet)
+/* Client resolving callback. Continues with the channel message processing */
+
+static void silc_client_channel_message_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcStatus status,
+ SilcDList clients,
+ void *context)
+{
+ /* If no client found, ignore the channel message, a silent error */
+ if (!clients)
+ silc_fsm_next(context, silc_client_channel_message_error);
+
+ /* Continue processing the channel message packet */
+ SILC_FSM_CALL_CONTINUE(context);
+}
+
+/* Process received channel message */
+
+SILC_FSM_STATE(silc_client_channel_message)
{
- SilcClientConnection conn = (SilcClientConnection)sock->user_data;
- SilcBuffer buffer = packet->buffer;
- SilcChannelPayload payload = NULL;
- SilcChannelID *id = NULL;
+ SilcClientConnection conn = fsm_context;
+ SilcClient client = conn->client;
+ SilcPacket packet = state_context;
+ SilcBuffer buffer = &packet->buffer;
+ SilcMessagePayload payload = NULL;
SilcChannelEntry channel;
- SilcChannelUser chu;
- SilcIDCacheEntry id_cache = NULL;
- SilcClientID *client_id = NULL;
- int found = FALSE;
+ SilcClientEntry client_entry;
+ SilcClientID remote_id;
+ SilcChannelID channel_id;
unsigned char *message;
+ SilcUInt32 message_len;
+ SilcChannelPrivateKey key = NULL;
- SILC_LOG_DEBUG(("Start"));
+ SILC_LOG_DEBUG(("Received channel message"));
- /* Sanity checks */
- if (packet->dst_id_type != SILC_ID_CHANNEL)
- goto out;
+ SILC_LOG_HEXDUMP(("Channel message"), silc_buffer_data(buffer),
+ silc_buffer_len(buffer));
- client_id = silc_id_str2id(packet->src_id, packet->src_id_len,
- SILC_ID_CLIENT);
- if (!client_id)
- goto out;
- id = silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CHANNEL);
- if (!id)
- goto out;
+ if (silc_unlikely(packet->dst_id_type != SILC_ID_CHANNEL)) {
+ /** Invalid packet */
+ silc_fsm_next(fsm, silc_client_channel_message_error);
+ return SILC_FSM_CONTINUE;
+ }
- /* Find the channel entry from channels on this connection */
- if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id,
- SILC_ID_CHANNEL, &id_cache))
- goto out;
+ if (silc_unlikely(!silc_id_str2id(packet->src_id,
+ packet->src_id_len, SILC_ID_CLIENT,
+ &remote_id, sizeof(remote_id)))) {
+ /** Invalid source ID */
+ silc_fsm_next(fsm, silc_client_channel_message_error);
+ return SILC_FSM_CONTINUE;
+ }
- channel = (SilcChannelEntry)id_cache->context;
+ /* Get sender client entry */
+ client_entry = silc_client_get_client_by_id(client, conn, &remote_id);
+ if (!client_entry || !client_entry->internal.valid) {
+ /** Resolve client info */
+ silc_client_unref_client(client, conn, client_entry);
+ SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
+ client, conn, &remote_id, NULL,
+ silc_client_channel_message_resolved,
+ fsm));
+ /* NOT REACHED */
+ }
- /* Parse the channel message payload. This also decrypts the payload */
- payload = silc_channel_payload_parse(buffer, channel->channel_key,
- channel->hmac);
- if (!payload)
- goto out;
+ if (silc_unlikely(!silc_id_str2id(packet->dst_id, packet->dst_id_len,
+ SILC_ID_CHANNEL, &channel_id,
+ sizeof(channel_id)))) {
+ /** Invalid destination ID */
+ silc_fsm_next(fsm, silc_client_channel_message_error);
+ return SILC_FSM_CONTINUE;
+ }
- message = silc_channel_get_data(payload, NULL);
+ /* Find the channel */
+ channel = silc_client_get_channel_by_id(client, conn, &channel_id);
+ if (silc_unlikely(!channel)) {
+ /** Unknown channel */
+ silc_fsm_next(fsm, silc_client_channel_message_error);
+ return SILC_FSM_CONTINUE;
+ }
+
+ /* Check that user is on channel */
+ if (silc_unlikely(!silc_client_on_channel(channel, client_entry))) {
+ /** User not on channel */
+ SILC_LOG_WARNING(("Message from user not on channel, client or "
+ "server bug"));
+ silc_fsm_next(fsm, silc_client_channel_message_error);
+ return SILC_FSM_CONTINUE;
+ }
- /* Find client entry */
- silc_list_start(channel->clients);
- while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
- if (!SILC_ID_CLIENT_COMPARE(chu->client->id, client_id)) {
- found = TRUE;
- break;
+ /* If there is no channel private key then just decrypt the message
+ with the channel key. If private keys are set then just go through
+ all private keys and check what decrypts correctly. */
+ if (!channel->internal.private_keys) {
+ /* Parse the channel message payload. This also decrypts the payload */
+ payload = silc_message_payload_parse(silc_buffer_data(buffer),
+ silc_buffer_len(buffer), FALSE,
+ FALSE, channel->internal.receive_key,
+ channel->internal.hmac,
+ packet->src_id, packet->src_id_len,
+ packet->dst_id, packet->dst_id_len,
+ NULL, FALSE, NULL);
+
+ /* If decryption failed and we have just performed channel key rekey
+ we will use the old key in decryption. If that fails too then we
+ cannot do more and will drop the packet. */
+ if (silc_unlikely(!payload)) {
+ SilcCipher cipher;
+ SilcHmac hmac;
+
+ if (!channel->internal.old_channel_keys ||
+ !silc_dlist_count(channel->internal.old_channel_keys))
+ goto out;
+
+ SILC_LOG_DEBUG(("Attempting to decrypt with old channel key(s)"));
+
+ silc_dlist_end(channel->internal.old_channel_keys);
+ silc_dlist_end(channel->internal.old_hmacs);
+ while ((cipher = silc_dlist_get(channel->internal.old_channel_keys))) {
+ hmac = silc_dlist_get(channel->internal.old_hmacs);
+ if (!hmac)
+ break;
+
+ payload = silc_message_payload_parse(silc_buffer_data(buffer),
+ silc_buffer_len(buffer),
+ FALSE, FALSE, cipher, hmac,
+ packet->src_id,
+ packet->src_id_len,
+ packet->dst_id,
+ packet->dst_id_len,
+ NULL, FALSE, NULL);
+ if (payload)
+ break;
+ }
+ if (!payload)
+ goto out;
+ }
+ } else {
+ /* If the private key mode is not set on the channel then try the actual
+ channel key first before trying private keys. */
+ if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY))
+ payload = silc_message_payload_parse(silc_buffer_data(buffer),
+ silc_buffer_len(buffer),
+ FALSE, FALSE,
+ channel->internal.receive_key,
+ channel->internal.hmac,
+ packet->src_id,
+ packet->src_id_len,
+ packet->dst_id,
+ packet->dst_id_len,
+ NULL, FALSE, NULL);
+
+ if (!payload) {
+ silc_dlist_start(channel->internal.private_keys);
+ while ((key = silc_dlist_get(channel->internal.private_keys))) {
+ /* Parse the message payload. This also decrypts the payload */
+ payload = silc_message_payload_parse(silc_buffer_data(buffer),
+ silc_buffer_len(buffer),
+ FALSE, FALSE, key->receive_key,
+ key->hmac, packet->src_id,
+ packet->src_id_len,
+ packet->dst_id,
+ packet->dst_id_len,
+ NULL, FALSE, NULL);
+ if (payload)
+ break;
+ }
+ if (key == SILC_LIST_END)
+ goto out;
}
}
+ message = silc_message_get_data(payload, &message_len);
+
/* Pass the message to application */
- client->ops->channel_message(client, conn, found ? chu->client : NULL,
- channel, message);
+ client->internal->ops->channel_message(
+ client, conn, client_entry, channel, payload,
+ key, silc_message_get_flags(payload),
+ message, message_len);
out:
- if (id)
- silc_free(id);
- if (client_id)
- silc_free(client_id);
+ silc_client_unref_client(client, conn, client_entry);
+ silc_client_unref_channel(client, conn, channel);
if (payload)
- silc_channel_payload_free(payload);
+ silc_message_payload_free(payload);
+ return SILC_FSM_FINISH;
+}
+
+/* Channel message error. */
+
+SILC_FSM_STATE(silc_client_channel_message_error)
+{
+ SilcPacket packet = state_context;
+ silc_packet_free(packet);
+ return SILC_FSM_FINISH;
+}
+
+/******************************* Channel Key ********************************/
+
+/* Timeout callback that is called after a short period of time after the
+ new channel key has been created. This removes the first channel key
+ in the list. */
+
+SILC_TASK_CALLBACK(silc_client_save_channel_key_rekey)
+{
+ SilcChannelEntry channel = (SilcChannelEntry)context;
+ SilcCipher key;
+ SilcHmac hmac;
+
+ if (channel->internal.old_channel_keys) {
+ silc_dlist_start(channel->internal.old_channel_keys);
+ key = silc_dlist_get(channel->internal.old_channel_keys);
+ if (key) {
+ silc_dlist_del(channel->internal.old_channel_keys, key);
+ silc_cipher_free(key);
+ }
+ }
+
+ if (channel->internal.old_hmacs) {
+ silc_dlist_start(channel->internal.old_hmacs);
+ hmac = silc_dlist_get(channel->internal.old_hmacs);
+ if (hmac) {
+ silc_dlist_del(channel->internal.old_hmacs, hmac);
+ silc_hmac_free(hmac);
+ }
+ }
}
-/* Saves channel key from encoded `key_payload'. This is used when we
- receive Channel Key Payload and when we are processing JOIN command
- reply. */
+/* Saves channel key from encoded `key_payload'. This is used when we receive
+ Channel Key Payload and when we are processing JOIN command reply. */
-void silc_client_save_channel_key(SilcClientConnection conn,
- SilcBuffer key_payload,
- SilcChannelEntry channel)
+SilcBool silc_client_save_channel_key(SilcClient client,
+ SilcClientConnection conn,
+ SilcBuffer key_payload,
+ SilcChannelEntry channel)
{
- unsigned char *id_string, *key, *cipher, hash[32];
- unsigned int tmp_len;
- SilcChannelID *id;
- SilcIDCacheEntry id_cache = NULL;
+ unsigned char *id_string, *key, *cipher, *hmac, hash[SILC_HASH_MAXLEN];
+ SilcUInt32 tmp_len;
+ SilcChannelID id;
SilcChannelKeyPayload payload;
- payload = silc_channel_key_payload_parse(key_payload);
+ SILC_LOG_DEBUG(("New channel key"));
+
+ payload = silc_channel_key_payload_parse(silc_buffer_data(key_payload),
+ silc_buffer_len(key_payload));
if (!payload)
- return;
+ return FALSE;
id_string = silc_channel_key_get_id(payload, &tmp_len);
if (!id_string) {
silc_channel_key_payload_free(payload);
- return;
+ return FALSE;
}
- id = silc_id_str2id(id_string, tmp_len, SILC_ID_CHANNEL);
- if (!id) {
+ if (!silc_id_str2id(id_string, tmp_len, SILC_ID_CHANNEL, &id, sizeof(id))) {
silc_channel_key_payload_free(payload);
- return;
+ return FALSE;
}
/* Find channel. */
if (!channel) {
- if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id,
- SILC_ID_CHANNEL, &id_cache))
- goto out;
-
- /* Get channel entry */
- channel = (SilcChannelEntry)id_cache->context;
+ channel = silc_client_get_channel_by_id(client, conn, &id);
+ if (!channel) {
+ SILC_LOG_DEBUG(("Key for unknown channel"));
+ silc_channel_key_payload_free(payload);
+ return FALSE;
+ }
+ } else {
+ silc_client_ref_channel(client, conn, channel);
}
- /* Save the key */
- key = silc_channel_key_get_key(payload, &tmp_len);
+ /* Save the old key for a short period of time so that we can decrypt
+ channel message even after the rekey if some client would be sending
+ messages with the old key after the rekey. */
+ if (!channel->internal.old_channel_keys)
+ channel->internal.old_channel_keys = silc_dlist_init();
+ if (!channel->internal.old_hmacs)
+ channel->internal.old_hmacs = silc_dlist_init();
+ if (channel->internal.old_channel_keys && channel->internal.old_hmacs) {
+ silc_dlist_add(channel->internal.old_channel_keys,
+ channel->internal.receive_key);
+ silc_dlist_add(channel->internal.old_hmacs, channel->internal.hmac);
+ silc_schedule_task_add_timeout(client->schedule,
+ silc_client_save_channel_key_rekey,
+ channel, 15, 0);
+ }
+
+ /* Get channel cipher */
cipher = silc_channel_key_get_cipher(payload, NULL);
- channel->key_len = tmp_len * 8;
- channel->key = silc_calloc(tmp_len, sizeof(*channel->key));
- memcpy(channel->key, key, tmp_len);
+ if (!silc_cipher_alloc(cipher, &channel->internal.send_key)) {
+ client->internal->ops->say(
+ conn->client, conn,
+ SILC_CLIENT_MESSAGE_AUDIT,
+ "Cannot talk to channel: unsupported cipher %s",
+ cipher);
+ silc_client_unref_channel(client, conn, channel);
+ silc_channel_key_payload_free(payload);
+ return FALSE;
+ }
+ if (!silc_cipher_alloc(cipher, &channel->internal.receive_key)) {
+ client->internal->ops->say(
+ conn->client, conn,
+ SILC_CLIENT_MESSAGE_AUDIT,
+ "Cannot talk to channel: unsupported cipher %s",
+ cipher);
+ silc_client_unref_channel(client, conn, channel);
+ silc_channel_key_payload_free(payload);
+ return FALSE;
+ }
- if (!silc_cipher_alloc(cipher, &channel->channel_key)) {
- conn->client->ops->say(conn->client, conn,
- "Cannot talk to channel: unsupported cipher %s", cipher);
- goto out;
+ /* Set the cipher key. Both sending and receiving keys are same */
+ key = silc_channel_key_get_key(payload, &tmp_len);
+ silc_cipher_set_key(channel->internal.send_key, key, tmp_len * 8, TRUE);
+ silc_cipher_set_key(channel->internal.receive_key, key, tmp_len * 8, FALSE);
+
+ /* Get channel HMAC */
+ hmac = (channel->internal.hmac ?
+ (char *)silc_hmac_get_name(channel->internal.hmac) :
+ SILC_DEFAULT_HMAC);
+ if (!silc_hmac_alloc(hmac, NULL, &channel->internal.hmac)) {
+ client->internal->ops->say(
+ conn->client, conn,
+ SILC_CLIENT_MESSAGE_AUDIT,
+ "Cannot talk to channel: unsupported HMAC %s",
+ hmac);
+ silc_client_unref_channel(client, conn, channel);
+ silc_channel_key_payload_free(payload);
+ return FALSE;
}
- /* Set the cipher key */
- silc_cipher_set_key(channel->channel_key, key, channel->key_len);
+ channel->cipher = silc_cipher_get_name(channel->internal.send_key);
+ channel->hmac = silc_hmac_get_name(channel->internal.hmac);
- /* Generate HMAC key from the channel key data and set it */
- if (!channel->hmac)
- silc_hmac_alloc("hmac-sha1-96", NULL, &channel->hmac);
- silc_hash_make(channel->hmac->hash, key, tmp_len, hash);
- silc_hmac_set_key(channel->hmac, hash, silc_hash_len(channel->hmac->hash));
+ /* Set HMAC key */
+ silc_hash_make(silc_hmac_get_hash(channel->internal.hmac), key,
+ tmp_len, hash);
+ silc_hmac_set_key(channel->internal.hmac, hash,
+ silc_hash_len(silc_hmac_get_hash(channel->internal.hmac)));
memset(hash, 0, sizeof(hash));
+ silc_channel_key_payload_free(payload);
- /* Client is now joined to the channel */
- channel->on_channel = TRUE;
+ silc_client_unref_channel(client, conn, channel);
- out:
- silc_free(id);
- silc_channel_key_payload_free(payload);
+ return TRUE;
}
-/* Processes received key for channel. The received key will be used
- to protect the traffic on the channel for now on. Client must receive
- the key to the channel before talking on the channel is possible.
- This is the key that server has generated, this is not the channel
- private key, it is entirely local setting. */
+/* Received channel key packet. The key will replace old channel key. */
-void silc_client_receive_channel_key(SilcClient client,
- SilcSocketConnection sock,
- SilcBuffer packet)
+SILC_FSM_STATE(silc_client_channel_key)
{
- SILC_LOG_DEBUG(("Received key for channel"));
+ SilcClientConnection conn = fsm_context;
+ SilcClient client = conn->client;
+ SilcPacket packet = state_context;
+
+ SILC_LOG_DEBUG(("Received channel key"));
/* Save the key */
- silc_client_save_channel_key(sock->user_data, packet, NULL);
+ silc_client_save_channel_key(client, conn, &packet->buffer, NULL);
+ silc_packet_free(packet);
+
+ return SILC_FSM_FINISH;
}
-/* Adds private key for channel. This may be set only if the channel's mode
- mask includes the SILC_CHANNEL_MODE_PRIVKEY. This returns FALSE if the
- mode is not set. When channel has private key then the messages are
- encrypted using that key. All clients on the channel must also know the
- key in order to decrypt the messages. However, it is possible to have
- several private keys per one channel. In this case only some of the
- clients on the channel may now the one key and only some the other key.
-
- The private key for channel is optional. If it is not set then the
- channel messages are encrypted using the channel key generated by the
- server. However, setting the private key (or keys) for the channel
- significantly adds security. If more than one key is set the library
- will automatically try all keys at the message decryption phase. Note:
- setting many keys slows down the decryption phase as all keys has to
- be tried in order to find the correct decryption key. However, setting
- a few keys does not have big impact to the decryption performace.
-
- NOTE: that this is entirely local setting. The key set using this function
- is not sent to the network at any phase.
-
- NOTE: If the key material was originated by the SKE protocol (using
- silc_client_send_key_agreement) then the `key' MUST be the
- key->send_enc_key as this is dictated by the SILC protocol. However,
- currently it is not expected that the SKE key material would be used
- as channel private key. However, this API allows it. */
-
-int silc_client_add_channel_private_key(SilcClient client,
- SilcClientConnection conn,
- SilcChannelEntry channel,
- char *cipher,
- unsigned char *key,
- unsigned int key_len)
+/**************************** Channel Private Key ***************************/
+
+/* Add new channel private key */
+
+SilcBool silc_client_add_channel_private_key(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel,
+ const char *name,
+ char *cipher,
+ char *hmac,
+ unsigned char *key,
+ SilcUInt32 key_len,
+ SilcChannelPrivateKey *ret_key)
{
+ SilcChannelPrivateKey entry;
+ unsigned char hash[SILC_HASH_MAXLEN];
+ SilcSKEKeyMaterial keymat;
+
+ if (!client || !conn || !channel)
+ return FALSE;
+
+ if (!cipher)
+ cipher = SILC_DEFAULT_CIPHER;
+ if (!hmac)
+ hmac = SILC_DEFAULT_HMAC;
+
+ if (!silc_cipher_is_supported(cipher))
+ return FALSE;
+ if (!silc_hmac_is_supported(hmac))
+ return FALSE;
+
+ if (!channel->internal.private_keys) {
+ channel->internal.private_keys = silc_dlist_init();
+ if (!channel->internal.private_keys)
+ return FALSE;
+ }
+
+ /* Produce the key material */
+ keymat = silc_ske_process_key_material_data(key, key_len, 16, 256, 16,
+ conn->internal->sha1hash);
+ if (!keymat)
+ return FALSE;
+
+ /* Save the key */
+ entry = silc_calloc(1, sizeof(*entry));
+ if (!entry) {
+ silc_ske_free_key_material(keymat);
+ return FALSE;
+ }
+ entry->name = name ? strdup(name) : NULL;
+
+ /* Allocate the cipher and set the key */
+ if (!silc_cipher_alloc(cipher, &entry->send_key)) {
+ silc_free(entry);
+ silc_free(entry->name);
+ silc_ske_free_key_material(keymat);
+ return FALSE;
+ }
+ if (!silc_cipher_alloc(cipher, &entry->receive_key)) {
+ silc_free(entry);
+ silc_free(entry->name);
+ silc_cipher_free(entry->send_key);
+ silc_ske_free_key_material(keymat);
+ return FALSE;
+ }
+ silc_cipher_set_key(entry->send_key, keymat->send_enc_key,
+ keymat->enc_key_len, TRUE);
+ silc_cipher_set_key(entry->receive_key, keymat->send_enc_key,
+ keymat->enc_key_len, FALSE);
+
+ /* Generate HMAC key from the channel key data and set it */
+ if (!silc_hmac_alloc(hmac, NULL, &entry->hmac)) {
+ silc_free(entry);
+ silc_free(entry->name);
+ silc_cipher_free(entry->send_key);
+ silc_cipher_free(entry->receive_key);
+ silc_ske_free_key_material(keymat);
+ return FALSE;
+ }
+ silc_hash_make(silc_hmac_get_hash(entry->hmac), keymat->send_enc_key,
+ keymat->enc_key_len / 8, hash);
+ silc_hmac_set_key(entry->hmac, hash,
+ silc_hash_len(silc_hmac_get_hash(entry->hmac)));
+ memset(hash, 0, sizeof(hash));
+
+ /* Add to the private keys list */
+ silc_dlist_add(channel->internal.private_keys, entry);
+
+ if (!channel->internal.curr_key) {
+ channel->internal.curr_key = entry;
+ channel->cipher = silc_cipher_get_name(entry->send_key);
+ channel->hmac = silc_cipher_get_name(entry->send_key);
+ }
+
+ /* Free the key material */
+ silc_ske_free_key_material(keymat);
+
+ if (ret_key)
+ *ret_key = entry;
return TRUE;
}
after calling this to protect the channel messages. Returns FALSE on
on error, TRUE otherwise. */
-int silc_client_del_channel_private_keys(SilcClient client,
- SilcClientConnection conn,
- SilcChannelEntry channel)
+SilcBool silc_client_del_channel_private_keys(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel)
{
+ SilcChannelPrivateKey entry;
+
+ if (!client || !conn || !channel)
+ return FALSE;
+
+ if (!channel->internal.private_keys)
+ return FALSE;
+
+ silc_dlist_start(channel->internal.private_keys);
+ while ((entry = silc_dlist_get(channel->internal.private_keys))) {
+ silc_dlist_del(channel->internal.private_keys, entry);
+ silc_free(entry->name);
+ silc_cipher_free(entry->send_key);
+ silc_cipher_free(entry->receive_key);
+ silc_hmac_free(entry->hmac);
+ silc_free(entry);
+ }
+
+ channel->internal.curr_key = NULL;
+ if (channel->internal.send_key)
+ channel->cipher = silc_cipher_get_name(channel->internal.send_key);
+ else
+ channel->cipher = NULL;
+ if (channel->hmac)
+ channel->hmac = silc_hmac_get_name(channel->internal.hmac);
+ else
+ channel->hmac = NULL;
+
+ silc_dlist_uninit(channel->internal.private_keys);
+ channel->internal.private_keys = NULL;
return TRUE;
}
old channel key is used hereafter to protect the channel messages. This
returns FALSE on error, TRUE otherwise. */
-int silc_client_del_channel_private_key(SilcClient client,
- SilcClientConnection conn,
- SilcChannelEntry channel,
- SilcChannelPrivateKey key)
+SilcBool silc_client_del_channel_private_key(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel,
+ SilcChannelPrivateKey key)
{
+ SilcChannelPrivateKey entry;
- return TRUE;
+ if (!client || !conn || !channel)
+ return FALSE;
+
+ if (!channel->internal.private_keys)
+ return FALSE;
+
+ silc_dlist_start(channel->internal.private_keys);
+ while ((entry = silc_dlist_get(channel->internal.private_keys))) {
+ if (entry != key)
+ continue;
+
+ if (channel->internal.curr_key == entry) {
+ channel->internal.curr_key = NULL;
+ channel->cipher = silc_cipher_get_name(channel->internal.send_key);
+ channel->hmac = silc_hmac_get_name(channel->internal.hmac);
+ }
+
+ silc_dlist_del(channel->internal.private_keys, entry);
+ silc_free(entry->name);
+ silc_cipher_free(entry->send_key);
+ silc_cipher_free(entry->receive_key);
+ silc_hmac_free(entry->hmac);
+ silc_free(entry);
+
+ if (silc_dlist_count(channel->internal.private_keys) == 0) {
+ silc_dlist_uninit(channel->internal.private_keys);
+ channel->internal.private_keys = NULL;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
}
/* Returns array (pointers) of private keys associated to the `channel'.
used to delete the specific key by giving the pointer as argument to the
function silc_client_del_channel_private_key. */
-SilcChannelPrivateKey *
-silc_client_list_channel_private_keys(SilcClient client,
- SilcClientConnection conn,
- SilcChannelEntry channel,
- unsigned int *key_count)
+SilcDList silc_client_list_channel_private_keys(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel)
+{
+ SilcChannelPrivateKey entry;
+ SilcDList list;
+
+ if (!client || !conn || !channel)
+ return FALSE;
+
+ if (!channel->internal.private_keys)
+ return NULL;
+
+ list = silc_dlist_init();
+ if (!list)
+ return NULL;
+
+ silc_dlist_start(channel->internal.private_keys);
+ while ((entry = silc_dlist_get(channel->internal.private_keys)))
+ silc_dlist_add(list, entry);
+
+ return list;
+}
+
+/* Sets the `key' to be used as current channel private key on the
+ `channel'. Packet sent after calling this function will be secured
+ with `key'. */
+
+void silc_client_current_channel_private_key(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel,
+ SilcChannelPrivateKey key)
+{
+ if (!channel)
+ return;
+ channel->internal.curr_key = key;
+ channel->cipher = silc_cipher_get_name(key->send_key);
+ channel->hmac = silc_hmac_get_name(key->hmac);
+}
+
+/***************************** Utility routines *****************************/
+
+/* Returns the SilcChannelUser entry if the `client_entry' is joined on the
+ channel indicated by the `channel'. NULL if client is not joined on
+ the channel. */
+
+SilcChannelUser silc_client_on_channel(SilcChannelEntry channel,
+ SilcClientEntry client_entry)
{
+ SilcChannelUser chu;
+
+ if (silc_hash_table_find(channel->user_list, client_entry, NULL,
+ (void *)&chu))
+ return chu;
return NULL;
}
-/* Frees the SilcChannelPrivateKey array. */
+/* Adds client to channel. Returns TRUE if user was added or is already
+ added to the channel, FALSE on error. Must be called with both `channel'
+ and `client_entry' locked. */
+
+SilcBool silc_client_add_to_channel(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel,
+ SilcClientEntry client_entry,
+ SilcUInt32 cumode)
+{
+ SilcChannelUser chu;
+
+ if (silc_client_on_channel(channel, client_entry))
+ return TRUE;
+
+ SILC_LOG_DEBUG(("Add client %s to channel", client_entry->nickname));
+
+ chu = silc_calloc(1, sizeof(*chu));
+ if (!chu)
+ return FALSE;
+
+ chu->client = client_entry;
+ chu->channel = channel;
+ chu->mode = cumode;
+
+ silc_client_ref_client(client, conn, client_entry);
+ silc_client_ref_channel(client, conn, channel);
+
+ silc_hash_table_add(channel->user_list, client_entry, chu);
+ silc_hash_table_add(client_entry->channels, channel, chu);
+
+ return TRUE;
+}
+
+/* Removes client from a channel. Returns FALSE if user is not on channel.
+ This handles entry locking internally. */
+
+SilcBool silc_client_remove_from_channel(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel,
+ SilcClientEntry client_entry)
+{
+ SilcChannelUser chu;
+
+ chu = silc_client_on_channel(channel, client_entry);
+ if (!chu)
+ return FALSE;
+
+ SILC_LOG_DEBUG(("Remove client %s from channel", client_entry->nickname));
+
+ silc_rwlock_wrlock(client_entry->internal.lock);
+ silc_rwlock_wrlock(channel->internal.lock);
+
+ silc_hash_table_del(chu->client->channels, chu->channel);
+ silc_hash_table_del(chu->channel->user_list, chu->client);
+ silc_free(chu);
+
+ /* If channel became empty, delete it */
+ if (!silc_hash_table_count(channel->user_list))
+ silc_client_del_channel(client, conn, channel);
+
+ silc_rwlock_unlock(client_entry->internal.lock);
+ silc_rwlock_unlock(channel->internal.lock);
+
+ silc_client_unref_client(client, conn, client_entry);
+ silc_client_unref_channel(client, conn, channel);
+
+ return TRUE;
+}
+
+/* Removes a client entry from all channels it has joined. This handles
+ entry locking internally. */
+
+void silc_client_remove_from_channels(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry)
+{
+ SilcHashTableList htl;
+ SilcChannelUser chu;
+
+ if (!silc_hash_table_count(client_entry->channels))
+ return;
+
+ SILC_LOG_DEBUG(("Remove client from all joined channels"));
+
+ silc_rwlock_wrlock(client_entry->internal.lock);
+
+ silc_hash_table_list(client_entry->channels, &htl);
+ while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+ silc_rwlock_wrlock(chu->channel->internal.lock);
+
+ silc_hash_table_del(chu->client->channels, chu->channel);
+ silc_hash_table_del(chu->channel->user_list, chu->client);
+
+ /* If channel became empty, delete it */
+ if (!silc_hash_table_count(chu->channel->user_list))
+ silc_client_del_channel(client, conn, chu->channel);
+
+ silc_rwlock_unlock(chu->channel->internal.lock);
+
+ silc_client_unref_client(client, conn, chu->client);
+ silc_client_unref_channel(client, conn, chu->channel);
+ silc_free(chu);
+ }
+
+ silc_rwlock_unlock(client_entry->internal.lock);
+
+ silc_hash_table_list_reset(&htl);
+}
+
+/* Empties channel from users. This handles entry locking internally. */
+
+void silc_client_empty_channel(SilcClient client,
+ SilcClientConnection conn,
+ SilcChannelEntry channel)
+{
+ SilcHashTableList htl;
+ SilcChannelUser chu;
+
+ silc_rwlock_wrlock(channel->internal.lock);
+
+ silc_hash_table_list(channel->user_list, &htl);
+ while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+ silc_hash_table_del(chu->client->channels, chu->channel);
+ silc_hash_table_del(chu->channel->user_list, chu->client);
+ silc_client_unref_client(client, conn, chu->client);
+ silc_client_unref_channel(client, conn, chu->channel);
+ silc_free(chu);
+ }
+
+ silc_rwlock_unlock(channel->internal.lock);
+
+ silc_hash_table_list_reset(&htl);
+}
+
+/* Save public keys to channel public key list. Removes keys that are
+ marked to be removed. Must be called with `channel' locked. */
-void silc_client_free_channel_private_keys(SilcChannelPrivateKey *keys,
- unsigned int key_count)
+SilcBool silc_client_channel_save_public_keys(SilcChannelEntry channel,
+ unsigned char *chpk_list,
+ SilcUInt32 chpk_list_len,
+ SilcBool remove_all)
{
+ SilcArgumentDecodedList a, b;
+ SilcDList chpks;
+ SilcBool found;
+
+ if (remove_all) {
+ /* Remove all channel public keys */
+ if (!channel->channel_pubkeys)
+ return FALSE;
+
+ silc_dlist_start(channel->channel_pubkeys);
+ while ((b = silc_dlist_get(channel->channel_pubkeys)))
+ silc_dlist_del(channel->channel_pubkeys, b);
+
+ silc_dlist_uninit(channel->channel_pubkeys);
+ channel->channel_pubkeys = NULL;
+ return TRUE;
+ }
+
+ /* Parse channel public key list and add or remove public keys */
+ chpks = silc_argument_list_parse_decoded(chpk_list, chpk_list_len,
+ SILC_ARGUMENT_PUBLIC_KEY);
+ if (!chpks)
+ return FALSE;
+
+ if (!channel->channel_pubkeys) {
+ channel->channel_pubkeys = silc_dlist_init();
+ if (!channel->channel_pubkeys) {
+ silc_argument_list_free(chpks, SILC_ARGUMENT_PUBLIC_KEY);
+ return FALSE;
+ }
+ }
+
+ silc_dlist_start(chpks);
+ while ((a = silc_dlist_get(chpks))) {
+ found = FALSE;
+ silc_dlist_start(channel->channel_pubkeys);
+ while ((b = silc_dlist_get(channel->channel_pubkeys))) {
+ if (silc_pkcs_public_key_compare(a->argument, b->argument)) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if ((a->arg_type == 0x00 || a->arg_type == 0x03) && !found) {
+ silc_dlist_add(channel->channel_pubkeys, a);
+ silc_dlist_del(chpks, a);
+ } else if (a->arg_type == 0x01 && found) {
+ silc_dlist_del(channel->channel_pubkeys, b);
+ }
+ }
+
+ silc_argument_list_free(chpks, SILC_ARGUMENT_PUBLIC_KEY);
+
+ return TRUE;
}