Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / command.c
index 7ffc279f4fa165c9b62e24332482fdfb1cd1a968..b5827f22819405d28285b296fdb10e15997d93c9 100644 (file)
@@ -1,10 +1,10 @@
 /*
 
-  command.c 
+  command.c
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2002 Pekka Riikonen
+  Copyright (C) 1997 - 2007 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$ */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "client_internal.h"
 
-#define SILC_NOT_CONNECTED(x, c) \
-  x->internal->ops->say((x), (c), SILC_CLIENT_MESSAGE_ERROR, \
-          "You are not connected to a server, use /SERVER to connect");
+/************************** Types and definitions ***************************/
 
-/* Command operation that is called at the end of all commands. 
-   Usage: COMMAND; */
-#define COMMAND cmd->client->internal->ops->command(cmd->client, cmd->conn, \
-  cmd, TRUE, cmd->command->cmd)
+/* Command operation that is called at the end of all commands.
+   Usage: COMMAND(status); */
+#define COMMAND(status) cmd->conn->client->internal->ops->command(     \
+  cmd->conn->client, cmd->conn, TRUE, cmd->cmd, (status), cmd->argc, cmd->argv)
 
-/* Error to application. Usage: COMMAND_ERROR; */
-#define COMMAND_ERROR cmd->client->internal->ops->command(cmd->client, \
-  cmd->conn, cmd, FALSE, cmd->command->cmd)
+/* Error to application. Usage: COMMAND_ERROR(status); */
+#define COMMAND_ERROR(status)                                  \
+  cmd->conn->client->internal->ops->command(cmd->conn->client, \
+  cmd->conn, FALSE, cmd->cmd, (status), cmd->argc, cmd->argv)
 
