Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client_notify.c
index fd8deeb074a571383960dd9470e7d83595ccb663..2ccb574c1ac8a384d7fd6a36939e8a0c5b097e52 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2006 Pekka Riikonen
+  Copyright (C) 1997 - 2008 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
 
 /* Notify processing context */
 typedef struct {
-  SilcPacket packet;
-  SilcNotifyPayload payload;
-  SilcFSMThread fsm;
-  SilcChannelEntry channel;
+  SilcPacket packet;                 /* Notify packet */
+  SilcNotifyPayload payload;         /* Parsed notify payload */
+  SilcFSMThread fsm;                 /* Notify FSM thread */
+  SilcChannelEntry channel;          /* Channel entry being resolved */
+  SilcClientEntry client_entry;              /* Client entry being resolved */
+  SilcUInt32 resolve_retry;          /* Resolving retry counter */
 } *SilcClientNotify;
 
 /************************ Static utility functions **************************/
 
-/* Entry resolving callback.  This will continue processing the notify. */
+/* The client entires in notify processing are resolved if they do not exist,
+   or they are not valid.  We go to this callback after resolving where we
+   check if the client entry has become valid.  If resolving succeeded the
+   entry is valid but remains invalid if resolving failed.  This callback
+   will continue processing the notify.  We use this callback also with other
+   entry resolving. */
 
 static void silc_client_notify_resolved(SilcClient client,
                                        SilcClientConnection conn,
@@ -46,8 +53,21 @@ static void silc_client_notify_resolved(SilcClient client,
 {
   SilcClientNotify notify = context;
 
-  /* If no entries found, just finish the notify processing, a silent error */
-  if (!entries)
+  /* If entry is still invalid, resolving failed.  Finish notify processing. */
+  if (notify->client_entry && !notify->client_entry->internal.valid) {
+    /* If resolving timedout try it again many times. */
+    if (status != SILC_STATUS_ERR_TIMEDOUT || ++notify->resolve_retry > 1000) {
+      silc_fsm_next(notify->fsm, silc_client_notify_processed);
+
+      /* Unref client only in case of non-timeout error.  In case of timeout
+        occurred, the routine reprocessing the notify is expected not to
+        create new references of the entry. */
+      silc_client_unref_client(client, conn, notify->client_entry);
+    }
+  }
+
+  /* If no entries found, just finish the notify processing */
+  if (!entries && !notify->client_entry)
     silc_fsm_next(notify->fsm, silc_client_notify_processed);
 
   if (notify->channel) {
@@ -75,7 +95,7 @@ static SilcBool silc_client_notify_wait_continue(SilcClient client,
   /* Continue after last command reply received */
   if (SILC_STATUS_IS_ERROR(status) || status == SILC_STATUS_OK ||
       status == SILC_STATUS_LIST_END)
-    SILC_FSM_CALL_CONTINUE(notify->fsm);
+    SILC_FSM_CALL_CONTINUE_SYNC(notify->fsm);
 
   return TRUE;
 }
@@ -99,7 +119,7 @@ SILC_FSM_STATE(silc_client_notify)
   }
 
   if (!silc_notify_get_args(payload)) {
-    SILC_LOG_DEBUG(("Malformed notify"));
+    SILC_LOG_DEBUG(("Malformed notify %d", silc_notify_get_type(payload)));
     silc_notify_payload_free(payload);
     silc_packet_free(packet);
     return SILC_FSM_FINISH;
@@ -112,7 +132,6 @@ SILC_FSM_STATE(silc_client_notify)
     return SILC_FSM_FINISH;
   }
 
-  /* Save notify payload to packet context during processing */
   notify->packet = packet;
   notify->payload = payload;
   notify->fsm = fsm;
@@ -210,7 +229,7 @@ SILC_FSM_STATE(silc_client_notify)
     break;
   }
 
-  return SILC_FSM_YIELD;
+  return SILC_FSM_CONTINUE;
 }
 
 /* Notify processed, finish the packet processing thread */
@@ -279,6 +298,14 @@ SILC_FSM_STATE(silc_client_notify_invite)
 
   /* Get the channel entry */
   channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+  if (!channel) {
+    /** Resolve channel */
+    SILC_FSM_CALL(silc_client_get_channel_by_id_resolve(
+                                         client, conn, &id.u.channel_id,
+                                         silc_client_notify_resolved,
+                                         notify));
+    /* NOT REACHED */
+  }
 
   /* If channel is being resolved handle notify after resolving */
   if (channel->internal.resolve_cmd_ident) {
@@ -297,7 +324,7 @@ SILC_FSM_STATE(silc_client_notify_invite)
 
   /* Find Client entry and if not found query it */
   client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry || !client_entry->nickname[0]) {
+  if (!client_entry || !client_entry->internal.valid) {
     /** Resolve client */
     silc_client_unref_client(client, conn, client_entry);
     notify->channel = channel;
@@ -363,27 +390,40 @@ SILC_FSM_STATE(silc_client_notify_join)
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
-  /* Find Client entry and if not found query it */
-  client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry || !client_entry->nickname[0] ||
+  /* Find client entry and if not found query it.  If we just queried it
+     don't do it again, unless some data (like username) is missing. */
+  client_entry = notify->client_entry;
+  if (!client_entry)
+    client_entry = silc_client_get_client(client, conn, &id.u.client_id);
+  if (!client_entry || !client_entry->internal.valid ||
       !client_entry->username[0]) {
     /** Resolve client */
-    silc_client_unref_client(client, conn, client_entry);
     notify->channel = channel;
+    notify->client_entry = client_entry;
     SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
                  silc_client_get_client_by_id_resolve(
-                                        client, conn, &id.u.client_id, NULL,
-                                        silc_client_notify_resolved,
+                                        client, conn, client_entry ?
+                                        &client_entry->id : &id.u.client_id,
+                                        NULL, silc_client_notify_resolved,
                                         notify));
     /* NOT REACHED */
   }
 
+  silc_rwlock_wrlock(client_entry->internal.lock);
+  silc_rwlock_wrlock(channel->internal.lock);
+
   if (client_entry != conn->local_entry)
-    silc_client_nickname_format(client, conn, client_entry);
+    silc_client_nickname_format(client, conn, client_entry, FALSE);
 
   /* Join the client to channel */
-  if (!silc_client_add_to_channel(client, conn, channel, client_entry, 0))
+  if (!silc_client_add_to_channel(client, conn, channel, client_entry, 0)) {
+    silc_rwlock_unlock(channel->internal.lock);
+    silc_rwlock_unlock(client_entry->internal.lock);
     goto out;
+  }
+
+  silc_rwlock_unlock(channel->internal.lock);
+  silc_rwlock_unlock(client_entry->internal.lock);
 
   /* Notify application. */
   NOTIFY(client, conn, type, client_entry, channel);
@@ -445,7 +485,8 @@ SILC_FSM_STATE(silc_client_notify_leave)
     goto out;
 
   /* Remove client from channel */
-  silc_client_remove_from_channel(client, conn, channel, client_entry);
+  if (!silc_client_remove_from_channel(client, conn, channel, client_entry))
+    goto out;
 
   /* Notify application. */
   NOTIFY(client, conn, type, client_entry, channel);
@@ -469,10 +510,12 @@ SILC_FSM_STATE(silc_client_notify_signoff)
   SilcClient client = conn->client;
   SilcClientNotify notify = state_context;
   SilcNotifyPayload payload = notify->payload;
+  SilcPacket packet = notify->packet;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry;
-   unsigned char *tmp;
+  SilcChannelEntry channel = NULL;
+  unsigned char *tmp;
   SilcUInt32 tmp_len;
   SilcID id;
 
@@ -489,13 +532,26 @@ SILC_FSM_STATE(silc_client_notify_signoff)
 
   /* Get signoff message */
   tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-  if (tmp_len > 128)
+  if (tmp && tmp_len > 128)
     tmp[128] = '\0';
 
+  if (packet->dst_id_type == SILC_ID_CHANNEL)
+    if (silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CHANNEL,
+                      &id.u.channel_id, sizeof(id.u.channel_id)))
+      channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+
   /* Notify application */
-  NOTIFY(client, conn, type, client_entry, tmp);
+  if (client_entry->internal.valid)
+    NOTIFY(client, conn, type, client_entry, tmp, channel);
+
+  /* Remove from channel */
+  if (channel) {
+    silc_client_remove_from_channel(client, conn, channel, client_entry);
+    silc_client_unref_channel(client, conn, channel);
+  }
 
   /* Delete client */
+  client_entry->internal.valid = FALSE;
   silc_client_del_client(client, conn, client_entry);
   silc_client_unref_client(client, conn, client_entry);
 
@@ -547,7 +603,7 @@ SILC_FSM_STATE(silc_client_notify_topic_set)
     /* NOT REACHED */
   }
 
