updates.
[silc.git] / lib / silcclient / client.c
index 66961bf82796ad075138633bf9d40ae44cb2ac67..f870a2a35d1dc7b773d5488e8c3eda5ac3203808 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
 
-  Copyright (C) 1997 - 2000 Pekka Riikonen
+  Copyright (C) 1997 - 2001 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$ */
 
 #include "clientlibincludes.h"
+#include "client_internal.h"
 
 /* Static task callback prototypes */
 SILC_TASK_CALLBACK(silc_client_connect_to_server_start);
 SILC_TASK_CALLBACK(silc_client_connect_to_server_second);
 SILC_TASK_CALLBACK(silc_client_connect_to_server_final);
-SILC_TASK_CALLBACK(silc_client_packet_process);
 SILC_TASK_CALLBACK(silc_client_packet_parse_real);
+SILC_TASK_CALLBACK(silc_client_rekey_callback);
+SILC_TASK_CALLBACK(silc_client_rekey_final);
 
 static void silc_client_packet_parse(SilcPacketParserContext *parser_context);
 static void silc_client_packet_parse_type(SilcClient client, 
@@ -49,18 +51,21 @@ SilcClient silc_client_alloc(SilcClientOperations *ops, void *application)
   return new_client;
 }
 
-/* Free's client object */
+/* Frees client object and its internals. */
 
 void silc_client_free(SilcClient client)
 {
   if (client) {
+    if (client->rng)
+      silc_rng_free(client->rng);
+
     silc_free(client);
   }
 }
 
 /* Initializes the client. This makes all the necessary steps to make
    the client ready to be run. One must call silc_client_run to run the
-   client. */
+   client. Returns FALSE if error occured, TRUE otherwise. */
 
 int silc_client_init(SilcClient client)
 {
@@ -76,7 +81,7 @@ int silc_client_init(SilcClient client)
   /* Initialize random number generator */
   client->rng = silc_rng_alloc();
   silc_rng_init(client->rng);
-  silc_math_primegen_init(); /* XXX */
+  silc_rng_global_init(client->rng);
 
   /* Register protocols */
   silc_client_protocols_register();
@@ -106,7 +111,8 @@ void silc_client_stop(SilcClient client)
   SILC_LOG_DEBUG(("Client stopped"));
 }
 