-#define SAY cmd->client->internal->ops->say
+/* Used to register new command */
+#define SILC_CLIENT_CMD(func, cmd, name, args)                         \
+silc_client_command_register(client, SILC_COMMAND_##cmd, name,                 \
+                            silc_client_command_##func,                \
+                            silc_client_command_reply_##func, args)
 
-/* Generic function to send any command. The arguments must be sent already
-   encoded into correct form and in correct order. */
+/* Used to unregister command */
+#define SILC_CLIENT_CMDU(func, cmd, name)                              \
+silc_client_command_unregister(client, SILC_COMMAND_##cmd,             \
+                              silc_client_command_##func,              \
+                              silc_client_command_reply_##func)
+
+#define SAY cmd->conn->client->internal->ops->say
+
+/************************ Static utility functions **************************/
 
-void silc_client_command_send(SilcClient client, SilcClientConnection conn,
-                             SilcCommand command, SilcUInt16 ident,
-                             SilcUInt32 argc, ...)
+/* Return next available command identifier. */
+
+static SilcUInt16 silc_client_cmd_ident(SilcClientConnection conn)
 {
-  SilcBuffer packet;
-  va_list ap;
+  SilcUInt16 cmd_ident;
 
-  va_start(ap, argc);
+  cmd_ident = silc_atomic_add_int16(&conn->internal->cmd_ident, 1);
+  if (!cmd_ident)
+    cmd_ident = silc_atomic_add_int16(&conn->internal->cmd_ident, 1);
 
-  packet = silc_command_payload_encode_vap(command, ident, argc, ap);
-  silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND, 
-                         NULL, 0, NULL, NULL, packet->data, 
-                         packet->len, TRUE);
-  silc_buffer_free(packet);
+  return cmd_ident;
+}
+
+/* State to finish command thread after an error in resolving command */
+
+SILC_FSM_STATE(silc_client_command_continue_error)
+{
+  /* Destructor will free all resources */
+  return SILC_FSM_FINISH;
+}
+
+/* Command reply callback to continue with the execution of a command.
+   This will continue when first successful reply is received, and ignores
+   the rest.  On the other hand, if only errors are received it will
+   wait for all errors before continuing. */
+
+static SilcBool silc_client_command_continue(SilcClient client,
+                                            SilcClientConnection conn,
+                                            SilcCommand command,
+                                            SilcStatus status,
+                                            SilcStatus error,
+                                            void *context,
+                                            va_list ap)
+{
+  SilcClientCommandContext cmd = context;
+
+  /* Continue immediately when successful reply is received */
+  if (status == SILC_STATUS_OK || !SILC_STATUS_IS_ERROR(error)) {
+    SILC_FSM_CALL_CONTINUE(&cmd->thread);
+    return FALSE;
+  }
+
+  /* Error */
+  COMMAND_ERROR(error);
+
+  /* Continue after last error is received */
+  if (SILC_STATUS_IS_ERROR(status) ||
+      (status == SILC_STATUS_LIST_END && SILC_STATUS_IS_ERROR(error))) {
+    silc_fsm_next(&cmd->thread, silc_client_command_continue_error);
+    SILC_FSM_CALL_CONTINUE(&cmd->thread);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/* Continues after resolving completed. */
+
+static void silc_client_command_resolve_continue(SilcClient client,
+                                                SilcClientConnection conn,
+                                                SilcStatus status,
+                                                SilcDList clients,
+                                                void *context)
+{
+  SilcClientCommandContext cmd = context;
+
+  if (status != SILC_STATUS_OK)
+    silc_fsm_next(&cmd->thread, silc_client_command_continue_error);
+
+  /* Continue with the command */
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
+}
+
+/* Dummy command callback.  Nothing interesting to do here.  Use this when
+   you just send command but don't care about reply. */
+
+SilcBool silc_client_command_called_dummy(SilcClient client,
+                                         SilcClientConnection conn,
+                                         SilcCommand command,
+                                         SilcStatus status,
+                                         SilcStatus error,
+                                         void *context,
+                                         va_list ap)
+{
+  return FALSE;
+}
+
+/* Dummy resolving callback.  Nothing interesting to do here.  Use this
+   when you just resolve entires but don't care about reply. */
+
+void silc_client_command_resolve_dummy(SilcClient client,
+                                      SilcClientConnection conn,
+                                      SilcStatus status,
+                                      SilcDList clients,
+                                      void *context)
+{
+  /* Nothing */
+}
+
+/* Register command to client */
+
+static SilcBool
+silc_client_command_register(SilcClient client,
+                            SilcCommand command,
+                            const char *name,
+                            SilcFSMStateCallback command_func,
+                            SilcFSMStateCallback command_reply_func,
+                            SilcUInt8 max_args)
+{
+  SilcClientCommand cmd;
+
+  cmd = silc_calloc(1, sizeof(*cmd));
+  if (!cmd)
+    return FALSE;
+  cmd->cmd = command;
+  cmd->command = command_func;
+  cmd->reply = command_reply_func;
+  cmd->max_args = max_args;
+  cmd->name = name ? strdup(name) : NULL;
+  if (!cmd->name) {
+    silc_free(cmd);
+    return FALSE;
+  }
+
+  silc_list_add(client->internal->commands, cmd);
+
+  return TRUE;
+}
+
+/* Unregister command from client */
+
+static SilcBool
+silc_client_command_unregister(SilcClient client,
+                              SilcCommand command,
+                              SilcFSMStateCallback command_func,
+                              SilcFSMStateCallback command_reply_func)
+{
+  SilcClientCommand cmd;
+
+  silc_list_start(client->internal->commands);
+  while ((cmd = silc_list_get(client->internal->commands)) != SILC_LIST_END) {
+    if (cmd->cmd == command && cmd->command == command_func &&
+       cmd->reply == command_reply_func) {
+      silc_list_del(client->internal->commands, cmd);
+      silc_free(cmd->name);
+      silc_free(cmd);
+      return TRUE;
+    }
+  }
+
+  return FALSE;
 }
 
 /* Finds and returns a pointer to the command list. Return NULL if the
    command is not found. */
 
-SilcClientCommand silc_client_command_find(SilcClient client,
-                                          const char *name)
+static SilcClientCommand silc_client_command_find(SilcClient client,
+                                                 const char *name)
 {
   SilcClientCommand cmd;
 
   silc_list_start(client->internal->commands);
   while ((cmd = silc_list_get(client->internal->commands)) != SILC_LIST_END) {
-    if (cmd->name && !strcmp(cmd->name, name))
+    if (cmd->name && !strcasecmp(cmd->name, name))
       return cmd;
   }
 
   return NULL;
 }
 
-/* Calls the command (executes it).  Application can call this after
-   it has allocated the SilcClientCommandContext with the function
-   silc_client_command_alloc and found the command from the client
-   library by calling silc_client_command_find.  This will execute
-   the command. */
+/* Command thread destructor */
 
-void silc_client_command_call(SilcClientCommand command, 
-                             SilcClientCommandContext cmd)
+static void silc_client_command_destructor(SilcFSMThread thread,
+                                          void *fsm_context,
+                                          void *destructor_context)
 {
-  (*command->command)((void *)cmd, NULL);
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+
+  /* Removes commands that aren't waiting for reply but are waiting
+     for something.  They may not have been removed yet. */
+  silc_list_del(conn->internal->pending_commands, cmd);
+
+  silc_client_command_free(cmd);
 }
 
-/* Add new pending command to be executed when reply to a command has been
-   received.  The `reply_cmd' is the command that will call the `callback'
-   with `context' when reply has been received.  If `ident is non-zero
-   the `callback' will be executed when received reply with command 
-   identifier `ident'. */
-
-void silc_client_command_pending(SilcClientConnection conn,
-                                SilcCommand reply_cmd,
-                                SilcUInt16 ident,
-                                SilcCommandCb callback,
-                                void *context)
+/* Add a command pending a command reply.  Used internally by the library. */
+
+static SilcBool
+silc_client_command_add_pending(SilcClientConnection conn,
+                               SilcClientCommandContext cmd,
+                               SilcClientCommandReply reply,
+                               void *context)
 {
-  SilcClientCommandPending *reply;
-
-  reply = silc_calloc(1, sizeof(*reply));
-  reply->reply_cmd = reply_cmd;
-  reply->ident = ident;
-  reply->context = context;
-  reply->callback = callback;
-  silc_dlist_add(conn->pending_commands, reply);
+  SilcClientCommandReplyCallback cb;
+
+  silc_mutex_lock(conn->internal->lock);
+
+  /* Add pending callback, if defined */
+  if (reply) {
+    cb = silc_calloc(1, sizeof(*cb));
+    if (!cb) {
+      silc_mutex_unlock(conn->internal->lock);
+      return FALSE;
+    }
+    cb->reply = reply;
+    cb->context = context;
+    silc_list_add(cmd->reply_callbacks, cb);
+  }
+
+  /* Add pending reply */
+  silc_list_add(conn->internal->pending_commands, cmd);
+
+  silc_mutex_unlock(conn->internal->lock);
+
+  return TRUE;
 }
 
-/* Deletes pending command by reply command type. */
+/* Generic function to send any command. The arguments must be sent already
+   encoded into correct format and in correct order.  Arguments come from
+   variable argument list pointer. */
+
+static SilcUInt16 silc_client_command_send_vap(SilcClient client,
+                                              SilcClientConnection conn,
+                                              SilcClientCommandContext cmd,
+                                              SilcCommand command,
+                                              SilcClientCommandReply reply,
+                                              void *reply_context,
+                                              SilcUInt32 argc, va_list ap)
+{
+  SilcBuffer packet;
+
+  SILC_LOG_DEBUG(("Send command %s", silc_get_command_name(command)));
+
+  if (conn->internal->disconnected)
+    return 0;
+
+  if (!cmd->cmd_ident)
+    cmd->cmd_ident = silc_client_cmd_ident(conn);
+
+  /* Encode command payload */
+  packet = silc_command_payload_encode_vap(command, cmd->cmd_ident, argc, ap);
+  if (!packet)
+    return 0;
+
+  /* Send the command */
+  if (!silc_packet_send(conn->stream, SILC_PACKET_COMMAND, 0,
+                       silc_buffer_datalen(packet))) {
+    silc_buffer_free(packet);
+    return 0;
+  }
 
-void silc_client_command_pending_del(SilcClientConnection conn,
-                                    SilcCommand reply_cmd,
-                                    SilcUInt16 ident)
+  /* Add the command pending command reply */
+  silc_client_command_add_pending(conn, cmd, reply, reply_context);
+
+  silc_buffer_free(packet);
+
+  return cmd->cmd_ident;
+}
+
+/* Generic function to send any command. The arguments must be sent already
+   encoded into correct format and in correct order.  Arguments come from
+   arrays. */
+
+static SilcUInt16
+silc_client_command_send_arg_array(SilcClient client,
+                                  SilcClientConnection conn,
+                                  SilcClientCommandContext cmd,
+                                  SilcCommand command,
+                                  SilcClientCommandReply reply,
+                                  void *reply_context,
+                                  SilcUInt32 argc,
+                                  unsigned char **argv,
+                                  SilcUInt32 *argv_lens,
+                                  SilcUInt32 *argv_types)
 {
-  SilcClientCommandPending *r;
+  SilcBuffer packet;
 
-  silc_dlist_start(conn->pending_commands);
-  while ((r = silc_dlist_get(conn->pending_commands)) != SILC_LIST_END) {
-    if (r->reply_cmd == reply_cmd && r->ident == ident) {
-      silc_dlist_del(conn->pending_commands, r);
-      break;
-    }
+  SILC_LOG_DEBUG(("Send command %s", silc_get_command_name(command)));
+
+  if (conn->internal->disconnected)
+    return 0;
+
+  if (!cmd->cmd_ident)
+    cmd->cmd_ident = silc_client_cmd_ident(conn);
+
+  /* Encode command payload */
+  packet = silc_command_payload_encode(command, argc, argv, argv_lens,
+                                      argv_types, cmd->cmd_ident);
+  if (!packet)
+    return 0;
+
+  /* Send the command */
+  if (!silc_packet_send(conn->stream, SILC_PACKET_COMMAND, 0,
+                       silc_buffer_datalen(packet))) {
+    silc_buffer_free(packet);
+    return 0;
   }
+
+  /* Add the command pending command reply */
+  silc_client_command_add_pending(conn, cmd, reply, reply_context);
+
+  silc_buffer_free(packet);
+
+  return cmd->cmd_ident;
 }
 
-/* Checks for pending commands and marks callbacks to be called from
-   the command reply function. Returns TRUE if there were pending command. */
+/* Generic function to send any command. The arguments must be sent already
+   encoded into correct format and in correct order.  This is used internally
+   by the library.  */
+
+static SilcUInt16 silc_client_command_send_va(SilcClientConnection conn,
+                                             SilcClientCommandContext cmd,
+                                             SilcCommand command,
+                                             SilcClientCommandReply reply,
+                                             void *reply_context,
+                                             SilcUInt32 argc, ...)
+{
+  va_list ap;
+  SilcUInt16 cmd_ident;
+
+  va_start(ap, argc);
+  cmd_ident = silc_client_command_send_vap(conn->client, conn, cmd, command,
+                                          reply, reply_context, argc, ap);
+  va_end(ap);
 
-int silc_client_command_pending_check(SilcClientConnection conn,
-                                     SilcClientCommandReplyContext ctx,
-                                     SilcCommand command, 
-                                     SilcUInt16 ident)
+  return cmd_ident;
+}
+
+/****************************** Command API *********************************/
+
+/* Free command context and its internals */
+
+void silc_client_command_free(SilcClientCommandContext cmd)
 {
-  SilcClientCommandPending *r;
-
-  silc_dlist_start(conn->pending_commands);
-  while ((r = silc_dlist_get(conn->pending_commands)) != SILC_LIST_END) {
-    if (r->reply_cmd == command && r->ident == ident) {
-      ctx->context = r->context;
-      ctx->callback = r->callback;
-      ctx->ident = ident;
-      return TRUE;
+  SilcClientCommandReplyCallback cb;
+  int i;
+
+  for (i = 0; i < cmd->argc; i++)
+    silc_free(cmd->argv[i]);
+  silc_free(cmd->argv);
+  silc_free(cmd->argv_lens);
+  silc_free(cmd->argv_types);
+
+  silc_list_start(cmd->reply_callbacks);
+  while ((cb = silc_list_get(cmd->reply_callbacks)))
+    silc_free(cb);
+
+  silc_free(cmd);
+}
+
+/* Executes a command */
+
+SilcUInt16 silc_client_command_call(SilcClient client,
+                                   SilcClientConnection conn,
+                                   const char *command_line, ...)
+{
+  va_list va;
+  SilcUInt32 argc = 0;
+  unsigned char **argv = NULL;
+  SilcUInt32 *argv_lens = NULL, *argv_types = NULL;
+  SilcUInt16 cmd_ident;
+  SilcClientCommand command;
+  SilcClientCommandContext cmd;
+  char *arg;
+
+  if (!conn) {
+    client->internal->ops->say(client, NULL, SILC_CLIENT_MESSAGE_COMMAND_ERROR,
+      "You are not connected to a server, please connect to server");
+    return 0;
+  }
+
+  /* Parse arguments */
+  va_start(va, command_line);
+  if (command_line) {
+    char *command_name;
+
+    /* Get command name */
+    command_name = silc_memdup(command_line, strcspn(command_line, " "));
+    if (!command_name)
+      return 0;
+
+    /* Find command by name */
+    command = silc_client_command_find(client, command_name);
+    if (!command) {
+      silc_free(command_name);
+      return 0;
+    }
+
+    /* Parse command line */
+    silc_parse_command_line((char *)command_line, &argv, &argv_lens,
+                           &argv_types, &argc, command->max_args);
+
+    silc_free(command_name);
+  } else {
+    arg = va_arg(va, char *);
+    if (!arg)
+      return 0;
+
+    /* Find command by name */
+    command = silc_client_command_find(client, arg);
+    if (!command)
+      return 0;
+
+    while (arg) {
+      argv = silc_realloc(argv, sizeof(*argv) * (argc + 1));
+      argv_lens = silc_realloc(argv_lens, sizeof(*argv_lens) * (argc + 1));
+      argv_types = silc_realloc(argv_types, sizeof(*argv_types) * (argc + 1));
+      if (!argv || !argv_lens || !argv_types)
+       return 0;
+      argv[argc] = silc_memdup(arg, strlen(arg));
+      if (!argv[argc])
+       return 0;
+      argv_lens[argc] = strlen(arg);
+      argv_types[argc] = argc;
+      argc++;
+      arg = va_arg(va, char *);
     }
   }
+  va_end(va);
 
-  return FALSE;
+  /* Allocate command context */
+  cmd = silc_calloc(1, sizeof(*cmd));
+  if (!cmd)
+    return 0;
+  cmd->conn = conn;
+  cmd->cmd = command->cmd;
+  cmd->argc = argc;
+  cmd->argv = argv;
+  cmd->argv_lens = argv_lens;
+  cmd->argv_types = argv_types;
+  cmd_ident = cmd->cmd_ident = silc_client_cmd_ident(conn);
+  cmd->called = TRUE;
+  cmd->verbose = TRUE;
+  silc_list_init(cmd->reply_callbacks,
+                struct SilcClientCommandReplyCallbackStruct, next);
+
+  /*** Call command */
+  SILC_LOG_DEBUG(("Calling %s command", silc_get_command_name(cmd->cmd)));
+  silc_fsm_thread_init(&cmd->thread, &conn->internal->fsm, cmd,
+                      silc_client_command_destructor, NULL, FALSE);
+  silc_fsm_start_sync(&cmd->thread, command->command);
+
+  return cmd_ident;
 }
 
-/* Allocate Command Context */
+/* Generic function to send any command. The arguments must be sent already
+   encoded into correct format and in correct order. */
 
-SilcClientCommandContext silc_client_command_alloc(void)
+SilcUInt16 silc_client_command_send(SilcClient client,
+                                   SilcClientConnection conn,
+                                   SilcCommand command,
+                                   SilcClientCommandReply reply,
+                                   void *reply_context,
+                                   SilcUInt32 argc, ...)
 {
-  SilcClientCommandContext ctx = silc_calloc(1, sizeof(*ctx));
-  ctx->users++;
-  return ctx;
-}
+  SilcClientCommandContext cmd;
+  va_list ap;
 
-/* Free command context and its internals */
+  if (!conn || !reply)
+    return 0;
+
+  /* Allocate command context */
+  cmd = silc_calloc(1, sizeof(*cmd));
+  if (!cmd)
+    return 0;
+  cmd->conn = conn;
+  cmd->cmd = command;
+  silc_list_init(cmd->reply_callbacks,
+                struct SilcClientCommandReplyCallbackStruct, next);
+
+  /* Send the command */
+  va_start(ap, argc);
+  cmd->cmd_ident =
+    silc_client_command_send_vap(client, conn, cmd, command, reply,
+                                reply_context, argc, ap);
+  va_end(ap);
+
+  if (!cmd->cmd_ident) {
+    silc_client_command_free(cmd);
+    return 0;
+  }
+
+  /*** Wait for command reply */
+  silc_fsm_thread_init(&cmd->thread, &conn->internal->fsm, cmd,
+                      silc_client_command_destructor, NULL, FALSE);
+  silc_fsm_start_sync(&cmd->thread, silc_client_command_reply_wait);
 
-void silc_client_command_free(SilcClientCommandContext ctx)
+  return cmd->cmd_ident;
+}
+
+/* Generic function to send any command. The arguments must be sent already
+   encoded into correct format and in correct order.  Arguments come from
+   arrays. */
+
+SilcUInt16 silc_client_command_send_argv(SilcClient client,
+                                        SilcClientConnection conn,
+                                        SilcCommand command,
+                                        SilcClientCommandReply reply,
+                                        void *reply_context,
+                                        SilcUInt32 argc,
+                                        unsigned char **argv,
+                                        SilcUInt32 *argv_lens,
+                                        SilcUInt32 *argv_types)
 {
-  ctx->users--;
-  SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users + 1,
-                 ctx->users));
-  if (ctx->users < 1) {
-    int i;
-
-    for (i = 0; i < ctx->argc; i++)
-      silc_free(ctx->argv[i]);
-    silc_free(ctx->argv_lens);
-    silc_free(ctx->argv_types);
-    silc_free(ctx);
+  SilcClientCommandContext cmd;
+
+  if (!conn || !reply)
+    return 0;
+
+  /* Allocate command context */
+  cmd = silc_calloc(1, sizeof(*cmd));
+  if (!cmd)
+    return 0;
+  cmd->conn = conn;
+  cmd->cmd = command;
+
+  /* Send the command */
+  cmd->cmd_ident =
+    silc_client_command_send_arg_array(client, conn, cmd, command, reply,
+                                      reply_context, argc, argv, argv_lens,
+                                      argv_types);
+  if (!cmd->cmd_ident) {
+    silc_client_command_free(cmd);
+    return 0;
   }
+
+  /*** Wait for command reply */
+  silc_fsm_thread_init(&cmd->thread, &conn->internal->fsm, cmd,
+                      silc_client_command_destructor, NULL, FALSE);
+  silc_fsm_start_sync(&cmd->thread, silc_client_command_reply_wait);
+
+  return cmd->cmd_ident;
 }
 
-/* Duplicate Command Context by adding reference counter. The context won't
-   be free'd untill it hits zero. */
+/* Attach to a command and command identifier to receive command reply. */
 
-SilcClientCommandContext silc_client_command_dup(SilcClientCommandContext ctx)
+SilcBool silc_client_command_pending(SilcClientConnection conn,
+                                    SilcCommand command,
+                                    SilcUInt16 ident,
+                                    SilcClientCommandReply reply,
+                                    void *context)
 {
-  ctx->users++;
-  SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users - 1,
-                 ctx->users));
-  return ctx;
+  SilcClientCommandContext cmd;
+  SilcClientCommandReplyCallback cb;
+
+  if (!conn || !reply)
+    return FALSE;
+
+  SILC_LOG_DEBUG(("Add pending command reply for ident %d", ident));
+
+  silc_mutex_lock(conn->internal->lock);
+
+  /* Find the pending command */
+  silc_list_start(conn->internal->pending_commands);
+  while ((cmd = silc_list_get(conn->internal->pending_commands)))
+    if ((cmd->cmd == command || command == SILC_COMMAND_NONE)
+       && cmd->cmd_ident == ident) {
+
+      /* Add the callback */
+      cb = silc_calloc(1, sizeof(*cb));
+      if (!cb)
+       continue;
+      cb->reply = reply;
+      cb->context = context;
+      silc_list_add(cmd->reply_callbacks, cb);
+    }
+
+  silc_mutex_unlock(conn->internal->lock);
+
+  return TRUE;
 }
 
-/* Command WHOIS. This command is used to query information about 
+/******************************** WHOIS *************************************/
+
+/* Command WHOIS. This command is used to query information about
    specific user. */
 
-SILC_CLIENT_CMD_FUNC(whois)
+SILC_FSM_STATE(silc_client_command_whois)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcClient client = conn->client;
+  SilcBuffer attrs = NULL;
+  unsigned char count[4], *tmp = NULL;
+  SilcBool details = FALSE, nick = FALSE;
+  unsigned char *pubkey = NULL;
+  char *nickname = NULL;
+  int i;
 
   /* Given without arguments fetches client's own information */
   if (cmd->argc < 2) {
-    buffer = silc_id_payload_encode(cmd->conn->local_id, SILC_ID_CLIENT);
-    silc_client_command_send(cmd->client, cmd->conn, SILC_COMMAND_WHOIS, 
-                            ++conn->cmd_ident,
-                            1, 3, buffer->data, buffer->len);
-    silc_buffer_free(buffer);
-    goto out;
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1, 4,
+                               silc_buffer_data(conn->internal->local_idp),
+                               silc_buffer_len(conn->internal->local_idp));
+
+    /* Notify application */
+    COMMAND(SILC_STATUS_OK);
+
+    /** Wait for command reply */
+    silc_fsm_next(fsm, silc_client_command_reply_wait);
+    return SILC_FSM_CONTINUE;
   }
 
-  buffer = silc_command_payload_encode(SILC_COMMAND_WHOIS,
-                                      cmd->argc - 1, ++cmd->argv,
-                                      ++cmd->argv_lens, ++cmd->argv_types,
-                                      0);
-  silc_client_packet_send(cmd->client, cmd->conn->sock,
-                         SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
-                         buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-  cmd->argv--;
-  cmd->argv_lens--;
-  cmd->argv_types--;
+  for (i = 1; i < cmd->argc; i++) {
+    if (!strcasecmp(cmd->argv[i], "-details")) {
+      details = TRUE;
+    } else if (!strcasecmp(cmd->argv[i], "-pubkey") && cmd->argc > i + 1) {
+      pubkey = cmd->argv[i + 1];
+      i++;
+    } else {
+      /* We assume that the first parameter is the nickname, if it isn't
+         -details or -pubkey. The last parameter should always be the count */
+      if (i == 1) {
+       nick = TRUE;
+      } else if (i == cmd->argc - 1) {
+       int c = atoi(cmd->argv[i]);
+       SILC_PUT32_MSB(c, count);
+       tmp = count;
+      }
+    }
+  }
 
-  /* Notify application */
-  COMMAND;
+  if (details) {
+    /* If pubkey is set, add all attributes to the attrs buffer, except
+       public key */
+    if (pubkey) {
+      attrs = silc_client_attributes_request(SILC_ATTRIBUTE_USER_INFO,
+                                             SILC_ATTRIBUTE_SERVICE,
+                                             SILC_ATTRIBUTE_STATUS_MOOD,
+                                             SILC_ATTRIBUTE_STATUS_FREETEXT,
+                                             SILC_ATTRIBUTE_STATUS_MESSAGE,
+                                             SILC_ATTRIBUTE_PREFERRED_LANGUAGE,
+                                             SILC_ATTRIBUTE_PREFERRED_CONTACT,
+                                             SILC_ATTRIBUTE_TIMEZONE,
+                                             SILC_ATTRIBUTE_GEOLOCATION,
+                                             SILC_ATTRIBUTE_DEVICE_INFO,
+                                            SILC_ATTRIBUTE_USER_ICON, 0);
+    } else {
+      attrs = silc_client_attributes_request(0);
+    }
+  }
 
- out:
-  silc_client_command_free(cmd);
-}
+  if (pubkey) {
+    SilcAttributeObjPk obj;
+    SilcPublicKey pk;
 
-/* Command WHOWAS. This command is used to query history information about
-   specific user that used to exist in the network. */
+    if (!silc_pkcs_load_public_key(pubkey, SILC_PKCS_ANY, &pk)) {
+      SAY(client, conn, SILC_CLIENT_MESSAGE_COMMAND_ERROR,
+         "Could not load public key %s, check the filename",
+         pubkey);
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      goto out;
+    }
 
-SILC_CLIENT_CMD_FUNC(whowas)
-{
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
+    switch (silc_pkcs_get_type(pk)) {
+    case SILC_PKCS_SILC:
+      obj.type = "silc-rsa";
+      break;
+    case SILC_PKCS_SSH2:
+      obj.type = "ssh-rsa";
+      break;
+    case SILC_PKCS_X509V3:
+      obj.type = "x509v3-sign-rsa";
+      break;
+    case SILC_PKCS_OPENPGP:
+      obj.type = "pgp-sign-rsa";
+      break;
+    default:
+      goto out;
+      break;
+    }
+    obj.data = silc_pkcs_public_key_encode(NULL, pk, &obj.data_len);
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
+    attrs = silc_attribute_payload_encode(attrs,
+                                          SILC_ATTRIBUTE_USER_PUBLIC_KEY,
+                                          SILC_ATTRIBUTE_FLAG_VALID,
+                                          &obj, sizeof(obj));
+    silc_free(obj.data);
   }
 
-  if (cmd->argc < 2 || cmd->argc > 3) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "Usage: /WHOWAS <nickname>[@<server>] [<count>]");
-    COMMAND_ERROR;
-    goto out;
+  if (nick) {
+    if (!silc_client_nickname_parse(client, conn, cmd->argv[1], &nickname))
+      goto out;
   }
 
-  buffer = silc_command_payload_encode(SILC_COMMAND_WHOWAS,
-                                      cmd->argc - 1, ++cmd->argv,
-                                      ++cmd->argv_lens, ++cmd->argv_types,
-                                      0);
-  silc_client_packet_send(cmd->client, cmd->conn->sock,
-                         SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
-                         buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-  cmd->argv--;
-  cmd->argv_lens--;
-  cmd->argv_types--;
+  /* Send command */
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL,
+                             3, 1, nick ? nickname : NULL,
+                             nick ? strlen(nickname) : 0,
+                             2, tmp ? tmp : NULL, tmp ? 4 : 0,
+                             3, silc_buffer_datalen(attrs));
+  silc_free(nickname);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_client_command_free(cmd);
+  return SILC_FSM_FINISH;
 }
 
-/* Command IDENTIFY. This command is used to query information about 
-   specific user, especially ID's. 
+/******************************** WHOWAS ************************************/
 
-   NOTE: This command is used only internally by the client library
-   and application MUST NOT call this command directly. */
+/* Command WHOWAS. This command is used to query history information about
+   specific user that used to exist in the network. */
 
-SILC_CLIENT_CMD_FUNC(identify)
+SILC_FSM_STATE(silc_client_command_whowas)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
+  unsigned char count[4];
+  int c;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    goto out;
+  if (cmd->argc < 2 || cmd->argc > 3) {
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /WHOWAS <nickname>[@<server>] [<count>]");
+    COMMAND_ERROR((cmd->argc < 2 ? SILC_STATUS_ERR_NOT_ENOUGH_PARAMS :
+                  SILC_STATUS_ERR_TOO_MANY_PARAMS));
+    return SILC_FSM_FINISH;
   }
 
-  if (cmd->argc < 2 || cmd->argc > 3)
-    goto out;
+  if (cmd->argc == 2) {
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL,
+                               1, 1, cmd->argv[1], cmd->argv_lens[1]);
+  } else {
+    c = atoi(cmd->argv[2]);
+    SILC_PUT32_MSB(c, count);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL,
+                               2, 1, cmd->argv[1], cmd->argv_lens[1],
+                               2, count, sizeof(count));
+  }
 
