Added SILC Server library.
[silc.git] / lib / silcclient / idlist.c
index 8e870e137cfa453d515f9a2156c10168bedd72e2..23674fc591fd42cd5ea803c7d6105c346021349b 100644 (file)
@@ -4,13 +4,12 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2001 Pekka Riikonen
+  Copyright (C) 2001 - 2005 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-  
+  the Free Software Foundation; version 2 of the License.
+
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 */
 /* $Id$ */
 
-#include "clientlibincludes.h"
-
-typedef struct {
-  SilcClientCommandContext cmd;
-  SilcGetClientCallback completion;
-  char *nickname;
-  char *server;
-  void *context;
-  int found;
-} *GetClientInternal;
-
-SILC_CLIENT_CMD_FUNC(get_client_callback)
-{
-  GetClientInternal i = (GetClientInternal)context;
-  SilcClientEntry *clients;
-  uint32 clients_count;
-
-  /* Get the clients */
-  clients = silc_client_get_clients_local(i->cmd->client, i->cmd->conn,
-                                         i->nickname, i->server,
-                                         &clients_count);
-  if (clients) {
-    i->completion(i->cmd->client, i->cmd->conn, clients, 
-                 clients_count, i->context);
-    i->found = TRUE;
-    silc_free(clients);
-  }
-}
+#include "silc.h"
+#include "silcclient.h"
+#include "client_internal.h"
 
-static void silc_client_get_client_destructor(void *context)
-{
-  GetClientInternal i = (GetClientInternal)context;
-
-  if (i->found == FALSE)
-    i->completion(i->cmd->client, i->cmd->conn, NULL, 0, i->context);
-
-  silc_client_command_free(i->cmd);
-  if (i->nickname)
-    silc_free(i->nickname);
-  if (i->server)
-    silc_free(i->server);
-  silc_free(i);
-}
-
-/* Finds client entry or entries by the `nickname' and `server'. The 
-   completion callback will be called when the client entries has been found.
-
-   Note: this function is always asynchronous and resolves the client
-   information from the server. Thus, if you already know the client
-   information then use the silc_client_get_client_by_id function to
-   get the client entry since this function may be very slow and should
-   be used only to initially get the client entries. */
-
-void silc_client_get_clients(SilcClient client,
-                            SilcClientConnection conn,
-                            const char *nickname,
-                            const char *server,
-                            SilcGetClientCallback completion,
-                            void *context)
-{
-  char ident[512];
-  SilcClientCommandContext ctx;
-  GetClientInternal i = silc_calloc(1, sizeof(*i));
-      
-  /* No ID found. Do query from the server. The query is done by 
-     sending simple IDENTIFY command to the server. */
-  ctx = silc_client_command_alloc();
-  ctx->client = client;
-  ctx->conn = conn;
-  ctx->command = silc_client_command_find("IDENTIFY");
-  memset(ident, 0, sizeof(ident));
-  snprintf(ident, sizeof(ident), "IDENTIFY %s", nickname);
-  silc_parse_command_line(ident, &ctx->argv, &ctx->argv_lens, 
-                         &ctx->argv_types, &ctx->argc, 2);
-
-  i->cmd = silc_client_command_dup(ctx);
-  i->nickname = nickname ? strdup(nickname) : NULL;
-  i->server = server ? strdup(server) : NULL;
-  i->completion = completion;
-  i->context = context;
+/******************************************************************************
 
-  /* Call the command */
-  ctx->command->cb(ctx, NULL);
+                         Client Searching Locally
 
-  /* Add pending callback */
-  silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
-                             conn->cmd_ident, 
-                             silc_client_get_client_destructor,
-                             silc_client_command_get_client_callback, 
-                             (void *)i);
-}
+******************************************************************************/
 
 /* Same as silc_client_get_clients function but does not resolve anything
    from the server. This checks local cache and returns all matching
@@ -124,20 +41,35 @@ SilcClientEntry *silc_client_get_clients_local(SilcClient client,
                                               SilcClientConnection conn,
                                               const char *nickname,
                                               const char *format,
-                                              uint32 *clients_count)
+                                              SilcUInt32 *clients_count)
 {
   SilcIDCacheEntry id_cache;
   SilcIDCacheList list = NULL;
   SilcClientEntry entry, *clients;
   int i = 0;
-  bool found = FALSE;
+  SilcBool found = FALSE;
+  char *nicknamec;
+
+  assert(client && conn);
+  if (!nickname)
+    return NULL;
+
+  /* Normalize nickname for search */
+  nicknamec = silc_identifier_check(nickname, strlen(nickname),
+                                   SILC_STRING_UTF8, 128, NULL);
+  if (!nicknamec)
+    return NULL;
 
   /* Find ID from cache */
-  if (!silc_idcache_find_by_name(conn->client_cache, (char *)nickname, &list))
+  if (!silc_idcache_find_by_name(conn->internal->client_cache, nicknamec,
+                                &list)) {
+    silc_free(nicknamec);
     return NULL;
+  }
 
   if (!silc_idcache_list_count(list)) {
     silc_idcache_list_free(list);
+    silc_free(nicknamec);
     return NULL;
   }
 
