More client library rewrites.
[silc.git] / lib / silcclient / command.c
index a97cdc0d951780c4d16f73c2c2464a6373bc59f6..0eed6981311cea8bcaea107d35515439af62e3b8 100644 (file)
@@ -2,15 +2,14 @@
 
   command.c
 
-  Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
+  Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2001 Pekka Riikonen
+  Copyright (C) 1997 - 2006 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
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-  
+  the Free Software Foundation; version 2 of the License.
+
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 */
 /* $Id$ */
 
-#include "clientlibincludes.h"
+#include "silc.h"
+#include "silcclient.h"
 #include "client_internal.h"
 
-/* Client command list. */
-SilcClientCommand silc_command_list[] =
-{
-  SILC_CLIENT_CMD(whois, WHOIS, "WHOIS", SILC_CF_LAG | SILC_CF_REG, 3),
-  SILC_CLIENT_CMD(whowas, WHOWAS, "WHOWAS", SILC_CF_LAG | SILC_CF_REG, 3),
-  SILC_CLIENT_CMD(identify, IDENTIFY, "IDENTIFY", 
-                 SILC_CF_LAG | SILC_CF_REG, 3),
-  SILC_CLIENT_CMD(nick, NICK, "NICK", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(list, LIST, "LIST", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(topic, TOPIC, "TOPIC", SILC_CF_LAG | SILC_CF_REG, 3),
-  SILC_CLIENT_CMD(invite, INVITE, "INVITE", SILC_CF_LAG | SILC_CF_REG, 3),
-  SILC_CLIENT_CMD(quit, QUIT, "QUIT", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(kill, KILL, "KILL", 
-                 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 3),
-  SILC_CLIENT_CMD(info, INFO, "INFO", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(connect, CONNECT, "CONNECT",
-                 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 3),
-  SILC_CLIENT_CMD(ping, PING, "PING", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(oper, OPER, "OPER",
-                 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
-  SILC_CLIENT_CMD(join, JOIN, "JOIN", SILC_CF_LAG | SILC_CF_REG, 5),
-  SILC_CLIENT_CMD(motd, MOTD, "MOTD", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(umode, UMODE, "UMODE", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(cmode, CMODE, "CMODE", SILC_CF_LAG | SILC_CF_REG, 4),
-  SILC_CLIENT_CMD(cumode, CUMODE, "CUMODE", SILC_CF_LAG | SILC_CF_REG, 5),
-  SILC_CLIENT_CMD(kick, KICK, "KICK", SILC_CF_LAG | SILC_CF_REG, 4),
-  SILC_CLIENT_CMD(restart, RESTART, "RESTART",
-                 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
-  SILC_CLIENT_CMD(close, CLOSE, "CLOSE",
-                 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 3),
-  SILC_CLIENT_CMD(shutdown, SHUTDOWN, "SHUTDOWN",
-                 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 1),
-  SILC_CLIENT_CMD(silcoper, SILCOPER, "SILCOPER",
-                 SILC_CF_LAG | SILC_CF_REG | SILC_CF_SILC_OPER, 3),
-  SILC_CLIENT_CMD(leave, LEAVE, "LEAVE", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(users, USERS, "USERS", SILC_CF_LAG | SILC_CF_REG, 2),
-  SILC_CLIENT_CMD(ban, BAN, "BAN", SILC_CF_LAG | SILC_CF_REG, 3),
-
-  { NULL, 0, NULL, 0, 0 },
-};
-
-#define SILC_NOT_CONNECTED(x, c) \
-  x->ops->say((x), (c), \
-          "You are not connected to a server, use /SERVER to connect");
-
-/* Command operation that is called at the end of all commands. 
-   Usage: COMMAND; */
-#define COMMAND cmd->client->ops->command(cmd->client, cmd->conn, \
-  cmd, TRUE, cmd->command->cmd)
-
-/* Error to application. Usage: COMMAND_ERROR; */
-#define COMMAND_ERROR cmd->client->ops->command(cmd->client, cmd->conn, \
-  cmd, FALSE, cmd->command->cmd)
+/************************** Types and definitions ***************************/
 
-/* Generic function to send any command. The arguments must be sent already
-   encoded into correct form and in correct order. */
+/* 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(status); */
+#define COMMAND_ERROR(status)                                  \
+  cmd->conn->client->internal->ops->command(cmd->conn->client, \
+  cmd->conn, FALSE, cmd->cmd, (status), cmd->argc, cmd->argv)
+
+/* 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)
+
+/* 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_send_command(SilcClient client, SilcClientConnection conn,
-                             SilcCommand command, unsigned short ident,
-                             unsigned int 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;
 }
 
-/* Finds and returns a pointer to the command list. Return NULL if the
-   command is not found. */
+/* State to finish command thread after an error in resolving command */
 
-SilcClientCommand *silc_client_command_find(const char *name)
+SILC_FSM_STATE(silc_client_command_continue_error)
 {
-  SilcClientCommand *cmd;
+  /* Destructor will free all resources */
+  return SILC_FSM_FINISH;
+}
 
-  for (cmd = silc_command_list; cmd->name; cmd++) {
-    if (!strcmp(cmd->name, name))
-      return cmd;
+/* 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;
   }
 
-  return NULL;
+  /* 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;
 }
 
-/* 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,
-                                unsigned short ident,
-                                SilcClientPendingDestructor destructor,
-                                SilcCommandCb callback,
-                                void *context)
+/* Continues after resolving completed. */
+
+static void silc_client_command_resolve_continue(SilcClient client,
+                                                SilcClientConnection conn,
+                                                SilcStatus status,
+                                                SilcDList clients,
+                                                void *context)
 {
-  SilcClientCommandPending *reply;
-
-  reply = silc_calloc(1, sizeof(*reply));
-  reply->reply_cmd = reply_cmd;
-  reply->ident = ident;
-  reply->context = context;
-  reply->callback = callback;
-  reply->destructor = destructor;
-  silc_dlist_add(conn->pending_commands, reply);
+  SilcClientCommandContext cmd = context;
+
+  if (status != SILC_STATUS_OK)
+    silc_fsm_next(&cmd->thread, silc_client_command_continue_error);
+
+  if (clients)
+    silc_dlist_uninit(clients);
+
+  /* Continue with the command */
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
 }
 
-/* Deletes pending command by reply command type. */
+/* Register command to client */
 
-void silc_client_command_pending_del(SilcClientConnection conn,
-                                    SilcCommand reply_cmd,
-                                    unsigned short ident)
+static SilcBool
+silc_client_command_register(SilcClient client,
+                            SilcCommand command,
+                            const char *name,
+                            SilcFSMStateCallback command_func,
+                            SilcFSMStateCallback command_reply_func,
+                            SilcUInt8 max_args)
 {
-  SilcClientCommandPending *r;
+  SilcClientCommand cmd;
 
-  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;
-    }
-  }
+  cmd = silc_calloc(1, sizeof(*cmd));
+  cmd->cmd = command;
+  cmd->command = command_func;
+  cmd->reply = command_reply_func;
+  cmd->name = name ? strdup(name) : NULL;
+  cmd->max_args = max_args;
+
+  silc_list_add(client->internal->commands, cmd);
+
+  return TRUE;
 }
 
-/* Checks for pending commands and marks callbacks to be called from
-   the command reply function. Returns TRUE if there were pending command. */
+/* Unregister command from client */
 
-int silc_client_command_pending_check(SilcClientConnection conn,
-                                     SilcClientCommandReplyContext ctx,
-                                     SilcCommand command, 
-                                     unsigned short ident)
+static SilcBool
+silc_client_command_unregister(SilcClient client,
+                              SilcCommand command,
+                              SilcFSMStateCallback command_func,
+                              SilcFSMStateCallback command_reply_func)
 {
-  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->destructor = r->destructor;
-      ctx->ident = 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_func &&
+       cmd->reply == command_reply_func) {
+      silc_list_del(client->internal->commands, cmd);
+      silc_free(cmd->name);
+      silc_free(cmd);
       return TRUE;
     }
   }
@@ -175,316 +174,700 @@ int silc_client_command_pending_check(SilcClientConnection conn,
   return FALSE;
 }
 
-/* Allocate Command Context */
+/* Finds and returns a pointer to the command list. Return NULL if the
+   command is not found. */
 
-SilcClientCommandContext silc_client_command_alloc()
+static SilcClientCommand silc_client_command_find(SilcClient client,
+                                                 const char *name)
 {
-  SilcClientCommandContext ctx = silc_calloc(1, sizeof(*ctx));
-  ctx->users++;
-  return ctx;
+  SilcClientCommand cmd;
+
+  silc_list_start(client->internal->commands);
+  while ((cmd = silc_list_get(client->internal->commands)) != SILC_LIST_END) {
+    if (cmd->name && !strcasecmp(cmd->name, name))
+      return cmd;
+  }
+
+  return NULL;
 }
 
 /* Free command context and its internals */
 
-void silc_client_command_free(SilcClientCommandContext ctx)
+static void silc_client_command_free(SilcClientCommandContext cmd)
+{
+  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_free(cmd);
+}
+
+/* Command thread destructor */
+
+static void silc_client_command_destructor(SilcFSMThread thread,
+                                          void *fsm_context,
+                                          void *destructor_context)
+{
+  silc_client_command_free(fsm_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)
+{
+  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;
+}
+
+/* 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)
 {
-  ctx->users--;
-  SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users + 1,
-                 ctx->users));
-  if (ctx->users < 1) {
-    int i;
+  SilcBuffer packet;
+
+  SILC_LOG_DEBUG(("Send command %s", silc_get_command_name(command)));
+
+  if (!cmd->cmd_ident)
+    cmd->cmd_ident = silc_client_cmd_ident(conn);
 
-    for (i = 0; i < ctx->argc; i++)
-      silc_free(ctx->argv[i]);
-    silc_free(ctx);
+  /* 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;
   }
+
+  /* Add the command pending command reply */
+  silc_client_command_add_pending(conn, cmd, reply, reply_context);
+
+  silc_buffer_free(packet);
+
+  return cmd->cmd_ident;
 }
 
-/* Duplicate Command Context by adding reference counter. The context won't
-   be free'd untill it hits zero. */
+/* 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)
+{
+  SilcBuffer packet;
+
+  SILC_LOG_DEBUG(("Send command %s", silc_get_command_name(command)));
+
+  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);
 
-SilcClientCommandContext silc_client_command_dup(SilcClientCommandContext ctx)
+  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.  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, ...)
 {
-  ctx->users++;
-  SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users - 1,
-                 ctx->users));
-  return ctx;
+  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);
+
+  return cmd_ident;
 }
 
-/* Pending command destructor. */
+/****************************** Command API *********************************/
+
+/* Executes a command */
 
-static void silc_client_command_destructor(void *context)
+SilcUInt16 silc_client_command_call(SilcClient client,
+                                   SilcClientConnection conn,
+                                   const char *command_line, ...)
 {
-  silc_client_command_free((SilcClientCommandContext)context);
+  va_list va;
+  SilcUInt32 argc = 0;
+  unsigned char **argv = NULL;
+  SilcUInt32 *argv_lens = NULL, *argv_types = NULL;
+  SilcClientCommand command;
+  SilcClientCommandContext cmd;
+  char *arg;
+
+  if (!conn) {
+    client->internal->ops->say(client, NULL, SILC_CLIENT_MESSAGE_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 FALSE;
+      argv[argc] = silc_memdup(arg, strlen(arg));
+      if (!argv[argc])
+       return FALSE;
+      argv_lens[argc] = strlen(arg);
+      argv_types[argc] = argc;
+      argc++;
+      arg = va_arg(va, char *);
+    }
+  }
+  va_end(va);
+
+  /* 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->cmd_ident = silc_client_cmd_ident(conn);
+  cmd->called = TRUE;
+  cmd->verbose = TRUE;
+
+  /*** 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->cmd_ident;
 }
 
-/* silc_client_get_client completion callback */
-void silc_client_command_completion(SilcClient client,
+/* Generic function to send any command. The arguments must be sent already
+   encoded into correct format and in correct order. */
+
+SilcUInt16 silc_client_command_send(SilcClient client,
                                    SilcClientConnection conn,
-                                   SilcClientEntry clients,
-                                   unsigned int clients_count,
-                                   void *context)
+                                   SilcCommand command,
+                                   SilcClientCommandReply reply,
+                                   void *reply_context,
+                                   SilcUInt32 argc, ...)
+{
+  SilcClientCommandContext cmd;
+  va_list ap;
+
+  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 */
+  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);
+
+  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)
+{
+  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;
+}
+
+/* Attach to a command and command identifier to receive command reply. */
+
+SilcBool silc_client_command_pending(SilcClientConnection conn,
+                                    SilcCommand command,
+                                    SilcUInt16 ident,
+                                    SilcClientCommandReply reply,
+                                    void *context)
 {
+  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;
+  SilcClient client = conn->client;
+  SilcBuffer attrs = NULL;
+  unsigned char count[4], *tmp = NULL;
+  SilcBool details = FALSE, nick = FALSE;
+  unsigned char *pubkey = NULL;
+  int i;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
+  /* Given without arguments fetches client's own information */
+  if (cmd->argc < 2) {
+    silc_client_command_send(conn->client, conn, SILC_COMMAND_WHOIS,
+                            NULL, NULL, 1, 4,
+                            silc_buffer_datalen(conn->internal->local_idp));
     goto out;
   }
 
-  if (cmd->argc < 2 || cmd->argc > 3) {
-    cmd->client->ops->say(cmd->client, conn, 
-            "Usage: /WHOIS <nickname>[@<server>] [<count>]");
-    COMMAND_ERROR;
-    goto out;
+  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;
+      }
+    }
   }
 
-  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--;
+  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);
+    }
+  }
+
+  if (pubkey) {
+    SilcAttributeObjPk obj;
+    SilcPublicKey pk;
+
+    if (!silc_pkcs_load_public_key(pubkey, &pk)) {
+      SAY(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+         "Could not load public key %s, check the filename",
+         pubkey);
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      goto out;
+    }
+
+    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(pk, &obj.data_len);
+
+    attrs = silc_attribute_payload_encode(attrs,
+                                          SILC_ATTRIBUTE_USER_PUBLIC_KEY,
+                                          SILC_ATTRIBUTE_FLAG_VALID,
+                                          &obj, sizeof(obj));
+  }
+
+  /* Send command */
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL,
+                             3, 1, nick ? cmd->argv[1] : NULL,
+                             nick ? cmd->argv_lens[1] : 0,
+                             2, tmp ? tmp : NULL, tmp ? 4 : 0,
+                             3, silc_buffer_datalen(attrs));
 
   /* 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;
 }
 
+/******************************** WHOWAS ************************************/
+
 /* Command WHOWAS. This command is used to query history information about
    specific user that used to exist in the network. */
 
-SILC_CLIENT_CMD_FUNC(whowas)
+SILC_FSM_STATE(silc_client_command_whowas)
 {
-  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;
-  }
+  unsigned char count[4];
+  int c;
 
   if (cmd->argc < 2 || cmd->argc > 3) {
-    cmd->client->ops->say(cmd->client, conn, 
-            "Usage: /WHOWAS <nickname>[@<server>] [<count>]");
-    COMMAND_ERROR;
-    goto out;
+    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;
   }
 
-  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--;
+  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));
+  }
 
   /* 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 IDENTIFY. This command is used to query information about 
+/******************************** IDENTIFY **********************************/
+
+/* Command IDENTIFY. This command is used to query information about
    specific user, especially ID's. */
 
