More client library rewrites.
[silc.git] / lib / silcclient / client_prvmsg.c
index d447e0af24f2abd8c51888a549fd721b884c27df..61c43b621aabf0e2d247bfc72a93e19121038e5c 100644 (file)
@@ -1,10 +1,10 @@
 /*
 
-  client_prvmsg.c 
+  client_prvmsg.c
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2002 Pekka Riikonen
+  Copyright (C) 1997 - 2006 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
 
 */
 /* $Id$ */
-/* This file includes the private message sending and receiving routines
-   and private message key handling routines. */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "client_internal.h"
 
-/* Sends private message to remote client. If private message key has
-   not been set with this client then the message will be encrypted using
-   normal session keys. Private messages are special packets in SILC
-   network hence we need this own function for them. This is similiar
-   to silc_client_packet_send_to_channel except that we send private
-   message. The `data' is the private message. If the `force_send' is
-   TRUE the packet is sent immediately. */
+/************************** Private Message Send ****************************/
 
-void silc_client_send_private_message(SilcClient client,
-                                     SilcClientConnection conn,
-                                     SilcClientEntry client_entry,
-                                     SilcMessageFlags flags,
-                                     unsigned char *data, 
-                                     SilcUInt32 data_len, 
-                                     bool force_send)
+/* Sends private message to remote client. */
+
+SilcBool silc_client_send_private_message(SilcClient client,
+                                         SilcClientConnection conn,
+                                         SilcClientEntry client_entry,
+                                         SilcMessageFlags flags,
+                                         SilcHash hash,
+                                         unsigned char *data,
+                                         SilcUInt32 data_len)
 {
-  SilcSocketConnection sock;
   SilcBuffer buffer;
-  SilcPacketContext packetdata;
-  const SilcBufferStruct packet;
-  SilcCipher cipher;
-  SilcHmac hmac;
-  int block_len;
-
-  assert(client && conn && client_entry);
-  sock = conn->sock;
+  SilcBool ret;
+
   SILC_LOG_DEBUG(("Sending private message"));
 
-  /* Encode private message payload */
-  buffer = silc_private_message_payload_encode(flags,
-                                              data_len, data,
-                                              client_entry->send_key,
-                                              client_entry->hmac_send,
-                                              client->rng);
-
-  /* If we don't have private message specific key then private messages
-     are just as any normal packet thus call normal packet sending.  If
-     the key exist then the encryption process is a bit different and
-     will be done in the rest of this function. */
-  if (!client_entry->send_key) {
-    silc_client_packet_send(client, sock, SILC_PACKET_PRIVATE_MESSAGE,
-                           client_entry->id, SILC_ID_CLIENT, NULL, NULL,
-                           buffer->data, buffer->len, force_send);
-    goto out;
-  }
+  if (!client || !conn || !client_entry)
+    return FALSE;
+  if (flags & SILC_MESSAGE_FLAG_SIGNED && !hash)
+    return FALSE;
 
-  /* We have private message specific key */
-
-  /* Get data used in the encryption */
-  cipher = conn->internal->send_key;
-  hmac = conn->internal->hmac_send;
-  block_len = silc_cipher_get_block_len(cipher);
-
-  /* Set the packet context pointers. */
-  data = buffer->data;
-  data_len = buffer->len;
-  packetdata.flags = SILC_PACKET_FLAG_PRIVMSG_KEY;
-  packetdata.type = SILC_PACKET_PRIVATE_MESSAGE;
-  packetdata.src_id = conn->local_id_data;
-  packetdata.src_id_len = silc_id_get_len(conn->local_id, SILC_ID_CLIENT);
-  packetdata.src_id_type = SILC_ID_CLIENT;
-  packetdata.dst_id = silc_id_id2str(client_entry->id, SILC_ID_CLIENT);
-  packetdata.dst_id_len = silc_id_get_len(client_entry->id, SILC_ID_CLIENT);
-  packetdata.dst_id_type = SILC_ID_CLIENT;
-  data_len = SILC_PACKET_DATALEN(data_len, SILC_PACKET_HEADER_LEN +
-                                packetdata.src_id_len +
-                                packetdata.dst_id_len);
-  packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + 
-    packetdata.src_id_len + packetdata.dst_id_len;
-  SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN +
-                     packetdata.src_id_len +
-                     packetdata.dst_id_len), block_len, packetdata.padlen);
-
-  /* Create the outgoing packet */
-  if (!silc_packet_assemble(&packetdata, client->rng, cipher, hmac, sock, 
-                            data, data_len, (const SilcBuffer)&packet)) {
-    SILC_LOG_ERROR(("Error assembling packet"));
-    goto out;
+  /* Encode private message payload */
+  buffer =
+    silc_message_payload_encode(flags, data, data_len,
+                               (!client_entry->internal.send_key ? FALSE :
+                                !client_entry->internal.generated),
+                               TRUE, client_entry->internal.send_key,
+                               client_entry->internal.hmac_send,
+                               client->rng, NULL, conn->private_key,
+                               hash, NULL);
+  if (!buffer) {
+    SILC_LOG_ERROR(("Error encoding private message"));
+    return FALSE;
   }
 
-  /* Encrypt the header and padding of the packet. */
-  silc_packet_encrypt(cipher, hmac, conn->internal->psn_send++,
-                     (SilcBuffer)&packet, SILC_PACKET_HEADER_LEN +
-                     packetdata.src_id_len + packetdata.dst_id_len +
-                     packetdata.padlen);
+  /* Send the private message packet */
+  ret = silc_packet_send_ext(conn->stream, SILC_PACKET_PRIVATE_MESSAGE,
+                            client_entry->internal.send_key ?
+                            SILC_PACKET_FLAG_PRIVMSG_KEY : 0,
+                            0, NULL, SILC_ID_CLIENT, &client_entry->id,
+                            silc_buffer_datalen(buffer), NULL, NULL);
 
-  SILC_LOG_HEXDUMP(("Private message packet, len %d", packet.len),
-                  packet.data, packet.len);
+  silc_buffer_free(buffer);
+  return ret;
+}
 
