updates.
[silc.git] / lib / silcclient / client.c
index 1c1cd3017df14628e7571f198be9e69292e2a724..089ea87aeee92e53eecb29c5ac529052691f9843 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);
 
 static void silc_client_packet_parse(SilcPacketParserContext *parser_context);
@@ -49,18 +49,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 +79,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 +109,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,7 +125,9 @@ 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,
@@ -134,13 +140,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++)
@@ -157,7 +164,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)
 {
@@ -165,23 +172,13 @@ 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;
-
 static int 
 silc_client_connect_to_server_internal(SilcClientInternalConnectContext *ctx)
 {
@@ -212,7 +209,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)
@@ -248,7 +248,9 @@ int silc_client_connect_to_server(SilcClient client, int port,
 /* 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. */
+   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,
@@ -260,14 +262,10 @@ int silc_client_start_key_exchange(SilcClient client,
 
   /* 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");
-    return FALSE;
-  }
 
   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
@@ -277,6 +275,8 @@ int silc_client_start_key_exchange(SilcClient client,
   proto_ctx->sock = 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. */
@@ -376,15 +376,19 @@ 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_task_unregister_by_callback(client->timeout_queue,
+                                    silc_client_failure_callback);
 
     /* Notify application of failure */
     client->ops->connect(client, ctx->sock->user_data, FALSE);
@@ -392,6 +396,16 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_second)
     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);
+  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));
@@ -405,18 +419,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
@@ -447,7 +461,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);
@@ -458,6 +473,8 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_final)
     if (ctx->dest_id)
       silc_free(ctx->dest_id);
     conn->sock->protocol = NULL;
+    silc_task_unregister_by_callback(client->timeout_queue,
+                                    silc_client_failure_callback);
 
     /* Notify application of failure */
     client->ops->connect(client, ctx->sock->user_data, FALSE);
@@ -491,16 +508,13 @@ 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;
 
-  /* Notify application of successful connection */
-  client->ops->connect(client, conn, TRUE);
-
+  silc_task_unregister_by_callback(client->timeout_queue,
+                                  silc_client_failure_callback);
   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_free(ctx);
   conn->sock->protocol = NULL;
 }
@@ -509,9 +523,9 @@ 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,
+                                int force_send)
 {
   int ret;
 
@@ -537,7 +551,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;
@@ -595,14 +609,13 @@ SILC_TASK_CALLBACK(silc_client_packet_process)
         close the connection */
       if (SILC_IS_DISCONNECTING(sock)) {
        client->ops->disconnect(client, conn);
-       silc_client_close_connection(client, sock);
+       silc_client_close_connection(client, conn);
        return;
       }
       
-      client->ops->say(client, conn, "Connection closed: premature EOF");
-      SILC_LOG_DEBUG(("Premature EOF from connection %d", sock->sock));
+      SILC_LOG_DEBUG(("EOF from connection %d", sock->sock));
       client->ops->disconnect(client, conn);
-      silc_client_close_connection(client, sock);
+      silc_client_close_connection(client, conn);
       return;
     }
 
@@ -613,6 +626,31 @@ SILC_TASK_CALLBACK(silc_client_packet_process)
   }
 }
 
+/* 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)) ||
+      packet_type != SILC_PACKET_CHANNEL_MESSAGE)
+    return TRUE;
+
+  return FALSE;
+}
+
 /* Parses whole packet, received earlier. */
 
 SILC_TASK_CALLBACK(silc_client_packet_parse_real)
@@ -628,7 +666,8 @@ 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);
+  ret = silc_packet_decrypt(conn->receive_key, conn->hmac, buffer, packet,
+                           silc_client_packet_decrypt_check, parse_ctx);
   if (ret < 0)
     goto out;
 
@@ -648,8 +687,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);
 }
 
