Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client_notify.c
index 1f81b435c98c0773b11420be56d56b6b997b944e..2ccb574c1ac8a384d7fd6a36939e8a0c5b097e52 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2005 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
 
 */
 /* $Id$ */
-/* This file includes the Notify packet handling. Notify packets are
-   important packets sent by the server. They tell different things to the
-   client such as nick changes, mode changes etc. */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "client_internal.h"
 
-/* Context used for resolving client, channel and server info. */
-typedef struct {
-  void *packet;
-  void *context;
-  SilcSocketConnection sock;
-} *SilcClientNotifyResolve;
+/************************** Types and definitions ***************************/
+
+#define NOTIFY conn->client->internal->ops->notify
 
-SILC_TASK_CALLBACK(silc_client_notify_check_client)
+/* 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 **************************/
+
+/* 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,
+                                       SilcStatus status,
+                                       SilcDList entries,
+                                       void *context)
 {
-  SilcClientNotifyResolve res = (SilcClientNotifyResolve)context;
-  SilcClient client = res->context;
-  SilcClientConnection conn = res->sock->user_data;
-  SilcClientID *client_id = res->packet;
-  silc_client_get_client_by_id_resolve(client, conn, client_id,
-                                      NULL, NULL, NULL);
-  silc_free(client_id);
-  silc_socket_free(res->sock);
-  silc_free(res);
+  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(notify->fsm);
 }
 
-SILC_TASK_CALLBACK(silc_client_notify_del_client_cb)
+/* 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)
 {
-  SilcClientNotifyResolve res = (SilcClientNotifyResolve)context;
-  SilcClient client = res->context;
-  SilcClientConnection conn = res->sock->user_data;
-  SilcClientID *client_id = res->packet;
-  SilcClientEntry client_entry;
-  client_entry = silc_client_get_client_by_id(client, conn, client_id);
-  if (client_entry)
-    silc_client_del_client(client, conn, client_entry);
-  silc_free(client_id);
-  silc_socket_free(res->sock);
-  silc_free(res);
+  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;
 }
 
-/* Called when notify is received and some async operation (such as command)
-   is required before processing the notify message. This calls again the
-   silc_client_notify_by_server and reprocesses the original notify packet. */
+/********************************* Notify ***********************************/
+
+/* Process received notify packet */
 
-static void silc_client_notify_by_server_pending(void *context, void *context2)
+SILC_FSM_STATE(silc_client_notify)
 {
-  SilcClientNotifyResolve res = (SilcClientNotifyResolve)context;
-  SilcClientCommandReplyContext reply =
-    (SilcClientCommandReplyContext)context2;
+  SilcPacket packet = state_context;
+  SilcClientNotify notify;
+  SilcNotifyPayload payload;
 
-  SILC_LOG_DEBUG(("Start"));
+  payload = silc_notify_payload_parse(silc_buffer_data(&packet->buffer),
+                                     silc_buffer_len(&packet->buffer));
+  if (!payload) {
+    SILC_LOG_DEBUG(("Malformed notify payload"));
+    silc_packet_free(packet);
+    return SILC_FSM_FINISH;
+  }
 
-  if (reply && !silc_command_get_status(reply->payload, NULL, NULL))
-    goto out;
+  if (!silc_notify_get_args(payload)) {
+    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;
+  }
 
-  silc_client_notify_by_server(res->context, res->sock, res->packet);
+  notify->packet = packet;
+  notify->payload = payload;
+  notify->fsm = fsm;
+  silc_fsm_set_state_context(fsm, notify);
 
- out:
-  silc_socket_free(res->sock);
-  silc_packet_context_free(res->packet);
-  silc_free(res);
+  /* Process the notify */
+  switch (silc_notify_get_type(payload)) {
+
+  case SILC_NOTIFY_TYPE_NONE:
+    /** NONE */
+    silc_fsm_next(fsm, silc_client_notify_none);
+    break;
+
+  case SILC_NOTIFY_TYPE_INVITE:
+    /** INVITE */
+    silc_fsm_next(fsm, silc_client_notify_invite);
+    break;
+
+  case SILC_NOTIFY_TYPE_JOIN:
+    /** JOIN */
+    silc_fsm_next(fsm, silc_client_notify_join);
+    break;
+
+  case SILC_NOTIFY_TYPE_LEAVE:
+    /** LEAVE */
+    silc_fsm_next(fsm, silc_client_notify_leave);
+    break;
+
+  case SILC_NOTIFY_TYPE_SIGNOFF:
+    /** SIGNOFF */
+    silc_fsm_next(fsm, silc_client_notify_signoff);
+    break;
+
+  case SILC_NOTIFY_TYPE_TOPIC_SET:
+    /** TOPIC_SET */
+    silc_fsm_next(fsm, silc_client_notify_topic_set);
+    break;
+
+  case SILC_NOTIFY_TYPE_NICK_CHANGE:
+    /** NICK_CHANGE */
+    silc_fsm_next(fsm, silc_client_notify_nick_change);
+    break;
+
+  case SILC_NOTIFY_TYPE_CMODE_CHANGE:
+    /** CMODE_CHANGE */
+    silc_fsm_next(fsm, silc_client_notify_cmode_change);
+    break;
+
+  case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
+    /** CUMODE_CHANGE */
+    silc_fsm_next(fsm, silc_client_notify_cumode_change);
+    break;
+
+  case SILC_NOTIFY_TYPE_MOTD:
+    /** MOTD */
+    silc_fsm_next(fsm, silc_client_notify_motd);
+    break;
+
+  case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
+    /** CHANNEL_CHANGE */
+    silc_fsm_next(fsm, silc_client_notify_channel_change);
+    break;
+
+  case SILC_NOTIFY_TYPE_KICKED:
+    /** KICKED */
+    silc_fsm_next(fsm, silc_client_notify_kicked);
+    break;
+
+  case SILC_NOTIFY_TYPE_KILLED:
+    /** KILLED */
+    silc_fsm_next(fsm, silc_client_notify_killed);
+    break;
+
+  case SILC_NOTIFY_TYPE_SERVER_SIGNOFF:
+    /** SERVER_SIGNOFF */
+    silc_fsm_next(fsm, silc_client_notify_server_signoff);
+    break;
+
+  case SILC_NOTIFY_TYPE_ERROR:
+    /** ERROR */
+    silc_fsm_next(fsm, silc_client_notify_error);
+    break;
+
+  case SILC_NOTIFY_TYPE_WATCH:
+    /** WATCH */
+    silc_fsm_next(fsm, silc_client_notify_watch);
+    break;
+
+  default:
+    /** Unknown notify */
+    silc_notify_payload_free(payload);
+    silc_packet_free(packet);
+    silc_free(notify);
+    return SILC_FSM_FINISH;
+    break;
+  }
+
+  return SILC_FSM_CONTINUE;
 }
 
-/* Resets the channel entry's resolve_cmd_ident after whatever-thing
-   was resolved is completed. */
+/* Notify processed, finish the packet processing thread */
 
-static void silc_client_channel_cond(void *context, void *context2)
+SILC_FSM_STATE(silc_client_notify_processed)
 {
-  SilcClientNotifyResolve res = (SilcClientNotifyResolve)context;
-  SilcClient client = res->context;
-  SilcClientConnection conn = res->sock->user_data;
-  SilcChannelID *channel_id = res->packet;
-  SilcChannelEntry channel;
-  channel = silc_client_get_channel_by_id(client, conn, channel_id);
-  if (channel)
-    channel->resolve_cmd_ident = 0;
-  silc_free(channel_id);
-  silc_socket_free(res->sock);
-  silc_free(res);
+  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;
 }
 
-/* Function that starts waiting for the `cmd_ident' to arrive and
-   marks the channel info being resolved.  */
+/********************************** NONE ************************************/
 
-static void silc_client_channel_set_wait(SilcClient client,
-                                        SilcClientConnection conn,
-                                        SilcChannelEntry channel,
-                                        SilcUInt16 cmd_ident)
+SILC_FSM_STATE(silc_client_notify_none)
 {
-  SilcClientNotifyResolve res;
-
-  if (!channel->resolve_cmd_ident) {
-    res = silc_calloc(1, sizeof(*res));
-    res->context = client;
-    res->sock = silc_socket_dup(conn->sock);
-    res->packet = silc_id_dup(channel->id, SILC_ID_CHANNEL);
-    silc_client_command_pending(conn, SILC_COMMAND_NONE, cmd_ident,
-                               silc_client_channel_cond, res);
-    channel->resolve_cmd_ident = cmd_ident;
-  }
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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));
+
+  /** Notify processed */
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
 }
 