-  if (cmd->argc == 2)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_IDENTIFY, 
-                                           ++conn->cmd_ident, 1,
-                                           1, cmd->argv[1],
-                                           cmd->argv_lens[1]);
-  else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_IDENTIFY, 
-                                           ++conn->cmd_ident, 2,
-                                           1, cmd->argv[1],
-                                           cmd->argv_lens[1],
-                                           4, cmd->argv[2],
-                                           cmd->argv_lens[2]);
-
-  silc_client_packet_send(cmd->client, cmd->conn->sock,
-                         SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
-                         buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
-/* Pending callbcak that will be called after the NICK command was
-   replied by the server.  This sets the nickname if there were no
-   errors. */
+/******************************** IDENTIFY **********************************/
 
-SILC_CLIENT_CMD_FUNC(nick_change)
+/* Command IDENTIFY. This command is used to query information about
+   specific user, especially ID's. */
+
+SILC_FSM_STATE(silc_client_command_identify)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcClientCommandReplyContext reply = 
-    (SilcClientCommandReplyContext)context2;
-  SilcCommandStatus status = silc_command_get_status(reply->payload);
-
-  if (status == SILC_STATUS_OK) {
-    /* Set the nickname */
-    silc_idcache_del_by_context(conn->client_cache, conn->local_entry);
-    if (conn->nickname)
-      silc_free(conn->nickname);
-    conn->nickname = strdup(cmd->argv[1]);
-    conn->local_entry->nickname = conn->nickname;
-    silc_client_nickname_format(cmd->client, conn, conn->local_entry);
-    silc_idcache_add(conn->client_cache, strdup(cmd->argv[1]), 
-                    conn->local_entry->id, conn->local_entry, 0, NULL);
-    COMMAND;
+  unsigned char count[4];
+  int c;
+
+  if (cmd->argc < 2 || cmd->argc > 3)
+    return SILC_FSM_FINISH;
+
+  if (cmd->argc == 2) {
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL,
+                               1, 1, cmd->argv[1], cmd->argv_lens[1]);
   } else {
-    COMMAND_ERROR;
+    c = atoi(cmd->argv[2]);
+    SILC_PUT32_MSB(c, count);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL,
+                               2, 1, cmd->argv[1], cmd->argv_lens[1],
+                               4, count, sizeof(count));
   }
 
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
+/********************************** NICK ************************************/
+
 /* Command NICK. Shows current nickname/sets new nickname on current
    window. */
 
-SILC_CLIENT_CMD_FUNC(nick)
+SILC_FSM_STATE(silc_client_command_nick)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd2, cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
 
   if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /NICK <nickname>");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
-  if (!strcmp(conn->nickname, cmd->argv[1]))
+  if (silc_utf8_strcasecmp(conn->local_entry->nickname, cmd->argv[1]))
     goto out;
 
   /* Show current nickname */
   if (cmd->argc < 2) {
     if (cmd->conn) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "Your nickname is %s on server %s", 
-         conn->nickname, conn->remote_host);
+      SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+         "Your nickname is %s on server %s",
+         conn->local_entry->nickname, conn->remote_host);
     } else {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "Your nickname is %s", conn->nickname);
+      SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+         "Your nickname is %s", conn->local_entry->nickname);
     }
 
-    COMMAND;
+    COMMAND(SILC_STATUS_OK);
     goto out;
   }
 
+  /* If JOIN command is active, wait for it to finish before sending NICK.
+     To avoid problems locally with changing IDs while joining, we do this. */
+  silc_mutex_lock(conn->internal->lock);
+  silc_list_start(conn->internal->pending_commands);
+  while ((cmd2 = silc_list_get(conn->internal->pending_commands))) {
+    if (cmd2->cmd == SILC_COMMAND_JOIN) {
+      silc_mutex_unlock(conn->internal->lock);
+      silc_fsm_next_later(fsm, silc_client_command_nick, 0, 300000);
+      return SILC_FSM_WAIT;
+    }
+  }
+  silc_mutex_unlock(conn->internal->lock);
+
   if (cmd->argv_lens[1] > 128)
     cmd->argv_lens[1] = 128;
 
   /* Send the NICK command */
-  buffer = silc_command_payload_encode(SILC_COMMAND_NICK, 1,
-                                      &cmd->argv[1],
-                                      &cmd->argv_lens[1], 
-                                      &cmd->argv_types[1],
-                                      ++cmd->conn->cmd_ident);
-  silc_client_packet_send(cmd->client, cmd->conn->sock,
-                         SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
-                         buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL,
+                             1, 1, cmd->argv[1], cmd->argv_lens[1]);
+
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
 
-  /* Register pending callback that will actually set the new nickname
-     if there were no errors returned by the server. */
-  silc_client_command_pending(conn, SILC_COMMAND_NICK, 
-                             cmd->conn->cmd_ident,
-                             silc_client_command_nick_change,
-                             silc_client_command_dup(cmd));
-  cmd->pending = TRUE;
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_client_command_free(cmd);
+  return SILC_FSM_FINISH;
 }
 
+/********************************** LIST ************************************/
+
 /* Command LIST. Lists channels on the current server. */
 
-SILC_CLIENT_CMD_FUNC(list)
+SILC_FSM_STATE(silc_client_command_list)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcIDCacheEntry id_cache = NULL;
-  SilcChannelEntry channel;
-  SilcBuffer buffer, idp = NULL;
-  char *name;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcClient client = conn->client;
+  SilcChannelEntry channel = NULL;
+  SilcBuffer idp = NULL;
 
   if (cmd->argc == 2) {
-    name = cmd->argv[1];
-
     /* Get the Channel ID of the channel */
-    if (silc_idcache_find_by_name_one(conn->channel_cache, name, &id_cache)) {
-      channel = (SilcChannelEntry)id_cache->context;
-      idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
-    }
+    channel = silc_client_get_channel(conn->client, cmd->conn, cmd->argv[1]);
+    if (channel)
+      idp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
   }
 
   if (!idp)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_LIST, 
-                                           ++conn->cmd_ident, 0);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 0);
   else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_LIST, 
-                                           ++conn->cmd_ident, 1,
-                                           1, idp->data, idp->len);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL,
+                               1, 1, silc_buffer_datalen(idp));
 
-  silc_client_packet_send(cmd->client, cmd->conn->sock,
-                         SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
-                         buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-  if (idp)
-    silc_buffer_free(idp);
+  silc_buffer_free(idp);
+  silc_client_unref_channel(client, conn, channel);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
+/********************************** TOPIC ***********************************/
+
 /* Command TOPIC. Sets/shows topic on a channel. */
 
-SILC_CLIENT_CMD_FUNC(topic)
+SILC_FSM_STATE(silc_client_command_topic)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcIDCacheEntry id_cache = NULL;
+  SilcClient client = conn->client;
   SilcChannelEntry channel;
-  SilcBuffer buffer, idp;
-  char *name;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcBuffer idp;
+  char *name, tmp[512];
 
   if (cmd->argc < 2 || cmd->argc > 3) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO,
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /TOPIC <channel> [<topic>]");
-    COMMAND_ERROR;
+    COMMAND_ERROR((cmd->argc < 2 ? SILC_STATUS_ERR_NOT_ENOUGH_PARAMS :
+                  SILC_STATUS_ERR_TOO_MANY_PARAMS));
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
-    name = conn->current_channel->channel_name;
+
+    if (client->internal->params->full_channel_names)
+      silc_snprintf(tmp, sizeof(tmp), conn->current_channel->channel_name);
+    else
+      silc_snprintf(tmp, sizeof(tmp), "%s%s%s",
+                   conn->current_channel->channel_name,
+                   conn->current_channel->server[0] ? "@" : "",
+                   conn->current_channel->server);
+    name = tmp;
   } else {
     name = cmd->argv[1];
   }
 
   if (!conn->current_channel) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "You are not on that channel");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
     goto out;
   }
 
   /* Get the Channel ID of the channel */
-  if (!silc_idcache_find_by_name_one(conn->channel_cache, name, &id_cache)) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "You are not on that channel");
-    COMMAND_ERROR;
+  channel = silc_client_get_channel(conn->client, conn, name);
+  if (!channel) {
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
     goto out;
   }
 
-  channel = (SilcChannelEntry)id_cache->context;
+  idp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
 
   /* Send TOPIC command to the server */
-  idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
   if (cmd->argc > 2)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_TOPIC, 
-                                           ++conn->cmd_ident, 2, 
-                                           1, idp->data, idp->len,
-                                           2, cmd->argv[2], 
-                                           strlen(cmd->argv[2]));
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 2,
+                               1, silc_buffer_datalen(idp),
+                               2, cmd->argv[2], strlen(cmd->argv[2]));
   else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_TOPIC, 
-                                           ++conn->cmd_ident, 1,
-                                           1, idp->data, idp->len);
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                               1, silc_buffer_datalen(idp));
+
   silc_buffer_free(idp);
+  silc_client_unref_channel(client, conn, channel);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_client_command_free(cmd);
+  return SILC_FSM_FINISH;
 }
 
+/********************************* INVITE ***********************************/
+
 /* Command INVITE. Invites specific client to join a channel. This is
    also used to mange the invite list of the channel. */
 
-SILC_CLIENT_CMD_FUNC(invite)
+SILC_FSM_STATE(silc_client_command_invite)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClient client = cmd->client;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
+  SilcClient client = conn->client;
   SilcClientEntry client_entry = NULL;
-  SilcChannelEntry channel;
-  SilcBuffer buffer, clidp, chidp;
-  SilcUInt32 type = 0;
+  SilcChannelEntry channel = NULL;
+  SilcBuffer clidp, chidp, args = NULL;
+  SilcPublicKey pubkey = NULL;
+  SilcDList clients = NULL;
   char *nickname = NULL, *name;
   char *invite = NULL;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  unsigned char action[1];
 
   if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO,
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /INVITE <channel> [<nickname>[@server>]"
        "[+|-[<nickname>[@<server>[!<username>[@hostname>]]]]]");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
 
     channel = conn->current_channel;
+    silc_client_ref_channel(client, conn, channel);
   } else {
     name = cmd->argv[1];
 
-    channel = silc_client_get_channel(cmd->client, conn, name);
+    channel = silc_client_get_channel(conn->client, conn, name);
     if (!channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are on that channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
   }
@@ -587,387 +1051,383 @@ SILC_CLIENT_CMD_FUNC(invite)
   /* Parse the typed nickname. */
   if (cmd->argc == 3) {
     if (cmd->argv[2][0] != '+' && cmd->argv[2][0] != '-') {
-      if (client->internal->params->nickname_parse)
-       client->internal->params->nickname_parse(cmd->argv[2], &nickname);
-      else
-       nickname = strdup(cmd->argv[2]);
-
-      /* Find client entry */
-      client_entry = silc_idlist_get_client(client, conn, nickname, 
-                                           cmd->argv[2], TRUE);
-      if (!client_entry) {
-       if (cmd->pending) {
-         COMMAND_ERROR;
-         goto out;
-       }
-      
-       /* Client entry not found, it was requested thus mark this to be
-          pending command. */
-       silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
-                                   conn->cmd_ident,
-                                   silc_client_command_invite, 
-                                   silc_client_command_dup(cmd));
-       cmd->pending = 1;
+      if (!silc_client_nickname_parse(client, conn, cmd->argv[2], &nickname)) {
+       silc_client_unref_channel(client, conn, channel);
        goto out;
       }
+
+      /* Find client entry */
+      clients = silc_client_get_clients_local(client, conn, cmd->argv[2],
+                                             FALSE);
+      if (!clients)
+       /* Resolve client information */
+       SILC_FSM_CALL(silc_client_get_clients(
+                                     client, conn, nickname, NULL,
+                                     silc_client_command_resolve_continue,
+                                     cmd));
+
+      client_entry = silc_dlist_get(clients);
     } else {
-      invite = cmd->argv[2];
-      invite++;
       if (cmd->argv[2][0] == '+')
-       type = 3;
+       action[0] = 0x00;
       else
-       type = 4;
+       action[0] = 0x01;
+
+      /* Check if it is public key file to be added to invite list */
+      silc_pkcs_load_public_key(cmd->argv[2] + 1, SILC_PKCS_ANY, &pubkey);
+      invite = cmd->argv[2];
+      if (!pubkey)
+       invite++;
+    }
+  }
+
+  if (invite) {
+    args = silc_buffer_alloc_size(2);
+    silc_buffer_format(args,
+                      SILC_STR_UI_SHORT(1),
+                      SILC_STR_END);
+    if (pubkey) {
+      chidp = silc_public_key_payload_encode(NULL, pubkey);
+      args = silc_argument_payload_encode_one(args, silc_buffer_data(chidp),
+                                             silc_buffer_len(chidp), 2);
+      silc_buffer_free(chidp);
+      silc_pkcs_public_key_free(pubkey);
+    } else {
+      args = silc_argument_payload_encode_one(args, invite, strlen(invite), 1);
     }
   }
 
   /* Send the command */
-  chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+  chidp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
   if (client_entry) {
-    clidp = silc_id_payload_encode(client_entry->id, SILC_ID_CLIENT);
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_INVITE, 
-                                           ++conn->cmd_ident, 3,
-                                           1, chidp->data, chidp->len,
-                                           2, clidp->data, clidp->len,
-                                           type, invite, invite ?
-                                           strlen(invite) : 0);
+    clidp = silc_id_payload_encode(&client_entry->id, SILC_ID_CLIENT);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 4,
+                               1, silc_buffer_datalen(chidp),
+                               2, silc_buffer_datalen(clidp),
+                               3, args ? action : NULL, args ? 1 : 0,
+                               4, silc_buffer_datalen(args));
     silc_buffer_free(clidp);
   } else {
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_INVITE, 
-                                           ++conn->cmd_ident, 2,
-                                           1, chidp->data, chidp->len,
-                                           type, invite, invite ?
-                                           strlen(invite) : 0);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 3,
+                               1, silc_buffer_datalen(chidp),
+                               3, args ? action : NULL, args ? 1 : 0,
+                               4, silc_buffer_datalen(args));
   }
 
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
   silc_buffer_free(chidp);
+  silc_buffer_free(args);
+  silc_free(nickname);
+  silc_client_list_free(client, conn, clients);
+  silc_client_unref_channel(client, conn, channel);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
   silc_free(nickname);
-  silc_client_command_free(cmd);
+  return SILC_FSM_FINISH;
 }
 
-typedef struct {
-  SilcClient client;
-  SilcClientConnection conn;
-} *QuitInternal;
+/********************************** QUIT ************************************/
+
+/* Close the connection */
 
-SILC_TASK_CALLBACK(silc_client_command_quit_cb)
+SILC_FSM_STATE(silc_client_command_quit_final)
 {
-  QuitInternal q = (QuitInternal)context;
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+
+  SILC_LOG_DEBUG(("Quitting"));
 
-  /* Close connection */
-  q->client->internal->ops->disconnect(q->client, q->conn);
-  silc_client_close_connection(q->client, NULL, q->conn->sock->user_data);
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
+
+  /* Signal to close connection */
+  conn->internal->status = SILC_CLIENT_CONN_DISCONNECTED;
+  if (!conn->internal->disconnected) {
+    conn->internal->disconnected = TRUE;
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+  }
 
-  silc_free(q);
+  return SILC_FSM_FINISH;
 }
 
 /* Command QUIT. Closes connection with current server. */
-SILC_CLIENT_CMD_FUNC(quit)
-{
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcBuffer buffer;
-  QuitInternal q;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+SILC_FSM_STATE(silc_client_command_quit)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
 
   if (cmd->argc > 1)
-    buffer = silc_command_payload_encode(SILC_COMMAND_QUIT, cmd->argc - 1, 
-                                        &cmd->argv[1], &cmd->argv_lens[1],
-                                        &cmd->argv_types[1], 0);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                               1, cmd->argv[1], cmd->argv_lens[1]);
   else