-SILC_CLIENT_CMD_FUNC(identify)
+SILC_FSM_STATE(silc_client_command_identify)
 {
-  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);
-    COMMAND_ERROR;
-    goto out;
-  }
+  if (cmd->argc < 2 || cmd->argc > 3)
+    return SILC_FSM_FINISH;
 
-  if (cmd->argc < 2 || cmd->argc > 3) {
-    cmd->client->ops->say(cmd->client, conn,
-            "Usage: /IDENTIFY <nickname>[@<server>] [<count>]");
-    COMMAND_ERROR;
-    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],
+                               4, count, sizeof(count));
   }
 
-  buffer = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
-                                      cmd->argc - 1, ++cmd->argv,
-                                      ++cmd->argv_lens, ++cmd->argv_types,
-                                      ++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);
-  cmd->argv--;
-  cmd->argv_lens--;
-  cmd->argv_types--;
-
-  /* Notify application */
-  COMMAND;
-
- out:
-  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 cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
+  if (cmd->argc < 2) {
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /NICK <nickname>");
+    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) {
-      cmd->client->ops->say(cmd->client, conn, 
-                           "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 {
-      cmd->client->ops->say(cmd->client, conn, 
-                           "Your nickname is %s", conn->nickname);
+      SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+         "Your nickname is %s", conn->local_entry->nickname);
     }
 