-/* Attaches to the channel's resolving cmd ident and calls the
-   notify handling with `packet' after it's received. */
+/********************************* INVITE ***********************************/
+
+/* Someone invite me to a channel */
 
-static void silc_client_channel_wait(SilcClient client,
-                                    SilcClientConnection conn,
-                                    SilcChannelEntry channel,
-                                    SilcPacketContext *packet)
+SILC_FSM_STATE(silc_client_notify_invite)
 {
-  SilcClientNotifyResolve res;
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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;
+  SilcChannelEntry channel = NULL;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len;
+  SilcID id;
 
-  if (!channel->resolve_cmd_ident)
-    return;
+  SILC_LOG_DEBUG(("Notify: INVITE"));
 
-  res = silc_calloc(1, sizeof(*res));
-  res->packet = silc_packet_context_dup(packet);
-  res->context = client;
-  res->sock = silc_socket_dup(conn->sock);
+  /* Get Channel ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-  silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                             channel->resolve_cmd_ident,
-                             silc_client_notify_by_server_pending, res);
+  /* Get the channel name */
+  tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+  if (!tmp)
+    goto out;
+
+  /* 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))
+    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->internal.valid) {
+    /** 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, conn, &id.u.client_id, NULL,
+                                        silc_client_notify_resolved,
+                                        notify));
+    /* NOT REACHED */
+  }
+
+  /* Notify application */
+  NOTIFY(client, conn, type, channel, tmp, client_entry);
+
+  silc_client_unref_client(client, conn, client_entry);
+
+ out:
+  /** Notify processed */
+  silc_client_unref_channel(client, conn, channel);
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
 }
 
-/* Resolve client, channel or server information. */
+/********************************** JOIN ************************************/
 
-static void silc_client_notify_by_server_resolve(SilcClient client,
-                                                SilcClientConnection conn,
-                                                SilcPacketContext *packet,
-                                                SilcIdType id_type,
-                                                void *id)
+/* Someone joined a channel */
+
+SILC_FSM_STATE(silc_client_notify_join)
 {
-  SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-  SilcBuffer idp = silc_id_payload_encode(id, id_type);
-
-  res->packet = silc_packet_context_dup(packet);
-  res->context = client;
-  res->sock = silc_socket_dup(conn->sock);
-
-  /* For client resolving use WHOIS, and otherwise use IDENTIFY */
-  if (id_type == SILC_ID_CLIENT) {
-    silc_client_command_register(client, SILC_COMMAND_WHOIS, NULL, NULL,
-                                silc_client_command_reply_whois_i, 0,
-                                ++conn->cmd_ident);
-    silc_client_command_send(client, conn, SILC_COMMAND_WHOIS, conn->cmd_ident,
-                            1, 4, idp->data, idp->len);
-    silc_client_command_pending(conn, SILC_COMMAND_WHOIS, conn->cmd_ident,
-                               silc_client_notify_by_server_pending, res);
-  } else {
-    silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
-                                silc_client_command_reply_identify_i, 0,
-                                ++conn->cmd_ident);
-    silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY,
-                            conn->cmd_ident, 1, 5, idp->data, idp->len);
-    silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, conn->cmd_ident,
-                               silc_client_notify_by_server_pending, res);
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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;
+  SilcChannelEntry channel = NULL;
+  SilcID id;
+
+  SILC_LOG_DEBUG(("Notify: JOIN"));
+
+  /* Get Channel ID */
+  if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
+
+  /* Get channel entry */
+  channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+  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.  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 */
+    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, FALSE);
+
+  /* Join the client to channel */
+  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_buffer_free(idp);
+
+  silc_rwlock_unlock(channel->internal.lock);
+  silc_rwlock_unlock(client_entry->internal.lock);
+
+  /* Notify application. */
+  NOTIFY(client, conn, type, client_entry, channel);
+
+  silc_client_unref_client(client, conn, client_entry);
+
+ out:
+  /** Notify processed */
+  silc_client_unref_channel(client, conn, channel);
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
 }
 
-/* Received notify message from server */
+/********************************** LEAVE ***********************************/
+
+/* Someone left a channel */
 
-void silc_client_notify_by_server(SilcClient client,
-                                 SilcSocketConnection sock,
-                                 SilcPacketContext *packet)
+SILC_FSM_STATE(silc_client_notify_leave)
 {
-  SilcBuffer buffer = packet->buffer;
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcNotifyPayload payload;
-  SilcNotifyType type;
-  SilcArgumentPayload args;
-
-  void *id;
-  SilcIdType id_type;
-  SilcClientID *client_id = NULL;
-  SilcChannelID *channel_id = NULL;
-  SilcServerID *server_id = NULL;
+  SilcClientConnection conn = fsm_context;
+  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 = NULL;
-  SilcClientEntry client_entry2 = NULL;
-  SilcChannelEntry channel;
-  SilcChannelUser chu;
-  SilcServerEntry server;
-  unsigned char *tmp;
-  SilcUInt32 tmp_len, mode;
+  SilcChannelEntry channel = NULL;
+  SilcID id;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_LOG_DEBUG(("Notify: LEAVE"));
 
-  payload = silc_notify_payload_parse(buffer->data, buffer->len);
-  if (!payload)
+  /* 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)))
+    goto out;
+  channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+  if (!channel)
     goto out;
 
-  type = silc_notify_get_type(payload);
-  args = silc_notify_get_args(payload);
-  if (!args)
+  /* 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;
 
-  switch(type) {
-  case SILC_NOTIFY_TYPE_NONE:
-    /* Notify application */
-    client->internal->ops->notify(client, conn, type,
-                                 silc_argument_get_arg_type(args, 1, NULL));
-    break;
+  /* Find Client entry */
+  client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
+  if (!client_entry)
+    goto out;
 
-  case SILC_NOTIFY_TYPE_INVITE:
-    /*
-     * Someone invited me to a channel. Find Client and Channel entries
-     * for the application.
-     */
+  /* Remove client from channel */
+  if (!silc_client_remove_from_channel(client, conn, channel, client_entry))
+    goto out;
 
-    SILC_LOG_DEBUG(("Notify: INVITE"));
+  /* Notify application. */
+  NOTIFY(client, conn, type, client_entry, channel);
 
-    /* Get Channel ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
+  silc_client_unref_client(client, conn, client_entry);
 
-    channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!channel_id)
-      goto out;
+ out:
+  /** Notify processed */
+  silc_client_unref_channel(client, conn, channel);
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-    /* Get the channel entry */
-    channel = silc_client_get_channel_by_id(client, conn, channel_id);
+/********************************* SIGNOFF **********************************/
 
-    /* Get sender Client ID */
-    tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
-    if (!tmp)
-      goto out;
+/* Someone quit SILC network */
 
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_id)
-      goto out;
+SILC_FSM_STATE(silc_client_notify_signoff)
+{
+  SilcClientConnection conn = fsm_context;
+  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;
+  SilcChannelEntry channel = NULL;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len;
+  SilcID id;
 
-    /* Find Client entry and if not found query it */
-    client_entry = silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry) {
-      silc_client_notify_by_server_resolve(client, conn, packet,
-                                          SILC_ID_CLIENT, client_id);
-      goto out;
-    }
+  SILC_LOG_DEBUG(("Notify: SIGNOFF"));
 
-    /* Get the channel name */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-    if (!tmp)
-      goto out;
+  /* Get Client ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-    /* Notify application */
-    client->internal->ops->notify(client, conn, type, channel, tmp,
-                                 client_entry);
-    break;
+  /* Find Client entry */
+  client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
+  if (!client_entry)
+    goto out;
 
-  case SILC_NOTIFY_TYPE_JOIN:
-    /*
-     * Someone has joined to a channel. Get their ID and nickname and
-     * cache them for later use.
-     */
+  /* Get signoff message */
+  tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+  if (tmp && tmp_len > 128)
+    tmp[128] = '\0';
 
-    SILC_LOG_DEBUG(("Notify: JOIN"));
+  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);
 
-    /* Get Channel ID */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-    if (!tmp)
-      goto out;
+  /* Notify application */
+  if (client_entry->internal.valid)
+    NOTIFY(client, conn, type, client_entry, tmp, channel);
 
-    channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!channel_id)
-      goto out;
+  /* Remove from channel */
+  if (channel) {
+    silc_client_remove_from_channel(client, conn, channel, client_entry);
+    silc_client_unref_channel(client, conn, channel);
+  }
 