@@ -702,11 +741,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;
@@ -715,7 +750,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:
@@ -764,9 +799,12 @@ void silc_client_packet_parse_type(SilcClient client,
       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,
@@ -793,11 +831,14 @@ void silc_client_packet_parse_type(SilcClient client,
        (SilcClientKEInternalContext *)sock->protocol->context;
 
       if (proto_ctx->packet)
-       silc_buffer_free(proto_ctx->packet);
+       silc_packet_context_free(proto_ctx->packet);
 
-      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,
@@ -816,22 +857,34 @@ 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;
+
   default:
     SILC_LOG_DEBUG(("Incorrect packet type %d, packet dropped", type));
     break;
@@ -891,7 +944,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);
@@ -926,227 +978,13 @@ void silc_client_packet_send(SilcClient client,
   silc_client_packet_send_real(client, sock, force_send);
 }
 
-/* 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)
-{
-  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"));
-
-  if (!channel || !channel->key) {
-    client->ops->say(client, conn, 
-                    "Cannot talk to channel: key does not exist");
-    return;
-  }
-
-  /* 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);
-
-  /* Encode the channel payload */
-  payload = silc_channel_payload_encode(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;
-  }
-
-  /* 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);
-
-  /* Set the packet context pointers. The destination ID is always
-     the Channel ID of the channel. Server and router will handle the
-     distribution of the packet. */
-  packetdata.flags = 0;
-  packetdata.type = SILC_PACKET_CHANNEL_MESSAGE;
-  packetdata.src_id = conn->local_id_data;
-  packetdata.src_id_len = SILC_ID_CLIENT_LEN;
-  packetdata.src_id_type = SILC_ID_CLIENT;
-  packetdata.dst_id = id_string;
-  packetdata.dst_id_len = SILC_ID_CHANNEL_LEN;
-  packetdata.dst_id_type = SILC_ID_CHANNEL;
-  packetdata.rng = client->rng;
-  packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN + 
-    packetdata.src_id_len + packetdata.dst_id_len;
-  packetdata.padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN +
-                                         packetdata.src_id_len +
-                                         packetdata.dst_id_len));
-
-  /* Prepare outgoing data buffer for packet sending */
-  silc_packet_send_prepare(sock, 
-                          SILC_PACKET_HEADER_LEN +
-                          packetdata.src_id_len + 
-                          packetdata.dst_id_len,
-                          packetdata.padlen,
-                          payload->len);
-
-  packetdata.buffer = sock->outbuf;
-
-  /* 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;
-
-  /* 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));
-
-  /* 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);
-
-  /* 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);
-
-  SILC_LOG_HEXDUMP(("Private message 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);
-}     
-
 /* Closes connection to remote end. Free's all allocated data except
    for some information such as nickname etc. that are valid at all time. */
 
 void silc_client_close_connection(SilcClient client,
-                                 SilcSocketConnection sock)
+                                 SilcClientConnection conn)
 {
-  SilcClientConnection conn;
+  SilcSocketConnection sock = conn->sock;
 
   /* We won't listen for this connection anymore */
   silc_schedule_unset_listen_fd(sock->sock);
@@ -1159,13 +997,10 @@ void silc_client_close_connection(SilcClient client,
   silc_net_close_connection(sock->sock);
 
   client->ops->say(client, sock->user_data,
-                  "Closed connection to host %s", sock->hostname ?
-                  sock->hostname : sock->ip);
+                  "Closed connection to host %s", sock->hostname);
 
   /* Free everything */
   if (sock->user_data) {
-    conn = (SilcClientConnection)sock->user_data;
-
     /* XXX Free all client entries and channel entries. */
 
     /* Clear ID caches */
@@ -1189,6 +1024,8 @@ void silc_client_close_connection(SilcClient client,
       memset(conn->hmac_key, 0, conn->hmac_key_len);
       silc_free(conn->hmac_key);
     }
+    if (conn->pending_commands)
+      silc_dlist_uninit(conn->pending_commands);
 
     conn->sock = NULL;
     conn->remote_port = 0;
@@ -1202,6 +1039,7 @@ void silc_client_close_connection(SilcClient client,
     conn->local_id_data = NULL;
     conn->remote_host = NULL;
     conn->current_channel = NULL;
+    conn->pending_commands = NULL;
 
     silc_client_del_connection(client, conn);
   }
@@ -1231,7 +1069,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->user_data);
 }
 
 /* Received error message from server. Display it on the screen. 
@@ -1249,502 +1087,310 @@ void silc_client_error_by_server(SilcClient client,
   silc_free(msg);
 }
 
-/* Received notify message from server */
+/* Processes the received new Client ID from server. Old Client ID is
+   deleted from cache and new one is added. */
 
