Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client_notify.c
index d54e6ccc811e20919f1f9e5fda96fce1b64d3c79..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
 
 #define NOTIFY conn->client->internal->ops->notify
 
+/* Notify processing context */
+typedef struct {
+  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,
@@ -36,12 +51,53 @@ static void silc_client_notify_resolved(SilcClient client,
                                        SilcDList entries,
                                        void *context)
 {
-  /* If no entries found, just finish the notify processing, a silent error */
-  if (!entries)
-    silc_fsm_next(context, silc_client_notify_processed);
+  SilcClientNotify notify = context;
+
+  /* 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) {
+    notify->channel->internal.resolve_cmd_ident = 0;
+    silc_client_unref_channel(client, conn, notify->channel);
+  }
 
   /* Continue processing the notify */
-  SILC_FSM_CALL_CONTINUE_SYNC(context);
+  SILC_FSM_CALL_CONTINUE_SYNC(notify->fsm);
+}
+
+/* Continue notify processing after it was suspended while waiting for
+   channel information being resolved. */
+
+static SilcBool silc_client_notify_wait_continue(SilcClient client,
+                                                SilcClientConnection conn,
+                                                SilcCommand command,
+                                                SilcStatus status,
+                                                SilcStatus error,
+                                                void *context,
+                                                va_list ap)
+{
+  SilcClientNotify notify = context;
+
+  /* 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_SYNC(notify->fsm);
+
+  return TRUE;
 }
 
 /********************************* Notify ***********************************/
@@ -51,6 +107,7 @@ static void silc_client_notify_resolved(SilcClient client,
 SILC_FSM_STATE(silc_client_notify)
 {
   SilcPacket packet = state_context;
+  SilcClientNotify notify;
   SilcNotifyPayload payload;
 
   payload = silc_notify_payload_parse(silc_buffer_data(&packet->buffer),
@@ -62,14 +119,23 @@ 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;
+  }
+
+  notify = silc_calloc(1, sizeof(*notify));
+  if (!notify) {
     silc_notify_payload_free(payload);
     silc_packet_free(packet);
     return SILC_FSM_FINISH;
   }
 
-  /* Save notify payload to packet context during processing */
-  packet->next = (void *)payload;
+  notify->packet = packet;
+  notify->payload = payload;
+  notify->fsm = fsm;
+  silc_fsm_set_state_context(fsm, notify);
 
   /* Process the notify */
   switch (silc_notify_get_type(payload)) {
@@ -158,21 +224,25 @@ SILC_FSM_STATE(silc_client_notify)
     /** Unknown notify */
     silc_notify_payload_free(payload);
     silc_packet_free(packet);
+    silc_free(notify);
     return SILC_FSM_FINISH;
     break;
   }
 
-  return SILC_FSM_YIELD;
+  return SILC_FSM_CONTINUE;
 }
 
 /* Notify processed, finish the packet processing thread */
 
 SILC_FSM_STATE(silc_client_notify_processed)
 {
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcPacket packet = notify->packet;
+  SilcNotifyPayload payload = notify->payload;
+
   silc_notify_payload_free(payload);
   silc_packet_free(packet);
+  silc_free(notify);
   return SILC_FSM_FINISH;
 }
 
@@ -182,11 +252,13 @@ SILC_FSM_STATE(silc_client_notify_none)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
 
+  SILC_LOG_DEBUG(("Notify: NONE"));
+
   /* Notify application */
   NOTIFY(client, conn, type, silc_argument_get_arg_type(args, 1, NULL));
 
@@ -203,8 +275,8 @@ SILC_FSM_STATE(silc_client_notify_invite)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry;
@@ -226,6 +298,25 @@ 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) {
+    silc_client_unref_channel(client, conn, channel);
+    SILC_FSM_CALL(silc_client_command_pending(
+                                     conn, SILC_COMMAND_NONE,
+                                     channel->internal.resolve_cmd_ident,
+                                     silc_client_notify_wait_continue,
+                                     notify));
+    /* NOT REACHED */
+  }
 
   /* Get sender Client ID */
   if (!silc_argument_get_decoded(args, 3, SILC_ARGUMENT_ID, &id, NULL))
@@ -233,13 +324,15 @@ 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);
-    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
+    notify->channel = channel;
+    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,
-                                        fsm));
+                                        notify));
     /* NOT REACHED */
   }
 
