Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client_channel.c
index 408f3fc9871635de4704fe33661a8965060a7de8..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,
-                                     SilcChannelPrivateKey key,
-                                     SilcMessageFlags flags,
-                                     unsigned char *data, 
-                                     uint32 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;
-  uint32 iv_len;
+  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)) {
+    SILC_LOG_ERROR(("Cannot send signed message without hash, missing "
+                   "arguments"));
+    return FALSE;
+  }
+  if (silc_unlikely(conn->internal->disconnected))
+    return FALSE;
 
-  SILC_LOG_DEBUG(("Sending packet to channel"));
+  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->mode & SILC_CHANNEL_MODE_PRIVKEY) {
+  if (channel->internal.private_keys) {
     if (key) {
       /* Use key application specified */
-      cipher = key->cipher;
+      cipher = key->send_key;
       hmac = key->hmac;
-    } else if (channel->curr_key) {
+    } else if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY &&
+              channel->internal.curr_key) {
       /* Use current private key */
-      cipher = channel->curr_key->cipher;
-      hmac = channel->curr_key->hmac;
-    } else if (!channel->curr_key && channel->private_keys) {
-      /* Use just some private key since we don't know what to use 
+      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->private_keys);
-      key = silc_dlist_get(channel->private_keys);
-      cipher = key->cipher;
+      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->curr_key = key;
+      channel->internal.curr_key = key;
     } else {
       /* Use normal channel key generated by the server */
-      cipher = channel->channel_key;
-      hmac = channel->hmac;
+      cipher = channel->internal.send_key;
+      hmac = channel->internal.hmac;
     }
   } else {
     /* Use normal channel key generated by the server */
-    cipher = channel->channel_key;
-    hmac = channel->hmac;
+    cipher = channel->internal.send_key;
+    hmac = channel->internal.hmac;
   }
 
-  if (!cipher || !hmac)
-    return;
+  if (silc_unlikely(!cipher || !hmac)) {
+    SILC_LOG_ERROR(("No cipher and HMAC for channel"));
+    return FALSE;
+  }
 
-  /* Generate IV */
-  iv_len = silc_cipher_get_block_len(cipher);
-  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_message_payload_encode(flags, data_len, data, iv_len, 
-                                               channel->iv, cipher, hmac);
-
-  /* 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);
+  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;
 }
 
-/* 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 **************************/
+
+/* Client resolving callback.  Continues with the channel message processing */
 
-void silc_client_channel_message(SilcClient client, 
-                                SilcSocketConnection sock, 
-                                SilcPacketContext *packet)
+static void silc_client_channel_message_resolved(SilcClient client,
+                                                SilcClientConnection conn,
+                                                SilcStatus status,
+                                                SilcDList clients,
+                                                void *context)
 {
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcBuffer buffer = packet->buffer;
-  SilcChannelMessagePayload payload = NULL;
-  SilcChannelID *id = NULL;
+  /* 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 = 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(("Received channel message"));
+
+  SILC_LOG_HEXDUMP(("Channel message"), silc_buffer_data(buffer),
+                  silc_buffer_len(buffer));
+
+  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;
+  }
 
-  SILC_LOG_DEBUG(("Start"));
+  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;
+  }
 
-  /* Sanity checks */
-  if (packet->dst_id_type != SILC_ID_CHANNEL)
-    goto out;
+  /* 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 */
+  }
 
-  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(!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 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;
+  /* 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;
+  }
 
-  channel = (SilcChannelEntry)id_cache->context;
+  /* 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;
+  }
 
-  /* If there is no channel private key then just decrypt the message 
+  /* 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->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
+  if (!channel->internal.private_keys) {
     /* Parse the channel message payload. This also decrypts the payload */
-    payload = silc_channel_message_payload_parse(buffer, channel->channel_key,
-                                                channel->hmac);
-    if (!payload)
-      goto out;
-  } else if (channel->private_keys) {
-    SilcChannelPrivateKey entry;
-
-    silc_dlist_start(channel->private_keys);
-    while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
-      /* Parse the channel message payload. This also decrypts the payload */
-      payload = silc_channel_message_payload_parse(buffer, entry->cipher,
-                                                  entry->hmac);
-      if (payload)
-       break;
+    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;
     }