-/* Runs the client. */
+/* Runs the client. This starts the scheduler from the utility library.
+   When this functions returns the execution of the appliation is over. */
 
 void silc_client_run(SilcClient client)
 {
@@ -121,9 +127,13 @@ void silc_client_run(SilcClient client)
    connection to the connection table and returns a pointer to it. A client
    can have multiple connections to multiple servers. Every connection must
    be added to the client using this function. User data `context' may
-   be sent as argument. */
+   be sent as argument. This function is normally used only if the 
+   application performed the connecting outside the library. The library
+   however may use this internally. */
 
 SilcClientConnection silc_client_add_connection(SilcClient client,
+                                               char *hostname,
+                                               int port,
                                                void *context)
 {
   SilcClientConnection conn;
@@ -132,11 +142,14 @@ SilcClientConnection silc_client_add_connection(SilcClient client,
   conn = silc_calloc(1, sizeof(*conn));
 
   /* Initialize ID caches */
-  conn->client_cache = silc_idcache_alloc(0);
-  conn->channel_cache = silc_idcache_alloc(0);
-  conn->server_cache = silc_idcache_alloc(0);
+  conn->client_cache = silc_idcache_alloc(0, NULL);
+  conn->channel_cache = silc_idcache_alloc(0, NULL);
+  conn->server_cache = silc_idcache_alloc(0, NULL);
   conn->client = client;
+  conn->remote_host = strdup(hostname);
+  conn->remote_port = port;
   conn->context = context;
+  conn->pending_commands = silc_dlist_init();
 
   /* Add the connection to connections table */
   for (i = 0; i < client->conns_count; i++)
@@ -153,7 +166,7 @@ SilcClientConnection silc_client_add_connection(SilcClient client,
   return conn;
 }
 
-/* Removes connection from client. */
+/* Removes connection from client. Frees all memory. */
 
 void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
 {
@@ -161,22 +174,58 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
 
   for (i = 0; i < client->conns_count; i++)
     if (client->conns[i] == conn) {
+      if (conn->pending_commands)
+       silc_dlist_uninit(conn->pending_commands);
       silc_free(conn);
       client->conns[i] = NULL;
     }
 }
 
-/* Internal context for connection process. This is needed as we
-   doing asynchronous connecting. */
-typedef struct {
-  SilcClient client;
-  SilcClientConnection conn;
-  SilcTask task;
-  int sock;
-  char *host;
-  int port;
-  int tries;
-} SilcClientInternalConnectContext;
+/* Adds listener socket to the listener sockets table. This function is
+   used to add socket objects that are listeners to the client.  This should
+   not be used to add other connection objects. */
+
+void silc_client_add_socket(SilcClient client, SilcSocketConnection sock)
+{
+  int i;
+
+  if (!client->sockets) {
+    client->sockets = silc_calloc(1, sizeof(*client->sockets));
+    client->sockets[0] = silc_socket_dup(sock);
+    client->sockets_count = 1;
+    return;
+  }
+
+  for (i = 0; i < client->sockets_count; i++) {
+    if (client->sockets[i] == NULL) {
+      client->sockets[i] = silc_socket_dup(sock);
+      return;
+    }
+  }
+
+  client->sockets = silc_realloc(client->sockets, sizeof(*client->sockets) *
+                                (client->sockets_count + 1));
+  client->sockets[client->sockets_count] = silc_socket_dup(sock);
+  client->sockets_count++;
+}
+
+/* Deletes listener socket from the listener sockets table. */
+
+void silc_client_del_socket(SilcClient client, SilcSocketConnection sock)
+{
+  int i;
+
+  if (!client->sockets)
+    return;
+
+  for (i = 0; i < client->sockets_count; i++) {
+    if (client->sockets[i] == sock) {
+      silc_socket_free(sock);
+      client->sockets[i] = NULL;
+      return;
+    }
+  }
+}
 
 static int 
 silc_client_connect_to_server_internal(SilcClientInternalConnectContext *ctx)
@@ -208,7 +257,10 @@ silc_client_connect_to_server_internal(SilcClientInternalConnectContext *ctx)
 /* Connects to remote server. This is the main routine used to connect
    to SILC server. Returns -1 on error and the created socket otherwise. 
    The `context' is user context that is saved into the SilcClientConnection
-   that is created after the connection is created. */
+   that is created after the connection is created. Note that application
+   may handle the connecting process outside the library. If this is the
+   case then this function is not used at all. When the connecting is
+   done the `connect' client operation is called. */
 
 int silc_client_connect_to_server(SilcClient client, int port,
                                  char *host, void *context)
@@ -220,9 +272,7 @@ int silc_client_connect_to_server(SilcClient client, int port,
   SILC_LOG_DEBUG(("Connecting to port %d of server %s",
                  port, host));
 
-  conn = silc_client_add_connection(client, context);
-  conn->remote_host = strdup(host);
-  conn->remote_port = port;
+  conn = silc_client_add_connection(client, host, port, context);
 
   client->ops->say(client, conn, 
                   "Connecting to port %d of server %s", port, host);
@@ -243,6 +293,65 @@ int silc_client_connect_to_server(SilcClient client, int port,
   return sock;
 }
 
+/* Start SILC Key Exchange (SKE) protocol to negotiate shared secret
+   key material between client and server.  This function can be called
+   directly if application is performing its own connecting and does not
+   use the connecting provided by this library. This function is normally
+   used only if the application performed the connecting outside the library.
+   The library however may use this internally. */
+
+int silc_client_start_key_exchange(SilcClient client,
+                                  SilcClientConnection conn,
+                                   int fd)
+{
+  SilcProtocol protocol;
+  SilcClientKEInternalContext *proto_ctx;
+  void *context;
+
+  /* Allocate new socket connection object */
+  silc_socket_alloc(fd, SILC_SOCKET_TYPE_SERVER, (void *)conn, &conn->sock);
+
+  conn->nickname = strdup(client->username);
+  conn->sock->hostname = conn->remote_host;
+  conn->sock->ip = strdup(conn->remote_host);
+  conn->sock->port = conn->remote_port;
+
+  /* Allocate internal Key Exchange context. This is sent to the
+     protocol as context. */
+  proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
+  proto_ctx->client = (void *)client;
+  proto_ctx->sock = silc_socket_dup(conn->sock);
+  proto_ctx->rng = client->rng;
+  proto_ctx->responder = FALSE;
+  proto_ctx->send_packet = silc_client_protocol_ke_send_packet;
+  proto_ctx->verify = silc_client_protocol_ke_verify_key;
+
+  /* Perform key exchange protocol. silc_client_connect_to_server_final
+     will be called after the protocol is finished. */
+  silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, 
+                     &protocol, (void *)proto_ctx,
+                     silc_client_connect_to_server_second);
+  if (!protocol) {
+    client->ops->say(client, conn, 
+                    "Error: Could not start authentication protocol");
+    return FALSE;
+  }
+  conn->sock->protocol = protocol;
+
+  /* Register the connection for network input and output. This sets
+     that scheduler will listen for incoming packets for this connection 
+     and sets that outgoing packets may be sent to this connection as well.
+     However, this doesn't set the scheduler for outgoing traffic, it will 
+     be set separately by calling SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT,
+     later when outgoing data is available. */
+  context = (void *)client;
+  SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(fd);
+
+  /* Execute the protocol */
+  protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0);
+  return TRUE;
+}
+
 /* Start of the connection to the remote server. This is called after
    succesful TCP/IP connection has been established to the remote host. */
 
@@ -252,8 +361,6 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_start)
     (SilcClientInternalConnectContext *)context;
   SilcClient client = ctx->client;
   SilcClientConnection conn = ctx->conn;
-  SilcProtocol protocol;
-  SilcClientKEInternalContext *proto_ctx;
   int opt, opt_len = sizeof(opt);
 
   SILC_LOG_DEBUG(("Start"));
@@ -297,52 +404,10 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_start)
   silc_task_unregister(client->io_queue, ctx->task);
   silc_free(ctx);
 
-  /* Allocate new socket connection object */
-  silc_socket_alloc(fd, SILC_SOCKET_TYPE_SERVER, (void *)conn, &conn->sock);
-  if (conn->sock == NULL) {
-    client->ops->say(client, conn, 
-                    "Error: Could not allocate connection socket");
+  if (!silc_client_start_key_exchange(client, conn, fd)) {
     silc_net_close_connection(fd);
     client->ops->connect(client, conn, FALSE);
-    return;
   }
-
-  conn->nickname = strdup(client->username);
-  conn->sock->hostname = conn->remote_host;
-  conn->sock->port = conn->remote_port;
-
-  /* Allocate internal Key Exchange context. This is sent to the
-     protocol as context. */
-  proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
-  proto_ctx->client = (void *)client;
-  proto_ctx->sock = conn->sock;
-  proto_ctx->rng = client->rng;
-  proto_ctx->responder = FALSE;
-
-  /* Perform key exchange protocol. silc_client_connect_to_server_final
-     will be called after the protocol is finished. */
-  silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, 
-                     &protocol, (void *)proto_ctx,
-                     silc_client_connect_to_server_second);
-  if (!protocol) {
-    client->ops->say(client, conn, 
-                    "Error: Could not start authentication protocol");
-    client->ops->connect(client, conn, FALSE);
-    return;
-  }
-  conn->sock->protocol = protocol;
-
-  /* Register the connection for network input and output. This sets
-     that scheduler will listen for incoming packets for this connection 
-     and sets that outgoing packets may be sent to this connection as well.
-     However, this doesn't set the scheduler for outgoing traffic, it will 
-     be set separately by calling SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT,
-     later when outgoing data is available. */
-  context = (void *)client;
-  SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(fd);
-
-  /* Execute the protocol */
-  protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0);
 }
 
 /* Second part of the connecting to the server. This executed 
@@ -359,22 +424,36 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_second)
 
   SILC_LOG_DEBUG(("Start"));
 
-  if (protocol->state == SILC_PROTOCOL_STATE_ERROR) {
+  if (protocol->state == SILC_PROTOCOL_STATE_ERROR ||
+      protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
     /* Error occured during protocol */
     SILC_LOG_DEBUG(("Error during KE protocol"));
     silc_protocol_free(protocol);
+    silc_ske_free_key_material(ctx->keymat);
     if (ctx->ske)
       silc_ske_free(ctx->ske);
     if (ctx->dest_id)
       silc_free(ctx->dest_id);
     ctx->sock->protocol = NULL;
-    silc_free(ctx);
+    silc_socket_free(ctx->sock);
 
     /* Notify application of failure */
     client->ops->connect(client, ctx->sock->user_data, FALSE);
+    silc_free(ctx);
     return;
   }
 
+  /* We now have the key material as the result of the key exchange
+     protocol. Take the key material into use. Free the raw key material
+     as soon as we've set them into use. */
+  silc_client_protocol_ke_set_keys(ctx->ske, ctx->sock, ctx->keymat,
+                                  ctx->ske->prop->cipher,
+                                  ctx->ske->prop->pkcs,
+                                  ctx->ske->prop->hash,
+                                  ctx->ske->prop->hmac,
+                                  ctx->ske->prop->group);
+  silc_ske_free_key_material(ctx->keymat);
+
   /* Allocate internal context for the authentication protocol. This
      is sent as context for the protocol. */
   proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
@@ -388,18 +467,18 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_second)
   if (!client->ops->get_auth_method(client, sock->user_data, sock->hostname,
                                    sock->port, &proto_ctx->auth_meth,
                                    &proto_ctx->auth_data, 
-                                   &proto_ctx->auth_data_len))
-    {
-      /* XXX do AUTH_REQUEST resolcing with server */
-      proto_ctx->auth_meth = SILC_PROTOCOL_CONN_AUTH_NONE;
-    }
+                                   &proto_ctx->auth_data_len)) {
+    client->ops->say(client, ctx->sock->user_data, 
+                    "Could not resolve authentication method to use, "
+                    "assume no authentication");
+    proto_ctx->auth_meth = SILC_AUTH_NONE;
+  }
 
   /* Free old protocol as it is finished now */
   silc_protocol_free(protocol);
   if (ctx->packet)
-    silc_buffer_free(ctx->packet);
+    silc_packet_context_free(ctx->packet);
   silc_free(ctx);
-  /* silc_free(ctx->keymat....); */
   sock->protocol = NULL;
 
   /* Allocate the authentication protocol. This is allocated here
@@ -430,7 +509,8 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_final)
 
   SILC_LOG_DEBUG(("Start"));
 
-  if (protocol->state == SILC_PROTOCOL_STATE_ERROR) {
+  if (protocol->state == SILC_PROTOCOL_STATE_ERROR ||
+      protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
     /* Error occured during protocol */
     SILC_LOG_DEBUG(("Error during authentication protocol"));
     silc_protocol_free(protocol);
@@ -440,11 +520,12 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_final)
       silc_ske_free(ctx->ske);
     if (ctx->dest_id)
       silc_free(ctx->dest_id);
-    silc_free(ctx);
     conn->sock->protocol = NULL;
+    silc_socket_free(ctx->sock);
 
     /* Notify application of failure */
     client->ops->connect(client, ctx->sock->user_data, FALSE);
+    silc_free(ctx);
     return;
   }
 
@@ -474,19 +555,20 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_final)
   conn->remote_id_data = silc_id_id2str(ctx->dest_id, SILC_ID_SERVER);
   conn->remote_id_data_len = SILC_ID_SERVER_LEN;
 
-  client->ops->say(client, conn, "Connected to port %d of host %s",
-                  conn->remote_port, conn->remote_host);
-
-  /* Notify application of successful connection */
-  client->ops->connect(client, conn, TRUE);
+  /* Register re-key timeout */
+  conn->rekey->timeout = 3600; /* XXX hardcoded */
+  conn->rekey->context = (void *)client;
+  silc_task_register(client->timeout_queue, conn->sock->sock, 
+                    silc_client_rekey_callback,
+                    (void *)conn->sock, conn->rekey->timeout, 0,
+                    SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
 
   silc_protocol_free(protocol);
   if (ctx->auth_data)
     silc_free(ctx->auth_data);
   if (ctx->ske)
     silc_ske_free(ctx->ske);
-  if (ctx->dest_id)
-    silc_free(ctx->dest_id);
+  silc_socket_free(ctx->sock);
   silc_free(ctx);
   conn->sock->protocol = NULL;
 }
@@ -495,12 +577,18 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_final)
    is used directly only in special cases. Normal cases should use
    silc_server_packet_send. Returns < 0 on error. */
 
-static int silc_client_packet_send_real(SilcClient client,
-                                       SilcSocketConnection sock,
-                                       int force_send)
+int silc_client_packet_send_real(SilcClient client,
+                                SilcSocketConnection sock,
+                                bool force_send,
+                                bool flush)
 {
   int ret;
 
+  /* If rekey protocol is active we must assure that all packets are
+     sent through packet queue. */
+  if (flush == FALSE && SILC_CLIENT_IS_REKEY(sock))
+    force_send = FALSE;
+
   /* Send the packet */
   ret = silc_packet_send(sock, force_send);
   if (ret != -2)
@@ -523,7 +611,7 @@ static int silc_client_packet_send_real(SilcClient client,
 /* Packet processing callback. This is used to send and receive packets
    from network. This is generic task. */
 
-SILC_TASK_CALLBACK(silc_client_packet_process)
+SILC_TASK_CALLBACK_GLOBAL(silc_client_packet_process)
 {
   SilcClient client = (SilcClient)context;
   SilcSocketConnection sock = NULL;
@@ -546,7 +634,7 @@ SILC_TASK_CALLBACK(silc_client_packet_process)
       silc_buffer_push(sock->outbuf, 
                       sock->outbuf->data - sock->outbuf->head);
 
-    ret = silc_client_packet_send_real(client, sock, TRUE);
+    ret = silc_client_packet_send_real(client, sock, TRUE, TRUE);
 
     /* If returned -2 could not write to connection now, will do
        it later. */
@@ -580,28 +668,58 @@ SILC_TASK_CALLBACK(silc_client_packet_process)
       /* If connection is disconnecting already we will finally
         close the connection */
       if (SILC_IS_DISCONNECTING(sock)) {
-       client->ops->disconnect(client, conn);
-       silc_client_close_connection(client, sock);
+       if (sock == conn->sock)
+         client->ops->disconnect(client, conn);
+       silc_client_close_connection(client, sock, conn);
        return;
       }
       
-      client->ops->say(client, conn, "Connection closed: premature EOF");
-      SILC_LOG_DEBUG(("Premature EOF from connection %d", sock->sock));
-      client->ops->disconnect(client, conn);
-      silc_client_close_connection(client, sock);
+      SILC_LOG_DEBUG(("EOF from connection %d", sock->sock));
+      if (sock == conn->sock)
+       client->ops->disconnect(client, conn);
+      silc_client_close_connection(client, sock, conn);
       return;
     }
 
     /* Process the packet. This will call the parser that will then
        decrypt and parse the packet. */
-    if (!silc_packet_receive_process(sock, conn->receive_key, conn->hmac,
-                                    silc_client_packet_parse, client)) {
-      silc_buffer_clear(sock->inbuf);
-      return;
-    }
+    if (sock->type != SILC_SOCKET_TYPE_UNKNOWN)
+      silc_packet_receive_process(sock, conn->receive_key, conn->hmac_receive,
+                                 silc_client_packet_parse, client);
+    else
+      silc_packet_receive_process(sock, NULL, NULL,
+                                 silc_client_packet_parse, client);
   }
 }
 
+/* Callback function that the silc_packet_decrypt will call to make the
+   decision whether the packet is normal or special packet. We will 
+   return TRUE if it is normal and FALSE if it is special */
+
+static int silc_client_packet_decrypt_check(SilcPacketType packet_type,
+                                           SilcBuffer buffer,
+                                           SilcPacketContext *packet,
+                                           void *context)
+{
+
+  /* Packet is normal packet, if: 
+
+     1) packet is private message packet and does not have private key set
+     2) is other packet than channel message packet
+
+     all other packets are special packets 
+  */
+
+  if (packet_type == SILC_PACKET_PRIVATE_MESSAGE &&
+      (buffer->data[2] & SILC_PACKET_FLAG_PRIVMSG_KEY))
+    return FALSE;
+
+  if (packet_type != SILC_PACKET_CHANNEL_MESSAGE)
+    return TRUE;
+
+  return FALSE;
+}
+
 /* Parses whole packet, received earlier. */
 
 SILC_TASK_CALLBACK(silc_client_packet_parse_real)
@@ -617,7 +735,14 @@ SILC_TASK_CALLBACK(silc_client_packet_parse_real)
   SILC_LOG_DEBUG(("Start"));
 
   /* Decrypt the received packet */
-  ret = silc_packet_decrypt(conn->receive_key, conn->hmac, buffer, packet);
+  if (sock->type != SILC_SOCKET_TYPE_UNKNOWN)
+    ret = silc_packet_decrypt(conn->receive_key, conn->hmac_receive, 
+                             buffer, packet,
+                             silc_client_packet_decrypt_check, parse_ctx);
+  else
+    ret = silc_packet_decrypt(NULL, NULL, buffer, packet,
+                             silc_client_packet_decrypt_check, parse_ctx);
+
   if (ret < 0)
     goto out;
 
@@ -637,8 +762,8 @@ SILC_TASK_CALLBACK(silc_client_packet_parse_real)
   silc_client_packet_parse_type(client, sock, packet);
 
  out:
-  silc_buffer_clear(buffer);
-  silc_free(packet);
+  /*  silc_buffer_clear(sock->inbuf); */
+  silc_packet_context_free(packet);
   silc_free(parse_ctx);
 }
 
@@ -656,7 +781,7 @@ void silc_client_packet_parse(SilcPacketParserContext *parser_context)
                     SILC_TASK_TIMEOUT,
                     SILC_TASK_PRI_NORMAL);
 }
-  
+
 /* Parses the packet type and calls what ever routines the packet type
    requires. This is done for all incoming packets. */
 
@@ -691,11 +816,7 @@ void silc_client_packet_parse_type(SilcClient client,
      * error and call the protocol callback. This fill cause error on
      * protocol and it will call the final callback.
      */
-    if (sock->protocol) {
-      sock->protocol->state = SILC_PROTOCOL_STATE_ERROR;
-      sock->protocol->execute(client->timeout_queue, 0,
-                             sock->protocol, sock->sock, 0, 0);
-    }
+    silc_client_process_failure(client, sock, packet);
     break;
   case SILC_PACKET_REJECT:
     break;
@@ -704,7 +825,7 @@ void silc_client_packet_parse_type(SilcClient client,
     /*
      * Received notify message 
      */
-    silc_client_notify_by_server(client, sock, buffer);
+    silc_client_notify_by_server(client, sock, packet);
     break;
 
   case SILC_PACKET_ERROR:
@@ -749,13 +870,17 @@ void silc_client_packet_parse_type(SilcClient client,
     break;
 
   case SILC_PACKET_KEY_EXCHANGE:
-    if (sock->protocol) {
+    if (sock->protocol && sock->protocol->protocol && 
+       sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) {
       SilcClientKEInternalContext *proto_ctx = 
        (SilcClientKEInternalContext *)sock->protocol->context;
 
-      proto_ctx->packet = buffer;
+      proto_ctx->packet = silc_packet_context_dup(packet);
       proto_ctx->dest_id_type = packet->src_id_type;
-      proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_type);
+      proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
+                                         packet->src_id_type);
+      if (!proto_ctx->dest_id)
+       break;
 
       /* Let the protocol handle the packet */
       sock->protocol->execute(client->timeout_queue, 0,
@@ -769,28 +894,80 @@ void silc_client_packet_parse_type(SilcClient client,
     break;
 
   case SILC_PACKET_KEY_EXCHANGE_1:
-    if (sock->protocol) {
-
+    if (sock->protocol && sock->protocol->protocol && 
+       (sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE ||
+        sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_REKEY)) {
+
+      if (sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_REKEY) {
+       SilcClientRekeyInternalContext *proto_ctx = 
+         (SilcClientRekeyInternalContext *)sock->protocol->context;
+       
+       if (proto_ctx->packet)
+         silc_packet_context_free(proto_ctx->packet);
+       
+       proto_ctx->packet = silc_packet_context_dup(packet);
+
+       /* Let the protocol handle the packet */
+       sock->protocol->execute(client->timeout_queue, 0, 
+                               sock->protocol, sock->sock, 0, 0);
+      } else {
+       SilcClientKEInternalContext *proto_ctx = 
+         (SilcClientKEInternalContext *)sock->protocol->context;
+       
+       if (proto_ctx->packet)
+         silc_packet_context_free(proto_ctx->packet);
+       
+       proto_ctx->packet = silc_packet_context_dup(packet);
+       proto_ctx->dest_id_type = packet->src_id_type;
+       proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
+                                           packet->src_id_type);
+       if (!proto_ctx->dest_id)
+         break;
+       
+       /* Let the protocol handle the packet */
+       sock->protocol->execute(client->timeout_queue, 0,
+                               sock->protocol, sock->sock, 0, 0);
+      }
     } else {
       SILC_LOG_ERROR(("Received Key Exchange 1 packet but no key exchange "
                      "protocol active, packet dropped."));
     }
     break;
   case SILC_PACKET_KEY_EXCHANGE_2:
-    if (sock->protocol) {
-      SilcClientKEInternalContext *proto_ctx = 
-       (SilcClientKEInternalContext *)sock->protocol->context;
-
-      if (proto_ctx->packet)
-       silc_buffer_free(proto_ctx->packet);
-
-      proto_ctx->packet = buffer;
-      proto_ctx->dest_id_type = packet->src_id_type;
-      proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_type);
-
-      /* Let the protocol handle the packet */
-      sock->protocol->execute(client->timeout_queue, 0,
-                             sock->protocol, sock->sock, 0, 0);
+    if (sock->protocol && sock->protocol->protocol && 
+       (sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE ||
+        sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_REKEY)) {
+
+      if (sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_REKEY) {
+       SilcClientRekeyInternalContext *proto_ctx = 
+         (SilcClientRekeyInternalContext *)sock->protocol->context;
+       
+       if (proto_ctx->packet)
+         silc_packet_context_free(proto_ctx->packet);
+       
+       proto_ctx->packet = silc_packet_context_dup(packet);
+
+       /* Let the protocol handle the packet */
+       sock->protocol->execute(client->timeout_queue, 0, 
+                               sock->protocol, sock->sock, 0, 0);
+      } else {
+       SilcClientKEInternalContext *proto_ctx = 
+         (SilcClientKEInternalContext *)sock->protocol->context;
+       
+       if (proto_ctx->packet)
+         silc_packet_context_free(proto_ctx->packet);
+       
+       proto_ctx->packet = silc_packet_context_dup(packet);
+       proto_ctx->dest_id_type = packet->src_id_type;
+       proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
+                                           packet->src_id_type);
+       if (!proto_ctx->dest_id)
+         break;
+       
+       /* Let the protocol handle the packet */
+       sock->protocol->execute(client->timeout_queue, 0,
+                               sock->protocol, sock->sock, 0, 0);
+      }
     } else {
       SILC_LOG_ERROR(("Received Key Exchange 2 packet but no key exchange "
                      "protocol active, packet dropped."));
@@ -805,22 +982,67 @@ void silc_client_packet_parse_type(SilcClient client,
        * user changes nickname but in that case the new ID is received
        * as command reply and not as this packet type.
        */
-      unsigned char *id_string;
-      unsigned short id_type;
-      
-      silc_buffer_unformat(buffer,
-                          SILC_STR_UI_SHORT(&id_type),
-                          SILC_STR_UI16_STRING_ALLOC(&id_string),
-                          SILC_STR_END);
-      
-      if ((SilcIdType)id_type != SILC_ID_CLIENT)
+      SilcIDPayload idp;
+
+      idp = silc_id_payload_parse(buffer);
+      if (!idp)
+       break;
+      if (silc_id_payload_get_type(idp) != SILC_ID_CLIENT)
        break;
 
-      silc_client_receive_new_id(client, sock, id_string);
-      silc_free(id_string);
+      silc_client_receive_new_id(client, sock, idp);
+      silc_id_payload_free(idp);
       break;
     }
 
+  case SILC_PACKET_HEARTBEAT:
+    /*
+     * Received heartbeat packet
+     */
+    SILC_LOG_DEBUG(("Heartbeat packet"));
+    break;
+
+  case SILC_PACKET_KEY_AGREEMENT:
+    /*
+     * Received key agreement packet
+     */
+    SILC_LOG_DEBUG(("Key agreement packet"));
+    silc_client_key_agreement(client, sock, packet);
+    break;
+
+  case SILC_PACKET_REKEY:
+    SILC_LOG_DEBUG(("Re-key packet"));
+    /* We ignore this for now */
+    break;
+
+  case SILC_PACKET_REKEY_DONE:
+    SILC_LOG_DEBUG(("Re-key done packet"));
+
+    if (sock->protocol && sock->protocol->protocol && 
+       sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_REKEY) {
+
+      SilcClientRekeyInternalContext *proto_ctx = 
+       (SilcClientRekeyInternalContext *)sock->protocol->context;
+      
+      if (proto_ctx->packet)
+       silc_packet_context_free(proto_ctx->packet);
+      
+      proto_ctx->packet = silc_packet_context_dup(packet);
+
+      /* Let the protocol handle the packet */
+      if (proto_ctx->responder == FALSE)
+       sock->protocol->execute(client->timeout_queue, 0, 
+                               sock->protocol, sock->sock, 0, 0);
+      else
+       /* Let the protocol handle the packet */
+       sock->protocol->execute(client->timeout_queue, 0, 
+                               sock->protocol, sock->sock, 0, 100000);
+    } else {
+      SILC_LOG_ERROR(("Received Re-key done packet but no re-key "
+                     "protocol active, packet dropped."));
+    }
+    break;
+
   default:
     SILC_LOG_DEBUG(("Incorrect packet type %d, packet dropped", type));
     break;
@@ -841,7 +1063,7 @@ void silc_client_packet_send(SilcClient client,
                             SilcCipher cipher,
                             SilcHmac hmac,
                             unsigned char *data, 
-                            unsigned int data_len, 
+                            uint32 data_len, 
                             int force_send)
 {
   SilcPacketContext packetdata;
@@ -853,8 +1075,8 @@ void silc_client_packet_send(SilcClient client,
     if (!cipher && ((SilcClientConnection)sock->user_data)->send_key)
       cipher = ((SilcClientConnection)sock->user_data)->send_key;
 
-    if (!hmac && ((SilcClientConnection)sock->user_data)->hmac)
-      hmac = ((SilcClientConnection)sock->user_data)->hmac;
+    if (!hmac && ((SilcClientConnection)sock->user_data)->hmac_send)
+      hmac = ((SilcClientConnection)sock->user_data)->hmac_send;
 
     if (!dst_id && ((SilcClientConnection)sock->user_data)->remote_id) {
       dst_id = ((SilcClientConnection)sock->user_data)->remote_id;
@@ -865,7 +1087,8 @@ void silc_client_packet_send(SilcClient client,
   /* Set the packet context pointers */
   packetdata.flags = 0;
   packetdata.type = type;
-  if (((SilcClientConnection)sock->user_data)->local_id_data)
+  if (sock->user_data && 
+      ((SilcClientConnection)sock->user_data)->local_id_data)
     packetdata.src_id = ((SilcClientConnection)sock->user_data)->local_id_data;
   else 
     packetdata.src_id = silc_calloc(SILC_ID_CLIENT_LEN, sizeof(unsigned char));
@@ -880,7 +1103,6 @@ void silc_client_packet_send(SilcClient client,
     packetdata.dst_id_len = 0;
     packetdata.dst_id_type = SILC_ID_NONE;
   }
-  packetdata.rng = client->rng;
   packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + 
     packetdata.src_id_len + packetdata.dst_id_len;
   packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen);
@@ -912,79 +1134,75 @@ void silc_client_packet_send(SilcClient client,
                   sock->outbuf->data, sock->outbuf->len);
 
   /* Now actually send the packet */
-  silc_client_packet_send_real(client, sock, force_send);
+  silc_client_packet_send_real(client, sock, force_send, FALSE);
 }
 
-/* Sends packet to a 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. */
-
-void silc_client_packet_send_to_channel(SilcClient client, 
-                                       SilcSocketConnection sock,
-                                       SilcChannelEntry channel,
-                                       unsigned char *data, 
-                                       unsigned int data_len, 
-                                       int force_send)
+void silc_client_packet_send_flush(SilcClient client, 
+                                  SilcSocketConnection sock,
+                                  SilcPacketType type, 
+                                  void *dst_id,
+                                  SilcIdType dst_id_type,
+                                  SilcCipher cipher,
+                                  SilcHmac hmac,
+                                  unsigned char *data, 
+                                  uint32 data_len)
 {
-  int i;
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcBuffer payload;
   SilcPacketContext packetdata;
-  SilcCipher cipher;
-  SilcHmac hmac;
-  unsigned char *id_string;
 
-  SILC_LOG_DEBUG(("Sending packet to channel"));
+  /* First flush the packet queue. */
+  
+  if (sock->outbuf->data - sock->outbuf->head)
+    silc_buffer_push(sock->outbuf, 
+                    sock->outbuf->data - sock->outbuf->head);
+  
+  silc_client_packet_send_real(client, sock, TRUE, TRUE);
+  
+  /* The packet has been sent and now it is time to set the connection
+     back to only for input. When there is again some outgoing data 
+     available for this connection it will be set for output as well. 
+     This call clears the output setting and sets it only for input. */
+  SILC_CLIENT_SET_CONNECTION_FOR_INPUT(sock->sock);
+  SILC_UNSET_OUTBUF_PENDING(sock);
+  silc_buffer_clear(sock->outbuf);
 
-  if (!channel || !channel->key) {
-    client->ops->say(client, conn, 
-                    "Cannot talk to channel: key does not exist");
-    return;
-  }
+  SILC_LOG_DEBUG(("Sending packet, type %d", type));
 
-  /* Generate IV */
-  if (!channel->iv)
-    for (i = 0; i < 16; i++)
-      channel->iv[i] = silc_rng_get_byte(client->rng);
-  else
-    silc_hash_make(client->md5hash, channel->iv, 16, channel->iv);
+  /* Get data used in the packet sending, keys and stuff */
+  if ((!cipher || !hmac || !dst_id) && sock->user_data) {
+    if (!cipher && ((SilcClientConnection)sock->user_data)->send_key)
+      cipher = ((SilcClientConnection)sock->user_data)->send_key;
 
-  /* Encode the channel payload */
-  payload = silc_channel_encode_payload(strlen(conn->nickname), conn->nickname,
-                                       data_len, data, 16, channel->iv, 
-                                       client->rng);
-  if (!payload) {
-    client->ops->say(client, conn, 
-                    "Error: Could not create packet to be sent to channel");
-    return;
-  }
+    if (!hmac && ((SilcClientConnection)sock->user_data)->hmac_send)
+      hmac = ((SilcClientConnection)sock->user_data)->hmac_send;
 
-  /* Get data used in packet header encryption, keys and stuff. Rest
-     of the packet (the payload) is, however, encrypted with the 
-     specified channel key. */
-  cipher = conn->send_key;
-  hmac = conn->hmac;
-  id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL);
+    if (!dst_id && ((SilcClientConnection)sock->user_data)->remote_id) {
+      dst_id = ((SilcClientConnection)sock->user_data)->remote_id;
+      dst_id_type = SILC_ID_SERVER;
+    }
+  }
 
-  /* 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. */
+  /* Set the packet context pointers */
   packetdata.flags = 0;
-  packetdata.type = SILC_PACKET_CHANNEL_MESSAGE;
-  packetdata.src_id = conn->local_id_data;
+  packetdata.type = type;
+  if (sock->user_data && 
+      ((SilcClientConnection)sock->user_data)->local_id_data)
+    packetdata.src_id = ((SilcClientConnection)sock->user_data)->local_id_data;
+  else 
+    packetdata.src_id = silc_calloc(SILC_ID_CLIENT_LEN, sizeof(unsigned char));
   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.rng = client->rng;
-  packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN + 
+  if (dst_id) {
+    packetdata.dst_id = silc_id_id2str(dst_id, dst_id_type);
+    packetdata.dst_id_len = silc_id_get_len(dst_id_type);
+    packetdata.dst_id_type = dst_id_type;
+  } else {
+    packetdata.dst_id = NULL;
+    packetdata.dst_id_len = 0;
+    packetdata.dst_id_type = SILC_ID_NONE;
+  }
+  packetdata.truelen = data_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));
+  packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen);
 
   /* Prepare outgoing data buffer for packet sending */
   silc_packet_send_prepare(sock, 
@@ -992,151 +1210,48 @@ void silc_client_packet_send_to_channel(SilcClient client,
                           packetdata.src_id_len + 
                           packetdata.dst_id_len,
                           packetdata.padlen,
-                          payload->len);
-
-  packetdata.buffer = sock->outbuf;
-
-  /* Encrypt payload of the packet. This is encrypted with the channel key. */
-  channel->channel_key->cipher->encrypt(channel->channel_key->context,
-                                       payload->data, payload->data,
-                                       payload->len - 16, /* -IV_LEN */
-                                       channel->iv);
-
-  /* Put the actual encrypted payload data into the 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);
-}
-
-/* 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. */
-
-void silc_client_packet_send_private_message(SilcClient client,
-                                            SilcSocketConnection sock,
-                                            SilcClientEntry client_entry,
-                                            unsigned char *data, 
-                                            unsigned int data_len, 
-                                            int force_send)
-{
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcBuffer buffer;
-  SilcPacketContext packetdata;
-  unsigned int nick_len;
-  SilcCipher cipher;
-  SilcHmac hmac;
-
-  SILC_LOG_DEBUG(("Sending private message"));
-
-  /* Create private message payload */
-  nick_len = strlen(conn->nickname);
-  buffer = silc_buffer_alloc(2 + nick_len + data_len);
-  silc_buffer_pull_tail(buffer, SILC_BUFFER_END(buffer));
-  silc_buffer_format(buffer,
-                    SILC_STR_UI_SHORT(nick_len),
-                    SILC_STR_UI_XNSTRING(conn->nickname,
-                                         nick_len),
-                    SILC_STR_UI_XNSTRING(data, data_len),
-                    SILC_STR_END);
-
-  /* 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;
-  }
-
-  /* We have private message specific key */
-
-  /* Get data used in the encryption */
-  cipher = client_entry->send_key;
-  hmac = conn->hmac;
+                          data_len);
 
-  /* Set the packet context pointers. */
-  packetdata.flags = 0;
-  packetdata.type = SILC_PACKET_PRIVATE_MESSAGE;
-  packetdata.src_id = conn->local_id_data;
-  packetdata.src_id_len = SILC_ID_CLIENT_LEN;
-  packetdata.src_id_type = SILC_ID_CLIENT;
-  if (client_entry)
-    packetdata.dst_id = silc_id_id2str(client_entry->id, SILC_ID_CLIENT);
-  else
-    packetdata.dst_id = conn->local_id_data;
-  packetdata.dst_id_len = SILC_ID_CLIENT_LEN;
-  packetdata.dst_id_type = SILC_ID_CLIENT;
-  packetdata.rng = client->rng;
-  packetdata.truelen = buffer->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));
+  SILC_LOG_DEBUG(("Putting data to outgoing buffer, len %d", data_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,
-                          buffer->len);
-  
   packetdata.buffer = sock->outbuf;
 
-  /* Encrypt payload of the packet. Encrypt with private message specific
-     key if it exist, otherwise with session key. */
-  cipher->cipher->encrypt(cipher->context, buffer->data, buffer->data,
-                         buffer->len, cipher->iv);
-      
-  /* Put the actual encrypted payload data into the buffer. */
-  silc_buffer_put(sock->outbuf, buffer->data, buffer->len);
+  /* Put the data to the buffer */
+  if (data && data_len)
+    silc_buffer_put(sock->outbuf, data, data_len);
 
   /* Create the outgoing packet */
   silc_packet_assemble(&packetdata);
 
-  /* Encrypt the header and padding of the packet. */
-  silc_packet_encrypt(cipher, hmac, sock->outbuf, SILC_PACKET_HEADER_LEN + 
-                     packetdata.src_id_len + packetdata.dst_id_len +
-                     packetdata.padlen);
+  /* Encrypt the packet */
+  if (cipher)
+    silc_packet_encrypt(cipher, hmac, sock->outbuf, sock->outbuf->len);
 
-  SILC_LOG_HEXDUMP(("Private message packet, len %d", sock->outbuf->len),
+  SILC_LOG_HEXDUMP(("Packet, 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_free(packetdata.dst_id);
-
- out:
-  silc_free(buffer);
-}     
+  silc_client_packet_send_real(client, sock, TRUE, TRUE);
+}
 
 /* Closes connection to remote end. Free's all allocated data except
-   for some information such as nickname etc. that are valid at all time. */
+   for some information such as nickname etc. that are valid at all time. 
+   If the `sock' is NULL then the conn->sock will be used.  If `sock' is
+   provided it will be checked whether the sock and `conn->sock' are the
+   same (they can be different, ie. a socket can use `conn' as its
+   connection but `conn->sock' might be actually a different connection
+   than the `sock'). */
 
 void silc_client_close_connection(SilcClient client,
-                                 SilcSocketConnection sock)
+                                 SilcSocketConnection sock,
+                                 SilcClientConnection conn)
 {
-  SilcClientConnection conn;
+  int del = FALSE;
+
+  if (!sock || (sock && conn->sock == sock))
+    del = TRUE;
+  if (!sock)
+    sock = conn->sock;
 
   /* We won't listen for this connection anymore */
   silc_schedule_unset_listen_fd(sock->sock);
@@ -1148,16 +1263,13 @@ void silc_client_close_connection(SilcClient client,
   /* Close the actual connection */
   silc_net_close_connection(sock->sock);
 
-  client->ops->say(client, sock->user_data,
-                  "Closed connection to host %s", sock->hostname ?
-                  sock->hostname : sock->ip);
-
   /* Free everything */
-  if (sock->user_data) {
-    conn = (SilcClientConnection)sock->user_data;
-
+  if (del && sock->user_data) {
     /* XXX Free all client entries and channel entries. */
 
+    client->ops->say(client, sock->user_data,
+                    "Closed connection to host %s", sock->hostname);
+
     /* Clear ID caches */
     silc_idcache_del_all(conn->client_cache);
     silc_idcache_del_all(conn->channel_cache);
@@ -1173,25 +1285,26 @@ void silc_client_close_connection(SilcClient client,
       silc_cipher_free(conn->send_key);
     if (conn->receive_key)
       silc_cipher_free(conn->receive_key);
-    if (conn->hmac)
-      silc_hmac_free(conn->hmac);
-    if (conn->hmac_key) {
-      memset(conn->hmac_key, 0, conn->hmac_key_len);
-      silc_free(conn->hmac_key);
-    }
+    if (conn->hmac_send)       /* conn->hmac_receive is same */
+      silc_hmac_free(conn->hmac_send);
+    if (conn->pending_commands)
+      silc_dlist_uninit(conn->pending_commands);
+    if (conn->rekey)
+      silc_free(conn->rekey);
 
     conn->sock = NULL;
     conn->remote_port = 0;
     conn->remote_type = 0;
     conn->send_key = NULL;
     conn->receive_key = NULL;
-    conn->hmac = NULL;
-    conn->hmac_key = NULL;
-    conn->hmac_key_len = 0;
+    conn->hmac_send = NULL;
+    conn->hmac_receive = NULL;
     conn->local_id = NULL;
     conn->local_id_data = NULL;
     conn->remote_host = NULL;
     conn->current_channel = NULL;
+    conn->pending_commands = NULL;
+    conn->rekey = NULL;
 
     silc_client_del_connection(client, conn);
   }
@@ -1221,7 +1334,7 @@ void silc_client_disconnected_by_server(SilcClient client,
   silc_free(msg);
 
   SILC_SET_DISCONNECTED(sock);
-  silc_client_close_connection(client, sock);
+  silc_client_close_connection(client, sock, sock->user_data);
 }
 
 /* Received error message from server. Display it on the screen. 
@@ -1239,28 +1352,18 @@ void silc_client_error_by_server(SilcClient client,
   silc_free(msg);
 }
 
-/* Received notify message from server */
-
-void silc_client_notify_by_server(SilcClient client,
-                                 SilcSocketConnection sock,
-                                 SilcBuffer message)
-{
-  char *msg;
-
-  msg = silc_calloc(message->len + 1, sizeof(char));
-  memcpy(msg, message->data, message->len);
-  client->ops->say(client, sock->user_data, msg);
-  silc_free(msg);
-}
-
 /* Processes the received new Client ID from server. Old Client ID is
    deleted from cache and new one is added. */
 
 void silc_client_receive_new_id(SilcClient client,
                                SilcSocketConnection sock,
-                               unsigned char *id_string)
+                               SilcIDPayload idp)
 {
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
+  int connecting = FALSE;
+
+  if (!conn->local_entry)
+    connecting = TRUE;
 
   /* Delete old ID from ID cache */
   silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, conn->local_id);
@@ -1268,239 +1371,340 @@ void silc_client_receive_new_id(SilcClient client,
   /* Save the new ID */
   if (conn->local_id)
     silc_free(conn->local_id);
-  conn->local_id = silc_id_str2id(id_string, SILC_ID_CLIENT);
   if (conn->local_id_data)
     silc_free(conn->local_id_data);
-  conn->local_id_data = 
-    silc_calloc(SILC_ID_CLIENT_LEN, sizeof(unsigned char));
-  memcpy(conn->local_id_data, id_string, SILC_ID_CLIENT_LEN);
-  conn->local_id_data_len = SILC_ID_CLIENT_LEN;
+
+  conn->local_id = silc_id_payload_get_id(idp);
+  conn->local_id_data = silc_id_payload_get_data(idp);
+  conn->local_id_data_len = silc_id_payload_get_len(idp);;
+
   if (!conn->local_entry)
     conn->local_entry = silc_calloc(1, sizeof(*conn->local_entry));
+
   conn->local_entry->nickname = conn->nickname;
+  if (!conn->local_entry->username) {
+    conn->local_entry->username = 
+      silc_calloc(strlen(client->username) + strlen(client->hostname) + 1,
+                 sizeof(conn->local_entry->username));
+    sprintf(conn->local_entry->username, "%s@%s", client->username,
+           client->hostname);
+  }
+  conn->local_entry->server = strdup(conn->remote_host);
   conn->local_entry->id = conn->local_id;
   
   /* Put it to the ID cache */
-  silc_idcache_add(conn->client_cache, conn->nickname, SILC_ID_CLIENT,
-                  conn->local_id, (void *)conn->local_entry, TRUE);
+  silc_idcache_add(conn->client_cache, conn->nickname, strlen(conn->nickname),
+                  SILC_ID_CLIENT, conn->local_id, (void *)conn->local_entry,
+                  TRUE, FALSE);
+
+  /* Notify application of successful connection. We do it here now that
+     we've received the Client ID and are allowed to send traffic. */
+  if (connecting)
+    client->ops->connect(client, conn, TRUE);
 }
 
 /* Processed received Channel ID for a channel. This is called when client
-   joins to channel and server replies with channel ID. The ID is cached. */
-
-void silc_client_new_channel_id(SilcClient client,
-                               SilcSocketConnection sock,
-                               char *channel_name,
-                               unsigned int mode,
-                               unsigned char *id_string)
+   joins to channel and server replies with channel ID. The ID is cached. 
+   Returns the created channel entry. */
+
+SilcChannelEntry silc_client_new_channel_id(SilcClient client,
+                                           SilcSocketConnection sock,
+                                           char *channel_name,
+                                           uint32 mode, 
+                                           SilcIDPayload idp)
 {
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcChannelID *id;
   SilcChannelEntry channel;
 
   SILC_LOG_DEBUG(("New channel ID"));
 
-  id = silc_id_str2id(id_string, SILC_ID_CHANNEL);
   channel = silc_calloc(1, sizeof(*channel));
   channel->channel_name = channel_name;
-  channel->id = id;
+  channel->id = silc_id_payload_get_id(idp);
   channel->mode = mode;
+  silc_list_init(channel->clients, struct SilcChannelUserStruct, next);
+
   conn->current_channel = channel;
-  
+
   /* Put it to the ID cache */
-  silc_idcache_add(conn->channel_cache, channel_name, SILC_ID_CHANNEL,
-                  (void *)id, (void *)channel, TRUE);
+  silc_idcache_add(conn->channel_cache, channel_name, strlen(channel_name),
+                  SILC_ID_CHANNEL, (void *)channel->id, (void *)channel, 
+                  TRUE, FALSE);
+
+  return channel;
 }
 
-/* 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. */
+/* Removes a client entry from all channel it has joined. This really is
+   a performance killer (client_entry should have pointers to channel 
+   entry list). */
 
-void silc_client_receive_channel_key(SilcClient client,
-                                    SilcSocketConnection sock,
-                                    SilcBuffer packet)
+void silc_client_remove_from_channels(SilcClient client,
+                                     SilcClientConnection conn,
+                                     SilcClientEntry client_entry)
 {
-  unsigned char *id_string, *key, *cipher;
-  unsigned int key_len;
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcChannelID *id;
-  SilcIDCacheEntry id_cache = NULL;
+  SilcIDCacheEntry id_cache;
+  SilcIDCacheList list;
   SilcChannelEntry channel;
-  SilcChannelKeyPayload payload;
+  SilcChannelUser chu;
 
-  SILC_LOG_DEBUG(("Received key for channel"));
-  
-  payload = silc_channel_key_parse_payload(packet);
-  if (!payload)
+  if (!silc_idcache_find_by_id(conn->channel_cache, SILC_ID_CACHE_ANY,
+                              SILC_ID_CHANNEL, &list))
     return;
 
-  id_string = silc_channel_key_get_id(payload, NULL);
-  if (!id_string) {
-    silc_channel_key_free_payload(payload);
-    return;
+  silc_idcache_list_first(list, &id_cache);
+  channel = (SilcChannelEntry)id_cache->context;
+  
+  while (channel) {
+    
+    /* Remove client from channel */
+    silc_list_start(channel->clients);
+    while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
+      if (chu->client == client_entry) {
+       silc_list_del(channel->clients, chu);
+       silc_free(chu);
+       break;
+      }
+    }
+
+    if (!silc_idcache_list_next(list, &id_cache))
+      break;
+    
+    channel = (SilcChannelEntry)id_cache->context;
   }
-  id = silc_id_str2id(id_string, SILC_ID_CHANNEL);
 
-  /* Find channel. */
-  if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id,
-                                  SILC_ID_CHANNEL, &id_cache))
-    goto out;
-  
-  /* Save the key */
-  key = silc_channel_key_get_key(payload, &key_len);
-  cipher = silc_channel_key_get_cipher(payload, NULL);
+  silc_idcache_list_free(list);
+}
 
+/* Replaces `old' client entries from all channels to `new' client entry.
+   This can be called for example when nickname changes and old ID entry
+   is replaced from ID cache with the new one. If the old ID entry is only
+   updated, then this fucntion needs not to be called. */
+
+void silc_client_replace_from_channels(SilcClient client, 
+                                      SilcClientConnection conn,
+                                      SilcClientEntry old,
+                                      SilcClientEntry new)
+{
+  SilcIDCacheEntry id_cache;
+  SilcIDCacheList list;
+  SilcChannelEntry channel;
+  SilcChannelUser chu;
+
+  if (!silc_idcache_find_by_id(conn->channel_cache, SILC_ID_CACHE_ANY,
+                              SILC_ID_CHANNEL, &list))
+    return;
+
+  silc_idcache_list_first(list, &id_cache);
   channel = (SilcChannelEntry)id_cache->context;
-  channel->key_len = key_len;
-  channel->key = silc_calloc(key_len, sizeof(*channel->key));
-  memcpy(channel->key, key, key_len);
-
-  silc_cipher_alloc(cipher, &channel->channel_key);
-  if (!channel->channel_key) {
-    client->ops->say(client, conn,
-                    "Cannot talk to channel: unsupported cipher %s", cipher);
-    goto out;
-  }
-  channel->channel_key->cipher->set_key(channel->channel_key->context, 
-                                       key, key_len);
+  
+  while (channel) {
+    
+    /* Replace client entry */
+    silc_list_start(channel->clients);
+    while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
+      if (chu->client == old) {
+       chu->client = new;
+       break;
+      }
+    }
 
-  /* Client is now joined to the channel */
-  channel->on_channel = TRUE;
+    if (!silc_idcache_list_next(list, &id_cache))
+      break;
+    
+    channel = (SilcChannelEntry)id_cache->context;
+  }
 
- out:
-  silc_free(id);
-  silc_channel_key_free_payload(payload);
+  silc_idcache_list_free(list);
 }
 
