updates.
[silc.git] / apps / silcd / server.c
index d7f552866ae93706ba206c5cfa839bb5f9a6ae1f..c1d097e8c3bcd8d4a7f9b759bf0ae479378a1fd0 100644 (file)
@@ -44,6 +44,7 @@ SILC_TASK_CALLBACK(silc_server_timeout_remote);
 SILC_TASK_CALLBACK(silc_server_channel_key_rekey);
 SILC_TASK_CALLBACK(silc_server_failure_callback);
 SILC_TASK_CALLBACK(silc_server_rekey_callback);
+SILC_TASK_CALLBACK(silc_server_get_stats);
 
 /* Allocates a new SILC server object. This has to be done before the server
    can be used. After allocation one must call silc_server_init to initialize
@@ -87,10 +88,15 @@ void silc_server_free(SilcServer server)
     silc_dlist_uninit(server->sim);
 #endif
 
+    silc_server_config_unref(&server->config_ref);
     if (server->rng)
       silc_rng_free(server->rng);
     if (server->pkcs)
       silc_pkcs_free(server->pkcs);
+    if (server->public_key)
+      silc_pkcs_public_key_free(server->public_key);
+    if (server->private_key)
+      silc_pkcs_private_key_free(server->private_key);
     if (server->pending_commands)
       silc_dlist_uninit(server->pending_commands);
     if (server->id_entry)
@@ -102,6 +108,7 @@ void silc_server_free(SilcServer server)
     silc_idcache_free(server->global_list->clients);
     silc_idcache_free(server->global_list->servers);
     silc_idcache_free(server->global_list->channels);
+    silc_hash_table_free(server->watcher_list);
 
     silc_free(server->sockets);
     silc_free(server);
@@ -139,18 +146,15 @@ bool silc_server_init(SilcServer server)
   SilcServerID *id;
   SilcServerEntry id_entry;
   SilcIDListPurge purge;
+  SilcSocketConnection newsocket = NULL;
 
   SILC_LOG_DEBUG(("Initializing server"));
-  assert(server);
-  assert(server->config);
-
-  /* Set public and private keys */
-  if (!server->config->server_info ||
-      !server->config->server_info->public_key ||
-      !server->config->server_info->private_key) {
-    SILC_LOG_ERROR(("Server public key and/or private key does not exist"));
-    return FALSE;
-  }
+
+  server->starttime = time(NULL);
+
+  /* Take config object for us */
+  silc_server_config_ref(&server->config_ref, server->config, 
+                        server->config);
 
   /* Steal public and private key from the config object */
   server->public_key = server->config->server_info->public_key;
@@ -178,7 +182,8 @@ bool silc_server_init(SilcServer server)
   silc_hash_alloc("sha1", &server->sha1hash);
 
   /* Allocate PKCS context for local public and private keys */
-  silc_pkcs_alloc(server->public_key->name, &server->pkcs);
+  if (!silc_pkcs_alloc(server->public_key->name, &server->pkcs))
+    goto err;
   silc_pkcs_public_key_set(server->pkcs, server->public_key);
   silc_pkcs_private_key_set(server->pkcs, server->private_key);
 
@@ -204,77 +209,83 @@ bool silc_server_init(SilcServer server)
   server->global_list->servers = silc_idcache_alloc(0, SILC_ID_SERVER, NULL);
   server->global_list->channels = silc_idcache_alloc(0, SILC_ID_CHANNEL, NULL);
 
+  /* Init watcher list */
+  server->watcher_list = 
+    silc_hash_table_alloc(1, silc_hash_client_id_hash, NULL,
+                         silc_hash_data_compare, (void *)CLIENTID_HASH_LEN,
+                         NULL, NULL, TRUE);
+  if (!server->watcher_list)
+    goto err;
+
   /* Create a listening server */
   if (!silc_server_listen(server, &sock))
     goto err;
 
+  /* Set socket to non-blocking mode */
+  silc_net_set_socket_nonblock(sock);
+  server->sock = sock;
+
   /* Allocate the entire socket list that is used in server. Eventually
      all connections will have entry in this table (it is a table of
      pointers to the actual object that is allocated individually
      later). */
   server->sockets = silc_calloc(server->config->param.connections_max,
                                sizeof(*server->sockets));
+  if (!server->sockets)
+    goto err;
 