-    if (entry == SILC_LIST_END)
-      goto out;
   } else {
-    goto out;
-  }
-
-  message = silc_channel_message_get_data(payload, NULL);
-
-  /* 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 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, 
-                              silc_channel_message_get_flags(payload),
-                              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_message_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];
-  uint32 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));
-
- out:
-  silc_free(id);
   silc_channel_key_payload_free(payload);
+
+  silc_client_unref_channel(client, conn, channel);
+
+  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 know the one key and only some the other key.
-
-   If `cipher' and/or `hmac' is NULL then default values will be used 
-   (aes-256-cbc for cipher and hmac-sha1-96 for hmac).
-
-   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,
-                                       char *hmac,
-                                       unsigned char *key,
-                                       uint32 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[32];
-  SilcSKEKeyMaterial *keymat;
+  unsigned char hash[SILC_HASH_MAXLEN];
+  SilcSKEKeyMaterial keymat;
 
-  if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY))
+  if (!client || !conn || !channel)
     return FALSE;
 
   if (!cipher)
-    cipher = "aes-256-cbc";
+    cipher = SILC_DEFAULT_CIPHER;
   if (!hmac)
-    hmac = "hmac-sha1-96";
+    hmac = SILC_DEFAULT_HMAC;
 
   if (!silc_cipher_is_supported(cipher))
     return FALSE;
-
   if (!silc_hmac_is_supported(hmac))
     return FALSE;
 
-  /* Produce the key material */
-  keymat = silc_calloc(1, sizeof(*keymat));
-  if (silc_ske_process_key_material_data(key, key_len, 16, 256, 16, 
-                                        client->md5hash, keymat) 
-      != SILC_SKE_STATUS_OK)
-    return FALSE;
-
-  /* Remove the current key, if it exists. */
-  if (channel->channel_key) {
-    silc_cipher_free(channel->channel_key);
-    memset(channel->key, 0, channel->key_len / 8);
-    silc_free(channel->key);
-    channel->channel_key = NULL;
-    channel->key = NULL;
-    channel->key_len = 0;
-  }
-  if (channel->hmac) {
-    silc_hmac_free(channel->hmac);
-    channel->hmac = NULL;
+  if (!channel->internal.private_keys) {
+    channel->internal.private_keys = silc_dlist_init();
+    if (!channel->internal.private_keys)
+      return FALSE;
   }
 
-  if (!channel->private_keys)
-    channel->private_keys = silc_dlist_init();
+  /* 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));
-  entry->key = silc_calloc(keymat->enc_key_len / 8, sizeof(*entry->key));
-  memcpy(entry->key, keymat->send_enc_key, keymat->enc_key_len / 8);
-  entry->key_len = keymat->enc_key_len / 8;
+  if (!entry) {
+    silc_ske_free_key_material(keymat);
+    return FALSE;
+  }
+  entry->name = name ? strdup(name) : NULL;
 
-  /* Allocate the cipher and set the key*/
-  silc_cipher_alloc(cipher, &entry->cipher);
-  silc_cipher_set_key(entry->cipher, entry->key, keymat->enc_key_len);
+  /* 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 */
-  silc_hmac_alloc(hmac, NULL, &entry->hmac);
-  silc_hash_make(entry->hmac->hash, entry->key, entry->key_len, hash);
-  silc_hmac_set_key(entry->hmac, hash, silc_hash_len(entry->hmac->hash));
+  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->private_keys, entry);
+  silc_dlist_add(channel->internal.private_keys, entry);
 
-  if (!channel->curr_key)
-    channel->curr_key = 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;
 }
 
@@ -438,29 +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 (!channel->private_keys)
+  if (!client || !conn || !channel)
+    return FALSE;
+
+  if (!channel->internal.private_keys)
     return FALSE;
 
-  silc_dlist_start(channel->private_keys);
-  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
-    silc_dlist_del(channel->private_keys, entry);
-    memset(entry->key, 0, entry->key_len);
-    silc_free(entry->key);
-    silc_cipher_free(entry->cipher);
+  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->curr_key = NULL;
+  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->private_keys);
-  channel->private_keys = NULL;
+  silc_dlist_uninit(channel->internal.private_keys);
+  channel->internal.private_keys = NULL;
 
   return TRUE;
 }
@@ -471,36 +671,43 @@ 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;
 
-  if (!channel->private_keys)
+  if (!client || !conn || !channel)
     return FALSE;
 
-  silc_dlist_start(channel->private_keys);
-  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
-    if (entry == key) {
-      if (channel->curr_key == entry)
-       channel->curr_key = NULL;
-
-      silc_dlist_del(channel->private_keys, entry);
-      memset(entry->key, 0, entry->key_len);
-      silc_free(entry->key);
-      silc_cipher_free(entry->cipher);
-      silc_hmac_free(entry->hmac);
-      silc_free(entry);
-
-      if (silc_dlist_count(channel->private_keys) == 0) {
-       silc_dlist_uninit(channel->private_keys);
-       channel->private_keys = NULL;
-      }
+  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);
 
-      return TRUE;
+    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;
@@ -512,35 +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,
-                                     uint32 *key_count)
+SilcDList silc_client_list_channel_private_keys(SilcClient client,
+                                               SilcClientConnection conn,
+                                               SilcChannelEntry channel)
 {
-  SilcChannelPrivateKey *keys = NULL, entry;
-  uint32 count = 0;
+  SilcChannelPrivateKey entry;
+  SilcDList list;
+
+  if (!client || !conn || !channel)
+    return FALSE;
+
+  if (!channel->internal.private_keys)
+    return NULL;
 
-  if (!channel->private_keys)
+  list = silc_dlist_init();
+  if (!list)
     return NULL;
 
-  silc_dlist_start(channel->private_keys);
-  while ((entry = silc_dlist_get(channel->private_keys)) != SILC_LIST_END) {
-    keys = silc_realloc(keys, sizeof(*keys) * (count + 1));
-    keys[count] = entry;
-    count++;
+  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;
+}
+
+/* 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);
   }
 
-  if (key_count)
-    *key_count = count;
+  silc_rwlock_unlock(client_entry->internal.lock);
 
-  return keys;
+  silc_hash_table_list_reset(&htl);
 }
 
-/* Frees the SilcChannelPrivateKey array. */
+/* Empties channel from users.  This handles entry locking internally. */
 
-void silc_client_free_channel_private_keys(SilcChannelPrivateKey *keys,
-                                          uint32 key_count)
+void silc_client_empty_channel(SilcClient client,
+                              SilcClientConnection conn,
+                              SilcChannelEntry channel)
 {
-  silc_free(keys);
+  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;
 }