-  /* Get ID */
+  /* Get ID of topic changer */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
@@ -558,18 +614,26 @@ SILC_FSM_STATE(silc_client_notify_topic_set)
 
   if (id.type == SILC_ID_CLIENT) {
     /* Find Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (!client_entry || !client_entry->nickname[0]) {
-      /** Resolve client */
-      silc_client_unref_client(client, conn, client_entry);
-      notify->channel = channel;
-      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
-                   silc_client_get_client_by_id_resolve(
+    client_entry = notify->client_entry;
+    if (!client_entry) {
+      client_entry = silc_client_get_client(client, conn, &id.u.client_id);
+      if (!client_entry || !client_entry->internal.valid) {
+       /** Resolve client */
+       notify->channel = channel;
+       notify->client_entry = client_entry;
+       SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                     silc_client_get_client_by_id_resolve(
                                           client, conn, &id.u.client_id, NULL,
                                           silc_client_notify_resolved,
                                           notify));
-      /* NOT REACHED */
+       /* NOT REACHED */
+      }
     }
+
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
+      goto out;
+
     entry = client_entry;
   } else if (id.type == SILC_ID_SERVER) {
     /* Find Server entry */
@@ -602,8 +666,10 @@ SILC_FSM_STATE(silc_client_notify_topic_set)
     entry = channel_entry;
   }
 