@@ -263,8 +356,8 @@ SILC_FSM_STATE(silc_client_notify_join)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry;
@@ -282,28 +375,55 @@ SILC_FSM_STATE(silc_client_notify_join)
   if (!channel)
     goto out;
 
+  /* If channel is being resolved handle notify after resolving */
+  if (channel->internal.resolve_cmd_ident) {
+    silc_client_unref_channel(client, conn, channel);
+    SILC_FSM_CALL(silc_client_command_pending(
+                                     conn, SILC_COMMAND_NONE,
+                                     channel->internal.resolve_cmd_ident,
+                                     silc_client_notify_wait_continue,
+                                     notify));
+    /* NOT REACHED */
+  }
+
   /* Get Client ID */
   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);
-    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
-                                        client, conn, &id.u.client_id, NULL,
-                                        silc_client_notify_resolved,
-                                        fsm));
+    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, 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 */
-  silc_client_add_to_channel(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);
@@ -325,13 +445,13 @@ SILC_FSM_STATE(silc_client_notify_leave)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  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 = NULL;
   SilcChannelEntry channel = NULL;
-  SilcChannelUser chu;
   SilcID id;
 
   SILC_LOG_DEBUG(("Notify: LEAVE"));
@@ -344,6 +464,17 @@ SILC_FSM_STATE(silc_client_notify_leave)
   if (!channel)
     goto out;
 
+  /* If channel is being resolved handle notify after resolving */
+  if (channel->internal.resolve_cmd_ident) {
+    silc_client_unref_channel(client, conn, channel);
+    SILC_FSM_CALL(silc_client_command_pending(
+                                     conn, SILC_COMMAND_NONE,
+                                     channel->internal.resolve_cmd_ident,
+                                     silc_client_notify_wait_continue,
+                                     notify));
+    /* NOT REACHED */
+  }
+
   /* Get Client ID */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
@@ -354,30 +485,8 @@ SILC_FSM_STATE(silc_client_notify_leave)
     goto out;
 
   /* Remove client from channel */
-  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 0 /* Kind of useless, server will return error if client keeps using
-        non-existing client, and the entry is removed then. */
-  /* Some client implementations actually quit network by first doing
-     LEAVE and then immediately SIGNOFF.  We'll check for this by doing
-     check for the client after 5 - 34 seconds.  If it is not valid after
-     that we'll remove the client from cache. */
-  if (!silc_hash_table_count(client_entry->channels)) {
-    SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-    res->context = client;
-    res->sock = silc_socket_dup(conn->sock);
-    res->packet = silc_id_dup(client_id, SILC_ID_CLIENT);
-    silc_schedule_task_add(client->schedule, conn->sock->sock,
-                          silc_client_notify_check_client, res,
-                          (5 + (silc_rng_get_rn16(client->rng) % 29)),
-                          0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-  }
-#endif
+  if (!silc_client_remove_from_channel(client, conn, channel, client_entry))
+    goto out;
 
   /* Notify application. */
   NOTIFY(client, conn, type, client_entry, channel);
@@ -399,12 +508,14 @@ SILC_FSM_STATE(silc_client_notify_signoff)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  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;
 
@@ -421,23 +532,28 @@ 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';
 
-  /* Notify application */
-  NOTIFY(client, conn, type, client_entry, tmp);
+  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);
 