-  do {
-    SilcSocketConnection newsocket = NULL;
-
-    /* Set socket to non-blocking mode */
-    silc_net_set_socket_nonblock(sock);
-    server->sock = sock;
-
-    /* Add ourselves also to the socket table. The entry allocated above
-       is sent as argument for fast referencing in the future. */
-    silc_socket_alloc(sock, SILC_SOCKET_TYPE_SERVER, NULL, &newsocket);
-    server->sockets[sock] = newsocket;
-
-    /* Perform name and address lookups to resolve the listenning address
-       and port. */
-    if (!silc_net_check_local_by_sock(sock, &newsocket->hostname,
-                                     &newsocket->ip)) {
-      if ((server->config->require_reverse_lookup && !newsocket->hostname) ||
-         !newsocket->ip) {
-       SILC_LOG_ERROR(("IP/DNS lookup failed for local host %s",
-                       newsocket->hostname ? newsocket->hostname :
-                       newsocket->ip ? newsocket->ip : ""));
-       server->stat.conn_failures++;
-       goto err;
-      }
-      if (!newsocket->hostname)
-       newsocket->hostname = strdup(newsocket->ip);
-    }
-    newsocket->port = silc_net_get_local_port(sock);
-
-    /* Create a Server ID for the server. */
-    silc_id_create_server_id(newsocket->ip, newsocket->port, server->rng, &id);
-    if (!id)
-      goto err;
+  /* Add ourselves also to the socket table. The entry allocated above
+     is sent as argument for fast referencing in the future. */
+  silc_socket_alloc(sock, SILC_SOCKET_TYPE_SERVER, NULL, &newsocket);
+  server->sockets[sock] = newsocket;
 
-    server->id = id;
-    server->id_string = silc_id_id2str(id, SILC_ID_SERVER);
-    server->id_string_len = silc_id_get_len(id, SILC_ID_SERVER);
-    server->id_type = SILC_ID_SERVER;
-    server->server_name = server->config->server_info->server_name;
-    server->config->server_info->server_name = NULL;
-
-    /* Add ourselves to the server list. We don't have a router yet
-       beacuse we haven't established a route yet. It will be done later.
-       For now, NULL is sent as router. This allocates new entry to
-       the ID list. */
-    id_entry =
-      silc_idlist_add_server(server->local_list, strdup(server->server_name),
-                            server->server_type, server->id, NULL, NULL);
-    if (!id_entry) {
-      SILC_LOG_ERROR(("Could not add ourselves to cache"));
+  /* Perform name and address lookups to resolve the listenning address
+     and port. */
+  if (!silc_net_check_local_by_sock(sock, &newsocket->hostname,
+                                   &newsocket->ip)) {
+    if ((server->config->require_reverse_lookup && !newsocket->hostname) ||
+       !newsocket->ip) {
+      SILC_LOG_ERROR(("IP/DNS lookup failed for local host %s",
+                     newsocket->hostname ? newsocket->hostname :
+                     newsocket->ip ? newsocket->ip : ""));
+      server->stat.conn_failures++;
       goto err;
     }
-    id_entry->data.status |= SILC_IDLIST_STATUS_REGISTERED;
+    if (!newsocket->hostname)
+      newsocket->hostname = strdup(newsocket->ip);
+  }
+  newsocket->port = silc_net_get_local_port(sock);
+
+  /* Create a Server ID for the server. */
+  silc_id_create_server_id(newsocket->ip, newsocket->port, server->rng, &id);
+  if (!id)
+    goto err;
+
+  server->id = id;
+  server->id_string = silc_id_id2str(id, SILC_ID_SERVER);
+  server->id_string_len = silc_id_get_len(id, SILC_ID_SERVER);
+  server->id_type = SILC_ID_SERVER;
+  server->server_name = server->config->server_info->server_name;
+  server->config->server_info->server_name = NULL;
+
+  /* Add ourselves to the server list. We don't have a router yet
+     beacuse we haven't established a route yet. It will be done later.
+     For now, NULL is sent as router. This allocates new entry to
+     the ID list. */
+  id_entry =
+    silc_idlist_add_server(server->local_list, strdup(server->server_name),
+                          server->server_type, server->id, NULL, NULL);
+  if (!id_entry) {
+    SILC_LOG_ERROR(("Could not add ourselves to cache"));
+    goto err;
+  }
+  id_entry->data.status |= SILC_IDLIST_STATUS_REGISTERED;
 
-    /* Put the allocated socket pointer also to the entry allocated above
-       for fast back-referencing to the socket list. */
-    newsocket->user_data = (void *)id_entry;
-    id_entry->connection = (void *)newsocket;
-    server->id_entry = id_entry;
-  } while (0);
+  /* Put the allocated socket pointer also to the entry allocated above
+     for fast back-referencing to the socket list. */
+  newsocket->user_data = (void *)id_entry;
+  id_entry->connection = (void *)newsocket;
+  server->id_entry = id_entry;
 
   /* Register protocols */
   silc_server_protocols_register();
@@ -283,7 +294,7 @@ bool silc_server_init(SilcServer server)
      timeout. It expires as soon as the caller calls silc_server_run. This
      task performs authentication protocol and key exchange with our
      primary router. */
-  silc_schedule_task_add(server->schedule, sock,
+  silc_schedule_task_add(server->schedule, 0,
                         silc_server_connect_to_router,
                         (void *)server, 0, 1,
                         SILC_TASK_TIMEOUT,
@@ -339,12 +350,20 @@ bool silc_server_init(SilcServer server)
                         (void *)purge, purge->timeout, 0,
                         SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
 
+  /* If we are normal server we'll retrieve network statisticial information
+     once in a while from the router. */
+  if (server->server_type == SILC_SERVER)
+    silc_schedule_task_add(purge->schedule, 0, silc_server_get_stats,
+                          server, 10, 0, SILC_TASK_TIMEOUT,
+                          SILC_TASK_PRI_LOW);
+
   SILC_LOG_DEBUG(("Server initialized"));
 
   /* We are done here, return succesfully */
   return TRUE;
 
  err:
+  silc_server_config_unref(&server->config_ref);
   silc_net_close_server(sock);
   return FALSE;
 }
@@ -358,28 +377,27 @@ bool silc_server_rehash(SilcServer server)
 {
   SilcServerConfig newconfig;
 
+  SILC_LOG_INFO(("Rehashing server"));
+
   /* Reset the logging system */
   silc_log_quick = TRUE;
   silc_log_flush_all();
 
   /* Start the main rehash phase (read again the config file) */
-  SILC_LOG_INFO(("Rehashing server"));
   newconfig = silc_server_config_alloc(server->config_file);
-
   if (!newconfig) {
     SILC_LOG_ERROR(("Rehash FAILED."));
     return FALSE;
   }
-  silc_server_config_unref(&server->config_ref);
-  server->config = newconfig;
-  silc_server_config_ref(&server->config_ref, newconfig, (void *) newconfig);
+
+  /* Reinit scheduler if necessary */
+  if (newconfig->param.connections_max > server->config->param.connections_max)
+    if (!silc_schedule_reinit(server->schedule, 
+                             newconfig->param.connections_max))
+      return FALSE;
 
   /* Fix the server_name field */
-  if (!strcmp(server->server_name, newconfig->server_info->server_name)) {
-    /* We don't need any update */
-    silc_free(newconfig->server_info->server_name);
-    newconfig->server_info->server_name = NULL;
-  } else {
+  if (strcmp(server->server_name, newconfig->server_info->server_name)) {
     silc_free(server->server_name);
     server->server_name = newconfig->server_info->server_name;
     newconfig->server_info->server_name = NULL;
@@ -387,125 +405,73 @@ bool silc_server_rehash(SilcServer server)
     /* Update the idcache list with a fresh pointer */
     silc_free(server->id_entry->server_name);
     server->id_entry->server_name = strdup(server->server_name);
-    silc_idcache_del_by_context(server->local_list->servers, server->id_entry);
-    silc_idcache_add(server->local_list->servers, server->id_entry->server_name,
-                    server->id_entry->id, server->id_entry, 0, NULL);
+    if (!silc_idcache_del_by_context(server->local_list->servers, 
+                                    server->id_entry))
+      return FALSE;
+    if (!silc_idcache_add(server->local_list->servers,
+                         server->id_entry->server_name,
+                         server->id_entry->id, server->id_entry, 0, NULL))
+      return FALSE;
   }
 
+  /* Set logging */
   silc_server_config_setlogfiles(server);
 
-  /* XXX There is still to implement the publickey change and modules
-     adding (we can't allow modules removing since we can't know which
-     one are actually in use */
-
-  return TRUE;
-}
-
-/* Drop root privileges. If some system call fails, die. */
-
-void silc_server_drop(SilcServer server)
-{
-  /* Are we executing silcd as root or a regular user? */
-  if (geteuid()) {
-    SILC_LOG_DEBUG(("Server started as user"));
+  /* Change new key pair if necessary */
+  if (newconfig->server_info->public_key &&
+      !silc_pkcs_public_key_compare(server->public_key,
+                                   newconfig->server_info->public_key)) {
+    silc_pkcs_public_key_free(server->public_key);
+    silc_pkcs_private_key_free(server->private_key);
+    server->public_key = newconfig->server_info->public_key;
+    server->private_key = newconfig->server_info->private_key;
+    newconfig->server_info->public_key = NULL;
+    newconfig->server_info->private_key = NULL;
+
+    /* Allocate PKCS context for local public and private keys */
+    silc_pkcs_free(server->pkcs);
+    if (!silc_pkcs_alloc(server->public_key->name, &server->pkcs))
+      return FALSE;
+    silc_pkcs_public_key_set(server->pkcs, server->public_key);
+    silc_pkcs_private_key_set(server->pkcs, server->private_key);
   }
-  else {
-    struct passwd *pw;
-    struct group *gr;
-    char *user, *group;
-
-    SILC_LOG_DEBUG(("Server started as root. Dropping privileges."));
-
-    /* Get the values given for user and group in configuration file */
-    user = server->config->server_info->user;
-    group = server->config->server_info->group;
-
-    if (!user || !group) {
-      fprintf(stderr, "Error:"
-       "\tSILC server must not be run as root.  For the security of your\n"
-       "\tsystem it is strongly suggested that you run SILC under dedicated\n"
-       "\tuser account.  Modify the ServerInfo configuration section to run\n"
-       "\tthe server as non-root user.\n");
-      exit(1);
-    }
 
-    /* Check whether the user/group does not begin with a number */
-    if (isdigit(user[0]) || isdigit(group[0])) {
-      SILC_LOG_DEBUG(("User and/or group starts with a number"));
-      fprintf(stderr, "Invalid user and/or group information\n");
-      fprintf(stderr, "Please assign them as names, not numbers\n");
-      exit(1);
-    }
-
-    if (!(pw = getpwnam(user))) {
-      fprintf(stderr, "Error: No such user %s found.\n", user);
-      exit(1);
-    }
-    if (!(gr = getgrnam(group))) {
-      fprintf(stderr, "Error: No such group %s found.\n", group);
-      exit(1);
-    }
+  /* Go through all configured routers after rehash */
+  silc_schedule_task_add(server->schedule, 0,
+                        silc_server_connect_to_router,
+                        (void *)server, 0, 1,
+                        SILC_TASK_TIMEOUT,
+                        SILC_TASK_PRI_NORMAL);
 
-    /* Check whether user and/or group is set to root. If yes, exit
-       immediately. Otherwise, setgid and setuid server to user.group */
-    if ((gr->gr_gid == 0) || (pw->pw_uid == 0)) {
-      fprintf(stderr, "Error:"
-       "\tSILC server must not be run as root.  For the security of your\n"
-       "\tsystem it is strongly suggested that you run SILC under dedicated\n"
-       "\tuser account.  Modify the ServerInfo configuration section to run\n"
-       "\tthe server as non-root user.\n");
-      exit(1);
-    }
+  /* Check whether our router status has changed */
+  if (newconfig->servers) {
+    SilcServerConfigServer *ptr = newconfig->servers;
 
-    SILC_LOG_DEBUG(("Changing to group %s (gid=%u)", group, gr->gr_gid));
-    if (setgid(gr->gr_gid) != 0) {
-      fprintf(stderr, "Error: Failed setgid() to %s (gid=%u). Exiting.\n",
-             group, gr->gr_gid);
-      exit(1);
-    }
-#if defined HAVE_SETGROUPS && defined HAVE_INITGROUPS
-    SILC_LOG_DEBUG(("Removing supplementary groups"));
-    if (setgroups(0, NULL) != 0) {
-      fprintf(stderr, "Error: Failed setgroups() to NULL. Exiting.\n");
-      exit(1);
-    }
-    SILC_LOG_DEBUG(("Setting supplementary groups for user %s", user));
-    if (initgroups(user, gr->gr_gid) != 0) {
-      fprintf(stderr, "Error: Failed initgroups() for user %s (gid=%u). "
-             "Exiting.\n", user, gr->gr_gid);
-      exit(1);
-    }
-#endif
-    SILC_LOG_DEBUG(("Changing to user %s (uid=%u)", user, pw->pw_uid));
-    if (setuid(pw->pw_uid) != 0) {
-      fprintf(stderr, "Error: Failed to setuid() to %s (gid=%u). Exiting.\n",
-              user, pw->pw_uid);
-      exit(1);
+    server->server_type = SILC_ROUTER;
+    while (ptr) {
+      if (ptr->backup_router) {
+       server->server_type = SILC_BACKUP_ROUTER;
+       server->backup_router = TRUE;
+       server->id_entry->server_type = SILC_BACKUP_ROUTER;
+       break;
+      }
+      ptr = ptr->next;
     }
   }
-}
 
-/* Fork server to background */
-
-void silc_server_daemonise(SilcServer server)
-{
-  int i;
-
-  SILC_LOG_DEBUG(("Forking SILC server to background"));
-
-  if ((i = fork()) < 0) {
-    fprintf(stderr, "Error: fork() failed: %s\n", strerror(errno));
-    exit(1);
-  }
+  /* Our old config is gone now. We'll unreference our reference made in
+     silc_server_init and then destroy it since we are destroying it
+     underneath the application (layer which called silc_server_init). */
+  silc_server_config_unref(&server->config_ref);
+  silc_server_config_destroy(server->config);
 
-  if (i) /* Kill the parent */
-    exit(0);
+  /* Take new config context */
+  server->config = newconfig;
+  silc_server_config_ref(&server->config_ref, server->config, server->config);
 
-  server->background = TRUE;
-  setsid();
+  SILC_LOG_DEBUG(("Server rehashed"));
 
-  /* XXX close stdin, stdout, stderr -- before this, check that all writes
-     to stderr are changed to SILC_SERVER_LOG_ERROR() */
+  return TRUE;
 }
 
 /* The heart of the server. This runs the scheduler thus runs the server.
@@ -656,7 +622,7 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_retry)
     return;
   }
 
-  /* we will lookup a fresh pointer later */
+  /* We will lookup a fresh pointer later */
   silc_server_config_unref(&sconn->conn);
 
   /* Wait one before retrying */
@@ -682,7 +648,9 @@ SILC_TASK_CALLBACK(silc_server_connect_router)
   rconn = silc_server_config_find_router_conn(server, sconn->remote_host,
                                              sconn->remote_port);
   if (!rconn) {
-    SILC_LOG_INFO(("Unconfigured server, giving up"));
+    SILC_LOG_INFO(("Unconfigured %s connection %s:%d, cannot connect",
+                  (sconn->backup ? "backup router" : "router"),
+                  sconn->remote_host, sconn->remote_port));
     silc_free(sconn->remote_host);
     silc_free(sconn->backup_replace_ip);
     silc_free(sconn);
@@ -732,9 +700,22 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router)
     SILC_LOG_DEBUG(("We are backup router/normal server"));
   }
 
+  if (!server->config->routers) {
+    /* There wasn't a configured router, we will continue but we don't
+       have a connection to outside world.  We will be standalone server. */
+    SILC_LOG_DEBUG(("No router(s), server will be standalone"));
+    server->standalone = TRUE;
+    return;
+  }
+
+  /* Cancel any possible retry timeouts */
+  silc_schedule_task_del_by_callback(server->schedule,
+                                    silc_server_connect_router);
+  silc_schedule_task_del_by_callback(server->schedule,
+                                    silc_server_connect_to_router_retry);
+
   /* Create the connections to all our routes */
-  ptr = server->config->routers;
-  while (ptr) {
+  for (ptr = server->config->routers; ptr; ptr = ptr->next) {
 
     SILC_LOG_DEBUG(("%s connection [%s] %s:%d",
                    ptr->backup_router ? "Backup router" : "Router",
@@ -742,6 +723,17 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router)
                    ptr->host, ptr->port));
 
     if (ptr->initiator) {
+      /* Check whether we are connected to this host already */
+      if (silc_server_num_sockets_by_remote(server, 
+                                           silc_net_is_ip(ptr->host) ?
+                                           ptr->host : NULL,
+                                           silc_net_is_ip(ptr->host) ?
+                                           NULL : ptr->host, ptr->port,
+                                           SILC_SOCKET_TYPE_ROUTER)) {
+       SILC_LOG_DEBUG(("We are already connected to this router"));
+       continue;
+      }
+
       /* Allocate connection object for hold connection specific stuff. */
       sconn = silc_calloc(1, sizeof(*sconn));
       sconn->server = server;
@@ -756,23 +748,12 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router)
       if (!server->router_conn && !sconn->backup)
        server->router_conn = sconn;
 
-      silc_schedule_task_add(server->schedule, fd,
+      silc_schedule_task_add(server->schedule, 0,
                             silc_server_connect_router,
                             (void *)sconn, 0, 1, SILC_TASK_TIMEOUT,
                             SILC_TASK_PRI_NORMAL);
     }
-
-    if (!ptr->next)
-      return;
-
-    ptr = ptr->next;
   }
-
-  SILC_LOG_DEBUG(("No router(s), server will be standalone"));
-
-  /* There wasn't a configured router, we will continue but we don't
-     have a connection to outside world.  We will be standalone server. */
-  server->standalone = TRUE;
 }
 
 /* Second part of connecting to router(s). Key exchange protocol has been
@@ -802,9 +783,6 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_second)
     if (ctx->ske)
       silc_ske_free(ctx->ske);
     silc_free(ctx->dest_id);
-    silc_server_config_unref(&ctx->cconfig);
-    silc_server_config_unref(&ctx->sconfig);
-    silc_server_config_unref(&ctx->rconfig);
     silc_free(ctx);
     silc_server_config_unref(&sconn->conn);
     silc_free(sconn->remote_host);
@@ -812,8 +790,8 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_second)
     silc_free(sconn);
     silc_schedule_task_del_by_callback(server->schedule,
                                       silc_server_failure_callback);
-    silc_server_disconnect_remote(server, sock, "Server closed connection: "
-                                 "Key exchange failed");
+    silc_server_disconnect_remote(server, sock, 
+                                 SILC_STATUS_ERR_KEY_EXCHANGE_FAILED, NULL);
     return;
   }
 
@@ -843,8 +821,8 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_second)
     silc_free(sconn);
     silc_schedule_task_del_by_callback(server->schedule,
                                       silc_server_failure_callback);
-    silc_server_disconnect_remote(server, sock, "Server closed connection: "
-                                 "Key exchange failed");
+    silc_server_disconnect_remote(server, sock, 
+                                 SILC_STATUS_ERR_KEY_EXCHANGE_FAILED, NULL);
     return;
   }
   silc_ske_free_key_material(ctx->keymat);
@@ -895,10 +873,14 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_second)
       silc_ske_free(ctx->ske);
     silc_free(ctx->dest_id);
     silc_free(ctx);
+    silc_server_config_unref(&sconn->conn);
+    silc_free(sconn->remote_host);
+    silc_free(sconn->backup_replace_ip);
+    silc_free(sconn);
     silc_schedule_task_del_by_callback(server->schedule,
                                       silc_server_failure_callback);
-    silc_server_disconnect_remote(server, sock, "Server closed connection: "
-                                 "Key exchange failed");
+    silc_server_disconnect_remote(server, sock, 
+                                 SILC_STATUS_ERR_KEY_EXCHANGE_FAILED, NULL);
     return;
   }
 
@@ -957,8 +939,8 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_final)
       protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
     /* Error occured during protocol */
     silc_free(ctx->dest_id);
-    silc_server_disconnect_remote(server, sock, "Server closed connection: "
-                                 "Authentication failed");
+    silc_server_disconnect_remote(server, sock, SILC_STATUS_ERR_AUTH_FAILED,
+                                 NULL);
     goto out;
   }
 
@@ -1017,8 +999,8 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_final)
   if (!id_entry) {
     silc_free(ctx->dest_id);
     SILC_LOG_ERROR(("Cannot add new server entry to cache"));
-    silc_server_disconnect_remote(server, sock, "Server closed connection: "
-                                 "Authentication failed");
+    silc_server_disconnect_remote(server, sock, SILC_STATUS_ERR_AUTH_FAILED,
+                                 NULL);
     goto out;
   }
 
@@ -1130,7 +1112,8 @@ silc_server_accept_new_connection_lookup(SilcSocketConnection sock,
                    sock->ip ? sock->ip : ""));
     server->stat.conn_failures++;
     silc_server_disconnect_remote(server, sock,
-                                 "Server closed connection: Unknown host");
+                                 SILC_STATUS_ERR_INCOMPLETE_INFORMATION,
+                                 "Unknown host or IP");
     return;
   }
 