-    /* XXX Notify application */
-    COMMAND;
+    COMMAND(SILC_STATUS_OK);
     goto out;
   }
 
-  /* Set new nickname */
-  buffer = silc_command_payload_encode(SILC_COMMAND_NICK,
-                                      cmd->argc - 1, ++cmd->argv,
-                                      ++cmd->argv_lens, ++cmd->argv_types,
-                                      ++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);
-  cmd->argv--;
-  cmd->argv_lens--;
-  cmd->argv_types--;
-  if (conn->nickname)
-    silc_free(conn->nickname);
-  conn->nickname = strdup(cmd->argv[1]);
+  if (cmd->argv_lens[1] > 128)
+    cmd->argv_lens[1] = 128;
+
+  /* Send the NICK command */
+  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);
+
+  /** 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;
-  }
+  SilcBuffer idp = NULL;
 
   if (cmd->argc == 2) {
-    name = cmd->argv[1];
-
     /* Get the Channel ID of the channel */
-    if (silc_idcache_find_by_data_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);
 
   /* 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;
   SilcChannelEntry channel;
-  SilcBuffer buffer, idp;
+  SilcBuffer idp;
   char *name;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
   if (cmd->argc < 2 || cmd->argc > 3) {
-    cmd->client->ops->say(cmd->client, conn,
-                         "Usage: /TOPIC <channel> [<topic>]");
-    COMMAND_ERROR;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /TOPIC <channel> [<topic>]");
+    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) {
-      cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
     name = conn->current_channel->channel_name;
@@ -493,76 +876,71 @@ SILC_CLIENT_CMD_FUNC(topic)
   }
 
   if (!conn->current_channel) {
-    cmd->client->ops->say(cmd->client, conn, "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_data_one(conn->channel_cache, name, &id_cache)) {
-    cmd->client->ops->say(cmd->client, conn, "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, 0, 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, 1, 
-                                           1, idp->data, idp->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, 1,
+                               1, silc_buffer_datalen(idp));
+
   silc_buffer_free(idp);
 
   /* 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;
-  unsigned int num = 0, type = 0;
-  char *nickname = NULL, *server = NULL, *name;
+  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) {
-    cmd->client->ops->say(cmd->client, conn,
-                  "Usage: /INVITE <channel> [<nickname>[@server>]"
-                  "[+|-[<nickname>[@<server>[!<username>[@hostname>]]]]]");
-    COMMAND_ERROR;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /INVITE <channel> [<nickname>[@server>]"
+       "[+|-[<nickname>[@<server>[!<username>[@hostname>]]]]]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
 
@@ -570,10 +948,9 @@ SILC_CLIENT_CMD_FUNC(invite)
   } 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) {
-      cmd->client->ops->say(cmd->client, conn, "You are on that channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
   }
@@ -581,460 +958,461 @@ SILC_CLIENT_CMD_FUNC(invite)
   /* Parse the typed nickname. */
   if (cmd->argc == 3) {
     if (cmd->argv[2][0] != '+' && cmd->argv[2][0] != '-') {
-      if (!silc_parse_nickname(cmd->argv[2], &nickname, &server, &num)) {
-       cmd->client->ops->say(cmd->client, conn, "Bad nickname");
-       COMMAND_ERROR;
-       goto out;
-      }
-      
+      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, 
-                                           server, num, TRUE);
-      if (!client_entry) {
-       if (nickname)
-         silc_free(nickname);
-       if (server)
-         silc_free(server);
-       
-       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_destructor,
-                                   silc_client_command_invite, 
-                                   silc_client_command_dup(cmd));
-       cmd->pending = 1;
-       return;
-      }
-      
-      cmd->client->ops->say(cmd->client, conn, 
-                           "Inviting %s to channel %s", cmd->argv[2], 
-                           channel->channel_name);
+      clients = silc_client_get_clients_local(client, conn, nickname,
+                                             cmd->argv[2]);
+      if (!clients)
+       /* Resolve client information */
+       SILC_FSM_CALL(silc_client_get_clients(
+                                     client, conn, nickname,
+                                     cmd->argv[2],
+                                     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, &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(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);
 
   /* 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:
-  if (nickname)
-    silc_free(nickname);
-  if (server)
-    silc_free(server);
-  silc_client_command_free(cmd);
+  silc_free(nickname);
+  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;
+  SilcClient client = conn->client;
+
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
+
+  /* Call connection callback */
+  conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED,
+                0, NULL, conn->callback_context);
 
-  /* Close connection */
-  q->client->ops->disconnect(q->client, q->conn);
-  silc_client_close_connection(q->client, NULL, q->conn->sock->user_data);
+  /* Signal to close connection */
+  conn->internal->disconnected = TRUE;
+  SILC_FSM_SEMA_POST(&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);
+    silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 0);
 
-  q = silc_calloc(1, sizeof(*q));
-  q->client = cmd->client;
-  q->conn = cmd->conn;
+  /* Sleep for a while */
+  sleep(1);
 
-  /* We quit the connection with little timeout */
-  silc_task_register(cmd->client->timeout_queue, 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);
-}
 
 /* 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;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer, idp;
+  SilcClient client = conn->client;
+  SilcBuffer idp, auth = NULL;
   SilcClientEntry target;
-  unsigned int num = 0;
-  char *nickname = NULL, *server = NULL;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcDList clients;
+  char *nickname = NULL, *comment = NULL;
 
   if (cmd->argc < 2) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "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 (!silc_parse_nickname(cmd->argv[1], &nickname, &server, &num)) {
-    cmd->client->ops->say(cmd->client, conn, "Bad nickname");
-    COMMAND_ERROR;
-    goto out;
-  }
+  if (client->internal->params->nickname_parse)
+    client->internal->params->nickname_parse(cmd->argv[1], &nickname);
+  else
+    nickname = strdup(cmd->argv[1]);
+  if (!nickname)
+    return SILC_FSM_FINISH;
 
   /* Get the target client */
-  target = silc_idlist_get_client(cmd->client, conn, nickname, 
-                                 server, num, TRUE);
-  if (!target) {
-    silc_free(nickname);
-    if (server)
-      silc_free(server);
-
-    if (cmd->pending) {
-      COMMAND_ERROR;
-      goto out;
+  clients = silc_client_get_clients_local(client, conn, nickname,
+                                         cmd->argv[1]);
+  if (!clients)
+    /* Resolve client information */
+    SILC_FSM_CALL(silc_client_get_clients(client, conn, nickname,
+                                         cmd->argv[1],
+                                         silc_client_command_resolve_continue,
+                                         cmd));
+
+  target = silc_dlist_get(clients);
+
+  if (cmd->argc >= 3) {
+    if (strcasecmp(cmd->argv[2], "-pubkey"))
+      comment = cmd->argv[2];
+
+    if (!strcasecmp(cmd->argv[2], "-pubkey") ||
+       (cmd->argc >= 4 && !strcasecmp(cmd->argv[3], "-pubkey"))) {
+      /* Encode the public key authentication payload */
+      auth = silc_auth_public_key_auth_generate(conn->public_key,
+                                               conn->private_key,
+                                               conn->client->rng,
+                                               conn->internal->sha1hash,
+                                               &target->id, SILC_ID_CLIENT);
     }
-
-    /* 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_destructor,
-                               silc_client_command_kill, 
-                               silc_client_command_dup(cmd));
-    cmd->pending = 1;
-    return;
-  }
+  }
 
   /* Send the KILL command to the server */
-  idp = silc_id_payload_encode(target->id, SILC_ID_CLIENT);
-  if (cmd->argc == 2)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_KILL, 0, 1, 
-                                           1, idp->data, idp->len);
-  else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_KILL, 0, 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);
+  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));
   silc_buffer_free(idp);