-  /* Now actually send the packet */
-  silc_client_packet_send_real(client, sock, force_send);
+/************************* Private Message Receive **************************/
 
-  /* Check for mandatory rekey */
-  if (conn->internal->psn_send == SILC_CLIENT_REKEY_THRESHOLD)
-    silc_schedule_task_add(client->schedule, sock->sock,
-                          silc_client_rekey_callback, sock, 0, 1,
-                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+/* Private message waiting context */
+typedef struct {
+  SilcMutex wait_lock;
+  SilcCond wait_cond;
+  SilcDList message_queue;
+  unsigned int stopped      : 1;
+} *SilcClientPrivateMessageWait;
 
-  silc_free(packetdata.dst_id);
+/* Client resolving callback.  Continues with the private message processing */
 
- out:
-  silc_buffer_free(buffer);
-}     
-
-static void silc_client_private_message_cb(SilcClient client,
-                                          SilcClientConnection conn,
-                                          SilcClientEntry *clients,
-                                          SilcUInt32 clients_count,
-                                          void *context)
+static void silc_client_private_message_resolved(SilcClient client,
+                                                SilcClientConnection conn,
+                                                SilcStatus status,
+                                                SilcDList clients,
+                                                void *context)
 {
-  SilcPacketContext *packet = (SilcPacketContext *)context;
-
-  if (!clients) {
-    silc_packet_context_free(packet);
-    return;
-  }
+  /* If no client found, ignore the private message, a silent error */
+  if (!clients)
+    silc_fsm_next(context, silc_client_private_message_error);
 
-  silc_client_private_message(client, conn->sock, packet);
-  silc_packet_context_free(packet);
+  /* Continue processing the private message packet */
+  SILC_FSM_CALL_CONTINUE(context);
 }
 
-/* Private message received. This processes the private message and
-   finally displays it on the screen. */
+/* Private message received. */
 
-void silc_client_private_message(SilcClient client, 
-                                SilcSocketConnection sock, 
-                                SilcPacketContext *packet)
+SILC_FSM_STATE(silc_client_private_message)
 {
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcPrivateMessagePayload payload = NULL;
-  SilcClientID *remote_id = NULL;
-  SilcClientEntry remote_client;
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcPacket packet = state_context;
+  SilcMessagePayload payload = NULL;
+  SilcClientID remote_id;
+  SilcClientEntry remote_client = NULL;
   SilcMessageFlags flags;
   unsigned char *message;
   SilcUInt32 message_len;
-  SilcCipher cipher = NULL;
-  SilcHmac hmac = NULL;
+  SilcClientPrivateMessageWait pmw;
 
-  if (packet->src_id_type != SILC_ID_CLIENT)
-    goto out;
+  SILC_LOG_DEBUG(("Received private message"));
 
-  remote_id = silc_id_str2id(packet->src_id, packet->src_id_len, 
-                            SILC_ID_CLIENT);
-  if (!remote_id)
-    goto out;
-
-  /* Check whether we know this client already */
-  remote_client = silc_client_get_client_by_id(client, conn, remote_id);
-  if (!remote_client || !remote_client->nickname) {
-    if (remote_client) {
-      if (remote_client->status & SILC_CLIENT_STATUS_RESOLVING) {
-       remote_client->status &= ~SILC_CLIENT_STATUS_RESOLVING;
-       goto out;
-      }
-      remote_client->status |= SILC_CLIENT_STATUS_RESOLVING;
-      remote_client->resolve_cmd_ident = conn->cmd_ident + 1;
-    }
+  if (packet->src_id_type != SILC_ID_CLIENT) {
+    /** Invalid packet */
+    silc_fsm_next(fsm, silc_client_private_message_error);
+    return SILC_FSM_CONTINUE;
+  }
 
-    /* Resolve the client info */
-    silc_client_get_client_by_id_resolve(client, conn, remote_id, NULL,
-                                        silc_client_private_message_cb,
-                                        silc_packet_context_dup(packet));
-    return;
+  if (!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_private_message_error);
+    return SILC_FSM_CONTINUE;
   }
 
-  cipher = remote_client->receive_key;
-  hmac = remote_client->hmac_receive;
-  if (packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY && !cipher && !hmac) {
-    silc_free(remote_id);
-    return;
+  /* Check whether we know this client already */
+  remote_client = silc_client_get_client_by_id(client, conn, &remote_id);
+  if (!remote_client || !remote_client->nickname[0]) {
+    /** Resolve client info */
+    silc_client_unref_client(client, conn, remote_client);
+    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
+                                        client, conn, &remote_id, NULL,
+                                        silc_client_private_message_resolved,
+                                        fsm));
+    /* NOT REACHED */
   }
 