-    /* Get channel entry */
-    channel = silc_client_get_channel_by_id(client, conn, channel_id);
-    if (!channel)
-      break;
+  /* Delete client */
+  client_entry->internal.valid = FALSE;
+  silc_client_del_client(client, conn, client_entry);
+  silc_client_unref_client(client, conn, client_entry);
 
-    /* Get Client ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
+ out:
+  /** Notify processed */
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_id)
-      goto out;
+/******************************** TOPIC_SET *********************************/
 
-    /* Find Client entry and if not found query it */
-    client_entry = silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry) {
-      silc_client_channel_set_wait(client, conn, channel,
-                                  conn->cmd_ident + 1);
-      silc_client_notify_by_server_resolve(client, conn, packet,
-                                          SILC_ID_CLIENT, client_id);
-      goto out;
-    }
+/* Someone set topic on a channel */
 
-    /* If nickname or username hasn't been resolved, do so */
-    if (!client_entry->nickname || !client_entry->username) {
-      if (client_entry->status & SILC_CLIENT_STATUS_RESOLVING) {
-       /* Attach to existing resolving */
-       SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-       res->packet = silc_packet_context_dup(packet);
-       res->context = client;
-       res->sock = silc_socket_dup(conn->sock);
-       silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                                   client_entry->resolve_cmd_ident,
-                                   silc_client_notify_by_server_pending,
-                                   res);
-       goto out;
-      }
+SILC_FSM_STATE(silc_client_notify_topic_set)
+{
+  SilcClientConnection conn = fsm_context;
+  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 = NULL;
+  SilcChannelEntry channel = NULL, channel_entry = NULL;
+  SilcServerEntry server = NULL;
+  void *entry;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len;
+  SilcID id;
 
-      /* Do new resolving */
-      silc_client_channel_set_wait(client, conn, channel,
-                                  conn->cmd_ident + 1);
-      silc_client_notify_by_server_resolve(client, conn, packet,
-                                          SILC_ID_CLIENT, client_id);
-      client_entry->status |= SILC_CLIENT_STATUS_RESOLVING;
-      client_entry->resolve_cmd_ident = conn->cmd_ident;
-      goto out;
-    } else {
-      if (client_entry != conn->local_entry)
-       silc_client_nickname_format(client, conn, client_entry);
+  SILC_LOG_DEBUG(("Notify: TOPIC_SET"));
+
+  /* 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)))
+    goto out;
+  channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+  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 ID of topic changer */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
+
+  /* Get topic */
+  tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+  if (!tmp)
+    goto out;
+
+  if (id.type == SILC_ID_CLIENT) {
+    /* Find Client entry */
+    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 */
+      }
     }
 
-    /* If information is being resolved for this channel, wait for it */
-    if (channel->resolve_cmd_ident) {
-      silc_client_channel_wait(client, conn, channel, packet);
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
       goto out;
-    }
 
-    /* Join the client to channel */
-    if (!silc_client_on_channel(channel, client_entry)) {
-      chu = silc_calloc(1, sizeof(*chu));
-      chu->client = client_entry;
-      chu->channel = channel;
-      silc_hash_table_add(channel->user_list, client_entry, chu);
-      silc_hash_table_add(client_entry->channels, channel, chu);
+    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 */
+      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,
+                                          notify));
+      /* NOT REACHED */
     }
+    entry = server;
+  } else {
+    /* Find Channel entry */
+    channel_entry = silc_client_get_channel_by_id(client, conn,
+                                                 &id.u.channel_id);
+    if (!channel_entry) {
+      /** Resolve channel */
+      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,
+                                   notify));
+      /* NOT REACHED */
+    }
+    entry = channel_entry;
+  }
 
-    /* Notify application. The channel entry is sent last as this notify
-       is for channel but application don't know it from the arguments
-       sent by server. */
-    client->internal->ops->notify(client, conn, type, client_entry, channel);
-    break;
+  silc_rwlock_wrlock(channel->internal.lock);
+  silc_free(channel->topic);
+  channel->topic = silc_memdup(tmp, strlen(tmp));
+  silc_rwlock_unlock(channel->internal.lock);
 
-  case SILC_NOTIFY_TYPE_LEAVE:
-    /*
-     * Someone has left a channel. We will remove it from the channel but
-     * we'll keep it in the cache in case we'll need it later.
-     */
+  /* Notify application. */
+  NOTIFY(client, conn, type, id.type, entry, channel->topic, channel);
 
-    SILC_LOG_DEBUG(("Notify: LEAVE"));
+  if (client_entry)
+    silc_client_unref_client(client, conn, client_entry);
+  if (server)
+    silc_client_unref_server(client, conn, server);
+  if (channel_entry)
+    silc_client_unref_channel(client, conn, channel_entry);
 
-    /* Get Client ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
+ out:
+  /** Notify processed */
+  silc_client_unref_channel(client, conn, channel);
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_id)
-      goto out;
+/****************************** NICK_CHANGE *********************************/
 
-    /* Find Client entry */
-    client_entry =
-      silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry)
-      goto out;
+/* Someone changed their nickname on a channel */
 
-    /* Get channel entry */
-    channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
-                               SILC_ID_CHANNEL);
-    if (!channel_id)
-      goto out;
-    channel = silc_client_get_channel_by_id(client, conn, channel_id);
-    if (!channel)
-      break;
-
-    /* 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);
-    }
+SILC_FSM_STATE(silc_client_notify_nick_change)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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, oldnick[256 + 1];
+  SilcUInt32 tmp_len;
+  SilcID id, id2;
+  SilcBool valid;
 
-    /* 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);
-    }
+  SILC_LOG_DEBUG(("Notify: NICK_CHANGE"));
 
-    /* Notify application. The channel entry is sent last as this notify
-       is for channel but application don't know it from the arguments
-       sent by server. */
-    client->internal->ops->notify(client, conn, type, client_entry, channel);
-    break;
+  /* Get ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-  case SILC_NOTIFY_TYPE_SIGNOFF:
-    /*
-     * Someone left SILC. We'll remove it from all channels and from cache.
-     */
+  /* Ignore my ID */
+  if (conn->local_id &&
+      SILC_ID_CLIENT_COMPARE(&id.u.client_id, conn->local_id))
+    goto out;
 
-    SILC_LOG_DEBUG(("Notify: SIGNOFF"));
+  /* Get new Client ID */
+  if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id2, NULL))
+    goto out;
 
-    /* Get Client ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
+  /* Ignore my ID */
+  if (conn->local_id &&
+      SILC_ID_CLIENT_COMPARE(&id2.u.client_id, conn->local_id))
+    goto out;
 
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_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;
 
-    /* Find Client entry */
-    client_entry =
-      silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry)
-      goto out;
+  /* Take the new nickname */
+  tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
+  if (!tmp)
+    goto out;
 
-    /* Get signoff message */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-    if (tmp_len > 128)
-      tmp = NULL;
+  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, &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_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;
+  }
 
-    /* Notify application */
-    client->internal->ops->notify(client, conn, type, client_entry, tmp);
+  /* 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;
+  }
 
-    /* Remove from all channels */
-    silc_client_remove_from_channels(client, conn, client_entry);
+  silc_rwlock_unlock(client_entry->internal.lock);
 
-    /* Remove from cache */
-    silc_idcache_del_by_context(conn->internal->client_cache, client_entry);
+  /* 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);
 
-    /* Free data */
-    silc_client_del_client_entry(client, conn, client_entry);
-    break;
+ out:
+  /** Notify processed */
+  silc_client_unref_client(client, conn, client_entry);
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-  case SILC_NOTIFY_TYPE_TOPIC_SET:
-    /*
-     * Someone set the topic on a channel.
-     */
+/****************************** CMODE_CHANGE ********************************/
 
-    SILC_LOG_DEBUG(("Notify: TOPIC_SET"));
+/* Someone changed channel mode */
 