-void silc_client_notify_by_server(SilcClient client,
-                                 SilcSocketConnection sock,
-                                 SilcBuffer message)
+void silc_client_receive_new_id(SilcClient client,
+                               SilcSocketConnection sock,
+                               SilcIDPayload idp)
 {
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcNotifyPayload payload;
-  SilcNotifyType type;
-  SilcArgumentPayload args;
-  int i;
+  int connecting = FALSE;
 
-  SilcClientID *client_id = NULL;
-  SilcChannelID *channel_id = NULL;
-  SilcIDPayload idp;
-  SilcClientEntry client_entry;
-  SilcChannelEntry channel;
-  SilcIDCacheEntry id_cache = NULL;
-  unsigned char *tmp;
-  unsigned int tmp_len;
+  if (!conn->local_entry)
+    connecting = TRUE;
 
-  payload = silc_notify_payload_parse(message);
-  type = silc_notify_get_type(payload);
-  args = silc_notify_get_args(payload);
+  /* Delete old ID from ID cache */
+  silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, conn->local_id);
+  
+  /* Save the new ID */
+  if (conn->local_id)
+    silc_free(conn->local_id);
+  if (conn->local_id_data)
+    silc_free(conn->local_id_data);
 
-  switch(type) {
-  case SILC_NOTIFY_TYPE_NONE:
-    break;
-  case SILC_NOTIFY_TYPE_INVITE:
-    /* 
-     * Someone invited me to a channel. Nothing interesting to do here.
-     */
-    break;
-  case SILC_NOTIFY_TYPE_JOIN:
-    /*
-     * Someone has joined to a channel. Get their ID and nickname and
-     * cache them for later use.
-     */
+  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);;
 
-    /* Get client ID (it's in ID payload) */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    idp = silc_id_payload_parse_data(tmp, tmp_len);
-    client_id = silc_id_payload_get_id(idp);
-    silc_id_payload_free(idp);
+  if (!conn->local_entry)
+    conn->local_entry = silc_calloc(1, sizeof(*conn->local_entry));
 
-    /* If it's my ID, ignore */
-    if (!SILC_ID_CLIENT_COMPARE(client_id, conn->local_id))
-      break;
+  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, 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);
+}
 
-    /* Check if we have that ID already */
-    if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id,
-                                    SILC_ID_CLIENT, &id_cache)) {
-      /* Add client to cache */
-      client_entry = silc_calloc(1, sizeof(*client_entry));
-      client_entry->id = client_id;
-      client_entry->nickname = 
-       strdup(silc_argument_get_arg_type(args, 2, NULL));
-      silc_idcache_add(conn->client_cache, client_entry->nickname, 
-                      SILC_ID_CLIENT, client_id, (void *)client_entry, TRUE);
-      client_id = NULL;
-    } else {
-      client_entry = (SilcClientEntry)id_cache->context;
-    }
+/* 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. 
+   Returns the created channel entry. */
+
+SilcChannelEntry silc_client_new_channel_id(SilcClient client,
+                                           SilcSocketConnection sock,
+                                           char *channel_name,
+                                           unsigned int mode, 
+                                           SilcIDPayload idp)
+{
+  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
+  SilcChannelEntry channel;
 
-    /* Get Channel ID (it's in ID payload) */
-    tmp = silc_argument_get_arg_type(args, 5, &tmp_len);
-    idp = silc_id_payload_parse_data(tmp, tmp_len);
-    channel_id = silc_id_payload_get_id(idp);
-    silc_id_payload_free(idp);
+  SILC_LOG_DEBUG(("New channel ID"));
 
-    /* Find channel entry */
-    if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
-                                    SILC_ID_CHANNEL, &id_cache))
-      break;
+  channel = silc_calloc(1, sizeof(*channel));
+  channel->channel_name = channel_name;
+  channel->id = silc_id_payload_get_id(idp);
+  channel->mode = mode;
+  silc_list_init(channel->clients, struct SilcChannelUserStruct, next);
 
-    channel = (SilcChannelEntry)id_cache->context;
+  conn->current_channel = channel;
 
-    /* Add client to channel */
-    for (i = 0; i < channel->clients_count; i++) {
-      if (channel->clients[i] == NULL) {
-       channel->clients[channel->clients_count] = client_entry;
-       channel->clients_count++;
-       break;
-      }
-    }
+  /* Put it to the ID cache */
+  silc_idcache_add(conn->channel_cache, channel_name, strlen(channel_name),
+                  SILC_ID_CHANNEL, (void *)channel->id, (void *)channel, 
+                  TRUE, FALSE);
 
-    if (i == channel->clients_count) {
-      channel->clients = silc_realloc(channel->clients, 
-                                     sizeof(*channel->clients) * 
-                                     (channel->clients_count + 1));
-      channel->clients[channel->clients_count] = client_entry;
-      channel->clients_count++;
-    }
+  return channel;
+}
 