-    buffer = silc_command_payload_encode(SILC_COMMAND_QUIT, 0,
-                                        NULL, NULL, NULL, 0);
-  silc_client_packet_send(cmd->client, cmd->conn->sock, SILC_PACKET_COMMAND, 
-                         NULL, 0, NULL, NULL, 
-                         buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-
-  q = silc_calloc(1, sizeof(*q));
-  q->client = cmd->client;
-  q->conn = cmd->conn;
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 0);
 
   /* Sleep for a while */
-  sleep(2);
+  sleep(1);
 
-  /* We quit the connection with little timeout */
-  silc_schedule_task_add(cmd->client->schedule, cmd->conn->sock->sock,
-                        silc_client_command_quit_cb, (void *)q,
-                        1, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+  /* We close the connection with a little timeout */
+  silc_fsm_next_later(fsm, silc_client_command_quit_final, 2, 0);
+  return SILC_FSM_WAIT;
+}
 
-  /* Notify application */
-  COMMAND;
+/********************************** KILL ************************************/
 
- out:
-  silc_client_command_free(cmd);
+/* Signature callback */
+
+static void silc_client_command_kill_signed(const SilcBuffer buffer,
+                                           void *context)
+{
+  SilcClientCommandContext cmd = context;
+
+  if (!buffer) {
+    silc_fsm_finish(&cmd->thread);
+    return;
+  }
+
+  silc_fsm_set_state_context(&cmd->thread, buffer);
+  SILC_FSM_CALL_CONTINUE_SYNC(&cmd->thread);
 }
 
-/* Timeout callback to remove the killed client from cache */
+/* Send KILL command */
 
-SILC_TASK_CALLBACK(silc_client_command_kill_remove_later)
+SILC_FSM_STATE(silc_client_command_kill_send)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClient client = cmd->client;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcClientEntry target;
-  char *nickname = NULL;
-  
-  /* Parse the typed nickname. */
-  if (client->internal->params->nickname_parse)
-    client->internal->params->nickname_parse(cmd->argv[1], &nickname);
-  else
-    nickname = strdup(cmd->argv[1]);
+  SilcClient client = conn->client;
+  SilcBuffer idp, auth = state_context;
+  SilcClientEntry target = cmd->context;
+  char *comment = NULL;
 
-  /* Get the target client */
-  target = silc_idlist_get_client(cmd->client, conn, nickname, 
-                                 cmd->argv[1], FALSE);
-  if (target)
-    /* Remove the client from all channels and free it */
-    silc_client_del_client(client, conn, target);
+  if (cmd->argc >= 3)
+    if (strcasecmp(cmd->argv[2], "-pubkey"))
+      comment = cmd->argv[2];
 
-  silc_free(nickname);
-  silc_client_command_free(cmd);
-}
+  /* Send the KILL command to the server */
+  idp = silc_id_payload_encode(&target->id, SILC_ID_CLIENT);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 3,
+                             1, silc_buffer_datalen(idp),
+                             2, comment, comment ? strlen(comment) : 0,
+                             3, silc_buffer_datalen(auth));
 
-/* Kill command's pending command callback to actually remove the killed
-   client from our local cache. */
+  silc_buffer_free(idp);
+  silc_client_unref_client(client, conn, target);
 
-SILC_CLIENT_CMD_FUNC(kill_remove)
-{
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClientCommandReplyContext reply = 
-    (SilcClientCommandReplyContext)context2;
-  SilcCommandStatus status = silc_command_get_status(reply->payload);
-
-  if (status == SILC_STATUS_OK) {
-    /* Remove with timeout */
-    silc_schedule_task_add(cmd->client->schedule, cmd->conn->sock->sock,
-                          silc_client_command_kill_remove_later, context,
-                          1, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-    return;
-  }
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
 
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
 /* Command KILL. Router operator can use this command to remove an client
    fromthe SILC Network. */
 
-SILC_CLIENT_CMD_FUNC(kill)
+SILC_FSM_STATE(silc_client_command_kill)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClient client = cmd->client;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer, idp;
+  SilcClient client = conn->client;
   SilcClientEntry target;
+  SilcDList clients;
   char *nickname = NULL;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
   if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "Usage: /KILL <nickname> [<comment>]");
-    COMMAND_ERROR;
-    goto out;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /KILL <nickname> [<comment>] [-pubkey]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
   /* Parse the typed nickname. */
-  if (client->internal->params->nickname_parse)
-    client->internal->params->nickname_parse(cmd->argv[1], &nickname);
-  else
-    nickname = strdup(cmd->argv[1]);
+  if (!silc_client_nickname_parse(client, conn, cmd->argv[1], &nickname))
+    return SILC_FSM_FINISH;
 
   /* Get the target client */
-  target = silc_idlist_get_client(cmd->client, conn, nickname, 
-                                 cmd->argv[1], TRUE);
-  if (!target) {
-    if (cmd->pending) {
-      COMMAND_ERROR;
-      goto out;
-    }
+  clients = silc_client_get_clients_local(client, conn, cmd->argv[1], FALSE);
+  if (!clients)
+    /* Resolve client information */
+    SILC_FSM_CALL(silc_client_get_clients(client, conn, nickname, NULL,
+                                         silc_client_command_resolve_continue,
+                                         cmd));
 
-    /* Client entry not found, it was requested thus mark this to be
-       pending command. */
-    silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
-                               conn->cmd_ident,  
-                               silc_client_command_kill, 
-                               silc_client_command_dup(cmd));
-    cmd->pending = 1;
-    goto out;
+  target = silc_dlist_get(clients);
+  cmd->context = silc_client_ref_client(client, conn, target);
+
+  silc_free(nickname);
+  silc_client_list_free(client, conn, clients);
+
+  /** Send KILL */
+  silc_fsm_next(fsm, silc_client_command_kill_send);
+
+  if (cmd->argc >= 3) {
+    if (!strcasecmp(cmd->argv[2], "-pubkey") ||
+       (cmd->argc >= 4 && !strcasecmp(cmd->argv[3], "-pubkey"))) {
+      /* Encode the public key authentication payload */
+      SILC_FSM_CALL(silc_auth_public_key_auth_generate(
+                                        conn->public_key,
+                                        conn->private_key,
+                                        conn->client->rng,
+                                        conn->internal->sha1hash,
+                                        &target->id, SILC_ID_CLIENT,
+                                        silc_client_command_kill_signed,
+                                        cmd));
+      /* NOT REACHED */
+    }
   }
 
-  /* Send the KILL command to the server */
-  idp = silc_id_payload_encode(target->id, SILC_ID_CLIENT);
+  return SILC_FSM_CONTINUE;
+}
+
+/********************************** INFO ************************************/
+
+/* Command INFO. Request information about specific server. If specific
+   server is not provided the current server is used. */
+
+SILC_FSM_STATE(silc_client_command_info)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+
+  /* Send the command */
   if (cmd->argc == 2)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_KILL, 
-                                           ++conn->cmd_ident, 1, 
-                                           1, idp->data, idp->len);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                               1, cmd->argv[1], cmd->argv_lens[1]);
   else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_KILL, 
-                                           ++conn->cmd_ident, 2, 
-                                           1, idp->data, idp->len,
-                                           2, cmd->argv[2], 
-                                           strlen(cmd->argv[2]));
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-  silc_buffer_free(idp);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 0);
 
   /* Notify application */
-  COMMAND;
-
-  /* Register a pending callback that will actually remove the killed
-     client from our cache. */
-  silc_client_command_pending(conn, SILC_COMMAND_KILL, conn->cmd_ident,
-                             silc_client_command_kill_remove,
-                             silc_client_command_dup(cmd));
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_free(nickname);
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
-/* Command INFO. Request information about specific server. If specific
-   server is not provided the current server is used. */
+/********************************** STATS ***********************************/
+
+/* Command STATS. Shows server and network statistics. */
 
-SILC_CLIENT_CMD_FUNC(info)
+SILC_FSM_STATE(silc_client_command_stats)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-  char *name = NULL;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
-  if (cmd->argc == 2)
-    name = strdup(cmd->argv[1]);
 
   /* Send the command */
-  if (name)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_INFO, 0, 1, 
-                                           1, name, strlen(name));
-  else
-    buffer = silc_command_payload_encode(SILC_COMMAND_INFO, 0,
-                                        NULL, NULL, NULL, 0);
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-  if (name)
-    silc_free(name);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                             1, silc_buffer_datalen(conn->internal->
+                                                    remote_idp));
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
-/* Command PING. Sends ping to server. This is used to test the 
-   communication channel. */
+/********************************** PING ************************************/
 
-SILC_CLIENT_CMD_FUNC(ping)
+/* Command PING. Sends ping to server. */
+
+SILC_FSM_STATE(silc_client_command_ping)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-  void *id;
-  int i;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
+  if (cmd->argc < 2) {
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
   /* Send the command */
-  buffer = silc_command_payload_encode_va(SILC_COMMAND_PING, 0, 1, 
-                                         1, conn->remote_id_data, 
-                                         silc_id_get_len(conn->remote_id,
-                                                         SILC_ID_SERVER));
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                             1, silc_buffer_datalen(conn->internal->
+                                                    remote_idp));
 
-  id = silc_id_str2id(conn->remote_id_data, conn->remote_id_data_len,
-                     SILC_ID_SERVER);
-  if (!id) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  /* Save ping time */
+  cmd->context = SILC_64_TO_PTR(silc_time());
 
-  /* Start counting time */
-  for (i = 0; i < conn->ping_count; i++) {
-    if (conn->ping[i].dest_id == NULL) {
-      conn->ping[i].start_time = time(NULL);
-      conn->ping[i].dest_id = id;
-      conn->ping[i].dest_name = strdup(conn->remote_host);
-      break;
-    }
-  }
-  if (i >= conn->ping_count) {
-    i = conn->ping_count;
-    conn->ping = silc_realloc(conn->ping, sizeof(*conn->ping) * (i + 1));
-    conn->ping[i].start_time = time(NULL);
-    conn->ping[i].dest_id = id;
-    conn->ping[i].dest_name = strdup(conn->remote_host);
-    conn->ping_count++;
-  }
-  
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
-/* Command JOIN. Joins to a channel. */
+/********************************** JOIN ************************************/
+
+typedef struct {
+  int type;
+  SilcBuffer auth;
+  SilcBuffer cauth;
+} *SilcClientJoinContext;
+
+/* Signature callback */
 
-SILC_CLIENT_CMD_FUNC(join)
+static void silc_client_command_join_signed(const SilcBuffer buffer,
+                                           void *context)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClientConnection conn = cmd->conn;
-  SilcChannelEntry channel;
-  SilcBuffer buffer, idp, auth = NULL;
-  char *name, *passphrase = NULL, *cipher = NULL, *hmac = NULL;
-  int i;
+  SilcClientCommandContext cmd = context;
+  SilcClientJoinContext j = cmd->context;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
+  if (!buffer) {
+    silc_fsm_finish(&cmd->thread);
+    return;
   }
 
+  if (!j->type)
+    j->auth = silc_buffer_copy(buffer);
+  else
+    j->cauth = silc_buffer_copy(buffer);
+
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
+}
+
+/* Command JOIN. Joins to a channel. */
+
+SILC_FSM_STATE(silc_client_command_join)
+{
+  SilcClientCommandContext cmd2, cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcClient client = conn->client;
+  SilcChannelEntry channel = NULL;
+  SilcClientJoinContext j = cmd->context;
+  SilcBuffer auth = NULL, cauth = NULL;
+  char *name, *passphrase = NULL, *pu8, *cipher = NULL, *hmac = NULL;
+  int i, passphrase_len = 0;
+
   if (cmd->argc < 2) {
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
-  
+
   /* See if we have joined to the requested channel already */
-  channel = silc_client_get_channel(cmd->client, conn, cmd->argv[1]);
+  channel = silc_client_get_channel(conn->client, conn, cmd->argv[1]);
   if (channel && silc_client_on_channel(channel, conn->local_entry))
     goto out;
 
-  idp = silc_id_payload_encode(conn->local_id, SILC_ID_CLIENT);
+  /* If NICK command is active, wait for it to finish before sending JOIN.
+     To avoid problems locally with changing IDs while joining, we do this. */
+  silc_mutex_lock(conn->internal->lock);
+  silc_list_start(conn->internal->pending_commands);
+  while ((cmd2 = silc_list_get(conn->internal->pending_commands))) {
+    if (cmd2->cmd == SILC_COMMAND_NICK) {
+      silc_mutex_unlock(conn->internal->lock);
+      silc_fsm_next_later(fsm, silc_client_command_join, 0, 300000);
+      return SILC_FSM_WAIT;
+    }
+  }
+  silc_mutex_unlock(conn->internal->lock);
 
   if (cmd->argv_lens[1] > 256)
     cmd->argv_lens[1] = 256;
@@ -976,118 +1436,182 @@ SILC_CLIENT_CMD_FUNC(join)
 
   for (i = 2; i < cmd->argc; i++) {
     if (!strcasecmp(cmd->argv[i], "-cipher") && cmd->argc > i + 1) {
-      cipher = cmd->argv[i + 1];
-      i++;
+      cipher = cmd->argv[++i];
     } else if (!strcasecmp(cmd->argv[i], "-hmac") && cmd->argc > i + 1) {
-      hmac = cmd->argv[i + 1];
-      i++;
-    } else if (!strcasecmp(cmd->argv[i], "-founder") && cmd->argc > i + 1) {
-      if (!strcasecmp(cmd->argv[i + 1], "-pubkey")) {
-       auth = silc_auth_public_key_auth_generate(cmd->client->public_key,
-                                                 cmd->client->private_key,
-                                                 cmd->client->rng, conn->hash,
-                                                 conn->local_id,
-                                                 SILC_ID_CLIENT);
-      } else {
-       auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0,
-                                       cmd->argv[i + 1], 
-                                       cmd->argv_lens[i + 1]);
+      hmac = cmd->argv[++i];
+    } else if (!strcasecmp(cmd->argv[i], "-founder")) {
+      if (!j || !j->auth) {
+       if (!j) {
+         j = silc_calloc(1, sizeof(*j));
+         if (!j)
+           goto out;
+         cmd->context = j;
+       }
+       j->type = 0;
+       silc_free(passphrase);
+       silc_client_unref_channel(client, conn, channel);
+       SILC_FSM_CALL(silc_auth_public_key_auth_generate(
+                                          conn->public_key,
+                                          conn->private_key,
+                                          conn->client->rng,
+                                          conn->internal->sha1hash,
+                                          conn->local_id,
+                                          SILC_ID_CLIENT,
+                                          silc_client_command_join_signed,
+                                          cmd));
+       /* NOT REACHED */
+      }
+    } else if (!strcasecmp(cmd->argv[i], "-auth")) {
+      SilcPublicKey pubkey = conn->public_key;
+      SilcPrivateKey privkey = conn->private_key;
+      unsigned char *pk, pkhash[SILC_HASH_MAXLEN], pubdata[128];
+      SilcUInt32 pk_len;
+
+      if (!j || !j->cauth) {
+       if (!j) {
+         j = silc_calloc(1, sizeof(*j));
+         if (!j)
+           goto out;
+         cmd->context = j;
+       }
+
+       if (cmd->argc >= i + 3) {
+         char *pass = "";
+         if (cmd->argc >= i + 4) {
+           pass = cmd->argv[i + 3];
+           i++;
+         }
+         if (!silc_load_key_pair(cmd->argv[i + 1], cmd->argv[i + 2], pass,
+                                 &pubkey, &privkey)) {
+           SAY(conn->client, conn, SILC_CLIENT_MESSAGE_COMMAND_ERROR,
+               "Could not load key pair, check your arguments");
+           COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+           goto out;
+         }
+         i += 2;
+       }
+
+       j->type = 1;
+       pk = silc_pkcs_public_key_encode(NULL, pubkey, &pk_len);
+       silc_hash_make(conn->internal->sha1hash, pk, pk_len, pkhash);
+       silc_free(pk);
+       silc_rng_get_rn_data(conn->client->rng, sizeof(pubdata), pubdata,
+                            sizeof(pubdata));
+       memcpy(pubdata, pkhash, 20);
+       silc_free(passphrase);
+       silc_client_unref_channel(client, conn, channel);
+       SILC_FSM_CALL(silc_auth_public_key_auth_generate_wpub(
+                                          pubkey, privkey,
+                                          pubdata, sizeof(pubdata),
+                                          conn->internal->sha1hash,
+                                          client->rng,
+                                          conn->local_id,
+                                          SILC_ID_CLIENT,
+                                          silc_client_command_join_signed,
+                                          cmd));
+       /* NOT REACHED */
       }
-      i++;
     } else {
-      passphrase = cmd->argv[i];
+      /* Passphrases must be UTF-8 encoded, so encode if it is not */
+      if (!silc_utf8_valid(cmd->argv[i], cmd->argv_lens[i])) {
+       passphrase_len = silc_utf8_encoded_len(cmd->argv[i],
+                                              cmd->argv_lens[i], 0);
+       pu8 = silc_calloc(passphrase_len, sizeof(*pu8));
+       passphrase_len = silc_utf8_encode(cmd->argv[i], cmd->argv_lens[i],
+                                         0, pu8, passphrase_len);
+       passphrase = pu8;
+      } else {
+       passphrase = strdup(cmd->argv[i]);
+       passphrase_len = cmd->argv_lens[i];
+      }
     }
   }
 
   /* Send JOIN command to the server */
-  buffer =
-    silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 6,
-                                  1, name, strlen(name),
-                                  2, idp->data, idp->len,
-                                  3, passphrase, 
-                                  passphrase ? strlen(passphrase) : 0,
-                                  4, cipher, cipher ? strlen(cipher) : 0,
-                                  5, hmac, hmac ? strlen(hmac) : 0,
-                                  6, auth ? auth->data : NULL,
-                                  auth ? auth->len : 0);
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-  silc_buffer_free(idp);
-  if (auth)
-    silc_buffer_free(auth);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 7,
+                             1, name, strlen(name),
+                             2, silc_buffer_datalen(conn->internal->
+                                                    local_idp),
+                             3, passphrase, passphrase_len,
+                             4, cipher, cipher ? strlen(cipher) : 0,
+                             5, hmac, hmac ? strlen(hmac) : 0,
+                             6, silc_buffer_datalen(auth),
+                             7, silc_buffer_datalen(cauth));
+
+  if (passphrase)
+    memset(passphrase, 0, strlen(passphrase));
+  silc_free(passphrase);
+  silc_client_unref_channel(client, conn, channel);
+  if (j) {
+    silc_buffer_free(j->auth);
+    silc_buffer_free(j->cauth);
+    silc_free(j);
+  }
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_client_command_free(cmd);
+  silc_client_unref_channel(client, conn, channel);
+  return SILC_FSM_FINISH;
 }
 