@@ -158,14 +90,14 @@ SilcClientEntry *silc_client_get_clients_local(SilcClient client,
     silc_idcache_list_first(list, &id_cache);
     while (id_cache) {
       entry = (SilcClientEntry)id_cache->context;
-      if (strcasecmp(entry->nickname, format)) {
+      if (!silc_utf8_strcasecmp(entry->nickname, format)) {
        if (!silc_idcache_list_next(list, &id_cache)) {
          break;
        } else {
          continue;
        }
       }
-      
+
       clients[i++] = id_cache->context;
       found = TRUE;
       if (!silc_idcache_list_next(list, &id_cache))
@@ -173,6 +105,8 @@ SilcClientEntry *silc_client_get_clients_local(SilcClient client,
     }
   }
 
+  silc_free(nicknamec);
+
   if (list)
     silc_idcache_list_free(list);
 
@@ -186,14 +120,364 @@ SilcClientEntry *silc_client_get_clients_local(SilcClient client,
   return clients;
 }
 
+
+/******************************************************************************
+
+                        Client Resolving from Server
+
+******************************************************************************/
+
 typedef struct {
   SilcClient client;
   SilcClientConnection conn;
-  uint32 list_count;
+  SilcGetClientCallback completion;
+  void *context;
+  char *nickname;
+  SilcClientEntry *clients;
+  SilcUInt32 clients_count;
+} *GetClientInternal;
+
+/* Completion for IDENTIFY */
+
+SILC_CLIENT_CMD_FUNC(get_client_callback)
+{
+  GetClientInternal i = (GetClientInternal)context;
+  SilcClientEntry *clients;
+  SilcUInt32 clients_count;
+
+  /* Get the clients */
+  clients = silc_client_get_clients_local(i->client, i->conn,
+                                         i->nickname, NULL,
+                                         &clients_count);
+  if (clients) {
+    i->completion(i->client, i->conn, clients, clients_count, i->context);
+    silc_free(clients);
+  } else {
+    i->completion(i->client, i->conn, NULL, 0, i->context);
+  }
+
+  silc_free(i->nickname);
+  silc_free(i);
+}
+
+/* Completion for WHOIS */
+
+SILC_CLIENT_CMD_FUNC(get_client_callback_wc)
+{
+  GetClientInternal i = (GetClientInternal)context;
+  SilcClientCommandReplyContext cmd = context2;
+  SilcClientID *client_id = NULL;
+  SilcClientEntry client_entry = NULL;
+  unsigned char *id_data;
+  SilcUInt32 len;
+
+  /* Get the client entry just returned from server */
+  id_data = silc_argument_get_arg_type(cmd->args, 2, &len);
+  if (id_data)
+    client_id = silc_id_payload_parse_id(id_data, len, NULL);
+  if (client_id)
+    client_entry = silc_client_get_client_by_id(i->client,
+                                               i->conn, client_id);
+  if (!client_entry) {
+    if (!SILC_STATUS_IS_ERROR(cmd->status) &&
+       cmd->status != SILC_STATUS_OK &&
+       cmd->status != SILC_STATUS_LIST_END) {
+      silc_free(client_id);
+      return;
+    }
+
+    i->completion(i->client, i->conn, i->clients, i->clients_count,
+                 i->context);
+    silc_free(client_id);
+    silc_free(i->clients);
+    silc_free(i->nickname);
+    silc_free(i);
+    return;
+  }
+
+  /* Save the client */
+  i->clients = silc_realloc(i->clients,
+                           (sizeof(*i->clients) * (i->clients_count + 1)));
+  i->clients[i->clients_count] = client_entry;
+  i->clients_count++;
+
+  /* Return if more data is expected */
+  if (cmd->status != SILC_STATUS_OK &&
+      cmd->status != SILC_STATUS_LIST_END) {
+    silc_free(client_id);
+    return;
+  }
+
+  i->completion(i->client, i->conn, i->clients, i->clients_count,
+               i->context);
+
+  silc_free(client_id);
+  silc_free(i->clients);
+  silc_free(i->nickname);
+  silc_free(i);
+}
+
+/* Our own WHOIS reply processor. */
+
+SILC_CLIENT_CMD_FUNC(get_client_callback_w)
+{
+  SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
+  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  if (!silc_command_get_status(cmd->payload, NULL, NULL)) {
+    if (SILC_STATUS_IS_ERROR(cmd->status))
+      goto out;
+    if (cmd->status == SILC_STATUS_LIST_END)
+      goto out;
+    goto err;
+  }
+
+  /* Save WHOIS info */
+  silc_client_command_reply_whois_save(cmd, cmd->status, FALSE);
+
+  /* Call pending completion for each reply */
+  if (cmd->status != SILC_STATUS_OK &&
+      cmd->status != SILC_STATUS_LIST_END) {
+    if (cmd->callbacks[0].callback)
+      (*cmd->callbacks[0].callback)(cmd->callbacks[0].context, cmd);
+    silc_client_command_reply_free(cmd);
+    return;
+  }
+
+ out:
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_WHOIS);
+
+ err:
+  /* If we received notify for invalid ID we'll remove the ID if we
+     have it cached. */
+  if (cmd->error == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) {
+    SilcClientEntry client_entry;
+    SilcUInt32 tmp_len;
+    unsigned char *tmp =
+      silc_argument_get_arg_type(silc_command_get_args(cmd->payload),
+                                2, &tmp_len);
+    if (tmp) {
+      SilcClientID *client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
+      if (client_id) {
+       client_entry = silc_client_get_client_by_id(cmd->client, conn,
+                                                   client_id);
+       if (client_entry)
+         silc_client_del_client(cmd->client, conn, client_entry);
+       silc_free(client_id);
+      }
+    }
+  }
+
+  /* Unregister this command reply */
+  silc_client_command_unregister(cmd->client, SILC_COMMAND_WHOIS,
+                                NULL, silc_client_command_reply_whois_i,
+                                cmd->ident);
+  silc_client_command_reply_free(cmd);
+}
+
+/* Finds client entry or entries by the `nickname' and `server'. The
+   completion callback will be called when the client entries has been found.
+
+   Note: this function is always asynchronous and resolves the client
+   information from the server. Thus, if you already know the client
+   information then use the silc_client_get_client_by_id function to
+   get the client entry since this function may be very slow and should
+   be used only to initially get the client entries. */
+
+void silc_client_get_clients_i(SilcClient client,
+                              SilcClientConnection conn,
+                              SilcCommand command,
+                              const char *nickname,
+                              const char *server,
+                              SilcBuffer attributes,
+                              SilcGetClientCallback completion,
+                              void *context)
+{
+  GetClientInternal i;
+  int len;
+  char *userhost = NULL;
+
+  assert(client && conn);
+
+  if (!nickname && !attributes)
+    return;
+
+  i = silc_calloc(1, sizeof(*i));
+  i->client = client;
+  i->conn = conn;
+  i->nickname = nickname ? strdup(nickname) : NULL;
+  i->completion = completion;
+  i->context = context;
+
+  if (nickname && server) {
+    len = strlen(nickname) + strlen(server) + 3;
+    userhost = silc_calloc(len, sizeof(*userhost));
+    silc_strncat(userhost, len, nickname, strlen(nickname));
+    silc_strncat(userhost, len, "@", 1);
+    silc_strncat(userhost, len, server, strlen(server));
+  } else if (nickname) {
+    userhost = silc_memdup(nickname, strlen(nickname));
+  }
+
+  /* Register our own command reply for this command */
+  if (command == SILC_COMMAND_IDENTIFY) {
+    silc_client_command_register(client, command, NULL, NULL,
+                                silc_client_command_reply_identify_i, 0,
+                                ++conn->cmd_ident);
+    /* Send the command */
+    silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY,
+                            conn->cmd_ident, 1, 1, userhost,
+                            strlen(userhost));
+
+    /* Add pending callback */
+    silc_client_command_pending(conn, command, conn->cmd_ident,
+                               silc_client_command_get_client_callback,
+                               (void *)i);
+  } else {
+    silc_client_command_register(client, command, NULL, NULL,
+                                silc_client_command_get_client_callback_w, 0,
+                                ++conn->cmd_ident);
+    /* Send the command */
+    silc_client_command_send(client, conn, command, conn->cmd_ident, 2,
+                            1, userhost, userhost ? strlen(userhost) : 0,
+                            3, attributes ? attributes->data : NULL,
+                            attributes ? attributes->len : 0);
+
+    /* Add pending callback */
+    silc_client_command_pending(conn, command, conn->cmd_ident,
+                               silc_client_command_get_client_callback_wc,
+                               (void *)i);
+  }
+  silc_free(userhost);
+}
+
+void silc_client_get_clients(SilcClient client,
+                            SilcClientConnection conn,
+                            const char *nickname,
+                            const char *server,
+                            SilcGetClientCallback completion,
+                            void *context)
+{
+  silc_client_get_clients_i(client, conn, SILC_COMMAND_IDENTIFY,
+                           nickname, server, NULL,
+                           completion, context);
+}
+
+void silc_client_get_clients_whois(SilcClient client,
+                                  SilcClientConnection conn,
+                                  const char *nickname,
+                                  const char *server,
+                                  SilcBuffer attributes,
+                                  SilcGetClientCallback completion,
+                                  void *context)
+{
+  silc_client_get_clients_i(client, conn, SILC_COMMAND_WHOIS,
+                           nickname, server, attributes,
+                           completion, context);
+}
+
+/* The old style function to find client entry. This is used by the
+   library internally. If `query' is TRUE then the client information is
+   requested by the server. The pending command callback must be set
+   by the caller. */
+/* XXX This function should be removed */
+
+SilcClientEntry silc_idlist_get_client(SilcClient client,
+                                      SilcClientConnection conn,
+                                      const char *nickname,
+                                      const char *format,
+                                      SilcBool query)
+{
+  SilcIDCacheEntry id_cache;
+  SilcIDCacheList list = NULL;
+  SilcClientEntry entry = NULL;
+  char *nicknamec;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  /* Normalize nickname for search */
+  nicknamec = silc_identifier_check(nickname, strlen(nickname),
+                                   SILC_STRING_UTF8, 128, NULL);
+  if (!nicknamec)
+    return NULL;
+
+  /* Find ID from cache */
+  if (!silc_idcache_find_by_name(conn->internal->client_cache,
+                                nicknamec, &list)) {
+  identify:
+
+    if (query) {
+      SILC_LOG_DEBUG(("Requesting Client ID from server"));
+
+      /* Register our own command reply for this command */
+      silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
+                                  silc_client_command_reply_identify_i, 0,
+                                  ++conn->cmd_ident);
+
+      /* Send the command */
+      silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY,
+                              conn->cmd_ident, 1, 1, nickname,
+                              strlen(nickname));
+
+      if (list)
+       silc_idcache_list_free(list);
+
+      silc_free(nicknamec);
+      return NULL;
+    }
+
+    silc_free(nicknamec);
+    return NULL;
+  }
+
+  if (!format) {
+    /* Take first found cache entry */
+    if (!silc_idcache_list_first(list, &id_cache))
+      goto identify;
+
+    entry = (SilcClientEntry)id_cache->context;
+  } else {
+    /* Check multiple cache entries for match */
+    silc_idcache_list_first(list, &id_cache);
+    while (id_cache) {
+      entry = (SilcClientEntry)id_cache->context;
+
+      if (!silc_utf8_strcasecmp(entry->nickname, format)) {
+       if (!silc_idcache_list_next(list, &id_cache)) {
+         entry = NULL;
+         break;
+       } else {
+         entry = NULL;
+         continue;
+       }
+      }
+
+      break;
+    }
+
+    /* If match weren't found, request it */
+    if (!entry)
+      goto identify;
+  }
+
+  silc_free(nicknamec);
+
+  if (list)
+    silc_idcache_list_free(list);
+
+  return entry;
+}
+
+typedef struct {
+  SilcClient client;
+  SilcClientConnection conn;
+  SilcUInt32 list_count;
   SilcBuffer client_id_list;
   SilcGetClientCallback completion;
   void *context;
-  int found;
+  int res_count;
 } *GetClientsByListInternal;
 
 SILC_CLIENT_CMD_FUNC(get_clients_list_callback)