+  if (packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY &&
+      !remote_client->internal.receive_key &&
+      !remote_client->internal.hmac_receive)
+    goto out;
+
   /* Parse the payload and decrypt it also if private message key is set */
-  payload = silc_private_message_payload_parse(packet->buffer->data,
-                                              packet->buffer->len,
-                                              cipher, hmac);
-  if (!payload) {
-    silc_free(remote_id);
-    return;
-  }
+  payload =
+    silc_message_payload_parse(silc_buffer_datalen(&packet->buffer),
+                              TRUE, !remote_client->internal.generated,
+                              remote_client->internal.receive_key,
+                              remote_client->internal.hmac_receive,
+                              NULL, FALSE, NULL);
+  if (!payload)
+    goto out;
 
-  flags = silc_private_message_get_flags(payload);
+#if 0 /* We need to rethink this.  This doesn't work with multiple
+        waiters, and performance is suboptimal. */
+  /* Check if some thread is waiting for this private message */
+  silc_mutex_lock(conn->internal->lock);
+  if (conn->internal->privmsg_wait &&
+      silc_hash_table_find_ext(conn->internal->privmsg_wait,
+                              &remote_client->id, NULL, (void **)&pmw,
+                              NULL, NULL, silc_hash_id_compare_full,
+                              SILC_32_TO_PTR(SILC_ID_CLIENT))) {
+    /* Signal that message was received */
+    silc_mutex_unlock(conn->internal->lock);
+    silc_mutex_lock(pmw->wait_lock);
+    if (!pmw->stopped) {
+      silc_dlist_add(pmw->message_queue, payload);
+      silc_cond_broadcast(pmw->wait_cond);
+      silc_mutex_unlock(pmw->wait_lock);
+      silc_packet_free(packet);
+      goto out;
+    }
+    silc_mutex_unlock(pmw->wait_lock);
+  } else
+    silc_mutex_unlock(conn->internal->lock);
+#endif /* 0 */
 
   /* Pass the private message to application */
-  message = silc_private_message_get_message(payload, &message_len);
-  client->internal->ops->private_message(client, conn, remote_client, flags,
-                                        message, message_len);
+  flags = silc_message_get_flags(payload);
+  message = silc_message_get_data(payload, &message_len);
+  client->internal->ops->private_message(client, conn, remote_client, payload,
+                                        flags, message, message_len);
 
   /* See if we are away (gone). If we are away we will reply to the
      sender with the set away message. */
   if (conn->internal->away && conn->internal->away->away &&
       !(flags & SILC_MESSAGE_FLAG_NOREPLY)) {
     /* If it's me, ignore */
-    if (SILC_ID_CLIENT_COMPARE(remote_id, conn->local_id))
+    if (SILC_ID_CLIENT_COMPARE(&remote_id, conn->local_id))
       goto out;
 
     /* Send the away message */
     silc_client_send_private_message(client, conn, remote_client,
                                     SILC_MESSAGE_FLAG_AUTOREPLY |
-                                    SILC_MESSAGE_FLAG_NOREPLY,
+                                    SILC_MESSAGE_FLAG_NOREPLY, NULL,
                                     conn->internal->away->away,
-                                    strlen(conn->internal->away->away), TRUE);
+                                    strlen(conn->internal->away->away));
   }
 
  out:
+  /** Packet processed */
+  silc_packet_free(packet);
+  silc_client_unref_client(client, conn, remote_client);
   if (payload)