+/********************************** MOTD ************************************/
+
 /* MOTD command. Requests motd from server. */
 
-SILC_CLIENT_CMD_FUNC(motd)
+SILC_FSM_STATE(silc_client_command_motd)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
 
   if (cmd->argc < 1 || cmd->argc > 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO,
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /MOTD [<server>]");
-    COMMAND_ERROR;
-    goto out;
+    COMMAND_ERROR((cmd->argc < 1 ? SILC_STATUS_ERR_NOT_ENOUGH_PARAMS :
+                  SILC_STATUS_ERR_TOO_MANY_PARAMS));
+    return SILC_FSM_FINISH;
   }
 
-  /* Send TOPIC command to the server */
+  /* Send the command */
   if (cmd->argc == 1)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_MOTD, 0, 1, 
-                                           1, conn->remote_host, 
-                                           strlen(conn->remote_host));
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                               1, conn->remote_host,
+                               strlen(conn->remote_host));
   else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_MOTD, 0, 1, 
-                                           1, cmd->argv[1], 
-                                           cmd->argv_lens[1]);
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                               1, cmd->argv[1], cmd->argv_lens[1]);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
+/********************************** UMODE ***********************************/
+
 /* UMODE. Set/unset user mode in SILC. This is used mainly to unset the
    modes as client cannot set itself server/router operator privileges. */
 
-SILC_CLIENT_CMD_FUNC(umode)
+SILC_FSM_STATE(silc_client_command_umode)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer, idp;
   unsigned char *cp, modebuf[4];
   SilcUInt32 mode, add, len;
   int i;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
   if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /UMODE +|-<modes>");
-    COMMAND_ERROR;
-    goto out;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
   mode = conn->local_entry->mode;
@@ -1108,6 +1632,14 @@ SILC_CLIENT_CMD_FUNC(umode)
        mode = 0;
        mode |= SILC_UMODE_SERVER_OPERATOR;
        mode |= SILC_UMODE_ROUTER_OPERATOR;
+       mode |= SILC_UMODE_GONE;
+       mode |= SILC_UMODE_INDISPOSED;
+       mode |= SILC_UMODE_BUSY;
+       mode |= SILC_UMODE_PAGE;
+       mode |= SILC_UMODE_HYPER;
+       mode |= SILC_UMODE_ROBOT;
+       mode |= SILC_UMODE_BLOCK_PRIVMSG;
+       mode |= SILC_UMODE_REJECT_WATCHING;
       } else {
        mode = SILC_UMODE_NONE;
       }
@@ -1130,78 +1662,132 @@ SILC_CLIENT_CMD_FUNC(umode)
       else
        mode &= ~SILC_UMODE_GONE;
       break;
+    case 'i':
+      if (add)
+       mode |= SILC_UMODE_INDISPOSED;
+      else
+       mode &= ~SILC_UMODE_INDISPOSED;
+      break;
+    case 'b':
+      if (add)
+       mode |= SILC_UMODE_BUSY;
+      else
+       mode &= ~SILC_UMODE_BUSY;
+      break;
+    case 'p':
+      if (add)
+       mode |= SILC_UMODE_PAGE;
+      else
+       mode &= ~SILC_UMODE_PAGE;
+      break;
+    case 'h':
+      if (add)
+       mode |= SILC_UMODE_HYPER;
+      else
+       mode &= ~SILC_UMODE_HYPER;
+      break;
+    case 't':
+      if (add)
+       mode |= SILC_UMODE_ROBOT;
+      else
+       mode &= ~SILC_UMODE_ROBOT;
+      break;
+    case 'P':
+      if (add)
+       mode |= SILC_UMODE_BLOCK_PRIVMSG;
+      else
+       mode &= ~SILC_UMODE_BLOCK_PRIVMSG;
+      break;
+    case 'w':
+      if (add)
+       mode |= SILC_UMODE_REJECT_WATCHING;
+      else
+       mode &= ~SILC_UMODE_REJECT_WATCHING;
+      break;
+    case 'I':
+      if (add)
+       mode |= SILC_UMODE_BLOCK_INVITE;
+      else
+       mode &= ~SILC_UMODE_BLOCK_INVITE;
+      break;
     default:
-      COMMAND_ERROR;
-      goto out;
+      COMMAND_ERROR(SILC_STATUS_ERR_UNKNOWN_MODE);
+      return SILC_FSM_FINISH;
       break;
     }
   }
 
-  idp = silc_id_payload_encode(conn->local_id, SILC_ID_CLIENT);
   SILC_PUT32_MSB(mode, modebuf);
 
-  /* Send the command packet. We support sending only one mode at once
-     that requires an argument. */
-  buffer = 
-    silc_command_payload_encode_va(SILC_COMMAND_UMODE, ++conn->cmd_ident, 2, 
-                                  1, idp->data, idp->len, 
-                                  2, modebuf, sizeof(modebuf));
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-  silc_buffer_free(idp);
+  /* Send the command */
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 2,
+                             1, silc_buffer_datalen(conn->internal->
+                                                    local_idp),
+                             2, modebuf, sizeof(modebuf));
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
+}
+
+/********************************** CMODE ***********************************/
+
+/* Signature callback */
+
+static void silc_client_command_cmode_signed(const SilcBuffer buffer,
+                                            void *context)
+{
+  SilcClientCommandContext cmd = context;
+
+  if (!buffer) {
+    silc_fsm_finish(&cmd->thread);
+    return;
+  }
+
+  silc_fsm_set_state_context(&cmd->thread, buffer);
+  SILC_FSM_CALL_CONTINUE_SYNC(&cmd->thread);
 }
 
 /* CMODE command. Sets channel mode. Modes that does not require any arguments
    can be set several at once. Those modes that require argument must be set
    separately (unless set with modes that does not require arguments). */
 
-SILC_CLIENT_CMD_FUNC(cmode)
+SILC_FSM_STATE(silc_client_command_cmode)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcChannelEntry channel;
-  SilcBuffer buffer, chidp, auth = NULL;
+  SilcClient client = conn->client;
+  SilcBuffer auth = state_context;
+  SilcChannelEntry channel = NULL;
+  SilcBuffer chidp, pk = NULL;
   unsigned char *name, *cp, modebuf[4], tmp[4], *arg = NULL;
   SilcUInt32 mode, add, type, len, arg_len = 0;
   int i;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
   if (cmd->argc < 3) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
 
     channel = conn->current_channel;
+    silc_client_ref_channel(client, conn, channel);
   } else {
     name = cmd->argv[1];
 
-    channel = silc_client_get_channel(cmd->client, conn, name);
+    channel = silc_client_get_channel(conn->client, conn, name);
     if (!channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are on that channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
   }
@@ -1270,9 +1856,9 @@ SILC_CLIENT_CMD_FUNC(cmode)
        mode |= SILC_CHANNEL_MODE_ULIMIT;
        type = 3;
        if (cmd->argc < 4) {
-         SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+         SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
              "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
-         COMMAND_ERROR;
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
          goto out;
        }
        ll = atoi(cmd->argv[3]);
@@ -1288,9 +1874,9 @@ SILC_CLIENT_CMD_FUNC(cmode)
        mode |= SILC_CHANNEL_MODE_PASSPHRASE;
        type = 4;
        if (cmd->argc < 4) {
-         SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+         SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
              "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
-         COMMAND_ERROR;
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
          goto out;
        }
        arg = cmd->argv[3];
@@ -1304,9 +1890,9 @@ SILC_CLIENT_CMD_FUNC(cmode)
        mode |= SILC_CHANNEL_MODE_CIPHER;
        type = 5;
        if (cmd->argc < 4) {
-         SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+         SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
              "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
-         COMMAND_ERROR;
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
          goto out;
        }
        arg = cmd->argv[3];
@@ -1320,9 +1906,9 @@ SILC_CLIENT_CMD_FUNC(cmode)
        mode |= SILC_CHANNEL_MODE_HMAC;
        type = 6;
        if (cmd->argc < 4) {
-         SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+         SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
              "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
-         COMMAND_ERROR;
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
          goto out;
        }
        arg = cmd->argv[3];
@@ -1333,148 +1919,223 @@ SILC_CLIENT_CMD_FUNC(cmode)
       break;
     case 'f':
       if (add) {
-       mode |= SILC_CHANNEL_MODE_FOUNDER_AUTH;
-       type = 7;
+       if (!auth) {
+         SilcPublicKey pubkey = conn->public_key;
+         SilcPrivateKey privkey = conn->private_key;
+
+         mode |= SILC_CHANNEL_MODE_FOUNDER_AUTH;
+         type = 7;
+
+         if (cmd->argc >= 5) {
+           char *pass = "";
+           if (cmd->argc >= 6)
+             pass = cmd->argv[5];
+           if (!silc_load_key_pair(cmd->argv[3], cmd->argv[4], pass,
+                                   &pubkey, &privkey)) {
+             SAY(client, conn, SILC_CLIENT_MESSAGE_COMMAND_ERROR,
+                 "Could not load key pair, check your arguments");
+             COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+             goto out;
+           }
+         }
 
-       if (cmd->argc < 4) {
-         SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-             "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
-         COMMAND_ERROR;
-         goto out;
+         pk = silc_public_key_payload_encode(NULL, pubkey);
+         silc_client_unref_channel(client, conn, channel);
+         SILC_FSM_CALL(silc_auth_public_key_auth_generate(
+                                            pubkey, privkey,
+                                            conn->client->rng,
+                                            conn->internal->sha1hash,
+                                            conn->local_id,
+                                            SILC_ID_CLIENT,
+                                            silc_client_command_cmode_signed,
+                                            cmd));
+         /* NOT REACHED */
+       }
+       arg = silc_buffer_data(auth);
+       arg_len = silc_buffer_len(auth);
+      } else {
+       mode &= ~SILC_CHANNEL_MODE_FOUNDER_AUTH;
+      }
+      break;
+    case 'C':
+      if (add) {
+       int k;
+       SilcBool chadd = FALSE;
+       SilcPublicKey chpk = NULL;
+
+       mode |= SILC_CHANNEL_MODE_CHANNEL_AUTH;
+       type = 9;
+
+       if (cmd->argc == 3) {
+         /* Send empty command to receive the public key list. */
+         chidp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
+         silc_client_command_send_va(conn, cmd, SILC_COMMAND_CMODE,
+                                     NULL, NULL, 1,
+                                     1, silc_buffer_datalen(chidp));
+         silc_buffer_free(chidp);
+         silc_client_unref_channel(client, conn, channel);
+
+         /* Notify application */
+         COMMAND(SILC_STATUS_OK);
+
+         /** Wait for command reply */
+         silc_fsm_next(fsm, silc_client_command_reply_wait);
+         return SILC_FSM_CONTINUE;
        }
 
-       if (!strcasecmp(cmd->argv[3], "-pubkey")) {
-         auth = silc_auth_public_key_auth_generate(cmd->client->public_key,
-                                                   cmd->client->private_key,
-                                                   cmd->client->rng, 
-                                                   conn->hash,
-                                                   conn->local_id,
-                                                   SILC_ID_CLIENT);
-       } else {
-         auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0,
-                                         cmd->argv[3], cmd->argv_lens[3]);
+       if (cmd->argc >= 4) {
+         auth = silc_buffer_alloc_size(2);
+         silc_buffer_format(auth,
+                            SILC_STR_UI_SHORT(cmd->argc - 3),
+                            SILC_STR_END);
+       }
+
+       for (k = 3; k < cmd->argc; k++) {
+         if (cmd->argv[k][0] == '+')
+           chadd = TRUE;
+         if (!silc_pkcs_load_public_key(cmd->argv[k] + 1, SILC_PKCS_ANY,
+                                        &chpk)) {
+           SAY(conn->client, conn, SILC_CLIENT_MESSAGE_COMMAND_ERROR,
+               "Could not load public key %s, check the filename",
+               cmd->argv[k]);
+           COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+           silc_buffer_free(auth);
+           goto out;
+         }
+
+         if (chpk) {
+           pk = silc_public_key_payload_encode(NULL, chpk);
+           auth = silc_argument_payload_encode_one(auth,
+                                                   silc_buffer_datalen(pk),
+                                                   chadd ? 0x00 : 0x01);
+           silc_pkcs_public_key_free(chpk);
+           silc_buffer_free(pk);
+           pk = NULL;
+         }
        }
 
-       arg = auth->data;
-       arg_len = auth->len;
+       arg = silc_buffer_data(auth);
+       arg_len = silc_buffer_len(auth);
       } else {
-       mode &= ~SILC_CHANNEL_MODE_FOUNDER_AUTH;
+       mode &= ~SILC_CHANNEL_MODE_CHANNEL_AUTH;
       }
       break;
     default:
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_UNKNOWN_MODE);
       goto out;
       break;
     }
   }
 
-  chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+  chidp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
   SILC_PUT32_MSB(mode, modebuf);
 