+  silc_rwlock_wrlock(channel->internal.lock);
   silc_free(channel->topic);
   channel->topic = silc_memdup(tmp, strlen(tmp));
+  silc_rwlock_unlock(channel->internal.lock);
 
   /* Notify application. */
   NOTIFY(client, conn, type, id.type, entry, channel->topic, channel);
@@ -635,9 +701,10 @@ SILC_FSM_STATE(silc_client_notify_nick_change)
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry = NULL;
-  unsigned char *tmp, *nick, oldnick[128 + 1];
+  unsigned char *tmp, oldnick[256 + 1];
   SilcUInt32 tmp_len;
   SilcID id, id2;
+  SilcBool valid;
 
   SILC_LOG_DEBUG(("Notify: NICK_CHANGE"));
 
@@ -659,23 +726,20 @@ SILC_FSM_STATE(silc_client_notify_nick_change)
       SILC_ID_CLIENT_COMPARE(&id2.u.client_id, conn->local_id))
     goto out;
 
-  /* Find old Client entry */
+  /* Find old client entry.  If we don't have the entry, we ignore this
+     notify. */
   client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry || !client_entry->nickname[0]) {
-    /** Resolve client */
-    silc_client_unref_client(client, conn, client_entry);
-    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
-                                        client, conn, &id.u.client_id, NULL,
-                                        silc_client_notify_resolved,
-                                        notify));
-    /* NOT REACHED */
-  }
+  if (!client_entry)
+    goto out;
+  valid = client_entry->internal.valid;
 
   /* Take the new nickname */
   tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
   if (!tmp)
     goto out;
 
+  silc_rwlock_wrlock(client_entry->internal.lock);
+
   /* Check whether nickname changed at all.  It is possible that nick
      change notify is received but nickname didn't change, only the
      ID changes.  If Client ID hash match, nickname didn't change. */