-    silc_private_message_payload_free(payload);
-  silc_free(remote_id);
+    silc_message_payload_free(payload);
+  return SILC_FSM_FINISH;
+}
+
+/* Private message error. */
+
+SILC_FSM_STATE(silc_client_private_message_error)
+{
+  SilcPacket packet = state_context;
+  silc_packet_free(packet);
+  return SILC_FSM_FINISH;
+}
+
+#if 0 /* XXX we need to rethink this */
+/* Initialize private message waiting in a thread. */
+
+void *silc_client_private_message_wait_init(SilcClientConnection conn,
+                                           SilcClientEntry client_entry)
+{
+  SilcClientPrivateMessageWait pmw;
+
+  pmw = silc_calloc(1, sizeof(*pmw));
+  if (!pmw)
+    return NULL;
+
+  pmw->message_queue = silc_dlist_init();
+  if (!pmw->message_queue) {
+    silc_free(pmw);
+    return NULL;
+  }
+
+  /* Allocate mutex and conditional variable */
+  if (!silc_mutex_alloc(&pmw->wait_lock)) {
+    silc_dlist_uninit(pmw->message_queue);
+    silc_free(pmw);
+    return NULL;
+  }
+  if (!silc_cond_alloc(&pmw->wait_cond)) {
+    silc_dlist_uninit(pmw->message_queue);
+    silc_mutex_free(pmw->wait_lock);
+    silc_free(pmw);
+    return NULL;
+  }
+
+  silc_mutex_lock(conn->internal->lock);
+
+  /* Allocate waiting hash table */
+  if (!conn->internal->privmsg_wait) {
+    conn->internal->privmsg_wait =
+      silc_hash_table_alloc(0, silc_hash_id,
+                           SILC_32_TO_PTR(SILC_ID_CLIENT),
+                           silc_hash_id_compare,
+                           SILC_32_TO_PTR(SILC_ID_CLIENT), NULL, NULL, TRUE);
+    if (!conn->internal->privmsg_wait) {
+      silc_mutex_unlock(conn->internal->lock);
+      silc_dlist_uninit(pmw->message_queue);
+      silc_mutex_free(pmw->wait_lock);
+      silc_cond_free(pmw->wait_cond);
+      silc_free(pmw);
+      return NULL;
+    }
+  }
+
+  /* Add to waiting hash table */
+  silc_hash_table_add(conn->internal->privmsg_wait, client_entry->id, pmw);
+
+  silc_mutex_unlock(conn->internal->lock);
+
+  return (void *)pmw;
+}
+
+/* Uninitialize private message waiting. */
+
+void silc_client_private_message_wait_uninit(SilcClientConnection conn,
+                                            SilcClientEntry client_entry,
+                                            void *waiter)
+{
+  SilcClientPrivateMessageWait pmw = waiter;
+  SilcMessagePayload payload;
+
+  /* Signal any threads to stop waiting */
+  silc_mutex_lock(pmw->wait_lock);
+  pmw->stopped = TRUE;
+  silc_cond_broadcast(pmw->wait_cond);
+  silc_mutex_unlock(pmw->wait_lock);
+
+  /* Re-acquire lock and free resources */
+  silc_mutex_lock(pmw->wait_lock);
+
+  /* Free any remaining message */
+  silc_dlist_start(pmw->message_queue);
+  while ((payload = silc_dlist_get(pmw->message_queue)))
+    silc_message_payload_free(payload);
+
+  silc_dlist_uninit(pmw->message_queue);
+  silc_cond_free(pmw->wait_cond);
+  silc_mutex_unlock(pmw->wait_lock);
+  silc_mutex_free(pmw->wait_lock);
+
+  silc_mutex_lock(conn->internal->lock);
+  silc_hash_table_del_by_context(conn->internal->privmsg_wait,
+                                client_entry->id, pmw);
+  silc_mutex_unlock(conn->internal->lock);
+
+  silc_free(pmw);
+}
+
+/* Blocks the calling process or thread until a private message has been
+   received from the specified client. */
+
+SilcBool silc_client_private_message_wait(SilcClientConnection conn,
+                                         SilcClientEntry client_entry,
+                                         void *waiter,
+                                         SilcMessagePayload *payload)
+{
+  SilcClientPrivateMessageWait pmw = waiter;
+  SilcPacket packet;
+
+  silc_mutex_lock(pmw->wait_lock);
+
+  /* Wait here until private message has been received */
+  while (silc_dlist_count(pmw->message_queue) == 0) {
+    if (pmw->stopped) {
+      silc_mutex_unlock(pmw->wait_lock);
+      return FALSE;
+    }
+    silc_cond_wait(pmw->wait_cond, pmw->wait_lock);
+  }
+
+  /* Return message */
+  silc_dlist_start(pmw->message_queue);
+  *payload = silc_dlist_get(pmw->message_queue);
+  silc_dlist_del(pmw->message_queue, *payload);
+
+  silc_mutex_unlock(pmw->wait_lock);
+
+  return TRUE;
 }
+#endif /* 0 */
+
+/*************************** Private Message Key ****************************/
 