-  /* Send the command packet. We support sending only one mode at once
-     that requires an argument. */
+  /* Send the command. We support sending only one mode at once that
+     requires an argument. */
   if (type && arg) {
-    buffer = 
-      silc_command_payload_encode_va(SILC_COMMAND_CMODE, 0, 3, 
-                                    1, chidp->data, chidp->len, 
-                                    2, modebuf, sizeof(modebuf),
-                                    type, arg, arg_len);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 4,
+                               1, silc_buffer_datalen(chidp),
+                               2, modebuf, sizeof(modebuf),
+                               type, arg, arg_len,
+                               8, silc_buffer_datalen(pk));
   } else {
-    buffer = 
-      silc_command_payload_encode_va(SILC_COMMAND_CMODE, 0, 2, 
-                                    1, chidp->data, chidp->len, 
-                                    2, modebuf, sizeof(modebuf));
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 2,
+                               1, silc_buffer_datalen(chidp),
+                               2, modebuf, sizeof(modebuf));
   }
 
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
   silc_buffer_free(chidp);
-  if (auth)
-    silc_buffer_free(auth);
+  silc_buffer_free(auth);
+  silc_buffer_free(pk);
+  silc_client_unref_channel(client, conn, channel);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_client_command_free(cmd);
+  silc_client_unref_channel(client, conn, channel);
+  return SILC_FSM_FINISH;
+}
+
+/********************************* CUMODE ***********************************/
+
+/* Signature callback */
+
+static void silc_client_command_cumode_signed(const SilcBuffer buffer,
+                                             void *context)
+{
+  SilcClientCommandContext cmd = context;
+
+  if (!buffer) {
+    silc_fsm_finish(&cmd->thread);
+    return;
+  }
+
+  silc_fsm_set_state_context(&cmd->thread, buffer);
+  SILC_FSM_CALL_CONTINUE_SYNC(&cmd->thread);
 }
 
 /* CUMODE command. Changes client's mode on a channel. */
 
-SILC_CLIENT_CMD_FUNC(cumode)
+SILC_FSM_STATE(silc_client_command_cumode)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClient client = cmd->client;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcChannelEntry channel;
+  SilcClient client = conn->client;
+  SilcBuffer auth = state_context;
+  SilcChannelEntry channel = NULL;
   SilcChannelUser chu;
   SilcClientEntry client_entry;
-  SilcBuffer buffer, clidp, chidp, auth = NULL;
+  SilcBuffer clidp, chidp;
+  SilcDList clients = NULL;
   unsigned char *name, *cp, modebuf[4];
   SilcUInt32 mode = 0, add, len;
   char *nickname = NULL;
   int i;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
   if (cmd->argc < 4) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /CUMODE <channel> +|-<modes> <nickname>[@<server>]");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
 
     channel = conn->current_channel;
+    silc_client_ref_channel(client, conn, channel);
   } else {
     name = cmd->argv[1];
 
-    channel = silc_client_get_channel(cmd->client, conn, name);
+    channel = silc_client_get_channel(conn->client, conn, name);
     if (!channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are on that channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
   }
 
   /* Parse the typed nickname. */
-  if (client->internal->params->nickname_parse)
-    client->internal->params->nickname_parse(cmd->argv[3], &nickname);
-  else
-    nickname = strdup(cmd->argv[3]);
+  if (!silc_client_nickname_parse(client, conn, cmd->argv[3], &nickname))
+    goto out;
 
   /* Find client entry */
-  client_entry = silc_idlist_get_client(cmd->client, conn, nickname,
-                                       cmd->argv[3], TRUE);
-  if (!client_entry) {
-    if (cmd->pending) {
-      COMMAND_ERROR;
-      goto out;
-    }
+  clients = silc_client_get_clients_local(client, conn, cmd->argv[3], FALSE);
+  if (!clients)
+    /* Resolve client information */
+    SILC_FSM_CALL(silc_client_get_clients(client, conn, nickname, NULL,
+                                         silc_client_command_resolve_continue,
+                                         cmd));
+
+  client_entry = silc_dlist_get(clients);
 
-    /* Client entry not found, it was requested thus mark this to be
-       pending command. */
-    silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
-                               conn->cmd_ident,  
-                               silc_client_command_cumode, 
-                               silc_client_command_dup(cmd));
-    cmd->pending = 1;
-    goto out;
-  }
-  
   /* Get the current mode */
   chu = silc_client_on_channel(channel, client_entry);
   if (chu)
@@ -1495,25 +2156,47 @@ SILC_CLIENT_CMD_FUNC(cumode)
       if (add) {
        mode |= SILC_CHANNEL_UMODE_CHANFO;
        mode |= SILC_CHANNEL_UMODE_CHANOP;
+       mode |= SILC_CHANNEL_UMODE_BLOCK_MESSAGES;
+       mode |= SILC_CHANNEL_UMODE_BLOCK_MESSAGES_USERS;
+       mode |= SILC_CHANNEL_UMODE_BLOCK_MESSAGES_ROBOTS;
       } else {
        mode = SILC_CHANNEL_UMODE_NONE;
       }
       break;
     case 'f':
       if (add) {
-       if (cmd->argc == 5) {
-         if (!strcasecmp(cmd->argv[4], "-pubkey")) {
-           auth = silc_auth_public_key_auth_generate(cmd->client->public_key,
-                                                     cmd->client->private_key,
-                                                     cmd->client->rng,
-                                                     conn->hash,
-                                                     conn->local_id,
-                                                     SILC_ID_CLIENT);
-         } else {
-           auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0,
-                                           cmd->argv[4], cmd->argv_lens[4]);
+       if (!auth) {
+         SilcPublicKey pubkey = conn->public_key;
+         SilcPrivateKey privkey = conn->private_key;
+
+         if (cmd->argc >= 6) {
+           char *pass = "";
+           if (cmd->argc >= 7)
+             pass = cmd->argv[6];
+           if (!silc_load_key_pair(cmd->argv[4], cmd->argv[5], pass,
+                                   &pubkey, &privkey)) {
+             SAY(conn->client, conn, SILC_CLIENT_MESSAGE_COMMAND_ERROR,
+                 "Could not load key pair, check your arguments");
+             COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+             goto out;
+           }
          }
+
+         silc_free(nickname);
+         silc_client_list_free(client, conn, clients);
+         silc_client_unref_channel(client, conn, channel);
+
+         SILC_FSM_CALL(silc_auth_public_key_auth_generate(
+                                            pubkey, privkey,
+                                            conn->client->rng,
+                                            conn->internal->sha1hash,
+                                            conn->local_id,
+                                            SILC_ID_CLIENT,
+                                            silc_client_command_cumode_signed,
+                                            cmd));
+         /* NOT REACHED */
        }
+
        mode |= SILC_CHANNEL_UMODE_CHANFO;
       } else {
        mode &= ~SILC_CHANNEL_UMODE_CHANFO;
@@ -1525,777 +2208,740 @@ SILC_CLIENT_CMD_FUNC(cumode)
       else
        mode &= ~SILC_CHANNEL_UMODE_CHANOP;
       break;
+    case 'b':
+      if (add)
+       mode |= SILC_CHANNEL_UMODE_BLOCK_MESSAGES;
+      else
+       mode &= ~SILC_CHANNEL_UMODE_BLOCK_MESSAGES;
+      break;
+    case 'u':
+      if (add)
+       mode |= SILC_CHANNEL_UMODE_BLOCK_MESSAGES_USERS;
+      else
+       mode &= ~SILC_CHANNEL_UMODE_BLOCK_MESSAGES_USERS;
+      break;
+    case 'r':
+      if (add)
+       mode |= SILC_CHANNEL_UMODE_BLOCK_MESSAGES_ROBOTS;
+      else
+       mode &= ~SILC_CHANNEL_UMODE_BLOCK_MESSAGES_ROBOTS;
+      break;
+    case 'q':
+      if (add)
+       mode |= SILC_CHANNEL_UMODE_QUIET;
+      else
+       mode &= ~SILC_CHANNEL_UMODE_QUIET;
+      break;
     default:
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_UNKNOWN_MODE);
       goto out;
       break;
     }
   }
 
-  chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+  chidp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
   SILC_PUT32_MSB(mode, modebuf);
-  clidp = silc_id_payload_encode(client_entry->id, SILC_ID_CLIENT);
+  clidp = silc_id_payload_encode(&client_entry->id, SILC_ID_CLIENT);
 
   /* Send the command packet. We support sending only one mode at once
      that requires an argument. */
-  buffer = silc_command_payload_encode_va(SILC_COMMAND_CUMODE, 0, 
-                                         auth ? 4 : 3, 
-                                         1, chidp->data, chidp->len, 
-                                         2, modebuf, 4,
-                                         3, clidp->data, clidp->len,
-                                         4, auth ? auth->data : NULL, 
-                                         auth ? auth->len : 0);
-  
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, auth ? 4 : 3,
+                             1, silc_buffer_datalen(chidp),
+                             2, modebuf, 4,
+                             3, silc_buffer_datalen(clidp),
+                             4, silc_buffer_datalen(auth));
+
   silc_buffer_free(chidp);
   silc_buffer_free(clidp);
   if (auth)
     silc_buffer_free(auth);
-  
+  silc_free(nickname);
+  silc_client_list_free(client, conn, clients);
+  silc_client_unref_channel(client, conn, channel);
+
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
+  silc_client_unref_channel(client, conn, channel);
+  silc_client_list_free(client, conn, clients);
   silc_free(nickname);
-  silc_client_command_free(cmd);
+  return SILC_FSM_FINISH;
 }
 
+/********************************** KICK ************************************/
+
 /* KICK command. Kicks a client out of channel. */
 
-SILC_CLIENT_CMD_FUNC(kick)
+SILC_FSM_STATE(silc_client_command_kick)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClient client = cmd->client;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcIDCacheEntry id_cache = NULL;
-  SilcChannelEntry channel;
-  SilcBuffer buffer, idp, idp2;
+  SilcClient client = conn->client;
+  SilcChannelEntry channel = NULL;
+  SilcBuffer idp, idp2;
   SilcClientEntry target;
-  char *name;
-  char *nickname = NULL;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcDList clients = NULL;
+  char *name, tmp[512];
 
   if (cmd->argc < 3) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /KICK <channel> <nickname> [<comment>]");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
-    name = conn->current_channel->channel_name;
+
+    if (client->internal->params->full_channel_names)
+      silc_snprintf(tmp, sizeof(tmp), conn->current_channel->channel_name);
+    else
+      silc_snprintf(tmp, sizeof(tmp), "%s%s%s",
+                   conn->current_channel->channel_name,
+                   conn->current_channel->server[0] ? "@" : "",
+                   conn->current_channel->server);
+    name = tmp;
   } else {
     name = cmd->argv[1];
   }
 
   if (!conn->current_channel) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "You are not on that channel");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
     goto out;
   }
 
   /* Get the Channel ID of the channel */
-  if (!silc_idcache_find_by_name_one(conn->channel_cache, name, &id_cache)) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "You are not on that channel");
-    COMMAND_ERROR;
+  channel = silc_client_get_channel(conn->client, conn, name);
+  if (!channel) {
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
     goto out;
   }
 
-  channel = (SilcChannelEntry)id_cache->context;
-
-  /* Parse the typed nickname. */
-  if (client->internal->params->nickname_parse)
-    client->internal->params->nickname_parse(cmd->argv[2], &nickname);
-  else
-    nickname = strdup(cmd->argv[2]);
-
   /* Get the target client */
-  target = silc_idlist_get_client(cmd->client, conn, nickname, 
-                                 cmd->argv[2], FALSE);
-  if (!target) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+  clients = silc_client_get_clients_local(client, conn, cmd->argv[2], FALSE);
+  if (!clients) {
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "No such client: %s", cmd->argv[2]);
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NO_SUCH_NICK);
     goto out;
   }
+  target = silc_dlist_get(clients);
 
   /* Send KICK command to the server */
-  idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
-  idp2 = silc_id_payload_encode(target->id, SILC_ID_CLIENT);
+  idp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
+  idp2 = silc_id_payload_encode(&target->id, SILC_ID_CLIENT);
   if (cmd->argc == 3)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_KICK, 0, 2, 
-                                           1, idp->data, idp->len,
-                                           2, idp2->data, idp2->len);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 2,
+                               1, silc_buffer_datalen(idp),
+                               2, silc_buffer_datalen(idp2));
   else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_KICK, 0, 3, 
-                                           1, idp->data, idp->len,
-                                           2, idp2->data, idp2->len,
-                                           3, cmd->argv[3], 
-                                           strlen(cmd->argv[3]));
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 3,
+                               1, silc_buffer_datalen(idp),
+                               2, silc_buffer_datalen(idp2),
+                               3, cmd->argv[3], strlen(cmd->argv[3]));
+
   silc_buffer_free(idp);
   silc_buffer_free(idp2);
+  silc_client_list_free(client, conn, clients);
+  silc_client_unref_channel(client, conn, channel);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_free(nickname);
-  silc_client_command_free(cmd);
+  silc_client_unref_channel(client, conn, channel);
+  return SILC_FSM_FINISH;
 }
 
-static void silc_client_command_oper_send(unsigned char *data,
-                                         SilcUInt32 data_len, void *context)
+/***************************** OPER & SILCOPER ******************************/
+
+typedef struct {
+  unsigned char *passphrase;
+  SilcUInt32 passphrase_len;
+  SilcBuffer auth;
+} *SilcClientCommandOper;
+
+/* Ask passphrase callback */
+
+static void silc_client_command_oper_cb(const unsigned char *data,
+                                       SilcUInt32 data_len, void *context)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer, auth;
+  SilcClientCommandContext cmd = context;
+  SilcClientCommandOper oper = cmd->context;
 
-  if (cmd->argc >= 3) {
-    /* Encode the public key authentication payload */
-    auth = silc_auth_public_key_auth_generate(cmd->client->public_key,
-                                             cmd->client->private_key,
-                                             cmd->client->rng, conn->hash,
-                                             conn->local_id,
-                                             SILC_ID_CLIENT);
-  } else {
-    /* Encode the password authentication payload */
-    auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0,
-                                   data, data_len);
-  }
+  if (data && data_len)
+    oper->passphrase = silc_memdup(data, data_len);
+  oper->passphrase_len = data_len;
+
+  /* Continue */
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
+}
 
-  buffer = silc_command_payload_encode_va(SILC_COMMAND_OPER, 0, 2, 
-                                         1, cmd->argv[1], 
-                                         strlen(cmd->argv[1]),
-                                         2, auth->data, auth->len);
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
+static void silc_client_command_oper_sign_cb(const SilcBuffer data,
+                                            void *context)
+{
+  SilcClientCommandContext cmd = context;
+  SilcClientCommandOper oper = cmd->context;
 
-  silc_buffer_free(buffer);
-  silc_buffer_free(auth);
+  if (data)
+    oper->auth = silc_buffer_copy(data);
 
-  /* Notify application */
-  COMMAND;
+  /* Continue */
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
 }
 
-/* OPER command. Used to obtain server operator privileges. */
+/* Send OPER/SILCOPER command */
 
-SILC_CLIENT_CMD_FUNC(oper)
+SILC_FSM_STATE(silc_client_command_oper_send)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
+  SilcClientCommandOper oper = cmd->context;
+  SilcBuffer auth = oper ? oper->auth : NULL;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
-  if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "Usage: /OPER <username> [-pubkey]");
-    COMMAND_ERROR;
-    goto out;
-  }
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 2,
+                             1, cmd->argv[1], strlen(cmd->argv[1]),
+                             2, silc_buffer_datalen(auth));
 