@@ -202,53 +486,56 @@ SILC_CLIENT_CMD_FUNC(get_clients_list_callback)
   SilcIDCacheEntry id_cache = NULL;
   SilcBuffer client_id_list = i->client_id_list;
   SilcClientEntry *clients = NULL;
-  uint32 clients_count = 0;
+  SilcUInt32 clients_count = 0;
+  SilcBool found = FALSE;
   int c;
 
   SILC_LOG_DEBUG(("Start"));
 
+  if (i->res_count) {
+    i->res_count--;
+    if (i->res_count)
+      return;
+  }
+
+  SILC_LOG_DEBUG(("Resolved all clients"));
+
+  clients = silc_calloc(i->list_count, sizeof(*clients));
+
   for (c = 0; c < i->list_count; c++) {
-    uint16 idp_len;
+    SilcUInt16 idp_len;
     SilcClientID *client_id;
 
     /* Get Client ID */
     SILC_GET16_MSB(idp_len, client_id_list->data + 2);
     idp_len += 4;
-    client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
-    if (!client_id)
+    client_id = silc_id_payload_parse_id(client_id_list->data, idp_len, NULL);
+    if (!client_id) {
+      silc_buffer_pull(client_id_list, idp_len);
       continue;
+    }
 
     /* Get the client entry */
-    if (silc_idcache_find_by_id_one_ext(i->conn->client_cache, 
-                                       (void *)client_id, 
-                                       NULL, NULL, 
+    if (silc_idcache_find_by_id_one_ext(i->conn->internal->client_cache,
+                                       (void *)client_id,
+                                       NULL, NULL,
                                        silc_hash_client_id_compare, NULL,
                                        &id_cache)) {
-      clients = silc_realloc(clients, sizeof(*clients) * 
-                            (clients_count + 1));
       clients[clients_count] = (SilcClientEntry)id_cache->context;
       clients_count++;
-      i->found = TRUE;
+      found = TRUE;
     }
 
     silc_free(client_id);
     silc_buffer_pull(client_id_list, idp_len);
   }
 
-  if (i->found) {
+  if (found) {
     i->completion(i->client, i->conn, clients, clients_count, i->context);
     silc_free(clients);
-  }
-}
-
-static void silc_client_get_clients_list_destructor(void *context)
-{
-  GetClientsByListInternal i = (GetClientsByListInternal)context;
-
-  SILC_LOG_DEBUG(("Start"));
-
-  if (i->found == FALSE)
+  } else {
     i->completion(i->client, i->conn, NULL, 0, i->context);
+  }
 
   if (i->client_id_list)
     silc_buffer_free(i->client_id_list);
@@ -264,7 +551,7 @@ static void silc_client_get_clients_list_destructor(void *context)
 
 void silc_client_get_clients_by_list(SilcClient client,
                                     SilcClientConnection conn,
-                                    uint32 list_count,
+                                    SilcUInt32 list_count,
                                     SilcBuffer client_id_list,
                                     SilcGetClientCallback completion,
                                     void *context)
@@ -272,8 +559,11 @@ void silc_client_get_clients_by_list(SilcClient client,
   SilcIDCacheEntry id_cache = NULL;
   int i;
   unsigned char **res_argv = NULL;
-  uint32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
+  SilcUInt32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
   GetClientsByListInternal in;
+  SilcBool wait_res = FALSE;
+
+  assert(client && conn && client_id_list);
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -286,42 +576,54 @@ void silc_client_get_clients_by_list(SilcClient client,
   in->context = context;
 
   for (i = 0; i < list_count; i++) {
-    uint16 idp_len;
+    SilcUInt16 idp_len;
     SilcClientID *client_id;
     SilcClientEntry entry;
+    SilcBool ret;
 
     /* Get Client ID */
     SILC_GET16_MSB(idp_len, client_id_list->data + 2);
     idp_len += 4;
-    client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
-    if (!client_id)
+    client_id = silc_id_payload_parse_id(client_id_list->data, idp_len, NULL);
+    if (!client_id) {
+      silc_buffer_pull(client_id_list, idp_len);
       continue;
+    }
 
     /* Check if we have this client cached already. */
-    id_cache = NULL;
-    silc_idcache_find_by_id_one_ext(conn->client_cache, (void *)client_id, 
-                                   NULL, NULL, 
-                                   silc_hash_client_id_compare, NULL,
-                                   &id_cache);
+    ret =
+      silc_idcache_find_by_id_one_ext(conn->internal->client_cache,
+                                     (void *)client_id, NULL, NULL,
+                                     silc_hash_client_id_compare, NULL,
+                                     &id_cache);
 
     /* If we don't have the entry or it has incomplete info, then resolve
        it from the server. */
-    entry = id_cache ? (SilcClientEntry)id_cache->context : NULL;
-    if (!id_cache || !entry->nickname) {
+    if (!ret || !((SilcClientEntry)id_cache->context)->nickname) {
+      entry = ret ? (SilcClientEntry)id_cache->context : NULL;
 
       if (entry) {
        if (entry->status & SILC_CLIENT_STATUS_RESOLVING) {
-         client_entry->status &= ~SILC_CLIENT_STATUS_RESOLVING;
+         /* Attach to this resolving and wait until it finishes */
+         silc_client_command_pending(
+                           conn, SILC_COMMAND_NONE,
+                           entry->resolve_cmd_ident,
+                           silc_client_command_get_clients_list_callback,
+                           (void *)in);
+         wait_res = TRUE;
+         in->res_count++;
+
          silc_free(client_id);
          silc_buffer_pull(client_id_list, idp_len);
          continue;
        }
 
        entry->status |= SILC_CLIENT_STATUS_RESOLVING;
+       entry->resolve_cmd_ident = conn->cmd_ident + 1;
       }
 
       /* No we don't have it, query it from the server. Assemble argument
-        table that will be sent fr the IDENTIFY command later. */
+        table that will be sent for the IDENTIFY command later. */
       res_argv = silc_realloc(res_argv, sizeof(*res_argv) *
                              (res_argc + 1));
       res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) *
@@ -338,6 +640,9 @@ void silc_client_get_clients_by_list(SilcClient client,
     silc_buffer_pull(client_id_list, idp_len);
   }
 
+  silc_buffer_push(client_id_list, client_id_list->data -
+                  client_id_list->head);
+
   /* Query the client information from server if the list included clients
      that we don't know about. */
   if (res_argc) {
@@ -347,18 +652,21 @@ void silc_client_get_clients_by_list(SilcClient client,
     res_cmd = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
                                          res_argc, res_argv, res_argv_lens,
                                          res_argv_types, ++conn->cmd_ident);
-    silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND, 
+    silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND,
                            NULL, 0, NULL, NULL, res_cmd->data, res_cmd->len,
                            TRUE);
 
-    silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
-                               conn->cmd_ident, 
-                               silc_client_get_clients_list_destructor,
-                               silc_client_command_get_clients_list_callback, 
+    /* Register our own command reply for this command */
+    silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
+                                silc_client_command_reply_identify_i, 0,
+                                conn->cmd_ident);
+
+    /* Process the applications request after reply has been received  */
+    silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, conn->cmd_ident,
+                               silc_client_command_get_clients_list_callback,
                                (void *)in);
+    in->res_count++;
 
-    silc_buffer_push(client_id_list, client_id_list->data - 
-                    client_id_list->head);
     silc_buffer_free(res_cmd);
     silc_free(res_argv);
     silc_free(res_argv_lens);
@@ -366,95 +674,180 @@ void silc_client_get_clients_by_list(SilcClient client,
     return;
   }
 
-  silc_buffer_push(client_id_list, client_id_list->data - 
-                  client_id_list->head);
+  if (wait_res)
+    return;
 
   /* We have the clients in cache, get them and call the completion */
   silc_client_command_get_clients_list_callback((void *)in, NULL);
 }
 
-/* The old style function to find client entry. This is used by the
-   library internally. If `query' is TRUE then the client information is
-   requested by the server. The pending command callback must be set
-   by the caller. */
+typedef struct {
+  SilcClient client;
+  SilcClientConnection conn;
+  SilcChannelID channel_id;
+  SilcGetClientCallback completion;
+  void *context;
+  int res_count;
+} *GetClientsByChannelInternal;
 
-SilcClientEntry silc_idlist_get_client(SilcClient client,
-                                      SilcClientConnection conn,
-                                      const char *nickname,
-                                      const char *format,
-                                      bool query)
+SILC_CLIENT_CMD_FUNC(get_clients_by_channel_cb)
 {
-  SilcIDCacheEntry id_cache;
-  SilcIDCacheList list = NULL;
-  SilcClientEntry entry = NULL;
+  GetClientsByChannelInternal i = context;
+  SilcClientEntry *clients = NULL;
+  SilcUInt32 clients_count = 0;
+  SilcBool found = FALSE;
+  SilcChannelEntry channel;
+  SilcHashTableList htl;
+  SilcChannelUser chu;
+
+  if (i->res_count) {
+    i->res_count--;
+    if (i->res_count)
+      return;
+  }
+
+  channel = silc_client_get_channel_by_id(i->client, i->conn, &i->channel_id);
+  if (channel && !silc_hash_table_count(channel->user_list)) {
+    clients = silc_calloc(silc_hash_table_count(channel->user_list),
+                         sizeof(*clients));
+    silc_hash_table_list(channel->user_list, &htl);
+    while (silc_hash_table_get(&htl, NULL, (void *)&chu))
+      clients[clients_count++] = chu->client;
+    silc_hash_table_list_reset(&htl);
+    found = TRUE;
+  }
+
+  if (found) {
+    i->completion(i->client, i->conn, clients, clients_count, i->context);
+    silc_free(clients);
+  } else {
+    i->completion(i->client, i->conn, NULL, 0, i->context);
+  }
+
+  silc_free(i);
+}
+
+/* Gets client entries by the channel entry indicated by `channel'.  Thus,
+   it resolves the clients currently on that channel. */
+
+void silc_client_get_clients_by_channel(SilcClient client,
+                                       SilcClientConnection conn,
+                                       SilcChannelEntry channel,
+                                       SilcGetClientCallback completion,
+                                       void *context)
+{
+  GetClientsByChannelInternal in;
+  SilcHashTableList htl;
+  SilcChannelUser chu;
+  SilcClientEntry entry;
+  unsigned char **res_argv = NULL;
+  SilcUInt32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
+  SilcBuffer idp;
+  SilcBool wait_res = FALSE;
+
+  assert(client && conn && channel);
 
   SILC_LOG_DEBUG(("Start"));
 
-  /* Find ID from cache */
-  if (!silc_idcache_find_by_name(conn->client_cache, (char *)nickname, 
-                                &list)) {
-  identify:
+  in = silc_calloc(1, sizeof(*in));
+  in->client = client;
+  in->conn = conn;
+  in->channel_id = *channel->id;
+  in->completion = completion;
+  in->context = context;
 
-    if (query) {
-      char ident[512];
-      SilcClientCommandContext ctx;
-      
-      SILC_LOG_DEBUG(("Requesting Client ID from server"));
-      
-      /* No ID found. Do query from the server. The query is done by 
-        sending simple IDENTIFY command to the server. */
-      ctx = silc_client_command_alloc();
-      ctx->client = client;
-      ctx->conn = conn;
-      ctx->command = silc_client_command_find("IDENTIFY");
-      memset(ident, 0, sizeof(ident));
-      snprintf(ident, sizeof(ident), "IDENTIFY %s", nickname);
-      silc_parse_command_line(ident, &ctx->argv, &ctx->argv_lens, 
-                             &ctx->argv_types, &ctx->argc, 2);
-      ctx->command->cb(ctx, NULL);
-      
-      if (list)
-       silc_idcache_list_free(list);
+  /* If user list does not exist, send USERS command. */
+  if (!channel->user_list || !silc_hash_table_count(channel->user_list)) {
+    SILC_LOG_DEBUG(("Sending USERS"));
+    silc_client_command_register(client, SILC_COMMAND_USERS, NULL, NULL,
+                                silc_client_command_reply_users_i, 0,
+                                ++conn->cmd_ident);
+    silc_client_command_send(client, conn, SILC_COMMAND_USERS,
+                            conn->cmd_ident, 1, 2, channel->channel_name,
+                            strlen(channel->channel_name));
+    silc_client_command_pending(conn, SILC_COMMAND_USERS, conn->cmd_ident,
+                               silc_client_command_get_clients_by_channel_cb,
+                               in);
+    return;
+  }
 
-      return NULL;
+  silc_hash_table_list(channel->user_list, &htl);
+  while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+    entry = chu->client;
+
+    /* If the entry has incomplete info, then resolve it from the server. */
+    if (!entry->nickname || !entry->realname) {
+      if (entry->status & SILC_CLIENT_STATUS_RESOLVING) {
+       /* Attach to this resolving and wait until it finishes */
+       silc_client_command_pending(
+                           conn, SILC_COMMAND_NONE,
+                           entry->resolve_cmd_ident,
+                           silc_client_command_get_clients_by_channel_cb,
+                           (void *)in);
+       wait_res = TRUE;
+       in->res_count++;
+       continue;
+      }
+      entry->status |= SILC_CLIENT_STATUS_RESOLVING;
+      entry->resolve_cmd_ident = conn->cmd_ident + 1;
+
+      idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+
+      /* No we don't have it, query it from the server. Assemble argument
+        table that will be sent for the WHOIS command later. */
+      res_argv = silc_realloc(res_argv, sizeof(*res_argv) *
+                             (res_argc + 1));
+      res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) *
+                                  (res_argc + 1));
+      res_argv_types = silc_realloc(res_argv_types, sizeof(*res_argv_types) *
+                                   (res_argc + 1));
+      res_argv[res_argc] = silc_memdup(idp->data, idp->len);
+      res_argv_lens[res_argc] = idp->len;
+      res_argv_types[res_argc] = res_argc + 4;
+      res_argc++;
+
+      silc_buffer_free(idp);
     }
-    return NULL;
   }
+  silc_hash_table_list_reset(&htl);
 
-  if (!format) {
-    /* Take first found cache entry */
-    if (!silc_idcache_list_first(list, &id_cache))
-      goto identify;
+  /* Query the client information from server if the list included clients
+     that we don't know about. */
+  if (res_argc) {
+    SilcBuffer res_cmd;
 
-    entry = (SilcClientEntry)id_cache->context;
-  } else {
-    /* Check multiple cache entries for match */
-    silc_idcache_list_first(list, &id_cache);
-    while (id_cache) {
-      entry = (SilcClientEntry)id_cache->context;
+    /* Send the WHOIS command to server */
+    res_cmd = silc_command_payload_encode(SILC_COMMAND_WHOIS,
+                                         res_argc, res_argv, res_argv_lens,
+                                         res_argv_types, ++conn->cmd_ident);
+    silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND,
+                           NULL, 0, NULL, NULL, res_cmd->data, res_cmd->len,
+                           TRUE);
 
-      if (strcasecmp(entry->nickname, format)) {
-       if (!silc_idcache_list_next(list, &id_cache)) {
-         entry = NULL;
-         break;
-       } else {
-         entry = NULL;
-         continue;
-       }
-      }
+    /* Register our own command reply for this command */
+    silc_client_command_register(client, SILC_COMMAND_WHOIS, NULL, NULL,
+                                silc_client_command_reply_whois_i, 0,
+                                conn->cmd_ident);
 
-      break;
-    }
+    /* Process the applications request after reply has been received  */
+    silc_client_command_pending(
+                          conn, SILC_COMMAND_WHOIS, conn->cmd_ident,
+                          silc_client_command_get_clients_by_channel_cb,
+                          (void *)in);
+    in->res_count++;
 
-    /* If match weren't found, request it */
-    if (!entry)
-      goto identify;
+    silc_buffer_free(res_cmd);
+    silc_free(res_argv);
+    silc_free(res_argv_lens);
+    silc_free(res_argv_types);
+    return;
   }
 
-  if (list)
-    silc_idcache_list_free(list);
+  if (wait_res)
+    return;
 
-  return entry;
+  /* We have the clients in cache, get them and call the completion */
+  silc_client_command_get_clients_by_channel_cb((void *)in, NULL);
 }
 
 /* Finds entry for client by the client's ID. Returns the entry or NULL
@@ -466,12 +859,16 @@ SilcClientEntry silc_client_get_client_by_id(SilcClient client,
 {
   SilcIDCacheEntry id_cache;
 
-  SILC_LOG_DEBUG(("Finding client by ID (%s)", 
+  assert(client && conn);
+  if (!client_id)
+    return NULL;
+
+  SILC_LOG_DEBUG(("Finding client by ID (%s)",
                  silc_id_render(client_id, SILC_ID_CLIENT)));
 
   /* Find ID from cache */
-  if (!silc_idcache_find_by_id_one_ext(conn->client_cache, (void *)client_id, 
-                                      NULL, NULL, 
+  if (!silc_idcache_find_by_id_one_ext(conn->internal->client_cache,
+                                      (void *)client_id, NULL, NULL,
                                       silc_hash_client_id_compare, NULL,
                                       &id_cache))
     return NULL;
@@ -487,7 +884,6 @@ typedef struct {
   SilcClientID *client_id;
   SilcGetClientCallback completion;
   void *context;
-  int found;
 } *GetClientByIDInternal;
 
 SILC_CLIENT_CMD_FUNC(get_client_by_id_callback)
@@ -496,23 +892,16 @@ SILC_CLIENT_CMD_FUNC(get_client_by_id_callback)
   SilcClientEntry entry;
 
   /* Get the client */
-  entry = silc_client_get_client_by_id(i->client, i->conn,
-                                      i->client_id);
+  entry = silc_client_get_client_by_id(i->client, i->conn, i->client_id);
   if (entry) {
-    i->completion(i->client, i->conn, &entry, 1, i->context);
-    i->found = TRUE;
+    if (i->completion)
+      i->completion(i->client, i->conn, &entry, 1, i->context);
+  } else {
+    if (i->completion)
+      i->completion(i->client, i->conn, NULL, 0, i->context);
   }
-}
-
-static void silc_client_get_client_by_id_destructor(void *context)
-{
-  GetClientByIDInternal i = (GetClientByIDInternal)context;
-
-  if (i->found == FALSE)
-    i->completion(i->client, i->conn, NULL, 0, i->context);
 
-  if (i->client_id)
-    silc_free(i->client_id);
+  silc_free(i->client_id);
   silc_free(i);
 }
 
@@ -523,41 +912,57 @@ static void silc_client_get_client_by_id_destructor(void *context)
 void silc_client_get_client_by_id_resolve(SilcClient client,
                                          SilcClientConnection conn,
                                          SilcClientID *client_id,
+                                         SilcBuffer attributes,
                                          SilcGetClientCallback completion,
                                          void *context)
 {
   SilcBuffer idp;
   GetClientByIDInternal i = silc_calloc(1, sizeof(*i));
 
-  SILC_LOG_DEBUG(("Start"));
+  assert(client && conn && client_id);
 
-  idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT);
-  silc_client_send_command(client, conn, SILC_COMMAND_WHOIS, 
-                          ++conn->cmd_ident,
-                          1, 3, idp->data, idp->len);
-  silc_buffer_free(idp);
+  SILC_LOG_DEBUG(("Start"));
 
   i->client = client;
   i->conn = conn;
   i->client_id = silc_id_dup(client_id, SILC_ID_CLIENT);
   i->completion = completion;
   i->context = context;
-      
+
+  /* Register our own command reply for this command */
+  silc_client_command_register(client, SILC_COMMAND_WHOIS, NULL, NULL,
+                              silc_client_command_reply_whois_i, 0,
+                              ++conn->cmd_ident);
+
+  /* Send the command */
+  idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT);
+  silc_client_command_send(client, conn, SILC_COMMAND_WHOIS, conn->cmd_ident,
+                          2, 3, attributes ? attributes->data : NULL,
+                          attributes ? attributes->len : 0,
+                          4, idp->data, idp->len);
+  silc_buffer_free(idp);
+
   /* Add pending callback */
-  silc_client_command_pending(conn, SILC_COMMAND_WHOIS, 
-                             conn->cmd_ident, 
-                             silc_client_get_client_by_id_destructor,
-                             silc_client_command_get_client_by_id_callback, 
+  silc_client_command_pending(conn, SILC_COMMAND_WHOIS, conn->cmd_ident,
+                             silc_client_command_get_client_by_id_callback,
                              (void *)i);
 }
 
+
+/******************************************************************************
+
+                Client, Channel and Server entry manipulation
+
+******************************************************************************/
+
+
 /* Creates new client entry and adds it to the ID cache. Returns pointer
    to the new entry. */
 
 SilcClientEntry
 silc_client_add_client(SilcClient client, SilcClientConnection conn,
-                      char *nickname, char *username, 
-                      char *userinfo, SilcClientID *id, uint32 mode)
+                      char *nickname, char *username,
+                      char *userinfo, SilcClientID *id, SilcUInt32 mode)
 {
   SilcClientEntry client_entry;
   char *nick = NULL;
@@ -569,24 +974,45 @@ silc_client_add_client(SilcClient client, SilcClientConnection conn,
   client_entry->id = id;
   client_entry->valid = TRUE;
   silc_parse_userfqdn(nickname, &nick, &client_entry->server);
-  silc_parse_userfqdn(username, &client_entry->username, 
+  silc_parse_userfqdn(username, &client_entry->username,
                      &client_entry->hostname);
   if (userinfo)
     client_entry->realname = strdup(userinfo);
   client_entry->mode = mode;
   if (nick)
     client_entry->nickname = strdup(nick);
+  client_entry->channels = silc_hash_table_alloc(1, silc_hash_ptr, NULL, NULL,
+                                                NULL, NULL, NULL, TRUE);
+
+  /* Normalize nickname */
+  if (client_entry->nickname) {
+    silc_free(nick);
+    nick = silc_identifier_check(client_entry->nickname,
+                                strlen(client_entry->nickname),
+                                SILC_STRING_UTF8, 128, NULL);
+    if (!nick) {
+      silc_free(client_entry->nickname);
+      silc_free(client_entry->username);
+      silc_free(client_entry->hostname);
+      silc_free(client_entry->server);
+      silc_hash_table_free(client_entry->channels);
+      silc_free(client_entry);
+      return NULL;
+    }
+  }
 
   /* Format the nickname */
   silc_client_nickname_format(client, conn, client_entry);
-  
+
   /* Add client to cache, the non-formatted nickname is saved to cache */
-  if (!silc_idcache_add(conn->client_cache, nick, client_entry->id, 
-                       (void *)client_entry, FALSE)) {
+  if (!silc_idcache_add(conn->internal->client_cache, nick, client_entry->id,
+                       (void *)client_entry, 0, NULL)) {
+    silc_free(nick);
     silc_free(client_entry->nickname);
     silc_free(client_entry->username);
     silc_free(client_entry->hostname);
     silc_free(client_entry->server);
+    silc_hash_table_free(client_entry->channels);
     silc_free(client_entry);
     return NULL;
   }
@@ -602,35 +1028,51 @@ void silc_client_update_client(SilcClient client,
                               const char *nickname,
                               const char *username,
                               const char *userinfo,
-                              uint32 mode)
+                              SilcUInt32 mode)
 {
   char *nick = NULL;
 
   SILC_LOG_DEBUG(("Start"));
 
-  if (!client_entry->username && username)
-    silc_parse_userfqdn(username, &client_entry->username, 
+  if ((!client_entry->username || !client_entry->hostname) && username) {
+    silc_free(client_entry->username);
+    silc_free(client_entry->hostname);
+    client_entry->username = NULL;
+    client_entry->hostname = NULL;
+    silc_parse_userfqdn(username, &client_entry->username,
                        &client_entry->hostname);
+  }
   if (!client_entry->realname && userinfo)
     client_entry->realname = strdup(userinfo);
   if (!client_entry->nickname && nickname) {
     silc_parse_userfqdn(nickname, &nick, &client_entry->server);
     client_entry->nickname = strdup(nick);
+
+    /* Normalize nickname */
+    silc_free(nick);
+    nick = silc_identifier_check(client_entry->nickname,
+                                strlen(client_entry->nickname),
+                                SILC_STRING_UTF8, 128, NULL);
+    if (!nick)
+      return;
+
+    /* Format nickname */
     silc_client_nickname_format(client, conn, client_entry);
   }
   client_entry->mode = mode;
 
   if (nick) {
     /* Remove the old cache entry and create a new one */
-    silc_idcache_del_by_context(conn->client_cache, client_entry);
-    silc_idcache_add(conn->client_cache, nick, client_entry->id, 
-                    client_entry, FALSE);
+    silc_idcache_del_by_context(conn->internal->client_cache, client_entry);
+    silc_idcache_add(conn->internal->client_cache, nick, client_entry->id,
+                    client_entry, 0, NULL);
   }
 }
 
 /* Deletes the client entry and frees all memory. */
 
-void silc_client_del_client_entry(SilcClient client, 
+void silc_client_del_client_entry(SilcClient client,
+                                 SilcClientConnection conn,
                                  SilcClientEntry client_entry)
 {
   SILC_LOG_DEBUG(("Start"));
@@ -638,50 +1080,183 @@ void silc_client_del_client_entry(SilcClient client,
   silc_free(client_entry->nickname);
   silc_free(client_entry->username);
   silc_free(client_entry->realname);
+  silc_free(client_entry->hostname);
   silc_free(client_entry->server);
   silc_free(client_entry->id);
+  silc_free(client_entry->fingerprint);
+  if (client_entry->public_key)
+    silc_pkcs_public_key_free(client_entry->public_key);
+  silc_hash_table_free(client_entry->channels);
   if (client_entry->send_key)
     silc_cipher_free(client_entry->send_key);
   if (client_entry->receive_key)
     silc_cipher_free(client_entry->receive_key);
   silc_free(client_entry->key);
+  silc_client_ftp_session_free_client(conn, client_entry);
+  if (client_entry->ke)
+    silc_client_abort_key_agreement(client, conn, client_entry);
   silc_free(client_entry);
 }
 
 /* Removes client from the cache by the client entry. */
 
-bool silc_client_del_client(SilcClient client, SilcClientConnection conn,
+SilcBool silc_client_del_client(SilcClient client, SilcClientConnection conn,
                            SilcClientEntry client_entry)
 {
-  bool ret = silc_idcache_del_by_context(conn->client_cache, client_entry);
-  silc_client_del_client_entry(client, client_entry);
+  SilcBool ret = silc_idcache_del_by_context(conn->internal->client_cache,
+                                        client_entry);
+
+  if (ret) {
+    /* Remove from channels */
+    silc_client_remove_from_channels(client, conn, client_entry);
+
+    /* Free the client entry data */
+    silc_client_del_client_entry(client, conn, client_entry);
+  }
+
   return ret;
 }
 
+/* Add new channel entry to the ID Cache */
+
+SilcChannelEntry silc_client_add_channel(SilcClient client,
+                                        SilcClientConnection conn,
+                                        const char *channel_name,
+                                        SilcUInt32 mode,
+                                        SilcChannelID *channel_id)
+{
+  SilcChannelEntry channel;
+  char *channel_namec;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  channel = silc_calloc(1, sizeof(*channel));
+  channel->channel_name = strdup(channel_name);
+  channel->id = channel_id;
+  channel->mode = mode;
+  channel->user_list = silc_hash_table_alloc(1, silc_hash_ptr, NULL, NULL,
+                                            NULL, NULL, NULL, TRUE);
+
+  /* Normalize channel name */
+  channel_namec = silc_channel_name_check(channel_name, strlen(channel_name),
+                                         SILC_STRING_UTF8, 256, NULL);
+  if (!channel_namec) {
+    silc_free(channel->channel_name);
+    silc_hash_table_free(channel->user_list);
+    silc_free(channel);
+    return NULL;
+  }
+
+  /* Put it to the ID cache */
+  if (!silc_idcache_add(conn->internal->channel_cache, channel_namec,
+                       (void *)channel->id, (void *)channel, 0, NULL)) {
+    silc_free(channel_namec);
+    silc_free(channel->channel_name);
+    silc_hash_table_free(channel->user_list);
+    silc_free(channel);
+    return NULL;
+  }
+
+  return channel;
+}
+
+/* Foreach callbcak to free all users from the channel when deleting a
+   channel entry. */
+
+static void silc_client_del_channel_foreach(void *key, void *context,
+                                           void *user_context)
+{
+  SilcChannelUser chu = (SilcChannelUser)context;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  /* Remove the context from the client's channel hash table as that
+     table and channel's user_list hash table share this same context. */
+  silc_hash_table_del(chu->client->channels, chu->channel);
+  silc_free(chu);
+}
+
 /* Removes channel from the cache by the channel entry. */
 
-bool silc_client_del_channel(SilcClient client, SilcClientConnection conn,
+SilcBool silc_client_del_channel(SilcClient client, SilcClientConnection conn,
                             SilcChannelEntry channel)
 {
-  bool ret = silc_idcache_del_by_context(conn->channel_cache, channel);
+  SilcBool ret = silc_idcache_del_by_context(conn->internal->channel_cache,
+                                        channel);
+
+  SILC_LOG_DEBUG(("Start"));
+
+  /* Free all client entrys from the users list. The silc_hash_table_free
+     will free all the entries so they are not freed at the foreach
+     callback. */
+  silc_hash_table_foreach(channel->user_list, silc_client_del_channel_foreach,
+                         NULL);
+  silc_hash_table_free(channel->user_list);
+
   silc_free(channel->channel_name);
+  silc_free(channel->topic);
   silc_free(channel->id);
+  if (channel->founder_key)
+    silc_pkcs_public_key_free(channel->founder_key);
   silc_free(channel->key);
   if (channel->channel_key)
     silc_cipher_free(channel->channel_key);
   if (channel->hmac)
     silc_hmac_free(channel->hmac);
-  if (channel->old_channel_key)
-    silc_cipher_free(channel->old_channel_key);
-  if (channel->old_hmac)
-    silc_hmac_free(channel->old_hmac);
-  if (channel->rekey_task)
-    silc_schedule_task_del(conn->client->schedule, channel->rekey_task);
+  if (channel->old_channel_keys) {
+    SilcCipher key;
+    silc_dlist_start(channel->old_channel_keys);
+    while ((key = silc_dlist_get(channel->old_channel_keys)) != SILC_LIST_END)
+      silc_cipher_free(key);
+    silc_dlist_uninit(channel->old_channel_keys);
+  }
+  if (channel->old_hmacs) {
+    SilcHmac hmac;
+    silc_dlist_start(channel->old_hmacs);
+    while ((hmac = silc_dlist_get(channel->old_hmacs)) != SILC_LIST_END)
+      silc_hmac_free(hmac);
+    silc_dlist_uninit(channel->old_hmacs);
+  }
+  silc_schedule_task_del_by_context(conn->client->schedule, channel);
   silc_client_del_channel_private_keys(client, conn, channel);
   silc_free(channel);
   return ret;
 }
 
+/* Replaces the channel ID of the `channel' to `new_id'. Returns FALSE
+   if the ID could not be changed. */
+
+SilcBool silc_client_replace_channel_id(SilcClient client,
+                                   SilcClientConnection conn,
+                                   SilcChannelEntry channel,
+                                   SilcChannelID *new_id)
+{
+  char *channel_namec;
+
+  if (!new_id)
+    return FALSE;
+
+  SILC_LOG_DEBUG(("Old Channel ID id(%s)",
+                 silc_id_render(channel->id, SILC_ID_CHANNEL)));
+  SILC_LOG_DEBUG(("New Channel ID id(%s)",
+                 silc_id_render(new_id, SILC_ID_CHANNEL)));
+
+  silc_idcache_del_by_id(conn->internal->channel_cache, channel->id);
+  silc_free(channel->id);
+  channel->id = new_id;
+
+  /* Normalize channel name */
+  channel_namec = silc_channel_name_check(channel->channel_name,
+                                         strlen(channel->channel_name),
+                                         SILC_STRING_UTF8, 256, NULL);
+  if (!channel_namec)
+    return FALSE;
+
+  return silc_idcache_add(conn->internal->channel_cache, channel_namec,
+                         (void *)channel->id, (void *)channel, 0, NULL);
+
+}
+
 /* Finds entry for channel by the channel name. Returns the entry or NULL
    if the entry was not found. It is found only if the client is joined
    to the channel. */
@@ -693,14 +1268,30 @@ SilcChannelEntry silc_client_get_channel(SilcClient client,
   SilcIDCacheEntry id_cache;
   SilcChannelEntry entry;
 
+  assert(client && conn);
+  if (!channel)
+    return NULL;
+
   SILC_LOG_DEBUG(("Start"));
 
-  if (!silc_idcache_find_by_name_one(conn->channel_cache, channel, 
-                                    &id_cache))
+  /* Normalize name for search */
+  channel = silc_channel_name_check(channel, strlen(channel), SILC_STRING_UTF8,
+                                   256, NULL);
+  if (!channel)
+    return NULL;
+
+  if (!silc_idcache_find_by_name_one(conn->internal->channel_cache, channel,
+                                    &id_cache)) {
+    silc_free(channel);
     return NULL;
+  }
 
   entry = (SilcChannelEntry)id_cache->context;
 
+  SILC_LOG_DEBUG(("Found"));
+
+  silc_free(channel);
+
   return entry;
 }
 
@@ -715,50 +1306,105 @@ SilcChannelEntry silc_client_get_channel_by_id(SilcClient client,
   SilcIDCacheEntry id_cache;
   SilcChannelEntry entry;
 
+  assert(client && conn);
+  if (!channel_id)
+    return NULL;
+
   SILC_LOG_DEBUG(("Start"));
 
-  if (!silc_idcache_find_by_id_one(conn->channel_cache, channel_id, 
+  if (!silc_idcache_find_by_id_one(conn->internal->channel_cache, channel_id,
                                   &id_cache))
     return NULL;
 
   entry = (SilcChannelEntry)id_cache->context;
 
+  SILC_LOG_DEBUG(("Found"));
+
   return entry;
 }
 
 typedef struct {
   SilcClient client;
   SilcClientConnection conn;
-  SilcChannelID *channel_id;
+  union {
+    SilcChannelID *channel_id;
+    char *channel_name;
+  } u;
   SilcGetChannelCallback completion;
   void *context;
-  int found;
-} *GetChannelByIDInternal;
+} *GetChannelInternal;
 
-SILC_CLIENT_CMD_FUNC(get_channel_by_id_callback)
+SILC_CLIENT_CMD_FUNC(get_channel_resolve_callback)
 {
-  GetChannelByIDInternal i = (GetChannelByIDInternal)context;
+  GetChannelInternal i = (GetChannelInternal)context;
   SilcChannelEntry entry;
 
   SILC_LOG_DEBUG(("Start"));
 
   /* Get the channel */
-  entry = silc_client_get_channel_by_id(i->client, i->conn,
-                                       i->channel_id);
+  entry = silc_client_get_channel(i->client, i->conn, i->u.channel_name);
   if (entry) {
     i->completion(i->client, i->conn, &entry, 1, i->context);
-    i->found = TRUE;
+  } else {
+    i->completion(i->client, i->conn, NULL, 0, i->context);
   }
+
+  silc_free(i->u.channel_name);
+  silc_free(i);
+}
+
+/* Resolves channel entry from the server by the channel name. */
+
+void silc_client_get_channel_resolve(SilcClient client,
+                                    SilcClientConnection conn,
+                                    char *channel_name,
+                                    SilcGetChannelCallback completion,
+                                    void *context)
+{
+  GetChannelInternal i = silc_calloc(1, sizeof(*i));
+
+  assert(client && conn && channel_name);
+
+  SILC_LOG_DEBUG(("Start"));
+
+  i->client = client;
+  i->conn = conn;
+  i->u.channel_name = strdup(channel_name);
+  i->completion = completion;
+  i->context = context;
+
+  /* Register our own command reply for this command */
+  silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
+                              silc_client_command_reply_identify_i, 0,
+                              ++conn->cmd_ident);
+
+  /* Send the command */
+  silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY,
+                          conn->cmd_ident,
+                          1, 3, channel_name, strlen(channel_name));
+
+  /* Add pending callback */
+  silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, conn->cmd_ident,
+                             silc_client_command_get_channel_resolve_callback,
+                             (void *)i);
 }
 
-static void silc_client_get_channel_by_id_destructor(void *context)
+SILC_CLIENT_CMD_FUNC(get_channel_by_id_callback)
 {
-  GetChannelByIDInternal i = (GetChannelByIDInternal)context;
+  GetChannelInternal i = (GetChannelInternal)context;
+  SilcChannelEntry entry;
 
-  if (i->found == FALSE)
+  SILC_LOG_DEBUG(("Start"));
+
+  /* Get the channel */
+  entry = silc_client_get_channel_by_id(i->client, i->conn, i->u.channel_id);
+  if (entry) {
+    i->completion(i->client, i->conn, &entry, 1, i->context);
+  } else {
     i->completion(i->client, i->conn, NULL, 0, i->context);
+  }
 
-  silc_free(i->channel_id);
+  silc_free(i->u.channel_id);
   silc_free(i);
 }
 
@@ -771,55 +1417,34 @@ void silc_client_get_channel_by_id_resolve(SilcClient client,
                                           void *context)
 {
   SilcBuffer idp;
-  GetChannelByIDInternal i = silc_calloc(1, sizeof(*i));
+  GetChannelInternal i = silc_calloc(1, sizeof(*i));
 
-  SILC_LOG_DEBUG(("Start"));
+  assert(client && conn && channel_id);
 
-  idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL);
-  silc_client_send_command(client, conn, SILC_COMMAND_IDENTIFY, 
-                          ++conn->cmd_ident,
-                          1, 5, idp->data, idp->len);
-  silc_buffer_free(idp);
+  SILC_LOG_DEBUG(("Start"));
 
   i->client = client;
   i->conn = conn;
-  i->channel_id = silc_id_dup(channel_id, SILC_ID_CHANNEL);
+  i->u.channel_id = silc_id_dup(channel_id, SILC_ID_CHANNEL);
   i->completion = completion;
   i->context = context;
-      
-  /* Add pending callback */
-  silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
-                             conn->cmd_ident, 
-                             silc_client_get_channel_by_id_destructor,
-                             silc_client_command_get_channel_by_id_callback, 
-                             (void *)i);
-}
-
-/* Find channel entry by ID. This routine is used internally by the library. */
 
-SilcChannelEntry silc_idlist_get_channel_by_id(SilcClient client,
-                                              SilcClientConnection conn,
-                                              SilcChannelID *channel_id,
-                                              int query)
-{
-  SilcBuffer idp;
-  SilcChannelEntry channel;
+  /* Register our own command reply for this command */
+  silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
+                              silc_client_command_reply_identify_i, 0,
+                              ++conn->cmd_ident);
 
-  SILC_LOG_DEBUG(("Start"));
-
-  channel = silc_client_get_channel_by_id(client, conn, channel_id);
-  if (channel)
-    return channel;
-
-  if (query) {
-    idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL);
-    silc_client_send_command(client, conn, SILC_COMMAND_IDENTIFY, 
-                            ++conn->cmd_ident,
-                            1, 5, idp->data, idp->len);
-    silc_buffer_free(idp);
-  }
+  /* Send the command */
+  idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL);
+  silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY,
+                          conn->cmd_ident,
+                          1, 5, idp->data, idp->len);
+  silc_buffer_free(idp);
 
-  return NULL;
+  /* Add pending callback */
+  silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, conn->cmd_ident,
+                             silc_client_command_get_channel_by_id_callback,
+                             (void *)i);
 }
 
 /* Finds entry for server by the server name. */