-/* Function that actually employes the received private message key */
+/* Client resolving callback.  Here we simply mark that we are the responder
+   side of this private message key request.  */
 
 static void silc_client_private_message_key_cb(SilcClient client,
                                               SilcClientConnection conn,
-                                              SilcClientEntry *clients,
-                                              SilcUInt32 clients_count,
+                                              SilcStatus status,
+                                              SilcDList clients,
                                               void *context)
 {
-  SilcPacketContext *packet = (SilcPacketContext *)context;
-  unsigned char *key;
-  SilcUInt16 key_len;
+  SilcPacket packet = context;
   unsigned char *cipher = NULL, *hmac = NULL;
+  SilcClientEntry client_entry;
   int ret;
 
-  if (!clients)
-    goto out;
+  if (!clients) {
+    silc_packet_free(packet);
+    return;
+  }
 
   /* Parse the private message key payload */
-  ret = silc_buffer_unformat(packet->buffer,
-                            SILC_STR_UI16_NSTRING(&key, &key_len),
+  ret = silc_buffer_unformat(&packet->buffer,
                             SILC_STR_UI16_STRING_ALLOC(&cipher),
                             SILC_STR_UI16_STRING_ALLOC(&hmac),
                             SILC_STR_END);
   if (!ret)
     goto out;
 
-  if (key_len > packet->buffer->len)
-    goto out;
+  /* Mark that we are responder */
+  client_entry = silc_dlist_get(clients);
+  client_entry->internal.prv_resp = TRUE;
 
-  /* Now take the key in use */
-  if (!silc_client_add_private_message_key(client, conn, clients[0],
-                                          cipher, hmac, key, key_len,
-                                          FALSE, TRUE))
-    goto out;
-
-  /* Print some info for application */
-  client->internal->ops->say(
-                    client, conn, SILC_CLIENT_MESSAGE_AUDIT, 
-                    "Received private message key from %s%s%s %s%s%s", 
-                    clients[0]->nickname,
-                    clients[0]->server ? "@" : "",
-                    clients[0]->server ? clients[0]->server : "",
-                    clients[0]->username ? "(" : "",
-                    clients[0]->username ? clients[0]->username : "",
-                    clients[0]->username ? ")" : "");
+  /* XXX we should notify application that remote wants to set up the
+     static key */
 
  out:
   silc_free(cipher);
   silc_free(hmac);
-  silc_packet_context_free(packet);
+  silc_packet_free(packet);
 }
 
-/* Processes incoming Private Message Key payload. The libary always
-   accepts the key and takes it into use. */
+/* Processes incoming Private Message Key payload to indicate that the
+   sender whishes to set up a static private message key. */
 
-void silc_client_private_message_key(SilcClient client,
-                                    SilcSocketConnection sock,
-                                    SilcPacketContext *packet)
+SILC_FSM_STATE(silc_client_private_message_key)
 {
-  SilcClientID *remote_id;
-
-  if (packet->src_id_type != SILC_ID_CLIENT)
-    return;
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcPacket packet = state_context;
+  SilcClientID remote_id;
+
+  if (packet->src_id_type != SILC_ID_CLIENT) {
+    silc_packet_free(packet);
+    return SILC_FSM_FINISH;
+  }
 
-  remote_id = silc_id_str2id(packet->src_id, packet->src_id_len, 
-                            SILC_ID_CLIENT);
-  if (!remote_id)
-    return;
+  if (!silc_id_str2id(packet->src_id, packet->src_id_len, SILC_ID_CLIENT,
+                     &remote_id, sizeof(remote_id))) {
+    silc_packet_free(packet);
+    return SILC_FSM_FINISH;
+  }
 
-  silc_client_get_client_by_id_resolve(client, sock->user_data, remote_id,
-                                      NULL,
+  /* Always resolve the remote client.  The actual packet is processed
+     in the resolving callback. */
+  SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
+                                      client, conn, &remote_id, NULL,
                                       silc_client_private_message_key_cb,
-                                      silc_packet_context_dup(packet));
-  silc_free(remote_id);
+                                      fsm));
 }
 
 /* Adds private message key to the client library. The key will be used to
@@ -326,30 +430,32 @@ void silc_client_private_message_key(SilcClient client,
 
    It is not necessary to set key for normal private message usage. If the
    key is not set then the private messages are encrypted using normal
-   session keys. Setting the private key, however, increases the security. 
+   session keys. Setting the private key, however, increases the security.
 
    Returns FALSE if the key is already set for the `client_entry', TRUE
    otherwise. */
 
-bool silc_client_add_private_message_key(SilcClient client,
-                                        SilcClientConnection conn,
-                                        SilcClientEntry client_entry,
-                                        const char *cipher,
-                                        const char *hmac,
-                                        unsigned char *key,
-                                        SilcUInt32 key_len,
-                                        bool generate_key,
-                                        bool responder)
+SilcBool silc_client_add_private_message_key(SilcClient client,
+                                            SilcClientConnection conn,
+                                            SilcClientEntry client_entry,
+                                            const char *cipher,
+                                            const char *hmac,
+                                            unsigned char *key,
+                                            SilcUInt32 key_len,
+                                            SilcBool generate_key,
+                                            SilcBool responder)
 {
   unsigned char private_key[32];
   SilcUInt32 len;
+  SilcSKEKeyMaterial keymat;
+  SilcBool ret;
   int i;
-  SilcSKEKeyMaterial *keymat;
 
-  assert(client && client_entry);
+  if (!client || !client_entry)
+    return FALSE;
 
   /* Return FALSE if key already set */
-  if (client_entry->send_key && client_entry->receive_key)
+  if (client_entry->internal.send_key && client_entry->internal.receive_key)
     return FALSE;
 
   if (!cipher)
@@ -370,55 +476,30 @@ bool silc_client_add_private_message_key(SilcClient client,
       private_key[i] = silc_rng_get_byte_fast(client->rng);
     key = private_key;
     key_len = len;
-    client_entry->generated = TRUE;
   }
 
   /* Save the key */
-  client_entry->key = silc_memdup(key, key_len);
-  client_entry->key_len = key_len;
+  client_entry->internal.key = silc_memdup(key, key_len);
+  client_entry->internal.key_len = key_len;
 
   /* Produce the key material as the protocol defines */
-  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)
+  keymat = silc_ske_process_key_material_data(key, key_len, 16, 256, 16,
+                                             conn->internal->sha1hash);
+  if (!keymat)
     return FALSE;
 