-    /* Get channel entry */
-    channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
-                               SILC_ID_CHANNEL);
-    if (!channel_id)
-      goto out;
-    channel = silc_client_get_channel_by_id(client, conn, channel_id);
-    if (!channel)
-      break;
+SILC_FSM_STATE(silc_client_notify_cmode_change)
+{
+  SilcClientConnection conn = fsm_context;
+  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 = NULL;
+  SilcChannelEntry channel = NULL, channel_entry = NULL;
+  SilcServerEntry server = NULL;
+  void *entry;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len, mode;
+  SilcID id;
+  char *passphrase, *cipher, *hmac;
+  SilcPublicKey founder_key = NULL;
+  SilcDList chpks = NULL;
 
-    /* Get ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
-    id = silc_id_payload_parse_id(tmp, tmp_len, &id_type);
-    if (!id)
-      goto out;
+  SILC_LOG_DEBUG(("Notify: CMODE_CHANGE"));
+
+  /* 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)))
+    goto out;
+  channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+  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 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 */
-    if (id_type == SILC_ID_CLIENT) {
-      /* Find Client entry */
-      client_id = id;
-      client_entry = silc_client_get_client_by_id(client, conn, client_id);
-      if (!client_entry) {
-       silc_client_channel_set_wait(client, conn, channel,
-                                    conn->cmd_ident + 1);
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_CLIENT, client_id);
-       goto out;
-      }
-    } else if (id_type == SILC_ID_SERVER) {
-      /* Find Server entry */
-      server_id = id;
-      server = silc_client_get_server_by_id(client, conn, server_id);
-      if (!server) {
-       silc_client_channel_set_wait(client, conn, channel,
-                                    conn->cmd_ident + 1);
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_SERVER, server_id);
-       server = silc_client_add_server(client, conn, NULL, NULL, server_id);
-       if (!server)
-         goto out;
-
-       server->resolve_cmd_ident = conn->cmd_ident;
-       server_id = NULL;
-       goto out;
+    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 */
       }
+    }
 
-      /* If entry being resoled, wait for it before processing this notify */
-      if (server->resolve_cmd_ident) {
-       SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-       res->packet = silc_packet_context_dup(packet);
-       res->context = client;
-       res->sock = silc_socket_dup(conn->sock);
-       silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                                   server->resolve_cmd_ident,
-                                   silc_client_notify_by_server_pending, res);
-       goto out;
-      }
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
+      goto out;
 
-      /* Save the pointer to the client_entry pointer */
-      client_entry = (SilcClientEntry)server;
-    } else {
-      /* Find Channel entry */
-      silc_free(channel_id);
-      channel_id = id;
-      client_entry = (SilcClientEntry)
-       silc_client_get_channel_by_id(client, conn, channel_id);
-      if (!client_entry) {
-       silc_client_channel_set_wait(client, conn, channel,
-                                    conn->cmd_ident + 1);
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_CHANNEL, channel_id);
-       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 */
+      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,
+                                          notify));
+      /* NOT REACHED */
+    }
+    entry = server;
+  } else {
+    /* Find Channel entry */
+    channel_entry = silc_client_get_channel_by_id(client, conn,
+                                                 &id.u.channel_id);
+    if (!channel_entry) {
+      /** Resolve channel */
+      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,
+                                   notify));
+      /* NOT REACHED */
     }
+    entry = channel_entry;
+  }
+
+  silc_rwlock_wrlock(channel->internal.lock);
 
-    /* Get topic */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-    if (!tmp)
+  /* 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)) {
+      silc_rwlock_unlock(channel->internal.lock);
       goto out;
+    }
+    if (!channel->founder_key) {
+      channel->founder_key = founder_key;
+      founder_key = NULL;
+    }
+  }
+
+  /* Get the cipher */
+  cipher = silc_argument_get_arg_type(args, 3, &tmp_len);
+
+  /* Get the hmac */
+  hmac = silc_argument_get_arg_type(args, 4, &tmp_len);
+  if (hmac) {
+    unsigned char hash[SILC_HASH_MAXLEN];
+    SilcHmac newhmac;
 
-    /* If information is being resolved for this channel, wait for it */
-    if (channel->resolve_cmd_ident) {
-      silc_client_channel_wait(client, conn, channel, packet);
+    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);
     if (tmp) {
-      silc_free(channel->topic);
-      channel->topic = silc_memdup(tmp, strlen(tmp));
+      silc_hash_make(silc_hmac_get_hash(newhmac), tmp, tmp_len, hash);
+      silc_hmac_set_key(newhmac, hash,
+                       silc_hash_len(silc_hmac_get_hash(newhmac)));
+      if (channel->internal.hmac)
+       silc_hmac_free(channel->internal.hmac);
+      channel->internal.hmac = newhmac;
+      memset(hash, 0, sizeof(hash));
     }
+  }
 
-    /* Notify application. The channel entry is sent last as this notify
-       is for channel but application don't know it from the arguments
-       sent by server. */
-    client->internal->ops->notify(client, conn, type, id_type,
-                                 client_entry, tmp, channel);
+  /* Get the passphrase if it was set */
+  passphrase = silc_argument_get_arg_type(args, 5, &tmp_len);
 
-    break;
+  /* Get user limit */
+  tmp = silc_argument_get_arg_type(args, 8, &tmp_len);
+  if (tmp && tmp_len == 4)
+    SILC_GET32_MSB(channel->user_limit, tmp);
+  if (!(channel->mode & SILC_CHANNEL_MODE_ULIMIT))
+    channel->user_limit = 0;
 
-  case SILC_NOTIFY_TYPE_NICK_CHANGE:
-    /*
-     * Someone changed their nickname. If we don't have entry for the new
-     * ID we will query it and return here after it's done. After we've
-     * returned we fetch the old entry and free it and notify the
-     * application.
-     */
-
-    SILC_LOG_DEBUG(("Notify: NICK_CHANGE"));
-
-    /* Get old Client ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
+  /* Get the channel public key that was added or removed */
+  tmp = silc_argument_get_arg_type(args, 7, &tmp_len);
+  if (tmp)
+    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);
 
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_id)
-      goto out;
+  /* Save the new mode */
+  channel->mode = mode;
 
-    /* Ignore my ID */
-    if (conn->local_id && SILC_ID_CLIENT_COMPARE(client_id, conn->local_id))
-      break;
+  silc_rwlock_unlock(channel->internal.lock);
 
-    /* Find old Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry)
-      goto out;
-    silc_free(client_id);
-    client_id = NULL;
-
-    /* Wait for resolving if necessary */
-    if (client_entry->status & SILC_CLIENT_STATUS_RESOLVING) {
-      SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-      res->packet = silc_packet_context_dup(packet);
-      res->context = client;
-      res->sock = silc_socket_dup(conn->sock);
-      silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                                 client_entry->resolve_cmd_ident,
-                                 silc_client_notify_by_server_pending, res);
-      goto out;
-    }
+  /* Notify application. */
+  NOTIFY(client, conn, type, id.type, entry, mode, cipher, hmac,
+        passphrase, channel->founder_key, chpks, channel);
 
-    client_entry->valid = FALSE;
+ out:
+  if (founder_key)
+    silc_pkcs_public_key_free(founder_key);
+  if (chpks)
+    silc_argument_list_free(chpks, SILC_ARGUMENT_PUBLIC_KEY);
+  if (client_entry)
+    silc_client_unref_client(client, conn, client_entry);
+  if (server)
+    silc_client_unref_server(client, conn, server);
+  if (channel_entry)
+    silc_client_unref_channel(client, conn, channel_entry);
+  silc_client_unref_channel(client, conn, channel);
+
+  /** Notify processed */
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-    /* Get new Client ID */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-    if (!tmp)
-      goto out;
+/***************************** CUMODE_CHANGE ********************************/
 
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_id)
-      goto out;
+/* Someone changed a user's mode on a channel */
 
-    /* Take the nickname */
-    tmp = silc_argument_get_arg_type(args, 3, NULL);
-    if (!tmp)
-      goto out;
+SILC_FSM_STATE(silc_client_notify_cumode_change)
+{
+  SilcClientConnection conn = fsm_context;
+  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 = NULL, client_entry2 = NULL;
+  SilcChannelEntry channel = NULL, channel_entry = NULL;
+  SilcServerEntry server = NULL;
+  SilcChannelUser chu;
+  void *entry;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len, mode;
+  SilcID id, id2;
+
+  SILC_LOG_DEBUG(("Notify: CUMODE_CHANGE"));
+
+  /* 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)))
+    goto out;
+  channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+  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);
 
-    /* Check whether nickname changed at all.  It is possible that nick
-       change notify is received but nickname didn't change, only the
-       ID changes.  Check whether the hashes in the Client ID match, if
-       they do nickname didn't change. */
-    if (SILC_ID_COMPARE_HASH(client_entry->id, client_id) &&
-       silc_utf8_strcasecmp(tmp, client_entry->nickname)) {
-      /* Nickname didn't change.  Update only Client ID. */
-
-      /* Normalize nickname */
-      tmp = silc_identifier_check(tmp, strlen(tmp),
-                                 SILC_STRING_UTF8, 128, NULL);
-      if (!tmp)
-       goto out;
-
-      silc_idcache_del_by_context(conn->internal->client_cache,
-                                 client_entry);
-      silc_free(client_entry->id);
-      client_entry->id = silc_id_dup(client_id, SILC_ID_CLIENT);
-      silc_idcache_add(conn->internal->client_cache, tmp,
-                      client_entry->id, client_entry, 0, NULL);
-
-      /* Notify application */
-      client->internal->ops->notify(client, conn, type,
-                                   client_entry, client_entry);
-      break;
+  /* 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 = 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 */
+      }
     }
 