-  /* Remove from all channels */
-  silc_client_remove_from_channels(client, conn, client_entry);
+  /* Notify application */
+  if (client_entry->internal.valid)
+    NOTIFY(client, conn, type, client_entry, tmp, channel);
 
-#if 0
-  /* Remove from cache */
-  silc_idcache_del_by_context(conn->internal->client_cache, client_entry);
-#endif
+  /* Remove from channel */
+  if (channel) {
+    silc_client_remove_from_channel(client, conn, channel, client_entry);
+    silc_client_unref_channel(client, conn, channel);
+  }
 
-  /* Free data */
+  /* Delete client */
+  client_entry->internal.valid = FALSE;
+  silc_client_del_client(client, conn, client_entry);
   silc_client_unref_client(client, conn, client_entry);
-  silc_client_del_client_entry(client, conn, client_entry);
 
  out:
   /** Notify processed */
@@ -453,8 +569,9 @@ SILC_FSM_STATE(silc_client_notify_topic_set)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  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 = NULL;
@@ -475,7 +592,18 @@ SILC_FSM_STATE(silc_client_notify_topic_set)
   if (!channel)
     goto out;
 
-  /* Get ID */
+  /* If channel is being resolved handle notify after resolving */
+  if (channel->internal.resolve_cmd_ident) {
+    silc_client_unref_channel(client, conn, channel);
+    SILC_FSM_CALL(silc_client_command_pending(
+                                     conn, SILC_COMMAND_NONE,
+                                     channel->internal.resolve_cmd_ident,
+                                     silc_client_notify_wait_continue,
+                                     notify));
+    /* NOT REACHED */
+  }
+
+  /* Get ID of topic changer */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
@@ -486,26 +614,38 @@ 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);
-      SILC_FSM_CALL(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,
-                                          fsm));
-      /* NOT REACHED */
+                                          notify));
+       /* 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 */
     server = silc_client_get_server_by_id(client, conn, &id.u.server_id);
     if (!server) {
       /** Resolve server */
-      SILC_FSM_CALL(silc_client_get_server_by_id_resolve(
+      notify->channel = channel;
+      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                   silc_client_get_server_by_id_resolve(
                                           client, conn, &id.u.server_id,
                                           silc_client_notify_resolved,
-                                          fsm));
+                                          notify));
       /* NOT REACHED */
     }
     entry = server;
@@ -515,17 +655,21 @@ SILC_FSM_STATE(silc_client_notify_topic_set)
                                                  &id.u.channel_id);
     if (!channel_entry) {
       /** Resolve channel */
-      SILC_FSM_CALL(silc_client_get_channel_by_id_resolve(
+      notify->channel = channel;
+      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                   silc_client_get_channel_by_id_resolve(
                                    client, conn, &id.u.channel_id,
                                    silc_client_notify_resolved,
-                                   fsm));
+                                   notify));
       /* NOT REACHED */
     }
     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);
@@ -552,14 +696,15 @@ SILC_FSM_STATE(silc_client_notify_nick_change)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   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;
+  SilcID id, id2;
+  SilcBool valid;
 
   SILC_LOG_DEBUG(("Notify: NICK_CHANGE"));
 
@@ -572,58 +717,58 @@ SILC_FSM_STATE(silc_client_notify_nick_change)
       SILC_ID_CLIENT_COMPARE(&id.u.client_id, conn->local_id))
     goto out;
 
-  /* Find old 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);
-    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
-                                        client, conn, &id.u.client_id, NULL,
-                                        silc_client_notify_resolved,
-                                        fsm));
-    /* NOT REACHED */
-  }
-
   /* Get new Client ID */
-  if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id, NULL))
+  if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id2, NULL))
+    goto out;
+
+  /* Ignore my ID */
+  if (conn->local_id &&
+      SILC_ID_CLIENT_COMPARE(&id2.u.client_id, conn->local_id))
     goto out;
 