-/* 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. */
+/* Parses mode mask and returns the mode as string. */
 
-void silc_client_channel_message(SilcClient client, 
-                                SilcSocketConnection sock, 
-                                SilcPacketContext *packet)
+char *silc_client_chmode(uint32 mode, SilcChannelEntry channel)
 {
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcBuffer buffer = packet->buffer;
-  SilcChannelPayload payload = NULL;
-  SilcChannelID *id = NULL;
-  SilcChannelEntry channel;
-  SilcIDCacheEntry id_cache = NULL;
+  char string[100];
 
-  /* Sanity checks */
-  if (packet->dst_id_type != SILC_ID_CHANNEL)
-    goto out;
+  if (!mode)
+    return NULL;
 
-  id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL);
+  memset(string, 0, sizeof(string));
 
-  /* Find the channel entry from channels on this window */
-  if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id,
-                                  SILC_ID_CHANNEL, &id_cache))
-    goto out;
+  if (mode & SILC_CHANNEL_MODE_PRIVATE)
+    strncat(string, "p", 1);
 
-  channel = (SilcChannelEntry)id_cache->context;
+  if (mode & SILC_CHANNEL_MODE_SECRET)
+    strncat(string, "s", 1);
 
-  /* Decrypt the channel message payload. Push the IV out of the way,
-     since it is not encrypted (after pushing buffer->tail has the IV). */
-  silc_buffer_push_tail(buffer, 16);
-  channel->channel_key->cipher->decrypt(channel->channel_key->context,
-                                       buffer->data, buffer->data,
-                                       buffer->len, buffer->tail);
-  silc_buffer_pull_tail(buffer, 16);
-
-  /* Parse the channel message payload */
-  payload = silc_channel_parse_payload(buffer);
-  if (!payload)
-    goto out;
+  if (mode & SILC_CHANNEL_MODE_PRIVKEY)
+    strncat(string, "k", 1);
 