-    /* Create new client entry, and save all old information with the
-       new nickname and client ID */
-    client_entry2 = silc_client_add_client(client, conn, NULL, NULL,
-                                          client_entry->realname,
-                                          silc_id_dup(client_id,
-                                                      SILC_ID_CLIENT), 0);
-    if (!client_entry2)
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
       goto out;
 
-    if (client_entry->server)
-      client_entry2->server = strdup(client_entry->server);
-    if (client_entry->username)
-      client_entry2->username = strdup(client_entry->username);
-    if (client_entry->hostname)
-      client_entry2->hostname = strdup(client_entry->hostname);
-    client_entry2->fingerprint = client_entry->fingerprint;
-    client_entry2->fingerprint_len = client_entry->fingerprint_len;
-    client_entry->fingerprint = NULL;
-    client_entry->fingerprint_len = 0;
-    silc_client_update_client(client, conn, client_entry2, tmp, NULL, NULL,
-                             client_entry->mode);
-
-    /* Remove the old from cache */
-    silc_idcache_del_by_context(conn->internal->client_cache, client_entry);
-
-    /* Replace old ID entry with new one on all channels. */
-    silc_client_replace_from_channels(client, conn, client_entry,
-                                     client_entry2);
-
-    /* Notify application */
-    client->internal->ops->notify(client, conn, type,
-                                 client_entry, client_entry2);
-
-    /* Free old client entry */
-    silc_client_del_client_entry(client, conn, client_entry);
+    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 */
+      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,
+                                          notify));
+      /* NOT REACHED */
+    }
+    entry = server;
+  } else {
+    /* Find Channel entry */
+    channel_entry = silc_client_get_channel_by_id(client, conn,
+                                                 &id.u.channel_id);
+    if (!channel_entry) {
+      /** Resolve channel */
+      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,
+                                   notify));
+      /* NOT REACHED */
+    }
+    entry = channel_entry;
+  }
 
-    break;
+  /* 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);
 
-  case SILC_NOTIFY_TYPE_CMODE_CHANGE:
-    {
-      /*
-       * Someone changed a channel mode
-       */
-      char *passphrase, *cipher, *hmac;
-      SilcPublicKey founder_key = NULL;
-      SilcBufferStruct chpks;
-
-      SILC_LOG_DEBUG(("Notify: CMODE_CHANGE"));
-
-      /* Get channel entry */
-      channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
-                                 SILC_ID_CHANNEL);
-      if (!channel_id)
-       goto out;
-      channel = silc_client_get_channel_by_id(client, conn, channel_id);
-      if (!channel)
-       goto out;
-
-      /* Get ID */
-      tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-      if (!tmp)
-       goto out;
-      id = silc_id_payload_parse_id(tmp, tmp_len, &id_type);
-      if (!id)
-       goto out;
-
-      /* Find Client entry */
-      if (id_type == SILC_ID_CLIENT) {
-       /* Find Client entry */
-       client_id = id;
-       client_entry = silc_client_get_client_by_id(client, conn, client_id);
-       if (!client_entry) {
-         silc_client_channel_set_wait(client, conn, channel,
-                                      conn->cmd_ident + 1);
-         silc_client_notify_by_server_resolve(client, conn, packet,
-                                              SILC_ID_CLIENT, client_id);
-         goto out;
-       }
-
-       if (!client_entry->nickname) {
-         if (client_entry->status & SILC_CLIENT_STATUS_RESOLVING) {
-           /* Attach to existing resolving */
-           SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-           res->packet = silc_packet_context_dup(packet);
-           res->context = client;
-           res->sock = silc_socket_dup(conn->sock);
-           silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                                       client_entry->resolve_cmd_ident,
-                                       silc_client_notify_by_server_pending,
-                                       res);
-           goto out;
-         }
-
-         /* Do new resolving */
-         silc_client_channel_set_wait(client, conn, channel,
-                                      conn->cmd_ident + 1);
-         silc_client_notify_by_server_resolve(client, conn, packet,
-                                              SILC_ID_CLIENT, client_id);
-         client_entry->status |= SILC_CLIENT_STATUS_RESOLVING;
-         client_entry->resolve_cmd_ident = conn->cmd_ident;
-         goto out;
-       }
-      } else if (id_type == SILC_ID_SERVER) {
-       /* Find Server entry */
-       server_id = id;
-       server = silc_client_get_server_by_id(client, conn, server_id);
-       if (!server) {
-         silc_client_channel_set_wait(client, conn, channel,
-                                      conn->cmd_ident + 1);
-         silc_client_notify_by_server_resolve(client, conn, packet,
-                                              SILC_ID_SERVER, server_id);
-         server = silc_client_add_server(client, conn, NULL, NULL, server_id);
-         if (!server)
-           goto out;
-
-         server->resolve_cmd_ident = conn->cmd_ident;
-         server_id = NULL;
-         goto out;
-       }
-
-       /* If entry being resoled, wait for it before processing this notify */
-       if (server->resolve_cmd_ident) {
-         SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-         res->packet = silc_packet_context_dup(packet);
-         res->context = client;
-         res->sock = silc_socket_dup(conn->sock);
-         silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                                     server->resolve_cmd_ident,
-                                     silc_client_notify_by_server_pending,
-                                     res);
-         goto out;
-       }
-
-       /* Save the pointer to the client_entry pointer */
-       client_entry = (SilcClientEntry)server;
-      } else {
-       /* Find Channel entry */
-       silc_free(channel_id);
-       channel_id = id;
-       client_entry = (SilcClientEntry)
-         silc_client_get_channel_by_id(client, conn, channel_id);
-       if (!client_entry) {
-         silc_client_channel_set_wait(client, conn, channel,
-                                      conn->cmd_ident + 1);
-         silc_client_notify_by_server_resolve(client, conn, packet,
-                                              SILC_ID_CHANNEL, channel_id);
-         goto out;
-       }
-      }
+  /* Notify application. */
+  NOTIFY(client, conn, type, id.type, entry, mode, client_entry2, channel);
 
-      /* Get the mode */
-      tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-      if (!tmp)
-       goto out;
+ out:
+  silc_client_unref_client(client, conn, client_entry2);
+  if (client_entry)
+    silc_client_unref_client(client, conn, client_entry);
+  if (server)
+    silc_client_unref_server(client, conn, server);
+  if (channel_entry)
+    silc_client_unref_channel(client, conn, channel_entry);
+  silc_client_unref_channel(client, conn, channel);
+
+  /** Notify processed */
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-      SILC_GET32_MSB(mode, tmp);
+/********************************* MOTD *************************************/
 
-      /* If information is being resolved for this channel, wait for it */
-      if (channel->resolve_cmd_ident) {
-       silc_client_channel_wait(client, conn, channel, packet);
-       goto out;
-      }
+/* Received Message of the day */
 
-      /* Save the new mode */
-      channel->mode = mode;
+SILC_FSM_STATE(silc_client_notify_motd)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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;
+  SilcUInt32 tmp_len;
 
-      /* Get the cipher */
-      cipher = silc_argument_get_arg_type(args, 3, &tmp_len);
+  SILC_LOG_DEBUG(("Notify: MOTD"));
 
-      /* Get the hmac */
-      hmac = silc_argument_get_arg_type(args, 4, &tmp_len);
-      if (hmac) {
-       unsigned char hash[SILC_HASH_MAXLEN];
+  /* Get motd */
+  tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+  if (!tmp)
+    goto out;
 
-       if (channel->hmac)
-         silc_hmac_free(channel->hmac);
-       if (!silc_hmac_alloc(hmac, NULL, &channel->hmac))
-         goto out;
+  /* Notify application */
+  NOTIFY(client, conn, type, tmp);
 
-       silc_hash_make(silc_hmac_get_hash(channel->hmac),
-                      channel->key, channel->key_len / 8,
-                      hash);
-       silc_hmac_set_key(channel->hmac, hash,
-                         silc_hash_len(silc_hmac_get_hash(channel->hmac)));
-       memset(hash, 0, sizeof(hash));
-      }
+ out:
+  /** Notify processed */
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-      /* Get the passphrase if it was set */
-      passphrase = silc_argument_get_arg_type(args, 5, &tmp_len);
+/**************************** CHANNEL CHANGE ********************************/
 