+  /* 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)
+    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. */
-  if (SILC_ID_COMPARE_HASH(&client_entry->id, &id.u.client_id) &&
+  if (SILC_ID_COMPARE_HASH(&client_entry->id, &id2.u.client_id) &&
       silc_utf8_strcasecmp(tmp, client_entry->nickname)) {
     /* Nickname didn't change.  Update only Client ID.  We don't notify
        application because nickname didn't change. */
-    silc_idcache_update(conn->internal->client_cache, client_entry,
-                       &client_entry->id, &id.u.client_id, NULL, NULL, FALSE);
+    silc_mutex_lock(conn->internal->lock);
+    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 */
-  if (!silc_idcache_update(conn->internal->client_cache, client_entry,
-                          NULL, NULL, client_entry->nickname_normalized,
-                          nick, TRUE)) {
-    silc_free(nick);
+  /* 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;
   }
-  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 */
@@ -640,8 +785,9 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  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 = NULL;
@@ -665,38 +811,61 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
   if (!channel)
     goto out;
 
+  /* If channel is being resolved handle notify after resolving */
+  if (channel->internal.resolve_cmd_ident) {
+    silc_client_unref_channel(client, conn, channel);
+    SILC_FSM_CALL(silc_client_command_pending(
+                                     conn, SILC_COMMAND_NONE,
+                                     channel->internal.resolve_cmd_ident,
+                                     silc_client_notify_wait_continue,
+                                     notify));
+    /* NOT REACHED */
+  }
+
   /* 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 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);
-      SILC_FSM_CALL(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,
-                                          fsm));
-      /* NOT REACHED */
+                                          notify));
+       /* 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 */
     server = silc_client_get_server_by_id(client, conn, &id.u.server_id);
     if (!server) {
       /** Resolve server */
-      SILC_FSM_CALL(silc_client_get_server_by_id_resolve(
+      notify->channel = channel;
+      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                   silc_client_get_server_by_id_resolve(
                                           client, conn, &id.u.server_id,
                                           silc_client_notify_resolved,
-                                          fsm));
+                                          notify));
       /* NOT REACHED */
     }
     entry = server;
@@ -706,20 +875,26 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
                                                  &id.u.channel_id);
     if (!channel_entry) {
       /** Resolve channel */
-      SILC_FSM_CALL(silc_client_get_channel_by_id_resolve(
+      notify->channel = channel;
+      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                   silc_client_get_channel_by_id_resolve(
                                    client, conn, &id.u.channel_id,
                                    silc_client_notify_resolved,
-                                   fsm));
+                                   notify));
       /* NOT REACHED */
     }
     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;
@@ -735,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);
@@ -761,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,
@@ -800,8 +980,9 @@ SILC_FSM_STATE(silc_client_notify_cumode_change)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  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 = NULL, client_entry2 = NULL;
@@ -823,38 +1004,81 @@ SILC_FSM_STATE(silc_client_notify_cumode_change)
   if (!channel)
     goto out;
 
+  /* If channel is being resolved handle notify after resolving */
+  if (channel->internal.resolve_cmd_ident) {
+    silc_client_unref_channel(client, conn, channel);
+    SILC_FSM_CALL(silc_client_command_pending(
+                                     conn, SILC_COMMAND_NONE,
+                                     channel->internal.resolve_cmd_ident,
+                                     silc_client_notify_wait_continue,
+                                     notify));
+    /* NOT REACHED */
+  }
+
+  /* Get target Client ID */
+  if (!silc_argument_get_decoded(args, 3, SILC_ARGUMENT_ID, &id2, NULL))
+    goto out;
+
+  /* Find target Client entry */
+  client_entry2 = silc_client_get_client_by_id(client, conn, &id2.u.client_id);
+  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(
+                                        client, conn, &id2.u.client_id, NULL,
+                                        silc_client_notify_resolved,
+                                        notify));
+    /* 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);
-      SILC_FSM_CALL(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,
-                                          fsm));
-      /* NOT REACHED */
+                                          notify));
+       /* 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 */
     server = silc_client_get_server_by_id(client, conn, &id.u.server_id);
     if (!server) {
       /** Resolve server */
-      SILC_FSM_CALL(silc_client_get_server_by_id_resolve(
+      notify->channel = channel;
+      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                   silc_client_get_server_by_id_resolve(
                                           client, conn, &id.u.server_id,
                                           silc_client_notify_resolved,
-                                          fsm));
+                                          notify));
       /* NOT REACHED */
     }
     entry = server;