@@ -1155,10 +1138,9 @@ silc_server_accept_new_connection_lookup(SilcSocketConnection sock,
     /* The connection is denied */
     SILC_LOG_INFO(("Connection %s (%s) is denied",
                   sock->hostname, sock->ip));
-    silc_server_disconnect_remote(server, sock, deny->reason ?
-                                 deny->reason :
-                                 "Server closed connection: "
-                                 "Connection refused");
+    silc_server_disconnect_remote(server, sock, 
+                                 SILC_STATUS_ERR_BANNED_FROM_SERVER,
+                                 deny->reason);
     server->stat.conn_failures++;
     return;
   }
@@ -1180,8 +1162,7 @@ silc_server_accept_new_connection_lookup(SilcSocketConnection sock,
     SILC_LOG_INFO(("Connection %s (%s) is not allowed", sock->hostname,
                   sock->ip));
     silc_server_disconnect_remote(server, sock,
-                                 "Server closed connection: "
-                                 "Connection refused");
+                                 SILC_STATUS_ERR_BANNED_FROM_SERVER);
     server->stat.conn_failures++;
     return;
   }
@@ -1250,7 +1231,7 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection)
 
   /* Check for maximum allowed connections */
   if (sock > server->config->param.connections_max) {
-    SILC_LOG_ERROR(("Refusing connection, server is full, try again later"));
+    SILC_LOG_ERROR(("Refusing connection, server is full"));
     server->stat.conn_failures++;
     silc_net_close_connection(sock);
     return;
@@ -1307,8 +1288,9 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_second)
     silc_free(ctx);
     silc_schedule_task_del_by_callback(server->schedule,
                                       silc_server_failure_callback);