-    /* XXX add support for multiple same nicks on same channel. Check
-       for them here */
-    break;
-  case SILC_NOTIFY_TYPE_LEAVE:
-    /*
-     * Someone has left a channel. We will remove it from the channel but
-     * we'll keep it in the cache in case we'll need it later.
-     */
-    
-    /* Get nickname */
-    /* XXX Protocol must be changed to send Client ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    
-    /* Find channel entry */
-    /* XXX this can return wrong entry */
-    client_entry = silc_idlist_get_client(client, conn, tmp, NULL, 0);
-    if (!client_entry)
-      break;
-    
-    /* Get Channel ID (it's in ID payload) */
-    tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
-    idp = silc_id_payload_parse_data(tmp, tmp_len);
-    channel_id = silc_id_payload_get_id(idp);
-    silc_id_payload_free(idp);
-    
-    /* Find channel entry */
-    if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
-                                    SILC_ID_CHANNEL, &id_cache))
-      break;
-    
-    channel = (SilcChannelEntry)id_cache->context;
+/* 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_remove_from_channels(SilcClient client,
+                                     SilcClientConnection conn,
+                                     SilcClientEntry client_entry)
+{
+  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;
+  
+  while (channel) {
     
     /* Remove client from channel */
-    for (i = 0; i < channel->clients_count; i++) {
-      if (channel->clients[i] == client_entry) {
-       channel->clients[i] = NULL;
-       channel->clients_count--;
+    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;
       }
     }
-    break;
-  case SILC_NOTIFY_TYPE_SIGNOFF:
-    /*
-     * Someone left SILC. We'll remove it from the channel and from cache.
-     */
 
-    /* Get nickname */
-    /* XXX Protocol must be changed to send Client ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    
-    /* Find channel entry */
-    /* XXX this can return wrong entry */
-    client_entry = silc_idlist_get_client(client, conn, tmp, NULL, 0);
-    if (!client_entry)
-      break;
-    
-    /* Get Channel ID (it's in ID payload) */
-    tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
-    idp = silc_id_payload_parse_data(tmp, tmp_len);
-    channel_id = silc_id_payload_get_id(idp);
-    silc_id_payload_free(idp);
-    
-    /* Find channel entry */
-    if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
-                                    SILC_ID_CHANNEL, &id_cache))
+    if (!silc_idcache_list_next(list, &id_cache))
       break;
     
     channel = (SilcChannelEntry)id_cache->context;
+  }
+
+  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;
+  
+  while (channel) {
     
-    /* Remove client from channel */
-    for (i = 0; i < channel->clients_count; i++) {
-      if (channel->clients[i] == client_entry) {
-       channel->clients[i] = NULL;
-       channel->clients_count--;
+    /* 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;
       }
     }
 
-    /* Remove from cache */
-    silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, 
-                          client_entry->id);
-    break;
-  case SILC_NOTIFY_TYPE_TOPIC_SET:
-    /*
-     * Someone set the topic on a channel. Nothing interesting to do here.
-     */
-    break;
-  case SILC_NOTIFY_TYPE_NICK_CHANGE:
-    /*
-     * Someone changed their nickname. Cache the new Client ID.
-     */
-
-    /* XXX Protocol must be changed to send the old Client ID and the
-       new Client ID. Now we get only nickname, thus, we'll make NAMES
-       to receive the new ID. Other choice is to do IDENTIFY but I'm
-       doing NAMES for now. */
-
-    /* Get nickname */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    
-    /* Find channel entry */
-    /* XXX this can return wrong entry */
-    client_entry = silc_idlist_get_client(client, conn, tmp, NULL, 0);
-    if (!client_entry)
-      break;
-    
-    /* Get Channel ID (it's in ID payload) */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-    idp = silc_id_payload_parse_data(tmp, tmp_len);
-    channel_id = silc_id_payload_get_id(idp);
-    silc_id_payload_free(idp);
-    
-    /* Find channel entry */
-    if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
-                                    SILC_ID_CHANNEL, &id_cache))
+    if (!silc_idcache_list_next(list, &id_cache))
       break;
     
     channel = (SilcChannelEntry)id_cache->context;