-      /* Get the channel founder key if it was set */
-      tmp = silc_argument_get_arg_type(args, 6, &tmp_len);
-      if (tmp) {
-       if (!silc_pkcs_public_key_payload_decode(tmp, tmp_len, &founder_key))
-         founder_key = NULL;
-      }
+/* Router has enforced a new ID to a channel, change it */
 
-      /* Get user limit */
-      tmp = silc_argument_get_arg_type(args, 8, &tmp_len);
-      if (tmp && tmp_len == 4)
-        SILC_GET32_MSB(channel->user_limit, tmp);
-      if (!(channel->mode & SILC_CHANNEL_MODE_ULIMIT))
-        channel->user_limit = 0;
-
-      /* Get the channel public key that was added or removed */
-      tmp = silc_argument_get_arg_type(args, 7, &tmp_len);
-      if (tmp)
-       silc_buffer_set(&chpks, tmp, tmp_len);
-
-      /* Notify application. The channel entry is sent last as this notify
-        is for channel but application don't know it from the arguments
-        sent by server. */
-      client->internal->ops->notify(client, conn, type, id_type,
-                                   client_entry, mode, cipher, hmac,
-                                   passphrase, founder_key,
-                                   tmp ? &chpks : NULL, channel);
-
-      if (founder_key)
-       silc_pkcs_public_key_free(founder_key);
-    }
-    break;
+SILC_FSM_STATE(silc_client_notify_channel_change)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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;
+  SilcID id;
+
+  SILC_LOG_DEBUG(("Notify: CHANNEL_CHANGE"));
+
+  /* Get the old ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-  case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
-    /*
-     * Someone changed user's mode on a channel
-     */
+  /* Get the channel entry */
+  channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+  if (!channel)
+    goto out;
 
-    SILC_LOG_DEBUG(("Notify: CUMODE_CHANGE"));
+  /* 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 channel entry */
-    channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
-                               SILC_ID_CHANNEL);
-    if (!channel_id)
-      goto out;
-    channel = silc_client_get_channel_by_id(client, conn, channel_id);
-    if (!channel)
-      break;
+  /* Get the new ID */
+  if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-    /* Get ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
-    id = silc_id_payload_parse_id(tmp, tmp_len, &id_type);
-    if (!id)
-      goto out;
+  /* Replace the Channel ID */
+  if (!silc_client_replace_channel_id(client, conn, channel, &id.u.channel_id))
+    goto out;
 
-    /* Find Client entry */
-    if (id_type == SILC_ID_CLIENT) {
-      /* Find Client entry */
-      client_id = id;
-      client_entry = silc_client_get_client_by_id(client, conn, client_id);
-      if (!client_entry) {
-       silc_client_channel_set_wait(client, conn, channel,
-                                    conn->cmd_ident + 1);
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_CLIENT, client_id);
-       goto out;
-      }
+  /* Notify application */
+  NOTIFY(client, conn, type, channel, channel);
 
-      if (!client_entry->nickname) {
-       if (client_entry->status & SILC_CLIENT_STATUS_RESOLVING) {
-         /* Attach to existing resolving */
-         SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-         res->packet = silc_packet_context_dup(packet);
-         res->context = client;
-         res->sock = silc_socket_dup(conn->sock);
-         silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                                     client_entry->resolve_cmd_ident,
-                                     silc_client_notify_by_server_pending,
-                                     res);
-         goto out;
-       }
-
-       /* Do new resolving */
-       silc_client_channel_set_wait(client, conn, channel,
-                                    conn->cmd_ident + 1);
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_CLIENT, client_id);
-        client_entry->status |= SILC_CLIENT_STATUS_RESOLVING;
-        client_entry->resolve_cmd_ident = conn->cmd_ident;
-       goto out;
-      }
-    } else if (id_type == SILC_ID_SERVER) {
-      /* Find Server entry */
-      server_id = id;
-      server = silc_client_get_server_by_id(client, conn, server_id);
-      if (!server) {
-       silc_client_channel_set_wait(client, conn, channel,
-                                    conn->cmd_ident + 1);
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_SERVER, server_id);
-       server = silc_client_add_server(client, conn, NULL, NULL, server_id);
-       if (!server)
-         goto out;
-
-       server->resolve_cmd_ident = conn->cmd_ident;
-       server_id = NULL;
-       goto out;
-      }
+ out:
+  /** Notify processed */
+  silc_client_unref_channel(client, conn, channel);
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-      /* If entry being resoled, wait for it before processing this notify */
-      if (server->resolve_cmd_ident) {
-       SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-       res->packet = silc_packet_context_dup(packet);
-       res->context = client;
-       res->sock = silc_socket_dup(conn->sock);
-       silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                                   server->resolve_cmd_ident,
-                                   silc_client_notify_by_server_pending, res);
-       goto out;
-      }
+/******************************** KICKED ************************************/
 
-      /* Save the pointer to the client_entry pointer */
-      client_entry = (SilcClientEntry)server;
-    } else {
-      /* Find Channel entry */
-      silc_free(channel_id);
-      channel_id = id;
-      client_entry = (SilcClientEntry)
-       silc_client_get_channel_by_id(client, conn, channel_id);
-      if (!client_entry) {
-       silc_client_channel_set_wait(client, conn, channel,
-                                    conn->cmd_ident + 1);
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_CHANNEL, channel_id);
-       goto out;
-      }
-    }
+/* Some client was kicked from a channel */
 
-    /* Get the mode */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-    if (!tmp)
-      goto out;
+SILC_FSM_STATE(silc_client_notify_kicked)
+{
+  SilcClientConnection conn = fsm_context;
+  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, client_entry2;
+  SilcChannelEntry channel = NULL;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len;
+  SilcID id;
 
-    SILC_GET32_MSB(mode, tmp);
+  SILC_LOG_DEBUG(("Notify: KICKED"));
 
-    /* If information is being resolved for this channel, wait for it */
-    if (channel->resolve_cmd_ident) {
-      silc_client_channel_wait(client, conn, channel, packet);
-      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)))
+    goto out;
+  channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+  if (!channel)
+    goto out;
 
-    /* Get target Client ID */
-    tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
-    if (!tmp)
-      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 */
+  }
 
-    silc_free(client_id);
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_id)
-      goto out;
+  /* Get the kicked Client ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-    /* Find target Client entry */
-    client_entry2 =
-      silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry2) {
-      silc_client_notify_by_server_resolve(client, conn, packet,
-                                          SILC_ID_CLIENT, client_id);
-      goto out;
-    }
+  /* Find client entry */
+  client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
+  if (!client_entry)
+    goto out;
 
-    /* Save the mode */
-    chu = silc_client_on_channel(channel, client_entry2);
-    if (chu)
-      chu->mode = mode;
-
-    /* Notify application. The channel entry is sent last as this notify
-       is for channel but application don't know it from the arguments
-       sent by server. */
-    client->internal->ops->notify(client, conn, type,
-                                 id_type, client_entry, mode,
-                                 client_entry2, channel);
-    break;
+  /* Get kicker's Client ID */
+  if (!silc_argument_get_decoded(args, 3, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-  case SILC_NOTIFY_TYPE_MOTD:
-    /*
-     * Received Message of the day
-     */
+  /* 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->internal.valid) {
+    /** Resolve client */
+    silc_client_unref_client(client, conn, client_entry);
+    silc_client_unref_client(client, conn, client_entry2);
+    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,
+                                        notify));
+    /* NOT REACHED */
+  }
 
-    SILC_LOG_DEBUG(("Notify: MOTD"));
+  /* Get comment */
+  tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
 
-    /* Get motd */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
+  /* Remove kicked client from channel */
+  if (client_entry != conn->local_entry) {
+    if (!silc_client_remove_from_channel(client, conn, channel, client_entry))
       goto out;
+  }
 
-    /* Notify application */
-    client->internal->ops->notify(client, conn, type, tmp);
-    break;
+  /* Notify application. */
+  NOTIFY(client, conn, type, client_entry, tmp, client_entry2, channel);
 