-    silc_server_disconnect_remote(server, sock, "Server closed connection: "
-                                 "Key exchange failed");
+    silc_server_disconnect_remote(server, sock, 
+                                 SILC_STATUS_ERR_KEY_EXCHANGE_FAILED,
+                                 NULL);
     server->stat.auth_failures++;
     return;
   }
@@ -1338,8 +1320,8 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_second)
     silc_free(ctx);
     silc_schedule_task_del_by_callback(server->schedule,
                                       silc_server_failure_callback);
-    silc_server_disconnect_remote(server, sock, "Server closed connection: "
-                                 "Key exchange failed");
+    silc_server_disconnect_remote(server, sock, 
+                                 SILC_STATUS_ERR_KEY_EXCHANGE_FAILED, NULL);
     server->stat.auth_failures++;
     return;
   }
@@ -1418,8 +1400,8 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final)
     silc_free(ctx);
     silc_schedule_task_del_by_callback(server->schedule,
                                       silc_server_failure_callback);
-    silc_server_disconnect_remote(server, sock, "Server closed connection: "
-                                 "Authentication failed");
+    silc_server_disconnect_remote(server, sock, SILC_STATUS_ERR_AUTH_FAILED,
+                                 NULL);
     server->stat.auth_failures++;
     return;
   }
@@ -1452,9 +1434,8 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final)
       if (!client) {
        SILC_LOG_ERROR(("Could not add new client to cache"));
        silc_free(sock->user_data);
-       silc_server_disconnect_remote(server, sock,
-                                     "Server closed connection: "
-                                     "Authentication failed");
+       silc_server_disconnect_remote(server, sock, 
+                                     SILC_STATUS_ERR_AUTH_FAILED, NULL);
        server->stat.auth_failures++;
        goto out;
       }
@@ -1462,8 +1443,7 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final)
       /* Statistics */
       server->stat.my_clients++;
       server->stat.clients++;
-      if (server->server_type == SILC_ROUTER)
-       server->stat.cell_clients++;
+      server->stat.cell_clients++;
 
       /* Get connection parameters */
       if (conn->param) {
@@ -1558,9 +1538,8 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final)
       if (!new_server) {
        SILC_LOG_ERROR(("Could not add new server to cache"));
        silc_free(sock->user_data);
-       silc_server_disconnect_remote(server, sock,
-                                     "Server closed connection: "
-                                     "Authentication failed");
+       silc_server_disconnect_remote(server, sock, 
+                                     SILC_STATUS_ERR_AUTH_FAILED, NULL);
        server->stat.auth_failures++;
        goto out;
       }