-  /* Pass the message to application */
-  if (packet->src_id_type == SILC_ID_CLIENT) {
-    client->ops->channel_message(client, conn, 
-                                silc_channel_get_nickname(payload, NULL),
-                                channel->channel_name,
-                                silc_channel_get_data(payload, NULL));
-  } else {
-    /* Message from server */
-    /* XXX maybe this should be passed to app... */
-    client->ops->say(client, conn, "%s", silc_channel_get_data(payload, NULL));
+  if (mode & SILC_CHANNEL_MODE_INVITE)
+    strncat(string, "i", 1);
+
+  if (mode & SILC_CHANNEL_MODE_TOPIC)
+    strncat(string, "t", 1);
+
+  if (mode & SILC_CHANNEL_MODE_ULIMIT)
+    strncat(string, "l", 1);
+
+  if (mode & SILC_CHANNEL_MODE_PASSPHRASE)
+    strncat(string, "a", 1);
+
+  if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)
+    strncat(string, "f", 1);
+
+  if (mode & SILC_CHANNEL_MODE_CIPHER) {
+    char cipher[30];
+    memset(cipher, 0, sizeof(cipher));
+    snprintf(cipher, sizeof(cipher), " c (%s)", 
+            channel->channel_key->cipher->name);
+    strncat(string, cipher, strlen(cipher));
   }
 