-  case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
-    /*
-     * Router has enforced a new ID to a channel. Let's change the old
-     * ID to the one provided here.
-     */
+  /* If I was kicked from channel, remove the channel */
+  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);
+  }
 
-    SILC_LOG_DEBUG(("Notify: CHANNEL_CHANGE"));
+  silc_client_unref_client(client, conn, client_entry);
+  silc_client_unref_client(client, conn, client_entry2);
 
-    /* Get the old ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
-    channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!channel_id)
-      goto out;
+ out:
+  /** Notify processed */
+  silc_client_unref_channel(client, conn, channel);
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-    /* Get the channel entry */
-    channel = silc_client_get_channel_by_id(client, conn, channel_id);
-    if (!channel)
-      goto out;
+/******************************** KILLED ************************************/
 
-    silc_free(channel_id);
-    channel_id = NULL;
+/* Some client was killed from the network */
 
-    /* Get the new ID */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-    if (!tmp)
-      goto out;
-    channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!channel_id)
-      goto out;
+SILC_FSM_STATE(silc_client_notify_killed)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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;
+  SilcChannelEntry channel_entry = NULL;
+  SilcServerEntry server = NULL;
+  void *entry;
+  char *comment;
+  SilcUInt32 comment_len;
+  SilcID id;
+
+  SILC_LOG_DEBUG(("Notify: KILLED"));
+
+  /* Get Client ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-    /* Replace the Channel ID */
-    if (silc_client_replace_channel_id(client, conn, channel, channel_id))
-      channel_id = NULL;
+  /* Find Client entry */
+  client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
+  if (!client_entry)
+    goto out;
 
-    /* Notify application */
-    client->internal->ops->notify(client, conn, type, channel, channel);
-    break;
+  /* Get comment */
+  comment = silc_argument_get_arg_type(args, 2, &comment_len);
 
-  case SILC_NOTIFY_TYPE_KICKED:
-    /*
-     * A client (maybe me) was kicked from a channel
-     */
+  /* Get killer's ID */
+  if (!silc_argument_get_decoded(args, 3, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
+
+  if (id.type == SILC_ID_CLIENT) {
+    /* Find Client entry */
+    client_entry2 = silc_client_get_client_by_id(client, conn,
+                                                &id.u.client_id);
+    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,
+                                          notify));
+      /* NOT REACHED */
+    }
+    entry = client_entry2;
+  } 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(
+                                          client, conn, &id.u.server_id,
+                                          silc_client_notify_resolved,
+                                          notify));
+      /* NOT REACHED */
+    }
+    entry = server;
+  } else {
+    /* Find Channel entry */
+    channel_entry = silc_client_get_channel_by_id(client, conn,
+                                                 &id.u.channel_id);
+    if (!channel_entry) {
+      /** 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 */
+    }
+    entry = channel_entry;
+  }
 
-    SILC_LOG_DEBUG(("Notify: KICKED"));
+  /* Notify application. */
+  NOTIFY(client, conn, type, client_entry, comment, id.type, entry);
 
+  /* Delete the killed client */
+  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);
+  if (client_entry2)
+    silc_client_unref_client(client, conn, client_entry2);
+  if (server)
+    silc_client_unref_server(client, conn, server);
+  if (channel_entry)
+    silc_client_unref_channel(client, conn, channel_entry);
+
+  /** Notify processed */
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
+
+/**************************** SERVER SIGNOFF ********************************/
+
+/* Some server quit SILC network.  Remove its clients from channels. */
+
+SILC_FSM_STATE(silc_client_notify_server_signoff)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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: 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 */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
+    if (!silc_argument_get_decoded(args, i + 1, SILC_ARGUMENT_ID, &id, NULL))
       goto out;
 
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_id)
-      goto out;
+    /* Get the client entry */
+    client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
+    if (client_entry && client_entry->internal.valid)
+      silc_dlist_add(clients, client_entry);
+  }
 
-    /* Find Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry)
-      goto out;
+  /* Notify application. */
+  NOTIFY(client, conn, type, server_entry, clients);
 
-    /* Get channel entry */
-    channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
-                               SILC_ID_CHANNEL);
-    if (!channel_id)
-      goto out;
-    channel = silc_client_get_channel_by_id(client, conn, channel_id);
-    if (!channel)
-      break;
+  /* Delete the clients */
+  silc_dlist_start(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);
+  }
 
-    /* From protocol version 1.1 we get the kicker's client ID as well */
-    tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
-    if (tmp) {
-      silc_free(client_id);
-      client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-      if (!client_id)
-       goto out;
-
-      /* Find kicker's client entry and if not found resolve it */
-      client_entry2 = silc_client_get_client_by_id(client, conn, client_id);
-      if (!client_entry2) {
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_CLIENT, client_id);
-       goto out;
-      } else {
-       if (client_entry2 != conn->local_entry)
-         silc_client_nickname_format(client, conn, client_entry2);
-      }
-    }
+ 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;
+}
 
-    /* Get comment */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+/******************************** ERROR *************************************/
 
-    /* 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);
-      }
-    }
+/* Some error occurred */
 
-    /* Notify application. The channel entry is sent last as this notify
-       is for channel but application don't know it from the arguments
-       sent by server. */
-    client->internal->ops->notify(client, conn, type, client_entry, tmp,
-                                 client_entry2, channel);
-
-    /* Remove kicked client (us) from channel */
-    if (client_entry == conn->local_entry) {
-      /* If I was kicked from channel, remove the channel */
-      if (conn->current_channel == channel)
-       conn->current_channel = NULL;
-      silc_client_del_channel(client, conn, channel);
-    } else {
-      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_entry->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) % 529)),
-                              0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-      }
-    }
-    break;
+SILC_FSM_STATE(silc_client_notify_error)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len;
+  SilcID id;
+  SilcStatus error;
 
-  case SILC_NOTIFY_TYPE_KILLED:
-    {
-      /*
-       * A client (maybe me) was killed from the network.
-       */
-      char *comment;
-      SilcUInt32 comment_len;
-
-      SILC_LOG_DEBUG(("Notify: KILLED"));
-
-      /* Get Client ID */
-      tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-      if (!tmp)
-       goto out;
-
-      client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-      if (!client_id)
-       goto out;
-
-      /* Find Client entry */
-      client_entry = silc_client_get_client_by_id(client, conn, client_id);
-      if (!client_entry)
-       goto out;
-
-      /* Get comment */
-      comment = silc_argument_get_arg_type(args, 2, &comment_len);
-
-      /* From protocol version 1.1 we get killer's client ID as well */
-      tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
-      if (tmp) {
-       silc_free(client_id);
-       client_id = NULL;
-       id = silc_id_payload_parse_id(tmp, tmp_len, &id_type);
-       if (!id)
-         goto out;
-
-       /* Find Client entry */
-       if (id_type == SILC_ID_CLIENT) {
-         /* Find Client entry */
-         client_id = id;
-         client_entry2 = silc_client_get_client_by_id(client, conn,
-                                                      client_id);
-         if (!client_entry) {
-           silc_client_notify_by_server_resolve(client, conn, packet,
-                                                SILC_ID_CLIENT, client_id);
-           goto out;
-         }
-       } else if (id_type == SILC_ID_SERVER) {
-         /* Find Server entry */
-         server_id = id;
-         server = silc_client_get_server_by_id(client, conn, server_id);
-         if (!server) {
-           silc_client_notify_by_server_resolve(client, conn, packet,
-                                                SILC_ID_SERVER, server_id);
-           server = silc_client_add_server(client, conn, NULL, NULL,
-                                           server_id);
-           if (!server)
-             goto out;
-
-           server->resolve_cmd_ident = conn->cmd_ident;
-           server_id = NULL;
-           goto out;
-         }
-
-         if (server->resolve_cmd_ident) {
-           SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-           res->packet = silc_packet_context_dup(packet);
-           res->context = client;
-           res->sock = silc_socket_dup(conn->sock);
-           silc_client_command_pending(conn, SILC_COMMAND_NONE,
-                                       server->resolve_cmd_ident,
-                                       silc_client_notify_by_server_pending,
-                                       res);
-           goto out;
-         }
-
-         /* Save the pointer to the client_entry pointer */
-         client_entry2 = (SilcClientEntry)server;
-       } else {
-         /* Find Channel entry */
-         channel_id = id;
-         channel = silc_client_get_channel_by_id(client, conn, channel_id);
-         if (!channel) {
-           silc_client_notify_by_server_resolve(client, conn, packet,
-                                                SILC_ID_CHANNEL, channel_id);
-           goto out;
-         }
-
-         /* Save the pointer to the client_entry pointer */
-         client_entry2 = (SilcClientEntry)channel;
-         silc_free(channel_id);
-         channel_id = NULL;
-       }
-      }
+  /* Get error */
+  tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+  if (!tmp && tmp_len != 1)
+    goto out;
+  error = (SilcStatus)tmp[0];
 
-      /* Notify application. */
-      client->internal->ops->notify(client, conn, type, client_entry,
-                                   comment, id_type, client_entry2);
+  SILC_LOG_DEBUG(("Notify: ERROR (%d)", error));
 
-      if (client_entry != conn->local_entry)
-       /* Remove the client from all channels and free it */
-       silc_client_del_client(client, conn, client_entry);
+  /* Handle the error */
+  if (error == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) {
+    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 && 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);
     }