-  if (cmd->argc < 3) {
-    /* Get passphrase */
-    cmd->client->internal->ops->ask_passphrase(cmd->client, conn,
-                                    silc_client_command_oper_send,
-                                    context);
-    return;
+  silc_buffer_clear(auth);
+  silc_buffer_free(auth);
+  if (oper) {
+    silc_free(oper->passphrase);
+    silc_free(oper);
   }
 
-  silc_client_command_oper_send(NULL, 0, context);
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
-static void silc_client_command_silcoper_send(unsigned char *data,
-                                             SilcUInt32 data_len, void *context)
+/* Get authentication data */
+
+SILC_FSM_STATE(silc_client_command_oper_get_auth)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer, auth;
+  SilcClientCommandOper oper = cmd->context;
 
-  if (cmd->argc >= 3) {
+  silc_fsm_next(fsm, silc_client_command_oper_send);
+
+  if (!oper || !oper->passphrase) {
     /* Encode the public key authentication payload */
-    auth = silc_auth_public_key_auth_generate(cmd->client->public_key,
-                                             cmd->client->private_key,
-                                             cmd->client->rng, conn->hash,
-                                             conn->local_id,
-                                             SILC_ID_CLIENT);
-  } else {
-    /* Encode the password authentication payload */
-    auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0,
-                                   data, data_len);
+    SILC_FSM_CALL(silc_auth_public_key_auth_generate(
+                                      conn->public_key,
+                                      conn->private_key,
+                                      conn->client->rng,
+                                      conn->internal->hash,
+                                      conn->local_id, SILC_ID_CLIENT,
+                                      silc_client_command_oper_sign_cb,
+                                      oper));
+    /* NOT REACHED */
+  }
+
+  /* Encode the password authentication payload */
+  oper->auth = silc_auth_payload_encode(NULL, SILC_AUTH_PASSWORD, NULL, 0,
+                                       oper->passphrase, oper->passphrase_len);
+
+  return SILC_FSM_CONTINUE;
+}
+
+/* OPER command. Used to obtain server operator privileges. */
+
+SILC_FSM_STATE(silc_client_command_oper)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcClientCommandOper oper;
+
+  if (cmd->argc < 2) {
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /OPER <username> [-pubkey]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
-  buffer = silc_command_payload_encode_va(SILC_COMMAND_SILCOPER, 0, 2, 
-                                         1, cmd->argv[1], 
-                                         strlen(cmd->argv[1]),
-                                         2, auth->data, auth->len);
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
+  silc_fsm_next(fsm, silc_client_command_oper_get_auth);
 
-  silc_buffer_free(buffer);
-  silc_buffer_free(auth);
+  /* Get passphrase */
+  if (cmd->argc < 3) {
+    oper = silc_calloc(1, sizeof(*oper));
+    if (!oper)
+      return SILC_FSM_FINISH;
+    cmd->context = oper;
+    SILC_FSM_CALL(conn->client->internal->
+                 ops->ask_passphrase(conn->client, conn,
+                                     silc_client_command_oper_cb, cmd));
+  }
 
-  /* Notify application */
-  COMMAND;
+  return SILC_FSM_CONTINUE;
 }
 
 /* SILCOPER command. Used to obtain router operator privileges. */
 
-SILC_CLIENT_CMD_FUNC(silcoper)
+SILC_FSM_STATE(silc_client_command_silcoper)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcClientCommandOper oper;
 
   if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /SILCOPER <username> [-pubkey]");
-    COMMAND_ERROR;
-    goto out;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
+  silc_fsm_next(fsm, silc_client_command_oper_send);
+
+  /* Get passphrase */
   if (cmd->argc < 3) {
-    /* Get passphrase */
-    cmd->client->internal->ops->ask_passphrase(cmd->client, conn,
-                                    silc_client_command_silcoper_send,
-                                    context);
-    return;
+    oper = silc_calloc(1, sizeof(*oper));
+    if (!oper)
+      return SILC_FSM_FINISH;
+    cmd->context = oper;
+    SILC_FSM_CALL(conn->client->internal->
+                 ops->ask_passphrase(conn->client, conn,
+                                     silc_client_command_oper_cb, cmd));
   }
 
-  silc_client_command_silcoper_send(NULL, 0, context);
-
- out:
-  silc_client_command_free(cmd);
+  return SILC_FSM_CONTINUE;
 }
 
+/*********************************** BAN ************************************/
+
 /* Command BAN. This is used to manage the ban list of the channel. */
 
-SILC_CLIENT_CMD_FUNC(ban)
+SILC_FSM_STATE(silc_client_command_ban)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
+  SilcClient client = conn->client;
   SilcChannelEntry channel;
-  SilcBuffer buffer, chidp;
-  int type = 0;
+  SilcBuffer chidp, args = NULL;
   char *name, *ban = NULL;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  unsigned char action[1];
+  SilcPublicKey pubkey = NULL;
 
   if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /BAN <channel> "
        "[+|-[<nickname>[@<server>[!<username>[@hostname>]]]]]");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
 
     channel = conn->current_channel;
+    silc_client_ref_channel(client, conn, channel);
   } else {
     name = cmd->argv[1];
 
-    channel = silc_client_get_channel(cmd->client, conn, name);
+    channel = silc_client_get_channel(conn->client, conn, name);
     if (!channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are on that channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
   }
 
   if (cmd->argc == 3) {
     if (cmd->argv[2][0] == '+')
-      type = 2;
+      action[0] = 0x00;
     else
-      type = 3;
+      action[0] = 0x01;
 
+    /* Check if it is public key file to be added to invite list */
+    silc_pkcs_load_public_key(cmd->argv[2] + 1, SILC_PKCS_ANY, &pubkey);
     ban = cmd->argv[2];
-    ban++;
+    if (!pubkey)
+      ban++;
+  }
+
+  if (ban) {
+    args = silc_buffer_alloc_size(2);
+    silc_buffer_format(args,
+                      SILC_STR_UI_SHORT(1),
+                      SILC_STR_END);
+    if (pubkey) {
+      chidp = silc_public_key_payload_encode(NULL, pubkey);
+      args = silc_argument_payload_encode_one(args,
+                                             silc_buffer_datalen(chidp), 2);
+      silc_buffer_free(chidp);
+      silc_pkcs_public_key_free(pubkey);
+    } else {
+      args = silc_argument_payload_encode_one(args, ban, strlen(ban), 1);
+    }
   }
 
-  chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+  chidp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
 
   /* Send the command */
-  if (ban)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_BAN, 0, 2, 
-                                           1, chidp->data, chidp->len,
-                                           type, ban, strlen(ban));
-  else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_BAN, 0, 1, 
-                                           1, chidp->data, chidp->len);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 3,
+                             1, silc_buffer_datalen(chidp),
+                             2, args ? action : NULL, args ? 1 : 0,
+                             3, silc_buffer_datalen(args));
 
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
   silc_buffer_free(chidp);
+  silc_buffer_free(args);
+  silc_client_unref_channel(client, conn, channel);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_client_command_free(cmd);
+  return SILC_FSM_FINISH;
 }
 
-/* LEAVE command. Leaves a channel. Client removes itself from a channel. */
+/********************************* DETACH ***********************************/
 
-SILC_CLIENT_CMD_FUNC(leave)
+/* Command DETACH. This is used to detach from the server */
+
+SILC_FSM_STATE(silc_client_command_detach)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcChannelEntry channel;
-  SilcChannelUser chu;
-  SilcBuffer buffer, idp;
-  char *name;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 0);
+
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
+}
+
+/********************************** WATCH ***********************************/
+
+/* Command WATCH. */
+
+SILC_FSM_STATE(silc_client_command_watch)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcBuffer args = NULL;
+  int type = 0;
+  const char *pubkey = NULL;
+  SilcBool pubkey_add = TRUE;
+
+  if (cmd->argc < 3) {
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+
+  if (!strcasecmp(cmd->argv[1], "-add")) {
+    type = 2;
+  } else if (!strcasecmp(cmd->argv[1], "-del")) {
+    type = 3;
+  } else if (!strcasecmp(cmd->argv[1], "-pubkey") && cmd->argc >= 3) {
+    type = 4;
+    pubkey = cmd->argv[2] + 1;
+    if (cmd->argv[2][0] == '-')
+      pubkey_add = FALSE;
+  } else {
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
+  if (pubkey) {
+    SilcPublicKey pk;
+    SilcBuffer buffer;
+
+    if (!silc_pkcs_load_public_key(pubkey, SILC_PKCS_ANY, &pk)) {
+      SAY(conn->client, conn, SILC_CLIENT_MESSAGE_COMMAND_ERROR,
+         "Could not load public key %s, check the filename", pubkey);
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      goto out;
+    }
+
+    args = silc_buffer_alloc_size(2);
+    silc_buffer_format(args,
+                      SILC_STR_UI_SHORT(1),
+                      SILC_STR_END);
+    buffer = silc_public_key_payload_encode(NULL, pk);
+    args = silc_argument_payload_encode_one(args, silc_buffer_datalen(buffer),
+                                           pubkey_add ? 0x00 : 0x01);
+    silc_buffer_free(buffer);
+    silc_pkcs_public_key_free(pk);
+  }
+
+  /* If watching by nickname, resolve all users with that nickname so that
+     we get their information immediately. */
+  if (type == 2)
+    silc_client_get_clients(conn->client, conn, cmd->argv[2], NULL,
+                           silc_client_command_resolve_dummy, NULL);
+
+  /* Send the commmand */
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 2,
+                             1, silc_buffer_datalen(conn->internal->
+                                                    local_idp),
+                             type, pubkey ? args->data : cmd->argv[2],
+                             pubkey ? silc_buffer_len(args) :
+                             cmd->argv_lens[2]);
+
+  silc_buffer_free(args);
+
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
+
+ out:
+  return SILC_FSM_FINISH;
+}
+
+/********************************** LEAVE ***********************************/
+
+/* LEAVE command. Leaves a channel. Client removes itself from a channel. */
+
+SILC_FSM_STATE(silc_client_command_leave)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcClient client = conn->client;
+  SilcChannelEntry channel;
+  SilcBuffer idp;
+  char *name, tmp[512];
+
   if (cmd->argc != 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /LEAVE <channel>");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
-    name = conn->current_channel->channel_name;
+
+    if (client->internal->params->full_channel_names)
+      silc_snprintf(tmp, sizeof(tmp), conn->current_channel->channel_name);
+    else
+      silc_snprintf(tmp, sizeof(tmp), "%s%s%s",
+                   conn->current_channel->channel_name,
+                   conn->current_channel->server[0] ? "@" : "",
+                   conn->current_channel->server);
+    name = tmp;
   } else {
     name = cmd->argv[1];
   }
 
   /* Get the channel entry */
-  channel = silc_client_get_channel(cmd->client, conn, name);
+  channel = silc_client_get_channel(conn->client, conn, name);
   if (!channel) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "You are not on that channel");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
     goto out;
   }
 
-  /* Remove us from channel */
-  chu = silc_client_on_channel(channel, conn->local_entry);
-  if (chu) {
-    silc_hash_table_del(chu->client->channels, chu->channel);
-    silc_hash_table_del(chu->channel->user_list, chu->client);
-    silc_free(chu);
-  }
+  idp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
 
   /* Send LEAVE command to the server */
-  idp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
-  buffer = silc_command_payload_encode_va(SILC_COMMAND_LEAVE, 0, 1, 
-                                         1, idp->data, idp->len);
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                             1, silc_buffer_datalen(idp));
+
   silc_buffer_free(idp);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
 
   if (conn->current_channel == channel)
     conn->current_channel = NULL;
 
-  silc_client_del_channel(cmd->client, cmd->conn, channel);
+  silc_client_unref_channel(client, conn, channel);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_client_command_free(cmd);
+  return SILC_FSM_FINISH;
 }
 
+/********************************** USERS ***********************************/
+
 /* Command USERS. Requests the USERS of the clients joined on requested
    channel. */
 
-SILC_CLIENT_CMD_FUNC(users)
+SILC_FSM_STATE(silc_client_command_users)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-  char *name;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  char *name, tmp[512];
 
   if (cmd->argc != 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
        "Usage: /USERS <channel>");
-    COMMAND_ERROR;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-         "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
-    name = conn->current_channel->channel_name;
+
+    if (conn->client->internal->params->full_channel_names)
+      silc_snprintf(tmp, sizeof(tmp), conn->current_channel->channel_name);
+    else
+      silc_snprintf(tmp, sizeof(tmp), "%s%s%s",
+                   conn->current_channel->channel_name,
+                   conn->current_channel->server[0] ? "@" : "",
+                   conn->current_channel->server);
+    name = tmp;
   } else {
     name = cmd->argv[1];
   }
 
   /* Send USERS command to the server */
-  buffer = silc_command_payload_encode_va(SILC_COMMAND_USERS, 
-                                         ++conn->cmd_ident, 1, 
-                                         2, name, strlen(name));
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, 
-                         NULL, 0, NULL, NULL, buffer->data, 
-                         buffer->len, TRUE);
-  silc_buffer_free(buffer);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                             2, name, strlen(name));
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
+
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
  out:
-  silc_client_command_free(cmd);
+  return SILC_FSM_FINISH;
 }
 
+/********************************* GETKEY ***********************************/
+
 /* Command GETKEY. Used to fetch remote client's public key. */
 
-SILC_CLIENT_CMD_FUNC(getkey)
+SILC_FSM_STATE(silc_client_command_getkey)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcClient client = cmd->client;
-  SilcClientEntry client_entry = NULL;
-  SilcServerEntry server_entry = NULL;
-  char *nickname = NULL;
-  SilcBuffer idp, buffer;
-
-  SILC_LOG_DEBUG(("Start"));
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcClient client = conn->client;
+  SilcClientEntry client_entry;
+  SilcServerEntry server_entry;
+  SilcDList clients;
+  SilcBuffer idp;
 
   if (cmd->argc < 2) {
-    client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_INFO, 
+    client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_INFO,
                     "Usage: /GETKEY <nickname or server name>");
-    COMMAND_ERROR;
-    goto out;
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
-  /* Parse the typed nickname. */
-  if (client->internal->params->nickname_parse)
-    client->internal->params->nickname_parse(cmd->argv[1], &nickname);
-  else
-    nickname = strdup(cmd->argv[1]);
-
   /* Find client entry */
-  client_entry = silc_idlist_get_client(client, conn, nickname, cmd->argv[1],
-                                       FALSE);
-  if (!client_entry) {
-    /* Check whether user requested server actually */
+  clients = silc_client_get_clients_local(client, conn, cmd->argv[1], FALSE);
+  if (!clients) {
+    /* Check whether user requested server */
     server_entry = silc_client_get_server(client, conn, cmd->argv[1]);
-
     if (!server_entry) {
-      /* No. what ever user wants we don't have it, so resolve it. We
-        will first try to resolve the client, and if that fails then
-        we'll try to resolve the server. */
-
-      if (!cmd->pending) {
-       /* This will send the IDENTIFY command for nickname */
-       silc_idlist_get_client(client, conn, nickname, cmd->argv[1], TRUE);
-       silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
-                                   conn->cmd_ident,  
-                                   silc_client_command_getkey, 
-                                   silc_client_command_dup(cmd));
-       cmd->pending = 1;
-       goto out;
-      } else {
-       SilcClientCommandReplyContext reply = 
-         (SilcClientCommandReplyContext)context2;
-       SilcCommandStatus status = silc_command_get_status(reply->payload);
-       
-       /* If nickname was not found, then resolve the server. */
-       if (status == SILC_STATUS_ERR_NO_SUCH_NICK) {
-         /* This sends the IDENTIFY command to resolve the server. */
-         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, 
-                                  2, cmd->argv[1], cmd->argv_lens[1]);
-         silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
-                                     conn->cmd_ident, 
-                                     silc_client_command_getkey, 
-                                     silc_client_command_dup(cmd));
-         goto out;
-       }
-
-       /* If server was not found, then we've resolved both nickname and
-          server and did not find anybody. */
-       if (status == SILC_STATUS_ERR_NO_SUCH_SERVER) {
-         SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_ERROR, "%s", 
-            silc_client_command_status_message(SILC_STATUS_ERR_NO_SUCH_NICK));
-         SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_ERROR, "%s", 
-           silc_client_command_status_message(status));
-         COMMAND_ERROR;
-         goto out;
-       }
-
-       COMMAND_ERROR;
-       goto out;
+      if (cmd->resolved) {
+       /* Resolving didn't find anything.  We should never get here as
+          errors are handled in the resolving callback. */
+       COMMAND_ERROR(SILC_STATUS_ERR_NO_SUCH_NICK);
+       COMMAND_ERROR(SILC_STATUS_ERR_NO_SUCH_SERVER);
+       return SILC_FSM_FINISH;
       }
