updates.
[silc.git] / apps / silcd / server.c
index 064ff3860b57922d4bbbf797e50d07b304ef382f..c1d097e8c3bcd8d4a7f9b759bf0ae479378a1fd0 100644 (file)
@@ -108,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);
@@ -208,6 +209,14 @@ 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;
@@ -781,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;
   }
 
@@ -812,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);
@@ -870,8 +879,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;
   }
 
@@ -930,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;
   }
 
@@ -990,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;
   }
 
@@ -1103,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;
   }
 
@@ -1128,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;
   }
@@ -1153,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;
   }
@@ -1280,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;
   }
@@ -1311,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;
   }
@@ -1391,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;
   }
@@ -1425,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;
       }
@@ -1530,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;
       }
@@ -1926,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;
@@ -2435,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 */
@@ -2506,6 +2549,14 @@ 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--;
@@ -2513,6 +2564,7 @@ void silc_server_free_client_data(SilcServer server,
     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 */
@@ -2677,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;
@@ -2692,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. */
@@ -2699,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--;
 
@@ -2728,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. */
@@ -2740,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,
@@ -2779,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. */
@@ -2792,7 +2815,6 @@ void silc_server_remove_from_channels(SilcServer server,
     }
   }
 
- out:
   silc_hash_table_list_reset(&htl);
   silc_buffer_free(clidp);
 }
@@ -2803,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--;
 
@@ -2853,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. */
@@ -2863,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;
   }
 
@@ -2912,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"));
 
@@ -2924,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);
@@ -2934,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");
 }
 
@@ -3015,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;
 }
 
@@ -3079,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;
 }
 
@@ -3547,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"));
 
@@ -3576,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,
@@ -3592,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);
@@ -3968,6 +3975,113 @@ void silc_server_save_users_on_channel(SilcServer server,
   }
 }
 
+/* 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);
+  }
+}
+
 /* Lookups route to the client indicated by the `id_data'. The connection
    object and internal data object is returned. Returns NULL if route
    could not be found to the client. If the `client_id' is specified then
@@ -4060,7 +4174,10 @@ 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;
@@ -4071,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);
@@ -4085,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;
 }
@@ -4135,14 +4270,16 @@ SilcClientEntry silc_server_get_client_resolve(SilcServer server,
       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,