-  /* Allocate the cipher and HMAC */
-  silc_cipher_alloc(cipher, &client_entry->send_key);
-  silc_cipher_alloc(cipher, &client_entry->receive_key);
-  silc_hmac_alloc(hmac, NULL, &client_entry->hmac_send);
-  silc_hmac_alloc(hmac, NULL, &client_entry->hmac_receive);
+  /* Set the key into use */
+  ret = silc_client_add_private_message_key_ske(client, conn, client_entry,
+                                               cipher, hmac, keymat,
+                                               responder);
 
-  /* Set the keys */
-  if (responder == TRUE) {
-    silc_cipher_set_key(client_entry->send_key, keymat->receive_enc_key,
-                       keymat->enc_key_len);
-    silc_cipher_set_iv(client_entry->send_key, keymat->receive_iv);
-    silc_cipher_set_key(client_entry->receive_key, keymat->send_enc_key,
-                       keymat->enc_key_len);
-    silc_cipher_set_iv(client_entry->receive_key, keymat->send_iv);
-    silc_hmac_set_key(client_entry->hmac_send, keymat->receive_hmac_key,
-                     keymat->hmac_key_len);
-    silc_hmac_set_key(client_entry->hmac_receive, keymat->send_hmac_key,
-                     keymat->hmac_key_len);
-  } else {
-    silc_cipher_set_key(client_entry->send_key, keymat->send_enc_key,
-                       keymat->enc_key_len);
-    silc_cipher_set_iv(client_entry->send_key, keymat->send_iv);
-    silc_cipher_set_key(client_entry->receive_key, keymat->receive_enc_key,
-                       keymat->enc_key_len);
-    silc_cipher_set_iv(client_entry->receive_key, keymat->receive_iv);
-    silc_hmac_set_key(client_entry->hmac_send, keymat->send_hmac_key,
-                     keymat->hmac_key_len);
-    silc_hmac_set_key(client_entry->hmac_receive, keymat->receive_hmac_key,
-                     keymat->hmac_key_len);
-  }
+  if (!generate_key)
+    client_entry->internal.generated = FALSE;
 
   /* Free the key material */
   silc_ske_free_key_material(keymat);
 
-  return TRUE;
+  return ret;
 }
 
 /* Same as above but takes the key material from the SKE key material
@@ -427,18 +508,19 @@ bool silc_client_add_private_message_key(SilcClient client,
    `cipher' and `hmac' SHOULD be provided as it is negotiated also in
    the SKE protocol. */
 