-    }
 
-    idp = silc_id_payload_encode(server_entry->server_id, SILC_ID_SERVER);
+      /* No client or server exist with this name, query for both. */
+      cmd->resolved = TRUE;
+      SILC_FSM_CALL(silc_client_command_send(client, conn,
+                                            SILC_COMMAND_IDENTIFY,
+                                            silc_client_command_continue,
+                                            cmd, 2,
+                                            1, cmd->argv[1],
+                                            strlen(cmd->argv[1]),
+                                            2, cmd->argv[1],
+                                            strlen(cmd->argv[1])));
+      /* NOT REACHED */
+    }
+    idp = silc_id_payload_encode(&server_entry->id, SILC_ID_SERVER);
+    silc_client_unref_server(client, conn, server_entry);
   } else {
-    idp = silc_id_payload_encode(client_entry->id, SILC_ID_CLIENT);
+    client_entry = silc_dlist_get(clients);
+    idp = silc_id_payload_encode(&client_entry->id, SILC_ID_CLIENT);
+    silc_client_list_free(client, conn, clients);
   }
 
-  buffer = silc_command_payload_encode_va(SILC_COMMAND_GETKEY, 0, 1, 
-                                         1, idp->data, idp->len);
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
+  /* Send the commmand */
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                             1, silc_buffer_datalen(idp));
+
   silc_buffer_free(idp);
 
   /* Notify application */
-  COMMAND;
-
- out:
-  silc_free(nickname);
-  silc_client_command_free(cmd);
-}
-
-/* Register a new command indicated by the `command' to the SILC client.
-   The `name' is optional command name.  If provided the command may be
-   searched using the silc_client_command_find by that name.  The
-   `command_function' is the function to be called when the command is
-   executed, and the `command_reply_function' is the function to be
-   called after the server has sent reply back to the command. 
-
-   The `ident' is optional identifier for the command.  If non-zero
-   the `command_reply_function' for the command type `command' will be
-   called only if the command reply sent by server includes the 
-   command identifier `ident'. Application usually does not need it
-   and set it to zero value. */
-
-bool silc_client_command_register(SilcClient client,
-                                 SilcCommand command,
-                                 const char *name,
-                                 SilcCommandCb command_function,
-                                 SilcCommandCb command_reply_function,
-                                 SilcUInt8 max_args,
-                                 SilcUInt16 ident)
-{
-  SilcClientCommand cmd;
-
-  cmd = silc_calloc(1, sizeof(*cmd));
-  cmd->cmd = command;
-  cmd->command = command_function;
-  cmd->reply = command_reply_function;
-  cmd->name = name ? strdup(name) : NULL;
-  cmd->max_args = max_args;
-  cmd->ident = ident;
-
-  silc_list_add(client->internal->commands, cmd);
-
-  return TRUE;
-}
-
-/* Unregister a command indicated by the `command' with command function
-   `command_function' and command reply function `command_reply_function'.
-   Returns TRUE if the command was found and unregistered. */
-
-bool silc_client_command_unregister(SilcClient client,
-                                   SilcCommand command,
-                                   SilcCommandCb command_function,
-                                   SilcCommandCb command_reply_function,
-                                   SilcUInt16 ident)
-{
-  SilcClientCommand cmd;
-
-  silc_list_start(client->internal->commands);
-  while ((cmd = silc_list_get(client->internal->commands)) != SILC_LIST_END) {
-    if (cmd->cmd == command && cmd->command == command_function &&
-       cmd->reply == command_reply_function && cmd->ident == ident) {
-      silc_list_del(client->internal->commands, cmd);
-      silc_free(cmd->name);
-      silc_free(cmd);
-      return TRUE;
-    }
-  }
+  COMMAND(SILC_STATUS_OK);
 
-  return FALSE;
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
-/* Private range commands, specific to this implementation (and compatible
-   with SILC Server). */
-
-/* CONNECT command. Connects the server to another server. */
-
-SILC_CLIENT_CMD_FUNC(connect)
-{
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-  unsigned char port[4];
-  SilcUInt32 tmp;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
-  if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "Usage: /CONNECT <server> [<port>]");
-    COMMAND_ERROR;
-    goto out;
-  }
-
-  if (cmd->argc == 3) {
-    tmp = atoi(cmd->argv[2]);
-    SILC_PUT32_MSB(tmp, port);
-  }
-
-  if (cmd->argc == 3)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_PRIV_CONNECT, 0, 2, 
-                                           1, cmd->argv[1], 
-                                           strlen(cmd->argv[1]),
-                                           2, port, 4);
-  else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_PRIV_CONNECT, 0, 1,
-                                           1, cmd->argv[1], 
-                                           strlen(cmd->argv[1]));
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
-  silc_buffer_free(buffer);
-
-  /* Notify application */
-  COMMAND;
-
- out:
-  silc_client_command_free(cmd);
-}
+/********************************* SERVICE **********************************/
 
+/* Command SERVICE.  Negotiates service agreement with server. */
+/* XXX incomplete */
 
-/* CLOSE command. Close server connection to the remote server */
-SILC_CLIENT_CMD_FUNC(close)
+SILC_FSM_STATE(silc_client_command_service)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
+#if 0
   SilcClientConnection conn = cmd->conn;
   SilcBuffer buffer;
-  unsigned char port[4];
-  SilcUInt32 tmp;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  char *name;
 
   if (cmd->argc < 2) {
-    SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_INFO, 
-       "Usage: /CLOSE <server> [<port>]");
-    COMMAND_ERROR;
-    goto out;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /SERVICE [<service name>] [-pubkey]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
-  if (cmd->argc == 3) {
-    tmp = atoi(cmd->argv[2]);
-    SILC_PUT32_MSB(tmp, port);
-  }
+  name = cmd->argv[1];
 
-  if (cmd->argc == 3)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_PRIV_CLOSE, 0, 2, 
-                                           1, cmd->argv[1], 
-                                           strlen(cmd->argv[1]),
-                                           2, port, 4);
-  else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_PRIV_CLOSE, 0, 1,
-                                           1, cmd->argv[1], 
-                                           strlen(cmd->argv[1]));
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL,
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
+  /* Send SERVICE command to the server */
+  buffer = silc_command_payload_encode_va(SILC_COMMAND_SERVICE,
+                                         ++conn->cmd_ident, 1,
+                                         1, name, strlen(name));
+  silc_client_packet_send(conn->client, conn->sock, SILC_PACKET_COMMAND,
+                         NULL, 0, NULL, NULL, buffer->data,
+                         buffer->len, TRUE);
   silc_buffer_free(buffer);
+#endif /* 0 */
 
   /* Notify application */
-  COMMAND;
-
- out:
-  silc_client_command_free(cmd);
-}
-/* SHUTDOWN command. Shutdowns the server. */
-
-SILC_CLIENT_CMD_FUNC(shutdown)
-{
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
-  /* Send the command */
-  silc_client_command_send(cmd->client, cmd->conn, 
-                          SILC_COMMAND_PRIV_SHUTDOWN, 0, 0);
-
-  /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
 /* Register all default commands provided by the client library for the
@@ -2303,10 +2949,10 @@ SILC_CLIENT_CMD_FUNC(shutdown)
 
 void silc_client_commands_register(SilcClient client)
 {
-  silc_list_init(client->internal->commands, struct SilcClientCommandStruct, 
+  silc_list_init(client->internal->commands, struct SilcClientCommandStruct,
                 next);
 
-  SILC_CLIENT_CMD(whois, WHOIS, "WHOIS", 3);
+  SILC_CLIENT_CMD(whois, WHOIS, "WHOIS", 5);
   SILC_CLIENT_CMD(whowas, WHOWAS, "WHOWAS", 3);
   SILC_CLIENT_CMD(identify, IDENTIFY, "IDENTIFY", 3);
   SILC_CLIENT_CMD(nick, NICK, "NICK", 2);
@@ -2314,25 +2960,25 @@ void silc_client_commands_register(SilcClient client)
   SILC_CLIENT_CMD(topic, TOPIC, "TOPIC", 3);
   SILC_CLIENT_CMD(invite, INVITE, "INVITE", 3);
   SILC_CLIENT_CMD(quit, QUIT, "QUIT", 2);
-  SILC_CLIENT_CMD(kill, KILL, "KILL", 3);
+  SILC_CLIENT_CMD(kill, KILL, "KILL", 4);
   SILC_CLIENT_CMD(info, INFO, "INFO", 2);
+  SILC_CLIENT_CMD(stats, STATS, "STATS", 0);
   SILC_CLIENT_CMD(ping, PING, "PING", 2);
   SILC_CLIENT_CMD(oper, OPER, "OPER", 3);
   SILC_CLIENT_CMD(join, JOIN, "JOIN", 9);
   SILC_CLIENT_CMD(motd, MOTD, "MOTD", 2);
   SILC_CLIENT_CMD(umode, UMODE, "UMODE", 2);
-  SILC_CLIENT_CMD(cmode, CMODE, "CMODE", 4);
-  SILC_CLIENT_CMD(cumode, CUMODE, "CUMODE", 5);
+  SILC_CLIENT_CMD(cmode, CMODE, "CMODE", 6);
+  SILC_CLIENT_CMD(cumode, CUMODE, "CUMODE", 9);
   SILC_CLIENT_CMD(kick, KICK, "KICK", 4);
   SILC_CLIENT_CMD(ban, BAN, "BAN", 3);
+  SILC_CLIENT_CMD(detach, DETACH, "DETACH", 0);
+  SILC_CLIENT_CMD(watch, WATCH, "WATCH", 3);
   SILC_CLIENT_CMD(silcoper, SILCOPER, "SILCOPER", 3);
   SILC_CLIENT_CMD(leave, LEAVE, "LEAVE", 2);
   SILC_CLIENT_CMD(users, USERS, "USERS", 2);
   SILC_CLIENT_CMD(getkey, GETKEY, "GETKEY", 2);
-
-  SILC_CLIENT_CMD(connect, PRIV_CONNECT, "CONNECT", 3);
-  SILC_CLIENT_CMD(close, PRIV_CLOSE, "CLOSE", 3);
-  SILC_CLIENT_CMD(shutdown, PRIV_SHUTDOWN, "SHUTDOWN", 1);
+  SILC_CLIENT_CMD(service, SERVICE, "SERVICE", 10);
 }
 
 /* Unregister all commands. */
@@ -2349,6 +2995,7 @@ void silc_client_commands_unregister(SilcClient client)
   SILC_CLIENT_CMDU(quit, QUIT, "QUIT");
   SILC_CLIENT_CMDU(kill, KILL, "KILL");
   SILC_CLIENT_CMDU(info, INFO, "INFO");
+  SILC_CLIENT_CMDU(stats, STATS, "STATS");
   SILC_CLIENT_CMDU(ping, PING, "PING");
   SILC_CLIENT_CMDU(oper, OPER, "OPER");
   SILC_CLIENT_CMDU(join, JOIN, "JOIN");
@@ -2358,12 +3005,140 @@ void silc_client_commands_unregister(SilcClient client)
   SILC_CLIENT_CMDU(cumode, CUMODE, "CUMODE");
   SILC_CLIENT_CMDU(kick, KICK, "KICK");
   SILC_CLIENT_CMDU(ban, BAN, "BAN");
+  SILC_CLIENT_CMDU(detach, DETACH, "DETACH");
+  SILC_CLIENT_CMDU(watch, WATCH, "WATCH");
   SILC_CLIENT_CMDU(silcoper, SILCOPER, "SILCOPER");
   SILC_CLIENT_CMDU(leave, LEAVE, "LEAVE");
   SILC_CLIENT_CMDU(users, USERS, "USERS");
   SILC_CLIENT_CMDU(getkey, GETKEY, "GETKEY");
+  SILC_CLIENT_CMDU(service, SERVICE, "SERVICE");
+}
+
+/****************** Client Side Incoming Command Handling *******************/
+
+typedef struct {
+  SilcClientConnection conn;
+  SilcUInt16 cmd_ident;
+} *SilcClientProcessWhois;
+
+/* Send reply to WHOIS from server */
+
+static void
+silc_client_command_process_whois_send(SilcBool success,
+                                      const unsigned char *data,
+                                      SilcUInt32 data_len, void *context)
+{
+  SilcClientProcessWhois w = context;
+  SilcBufferStruct buffer;
+  SilcBuffer packet;
+
+  if (!data) {
+    silc_free(w);
+    return;
+  }
+
+  silc_buffer_set(&buffer, (unsigned char *)data, data_len);
+
+  /* Send the attributes back in COMMAND_REPLY packet */
+  packet =
+    silc_command_reply_payload_encode_va(SILC_COMMAND_WHOIS,
+                                        SILC_STATUS_OK, 0, w->cmd_ident,
+                                        1, 11, buffer.data,
+                                        silc_buffer_len(&buffer));
+  if (!packet) {
+    silc_free(w);
+    return;
+  }
+
+  SILC_LOG_DEBUG(("Sending back requested WHOIS attributes"));
+
+  silc_packet_send(w->conn->stream, SILC_PACKET_COMMAND_REPLY, 0,
+                  silc_buffer_datalen(packet));
+
+  silc_buffer_free(packet);
+  silc_free(w);
+}
+
+/* Process WHOIS command from server */
+
+static void silc_client_command_process_whois(SilcClient client,
+                                             SilcClientConnection conn,
+                                             SilcCommandPayload payload,
+                                             SilcArgumentPayload args)
+{
+  SilcClientProcessWhois w;
+  SilcDList attrs;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len;
+
+  SILC_LOG_DEBUG(("Received WHOIS command"));
+
+  /* Try to take the Requested Attributes */
+  tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
+  if (!tmp)
+    return;
+
+  attrs = silc_attribute_payload_parse(tmp, tmp_len);
+  if (!attrs)
+    return;
+
+  w = silc_calloc(1, sizeof(*w));
+  if (!w) {
+    silc_attribute_payload_list_free(attrs);
+    return;
+  }
+  w->conn = conn;
+  w->cmd_ident = silc_command_get_ident(payload);
+
+  /* Process requested attributes */
+  silc_client_attributes_process(client, conn, attrs,
+                                silc_client_command_process_whois_send, w);
+  silc_attribute_payload_list_free(attrs);
+}
+
+/* Client is able to receive some command packets even though they are
+   special case.  Server may send WHOIS command to the client to retrieve
+   Requested Attributes information for WHOIS query the server is
+   processing. This function currently handles only the WHOIS command,
+   but if in the future more commands may arrive then this can be made
+   to support other commands too. */
+
+SILC_FSM_STATE(silc_client_command)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcPacket packet = state_context;
+  SilcCommandPayload payload;
+  SilcCommand command;
+  SilcArgumentPayload args;
+
+  /* Get command payload from packet */
+  payload = silc_command_payload_parse(packet->buffer.data,
+                                      silc_buffer_len(&packet->buffer));
+  if (!payload) {
+    SILC_LOG_DEBUG(("Bad command packet"));
+    return SILC_FSM_FINISH;
+  }
+
+  /* Get arguments */
+  args = silc_command_get_args(payload);
+
+  /* Get the command */
+  command = silc_command_get(payload);
+  switch (command) {
+
+  case SILC_COMMAND_WHOIS:
+    /* Ignore everything if requested by application */
+    if (conn->internal->params.ignore_requested_attributes)
+      break;
+
+    silc_client_command_process_whois(client, conn, payload, args);
+    break;
+
+  default:
+    break;
+  }
 
-  SILC_CLIENT_CMDU(connect, PRIV_CONNECT, "CONNECT");
-  SILC_CLIENT_CMDU(close, PRIV_CLOSE, "CLOSE");
-  SILC_CLIENT_CMDU(shutdown, PRIV_SHUTDOWN, "SHUTDOWN");
+  silc_command_payload_free(payload);
+  return SILC_FSM_FINISH;
 }