-    
-    {
-      SilcClientCommandContext ctx;
-      char names[512];
-      
-      ctx = silc_calloc(1, sizeof(*ctx));
-      ctx->client = client;
-      ctx->conn = conn;
-      ctx->command = silc_client_command_find("NAMES");
-      memset(names, 0, sizeof(names));
-      snprintf(names, sizeof(names), "NAMES %s", channel->channel_name);
-      silc_parse_command_line(names, &ctx->argv, &ctx->argv_lens, 
-                             &ctx->argv_types, &ctx->argc, 2);
-      ctx->command->cb(ctx);
-    }
-    break;
-  default:
-    break;
   }
 
-  client->ops->notify(client, conn, payload);
-  silc_notify_payload_free(payload);
-  if (client_id)
-    silc_free(client_id);
-  if (channel_id)
-    silc_free(channel_id);
+  silc_idcache_list_free(list);
 }
 
-/* Processes the received new Client ID from server. Old Client ID is
-   deleted from cache and new one is added. */
+/* Parses mode mask and returns the mode as string. */
 
-void silc_client_receive_new_id(SilcClient client,
-                               SilcSocketConnection sock,
-                               unsigned char *id_string)
+char *silc_client_chmode(unsigned int mode, SilcChannelEntry channel)
 {
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
+  char string[100];
 
-  /* Delete old ID from ID cache */
-  silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, conn->local_id);
-  
-  /* 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;
-  if (!conn->local_entry)
-    conn->local_entry = silc_calloc(1, sizeof(*conn->local_entry));
-  conn->local_entry->nickname = conn->nickname;
-  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);
-}
+  if (!mode)
+    return NULL;
 
-/* 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. */
+  memset(string, 0, sizeof(string));
 
-void silc_client_new_channel_id(SilcClient client,
-                               SilcSocketConnection sock,
-                               char *channel_name,
-                               unsigned int mode,
-                               unsigned char *id_string)
-{
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcChannelID *id;
-  SilcChannelEntry channel;
+  if (mode & SILC_CHANNEL_MODE_PRIVATE)
+    strncat(string, "p", 1);
 
-  SILC_LOG_DEBUG(("New channel ID"));
+  if (mode & SILC_CHANNEL_MODE_SECRET)
+    strncat(string, "s", 1);
 
-  id = silc_id_str2id(id_string, SILC_ID_CHANNEL);
-  channel = silc_calloc(1, sizeof(*channel));
-  channel->channel_name = channel_name;
-  channel->id = id;
-  channel->mode = mode;
-  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);
-}
+  if (mode & SILC_CHANNEL_MODE_PRIVKEY)
+    strncat(string, "k", 1);
 
-/* 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. */
+  if (mode & SILC_CHANNEL_MODE_INVITE)
+    strncat(string, "i", 1);
 
-void silc_client_receive_channel_key(SilcClient client,
-                                    SilcSocketConnection sock,
-                                    SilcBuffer packet)
-{
-  unsigned char *id_string, *key, *cipher;
-  unsigned int key_len;
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcChannelID *id;
-  SilcIDCacheEntry id_cache = NULL;
-  SilcChannelEntry channel;
-  SilcChannelKeyPayload payload;
+  if (mode & SILC_CHANNEL_MODE_TOPIC)
+    strncat(string, "t", 1);
 
-  SILC_LOG_DEBUG(("Received key for channel"));
-  
-  payload = silc_channel_key_payload_parse(packet);
-  if (!payload)
-    return;
+  if (mode & SILC_CHANNEL_MODE_ULIMIT)
+    strncat(string, "l", 1);
 
-  id_string = silc_channel_key_get_id(payload, NULL);
-  if (!id_string) {
-    silc_channel_key_payload_free(payload);
-    return;
-  }
-  id = silc_id_str2id(id_string, SILC_ID_CHANNEL);
+  if (mode & SILC_CHANNEL_MODE_PASSPHRASE)
+    strncat(string, "a", 1);
 
-  /* 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);
+  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));
+  }
 
-  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;
+  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));
   }
-  channel->channel_key->cipher->set_key(channel->channel_key->context, 
-                                       key, key_len);
 
-  /* Client is now joined to the channel */
-  channel->on_channel = TRUE;
+  /* Rest of mode is ignored */
 
- out:
-  silc_free(id);
-  silc_channel_key_payload_free(payload);
+  return strdup(string);
 }
 
-/* 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 channel user mode mask and returns te mode as string */
 