- out:
-  if (id)
-    silc_free(id);
-  if (payload)
-    silc_channel_free_payload(payload);
+  if (mode & SILC_CHANNEL_MODE_HMAC) {
+    char hmac[30];
+    memset(hmac, 0, sizeof(hmac));
+    snprintf(hmac, sizeof(hmac), " h (%s)", 
+            channel->hmac->hmac->name);
+    strncat(string, hmac, strlen(hmac));
+  }
+
+  /* Rest of mode is ignored */
+
+  return strdup(string);
+}
+
+/* Parses channel user mode mask and returns te mode as string */
+
+char *silc_client_chumode(uint32 mode)
+{
+  char string[4];
+
+  if (!mode)
+    return NULL;
+
+  memset(string, 0, sizeof(string));
+
+  if (mode & SILC_CHANNEL_UMODE_CHANFO)
+    strncat(string, "f", 1);
+
+  if (mode & SILC_CHANNEL_UMODE_CHANOP)
+    strncat(string, "o", 1);
+
+  return strdup(string);
 }
 
-/* Private message received. This processes the private message and
-   finally displays it on the screen. */
+/* Parses channel user mode and returns it as special mode character. */
 
-void silc_client_private_message(SilcClient client, 
-                                SilcSocketConnection sock, 
+char *silc_client_chumode_char(uint32 mode)
+{
+  char string[4];
+
+  if (!mode)
+    return NULL;
+
+  memset(string, 0, sizeof(string));
+
+  if (mode & SILC_CHANNEL_UMODE_CHANFO)
+    strncat(string, "*", 1);
+
+  if (mode & SILC_CHANNEL_UMODE_CHANOP)
+    strncat(string, "@", 1);
+
+  return strdup(string);
+}
+
+/* Registers failure timeout to process the received failure packet
+   with timeout. */
+
+void silc_client_process_failure(SilcClient client,
+                                SilcSocketConnection sock,
                                 SilcPacketContext *packet)
 {
+  uint32 failure = 0;
+
+  if (sock->protocol) {
+    if (packet->buffer->len >= 4)
+      SILC_GET32_MSB(failure, packet->buffer->data);
+
+    /* Notify application */
+    client->ops->failure(client, sock->user_data, sock->protocol,
+                        (void *)failure);
+  }
+}
+
+/* A timeout callback for the re-key. We will be the initiator of the
+   re-key protocol. */
+
+SILC_TASK_CALLBACK(silc_client_rekey_callback)
+{
+  SilcSocketConnection sock = (SilcSocketConnection)context;
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcBuffer buffer = packet->buffer;
-  unsigned short nick_len;
-  unsigned char *nickname, *message;
-
-  /* Get nickname */
-  silc_buffer_unformat(buffer, 
-                      SILC_STR_UI16_NSTRING_ALLOC(&nickname, &nick_len),
-                      SILC_STR_END);
-  silc_buffer_pull(buffer, 2 + nick_len);
-     
-  message = silc_calloc(buffer->len + 1, sizeof(char));
-  memcpy(message, buffer->data, buffer->len);
-
-  /* Pass the private message to application */
-  client->ops->private_message(client, conn, nickname, message);
-
-  /* See if we are away (gone). If we are away we will reply to the
-     sender with the set away message. */
-  if (conn->away && conn->away->away) {
-    SilcClientID *remote_id;
-    SilcClientEntry remote_client;
-    SilcIDCacheEntry id_cache;
-
-    if (packet->src_id_type != SILC_ID_CLIENT)
-      goto out;
-
-    remote_id = silc_id_str2id(packet->src_id, SILC_ID_CLIENT);
-    if (!remote_id)
-      goto out;
-
-    if (!SILC_ID_CLIENT_COMPARE(remote_id, conn->local_id))
-      goto out;
-
-    /* Check whether we know this client already */
-    if (!silc_idcache_find_by_id_one(conn->client_cache, remote_id,
-                                    SILC_ID_CLIENT, &id_cache))
-      {
-       /* Allocate client entry */
-       remote_client = silc_calloc(1, sizeof(*remote_client));
-       remote_client->id = remote_id;
-       remote_client->nickname = strdup(nickname);
-
-       /* Save the client to cache */
-       silc_idcache_add(conn->client_cache, remote_client->nickname,
-                        SILC_ID_CLIENT, remote_client->id, remote_client, 
-                        TRUE);
-      } else {
-       silc_free(remote_id);
-       remote_client = (SilcClientEntry)id_cache->context;
-      }
+  SilcClient client = (SilcClient)conn->rekey->context;
+  SilcProtocol protocol;
+  SilcClientRekeyInternalContext *proto_ctx;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  /* Allocate internal protocol context. This is sent as context
+     to the protocol. */
+  proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
+  proto_ctx->client = (void *)client;
+  proto_ctx->sock = silc_socket_dup(sock);
+  proto_ctx->responder = FALSE;
+  proto_ctx->pfs = conn->rekey->pfs;
+      
+  /* Perform rekey protocol. Will call the final callback after the
+     protocol is over. */
+  silc_protocol_alloc(SILC_PROTOCOL_CLIENT_REKEY, 
+                     &protocol, proto_ctx, silc_client_rekey_final);
+  sock->protocol = protocol;
+      
+  /* Run the protocol */
+  protocol->execute(client->timeout_queue, 0, protocol, 
+                   sock->sock, 0, 0);
+
+  /* Re-register re-key timeout */
+  silc_task_register(client->timeout_queue, sock->sock, 
+                    silc_client_rekey_callback,
+                    context, conn->rekey->timeout, 0,
+                    SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+}
+
+/* The final callback for the REKEY protocol. This will actually take the
+   new key material into use. */
 
-    /* Send the away message */
-    silc_client_packet_send_private_message(client, sock, remote_client,
-                                           conn->away->away,
-                                           strlen(conn->away->away), TRUE);
+SILC_TASK_CALLBACK(silc_client_rekey_final)
+{
+  SilcProtocol protocol = (SilcProtocol)context;
+  SilcClientRekeyInternalContext *ctx =
+    (SilcClientRekeyInternalContext *)protocol->context;
+  SilcClient client = (SilcClient)ctx->client;
+  SilcSocketConnection sock = ctx->sock;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  if (protocol->state == SILC_PROTOCOL_STATE_ERROR ||
+      protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
+    /* Error occured during protocol */
+    silc_protocol_cancel(client->timeout_queue, protocol);
+    silc_protocol_free(protocol);
+    sock->protocol = NULL;
+    if (ctx->packet)
+      silc_packet_context_free(ctx->packet);
+    if (ctx->ske)
+      silc_ske_free(ctx->ske);
+    silc_socket_free(ctx->sock);
+    silc_free(ctx);
+    return;
   }
 
- out:
-  memset(message, 0, buffer->len);
-  silc_free(message);
-  silc_free(nickname);
+  /* Cleanup */
+  silc_protocol_free(protocol);
+  sock->protocol = NULL;
+  if (ctx->packet)
+    silc_packet_context_free(ctx->packet);
+  if (ctx->ske)
+    silc_ske_free(ctx->ske);
+  silc_socket_free(ctx->sock);
+  silc_free(ctx);
 }