@@ -687,30 +751,24 @@ SILC_FSM_STATE(silc_client_notify_nick_change)
     silc_idcache_update_by_context(conn->internal->client_cache, client_entry,
                                   &id2.u.client_id, NULL, FALSE);
     silc_mutex_unlock(conn->internal->lock);
+    silc_rwlock_unlock(client_entry->internal.lock);
     goto out;
   }
 
-  /* Normalize nickname */
-  nick = silc_identifier_check(tmp, tmp_len, SILC_STRING_UTF8, 128, NULL);
-  if (!nick)
-    goto out;
-
-  /* Update nickname */
-  silc_mutex_lock(conn->internal->lock);
-  if (!silc_idcache_update_by_context(conn->internal->client_cache,
-                                     client_entry, NULL, nick, TRUE)) {
-    silc_free(nick);
-    silc_mutex_unlock(conn->internal->lock);
+  /* Change the nickname */
+  memcpy(oldnick, client_entry->nickname, sizeof(client_entry->nickname));
+  if (!silc_client_change_nickname(client, conn, client_entry, tmp,
+                                  &id2.u.client_id, NULL, 0)) {
+    silc_rwlock_unlock(client_entry->internal.lock);
     goto out;
   }
-  silc_mutex_unlock(conn->internal->lock);
-  memcpy(oldnick, client_entry->nickname, sizeof(client_entry->nickname));
-  memcpy(client_entry->nickname, tmp, tmp_len);
-  client_entry->nickname_normalized = nick;
-  silc_client_nickname_format(client, conn, client_entry);
 
-  /* Notify application */
-  NOTIFY(client, conn, type, client_entry, client_entry->nickname, oldnick);
+  silc_rwlock_unlock(client_entry->internal.lock);
+
+  /* Notify application, if client entry is valid.  We do not send nick change
+     notify for entries that were invalid (application doesn't know them). */
+  if (valid)
+    NOTIFY(client, conn, type, client_entry, oldnick, client_entry->nickname);
 
  out:
   /** Notify processed */
@@ -770,24 +828,32 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
     goto out;
   SILC_GET32_MSB(mode, tmp);
 
-  /* Get ID */
+  /* Get ID of who changed the mode */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
   if (id.type == SILC_ID_CLIENT) {
     /* Find Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (!client_entry || !client_entry->nickname[0]) {
-      /** Resolve client */
-      silc_client_unref_client(client, conn, client_entry);
-      notify->channel = channel;
-      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
-                   silc_client_get_client_by_id_resolve(
+    client_entry = notify->client_entry;
+    if (!client_entry) {
+      client_entry = silc_client_get_client(client, conn, &id.u.client_id);
+      if (!client_entry || !client_entry->internal.valid) {
+       /** Resolve client */
+       notify->channel = channel;
+       notify->client_entry = client_entry;
+       SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                     silc_client_get_client_by_id_resolve(
                                           client, conn, &id.u.client_id, NULL,
                                           silc_client_notify_resolved,
                                           notify));
-      /* NOT REACHED */
+       /* NOT REACHED */
+      }
     }
+
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
+      goto out;
+
     entry = client_entry;
   } else if (id.type == SILC_ID_SERVER) {
     /* Find Server entry */
@@ -820,11 +886,15 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
     entry = channel_entry;
   }
 
+  silc_rwlock_wrlock(channel->internal.lock);
+
   /* Get the channel founder key if it was set */
   tmp = silc_argument_get_arg_type(args, 6, &tmp_len);
   if (tmp) {
-    if (!silc_public_key_payload_decode(tmp, tmp_len, &founder_key))
+    if (!silc_public_key_payload_decode(tmp, tmp_len, &founder_key)) {
+      silc_rwlock_unlock(channel->internal.lock);
       goto out;
+    }
     if (!channel->founder_key) {
       channel->founder_key = founder_key;
       founder_key = NULL;
@@ -840,8 +910,10 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
     unsigned char hash[SILC_HASH_MAXLEN];
     SilcHmac newhmac;
 
-    if (!silc_hmac_alloc(hmac, NULL, &newhmac))
+    if (!silc_hmac_alloc(hmac, NULL, &newhmac)) {
+      silc_rwlock_unlock(channel->internal.lock);
       goto out;
+    }
 
     /* Get HMAC key from the old HMAC context, and update it to the new one */
     tmp = (unsigned char *)silc_hmac_get_key(channel->internal.hmac, &tmp_len);
@@ -866,14 +938,17 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
   if (!(channel->mode & SILC_CHANNEL_MODE_ULIMIT))
     channel->user_limit = 0;
 
-  /* Save the new mode */
-  channel->mode = mode;
-
   /* Get the channel public key that was added or removed */
   tmp = silc_argument_get_arg_type(args, 7, &tmp_len);
   if (tmp)
-    chpks = silc_argument_list_parse_decoded(tmp, tmp_len,
-                                            SILC_ARGUMENT_PUBLIC_KEY);
+    silc_client_channel_save_public_keys(channel, tmp, tmp_len, FALSE);
+  else if (channel->mode & SILC_CHANNEL_MODE_CHANNEL_AUTH)
+    silc_client_channel_save_public_keys(channel, NULL, 0, TRUE);
+
+  /* Save the new mode */
+  channel->mode = mode;
+
+  silc_rwlock_unlock(channel->internal.lock);
 
   /* Notify application. */
   NOTIFY(client, conn, type, id.type, entry, mode, cipher, hmac,
@@ -946,7 +1021,7 @@ SILC_FSM_STATE(silc_client_notify_cumode_change)
 
   /* Find target Client entry */
   client_entry2 = silc_client_get_client_by_id(client, conn, &id2.u.client_id);
-  if (!client_entry2 || !client_entry2->nickname[0]) {
+  if (!client_entry2 || !client_entry2->internal.valid) {
     /** Resolve client */
     silc_client_unref_client(client, conn, client_entry2);
     SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
@@ -956,30 +1031,42 @@ SILC_FSM_STATE(silc_client_notify_cumode_change)
     /* NOT REACHED */
   }
 
+  /* If target client is not on channel, ignore this notify */
+  if (!silc_client_on_channel(channel, client_entry2))
+    goto out;
+
   /* Get the mode */
   tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
   if (!tmp)
     goto out;
   SILC_GET32_MSB(mode, tmp);
 
-  /* Get ID */
+  /* Get ID of mode changer */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
   if (id.type == SILC_ID_CLIENT) {
     /* Find Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (!client_entry || !client_entry->nickname[0]) {
-      /** Resolve client */
-      silc_client_unref_client(client, conn, client_entry);
-      notify->channel = channel;
-      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
-                   silc_client_get_client_by_id_resolve(
+    client_entry = notify->client_entry;
+    if (!client_entry) {
+      client_entry = silc_client_get_client(client, conn, &id.u.client_id);
+      if (!client_entry || !client_entry->internal.valid) {
+       /** Resolve client */
+       notify->channel = channel;
+       notify->client_entry = client_entry;
+       SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                     silc_client_get_client_by_id_resolve(
                                           client, conn, &id.u.client_id, NULL,
                                           silc_client_notify_resolved,
                                           notify));
-      /* NOT REACHED */
+       /* NOT REACHED */
+      }
     }
+
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
+      goto out;
+
     entry = client_entry;
   } else if (id.type == SILC_ID_SERVER) {
     /* Find Server entry */
@@ -1013,9 +1100,11 @@ SILC_FSM_STATE(silc_client_notify_cumode_change)
   }
 
   /* Save the mode */
+  silc_rwlock_wrlock(channel->internal.lock);
   chu = silc_client_on_channel(channel, client_entry2);
   if (chu)
     chu->mode = mode;
+  silc_rwlock_unlock(channel->internal.lock);
 
   /* Notify application. */
   NOTIFY(client, conn, type, id.type, entry, mode, client_entry2, channel);
@@ -1136,7 +1225,6 @@ SILC_FSM_STATE(silc_client_notify_kicked)
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry, client_entry2;
   SilcChannelEntry channel = NULL;
-  SilcChannelUser chu;
   unsigned char *tmp;
   SilcUInt32 tmp_len;
   SilcID id;
@@ -1162,11 +1250,11 @@ SILC_FSM_STATE(silc_client_notify_kicked)
     /* NOT REACHED */
   }
 
-  /* Get Client ID */
+  /* Get the kicked Client ID */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
-  /* Find Client entry */
+  /* Find client entry */
   client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
   if (!client_entry)
     goto out;
@@ -1177,7 +1265,7 @@ SILC_FSM_STATE(silc_client_notify_kicked)
 
   /* Find kicker's client entry and if not found resolve it */
   client_entry2 = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry2 || !client_entry2->nickname[0]) {
+  if (!client_entry2 || !client_entry2->internal.valid) {
     /** Resolve client */
     silc_client_unref_client(client, conn, client_entry);
     silc_client_unref_client(client, conn, client_entry2);
@@ -1195,12 +1283,8 @@ SILC_FSM_STATE(silc_client_notify_kicked)
 
   /* Remove kicked client from channel */
   if (client_entry != conn->local_entry) {
-    chu = silc_client_on_channel(channel, client_entry);
-    if (chu) {
-      silc_hash_table_del(client_entry->channels, channel);
-      silc_hash_table_del(channel->user_list, client_entry);
-      silc_free(chu);
-    }
+    if (!silc_client_remove_from_channel(client, conn, channel, client_entry))
+      goto out;
   }
 
   /* Notify application. */
@@ -1210,6 +1294,7 @@ SILC_FSM_STATE(silc_client_notify_kicked)
   if (client_entry == conn->local_entry) {
     if (conn->current_channel == channel)
       conn->current_channel = NULL;
+    silc_client_empty_channel(client, conn, channel);
     silc_client_del_channel(client, conn, channel);
   }
 
@@ -1265,7 +1350,7 @@ SILC_FSM_STATE(silc_client_notify_killed)
     /* Find Client entry */
     client_entry2 = silc_client_get_client_by_id(client, conn,
                                                 &id.u.client_id);
-    if (!client_entry2 || !client_entry2->nickname[0]) {
+    if (!client_entry2 || !client_entry2->internal.valid) {
       /** Resolve client */
       silc_client_unref_client(client, conn, client_entry);
       silc_client_unref_client(client, conn, client_entry2);
@@ -1307,8 +1392,11 @@ SILC_FSM_STATE(silc_client_notify_killed)
   NOTIFY(client, conn, type, client_entry, comment, id.type, entry);
 
   /* Delete the killed client */
-  if (client_entry != conn->local_entry)
+  if (client_entry != conn->local_entry) {
+    silc_client_remove_from_channels(client, conn, client_entry);
+    client_entry->internal.valid = FALSE;
     silc_client_del_client(client, conn, client_entry);
+  }
 
  out:
   silc_client_unref_client(client, conn, client_entry);
@@ -1337,16 +1425,24 @@ SILC_FSM_STATE(silc_client_notify_server_signoff)
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry;
+  SilcServerEntry server_entry = NULL;
   SilcDList clients;
   SilcID id;
   int i;
 
-  SILC_LOG_DEBUG(("Notify: SIGNOFF"));
+  SILC_LOG_DEBUG(("Notify: SERVER_SIGNOFF"));
 
   clients = silc_dlist_init();
   if (!clients)
     goto out;
 
+  /* Get server ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
+
+  /* Get server, in case we have it cached */
+  server_entry = silc_client_get_server_by_id(client, conn, &id.u.server_id);
+
   for (i = 1; i < silc_argument_get_arg_num(args); i++) {
     /* Get Client ID */
     if (!silc_argument_get_decoded(args, i + 1, SILC_ARGUMENT_ID, &id, NULL))
@@ -1354,21 +1450,24 @@ SILC_FSM_STATE(silc_client_notify_server_signoff)
 
     /* Get the client entry */
     client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (client_entry)
+    if (client_entry && client_entry->internal.valid)
       silc_dlist_add(clients, client_entry);
   }
 
-  /* Notify application.  We don't keep server entries so the server
-     entry is returned as NULL. The client's are returned as list. */
-  NOTIFY(client, conn, type, NULL, clients);
+  /* Notify application. */
+  NOTIFY(client, conn, type, server_entry, clients);
 
   /* Delete the clients */
   silc_dlist_start(clients);
-  while ((client_entry = silc_dlist_get(clients)))
+  while ((client_entry = silc_dlist_get(clients))) {
+    silc_client_remove_from_channels(client, conn, client_entry);
+    client_entry->internal.valid = FALSE;
     silc_client_del_client(client, conn, client_entry);
+  }
 
  out:
   /** Notify processed */
+  silc_client_unref_server(client, conn, server_entry);
   silc_client_list_free(client, conn, clients);
   silc_fsm_next(fsm, silc_client_notify_processed);
   return SILC_FSM_CONTINUE;
@@ -1405,7 +1504,8 @@ SILC_FSM_STATE(silc_client_notify_error)
     if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id, NULL))
       goto out;
     client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (client_entry) {
+    if (client_entry && client_entry != conn->local_entry) {
+      silc_client_remove_from_channels(client, conn, client_entry);
       silc_client_del_client(client, conn, client_entry);
       silc_client_unref_client(client, conn, client_entry);
     }
@@ -1434,7 +1534,6 @@ SILC_FSM_STATE(silc_client_notify_watch)
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry = NULL;
   SilcNotifyType ntype = 0;
-  SilcBool del_client = FALSE;
   unsigned char *pk, *tmp;
   SilcUInt32 mode, pk_len, tmp_len;
   SilcPublicKey public_key = NULL;
@@ -1446,9 +1545,9 @@ SILC_FSM_STATE(silc_client_notify_watch)
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
-  /* Find Client entry and if not found resolve it */
+  /* Find client entry and if not found resolve it */
   client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry || !client_entry->nickname[0]) {
+  if (!client_entry || !client_entry->internal.valid) {
     /** Resolve client */
     silc_client_unref_client(client, conn, client_entry);
     SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
@@ -1476,11 +1575,8 @@ SILC_FSM_STATE(silc_client_notify_watch)
   if (tmp) {
     char *tmp_nick = NULL;
 
-    if (client->internal->params->nickname_parse)
-      client->internal->params->nickname_parse(client_entry->nickname,
-                                              &tmp_nick);
-    else
-      tmp_nick = strdup(tmp);
+    silc_client_nickname_parse(client, conn, client_entry->nickname,
+                              &tmp_nick);
 
     /* If same nick, the client was new to us and has become "present"
        to network.  Send NULL as nick to application. */
@@ -1505,19 +1601,14 @@ SILC_FSM_STATE(silc_client_notify_watch)
 
   client_entry->mode = mode;
 
-  /* If nickname was changed, remove the client entry unless the
-     client is on some channel */
-  /* XXX, why do we need to remove the client entry?? */
-  if (tmp && ntype == SILC_NOTIFY_TYPE_NICK_CHANGE &&
-      !silc_hash_table_count(client_entry->channels))
-    del_client = TRUE;
-  else if (ntype == SILC_NOTIFY_TYPE_SIGNOFF ||
-          ntype == SILC_NOTIFY_TYPE_SERVER_SIGNOFF ||
-          ntype == SILC_NOTIFY_TYPE_KILLED)
-    del_client = TRUE;
-
-  if (del_client)
+  /* Remove client that left the network. */
+  if (ntype == SILC_NOTIFY_TYPE_SIGNOFF ||
+      ntype == SILC_NOTIFY_TYPE_SERVER_SIGNOFF ||
+      ntype == SILC_NOTIFY_TYPE_KILLED) {
+    silc_client_remove_from_channels(client, conn, client_entry);
+    client_entry->internal.valid = FALSE;
     silc_client_del_client(client, conn, client_entry);
+  }
 
   if (public_key)
     silc_pkcs_public_key_free(public_key);