@@ -1954,13 +1933,26 @@ void silc_server_packet_parse_type(SilcServer server,
   /* Parse the packet type */
   switch (type) {
   case SILC_PACKET_DISCONNECT:
-    SILC_LOG_DEBUG(("Disconnect packet"));
-    if (packet->flags & SILC_PACKET_FLAG_LIST)
-      break;
-    if (silc_string_is_ascii(packet->buffer->data, packet->buffer->len)) {
-      /* Duplicate to null terminate the string. */
-      char *message = silc_memdup(packet->buffer->data, packet->buffer->len);
-      SILC_LOG_ERROR(("%s", message));
+    {
+      SilcStatus status;
+      char *message = NULL;
+
+      SILC_LOG_DEBUG(("Disconnect packet"));
+
+      if (packet->flags & SILC_PACKET_FLAG_LIST)
+       break;
+      if (packet->buffer->len < 1)
+       break;
+
+      status = (SilcStatus)packet->buffer->data[0];
+      if (packet->buffer->len > 1 &&
+         silc_utf8_valid(packet->buffer->data + 1, packet->buffer->len - 1))
+       message = silc_memdup(packet->buffer->data, packet->buffer->len);
+
+      SILC_LOG_ERROR(("Disconnected by %s (%s): %s (%d) %s", 
+                     sock->ip, sock->hostname,
+                     silc_get_status_message(status), status,
+                     message ? message : ""));
       silc_free(message);
     }
     break;
@@ -2363,6 +2355,14 @@ void silc_server_packet_parse_type(SilcServer server,
     silc_server_ftp(server, sock, packet);
     break;
 
+  case SILC_PACKET_RESUME_CLIENT:
+    /* Resume client */
+    SILC_LOG_DEBUG(("Resume Client packet"));
+    if (packet->flags & SILC_PACKET_FLAG_LIST)
+      break;
+    silc_server_resume_client(server, sock, packet);
+    break;
+
   case SILC_PACKET_RESUME_ROUTER:
     /* Resume router packet received. This packet is received for backup
        router resuming protocol. */
@@ -2455,25 +2455,48 @@ void silc_server_close_connection(SilcServer server,
 
 void silc_server_disconnect_remote(SilcServer server,
                                   SilcSocketConnection sock,
-                                  const char *fmt, ...)
+                                  SilcStatus status, ...)
 {
   va_list ap;
-  unsigned char buf[4096];
+  unsigned char buf[512];
+  SilcBuffer buffer;
+  char *cp;
+  int len;
 
   if (!sock)
     return;
 
   memset(buf, 0, sizeof(buf));
-  va_start(ap, fmt);
-  vsprintf(buf, fmt, ap);
+  va_start(ap, status);
+  cp = va_arg(ap, char *);
+  if (cp) {
+    vsnprintf(buf, sizeof(buf) - 1, cp, ap);
+    cp = buf;
+  }
   va_end(ap);
 
   SILC_LOG_DEBUG(("Disconnecting remote host"));
 
   /* Notify remote end that the conversation is over. The notify message
      is tried to be sent immediately. */
+
+  len = 1;
+  if (cp)
+    len += silc_utf8_encoded_len(buf, strlen(buf), SILC_STRING_ASCII);
+
+  buffer = silc_buffer_alloc_size(len);
+  if (!buffer)
+    goto out;
+
+  buffer->data[0] = status;
+  if (cp)
+    silc_utf8_encode(buf, strlen(buf), SILC_STRING_ASCII, buffer->data + 1,
+                    buffer->len - 1);
   silc_server_packet_send(server, sock, SILC_PACKET_DISCONNECT, 0,
-                         buf, strlen(buf), TRUE);
+                         buffer->data, buffer->len, TRUE);
+  silc_buffer_free(buffer);
+
+ out:
   silc_server_packet_queue_purge(server, sock);
 
   /* Mark the connection to be disconnected */
@@ -2526,13 +2549,22 @@ void silc_server_free_client_data(SilcServer server,
     silc_server_remove_from_channels(server, NULL, client,
                                     FALSE, NULL, FALSE);
 
+  /* Check if anyone is watching this nickname */
+  if (server->server_type == SILC_ROUTER)
+    silc_server_check_watcher_list(server, client, NULL,
+                                  SILC_NOTIFY_TYPE_SIGNOFF);
+
+  /* Remove this client from watcher list if it is */
+  silc_server_del_from_watcher_list(server, client);
+
   /* Update statistics */
   server->stat.my_clients--;
   server->stat.clients--;
-  if (server->server_type == SILC_ROUTER)
+  if (server->stat.cell_clients)
     server->stat.cell_clients--;
   SILC_OPER_STATS_UPDATE(client, server, SILC_UMODE_SERVER_OPERATOR);
   SILC_OPER_STATS_UPDATE(client, router, SILC_UMODE_ROUTER_OPERATOR);
+  silc_schedule_task_del_by_context(server->schedule, client);
 
   /* We will not delete the client entry right away. We will take it
      into history (for WHOWAS command) for 5 minutes */
@@ -2543,9 +2575,9 @@ void silc_server_free_client_data(SilcServer server,
                         (void *)i, 300, 0,
                         SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
   client->data.status &= ~SILC_IDLIST_STATUS_REGISTERED;
+  client->mode = 0;
   client->router = NULL;
   client->connection = NULL;
-  client->mode = 0;
 }
 
 /* Frees user_data pointer from socket connection object. This also sends
@@ -2697,9 +2729,9 @@ void silc_server_free_sock_user_data(SilcServer server,
 void silc_server_remove_from_channels(SilcServer server,
                                      SilcSocketConnection sock,
                                      SilcClientEntry client,
-                                     int notify,
-                                     char *signoff_message,
-                                     int keygen)
+                                     bool notify,
+                                     const char *signoff_message,
+                                     bool keygen)
 {
   SilcChannelEntry channel;
   SilcChannelClientEntry chl;
@@ -2712,6 +2744,8 @@ void silc_server_remove_from_channels(SilcServer server,
     return;
 
   clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+  if (!clidp)
+    notify = FALSE;
 
   /* Remove the client from all channels. The client is removed from
      the channels' user list. */
@@ -2719,22 +2753,15 @@ void silc_server_remove_from_channels(SilcServer server,
   while (silc_hash_table_get(&htl, NULL, (void *)&chl)) {
     channel = chl->channel;
 
-    /* Remove channel from client's channel list */
-    silc_hash_table_del(client->channels, channel);
-
-    /* Remove channel if there is no users anymore */
+    /* Remove channel if this is last client leaving the channel, unless
+       the channel is permanent. */
     if (server->server_type == SILC_ROUTER &&
        silc_hash_table_count(channel->user_list) < 2) {
-      if (channel->rekey)
-       silc_schedule_task_del_by_context(server->schedule, channel->rekey);
-      if (silc_idlist_del_channel(server->local_list, channel))
-       server->stat.my_channels--;
-      else
-        silc_idlist_del_channel(server->global_list, channel);
+      silc_server_channel_delete(server, channel);
       continue;
     }
 
-    /* Remove client from channel's client list */
+    silc_hash_table_del(client->channels, channel);
     silc_hash_table_del(channel->user_list, chl->client);
     channel->user_count--;
 
@@ -2748,7 +2775,8 @@ void silc_server_remove_from_channels(SilcServer server,
     server->stat.my_chanclients--;
 
     /* If there is not at least one local user on the channel then we don't
-       need the channel entry anymore, we can remove it safely. */
+       need the channel entry anymore, we can remove it safely, unless the
+       channel is permanent channel */
     if (server->server_type != SILC_ROUTER &&
        !silc_server_channel_has_local(channel)) {
       /* Notify about leaving client if this channel has global users. */
@@ -2760,37 +2788,12 @@ void silc_server_remove_from_channels(SilcServer server,
                                           signoff_message, signoff_message ?
                                           strlen(signoff_message) : 0);
 
-      if (channel->rekey)
-       silc_schedule_task_del_by_context(server->schedule, channel->rekey);
-
-      if (channel->founder_key) {
-       /* The founder auth data exists, do not remove the channel entry */
-       SilcChannelClientEntry chl2;
-       SilcHashTableList htl2;
-
-       channel->disabled = TRUE;
-
-       silc_hash_table_list(channel->user_list, &htl2);
-       while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) {
-         silc_hash_table_del(chl2->client->channels, channel);
-         silc_hash_table_del(channel->user_list, chl2->client);
-         channel->user_count--;
-         silc_free(chl2);
-       }
-       silc_hash_table_list_reset(&htl2);
-       continue;
-      }
-
-      /* Remove the channel entry */
-      if (silc_idlist_del_channel(server->local_list, channel))
-       server->stat.my_channels--;
-      else
-        silc_idlist_del_channel(server->global_list, channel);
+      silc_schedule_task_del_by_context(server->schedule, channel->rekey);
+      silc_server_channel_delete(server, channel);
       continue;
     }
 
-    /* Send notify to channel about client leaving SILC and thus
-       the entire channel. */
+    /* Send notify to channel about client leaving SILC and channel too */
     if (notify)
       silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
                                         SILC_NOTIFY_TYPE_SIGNOFF,
@@ -2799,10 +2802,10 @@ void silc_server_remove_from_channels(SilcServer server,
                                         signoff_message, signoff_message ?
                                         strlen(signoff_message) : 0);
 
+    /* Re-generate channel key if needed */
     if (keygen && !(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
-      /* Re-generate channel key */
       if (!silc_server_create_channel_key(server, channel, 0))
-       goto out;
+       continue;
 
       /* Send the channel key to the channel. The key of course is not sent
         to the client who was removed from the channel. */
@@ -2812,7 +2815,6 @@ void silc_server_remove_from_channels(SilcServer server,
     }
   }
 
- out:
   silc_hash_table_list_reset(&htl);
   silc_buffer_free(clidp);
 }
@@ -2823,44 +2825,33 @@ void silc_server_remove_from_channels(SilcServer server,
    last client leaves the channel. If `notify' is FALSE notify messages
    are not sent. */
 
-int silc_server_remove_from_one_channel(SilcServer server,
-                                       SilcSocketConnection sock,
-                                       SilcChannelEntry channel,
-                                       SilcClientEntry client,
-                                       int notify)
+bool silc_server_remove_from_one_channel(SilcServer server,
+                                        SilcSocketConnection sock,
+                                        SilcChannelEntry channel,
+                                        SilcClientEntry client,
+                                        bool notify)
 {
   SilcChannelClientEntry chl;
   SilcBuffer clidp;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_LOG_DEBUG(("Removing %s from channel %s",
+                 silc_id_render(client->id, SILC_ID_CLIENT), 
+                 channel->channel_name));
 
   /* Get the entry to the channel, if this client is not on the channel
      then return Ok. */
   if (!silc_hash_table_find(client->channels, channel, NULL, (void *)&chl))
     return TRUE;
 
-  /* Remove the client from the channel. The client is removed from
-     the channel's user list. */
-
-  clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
-
-  /* Remove channel from client's channel list */
-  silc_hash_table_del(client->channels, chl->channel);
-
-  /* Remove channel if there is no users anymore */
+  /* Remove channel if this is last client leaving the channel, unless
+     the channel is permanent. */
   if (server->server_type == SILC_ROUTER &&
       silc_hash_table_count(channel->user_list) < 2) {
-    if (channel->rekey)
-      silc_schedule_task_del_by_context(server->schedule, channel->rekey);
-    if (silc_idlist_del_channel(server->local_list, channel))
-      server->stat.my_channels--;
-    else
-      silc_idlist_del_channel(server->global_list, channel);
-    silc_buffer_free(clidp);
+    silc_server_channel_delete(server, channel);
     return FALSE;
   }
 
-  /* Remove client from channel's client list */
+  silc_hash_table_del(client->channels, chl->channel);
   silc_hash_table_del(channel->user_list, chl->client);
   channel->user_count--;
 
@@ -2873,8 +2864,13 @@ int silc_server_remove_from_one_channel(SilcServer server,
   silc_free(chl);
   server->stat.my_chanclients--;
 
+  clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+  if (!clidp)
+    notify = FALSE;
+
   /* If there is not at least one local user on the channel then we don't
-     need the channel entry anymore, we can remove it safely. */
+     need the channel entry anymore, we can remove it safely, unless the
+     channel is permanent channel */
   if (server->server_type != SILC_ROUTER &&
       !silc_server_channel_has_local(channel)) {
     /* Notify about leaving client if this channel has global users. */
@@ -2883,34 +2879,9 @@ int silc_server_remove_from_one_channel(SilcServer server,
                                         SILC_NOTIFY_TYPE_LEAVE, 1,
                                         clidp->data, clidp->len);
 
+    silc_schedule_task_del_by_context(server->schedule, channel->rekey);
+    silc_server_channel_delete(server, channel);
     silc_buffer_free(clidp);
-
-    if (channel->rekey)
-      silc_schedule_task_del_by_context(server->schedule, channel->rekey);
-
-    if (channel->founder_key) {
-      /* The founder auth data exists, do not remove the channel entry */
-      SilcChannelClientEntry chl2;
-      SilcHashTableList htl2;
-
-      channel->disabled = TRUE;
-
-      silc_hash_table_list(channel->user_list, &htl2);
-      while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) {
-       silc_hash_table_del(chl2->client->channels, channel);
-       silc_hash_table_del(channel->user_list, chl2->client);
-       channel->user_count--;
-       silc_free(chl2);
-      }
-      silc_hash_table_list_reset(&htl2);
-      return FALSE;
-    }
-
-    /* Remove the channel entry */
-    if (silc_idlist_del_channel(server->local_list, channel))
-      server->stat.my_channels--;
-    else
-      silc_idlist_del_channel(server->global_list, channel);
     return FALSE;
   }
 
@@ -2932,6 +2903,7 @@ SILC_TASK_CALLBACK(silc_server_timeout_remote)
 {
   SilcServer server = (SilcServer)context;
   SilcSocketConnection sock = server->sockets[fd];
+  SilcProtocolType protocol = 0;
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -2944,6 +2916,7 @@ SILC_TASK_CALLBACK(silc_server_timeout_remote)
   /* If we have protocol active we must assure that we call the protocol's
      final callback so that all the memory is freed. */
   if (sock->protocol) {
+    protocol = sock->protocol->protocol->type;
     silc_protocol_cancel(sock->protocol, server->schedule);
     sock->protocol->state = SILC_PROTOCOL_STATE_ERROR;
     silc_protocol_execute_final(sock->protocol, server->schedule);
@@ -2954,7 +2927,11 @@ SILC_TASK_CALLBACK(silc_server_timeout_remote)
   if (sock->user_data)
     silc_server_free_sock_user_data(server, sock, NULL);
 
-  silc_server_disconnect_remote(server, sock, "Server closed connection: "
+  silc_server_disconnect_remote(server, sock, 
+                               protocol == 
+                               SILC_PROTOCOL_SERVER_CONNECTION_AUTH ?
+                               SILC_STATUS_ERR_AUTH_FAILED :
+                               SILC_STATUS_ERR_KEY_EXCHANGE_FAILED,
                                "Connection timeout");
 }
 
@@ -3035,6 +3012,9 @@ SilcChannelEntry silc_server_create_new_channel(SilcServer server,
 
   server->stat.my_channels++;
 
+  if (server->server_type == SILC_ROUTER)
+    entry->users_resolved = TRUE;
+
   return entry;
 }
 
@@ -3099,6 +3079,9 @@ silc_server_create_new_channel_with_id(SilcServer server,
 
   server->stat.my_channels++;
 
+  if (server->server_type == SILC_ROUTER)
+    entry->users_resolved = TRUE;
+
   return entry;
 }
 
@@ -3567,7 +3550,8 @@ void silc_server_announce_get_channel_users(SilcServer server,
   SilcBuffer chidp, clidp;
   SilcBuffer tmp;
   int len;
-  unsigned char mode[4];
+  unsigned char mode[4], *fkey = NULL;
+  SilcUInt32 fkey_len = 0;
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -3596,10 +3580,13 @@ void silc_server_announce_get_channel_users(SilcServer server,
 
     /* CUMODE notify for mode change on the channel */
     SILC_PUT32_MSB(chl->mode, mode);
+    if (chl->mode & SILC_CHANNEL_UMODE_CHANFO && channel->founder_key)
+      fkey = silc_pkcs_public_key_encode(channel->founder_key, &fkey_len);
     tmp = silc_server_announce_encode_notify(SILC_NOTIFY_TYPE_CUMODE_CHANGE,
-                                            3, clidp->data, clidp->len,
+                                            4, clidp->data, clidp->len,
                                             mode, 4,
-                                            clidp->data, clidp->len);
+                                            clidp->data, clidp->len,
+                                            fkey, fkey_len);
     len = tmp->len;
     *channel_users_modes =
       silc_buffer_realloc(*channel_users_modes,
@@ -3612,7 +3599,7 @@ void silc_server_announce_get_channel_users(SilcServer server,
     silc_buffer_put(*channel_users_modes, tmp->data, tmp->len);
     silc_buffer_pull(*channel_users_modes, len);
     silc_buffer_free(tmp);
-
+    silc_free(fkey);
     silc_buffer_free(clidp);
   }
   silc_hash_table_list_reset(&htl);
@@ -3908,6 +3895,7 @@ void silc_server_save_users_on_channel(SilcServer server,
   SilcClientID *client_id;
   SilcClientEntry client;
   SilcIDCacheEntry cache;
+  SilcChannelClientEntry chl;
   bool global;
 
   SILC_LOG_DEBUG(("Start"));
@@ -3971,16 +3959,126 @@ void silc_server_save_users_on_channel(SilcServer server,
 
     silc_free(client_id);
 
-    if (!silc_server_client_on_channel(client, channel, NULL)) {
+    if (!silc_server_client_on_channel(client, channel, &chl)) {
       /* Client was not on the channel, add it. */
-      SilcChannelClientEntry chl = silc_calloc(1, sizeof(*chl));
+      chl = silc_calloc(1, sizeof(*chl));
       chl->client = client;
       chl->mode = mode;
       chl->channel = channel;
       silc_hash_table_add(channel->user_list, chl->client, chl);
       silc_hash_table_add(client->channels, chl->channel, chl);
       channel->user_count++;
+    } else {
+      /* Update mode */
+      chl->mode = mode;
+    }
+  }
+}
+
+/* Saves channels and channels user modes to the `client'.  Removes
+   the client from those channels that are not sent in the list but
+   it has joined. */
+
+void silc_server_save_user_channels(SilcServer server,
+                                   SilcSocketConnection sock,
+                                   SilcClientEntry client,
+                                   SilcBuffer channels,
+                                   SilcBuffer channels_user_modes)
+{
+  SilcDList ch;
+  SilcUInt32 *chumodes;
+  SilcChannelPayload entry;
+  SilcChannelEntry channel;
+  SilcChannelID *channel_id;
+  SilcChannelClientEntry chl;
+  SilcHashTable ht = NULL;
+  SilcHashTableList htl;
+  char *name;
+  int i = 0;
+
+  if (!channels ||!channels_user_modes)
+    goto out;
+  
+  ch = silc_channel_payload_parse_list(channels->data, channels->len);
+  if (ch && silc_get_mode_list(channels_user_modes, silc_dlist_count(ch),
+                              &chumodes)) {
+    ht = silc_hash_table_alloc(0, silc_hash_ptr, NULL, NULL, 
+                              NULL, NULL, NULL, TRUE);
+    silc_dlist_start(ch);
+    while ((entry = silc_dlist_get(ch)) != SILC_LIST_END) {
+      /* Check if we have this channel, and add it if we don't have it.
+        Also add the client on the channel unless it is there already. */
+      channel_id = silc_channel_get_id_parse(entry);
+      channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                              channel_id, NULL);
+      if (!channel)
+       channel = silc_idlist_find_channel_by_id(server->global_list,
+                                                channel_id, NULL);
+      if (!channel) {
+       if (server->server_type != SILC_SERVER) {
+         silc_free(channel_id);
+         i++;
+         continue;
+       }
+       
+       /* We don't have that channel anywhere, add it. */
+       name = silc_channel_get_name(entry, NULL);
+       channel = silc_idlist_add_channel(server->global_list, strdup(name), 0,
+                                         channel_id, server->router,
+                                         NULL, NULL, 0);
+       if (!channel) {
+         silc_free(channel_id);
+         i++;
+         continue;
+       }
+       channel_id = NULL;
+      }
+
+      channel->mode = silc_channel_get_mode(entry);
+
+      /* Add the client on the channel */
+      if (!silc_server_client_on_channel(client, channel, &chl)) {
+       chl = silc_calloc(1, sizeof(*chl));
+       chl->client = client;
+       chl->mode = chumodes[i++];
+       chl->channel = channel;
+       silc_hash_table_add(channel->user_list, chl->client, chl);
+       silc_hash_table_add(client->channels, chl->channel, chl);
+       channel->user_count++;
+      } else {
+       /* Update mode */
+       chl->mode = chumodes[i++];
+      }
+
+      silc_hash_table_add(ht, channel, channel);
+      silc_free(channel_id);
+    }
+    silc_channel_payload_list_free(ch);
+    silc_free(chumodes);
+  }
+
+ out:
+  /* Go through the list again and remove client from channels that
+     are no part of the list. */
+  if (ht) {
+    silc_hash_table_list(client->channels, &htl);
+    while (silc_hash_table_get(&htl, NULL, (void **)&chl)) {
+      if (!silc_hash_table_find(ht, chl->channel, NULL, NULL)) {
+       silc_hash_table_del(chl->channel->user_list, chl->client);
+       silc_hash_table_del(chl->client->channels, chl->channel);
+       silc_free(chl);
+      }
     }
+    silc_hash_table_list_reset(&htl);
+    silc_hash_table_free(ht);
+  } else {
+    silc_hash_table_list(client->channels, &htl);
+    while (silc_hash_table_get(&htl, NULL, (void **)&chl)) {
+      silc_hash_table_del(chl->channel->user_list, chl->client);
+      silc_hash_table_del(chl->client->channels, chl->channel);
+      silc_free(chl);
+    }
+    silc_hash_table_list_reset(&htl);
   }
 }
 
@@ -3989,17 +4087,22 @@ void silc_server_save_users_on_channel(SilcServer server,
    could not be found to the client. If the `client_id' is specified then
    it is used and the `id_data' is ignored. */
 
-SilcSocketConnection silc_server_get_client_route(SilcServer server,
-                                                 unsigned char *id_data,
-                                                 SilcUInt32 id_len,
-                                                 SilcClientID *client_id,
-                                                 SilcIDListData *idata)
+SilcSocketConnection
+silc_server_get_client_route(SilcServer server,
+                            unsigned char *id_data,
+                            SilcUInt32 id_len,
+                            SilcClientID *client_id,
+                            SilcIDListData *idata,
+                            SilcClientEntry *client_entry)
 {
   SilcClientID *id;
   SilcClientEntry client;
 
   SILC_LOG_DEBUG(("Start"));
 
+  if (client_entry)
+    *client_entry = NULL;
+
   /* Decode destination Client ID */
   if (!client_id) {
     id = silc_id_str2id(id_data, id_len, SILC_ID_CLIENT);
@@ -4031,6 +4134,8 @@ SilcSocketConnection silc_server_get_client_route(SilcServer server,
     /* Seems that client really is directly connected to us */
     if (idata)
       *idata = (SilcIDListData)client;
+    if (client_entry)
+      *client_entry = client;
     return client->connection;
   }
 
@@ -4069,7 +4174,10 @@ SilcSocketConnection silc_server_get_client_route(SilcServer server,
    Secret channels are not put to the list. */
 
 SilcBuffer silc_server_get_client_channel_list(SilcServer server,
-                                              SilcClientEntry client)
+                                              SilcClientEntry client,
+                                              bool get_private,
+                                              bool get_secret,
+                                              SilcBuffer *user_mode_list)
 {
   SilcBuffer buffer = NULL;
   SilcChannelEntry channel;
@@ -4080,12 +4188,16 @@ SilcBuffer silc_server_get_client_channel_list(SilcServer server,
   SilcUInt16 name_len;
   int len;
 
+  if (user_mode_list)
+    *user_mode_list = NULL;
+
   silc_hash_table_list(client->channels, &htl);
   while (silc_hash_table_get(&htl, NULL, (void *)&chl)) {
     channel = chl->channel;
 
-    if (channel->mode & SILC_CHANNEL_MODE_SECRET ||
-       channel->mode & SILC_CHANNEL_MODE_PRIVATE)
+    if (channel->mode & SILC_CHANNEL_MODE_SECRET && !get_secret)
+      continue;
+    if (channel->mode & SILC_CHANNEL_MODE_PRIVATE && !get_private)
       continue;
 
     cid = silc_id_id2str(channel->id, SILC_ID_CHANNEL);
@@ -4094,23 +4206,37 @@ SilcBuffer silc_server_get_client_channel_list(SilcServer server,
 
     len = 4 + name_len + id_len + 4;
     buffer = silc_buffer_realloc(buffer,
-                                (buffer ? (buffer)->truelen + len : len));
-    silc_buffer_pull_tail(buffer, ((buffer)->end - (buffer)->data));
+                                (buffer ? buffer->truelen + len : len));
+    silc_buffer_pull_tail(buffer, (buffer->end - buffer->data));
     silc_buffer_format(buffer,
                       SILC_STR_UI_SHORT(name_len),
                       SILC_STR_UI_XNSTRING(channel->channel_name,
                                            name_len),
                       SILC_STR_UI_SHORT(id_len),
                       SILC_STR_UI_XNSTRING(cid, id_len),
-                      SILC_STR_UI_INT(chl->mode), /* Client's mode */
+                      SILC_STR_UI_INT(chl->channel->mode),
                       SILC_STR_END);
     silc_buffer_pull(buffer, len);
     silc_free(cid);
+
+    if (user_mode_list) {
+      *user_mode_list = silc_buffer_realloc(*user_mode_list,
+                                           (*user_mode_list ?
+                                            (*user_mode_list)->truelen + 4 :
+                                            4));
+      silc_buffer_pull_tail(*user_mode_list, ((*user_mode_list)->end -
+                                             (*user_mode_list)->data));
+      SILC_PUT32_MSB(chl->mode, (*user_mode_list)->data);
+      silc_buffer_pull(*user_mode_list, 4);
+    }
   }
   silc_hash_table_list_reset(&htl);
 
   if (buffer)
     silc_buffer_push(buffer, buffer->data - buffer->head);
+  if (user_mode_list && *user_mode_list)
+    silc_buffer_push(*user_mode_list, ((*user_mode_list)->data -
+                                      (*user_mode_list)->head));
 
   return buffer;
 }
@@ -4120,6 +4246,7 @@ SilcBuffer silc_server_get_client_channel_list(SilcServer server,
 
 SilcClientEntry silc_server_get_client_resolve(SilcServer server,
                                               SilcClientID *client_id,
+                                              bool always_resolve,
                                               bool *resolved)
 {
   SilcClientEntry client;
@@ -4139,17 +4266,20 @@ SilcClientEntry silc_server_get_client_resolve(SilcServer server,
   if (!client && server->standalone)
     return NULL;
 
-  if (!client || !client->nickname || !client->username) {
+  if (!client || !client->nickname || !client->username ||
+      always_resolve) {
     SilcBuffer buffer, idp;
 
-    client->data.status |= SILC_IDLIST_STATUS_RESOLVING;
-    client->data.status &= ~SILC_IDLIST_STATUS_RESOLVED;
-    client->resolve_cmd_ident = ++server->cmd_ident;
+    if (client) {
+      client->data.status |= SILC_IDLIST_STATUS_RESOLVING;
+      client->data.status &= ~SILC_IDLIST_STATUS_RESOLVED;
+      client->resolve_cmd_ident = ++server->cmd_ident;
+    }
 
     idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT);
     buffer = silc_command_payload_encode_va(SILC_COMMAND_WHOIS,
                                            server->cmd_ident, 1,
-                                           3, idp->data, idp->len);
+                                           4, idp->data, idp->len);
     silc_server_packet_send(server, client ? client->router->connection :
                            server->router->connection,
                            SILC_PACKET_COMMAND, 0,
@@ -4244,3 +4374,30 @@ SILC_TASK_CALLBACK_GLOBAL(silc_server_rekey_final)
     silc_ske_free(ctx->ske);
   silc_free(ctx);
 }
+
+/* Task callback used to retrieve network statistical information from
+   router server once in a while. */
+
+SILC_TASK_CALLBACK(silc_server_get_stats)
+{
+  SilcServer server = (SilcServer)context;
+  SilcBuffer idp, packet;
+
+  SILC_LOG_DEBUG(("Retrieving stats from router"));
+
+  if (!server->standalone) {
+    idp = silc_id_payload_encode(server->router->id, SILC_ID_SERVER);
+    packet = silc_command_payload_encode_va(SILC_COMMAND_STATS, 
+                                           ++server->cmd_ident, 1,
+                                           1, idp->data, idp->len);
+    silc_server_packet_send(server, server->router->connection,
+                           SILC_PACKET_COMMAND, 0, packet->data,
+                           packet->len, FALSE);
+    silc_buffer_free(packet);
+    silc_buffer_free(idp);
+  }
+
+  silc_schedule_task_add(server->schedule, 0, silc_server_get_stats,
+                        server, 120, 0, SILC_TASK_TIMEOUT,
+                        SILC_TASK_PRI_LOW);
+}