-bool silc_client_add_private_message_key_ske(SilcClient client,
-                                            SilcClientConnection conn,
-                                            SilcClientEntry client_entry,
-                                            const char *cipher,
-                                            const char *hmac,
-                                            SilcSKEKeyMaterial *key,
-                                            bool responder)
+SilcBool silc_client_add_private_message_key_ske(SilcClient client,
+                                                SilcClientConnection conn,
+                                                SilcClientEntry client_entry,
+                                                const char *cipher,
+                                                const char *hmac,
+                                                SilcSKEKeyMaterial keymat,
+                                                SilcBool responder)
 {
-  assert(client && client_entry);
+  if (!client || !client_entry)
+    return FALSE;
 
   /* Return FALSE if key already set */
-  if (client_entry->send_key && client_entry->receive_key)
+  if (client_entry->internal.send_key && client_entry->internal.receive_key)
     return FALSE;
 
   if (!cipher)
@@ -452,121 +534,115 @@ bool silc_client_add_private_message_key_ske(SilcClient client,
   if (!silc_hmac_is_supported(hmac))
     return FALSE;
 
+  client_entry->internal.generated = TRUE;
+
   /* Allocate the cipher and HMAC */
-  silc_cipher_alloc(cipher, &client_entry->send_key);
-  silc_cipher_alloc(cipher, &client_entry->receive_key);
-  silc_hmac_alloc(hmac, NULL, &client_entry->hmac_send);
-  silc_hmac_alloc(hmac, NULL, &client_entry->hmac_receive);
+  if (!silc_cipher_alloc(cipher, &client_entry->internal.send_key))
+    return FALSE;
+  if (!silc_cipher_alloc(cipher, &client_entry->internal.receive_key))
+    return FALSE;
+  if (!silc_hmac_alloc(hmac, NULL, &client_entry->internal.hmac_send))
+    return FALSE;
+  if (!silc_hmac_alloc(hmac, NULL, &client_entry->internal.hmac_receive))
+    return FALSE;
 
   /* Set the keys */
   if (responder == TRUE) {
-    silc_cipher_set_key(client_entry->send_key, key->receive_enc_key,
-                       key->enc_key_len);
-    silc_cipher_set_iv(client_entry->send_key, key->receive_iv);
-    silc_cipher_set_key(client_entry->receive_key, key->send_enc_key,
-                       key->enc_key_len);
-    silc_cipher_set_iv(client_entry->receive_key, key->send_iv);
-    silc_hmac_set_key(client_entry->hmac_send, key->receive_hmac_key,
-                     key->hmac_key_len);
-    silc_hmac_set_key(client_entry->hmac_receive, key->send_hmac_key,
-                     key->hmac_key_len);
+    silc_cipher_set_key(client_entry->internal.send_key,
+                       keymat->receive_enc_key,
+                       keymat->enc_key_len);
+    silc_cipher_set_iv(client_entry->internal.send_key,
+                      keymat->receive_iv);
+    silc_cipher_set_key(client_entry->internal.receive_key,
+                       keymat->send_enc_key,
+                       keymat->enc_key_len);
+    silc_cipher_set_iv(client_entry->internal.receive_key, keymat->send_iv);
+    silc_hmac_set_key(client_entry->internal.hmac_send,
+                     keymat->receive_hmac_key,
+                     keymat->hmac_key_len);
+    silc_hmac_set_key(client_entry->internal.hmac_receive,
+                     keymat->send_hmac_key,
+                     keymat->hmac_key_len);
   } else {
-    silc_cipher_set_key(client_entry->send_key, key->send_enc_key,
-                       key->enc_key_len);
-    silc_cipher_set_iv(client_entry->send_key, key->send_iv);
-    silc_cipher_set_key(client_entry->receive_key, key->receive_enc_key,
-                       key->enc_key_len);
-    silc_cipher_set_iv(client_entry->receive_key, key->receive_iv);
-    silc_hmac_set_key(client_entry->hmac_send, key->send_hmac_key,
-                     key->hmac_key_len);
-    silc_hmac_set_key(client_entry->hmac_receive, key->receive_hmac_key,
-                     key->hmac_key_len);
+    silc_cipher_set_key(client_entry->internal.send_key,
+                       keymat->send_enc_key,
+                       keymat->enc_key_len);
+    silc_cipher_set_iv(client_entry->internal.send_key,
+                      keymat->send_iv);
+    silc_cipher_set_key(client_entry->internal.receive_key,
+                       keymat->receive_enc_key,
+                       keymat->enc_key_len);
+    silc_cipher_set_iv(client_entry->internal.receive_key, keymat->receive_iv);
+    silc_hmac_set_key(client_entry->internal.hmac_send,
+                     keymat->send_hmac_key,
+                     keymat->hmac_key_len);
+    silc_hmac_set_key(client_entry->internal.hmac_receive,
+                     keymat->receive_hmac_key,
+                     keymat->hmac_key_len);
   }
 
   return TRUE;
 }
 
-/* Sends private message key payload to the remote client indicated by
-   the `client_entry'. If the `force_send' is TRUE the packet is sent
-   immediately. Returns FALSE if error occurs, TRUE otherwise. The
-   application should call this function after setting the key to the
-   client.
+/* Sends private message key indicator.  The sender of this packet is
+   going to be the initiator, if and when, the users set up a static
+   private message key (not Key Agreement). */
 
-   Note that the key sent using this function is sent to the remote client
-   through the SILC network. The packet is protected using normal session
-   keys. */
-
-bool silc_client_send_private_message_key(SilcClient client,
-                                        SilcClientConnection conn,
-                                        SilcClientEntry client_entry,
-                                        bool force_send)
+SilcBool
+silc_client_send_private_message_key_request(SilcClient client,
+                                            SilcClientConnection conn,
+                                            SilcClientEntry client_entry)
 {
-  SilcSocketConnection sock;
-  SilcBuffer buffer;
-  int cipher_len, hmac_len;
   const char *cipher, *hmac;
 
-  assert(client && conn && client_entry);
+  if (!client || !conn || !client_entry)
+    return FALSE;
 
-  sock = conn->sock;
-  if (!client_entry->send_key || !client_entry->key)
+  if (!client_entry->internal.send_key && !client_entry->internal.receive_key)
     return FALSE;
 
-  SILC_LOG_DEBUG(("Sending private message key"));
-
-  cipher = silc_cipher_get_name(client_entry->send_key);
-  cipher_len = strlen(cipher);
-  hmac = silc_hmac_get_name(client_entry->hmac_send);
-  hmac_len = strlen(hmac);
-
-  /* Create private message key payload */
-  buffer = silc_buffer_alloc(2 + client_entry->key_len);
-  silc_buffer_pull_tail(buffer, SILC_BUFFER_END(buffer));
-  silc_buffer_format(buffer,
-                    SILC_STR_UI_SHORT(client_entry->key_len),
-                    SILC_STR_UI_XNSTRING(client_entry->key, 
-                                         client_entry->key_len),
-                    SILC_STR_UI_SHORT(cipher_len),
-                    SILC_STR_UI_XNSTRING(cipher,
-                                         cipher_len),
-                    SILC_STR_UI_SHORT(hmac_len),
-                    SILC_STR_UI_XNSTRING(hmac,
-                                         hmac_len),
-                    SILC_STR_END);
+  SILC_LOG_DEBUG(("Sending private message key indicator"));
 
-  /* Send the packet */
-  silc_client_packet_send(client, sock, SILC_PACKET_PRIVATE_MESSAGE_KEY,
-                         client_entry->id, SILC_ID_CLIENT, NULL, NULL,
-                         buffer->data, buffer->len, force_send);
-  silc_free(buffer);
+  cipher = silc_cipher_get_name(client_entry->internal.send_key);
+  hmac = silc_hmac_get_name(client_entry->internal.hmac_send);
 
-  return TRUE;
+  /* Send the packet */
+  return silc_packet_send_va_ext(conn->stream,
+                                SILC_PACKET_PRIVATE_MESSAGE_KEY,
+                                0, 0, NULL, SILC_ID_CLIENT,
+                                &client_entry->id, NULL, NULL,
+                                SILC_STR_UI_SHORT(strlen(cipher)),
+                                SILC_STR_DATA(cipher, strlen(cipher)),
+                                SILC_STR_UI_SHORT(strlen(hmac)),
+                                SILC_STR_DATA(hmac, strlen(hmac)),
+                                SILC_STR_END);
 }
 
 /* Removes the private message from the library. The key won't be used
    after this to protect the private messages with the remote `client_entry'
    client. Returns FALSE on error, TRUE otherwise. */
 
-bool silc_client_del_private_message_key(SilcClient client,
-                                       SilcClientConnection conn,
-                                       SilcClientEntry client_entry)
+SilcBool silc_client_del_private_message_key(SilcClient client,
+                                            SilcClientConnection conn,
+                                            SilcClientEntry client_entry)
 {
-  assert(client && client_entry);
+  if (!client || !client_entry)
+    return FALSE;
 
-  if (!client_entry->send_key && !client_entry->receive_key)
+  if (!client_entry->internal.send_key && !client_entry->internal.receive_key)
     return FALSE;
 
-  silc_cipher_free(client_entry->send_key);
-  silc_cipher_free(client_entry->receive_key);
+  silc_cipher_free(client_entry->internal.send_key);
+  silc_cipher_free(client_entry->internal.receive_key);
 
-  if (client_entry->key) {
-    memset(client_entry->key, 0, client_entry->key_len);
-    silc_free(client_entry->key);
+  if (client_entry->internal.key) {
+    memset(client_entry->internal.key, 0, client_entry->internal.key_len);
+    silc_free(client_entry->internal.key);
   }
 
-  client_entry->send_key = NULL;
-  client_entry->receive_key = NULL;
-  client_entry->key = NULL;
+  client_entry->internal.send_key = NULL;
+  client_entry->internal.receive_key = NULL;
+  client_entry->internal.key = NULL;
 
   return TRUE;
 }
@@ -574,7 +650,7 @@ bool silc_client_del_private_message_key(SilcClient client,
 /* Returns array of set private message keys associated to the connection
    `conn'. Returns allocated SilcPrivateMessageKeys array and the array
    count to the `key_count' argument. The array must be freed by the caller
-   by calling the silc_client_free_private_message_keys function. Note: 
+   by calling the silc_client_free_private_message_keys function. Note:
    the keys returned in the array is in raw format. It might not be desired
    to show the keys as is. The application might choose not to show the keys
    at all or to show the fingerprints of the keys. */
@@ -586,36 +662,33 @@ silc_client_list_private_message_keys(SilcClient client,
 {
   SilcPrivateMessageKeys keys;
   SilcUInt32 count = 0;
+  SilcList list;
   SilcIDCacheEntry id_cache;
-  SilcIDCacheList list;
   SilcClientEntry entry;
 
-  assert(client && conn);
+  if (!client || !conn)
+    return NULL;
 
   if (!silc_idcache_get_all(conn->internal->client_cache, &list))
     return NULL;
 
-  if (!silc_idcache_list_count(list)) {
-    silc_idcache_list_free(list);
+  keys = silc_calloc(silc_list_count(list), sizeof(*keys));
+  if (!keys)
     return NULL;
-  }
-
-  keys = silc_calloc(silc_idcache_list_count(list), sizeof(*keys));
 
-  silc_idcache_list_first(list, &id_cache);
-  while (id_cache) {
-    entry = (SilcClientEntry)id_cache->context;
-
-    if (entry->send_key) {
+  silc_list_start(list);
+  while ((id_cache = silc_list_get(list))) {
+    entry = id_cache->context;
+    if (entry->internal.send_key) {
       keys[count].client_entry = entry;
-      keys[count].cipher = (char *)silc_cipher_get_name(entry->send_key);
-      keys[count].key = entry->generated == FALSE ? entry->key : NULL;
-      keys[count].key_len = entry->generated == FALSE ? entry->key_len : 0;
+      keys[count].cipher = (char *)silc_cipher_get_name(entry->internal.
+                                                       send_key);
+      keys[count].key = (entry->internal.generated == FALSE ?
+                        entry->internal.key : NULL);
+      keys[count].key_len = (entry->internal.generated == FALSE ?
+                            entry->internal.key_len : 0);
       count++;
     }
-
-    if (!silc_idcache_list_next(list, &id_cache))
-      break;
   }
 
   if (key_count)
@@ -638,7 +711,7 @@ void silc_client_free_private_message_keys(SilcPrivateMessageKeys keys,
    to anyone who sends private message.  The `message' will be sent
    automatically back to the the client who send private message.  If
    away message is already set this replaces the old message with the
-   new one.  If `message' is NULL the old away message is removed. 
+   new one.  If `message' is NULL the old away message is removed.
    The sender may freely free the memory of the `message'. */
 
 void silc_client_set_away_message(SilcClient client,