-void silc_client_channel_message(SilcClient client, 
-                                SilcSocketConnection sock, 
-                                SilcPacketContext *packet)
+char *silc_client_chumode(unsigned int mode)
 {
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcBuffer buffer = packet->buffer;
-  SilcChannelPayload payload = NULL;
-  SilcChannelID *id = NULL;
-  SilcChannelEntry channel;
-  SilcIDCacheEntry id_cache = NULL;
-  SilcClientID *client_id = NULL;
-  int i;
-  char *nickname;
+  char string[4];
 
-  /* Sanity checks */
-  if (packet->dst_id_type != SILC_ID_CHANNEL)
-    goto out;
+  if (!mode)
+    return NULL;
 
-  client_id = silc_id_str2id(packet->src_id, SILC_ID_CLIENT);
-  id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL);
+  memset(string, 0, sizeof(string));
 
-  /* Find the channel entry from channels on this connection */
-  if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id,
-                                  SILC_ID_CHANNEL, &id_cache))
-    goto out;
+  if (mode & SILC_CHANNEL_UMODE_CHANFO)
+    strncat(string, "f", 1);
 
-  channel = (SilcChannelEntry)id_cache->context;
+  if (mode & SILC_CHANNEL_UMODE_CHANOP)
+    strncat(string, "o", 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_payload_parse(buffer);
-  if (!payload)
-    goto out;
+  return strdup(string);
+}
 
-  /* Find nickname */
-  nickname = "[unknown]";
-  for (i = 0; i < channel->clients_count; i++) {
-    if (channel->clients[i] && 
-       !SILC_ID_CLIENT_COMPARE(channel->clients[i]->id, client_id))
-      nickname = channel->clients[i]->nickname;
-  }
+/* Parses channel user mode and returns it as special mode character. */
 
-  /* Pass the message to application */
-  client->ops->channel_message(client, conn, nickname,
-                              channel->channel_name,
-                              silc_channel_get_data(payload, NULL));
+char *silc_client_chumode_char(unsigned int mode)
+{
+  char string[4];
 
- out:
-  if (id)
-    silc_free(id);
-  if (client_id)
-    silc_free(client_id);
-  if (payload)
-    silc_channel_payload_free(payload);
+  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);
 }
 
-/* Private message received. This processes the private message and
-   finally displays it on the screen. */
+/* Failure timeout callback. If this is called then we will immediately
+   process the received failure. We always process the failure with timeout
+   since we do not want to blindly trust to received failure packets. 
+   This won't be called (the timeout is cancelled) if the failure was
+   bogus (it is bogus if remote does not close the connection after sending
+   the failure). */
 
-void silc_client_private_message(SilcClient client, 
-                                SilcSocketConnection sock, 
-                                SilcPacketContext *packet)
+SILC_TASK_CALLBACK_GLOBAL(silc_client_failure_callback)
 {
-  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 it's me, ignore */
-    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;
-      }
+  SilcClientFailureContext *f = (SilcClientFailureContext *)context;
 
-    /* Send the away message */
-    silc_client_packet_send_private_message(client, sock, remote_client,
-                                           conn->away->away,
-                                           strlen(conn->away->away), TRUE);
+  if (f->sock->protocol) {
+    f->sock->protocol->state = SILC_PROTOCOL_STATE_FAILURE;
+    f->sock->protocol->execute(f->client->timeout_queue, 0,
+                              f->sock->protocol, f->sock->sock, 0, 0);
+    
+    /* Notify application */
+    f->client->ops->failure(f->client, f->sock->user_data, f->sock->protocol,
+                           (void *)f->failure);
   }
 
- out:
-  memset(message, 0, buffer->len);
-  silc_free(message);
-  silc_free(nickname);
+  silc_free(f);
+}
+
+/* Registers failure timeout to process the received failure packet
+   with timeout. */
+
+void silc_client_process_failure(SilcClient client,
+                                SilcSocketConnection sock,
+                                SilcPacketContext *packet)
+{
+  SilcClientFailureContext *f;
+  unsigned int failure = 0;
+
+  if (sock->protocol) {
+    if (packet->buffer->len >= 4)
+      SILC_GET32_MSB(failure, packet->buffer->data);
+
+    f = silc_calloc(1, sizeof(*f));
+    f->client = client;
+    f->sock = sock;
+    f->failure = failure;
+
+    /* We will wait 5 seconds to process this failure packet */
+    silc_task_register(client->timeout_queue, sock->sock,
+                      silc_client_failure_callback, (void *)f, 5, 0,
+                      SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+  }
 }