-    break;
+  }
 
-  case SILC_NOTIFY_TYPE_SERVER_SIGNOFF:
-    {
-      /*
-       * A server quit the SILC network and some clients must be removed
-       * from channels as they quit as well.
-       */
-      SilcClientEntry *clients = NULL;
-      SilcUInt32 clients_count = 0;
-      int i;
-
-      SILC_LOG_DEBUG(("Notify: SIGNOFF"));
-
-      for (i = 1; i < silc_argument_get_arg_num(args); i++) {
-       /* Get Client ID */
-       tmp = silc_argument_get_arg_type(args, i + 1, &tmp_len);
-       if (tmp) {
-         client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-         if (!client_id)
-           goto out;
-
-         /* Get the client entry */
-         client_entry = silc_client_get_client_by_id(client, conn, client_id);
-         if (client_entry) {
-           clients = silc_realloc(clients, sizeof(*clients) *
-                                  (clients_count + 1));
-           clients[clients_count] = client_entry;
-           clients_count++;
-         }
-         silc_free(client_id);
-       }
-      }
-      client_id = NULL;
-
-      /* Notify application. We don't keep server entries so the server
-        entry is returned as NULL. The client's are returned as array
-        of SilcClientEntry pointers. */
-      client->internal->ops->notify(client, conn, type, NULL,
-                                   clients, clients_count);
-
-      for (i = 0; i < clients_count; i++) {
-       /* Remove client from all channels */
-       client_entry = clients[i];
-       if (client_entry == conn->local_entry)
-         continue;
-
-       /* Remove the client from all channels and free it */
-       silc_client_del_client(client, conn, client_entry);
-      }
-      silc_free(clients);
+  /* Notify application. */
+  NOTIFY(client, conn, type, error);
 
-    }
-    break;
+ out:
+  /** Notify processed */
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
+}
 
-  case SILC_NOTIFY_TYPE_ERROR:
-    {
-      /*
-       * Some has occurred and server is notifying us about it.
-       */
-      SilcStatus error;
-
-      tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-      if (!tmp && tmp_len != 1)
-       goto out;
-      error = (SilcStatus)tmp[0];
-
-      SILC_LOG_DEBUG(("Notify: ERROR (%d)", error));
-
-      if (error == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) {
-       tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
-       if (tmp) {
-         client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-         if (!client_id)
-           goto out;
-         client_entry = silc_client_get_client_by_id(client, conn,
-                                                     client_id);
-         if (client_entry)
-           silc_client_del_client(client, conn, client_entry);
-       }
-      }
+/******************************** WATCH *************************************/
 
-      /* Notify application. */
-      client->internal->ops->notify(client, conn, type, error);
-    }
-    break;
+/* Received notify about some client we are watching */
 
-  case SILC_NOTIFY_TYPE_WATCH:
-    {
-      /*
-       * Received notify about some client we are watching
-       */
-      SilcNotifyType notify = 0;
-      SilcBool del_client = FALSE;
-      unsigned char *pk;
-      SilcUInt32 pk_len;
-      SilcPublicKey public_key = NULL;
-
-      SILC_LOG_DEBUG(("Notify: WATCH"));
-
-      /* Get sender Client ID */
-      tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-      if (!tmp)
-       goto out;
-      client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-      if (!client_id)
-       goto out;
-
-      /* Find Client entry and if not found query it */
-      client_entry = silc_client_get_client_by_id(client, conn, client_id);
-      if (!client_entry) {
-       silc_client_notify_by_server_resolve(client, conn, packet,
-                                            SILC_ID_CLIENT, client_id);
-       goto out;
-      }
+SILC_FSM_STATE(silc_client_notify_watch)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  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 ntype = 0;
+  unsigned char *pk, *tmp;
+  SilcUInt32 mode, pk_len, tmp_len;
+  SilcPublicKey public_key = NULL;
+  SilcID id;
 
-      /* Get user mode */
-      tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
-      if (!tmp || tmp_len != 4)
-       goto out;
-      SILC_GET32_MSB(mode, tmp);
-
-      /* Get notify type */
-      tmp = silc_argument_get_arg_type(args, 4, &tmp_len);
-      if (tmp && tmp_len != 2)
-       goto out;
-      if (tmp)
-       SILC_GET16_MSB(notify, 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);
-
-       /* If same nick, the client was new to us and has become "present"
-          to network.  Send NULL as nick to application. */
-       if (tmp_nick && silc_utf8_strcasecmp(tmp, tmp_nick))
-         tmp = NULL;
-
-       silc_free(tmp_nick);
-      }
+  SILC_LOG_DEBUG(("Notify: WATCH"));
 
-      /* Get public key, if present */
-      pk = silc_argument_get_arg_type(args, 5, &pk_len);
-      if (pk && !client_entry->public_key) {
-        if (silc_pkcs_public_key_payload_decode(pk, pk_len, &public_key)) {
-         client_entry->public_key = public_key;
-         public_key = NULL;
-       }
-      }
+  /* Get sender Client ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
 
-      /* Notify application. */
-      client->internal->ops->notify(client, conn, type, client_entry,
-                                   tmp, mode, notify,
-                                   client_entry->public_key);
-
-      client_entry->mode = mode;
-
-      /* If nickname was changed, remove the client entry unless the
-        client is on some channel */
-      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) {
-       SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
-       res->context = client;
-       res->sock = silc_socket_dup(conn->sock);
-       res->packet = client_id;
-        client_id = NULL;
-       silc_schedule_task_add(client->schedule, conn->sock->sock,
-                              silc_client_notify_del_client_cb, res,
-                              1, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-      }
+  /* 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->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,
+                                        notify));
+    /* NOT REACHED */
+  }
+
+  /* Get user mode */
+  tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
+  if (!tmp || tmp_len != 4)
+    goto out;
+  SILC_GET32_MSB(mode, tmp);
+
+  /* Get notify type */
+  tmp = silc_argument_get_arg_type(args, 4, &tmp_len);
+  if (tmp && tmp_len != 2)
+    goto out;
+  if (tmp)
+    SILC_GET16_MSB(ntype, tmp);
+
+  /* Get nickname */
+  tmp = silc_argument_get_arg_type(args, 2, NULL);
+  if (tmp) {
+    char *tmp_nick = NULL;
+
+    silc_client_nickname_parse(client, conn, client_entry->nickname,
+                              &tmp_nick);
 
-      silc_pkcs_public_key_free(public_key);
+    /* If same nick, the client was new to us and has become "present"
+       to network.  Send NULL as nick to application. */
+    if (tmp_nick && silc_utf8_strcasecmp(tmp, tmp_nick))
+      tmp = NULL;
+
+    silc_free(tmp_nick);
+  }
+
+  /* Get public key, if present */
+  pk = silc_argument_get_arg_type(args, 5, &pk_len);
+  if (pk && !client_entry->public_key) {
+    if (silc_public_key_payload_decode(pk, pk_len, &public_key)) {
+      client_entry->public_key = public_key;
+      public_key = NULL;
     }
-    break;
+  }
 
-  default:
-    break;
+  /* Notify application. */
+  NOTIFY(client, conn, type, client_entry, tmp, mode, ntype,
+        client_entry->public_key);
+
+  client_entry->mode = mode;
+
+  /* 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);
+
  out:
-  silc_notify_payload_free(payload);
-  silc_free(client_id);
-  silc_free(channel_id);
-  silc_free(server_id);
+  /** Notify processed */
+  silc_client_unref_client(client, conn, client_entry);
+  silc_fsm_next(fsm, silc_client_notify_processed);
+  return SILC_FSM_CONTINUE;
 }