+  silc_buffer_free(auth);
+  silc_free(nickname);
+  silc_client_list_free(client, conn, clients);
 
   /* Notify application */
-  COMMAND;
+  COMMAND(SILC_STATUS_OK);
 
- out:
-  if (nickname)
-    silc_free(nickname);
-  if (server)
-    silc_free(server);
-  silc_client_command_free(cmd);
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 }
 
+/********************************** INFO ************************************/
+
 /* Command INFO. Request information about specific server. If specific
    server is not provided the current server is used. */
 
-SILC_CLIENT_CMD_FUNC(info)
+SILC_FSM_STATE(silc_client_command_info)
 {
-  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;
-  }
 
-  if (cmd->argc < 2)
-    name = strdup(conn->remote_host);
+  /* Send the command */
+  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
-    name = strdup(cmd->argv[1]);
+    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;
+}
+
+/********************************** STATS ***********************************/
+
+/* Command STATS. Shows server and network statistics. */
+
+SILC_FSM_STATE(silc_client_command_stats)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+
+  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_INFO, 0, 1, 
-                                         1, 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,
+                             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 ************************************/
+
+/* Command PING. Sends ping to server. */
 
-SILC_CLIENT_CMD_FUNC(ping)
+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;
-  char *name = NULL;
 
-  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;
   }
 
-  if (cmd->argc == 1 || !strcmp(cmd->argv[1], conn->remote_host))
-    name = strdup(conn->remote_host);
-
   /* Send the command */
-  buffer = silc_command_payload_encode_va(SILC_COMMAND_PING, 0, 1, 
-                                         1, conn->remote_id_data, 
-                                         SILC_ID_SERVER_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(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 = name;
-      conn->ping_count++;
-      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 = name;
-    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;
 }
 
-SILC_CLIENT_CMD_FUNC(notice)
-{
-}
+/********************************** JOIN ************************************/
 
 /* Command JOIN. Joins to a channel. */
 
-SILC_CLIENT_CMD_FUNC(join)
+SILC_FSM_STATE(silc_client_command_join)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcIDCacheEntry id_cache = NULL;
-  SilcBuffer buffer, idp;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcChannelEntry channel;
+  SilcBuffer auth = NULL, cauth = NULL;
+  char *name, *passphrase = NULL, *pu8, *cipher = NULL, *hmac = NULL;
+  int i, passphrase_len = 0;
 
   if (cmd->argc < 2) {
-    /* Show channels currently joined to */
-
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   /* See if we have joined to the requested channel already */
-  if (silc_idcache_find_by_data_one(conn->channel_cache, cmd->argv[1],
-                                   &id_cache)) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "You are talking to channel %s", cmd->argv[1]);
-    conn->current_channel = (SilcChannelEntry)id_cache->context;
-#if 0
-    cmd->client->screen->bottom_line->channel = cmd->argv[1];
-    silc_screen_print_bottom_line(cmd->client->screen, 0);
-#endif
-    goto out;
-  }
+  channel = silc_client_get_channel(conn->client, conn, cmd->argv[1]);
+  if (channel && silc_client_on_channel(channel, conn->local_entry))
+    goto out;
+
+  if (cmd->argv_lens[1] > 256)
+    cmd->argv_lens[1] = 256;
+
+  name = cmd->argv[1];
+
+  for (i = 2; i < cmd->argc; i++) {
+    if (!strcasecmp(cmd->argv[i], "-cipher") && cmd->argc > i + 1) {
+      cipher = cmd->argv[++i];
+    } else if (!strcasecmp(cmd->argv[i], "-hmac") && cmd->argc > i + 1) {
+      hmac = cmd->argv[++i];
+    } else if (!strcasecmp(cmd->argv[i], "-founder")) {
+      auth = silc_auth_public_key_auth_generate(conn->public_key,
+                                               conn->private_key,
+                                               conn->client->rng,
+                                               conn->internal->sha1hash,
+                                               conn->local_id,
+                                               SILC_ID_CLIENT);
+    } 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;
+      SilcUInt32 pk_len;
+
+      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_ERROR,
+             "Could not load key pair, check your arguments");
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+         goto out;
+       }
+       i += 2;
+      }
 