@@ -864,36 +1088,23 @@ SILC_FSM_STATE(silc_client_notify_cumode_change)
                                                  &id.u.channel_id);
     if (!channel_entry) {
       /** Resolve channel */
-      SILC_FSM_CALL(silc_client_get_channel_by_id_resolve(
+      notify->channel = channel;
+      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                   silc_client_get_channel_by_id_resolve(
                                    client, conn, &id.u.channel_id,
                                    silc_client_notify_resolved,
-                                   fsm));
+                                   notify));
       /* NOT REACHED */
     }
     entry = channel_entry;
   }
 
-  /* Get target Client ID */
-  if (!silc_argument_get_decoded(args, 3, SILC_ARGUMENT_ID, &id2, NULL))
-    goto out;
-
-  /* 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]) {
-    /** Resolve client */
-    silc_client_unref_client(client, conn, client_entry);
-    silc_client_unref_client(client, conn, client_entry2);
-    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
-                                        client, conn, &id2.u.client_id, NULL,
-                                        silc_client_notify_resolved,
-                                        fsm));
-    /* NOT REACHED */
-  }
-
   /* 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);
@@ -921,8 +1132,8 @@ SILC_FSM_STATE(silc_client_notify_motd)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   unsigned char *tmp;
@@ -952,8 +1163,8 @@ SILC_FSM_STATE(silc_client_notify_channel_change)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcChannelEntry channel = NULL;
@@ -970,6 +1181,17 @@ SILC_FSM_STATE(silc_client_notify_channel_change)
   if (!channel)
     goto out;
 
+  /* If channel is being resolved handle notify after resolving */
+  if (channel->internal.resolve_cmd_ident) {
+    silc_client_unref_channel(client, conn, channel);
+    SILC_FSM_CALL(silc_client_command_pending(
+                                     conn, SILC_COMMAND_NONE,
+                                     channel->internal.resolve_cmd_ident,
+                                     silc_client_notify_wait_continue,
+                                     notify));
+    /* NOT REACHED */
+  }
+
   /* Get the new ID */
   if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
@@ -996,28 +1218,19 @@ SILC_FSM_STATE(silc_client_notify_kicked)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  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, client_entry2;
   SilcChannelEntry channel = NULL;
-  SilcChannelUser chu;
   unsigned char *tmp;
   SilcUInt32 tmp_len;
   SilcID id;
 
   SILC_LOG_DEBUG(("Notify: KICKED"));
 
-  /* Get Client ID */
-  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
-    goto out;
-
-  /* Find Client entry */
-  client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry)
-    goto out;
-
   /* Get channel entry */
   if (!silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CHANNEL,
                      &id.u.channel_id, sizeof(id.u.channel_id)))
@@ -1026,20 +1239,42 @@ SILC_FSM_STATE(silc_client_notify_kicked)
   if (!channel)
     goto out;
 
+  /* If channel is being resolved handle notify after resolving */
+  if (channel->internal.resolve_cmd_ident) {
+    silc_client_unref_channel(client, conn, channel);
+    SILC_FSM_CALL(silc_client_command_pending(
+                                     conn, SILC_COMMAND_NONE,
+                                     channel->internal.resolve_cmd_ident,
+                                     silc_client_notify_wait_continue,
+                                     notify));
+    /* NOT REACHED */
+  }
+
+  /* Get the kicked Client ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
+
+  /* Find client entry */
+  client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
+  if (!client_entry)
+    goto out;
+
   /* Get kicker's Client ID */
   if (!silc_argument_get_decoded(args, 3, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
   /* 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);
-    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
+    notify->channel = channel;
+    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,
-                                        fsm));
+                                        notify));
     /* NOT REACHED */
   }
 
@@ -1048,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. */
@@ -1063,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);
   }
 