@@ -831,14 +1456,28 @@ SilcServerEntry silc_client_get_server(SilcClient client,
   SilcIDCacheEntry id_cache;
   SilcServerEntry entry;
 
+  assert(client && conn);
+  if (!server_name)
+    return NULL;
+
   SILC_LOG_DEBUG(("Start"));
 
-  if (!silc_idcache_find_by_name_one(conn->server_cache, server_name, 
-                                    &id_cache))
+  /* Normalize server name for search */
+  server_name = silc_identifier_check(server_name, strlen(server_name),
+                                     SILC_STRING_UTF8, 256, NULL);
+  if (!server_name)
+    return NULL;
+
+  if (!silc_idcache_find_by_name_one(conn->internal->server_cache,
+                                    server_name, &id_cache)) {
+    silc_free(server_name);
     return NULL;
+  }
 
   entry = (SilcServerEntry)id_cache->context;
 
+  silc_free(server_name);
+
   return entry;
 }
 
@@ -851,10 +1490,14 @@ SilcServerEntry silc_client_get_server_by_id(SilcClient client,
   SilcIDCacheEntry id_cache;
   SilcServerEntry entry;
 
+  assert(client && conn);
+  if (!server_id)
+    return NULL;
+
   SILC_LOG_DEBUG(("Start"));
 
-  if (!silc_idcache_find_by_id_one(conn->server_cache, (void *)server_id, 
-                                  &id_cache))
+  if (!silc_idcache_find_by_id_one(conn->internal->server_cache,
+                                  (void *)server_id, &id_cache))
     return NULL;
 
   entry = (SilcServerEntry)id_cache->context;
@@ -862,12 +1505,62 @@ SilcServerEntry silc_client_get_server_by_id(SilcClient client,
   return entry;
 }
 
+/* Add new server entry */
+
+SilcServerEntry silc_client_add_server(SilcClient client,
+                                      SilcClientConnection conn,
+                                      const char *server_name,
+                                      const char *server_info,
+                                      SilcServerID *server_id)
+{
+  SilcServerEntry server_entry;
+  char *server_namec = NULL;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  server_entry = silc_calloc(1, sizeof(*server_entry));
+  if (!server_entry || !server_id)
+    return NULL;
+
+  server_entry->server_id = server_id;
+  if (server_name)
+    server_entry->server_name = strdup(server_name);
+  if (server_info)
+    server_entry->server_info = strdup(server_info);
+
+  /* Normalize server name */
+  if (server_name) {
+    server_namec = silc_identifier_check(server_name, strlen(server_name),
+                                        SILC_STRING_UTF8, 256, NULL);
+    if (!server_namec) {
+      silc_free(server_entry->server_id);
+      silc_free(server_entry->server_name);
+      silc_free(server_entry->server_info);
+      silc_free(server_entry);
+      return NULL;
+    }
+  }
+
+  /* Add server to cache */
+  if (!silc_idcache_add(conn->internal->server_cache, server_namec,
+                       server_entry->server_id, server_entry, 0, NULL)) {
+    silc_free(server_namec);
+    silc_free(server_entry->server_id);
+    silc_free(server_entry->server_name);
+    silc_free(server_entry->server_info);
+    silc_free(server_entry);
+    return NULL;
+  }
+
+  return server_entry;
+}
+
 /* Removes server from the cache by the server entry. */
 
-bool silc_client_del_server(SilcClient client, SilcClientConnection conn,
+SilcBool silc_client_del_server(SilcClient client, SilcClientConnection conn,
                            SilcServerEntry server)
 {
-  bool ret = silc_idcache_del_by_context(conn->server_cache, server);
+  SilcBool ret = silc_idcache_del_by_context(conn->internal->server_cache, server);
   silc_free(server->server_name);
   silc_free(server->server_info);
   silc_free(server->server_id);
@@ -875,24 +1568,67 @@ bool silc_client_del_server(SilcClient client, SilcClientConnection conn,
   return ret;
 }
 
+/* Updates the `server_entry' with the new information sent as argument. */
+
+void silc_client_update_server(SilcClient client,
+                              SilcClientConnection conn,
+                              SilcServerEntry server_entry,
+                              const char *server_name,
+                              const char *server_info)
+{
+  char *server_namec = NULL;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  if (server_name &&
+      (!server_entry->server_name ||
+       !silc_utf8_strcasecmp(server_entry->server_name, server_name))) {
+
+    silc_idcache_del_by_context(conn->internal->server_cache, server_entry);
+    silc_free(server_entry->server_name);
+    server_entry->server_name = strdup(server_name);
+
+    /* Normalize server name */
+    if (server_name) {
+      server_namec = silc_identifier_check(server_name, strlen(server_name),
+                                          SILC_STRING_UTF8, 256, NULL);
+      if (!server_namec)
+       return;
+
+      silc_idcache_add(conn->internal->server_cache, server_namec,
+                      server_entry->server_id,
+                      server_entry, 0, NULL);
+    }
+  }
+
+  if (server_info &&
+      (!server_entry->server_info ||
+       !silc_utf8_strcasecmp(server_entry->server_info, server_info))) {
+    silc_free(server_entry->server_info);
+    server_entry->server_info = strdup(server_info);
+  }
+}
+
 /* Formats the nickname of the client specified by the `client_entry'.
    If the format is specified by the application this will format the
    nickname and replace the old nickname in the client entry. If the
    format string is not specified then this function has no effect. */
 
-void silc_client_nickname_format(SilcClient client, 
+void silc_client_nickname_format(SilcClient client,
                                 SilcClientConnection conn,
                                 SilcClientEntry client_entry)
 {
   char *cp;
   char *newnick = NULL;
   int i, off = 0, len;
+  SilcBool freebase;
   SilcClientEntry *clients;
-  uint32 clients_count = 0;
+  SilcUInt32 clients_count = 0;
+  SilcClientEntry unformatted = NULL;
 
   SILC_LOG_DEBUG(("Start"));
 
-  if (!client->params->nickname_format[0])
+  if (!client->internal->params->nickname_format[0])
     return;
 
   if (!client_entry->nickname)
@@ -904,17 +1640,36 @@ void silc_client_nickname_format(SilcClient client,
   clients = silc_client_get_clients_local(client, conn,
                                          client_entry->nickname, NULL,
                                          &clients_count);
-  if (!clients && !client->params->nickname_force_format)
+  if (!clients && !client->internal->params->nickname_force_format)
     return;
 
   len = 0;
-  for (i = 0; i < clients_count; i++)
-    if (clients[i]->valid)
+  freebase = TRUE;
+  for (i = 0; i < clients_count; i++) {
+    if (clients[i]->valid && clients[i] != client_entry)
       len++;
-  if (!len)
+    if (clients[i]->valid && clients[i] != client_entry &&
+       silc_utf8_strcasecmp(clients[i]->nickname, client_entry->nickname))
+      freebase = FALSE;
+  }
+  if (!len || freebase)
     return;
 
-  cp = client->params->nickname_format;
+  if (clients_count == 1)
+    unformatted = clients[0];
+  else
+    for (i = 0; i < clients_count; i++)
+      if (silc_utf8_strncasecmp(clients[i]->nickname, client_entry->nickname,
+                               strlen(clients[i]->nickname)))
+       unformatted = clients[i];
+
+  /* If we are changing nickname of our local entry we'll enforce
+     that we will always get the unformatted nickname.  Give our
+     format number to the one that is not formatted now. */
+  if (unformatted && client_entry == conn->local_entry)
+    client_entry = unformatted;
+
+  cp = client->internal->params->nickname_format;
   while (*cp) {
     if (*cp == '%') {
       cp++;
@@ -936,6 +1691,9 @@ void silc_client_nickname_format(SilcClient client,
       if (!client_entry->hostname)
        break;
       len = strcspn(client_entry->hostname, ".");
+      i = strcspn(client_entry->hostname, "-");
+      if (i < len)
+        len = i;
       newnick = silc_realloc(newnick, sizeof(*newnick) * (off + len));
       memcpy(&newnick[off], client_entry->hostname, len);
       off += len;
@@ -977,7 +1735,7 @@ void silc_client_nickname_format(SilcClient client,
          break;
 
        for (i = 0; i < clients_count; i++) {
-         if (strncasecmp(clients[i]->nickname, newnick, off))
+         if (!silc_utf8_strncasecmp(clients[i]->nickname, newnick, off))
            continue;
          if (strlen(clients[i]->nickname) <= off)
            continue;
@@ -985,7 +1743,7 @@ void silc_client_nickname_format(SilcClient client,
          if (num > max)
            max = num;
        }
-       
+
        memset(tmp, 0, sizeof(tmp));
        snprintf(tmp, sizeof(tmp) - 1, "%d", ++max);
        len = strlen(tmp);