-  idp = silc_id_payload_encode(conn->local_id, SILC_ID_CLIENT);
+      pk = silc_pkcs_public_key_encode(pubkey, &pk_len);
+      silc_hash_make(conn->internal->sha1hash, pk, pk_len, pkhash);
+      silc_free(pk);
+      pubdata = silc_rng_get_rn_data(conn->client->rng, 128);
+      memcpy(pubdata, pkhash, 20);
+      cauth = silc_auth_public_key_auth_generate_wpub(pubkey, privkey,
+                                                     pubdata, 128,
+                                                     conn->internal->sha1hash,
+                                                     conn->local_id,
+                                                     SILC_ID_CLIENT);
+      memset(pubdata, 0, 128);
+      silc_free(pubdata);
+    } else {
+      /* 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 */
-  if (cmd->argc == 2)
-    buffer = 
-      silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 2,
-                                    1, cmd->argv[1], cmd->argv_lens[1],
-                                    2, idp->data, idp->len);
-  else if (cmd->argc == 3)
-    /* XXX Buggy */
-    buffer = 
-      silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 3,
-                                    1, cmd->argv[1], cmd->argv_lens[1],
-                                    2, idp->data, idp->len,
-                                    3, cmd->argv[2], cmd->argv_lens[2]);
-  else
-    buffer = 
-      silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 4,
-                                    1, cmd->argv[1], cmd->argv_lens[1],
-                                    2, idp->data, idp->len,
-                                    3, cmd->argv[2], cmd->argv_lens[2],
-                                    4, cmd->argv[3], cmd->argv_lens[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_buffer_free(idp);
+  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));
+
+  silc_buffer_free(auth);
+  silc_buffer_free(cauth);
+  if (passphrase)
+    memset(passphrase, 0, strlen(passphrase));
+  silc_free(passphrase);
 
   /* 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;
 }
 
+/********************************** 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) {
-    cmd->client->ops->say(cmd->client, conn,
-                         "Usage: /MOTD [<server>]");
-    COMMAND_ERROR;
-    goto out;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /MOTD [<server>]");
+    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];
-  unsigned int mode, add, len;
+  SilcUInt32 mode, add, len;
   int i;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
   if (cmd->argc < 2) {
-    cmd->client->ops->say(cmd->client, conn, 
-                 "Usage: /UMODE +|-<modes>");
-    COMMAND_ERROR;
-    goto out;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /UMODE +|-<modes>");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
   mode = conn->local_entry->mode;
@@ -1055,6 +1433,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;
       }
@@ -1077,65 +1463,104 @@ 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 ***********************************/
+
 /* 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;
+  SilcClient client = conn->client;
   SilcChannelEntry channel;
-  SilcBuffer buffer, chidp, auth = NULL;
+  SilcBuffer chidp, auth = NULL, pk = NULL;
   unsigned char *name, *cp, modebuf[4], tmp[4], *arg = NULL;
-  unsigned int mode, add, type, len, arg_len = 0;
+  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) {
-    cmd->client->ops->say(cmd->client, conn, 
-                 "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
-    COMMAND_ERROR;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
 
@@ -1143,10 +1568,9 @@ SILC_CLIENT_CMD_FUNC(cmode)
   } 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) {
-      cmd->client->ops->say(cmd->client, conn, "You are on that channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
   }
@@ -1197,11 +1621,29 @@ SILC_CLIENT_CMD_FUNC(cmode)
       else
        mode &= ~SILC_CHANNEL_MODE_TOPIC;
       break;
+    case 'm':
+      if (add)
+       mode |= SILC_CHANNEL_MODE_SILENCE_USERS;
+      else
+       mode &= ~SILC_CHANNEL_MODE_SILENCE_USERS;
+      break;
+    case 'M':
+      if (add)
+       mode |= SILC_CHANNEL_MODE_SILENCE_OPERS;
+      else
+       mode &= ~SILC_CHANNEL_MODE_SILENCE_OPERS;
+      break;
     case 'l':
       if (add) {
        int ll;
        mode |= SILC_CHANNEL_MODE_ULIMIT;
        type = 3;
+       if (cmd->argc < 4) {
+         SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+             "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+         goto out;
+       }
        ll = atoi(cmd->argv[3]);
        SILC_PUT32_MSB(ll, tmp);
        arg = tmp;
@@ -1214,6 +1656,12 @@ SILC_CLIENT_CMD_FUNC(cmode)
       if (add) {
        mode |= SILC_CHANNEL_MODE_PASSPHRASE;
        type = 4;
+       if (cmd->argc < 4) {
+         SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+             "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+         goto out;
+       }
        arg = cmd->argv[3];
        arg_len = cmd->argv_lens[3];
       } else {
@@ -1224,6 +1672,12 @@ SILC_CLIENT_CMD_FUNC(cmode)
       if (add) {
        mode |= SILC_CHANNEL_MODE_CIPHER;
        type = 5;
+       if (cmd->argc < 4) {
+         SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+             "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+         goto out;
+       }
        arg = cmd->argv[3];
        arg_len = cmd->argv_lens[3];
       } else {
@@ -1234,6 +1688,12 @@ SILC_CLIENT_CMD_FUNC(cmode)
       if (add) {
        mode |= SILC_CHANNEL_MODE_HMAC;
        type = 6;
+       if (cmd->argc < 4) {
+         SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+             "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
+         COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+         goto out;
+       }
        arg = cmd->argv[3];
        arg_len = cmd->argv_lens[3];
       } else {
@@ -1242,103 +1702,163 @@ SILC_CLIENT_CMD_FUNC(cmode)
       break;
     case 'f':
       if (add) {
+       SilcPublicKey pubkey = conn->public_key;
+       SilcPrivateKey privkey = conn->private_key;
+
        mode |= SILC_CHANNEL_MODE_FOUNDER_AUTH;
        type = 7;
 
-       if (!strcasecmp(cmd->argv[3], "-pubkey")) {
-         auth = silc_auth_public_key_auth_generate(cmd->client->public_key,
-                                                   cmd->client->private_key,
-                                                   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 >= 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_ERROR,
+               "Could not load key pair, check your arguments");
+           COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+           goto out;
+         }
        }
 
-       arg = auth->data;
-       arg_len = auth->len;
+       pk = silc_public_key_payload_encode(pubkey);
+       auth = silc_auth_public_key_auth_generate(pubkey, privkey,
+                                                 conn->client->rng,
+                                                 conn->internal->sha1hash,
+                                                 conn->local_id,
+                                                 SILC_ID_CLIENT);
+       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);
+
+         /* Notify application */
+         COMMAND(SILC_STATUS_OK);
+         goto out;
+       }
+
+       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, &chpk)) {
+           SAY(conn->client, conn, SILC_CLIENT_MESSAGE_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(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 = silc_buffer_data(auth);
+       arg_len = silc_buffer_len(auth);
+      } else {
+       mode &= ~SILC_CHANNEL_MODE_CHANNEL_AUTH;
+      }
+      break;
     default:
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_UNKNOWN_MODE);
       goto out;
       break;
     }
   }
 
-  if (type && cmd->argc < 3) {
-    COMMAND_ERROR;
-    goto out;
-  }
-
-  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);
 
   /* 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;
 }
 
+/********************************* CUMODE ***********************************/
+
 /* 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;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
+  SilcClient client = conn->client;
   SilcChannelEntry channel;
   SilcChannelUser chu;
   SilcClientEntry client_entry;
-  SilcBuffer buffer, clidp, chidp, auth = NULL;
+  SilcBuffer clidp, chidp, auth = NULL;
+  SilcDList clients = NULL;
   unsigned char *name, *cp, modebuf[4];
-  unsigned int mode = 0, add, len;
-  char *nickname = NULL, *server = NULL;
-  unsigned int num = 0;
+  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) {
-    cmd->client->ops->say(cmd->client, conn, 
-                 "Usage: /CUMODE <channel> +|-<modes> <nickname>[@<server>]");
-    COMMAND_ERROR;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /CUMODE <channel> +|-<modes> <nickname>[@<server>]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
 
@@ -1346,47 +1866,34 @@ SILC_CLIENT_CMD_FUNC(cumode)
   } 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) {
-      cmd->client->ops->say(cmd->client, conn, "You are on that channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
   }
 
   /* Parse the typed nickname. */
-  if (!silc_parse_nickname(cmd->argv[3], &nickname, &server, &num)) {
-    cmd->client->ops->say(cmd->client, conn, "Bad nickname");
-    COMMAND_ERROR;
-    goto out;
-  }
+  if (client->internal->params->nickname_parse)
+    client->internal->params->nickname_parse(cmd->argv[3], &nickname);
+  else
+    nickname = strdup(cmd->argv[3]);
 
   /* Find client entry */
-  client_entry = silc_idlist_get_client(cmd->client, conn, 
-                                       nickname, server, num, TRUE);
-  if (!client_entry) {
-    if (cmd->pending) {
-      COMMAND_ERROR;
-      goto out;
-    }
+  clients = silc_client_get_clients_local(client, conn, nickname,
+                                         cmd->argv[3]);
+  if (!clients)
+    /* Resolve client information */
+    SILC_FSM_CALL(silc_client_get_clients(client, conn, nickname, cmd->argv[3],
+                                         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_destructor,
-                               silc_client_command_cumode, 
-                               silc_client_command_dup(cmd));
-    cmd->pending = 1;
-    return;
-  }
-  
-  while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
-    if (chu->client == client_entry) {
-      chu->mode = mode;
-      break;
-    }
-  }
+  client_entry = silc_dlist_get(clients);
+
+  /* Get the current mode */
+  chu = silc_client_on_channel(channel, client_entry);
+  if (chu)
+    mode = chu->mode;
 
   /* Are we adding or removing mode */
   if (cmd->argv[2][0] == '-')
@@ -1403,24 +1910,36 @@ 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,
-                                                   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]);
+       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_ERROR,
+               "Could not load key pair, check your arguments");
+           COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+           goto out;
          }
        }
