X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=blobdiff_plain;f=lib%2Fsilcclient%2Fclient_channel.c;h=7dd4f0a6ad59a23572262fb48970de4decda3616;hp=c12532ca6d68af0ca77977354cd045bf1b5b4afe;hb=HEAD;hpb=3ef234937ec402fb77006783624375ef61ffa65d diff --git a/lib/silcclient/client_channel.c b/lib/silcclient/client_channel.c index c12532ca..7dd4f0a6 100644 --- a/lib/silcclient/client_channel.c +++ b/lib/silcclient/client_channel.c @@ -2,15 +2,14 @@ client_channel.c - Author: Pekka Riikonen + Author: Pekka Riikonen - 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 @@ -18,300 +17,608 @@ */ /* $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) +typedef struct { + SilcClient client; + SilcClientConnection conn; + SilcChannelEntry channel; +} *SilcClientChannelMessageContext; + +/* Message payload encoding callback */ + +static void silc_client_send_channel_message_final(SilcBuffer message, + void *context) { - int i; - SilcSocketConnection sock = conn->sock; - SilcBuffer payload; - SilcPacketContext packetdata; + SilcClientChannelMessageContext c = context; + + /* Send the channel message */ + if (message) + silc_packet_send_ext(c->conn->stream, SILC_PACKET_CHANNEL_MESSAGE, 0, + 0, NULL, SILC_ID_CHANNEL, &c->channel->id, + silc_buffer_datalen(message), NULL, NULL); + + silc_client_unref_channel(c->client, c->conn, c->channel); + silc_free(c); +} + +/* 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) +{ + SilcClientChannelMessageContext c; + SilcChannelUser chu; SilcCipher cipher; SilcHmac hmac; - unsigned char *id_string; - unsigned int iv_len; + SilcID sid, rid; - SILC_LOG_DEBUG(("Sending packet to channel")); + SILC_LOG_DEBUG(("Sending channel message")); - if (!channel || !channel->key || !channel->hmac) { - client->ops->say(client, conn, - "Cannot talk to channel: key does not exist"); - return; + if (silc_unlikely(!client || !conn || !channel)) + return FALSE; + if (silc_unlikely(flags & SILC_MESSAGE_FLAG_SIGNED && !hash)) { + SILC_LOG_ERROR(("Cannot send signed message without hash, missing " + "arguments")); + 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; } - /* 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); + /* 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; + } + + if (silc_unlikely(!cipher || !hmac)) { + SILC_LOG_ERROR(("No cipher and HMAC for channel")); + return FALSE; + } + + c = silc_calloc(1, sizeof(*c)); + if (!c) + return FALSE; + + c->client = client; + c->conn = conn; + c->channel = silc_client_ref_channel(client, conn, channel); + + sid.type = SILC_ID_CLIENT; + sid.u.client_id = chu->client->id; + rid.type = SILC_ID_CHANNEL; + rid.u.channel_id = chu->channel->id; + + /* Encode the message payload. This also encrypts the message payload. */ + silc_message_payload_encode(flags, data, data_len, TRUE, FALSE, + cipher, hmac, client->rng, NULL, + conn->private_key, hash, &sid, &rid, NULL, + silc_client_send_channel_message_final, c); + + return TRUE; +} + +/************************* Channel Message Receive **************************/ + +/* 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 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. */ +/* Process received channel message */ -void silc_client_channel_message(SilcClient client, - SilcSocketConnection sock, - SilcPacketContext *packet) +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; + } + + /* 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; + } - message = silc_channel_get_data(payload, NULL); + /* 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; } -/* Saves channel key from encoded `key_payload'. This is used when we - receive Channel Key Payload and when we are processing JOIN command - reply. */ +/* Channel message error. */ -void silc_client_save_channel_key(SilcClientConnection conn, - SilcBuffer key_payload, - SilcChannelEntry channel) +SILC_FSM_STATE(silc_client_channel_message_error) { - unsigned char *id_string, *key, *cipher, hash[32]; - unsigned int tmp_len; - SilcChannelID *id; - SilcIDCacheEntry id_cache = NULL; + 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. */ + +SilcBool silc_client_save_channel_key(SilcClient client, + SilcClientConnection conn, + SilcBuffer key_payload, + SilcChannelEntry channel) +{ + 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; } @@ -320,10 +627,40 @@ int silc_client_add_channel_private_key(SilcClient client, 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->internal.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; } @@ -334,13 +671,46 @@ int silc_client_del_channel_private_keys(SilcClient client, 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'. @@ -349,20 +719,260 @@ int silc_client_del_channel_private_key(SilcClient client, 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_free_channel_private_keys(SilcChannelPrivateKey *keys, - unsigned int key_count) +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. */ + +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; }