Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client_channel.c
index c12532ca6d68af0ca77977354cd045bf1b5b4afe..7dd4f0a6ad59a23572262fb48970de4decda3616 100644 (file)
@@ -2,15 +2,14 @@
 
   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)
+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;
 }