+
+       auth = silc_auth_public_key_auth_generate(pubkey, privkey,
+                                                 conn->client->rng,
+                                                 conn->internal->sha1hash,
+                                                 conn->local_id,
+                                                 SILC_ID_CLIENT);
        mode |= SILC_CHANNEL_UMODE_CHANFO;
       } else {
        mode &= ~SILC_CHANNEL_UMODE_CHANFO;
@@ -1432,76 +1951,95 @@ 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, 4, 
-                                         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);
+
   /* 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:
-  if (nickname)
-    silc_free(nickname);
-  if (server)
-    silc_free(server);
-  silc_client_command_free(cmd);
+  silc_client_list_free(client, conn, clients);
+  silc_free(nickname);
+  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;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcIDCacheEntry id_cache = NULL;
+  SilcClient client = conn->client;
   SilcChannelEntry channel;
-  SilcBuffer buffer, idp, idp2;
+  SilcBuffer idp, idp2;
   SilcClientEntry target;
+  SilcDList clients = NULL;
   char *name;
-  unsigned int num = 0;
-  char *nickname = NULL, *server = NULL;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  char *nickname = NULL;
 
   if (cmd->argc < 3) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Usage: /KICK <channel> <nickname> [<comment>]");
-    COMMAND_ERROR;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /KICK <channel> <nickname> [<comment>]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
     name = conn->current_channel->channel_name;
@@ -1510,363 +2048,395 @@ SILC_CLIENT_CMD_FUNC(kick)
   }
 
   if (!conn->current_channel) {
-    cmd->client->ops->say(cmd->client, conn, "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_data_one(conn->channel_cache, name, &id_cache)) {
-    cmd->client->ops->say(cmd->client, conn, "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 (!silc_parse_nickname(cmd->argv[2], &nickname, &server, &num)) {
-    cmd->client->ops->say(cmd->client, conn, "Bad nickname");
-    COMMAND_ERROR;
-    goto out;
-  }
+  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, 
-                                 server, num, FALSE);
-  if (!target) {
-    cmd->client->ops->say(cmd->client, conn, "No such client: %s",
-                         cmd->argv[2]);
-    COMMAND_ERROR;
+  clients = silc_client_get_clients_local(client, conn, nickname,
+                                         cmd->argv[2]);
+  if (!clients) {
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "No such client: %s", cmd->argv[2]);
+    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_free(nickname);
+  silc_client_list_free(client, conn, clients);
 
   /* 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:
-  if (nickname)
-    silc_free(nickname);
-  if (server)
-    silc_free(server);
-  silc_client_command_free(cmd);
+  silc_free(nickname);
+  return SILC_FSM_FINISH;
 }
 
-/* OPER command. Used to obtain server operator privileges. */
+/***************************** OPER & SILCOPER ******************************/
+
+typedef struct {
+  unsigned char *passphrase;
+  SilcUInt32 passphrase_len;
+} *SilcClientCommandOper;
 
-SILC_CLIENT_CMD_FUNC(oper)
+/* Ask passphrase callback */
+
+static void silc_client_command_oper_cb(unsigned char *data,
+                                       SilcUInt32 data_len, void *context)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-  unsigned char *auth_data;
-  SilcBuffer auth;
+  SilcClientCommandContext cmd = context;
+  SilcClientCommandOper oper = cmd->context;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  if (data && data_len)
+    oper->passphrase = silc_memdup(data, data_len);
+  oper->passphrase_len = data_len;
 
-  if (cmd->argc < 2) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Usage: /OPER <username> [<public key>]");
-    COMMAND_ERROR;
-    goto out;
-  }
+  /* Continue */
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
+}
 
-  if (cmd->argc == 3) {
-    /* XXX Get public key */
-    auth_data = NULL;
-    COMMAND_ERROR;
-    goto out;
-  } else {
-    /* Get passphrase */
+/* Send OPER/SILCOPER command */
 
-    auth_data = cmd->client->ops->ask_passphrase(cmd->client, conn);
-    if (!auth_data) {
-      COMMAND_ERROR;
-      goto out;
-    }
+SILC_FSM_STATE(silc_client_command_oper_send)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcClientCommandOper oper = cmd->context;
+  SilcBuffer auth;
 
-    /* Encode the authentication payload */
+  if (!oper || !oper->passphrase) {
+    /* Encode the public key authentication payload */
+    auth = silc_auth_public_key_auth_generate(conn->public_key,
+                                             conn->private_key,
+                                             conn->client->rng,
+                                             conn->internal->hash,
+                                             conn->local_id,
+                                             SILC_ID_CLIENT);
+  } else {
+    /* Encode the password authentication payload */
     auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0,
-                                   auth_data, strlen(auth_data));
+                                   oper->passphrase, oper->passphrase_len);
   }
 
-  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);
+  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));
 
-  silc_buffer_free(buffer);
+  silc_buffer_clear(auth);
   silc_buffer_free(auth);
-  memset(auth_data, 0, strlen(auth_data));
-  silc_free(auth_data);
+  if (oper) {
+    silc_free(oper->passphrase);
+    silc_free(oper);
+  }
 
   /* 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;
 }
 
-/* SILCOPER command. Used to obtain router operator privileges. */
+/* OPER command. Used to obtain server operator privileges. */
 
-SILC_CLIENT_CMD_FUNC(silcoper)
+SILC_FSM_STATE(silc_client_command_oper)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-  unsigned char *auth_data;
-  SilcBuffer auth;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  SilcClientCommandOper oper;
 
   if (cmd->argc < 2) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Usage: /SILCOPER <username> [<public key>]");
-    COMMAND_ERROR;
-    goto out;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /OPER <username> [-pubkey]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
   }
 
-  if (cmd->argc == 3) {
-    /* XXX Get public key */
-    auth_data = NULL;
-    COMMAND_ERROR;
-    goto out;
-  } else {
-    /* Get passphrase */
+  /* 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));
+  }
 
-    auth_data = cmd->client->ops->ask_passphrase(cmd->client, conn);
-    if (!auth_data) {
-      COMMAND_ERROR;
-      goto out;
-    }
+  silc_fsm_next(fsm, silc_client_command_oper_send);
+  return SILC_FSM_CONTINUE;
+}
 
-    /* Encode the authentication payload */
-    auth = silc_auth_payload_encode(SILC_AUTH_PASSWORD, NULL, 0,
-                                   auth_data, strlen(auth_data));
-  }
+/* SILCOPER command. Used to obtain router operator privileges. */
 
-  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_STATE(silc_client_command_silcoper)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcClientCommandOper oper;
 
-  silc_buffer_free(buffer);
-  silc_buffer_free(auth);
-  memset(auth_data, 0, strlen(auth_data));
-  silc_free(auth_data);
+  if (cmd->argc < 2) {
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /SILCOPER <username> [-pubkey]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
+  }
 
-  /* Notify application */
-  COMMAND;
+  /* 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));
+  }
 
- out:
-  silc_client_command_free(cmd);
+  silc_fsm_next(fsm, silc_client_command_oper_send);
+  return SILC_FSM_CONTINUE;
 }
 
-/* CONNECT command. Connects the server to another server. */
+/*********************************** BAN ************************************/
 
-SILC_CLIENT_CMD_FUNC(connect)
+/* Command BAN. This is used to manage the ban list of the channel. */
+
+SILC_FSM_STATE(silc_client_command_ban)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-  unsigned char port[4];
-  unsigned int tmp;
+  SilcChannelEntry channel;
+  SilcBuffer chidp, args = NULL;
+  char *name, *ban = NULL;
+  unsigned char action[1];
+  SilcPublicKey pubkey = NULL;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
+  if (cmd->argc < 2) {
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /BAN <channel> "
+       "[+|-[<nickname>[@<server>[!<username>[@hostname>]]]]]");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
-  if (cmd->argc < 2) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Usage: /CONNECT <server> [<port>]");
-    COMMAND_ERROR;
-    goto out;
+  if (cmd->argv[1][0] == '*') {
+    if (!conn->current_channel) {
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
+      goto out;
+    }
+
+    channel = conn->current_channel;
+  } else {
+    name = cmd->argv[1];
+
+    channel = silc_client_get_channel(conn->client, conn, name);
+    if (!channel) {
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
+      goto out;
+    }
   }
 
   if (cmd->argc == 3) {
-    tmp = atoi(cmd->argv[2]);
-    SILC_PUT32_MSB(tmp, port);
+    if (cmd->argv[2][0] == '+')
+      action[0] = 0x00;
+    else
+      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, &pubkey);
+    ban = cmd->argv[2];
+    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(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);
+    }
   }
 
-  if (cmd->argc == 3)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_CONNECT, 0, 2, 
-                                           1, cmd->argv[1], 
-                                           strlen(cmd->argv[1]),
-                                           2, port, 4);
-  else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_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);
+  chidp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
+
+  /* Send the command */
+  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_buffer_free(chidp);
+  silc_buffer_free(args);
 
   /* 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;
 }
 
-/* RESTART command. Restarts the server. You must be server operator
-   to be able to use this command. */
+/********************************* DETACH ***********************************/
 