@@ -1084,8 +1316,8 @@ SILC_FSM_STATE(silc_client_notify_killed)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry = NULL, client_entry2 = NULL;
@@ -1118,14 +1350,14 @@ 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);
       SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
                                           client, conn, &id.u.client_id, NULL,
                                           silc_client_notify_resolved,
-                                          fsm));
+                                          notify));
       /* NOT REACHED */
     }
     entry = client_entry2;
@@ -1137,7 +1369,7 @@ SILC_FSM_STATE(silc_client_notify_killed)
       SILC_FSM_CALL(silc_client_get_server_by_id_resolve(
                                           client, conn, &id.u.server_id,
                                           silc_client_notify_resolved,
-                                          fsm));
+                                          notify));
       /* NOT REACHED */
     }
     entry = server;
@@ -1150,7 +1382,7 @@ SILC_FSM_STATE(silc_client_notify_killed)
       SILC_FSM_CALL(silc_client_get_channel_by_id_resolve(
                                    client, conn, &id.u.channel_id,
                                    silc_client_notify_resolved,
-                                   fsm));
+                                   notify));
       /* NOT REACHED */
     }
     entry = channel_entry;
@@ -1160,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);
@@ -1185,21 +1420,29 @@ SILC_FSM_STATE(silc_client_notify_server_signoff)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   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))
@@ -1207,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;
@@ -1235,8 +1481,8 @@ SILC_FSM_STATE(silc_client_notify_error)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry;
@@ -1258,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);
     }
@@ -1281,13 +1528,12 @@ SILC_FSM_STATE(silc_client_notify_watch)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
-  SilcPacket packet = state_context;
-  SilcNotifyPayload payload = (SilcNotifyPayload)packet->next;
+  SilcClientNotify notify = state_context;
+  SilcNotifyPayload payload = notify->payload;
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry = NULL;
-  SilcNotifyType notify = 0;
-  SilcBool del_client = FALSE;
+  SilcNotifyType ntype = 0;
   unsigned char *pk, *tmp;
   SilcUInt32 mode, pk_len, tmp_len;
   SilcPublicKey public_key = NULL;
@@ -1299,15 +1545,15 @@ 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(
                                         client, conn, &id.u.client_id, NULL,
                                         silc_client_notify_resolved,
-                                        fsm));
+                                        notify));
     /* NOT REACHED */
   }
 
@@ -1322,18 +1568,15 @@ SILC_FSM_STATE(silc_client_notify_watch)
   if (tmp && tmp_len != 2)
     goto out;
   if (tmp)
-    SILC_GET16_MSB(notify, tmp);
+    SILC_GET16_MSB(ntype, tmp);
 
   /* Get nickname */
   tmp = silc_argument_get_arg_type(args, 2, NULL);
   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. */
@@ -1353,24 +1596,19 @@ SILC_FSM_STATE(silc_client_notify_watch)
   }
 
   /* Notify application. */
-  NOTIFY(client, conn, type, client_entry, tmp, mode, notify,
+  NOTIFY(client, conn, type, client_entry, tmp, mode, ntype,
         client_entry->public_key);
 
   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 && notify == SILC_NOTIFY_TYPE_NICK_CHANGE &&
-      !silc_hash_table_count(client_entry->channels))
-    del_client = TRUE;
-  else if (notify == SILC_NOTIFY_TYPE_SIGNOFF ||
-          notify == SILC_NOTIFY_TYPE_SERVER_SIGNOFF ||
-          notify == 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);