-SILC_CLIENT_CMD_FUNC(restart)
-{
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcBuffer buffer;
+/* Command DETACH. This is used to detach from the server */
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+SILC_FSM_STATE(silc_client_command_detach)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
 
-  buffer = silc_command_payload_encode(SILC_COMMAND_RESTART, 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);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 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;
 }
 
-/* CLOSE command. Close server connection to the remote server */
-SILC_CLIENT_CMD_FUNC(close)
+/********************************** WATCH ***********************************/
+
+/* Command WATCH. */
+
+SILC_FSM_STATE(silc_client_command_watch)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcBuffer buffer;
-  unsigned char port[4];
-  unsigned int tmp;
+  SilcBuffer args = NULL;
+  int type = 0;
+  const char *pubkey = NULL;
+  SilcBool pubkey_add = TRUE;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
+  if (cmd->argc < 3) {
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
-  if (cmd->argc < 2) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Usage: /CLOSE <server> [<port>]");
-    COMMAND_ERROR;
+  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 (cmd->argc == 3) {
-    tmp = atoi(cmd->argv[2]);
-    SILC_PUT32_MSB(tmp, port);
-  }
+  if (pubkey) {
+    SilcPublicKey pk;
+    SilcBuffer buffer;
 
-  if (cmd->argc == 3)
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_CLOSE, 0, 2, 
-                                           1, cmd->argv[1], 
-                                           strlen(cmd->argv[1]),
-                                           2, port, 4);
-  else
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_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);
-  silc_buffer_free(buffer);
-
-  /* Notify application */
-  COMMAND;
-
- out:
-  silc_client_command_free(cmd);
-}
-/* SHUTDOWN command. Shutdowns the server. */
-
-SILC_CLIENT_CMD_FUNC(shutdown)
-{
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+    if (!silc_pkcs_load_public_key(pubkey, &pk)) {
+      SAY(conn->client, conn, SILC_CLIENT_MESSAGE_ERROR,
+         "Could not load public key %s, check the filename", pubkey);
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      goto out;
+    }
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    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(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);
   }
 
-  /* Send the command */
-  silc_client_send_command(cmd->client, cmd->conn, 
-                          SILC_COMMAND_SHUTDOWN, 0, 0);
+  /* 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;
+  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 ***********************************/
+
 /* LEAVE command. Leaves a channel. Client removes itself from a channel. */
 
-SILC_CLIENT_CMD_FUNC(leave)
+SILC_FSM_STATE(silc_client_command_leave)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
+  SilcClientCommandContext cmd = fsm_context;
   SilcClientConnection conn = cmd->conn;
-  SilcIDCacheEntry id_cache = NULL;
   SilcChannelEntry channel;
-  SilcBuffer buffer, idp;
+  SilcBuffer idp;
   char *name;
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
-
   if (cmd->argc != 2) {
-    cmd->client->ops->say(cmd->client, conn, "Usage: /LEAVE <channel>");
-    COMMAND_ERROR;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /LEAVE <channel>");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
     name = conn->current_channel->channel_name;
@@ -1874,78 +2444,56 @@ SILC_CLIENT_CMD_FUNC(leave)
     name = cmd->argv[1];
   }
 
-  if (!conn->current_channel) {
-    cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
-    COMMAND_ERROR;
-    goto out;
-  }
-
-  /* Get the Channel ID of the channel */
-  if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
-    cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
-    COMMAND_ERROR;
+  /* Get the channel entry */
+  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 LEAVE command to the server */
-  idp = silc_id_payload_encode(id_cache->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_buffer_free(idp);
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                             1, silc_buffer_datalen(idp));
 
-  /* We won't talk anymore on this channel */
-  cmd->client->ops->say(cmd->client, conn, "You have left channel %s", name);
+  silc_buffer_free(idp);
 
-  conn->current_channel = NULL;
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
 
-  silc_idcache_del_by_id(conn->channel_cache, SILC_ID_CHANNEL, channel->id);
-  silc_free(channel->channel_name);
-  silc_free(channel->id);
-  silc_free(channel->key);
-  silc_cipher_free(channel->channel_key);
-  silc_free(channel);
+  if (conn->current_channel == channel)
+    conn->current_channel = NULL;
 
-  /* Notify application */
-  COMMAND;
+  /** 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;
-  SilcIDCacheEntry id_cache = NULL;
-  SilcChannelEntry channel;
-  SilcBuffer buffer, idp;
-  char *name, *line = NULL;
-  unsigned int line_len = 0;
-
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+  char *name;
 
   if (cmd->argc != 2) {
-    cmd->client->ops->say(cmd->client, conn, "Usage: /USERS <channel>");
-    COMMAND_ERROR;
+    SAY(conn->client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Usage: /USERS <channel>");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
   if (cmd->argv[1][0] == '*') {
     if (!conn->current_channel) {
-      cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
-      COMMAND_ERROR;
+      COMMAND_ERROR(SILC_STATUS_ERR_NOT_ON_CHANNEL);
       goto out;
     }
     name = conn->current_channel->channel_name;
@@ -1953,193 +2501,292 @@ SILC_CLIENT_CMD_FUNC(users)
     name = cmd->argv[1];
   }
 
-  if (!conn->current_channel) {
-    cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
-    COMMAND_ERROR;
-    goto out;
-  }
+  /* Send USERS command to the server */
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                             2, name, strlen(name));
 
-  /* Get the Channel ID of the channel */
-  if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
-    /* XXX should resolve the channel ID; LIST command */
-    cmd->client->ops->say(cmd->client, conn, 
-                         "You are not on that channel", name);
-    COMMAND_ERROR;
-    goto out;
-  }
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
 
-  channel = (SilcChannelEntry)id_cache->context;
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
 
-  if (!cmd->pending) {
-    /* Send USERS command to the server */
-    idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
-    buffer = silc_command_payload_encode_va(SILC_COMMAND_USERS, 
-                                           ++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_buffer_free(idp);
-
-    /* Register pending callback which will recall this command callback with
-       same context and reprocesses the command. When reprocessing we actually
-       display the information on the screen. */
-    silc_client_command_pending(conn, SILC_COMMAND_USERS, conn->cmd_ident, 
-                               silc_client_command_destructor,
-                               silc_client_command_users, 
-                               silc_client_command_dup(cmd));
-    cmd->pending = TRUE;
-    return;
-  }
+ out:
+  return SILC_FSM_FINISH;
+}
 
-  if (cmd->pending) {
-    /* Pending command. Now we've resolved the information from server and
-       we are ready to display the information on screen. */
-    int i;
-    SilcChannelUser chu;
+/********************************* GETKEY ***********************************/
 
-    cmd->client->ops->say(cmd->client, conn, "Users on %s", 
-                         channel->channel_name);
+/* Command GETKEY. Used to fetch remote client's public key. */
 
-    line = silc_calloc(4096, sizeof(*line));
-    line_len = 4096;
-    silc_list_start(channel->clients);
-    while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
-      SilcClientEntry e = chu->client;
-      char *m, tmp[80], len1;
+SILC_FSM_STATE(silc_client_command_getkey)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcClient client = conn->client;
+  SilcClientEntry client_entry;
+  SilcServerEntry server_entry;
+  SilcDList clients;
+  char *nickname = NULL;
+  SilcBuffer idp;
 
-      memset(line, 0, sizeof(line_len));
+  if (cmd->argc < 2) {
+    client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_INFO,
+                    "Usage: /GETKEY <nickname or server name>");
+    COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    return SILC_FSM_FINISH;
+  }
 
-      if (strlen(e->nickname) + strlen(e->server) + 100 > line_len) {
-       silc_free(line);
-       line_len += strlen(e->nickname) + strlen(e->server) + 100;
-       line = silc_calloc(line_len, sizeof(*line));
-      }
+  /* 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 (!nickname) {
+    COMMAND_ERROR(SILC_STATUS_ERR_RESOURCE_LIMIT);
+    return SILC_FSM_FINISH;
+  }
 
-      memset(tmp, 0, sizeof(tmp));
-      m = silc_client_chumode_char(chu->mode);
+  /* Find client entry */
+  clients = silc_client_get_clients_local(client, conn, nickname,
+                                         cmd->argv[1]);
+  if (!clients) {
+    /* Check whether user requested server */
+    server_entry = silc_client_get_server(client, conn, cmd->argv[1]);
+    if (!server_entry) {
+      /* No client or server exist with this name, query for both. */
+      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])));
+    }
+    idp = silc_id_payload_encode(&server_entry->id, SILC_ID_SERVER);
+  } else {
+    client_entry = silc_dlist_get(clients);
+    idp = silc_id_payload_encode(&client_entry->id, SILC_ID_CLIENT);
+    silc_client_list_free(client, conn, clients);
+  }
 
-      strncat(line, " ", 1);
-      strncat(line, e->nickname, strlen(e->nickname));
-      strncat(line, e->server ? "@" : "", 1);
+  /* Send the commmand */
+  silc_client_command_send_va(conn, cmd, cmd->cmd, NULL, NULL, 1,
+                             1, silc_buffer_datalen(idp));
 
-      len1 = 0;
-      if (e->server)
-       len1 = strlen(e->server);
-      strncat(line, e->server ? e->server : "", len1 > 30 ? 30 : len1);
+  silc_buffer_free(idp);
+  silc_free(nickname);
 
-      len1 = strlen(line);
-      if (len1 >= 30) {
-       memset(&line[29], 0, len1 - 29);
-      } else {
-       for (i = 0; i < 30 - len1 - 1; i++)
-         strcat(line, " ");
-      }
+  /* Notify application */
+  COMMAND(SILC_STATUS_OK);
 
-      if (e->mode & SILC_UMODE_GONE)
-       strcat(line, "  G");
-      else
-       strcat(line, "  H");
-      strcat(tmp, m ? m : "");
-      strncat(line, tmp, strlen(tmp));
+  /** Wait for command reply */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
+}
 
-      if (strlen(tmp) < 5)
-       for (i = 0; i < 5 - strlen(tmp); i++)
-         strcat(line, " ");
+/********************************* SERVICE **********************************/
 
-      strcat(line, e->username ? e->username : "");
+/* Command SERVICE.  Negotiates service agreement with server. */
+/* XXX incomplete */
 
-      cmd->client->ops->say(cmd->client, conn, "%s", line);
+SILC_FSM_STATE(silc_client_command_service)
+{
+  SilcClientCommandContext cmd = fsm_context;
+#if 0
+  SilcClientConnection conn = cmd->conn;
+  SilcBuffer buffer;
+  char *name;
 
-      if (m)
-       silc_free(m);
-    }
+  if (cmd->argc < 2) {
+    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 (line)
-    silc_free(line);
+  name = cmd->argv[1];
+
+  /* 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;
+  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 BAN. This is used to manage the ban list of the channel. */
+/* Register all default commands provided by the client library for the
+   application. */
 
-SILC_CLIENT_CMD_FUNC(ban)
+void silc_client_commands_register(SilcClient client)
 {
-  SilcClientCommandContext cmd = (SilcClientCommandContext)context;
-  SilcClientConnection conn = cmd->conn;
-  SilcChannelEntry channel;
-  SilcBuffer buffer, chidp;
-  int type = 0;
-  char *name, *ban = NULL;
+  silc_list_init(client->internal->commands, struct SilcClientCommandStruct,
+                next);
+
+  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);
+  SILC_CLIENT_CMD(list, LIST, "LIST", 2);
+  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", 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", 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(service, SERVICE, "SERVICE", 10);
+}
 
-  if (!cmd->conn) {
-    SILC_NOT_CONNECTED(cmd->client, cmd->conn);
-    COMMAND_ERROR;
-    goto out;
-  }
+/* Unregister all commands. */
 
-  if (cmd->argc < 2) {
-    cmd->client->ops->say(cmd->client, conn, 
-                  "Usage: /BAN <channel> "
-                  "[+|-[<nickname>[@<server>[!<username>[@hostname>]]]]]");
-    COMMAND_ERROR;
-    goto out;
-  }
+void silc_client_commands_unregister(SilcClient client)
+{
+  SILC_CLIENT_CMDU(whois, WHOIS, "WHOIS");
+  SILC_CLIENT_CMDU(whowas, WHOWAS, "WHOWAS");
+  SILC_CLIENT_CMDU(identify, IDENTIFY, "IDENTIFY");
+  SILC_CLIENT_CMDU(nick, NICK, "NICK");
+  SILC_CLIENT_CMDU(list, LIST, "LIST");
+  SILC_CLIENT_CMDU(topic, TOPIC, "TOPIC");
+  SILC_CLIENT_CMDU(invite, INVITE, "INVITE");
+  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");
+  SILC_CLIENT_CMDU(motd, MOTD, "MOTD");
+  SILC_CLIENT_CMDU(umode, UMODE, "UMODE");
+  SILC_CLIENT_CMDU(cmode, CMODE, "CMODE");
+  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");
+}
 
-  if (cmd->argv[1][0] == '*') {
-    if (!conn->current_channel) {
-      cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
-      COMMAND_ERROR;
-      goto out;
-    }
+/****************** Client Side Incoming Command Handling *******************/
 
-    channel = conn->current_channel;
-  } else {
-    name = cmd->argv[1];
+/* Reply to WHOIS command from server */
 
-    channel = silc_client_get_channel(cmd->client, conn, name);
-    if (!channel) {
-      cmd->client->ops->say(cmd->client, conn, "You are on that channel");
-      COMMAND_ERROR;
-      goto out;
-    }
-  }
+static void silc_client_command_process_whois(SilcClient client,
+                                             SilcClientConnection conn,
+                                             SilcCommandPayload payload,
+                                             SilcArgumentPayload args)
+{
+#if 0
+  SilcDList attrs;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len;
+  SilcBuffer buffer, packet;
 
-  if (cmd->argc == 3) {
-    if (cmd->argv[2][0] == '+')
-      type = 2;
-    else
-      type = 3;
+  SILC_LOG_DEBUG(("Received WHOIS command"));
 
-    ban = cmd->argv[2];
-    ban++;
-  }
+  /* Try to take the Requested Attributes */
+  tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
+  if (!tmp)
+    return;
 
-  chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+  attrs = silc_attribute_payload_parse(tmp, tmp_len);
+  if (!attrs)
+    return;
 
-  /* 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);
+  /* Process requested attributes */
+  buffer = silc_client_attributes_process(client, conn, attrs);
+  if (!buffer) {
+    silc_attribute_payload_list_free(attrs);
+    return;
+  }
 
-  silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
-                         0, NULL, NULL, buffer->data, buffer->len, TRUE);
+  /* Send the attributes back */
+  packet =
+    silc_command_reply_payload_encode_va(SILC_COMMAND_WHOIS,
+                                        SILC_STATUS_OK, 0,
+                                        silc_command_get_ident(payload),
+                                        1, 11, buffer->data, buffer->len);
+  silc_client_packet_send(client, sock, SILC_PACKET_COMMAND_REPLY,
+                         NULL, 0, NULL, NULL, packet->data,
+                         packet->len, TRUE);
+  silc_buffer_free(packet);
   silc_buffer_free(buffer);
-  silc_buffer_free(chidp);
+#endif /* 0 */
+}
 
-  /* Notify application */
-  COMMAND;
+/* 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. */
 
- out:
-  silc_client_command_free(cmd);
+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 (client->internal->params->ignore_requested_attributes)
+      break;
+
+    silc_client_command_process_whois(client, conn, payload, args);
+    break;
+
+  default:
+    break;
+  }
+
+  silc_command_payload_free(payload);
+  return SILC_FSM_FINISH;
 }