New silcconfig library and server parser. Merged silc-newconfig-final.patch.
[silc.git] / apps / silcd / command.c
index b9a0cc63b90f069216bf6bc42f1a58079f75f260..b4d5bcee37adf53669b7e5e77dde3ac515eb6dae 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
 
-  Copyright (C) 1997 - 2000 Pekka Riikonen
+  Copyright (C) 1997 - 2001 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
   GNU General Public License for more details.
 
 */
-/*
- * $Id$
- * $Log$
- * Revision 1.1.1.1  2000/06/27 11:36:56  priikone
- *     Importet from internal CVS/Added Log headers.
- *
- *
- */
+/* $Id$ */
 
 #include "serverincludes.h"
 #include "server_internal.h"
 
+static int silc_server_is_registered(SilcServer server,
+                                    SilcSocketConnection sock,
+                                    SilcServerCommandContext cmd,
+                                    SilcCommand command);
+static void 
+silc_server_command_send_status_reply(SilcServerCommandContext cmd,
+                                     SilcCommand command,
+                                     SilcCommandStatus status);
+static void 
+silc_server_command_send_status_data(SilcServerCommandContext cmd,
+                                    SilcCommand command,
+                                    SilcCommandStatus status,
+                                    uint32 arg_type,
+                                    const unsigned char *arg,
+                                    uint32 arg_len);
+static bool
+silc_server_command_pending_error_check(SilcServerCommandContext cmd,
+                                       SilcServerCommandReplyContext cmdr,
+                                       SilcCommand command);
+SILC_TASK_CALLBACK(silc_server_command_process_timeout);
+
 /* Server command list. */
 SilcServerCommand silc_command_list[] =
 {
   SILC_SERVER_CMD(whois, WHOIS, SILC_CF_LAG | SILC_CF_REG),
   SILC_SERVER_CMD(whowas, WHOWAS, SILC_CF_LAG | SILC_CF_REG),
   SILC_SERVER_CMD(identify, IDENTIFY, SILC_CF_LAG | SILC_CF_REG),
-  SILC_SERVER_CMD(nick, NICK, SILC_CF_LAG | SILC_CF_REG),
-  SILC_SERVER_CMD(list, LIST, SILC_CF_LAG | SILC_CF_REG),
+  SILC_SERVER_CMD(nick, NICK, SILC_CF_LAG_STRICT | SILC_CF_REG),
+  SILC_SERVER_CMD(list, LIST, SILC_CF_LAG_STRICT | SILC_CF_REG),
   SILC_SERVER_CMD(topic, TOPIC, SILC_CF_LAG | SILC_CF_REG),
   SILC_SERVER_CMD(invite, INVITE, SILC_CF_LAG | SILC_CF_REG),
   SILC_SERVER_CMD(quit, QUIT, SILC_CF_LAG | SILC_CF_REG),
-  SILC_SERVER_CMD(kill, KILL, SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER),
+  SILC_SERVER_CMD(kill, KILL, SILC_CF_LAG_STRICT | SILC_CF_REG | SILC_CF_OPER),
   SILC_SERVER_CMD(info, INFO, SILC_CF_LAG | SILC_CF_REG),
   SILC_SERVER_CMD(connect, CONNECT, 
                  SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER),
   SILC_SERVER_CMD(ping, PING, SILC_CF_LAG | SILC_CF_REG),
   SILC_SERVER_CMD(oper, OPER, SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER),
-  SILC_SERVER_CMD(join, JOIN, SILC_CF_LAG | SILC_CF_REG),
+  SILC_SERVER_CMD(join, JOIN, SILC_CF_LAG_STRICT | SILC_CF_REG),
   SILC_SERVER_CMD(motd, MOTD, SILC_CF_LAG | SILC_CF_REG),
   SILC_SERVER_CMD(umode, UMODE, SILC_CF_LAG | SILC_CF_REG),
-  SILC_SERVER_CMD(cmode, CMODE, SILC_CF_LAG | SILC_CF_REG),
-  SILC_SERVER_CMD(kick, KICK, SILC_CF_LAG | SILC_CF_REG),
-  SILC_SERVER_CMD(restart, RESTART, 
-                 SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER),
+  SILC_SERVER_CMD(cmode, CMODE, SILC_CF_LAG_STRICT | SILC_CF_REG),
+  SILC_SERVER_CMD(cumode, CUMODE, SILC_CF_LAG | SILC_CF_REG),
+  SILC_SERVER_CMD(kick, KICK, SILC_CF_LAG_STRICT | SILC_CF_REG),
+  SILC_SERVER_CMD(ban, BAN, SILC_CF_LAG_STRICT | SILC_CF_REG),
   SILC_SERVER_CMD(close, CLOSE,
                  SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER),
-  SILC_SERVER_CMD(die, DIE, SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER),
+  SILC_SERVER_CMD(shutdown, SHUTDOWN, SILC_CF_LAG | SILC_CF_REG | 
+                 SILC_CF_OPER),
   SILC_SERVER_CMD(silcoper, SILCOPER,
                  SILC_CF_LAG | SILC_CF_REG | SILC_CF_SILC_OPER),
-  SILC_SERVER_CMD(leave, LEAVE, SILC_CF_LAG | SILC_CF_REG),
-  SILC_SERVER_CMD(names, NAMES, SILC_CF_LAG | SILC_CF_REG),
+  SILC_SERVER_CMD(leave, LEAVE, SILC_CF_LAG_STRICT | SILC_CF_REG),
+  SILC_SERVER_CMD(users, USERS, SILC_CF_LAG | SILC_CF_REG),
+  SILC_SERVER_CMD(getkey, GETKEY, SILC_CF_LAG | SILC_CF_REG),
 
   { NULL, 0 },
 };
 
-/* List of pending commands. */
-SilcServerCommandPending *silc_command_pending = NULL;
+/* Performs several checks to the command. It first checks whether this
+   command was called as pending command callback. If it was then it checks
+   whether error occurred in the command reply where the pending command
+   callback was called. 
+
+   It also checks that the requested command includes correct amount
+   of arguments. */
+#define SILC_SERVER_COMMAND_CHECK(command, context, min, max)                \
+do {                                                                         \
+  uint32 _argc;                                                                      \
+                                                                             \
+  SILC_LOG_DEBUG(("Start"));                                                 \
+                                                                             \
+  if (silc_server_command_pending_error_check(cmd, context2, command)) {      \
+    silc_server_command_free(cmd);                                           \
+    return;                                                                  \
+  }                                                                          \
+                                                                             \
+  _argc = silc_argument_get_arg_num(cmd->args);                                      \
+  if (_argc < min) {                                                         \
+    silc_server_command_send_status_reply(cmd, command,                              \
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); \
+    silc_server_command_free(cmd);                                           \
+    return;                                                                  \
+  }                                                                          \
+  if (_argc > max) {                                                         \
+    silc_server_command_send_status_reply(cmd, command,                              \
+                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);   \
+    silc_server_command_free(cmd);                                           \
+    return;                                                                  \
+  }                                                                          \
+} while(0)
+
+/* Returns TRUE if the connection is registered. Unregistered connections
+   usually cannot send commands hence the check. */
+
+static int silc_server_is_registered(SilcServer server,
+                                    SilcSocketConnection sock,
+                                    SilcServerCommandContext cmd,
+                                    SilcCommand command)
+{
+  SilcIDListData idata = (SilcIDListData)sock->user_data;
+
+  if (!idata)
+    return FALSE;
 
-/* Add new pending command to the list of pending commands. Currently
-   pending commands are executed from command replies, thus we can
-   execute any command after receiving some specific command reply.
+  if (idata->status & SILC_IDLIST_STATUS_REGISTERED)
+    return TRUE;
 
-   The argument `reply_cmd' is the command reply from where the callback
-   function is to be called, thus, it IS NOT the command to be executed. */
+  silc_server_command_send_status_reply(cmd, command,
+                                       SILC_STATUS_ERR_NOT_REGISTERED);
+  return FALSE;
+}
 
-void silc_server_command_pending(SilcCommand reply_cmd,
-                                SilcCommandCb callback,
-                                void *context)
+/* Internal context to hold data when executed command with timeout. */
+typedef struct {
+  SilcServerCommandContext ctx;
+  SilcServerCommand *cmd;
+} *SilcServerCommandTimeout;
+
+/* Timeout callback to process commands with timeout for client. Client's
+   commands are always executed with timeout. */
+
+SILC_TASK_CALLBACK(silc_server_command_process_timeout)
 {
-  SilcServerCommandPending *reply, *r;
+  SilcServerCommandTimeout timeout = (SilcServerCommandTimeout)context;
+  SilcClientEntry client = (SilcClientEntry)timeout->ctx->sock->user_data;
 
-  reply = silc_calloc(1, sizeof(*reply));
-  reply->reply_cmd = reply_cmd;
-  reply->context = context;
-  reply->callback = callback;
+  if (!client) {
+    silc_server_command_free(timeout->ctx);
+    silc_free(timeout);
+  }
+
+  /* Update access time */
+  client->last_command = time(NULL);
+
+  if (!(timeout->cmd->flags & SILC_CF_REG))
+    timeout->cmd->cb(timeout->ctx, NULL);
+  else if (silc_server_is_registered(timeout->ctx->server, 
+                                    timeout->ctx->sock, 
+                                    timeout->ctx, 
+                                    timeout->cmd->cmd))
+    timeout->cmd->cb(timeout->ctx, NULL);
+  else
+    silc_server_command_free(timeout->ctx);
+
+  silc_free(timeout);
+}
+
+/* Processes received command packet. */
 
-  if (silc_command_pending == NULL) {
-    silc_command_pending = reply;
+void silc_server_command_process(SilcServer server,
+                                SilcSocketConnection sock,
+                                SilcPacketContext *packet)
+{
+  SilcServerCommandContext ctx;
+  SilcServerCommand *cmd;
+  SilcCommand command;
+
+  /* Allocate command context. This must be free'd by the
+     command routine receiving it. */
+  ctx = silc_server_command_alloc();
+  ctx->server = server;
+  ctx->sock = silc_socket_dup(sock);
+  ctx->packet = silc_packet_context_dup(packet); /* Save original packet */
+  
+  /* Parse the command payload in the packet */
+  ctx->payload = silc_command_payload_parse(packet->buffer->data,
+                                           packet->buffer->len);
+  if (!ctx->payload) {
+    SILC_LOG_ERROR(("Bad command payload, packet dropped"));
+    silc_buffer_free(packet->buffer);
+    silc_packet_context_free(packet);
+    silc_socket_free(ctx->sock);
+    silc_free(ctx);
     return;
   }
+  ctx->args = silc_command_get_args(ctx->payload);
 
-  for (r = silc_command_pending; r; r = r->next) {
-    if (r->next == NULL) {
-      r->next = reply;
+  /* Get the command */
+  command = silc_command_get(ctx->payload);
+  for (cmd = silc_command_list; cmd->cb; cmd++)
+    if (cmd->cmd == command)
       break;
+
+  if (cmd == NULL) {
+    silc_server_command_send_status_reply(ctx, command,
+                                         SILC_STATUS_ERR_UNKNOWN_COMMAND);
+    silc_server_command_free(ctx);
+    return;
+  }
+
+  /* Execute client's commands always with timeout.  Normally they are
+     executed with zero (0) timeout but if client is sending command more
+     frequently than once in 2 seconds, then the timeout may be 0 to 2
+     seconds. */
+  if (sock->type == SILC_SOCKET_TYPE_CLIENT) {
+    SilcClientEntry client = (SilcClientEntry)sock->user_data;
+    SilcServerCommandTimeout timeout = silc_calloc(1, sizeof(*timeout));
+    int fast;
+
+    timeout->ctx = ctx;
+    timeout->cmd = cmd;
+
+    if (client->last_command && (time(NULL) - client->last_command) < 2) {
+      client->fast_command++;
+      fast = FALSE;
+    } else {
+      client->fast_command = ((client->fast_command - 1) <= 0 ? 0 : 
+                             client->fast_command--);
+      fast = TRUE;
     }
+
+    if (!fast && ((cmd->flags & SILC_CF_LAG_STRICT) ||
+                 (client->fast_command > 5 && cmd->flags & SILC_CF_LAG)))
+      silc_schedule_task_add(server->schedule, sock->sock, 
+                        silc_server_command_process_timeout,
+                        (void *)timeout, 
+                        2 - (time(NULL) - client->last_command), 0,
+                        SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+    else
+      silc_schedule_task_add(server->schedule, sock->sock, 
+                        silc_server_command_process_timeout,
+                        (void *)timeout, 0, 1,
+                        SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+    return;
+  }
+
+  /* Execute for server */
+
+  if (!(cmd->flags & SILC_CF_REG))
+    cmd->cb(ctx, NULL);
+  else if (silc_server_is_registered(server, sock, ctx, cmd->cmd))
+    cmd->cb(ctx, NULL);
+  else
+    silc_server_command_free(ctx);
+}
+
+/* Allocate Command Context */
+
+SilcServerCommandContext silc_server_command_alloc()
+{
+  SilcServerCommandContext ctx = silc_calloc(1, sizeof(*ctx));
+  ctx->users++;
+  return ctx;
+}
+
+/* Free's the command context allocated before executing the command */
+
+void silc_server_command_free(SilcServerCommandContext ctx)
+{
+  ctx->users--;
+  SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users + 1,
+                 ctx->users));
+  if (ctx->users < 1) {
+    if (ctx->payload)
+      silc_command_payload_free(ctx->payload);
+    if (ctx->packet)
+      silc_packet_context_free(ctx->packet);
+    if (ctx->sock)
+      silc_socket_free(ctx->sock); /* Decrease reference counter */
+    silc_free(ctx);
+  }
+}
+
+/* Duplicate Command Context by adding reference counter. The context won't
+   be free'd untill it hits zero. */
+
+SilcServerCommandContext 
+silc_server_command_dup(SilcServerCommandContext ctx)
+{
+  ctx->users++;
+  SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users - 1,
+                 ctx->users));
+  return ctx;
+}
+
+/* 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.  It can be SILC_COMMAND_NONE
+   to match any command with the `ident'.  If `ident' is non-zero
+   the `callback' will be executed when received reply with command
+   identifier `ident'. If there already exists pending command for the
+   specified command, ident, callback and context this function has no
+   effect. */
+
+bool silc_server_command_pending(SilcServer server,
+                                SilcCommand reply_cmd,
+                                uint16 ident,
+                                SilcCommandCb callback,
+                                void *context)
+{
+  SilcServerCommandPending *reply;
+
+  /* Check whether identical pending already exists for same command,
+     ident, callback and callback context. If it does then it would be
+     error to register it again. */
+  silc_dlist_start(server->pending_commands);
+  while ((reply = silc_dlist_get(server->pending_commands)) != SILC_LIST_END) {
+    if (reply->reply_cmd == reply_cmd && reply->ident == ident &&
+       reply->callback == callback && reply->context == context)
+      return FALSE;
   }
+
+  reply = silc_calloc(1, sizeof(*reply));
+  reply->reply_cmd = reply_cmd;
+  reply->ident = ident;
+  reply->context = context;
+  reply->callback = callback;
+  silc_dlist_add(server->pending_commands, reply);
+
+  return TRUE;
 }
 
 /* Deletes pending command by reply command type. */
 
-void silc_server_command_pending_del(SilcCommand reply_cmd)
+void silc_server_command_pending_del(SilcServer server,
+                                    SilcCommand reply_cmd,
+                                    uint16 ident)
 {
-  SilcServerCommandPending *r, *tmp;
-  
-  if (silc_command_pending) {
-    if (silc_command_pending->reply_cmd == reply_cmd) {
-      silc_free(silc_command_pending);
-      silc_command_pending = NULL;
-      return;
-    }
+  SilcServerCommandPending *r;
 
-    for (r = silc_command_pending; r; r = r->next) {
-      if (r->next && r->next->reply_cmd == reply_cmd) {
-       tmp = r->next;
-       r->next = r->next->next;
-       silc_free(tmp);
-       break;
-      }
+  silc_dlist_start(server->pending_commands);
+  while ((r = silc_dlist_get(server->pending_commands)) != SILC_LIST_END) {
+    if (r->reply_cmd == reply_cmd && r->ident == ident) {
+      silc_dlist_del(server->pending_commands, r);
+      break;
     }
   }
 }
 
-/* Free's the command context allocated before executing the command */
+/* Checks for pending commands and marks callbacks to be called from
+   the command reply function. Returns TRUE if there were pending command. */
 
-static void silc_server_command_free(SilcServerCommandContext cmd)
+SilcServerCommandPendingCallbacks
+silc_server_command_pending_check(SilcServer server,
+                                 SilcServerCommandReplyContext ctx,
+                                 SilcCommand command, 
+                                 uint16 ident,
+                                 uint32 *callbacks_count)
 {
-  if (cmd) {
-    silc_command_free_payload(cmd->payload);
-    silc_free(cmd);
+  SilcServerCommandPending *r;
+  SilcServerCommandPendingCallbacks callbacks = NULL;
+  int i = 0;
+
+  silc_dlist_start(server->pending_commands);
+  while ((r = silc_dlist_get(server->pending_commands)) != SILC_LIST_END) {
+    if ((r->reply_cmd == command || r->reply_cmd == SILC_COMMAND_NONE)
+       && r->ident == ident) {
+      callbacks = silc_realloc(callbacks, sizeof(*callbacks) * (i + 1));
+      callbacks[i].context = r->context;
+      callbacks[i].callback = r->callback;
+      ctx->ident = ident;
+      i++;
+    }
   }
+
+  *callbacks_count = i;
+  return callbacks;
 }
 
-/* Sends command status message as command reply packet. */
+/* Sends simple status message as command reply packet */
 
 static void 
-silc_server_command_send_status_msg(SilcServerCommandContext cmd,
-                                   SilcCommand command,
-                                   SilcCommandStatus status,
-                                   unsigned char *msg,
-                                   unsigned int msg_len)
+silc_server_command_send_status_reply(SilcServerCommandContext cmd,
+                                     SilcCommand command,
+                                     SilcCommandStatus status)
 {
-  SilcBuffer sp_buf, buffer;
+  SilcBuffer buffer;
 
   SILC_LOG_DEBUG(("Sending command status %d", status));
 
-  sp_buf = silc_command_encode_status_payload(status, msg, msg_len);
-  buffer = silc_command_encode_payload_va(command, 1, 
-                                         sp_buf->data, sp_buf->len);
+  buffer = 
+    silc_command_reply_payload_encode_va(command, status, 
+                                        silc_command_get_ident(cmd->payload),
+                                        0);
   silc_server_packet_send(cmd->server, cmd->sock,
                          SILC_PACKET_COMMAND_REPLY, 0, 
                          buffer->data, buffer->len, FALSE);
   silc_buffer_free(buffer);
-  silc_buffer_free(sp_buf);
 }
 
-/* Sends simple status message as command reply packet */
+/* Sends command status reply with one extra argument. The argument
+   type must be sent as argument. */
 
 static void 
-silc_server_command_send_status_reply(SilcServerCommandContext cmd,
-                                     SilcCommand command,
-                                     SilcCommandStatus status)
+silc_server_command_send_status_data(SilcServerCommandContext cmd,
+                                    SilcCommand command,
+                                    SilcCommandStatus status,
+                                    uint32 arg_type,
+                                    const unsigned char *arg,
+                                    uint32 arg_len)
 {
-  SilcBuffer sp_buf, buffer;
+  SilcBuffer buffer;
 
   SILC_LOG_DEBUG(("Sending command status %d", status));
 
-  sp_buf = silc_command_encode_status_payload(status, NULL, 0);
-  buffer = silc_command_encode_payload_va(command, 1, 
-                                         sp_buf->data, sp_buf->len);
+  buffer = 
+    silc_command_reply_payload_encode_va(command, status, 
+                                        silc_command_get_ident(cmd->payload),
+                                        1, arg_type, arg, arg_len);
   silc_server_packet_send(cmd->server, cmd->sock,
                          SILC_PACKET_COMMAND_REPLY, 0, 
                          buffer->data, buffer->len, FALSE);
   silc_buffer_free(buffer);
-  silc_buffer_free(sp_buf);
 }
 
-/* Server side of command WHOIS. Processes user's query and sends found 
-   results as command replies back to the client. */
+/* This function can be called to check whether in the command reply
+   an error occurred. This function has no effect if this is called
+   when the command function was not called as pending command callback. 
+   This returns TRUE if error had occurred. */
 
-SILC_SERVER_CMD_FUNC(whois)
+static bool
+silc_server_command_pending_error_check(SilcServerCommandContext cmd,
+                                       SilcServerCommandReplyContext cmdr,
+                                       SilcCommand command)
 {
-  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  char *tmp, *nick = NULL, *server = NULL;
-  unsigned int argc, count = 0, len;
-  SilcClientList *entry;
-  SilcBuffer sp_buf, packet;
-  unsigned char *id_string;
+  SilcCommandStatus status;
+
+  if (!cmd->pending || !cmdr)
+    return FALSE;
+
+  SILC_GET16_MSB(status, silc_argument_get_arg_type(cmdr->args, 1, NULL));
+  if (status != SILC_STATUS_OK &&
+      status != SILC_STATUS_LIST_START &&
+      status != SILC_STATUS_LIST_ITEM &&
+      status != SILC_STATUS_LIST_END) {
+    SilcBuffer buffer;
+
+    /* Send the same command reply payload */
+    silc_command_set_ident(cmdr->payload, 
+                          silc_command_get_ident(cmd->payload));
+    buffer = silc_command_payload_encode_payload(cmdr->payload);
+    silc_server_packet_send(cmd->server, cmd->sock,
+                           SILC_PACKET_COMMAND_REPLY, 0, 
+                           buffer->data, buffer->len, FALSE);
+    silc_buffer_free(buffer);
+    return TRUE;
+  }
 
-  SILC_LOG_DEBUG(("Start"));
+  return FALSE;
+}
 
-  argc = silc_command_get_arg_num(cmd->payload);
-  if (argc < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-  if (argc > 2) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS,
-                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);
-    goto out;
-  }
+/******************************************************************************
 
-  /* Get the nickname@server string and parse it. */
-  tmp = silc_command_get_first_arg(cmd->payload, NULL);
-  if (tmp) {
-    if (strchr(tmp, '@')) {
-      len = strcspn(tmp, "@");
-      nick = silc_calloc(len + 1, sizeof(char));
-      memcpy(nick, tmp, len);
-      server = silc_calloc(strlen(tmp) - len, sizeof(char));
-      memcpy(server, tmp + len + 1, strlen(tmp) - len - 1);
+                              WHOIS Functions
+
+******************************************************************************/
+
+static int
+silc_server_command_whois_parse(SilcServerCommandContext cmd,
+                               SilcClientID ***client_id,
+                               uint32 *client_id_count,
+                               char **nickname,
+                               char **server_name,
+                               int *count,
+                               SilcCommand command)
+{
+  unsigned char *tmp;
+  uint32 len;
+  uint32 argc = silc_argument_get_arg_num(cmd->args);
+  int i, k;
+
+  /* If client ID is in the command it must be used instead of nickname */
+  tmp = silc_argument_get_arg_type(cmd->args, 3, &len);
+  if (!tmp) {
+    /* No ID, get the nickname@server string and parse it. */
+    tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+    if (tmp) {
+      silc_parse_userfqdn(tmp, nickname, server_name);
     } else {
-      nick = strdup(tmp);
+      silc_server_command_send_status_reply(cmd, command,
+                                           SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      return FALSE;
     }
   } else {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
+    /* Command includes ID, we must use that.  Also check whether the command
+       has more than one ID set - take them all. */
+
+    *client_id = silc_calloc(1, sizeof(**client_id));
+    (*client_id)[0] = silc_id_payload_parse_id(tmp, len);
+    if ((*client_id)[0] == NULL) {
+      silc_free(*client_id);
+      silc_server_command_send_status_reply(cmd, command,
+                                           SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      return FALSE;
+    }
+    *client_id_count = 1;
+
+    /* Take all ID's from the command packet */
+    if (argc > 1) {
+      for (k = 1, i = 1; i < argc; i++) {
+       tmp = silc_argument_get_arg_type(cmd->args, i + 3, &len);
+       if (tmp) {
+         *client_id = silc_realloc(*client_id, sizeof(**client_id) *
+                                   (*client_id_count + 1));
+         (*client_id)[k] = silc_id_payload_parse_id(tmp, len);
+         if ((*client_id)[k] == NULL) {
+           /* Cleanup all and fail */
+           for (i = 0; i < *client_id_count; i++)
+             silc_free((*client_id)[i]);
+           silc_free(*client_id);
+           silc_server_command_send_status_reply(
+                                        cmd, command,
+                                        SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+           return FALSE;
+         }
+         (*client_id_count)++;
+         k++;
+       }
+      }
+    }
   }
 
   /* Get the max count of reply messages allowed */
-  if (argc == 2) {
-    tmp = silc_command_get_next_arg(cmd->payload, NULL);
-    if (!tmp) {
-      silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS,
-                                           SILC_STATUS_ERR_TOO_MANY_PARAMS);
-      if (nick)
-       silc_free(nick);
-      if (server)
-       silc_free(server);
-      goto out;
-    }
-    count = atoi(tmp);
-  }
+  tmp = silc_argument_get_arg_type(cmd->args, 2, NULL);
+  if (tmp)
+    *count = atoi(tmp);
+  else
+    *count = 0;
 
-  /* Then, make the query from our local client list */
-  entry = silc_idlist_find_client_by_nickname(cmd->server->local_list->clients,
-                                             nick, server);
-  if (!entry) {
+  return TRUE;
+}
+
+/* Resolve context used by both WHOIS and IDENTIFY commands */
+typedef struct {
+  SilcServerEntry router;
+  uint16 ident;
+  unsigned char **res_argv;
+  uint32 *res_argv_lens;
+  uint32 *res_argv_types;
+  uint32 res_argc;
+} *SilcServerResolveContext;
+
+static bool
+silc_server_command_whois_check(SilcServerCommandContext cmd,
+                               SilcClientEntry *clients,
+                               uint32 clients_count)
+{
+  SilcServer server = cmd->server;
+  SilcClientEntry entry;
+  SilcServerResolveContext resolve = NULL, r = NULL;
+  uint32 resolve_count = 0;
+  int i, k;
+  bool no_res = TRUE;
 
-    /* If we are normal server and are connected to a router we will
-       make global query from the router. */
-    if (cmd->server->server_type == SILC_SERVER && !cmd->server->standalone) {
+  SILC_LOG_DEBUG(("Start"));
 
-      goto ok;
+  for (i = 0; i < clients_count; i++) {
+    entry = clients[i];
+    if (!entry)
+      continue;
+
+    if ((entry->nickname && entry->username && entry->userinfo) ||
+       !(entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) {
+      if (!entry->router)
+       continue;
+
+      /* If we are normal server, and we've not resolved this client from
+        router and it is global client, we'll check whether it is on some
+        channel.  If not then we cannot be sure about its validity, and
+        we'll resolve it from router. */
+      if (cmd->server->server_type != SILC_SERVER || cmd->pending ||
+         entry->connection || silc_hash_table_count(entry->channels))
+       continue;
     }
-    
-    /* If we are router then we will check our global list as well. */
-    if (cmd->server->server_type == SILC_ROUTER) {
-      entry =
-       silc_idlist_find_client_by_nickname(cmd->server->global_list->clients,
-                                           nick, server);
-      if (!entry) {
-       silc_server_command_send_status_msg(cmd, SILC_COMMAND_WHOIS,
-                                           SILC_STATUS_ERR_NO_SUCH_NICK,
-                                           tmp, strlen(tmp));
-       goto out;
+
+    /* We need to resolve this entry since it is not complete */
+
+    if (!cmd->pending && entry->data.status & SILC_IDLIST_STATUS_RESOLVING) {
+      /* The entry is being resolved (and we are not the resolver) so attach
+        to the command reply and we're done with this one. */
+      silc_server_command_pending(server, SILC_COMMAND_NONE, 
+                                 entry->resolve_cmd_ident,
+                                 silc_server_command_whois,
+                                 silc_server_command_dup(cmd));
+      no_res = FALSE;
+    } else {
+      if (entry->data.status & SILC_IDLIST_STATUS_RESOLVING) {
+       /* We've resolved this and it still is not ready.  We'll return
+          and are that this will be handled again after it is resolved. */
+       for (i = 0; i < resolve_count; i++) {
+         for (k = 0; k < r->res_argc; k++)
+           silc_free(r->res_argv[k]);
+         silc_free(r->res_argv);
+         silc_free(r->res_argv_lens);
+         silc_free(r->res_argv_types);
+       }
+       silc_free(resolve);
+       return FALSE;
+      } else {
+       /* We'll resolve this client */
+       SilcBuffer idp;
+
+       r = NULL;
+       for (k = 0; k < resolve_count; k++) {
+         if (resolve[k].router == entry->router) {
+           r = &resolve[k];
+           break;
+         }
+       }
+
+       if (!r) {
+         resolve = silc_realloc(resolve, sizeof(*resolve) * 
+                                (resolve_count + 1));
+         r = &resolve[resolve_count];
+         memset(r, 0, sizeof(*r));
+         r->router = entry->router;
+         r->ident = ++server->cmd_ident;
+         resolve_count++;
+       }
+
+       r->res_argv = silc_realloc(r->res_argv, sizeof(*r->res_argv) *
+                                  (r->res_argc + 1));
+       r->res_argv_lens = silc_realloc(r->res_argv_lens, 
+                                       sizeof(*r->res_argv_lens) *
+                                       (r->res_argc + 1));
+       r->res_argv_types = silc_realloc(r->res_argv_types, 
+                                        sizeof(*r->res_argv_types) *
+                                        (r->res_argc + 1));
+       idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+       r->res_argv[r->res_argc] = silc_calloc(idp->len, 
+                                              sizeof(**r->res_argv));
+       memcpy(r->res_argv[r->res_argc], idp->data, idp->len);
+       r->res_argv_lens[r->res_argc] = idp->len;
+       r->res_argv_types[r->res_argc] = r->res_argc + 3;
+       r->res_argc++;
+       silc_buffer_free(idp);
+
+       entry->resolve_cmd_ident = r->ident;
+       entry->data.status |= SILC_IDLIST_STATUS_RESOLVING;
+       entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVED;
       }
-      goto ok;
     }
+  }
 
-    silc_server_command_send_status_msg(cmd, SILC_COMMAND_WHOIS,
-                                       SILC_STATUS_ERR_NO_SUCH_NICK,
-                                       tmp, strlen(tmp));
-    goto out;
+  /* Do the resolving */
+  for (i = 0; i < resolve_count; i++) {
+    SilcBuffer res_cmd;
+
+    r = &resolve[i];
+
+    /* Send WHOIS request. We send WHOIS since we're doing the requesting
+       now anyway so make it a good one. */
+    res_cmd = silc_command_payload_encode(SILC_COMMAND_WHOIS,
+                                         r->res_argc, r->res_argv, 
+                                         r->res_argv_lens,
+                                         r->res_argv_types, 
+                                         r->ident);
+    silc_server_packet_send(server, r->router->connection,
+                           SILC_PACKET_COMMAND, cmd->packet->flags,
+                           res_cmd->data, res_cmd->len, FALSE);
+
+    /* Reprocess this packet after received reply */
+    silc_server_command_pending(server, SILC_COMMAND_WHOIS, 
+                               r->ident,
+                               silc_server_command_whois,
+                               silc_server_command_dup(cmd));
+    cmd->pending = TRUE;
+
+    silc_buffer_free(res_cmd);
+    for (k = 0; k < r->res_argc; k++)
+      silc_free(r->res_argv[k]);
+    silc_free(r->res_argv);
+    silc_free(r->res_argv_lens);
+    silc_free(r->res_argv_types);
+    no_res = FALSE;
   }
+  silc_free(resolve);
 
- ok:
-  /* XXX, works only for local server info */
+  return no_res;
+}
 
-  /* Send WHOIS reply */
-  id_string = silc_id_id2str(entry->id, SILC_ID_CLIENT);
-  tmp = silc_command_get_first_arg(cmd->payload, NULL),
-  sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0);
+static void
+silc_server_command_whois_send_reply(SilcServerCommandContext cmd,
+                                    SilcClientEntry *clients,
+                                    uint32 clients_count,
+                                    int count,
+                                    const char *nickname,
+                                    SilcClientID **client_ids)
+{
+  SilcServer server = cmd->server;
+  char *tmp;
+  int i, k, len, valid_count;
+  SilcBuffer packet, idp, channels;
+  SilcClientEntry entry;
+  SilcCommandStatus status;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  char nh[256], uh[256];
+  unsigned char idle[4], mode[4];
+  unsigned char *fingerprint;
+  SilcSocketConnection hsock;
+
+  /* Process only valid clients and ignore those that are not registered. */
+  valid_count = 0;
+  for (i = 0; i < clients_count; i++) {
+    if (clients[i]->data.status & SILC_IDLIST_STATUS_REGISTERED)
+      valid_count++;
+    else
+      clients[i] = NULL;
+  }
 
-  /* XXX */
-  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) {
-    char nh[256], uh[256];
-    SilcSocketConnection hsock;
+  if (!valid_count) {
+    /* No valid clients found, send error reply */
+    if (nickname) {
+      silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
+                                          SILC_STATUS_ERR_NO_SUCH_NICK,
+                                          3, nickname, strlen(nickname));
+    } else if (client_ids && client_ids[0]) {
+      SilcBuffer idp = silc_id_payload_encode(client_ids[0], SILC_ID_CLIENT);
+      silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
+                                          SILC_STATUS_ERR_NO_SUCH_CLIENT_ID,
+                                          2, idp->data, idp->len);
+      silc_buffer_free(idp);
+    }
+    return;
+  }
 
+  /* Start processing found clients. */
+  if (valid_count > 1)
+    status = SILC_STATUS_LIST_START;
+  else
+    status = SILC_STATUS_OK;
+
+  for (i = 0, k = 0; i < clients_count; i++) {
+    entry = clients[i];
+    if (!entry)
+      continue;
+
+    if (k >= 1)
+      status = SILC_STATUS_LIST_ITEM;
+    if (valid_count > 1 && k == valid_count - 1)
+      status = SILC_STATUS_LIST_END;
+    if (count && k - 1 == count)
+      status = SILC_STATUS_LIST_END;
+
+    /* Send WHOIS reply */
+    idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+    tmp = silc_argument_get_first_arg(cmd->args, NULL);
+    
     memset(uh, 0, sizeof(uh));
     memset(nh, 0, sizeof(nh));
-
+    memset(idle, 0, sizeof(idle));
+    
     strncat(nh, entry->nickname, strlen(entry->nickname));
-    strncat(nh, "@", 1);
-    len = entry->router ? strlen(entry->router->server_name) :
-      strlen(cmd->server->server_name);
-    strncat(nh, entry->router ? entry->router->server_name :
-           cmd->server->server_name, len);
-
+    if (!strchr(entry->nickname, '@')) {
+      strncat(nh, "@", 1);
+      if (entry->servername) {
+       strncat(nh, entry->servername, strlen(entry->servername));
+      } else {
+       len = entry->router ? strlen(entry->router->server_name) :
+         strlen(server->server_name);
+       strncat(nh, entry->router ? entry->router->server_name :
+               server->server_name, len);
+      }
+    }
+      
     strncat(uh, entry->username, strlen(entry->username));
-    strncat(uh, "@", 1);
-    hsock = (SilcSocketConnection)entry->connection;
-    len = hsock->hostname ? strlen(hsock->hostname) : strlen(hsock->ip);
-    strncat(uh, hsock->hostname ? hsock->hostname : hsock->ip, len);
+    if (!strchr(entry->username, '@')) {
+      strncat(uh, "@", 1);
+      hsock = (SilcSocketConnection)entry->connection;
+      len = strlen(hsock->hostname);
+      strncat(uh, hsock->hostname, len);
+    }
 
-    /* XXX */
-    if (entry->userinfo)
-      packet = 
-        silc_command_encode_payload_va(SILC_COMMAND_WHOIS, 5, 
-                                      sp_buf->data, sp_buf->len,
-                                      id_string, SILC_ID_CLIENT_LEN,
-                                      nh, strlen(nh),
-                                      uh, strlen(uh),
-                                      entry->userinfo, 
-                                      strlen(entry->userinfo));
+    channels = silc_server_get_client_channel_list(server, entry);
+
+    if (entry->data.fingerprint[0] != 0 && entry->data.fingerprint[1] != 0)
+      fingerprint = entry->data.fingerprint;
     else
-      packet = 
-        silc_command_encode_payload_va(SILC_COMMAND_WHOIS, 4,
-                                      sp_buf->data, sp_buf->len,
-                                      id_string, SILC_ID_CLIENT_LEN,
-                                      nh, strlen(nh),
-                                      uh, strlen(uh));
+      fingerprint = NULL;
+      
+    SILC_PUT32_MSB(entry->mode, mode);
 
-  } else {
-    /* XXX */
-    packet = 
-      silc_command_encode_payload_va(SILC_COMMAND_WHOIS, 4, 
-                                    sp_buf->data, sp_buf->len,
-                                    id_string, SILC_ID_CLIENT_LEN,
-                                    entry->nickname, strlen(entry->nickname),
-                                    tmp, strlen(tmp)); /* XXX */
-  }
-  silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
-                         0, packet->data, packet->len, FALSE);
+    if (entry->connection) {
+      SILC_PUT32_MSB((time(NULL) - entry->data.last_receive), idle);
+    }
 
-  silc_free(id_string);
-  silc_buffer_free(packet);
-  silc_free(sp_buf);
+    packet = 
+      silc_command_reply_payload_encode_va(SILC_COMMAND_WHOIS,
+                                          status, ident, 8, 
+                                          2, idp->data, idp->len,
+                                          3, nh, strlen(nh),
+                                          4, uh, strlen(uh),
+                                          5, entry->userinfo, 
+                                          strlen(entry->userinfo),
+                                          6, channels ? channels->data : NULL,
+                                          channels ? channels->len : 0,
+                                          7, mode, 4,
+                                          8, idle, 4,
+                                          9, fingerprint,
+                                          fingerprint ? 20 : 0);
+
+    silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
+                           0, packet->data, packet->len, FALSE);
+    
+    silc_buffer_free(packet);
+    silc_buffer_free(idp);
+    if (channels)
+      silc_buffer_free(channels);
 
- out:
-  silc_server_command_free(cmd);
+    k++;
+  }
 }
 
-SILC_SERVER_CMD_FUNC(whowas)
+static void 
+silc_server_command_whois_send_router(SilcServerCommandContext cmd)
 {
+  SilcServer server = cmd->server;
+  SilcBuffer tmpbuf;
+  uint16 old_ident;
+
+  old_ident = silc_command_get_ident(cmd->payload);
+  silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+  tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+  /* Send WHOIS command to our router */
+  silc_server_packet_send(server, (SilcSocketConnection)
+                         server->router->connection,
+                         SILC_PACKET_COMMAND, cmd->packet->flags,
+                         tmpbuf->data, tmpbuf->len, TRUE);
+
+  /* Reprocess this packet after received reply from router */
+  silc_server_command_pending(server, SILC_COMMAND_WHOIS, 
+                             silc_command_get_ident(cmd->payload),
+                             silc_server_command_whois,
+                             silc_server_command_dup(cmd));
+  cmd->pending = TRUE;
+  silc_command_set_ident(cmd->payload, old_ident);
+  silc_buffer_free(tmpbuf);
 }
 
-SILC_SERVER_CMD_FUNC(identify)
+static int
+silc_server_command_whois_process(SilcServerCommandContext cmd)
 {
-  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  char *tmp, *nick = NULL, *server = NULL;
-  unsigned int argc, count = 0, len;
-  SilcClientList *entry;
-  SilcBuffer sp_buf, packet;
-  unsigned char *id_string;
-
-  SILC_LOG_DEBUG(("Start"));
-
-  argc = silc_command_get_arg_num(cmd->payload);
-  if (argc < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-  if (argc > 2) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY,
-                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);
+  SilcServer server = cmd->server;
+  char *nick = NULL, *server_name = NULL;
+  int count = 0;
+  SilcClientEntry *clients = NULL, entry;
+  SilcClientID **client_id = NULL;
+  uint32 client_id_count = 0, clients_count = 0;
+  int i, ret = 0;
+  bool check_global = FALSE;
+
+  /* Parse the whois request */
+  if (!silc_server_command_whois_parse(cmd, &client_id, &client_id_count, 
+                                      &nick, &server_name, &count,
+                                      SILC_COMMAND_WHOIS))
+    return 0;
+
+  /* Send the WHOIS request to the router only if it included nickname.
+     Since nicknames can be expanded into many clients we need to send it
+     to router.  If the WHOIS included only client ID's we will check them
+     first locally since we just might have them. */
+  if (nick && !client_id_count && cmd->sock->type == SILC_SOCKET_TYPE_CLIENT &&
+      server->server_type == SILC_SERVER && !cmd->pending &&
+      !server->standalone) {
+    silc_server_command_whois_send_router(cmd);
+    ret = -1;
     goto out;
   }
 
-  /* Get the nickname@server string and parse it. */
-  tmp = silc_command_get_first_arg(cmd->payload, NULL);
-  if (tmp) {
-    if (strchr(tmp, '@')) {
-      len = strcspn(tmp, "@");
-      nick = silc_calloc(len + 1, sizeof(char));
-      memcpy(nick, tmp, len);
-      server = silc_calloc(strlen(tmp) - len, sizeof(char));
-      memcpy(server, tmp + len + 1, strlen(tmp) - len - 1);
-    } else {
-      nick = strdup(tmp);
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT)
+    check_global = TRUE;
+  else if (server->server_type != SILC_SERVER)
+    check_global = TRUE;
+
+  /* Get all clients matching that ID or nickname from local list */
+  if (client_id_count) {
+    /* Check all Client ID's received in the command packet */
+    for (i = 0; i < client_id_count; i++) {
+      entry = silc_idlist_find_client_by_id(server->local_list, 
+                                           client_id[i], TRUE, NULL);
+      if (!entry && check_global)
+       entry = silc_idlist_find_client_by_id(server->global_list, 
+                                             client_id[i], TRUE, NULL);
+      if (entry) {
+       clients = silc_realloc(clients, sizeof(*clients) * 
+                              (clients_count + 1));
+       clients[clients_count++] = entry;
+      } else {
+       /* If we are normal server and did not send the request first to router
+          do it now, since we do not have the Client ID information. */
+       if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT &&
+           server->server_type == SILC_SERVER && !cmd->pending && 
+           !server->standalone) {
+         silc_server_command_whois_send_router(cmd);
+         ret = -1;
+         goto out;
+       }
+      }
     }
   } else {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-
-  /* Get the max count of reply messages allowed */
-  if (argc == 2) {
-    tmp = silc_command_get_next_arg(cmd->payload, NULL);
-    if (!tmp) {
-      silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY,
-                                           SILC_STATUS_ERR_TOO_MANY_PARAMS);
-      goto out;
+    /* Find by nickname */
+    if (!silc_idlist_get_clients_by_hash(server->local_list, 
+                                        nick, server->md5hash,
+                                        &clients, &clients_count))
+      silc_idlist_get_clients_by_nickname(server->local_list, 
+                                         nick, server_name,
+                                         &clients, &clients_count);
+    if (check_global) {
+      if (!silc_idlist_get_clients_by_hash(server->global_list, 
+                                          nick, server->md5hash,
+                                          &clients, &clients_count))
+       silc_idlist_get_clients_by_nickname(server->global_list, 
+                                           nick, server_name,
+                                           &clients, &clients_count);
     }
-    count = atoi(tmp);
   }
-
-  /* Then, make the query from our local client list */
-  entry = silc_idlist_find_client_by_hash(cmd->server->local_list->clients,
-                                         nick, cmd->server->md5hash);
-  if (!entry) {
-
-    /* If we are normal server and are connected to a router we will
-       make global query from the router. */
-    if (cmd->server->server_type == SILC_SERVER && !cmd->server->standalone) {
-      SilcBuffer buffer = cmd->packet->buffer;
-
-      /* Forward the received IDENTIFY command to our router */
-      silc_buffer_push(buffer, buffer->data - buffer->head);
-      silc_server_packet_forward(cmd->server, (SilcSocketConnection)
-                                cmd->server->id_entry->router->connection,
-                                buffer->data, buffer->len,
-                                TRUE);
+  
+  if (!clients) {
+    /* If we are normal server and did not send the request first to router
+       do it now, since we do not have the information. */
+    if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT &&
+       server->server_type == SILC_SERVER && !cmd->pending && 
+       !server->standalone) {
+      silc_server_command_whois_send_router(cmd);
+      ret = -1;
       goto out;
     }
-    
-    /* If we are router then we will check our global list as well. */
-    if (cmd->server->server_type == SILC_ROUTER) {
-      entry = 
-       silc_idlist_find_client_by_hash(cmd->server->global_list->clients,
-                                       nick, cmd->server->md5hash);
-      if (!entry) {
-       silc_server_command_send_status_msg(cmd, SILC_COMMAND_IDENTIFY,
-                                           SILC_STATUS_ERR_NO_SUCH_NICK,
-                                           tmp, strlen(tmp));
-       goto out;
-      }
-      goto ok;
-    }
 
-    silc_server_command_send_status_msg(cmd, SILC_COMMAND_IDENTIFY,
-                                       SILC_STATUS_ERR_NO_SUCH_NICK,
-                                       tmp, strlen(tmp));
+    /* Such client(s) really does not exist in the SILC network. */
+    if (!client_id_count) {
+      silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
+                                          SILC_STATUS_ERR_NO_SUCH_NICK,
+                                          3, nick, strlen(nick));
+    } else {
+      SilcBuffer idp = silc_id_payload_encode(client_id[0], SILC_ID_CLIENT);
+      silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
+                                          SILC_STATUS_ERR_NO_SUCH_CLIENT_ID,
+                                          2, idp->data, idp->len);
+      silc_buffer_free(idp);
+    }
     goto out;
   }
 
- ok:
-  /* Send IDENTIFY reply */
-  id_string = silc_id_id2str(entry->id, SILC_ID_CLIENT);
-  tmp = silc_command_get_first_arg(cmd->payload, NULL);
-  sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0);
-  packet = silc_command_encode_payload_va(SILC_COMMAND_IDENTIFY, 3,
-                                         sp_buf->data, sp_buf->len,
-                                         id_string, SILC_ID_CLIENT_LEN,
-                                         nick, strlen(nick));
-  if (cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED) {
-    void *id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type);
-    silc_server_packet_send_dest(cmd->server, cmd->sock, 
-                                SILC_PACKET_COMMAND_REPLY, 0,
-                                id, cmd->packet->src_id_type,
-                                packet->data, packet->len, FALSE);
-    silc_free(id);
-  } else
-    silc_server_packet_send(cmd->server, cmd->sock, 
-                           SILC_PACKET_COMMAND_REPLY, 0, 
-                           packet->data, packet->len, FALSE);
+  /* Router always finds the client entry if it exists in the SILC network.
+     However, it might be incomplete entry and does not include all the
+     mandatory fields that WHOIS command reply requires. Check for these and
+     make query from the server who owns the client if some fields are 
+     missing. */
+  if (!silc_server_command_whois_check(cmd, clients, clients_count)) {
+    ret = -1;
+    goto out;
+  }
 
-  silc_free(id_string);
-  silc_buffer_free(packet);
-  silc_free(sp_buf);
+  /* Send the command reply */
+  silc_server_command_whois_send_reply(cmd, clients, clients_count,
+                                      count, nick, client_id);
 
  out:
-  if (nick)
-    silc_free(nick);
-  if (server)
-    silc_free(server);
-  silc_server_command_free(cmd);
+  if (client_id_count) {
+    for (i = 0; i < client_id_count; i++)
+      silc_free(client_id[i]);
+    silc_free(client_id);
+  }
+  silc_free(clients);
+  silc_free(nick);
+  silc_free(server_name);
+
+  return ret;
 }
 
-/* Checks string for bad characters and returns TRUE if they are found. */
+/* Server side of command WHOIS. Processes user's query and sends found 
+   results as command replies back to the client. */
 
-static int silc_server_command_bad_chars(char *nick)
+SILC_SERVER_CMD_FUNC(whois)
 {
-  if (strchr(nick, '\\')) return TRUE;
-  if (strchr(nick, '\"')) return TRUE;
-  if (strchr(nick, '´')) return TRUE;
-  if (strchr(nick, '`')) return TRUE;
-  if (strchr(nick, '\'')) return TRUE;
-  if (strchr(nick, '*')) return TRUE;
-  if (strchr(nick, '/')) return TRUE;
-  if (strchr(nick, '@')) return TRUE;
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  int ret = 0;
 
-  return FALSE;
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_WHOIS, cmd, 1, 3328);
+
+  ret = silc_server_command_whois_process(cmd);
+  silc_server_command_free(cmd);
 }
 
-/* Server side of command NICK. Sets nickname for user. Setting
-   nickname causes generation of a new client ID for the client. The
-   new client ID is sent to the client after changing the nickname. */
+/******************************************************************************
 
-SILC_SERVER_CMD_FUNC(nick)
-{
-  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  SilcClientList *id_entry = (SilcClientList *)cmd->sock->user_data;
-  SilcServer server = cmd->server;
-  SilcBuffer packet, sp_buf;
-  SilcClientID *new_id;
-  char *id_string;
-  char *nick;
+                              WHOWAS Functions
 
-  SILC_LOG_DEBUG(("Start"));
+******************************************************************************/
 
-#define LCC(x) server->local_list->client_cache[(x) - 32]
-#define LCCC(x) server->local_list->client_cache_count[(x) - 32]
+static int
+silc_server_command_whowas_parse(SilcServerCommandContext cmd,
+                                char **nickname,
+                                char **server_name,
+                                int *count)
+{
+  unsigned char *tmp;
+  uint32 len;
 
-  /* Check number of arguments */
-  if (silc_command_get_arg_num(cmd->payload) < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_NICK,
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOWAS,
                                          SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-
-  /* Check nickname */
-  nick = silc_command_get_arg_type(cmd->payload, 1, NULL);
-  if (silc_server_command_bad_chars(nick) == TRUE) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_NICK,
-                                         SILC_STATUS_ERR_BAD_NICKNAME);
-    goto out;
+    return FALSE;
   }
 
-  /* Create new Client ID */
-  silc_id_create_client_id(cmd->server->id, cmd->server->rng, 
-                          cmd->server->md5hash, nick,
-                          &new_id);
+  /* Get the nickname@server string and parse it. */
+  silc_parse_userfqdn(tmp, nickname, server_name);
 
-  /* Send notify about nickname change to our router. We send the new
-     ID and ask to replace it with the old one. */
-  if (cmd->server->server_type == SILC_SERVER && !cmd->server->standalone)
-    silc_server_send_replace_id(server, server->id_entry->router->connection, 
-                               FALSE, id_entry->id,
-                               SILC_ID_CLIENT, SILC_ID_CLIENT_LEN,
-                               new_id, SILC_ID_CLIENT, SILC_ID_CLIENT_LEN);
-
-  /* If we are router we have to distribute the new Client ID to all 
-     routers in SILC. */
-  if (cmd->server->server_type == SILC_ROUTER && !cmd->server->standalone)
-    silc_server_send_replace_id(server, server->id_entry->router->connection,  
-                               TRUE, id_entry->id,
-                               SILC_ID_CLIENT, SILC_ID_CLIENT_LEN,
-                               new_id, SILC_ID_CLIENT, SILC_ID_CLIENT_LEN);
+  /* Get the max count of reply messages allowed */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, NULL);
+  if (tmp)
+    *count = atoi(tmp);
+  else
+    *count = 0;
 
-  /* Remove old cache entry */
-  silc_idcache_del_by_id(LCC(id_entry->nickname[0]),
-                        LCCC(id_entry->nickname[0]), 
-                        SILC_ID_CLIENT, id_entry->id); 
-  
-  /* Free old ID */
-  if (id_entry->id) {
-    memset(id_entry->id, 0, SILC_ID_CLIENT_LEN);
-    silc_free(id_entry->id);
-  }
+  return TRUE;
+}
 
-  /* Save the nickname as this client is our local client */
-  if (id_entry->nickname)
-    silc_free(id_entry->nickname);
+static char
+silc_server_command_whowas_check(SilcServerCommandContext cmd,
+                                SilcClientEntry *clients,
+                                uint32 clients_count)
+{
+  SilcServer server = cmd->server;
+  int i;
+  SilcClientEntry entry;
 
-  id_entry->nickname = strdup(nick);
-  id_entry->id = new_id;
+  for (i = 0; i < clients_count; i++) {
+    entry = clients[i];
 
-  /* Update client cache */
-  LCCC(nick[0]) = silc_idcache_add(&LCC(nick[0]), LCCC(nick[0]),
-                                  id_entry->nickname, SILC_ID_CLIENT, 
-                                  id_entry->id, (void *)id_entry);
+    if (!entry->nickname || !entry->username) {
+      SilcBuffer tmpbuf;
+      uint16 old_ident;
 
-  /* Send the new Client ID as reply command back to client */
-  id_string = silc_id_id2str(id_entry->id, SILC_ID_CLIENT);
-  sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0);
-  packet = silc_command_encode_payload_va(SILC_COMMAND_NICK, 2, 
-                                         sp_buf->data, sp_buf->len,
-                                         id_string, SILC_ID_CLIENT_LEN);
-  silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
-                         0, packet->data, packet->len, FALSE);
+      if (!entry->router)
+       continue;
+      
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+      /* Send WHOWAS command */
+      silc_server_packet_send(server, entry->router->connection,
+                             SILC_PACKET_COMMAND, cmd->packet->flags,
+                             tmpbuf->data, tmpbuf->len, TRUE);
+      
+      /* Reprocess this packet after received reply */
+      silc_server_command_pending(server, SILC_COMMAND_WHOWAS, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_whowas, 
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_command_set_ident(cmd->payload, old_ident);
 
-  silc_free(id_string);
-  silc_buffer_free(packet);
-  silc_free(sp_buf);
+      silc_buffer_free(tmpbuf);
+      return FALSE;
+    }
+  }
 
- out:
-  silc_server_command_free(cmd);
-#undef LCC
-#undef LCCC
+  return TRUE;
 }
 
+static void
+silc_server_command_whowas_send_reply(SilcServerCommandContext cmd,
+                                     SilcClientEntry *clients,
+                                     uint32 clients_count)
+{
+  SilcServer server = cmd->server;
+  char *tmp;
+  int i, k, count = 0, len;
+  SilcBuffer packet, idp;
+  SilcClientEntry entry = NULL;
+  SilcCommandStatus status;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  char nh[256], uh[256];
+  int valid_count;
+
+  status = SILC_STATUS_OK;
+
+  /* Process only entries that are not registered anymore. */
+  valid_count = 0;
+  for (i = 0; i < clients_count; i++) {
+    if (clients[i]->data.status & SILC_IDLIST_STATUS_REGISTERED)
+      clients[i] = NULL;
+    else
+      valid_count++;
+  }
+
+  if (!valid_count) {
+    /* No valid entries found at all, just send error */
+    unsigned char *tmp;
+    
+    tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+    if (tmp)
+      silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOWAS,
+                                          SILC_STATUS_ERR_NO_SUCH_NICK,
+                                          3, tmp, strlen(tmp));
+    return;
+  }
+
+  if (valid_count > 1)
+    status = SILC_STATUS_LIST_START;
+
+  for (i = 0, k = 0; i < clients_count; i++) {
+    entry = clients[i];
+    if (!entry)
+      continue;
+
+    if (k >= 1)
+      status = SILC_STATUS_LIST_ITEM;
+    if (valid_count > 1 && k == valid_count - 1)
+      status = SILC_STATUS_LIST_END;
+    if (count && k - 1 == count)
+      status = SILC_STATUS_LIST_END;
+    if (count && k - 1 > count)
+      break;
+
+    /* Send WHOWAS reply */
+    idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+    tmp = silc_argument_get_first_arg(cmd->args, NULL);
+    memset(uh, 0, sizeof(uh));
+    memset(nh, 0, sizeof(nh));
+
+    strncat(nh, entry->nickname, strlen(entry->nickname));
+    if (!strchr(entry->nickname, '@')) {
+      strncat(nh, "@", 1);
+      if (entry->servername) {
+       strncat(nh, entry->servername, strlen(entry->servername));
+      } else {
+       len = entry->router ? strlen(entry->router->server_name) :
+         strlen(server->server_name);
+       strncat(nh, entry->router ? entry->router->server_name :
+               server->server_name, len);
+      }
+    }
+      
+    strncat(uh, entry->username, strlen(entry->username));
+    if (!strchr(entry->username, '@')) {
+      strncat(uh, "@", 1);
+      strcat(uh, "*private*");
+    }
+      
+    packet = 
+      silc_command_reply_payload_encode_va(SILC_COMMAND_WHOWAS,
+                                          status, ident, 4, 
+                                          2, idp->data, idp->len,
+                                          3, nh, strlen(nh),
+                                          4, uh, strlen(uh),
+                                          5, entry->userinfo, 
+                                          entry->userinfo ? 
+                                          strlen(entry->userinfo) : 0);
+    silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
+                           0, packet->data, packet->len, FALSE);
+    
+    silc_buffer_free(packet);
+    silc_buffer_free(idp);
+
+    k++;
+  }
+}
+
+static int
+silc_server_command_whowas_process(SilcServerCommandContext cmd)
+{
+  SilcServer server = cmd->server;
+  char *nick = NULL, *server_name = NULL;
+  int count = 0;
+  SilcClientEntry *clients = NULL;
+  uint32 clients_count = 0;
+  int ret = 0;
+  bool check_global = FALSE;
+
+  /* Protocol dictates that we must always send the received WHOWAS request
+     to our router if we are normal server, so let's do it now unless we
+     are standalone. We will not send any replies to the client until we
+     have received reply from the router. */
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT &&
+      server->server_type == SILC_SERVER && !cmd->pending && 
+      !server->standalone) {
+    SilcBuffer tmpbuf;
+    uint16 old_ident;
+
+    old_ident = silc_command_get_ident(cmd->payload);
+    silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+    tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+    /* Send WHOWAS command to our router */
+    silc_server_packet_send(server, (SilcSocketConnection)
+                           server->router->connection,
+                           SILC_PACKET_COMMAND, cmd->packet->flags,
+                           tmpbuf->data, tmpbuf->len, TRUE);
+
+    /* Reprocess this packet after received reply from router */
+    silc_server_command_pending(server, SILC_COMMAND_WHOWAS, 
+                               silc_command_get_ident(cmd->payload),
+                               silc_server_command_whowas,
+                               silc_server_command_dup(cmd));
+    cmd->pending = TRUE;
+    silc_command_set_ident(cmd->payload, old_ident);
+
+    silc_buffer_free(tmpbuf);
+    ret = -1;
+    goto out;
+  }
+
+  /* We are ready to process the command request. Let's search for the
+     requested client and send reply to the requesting client. */
+
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT)
+    check_global = TRUE;
+  else if (server->server_type != SILC_SERVER)
+    check_global = TRUE;
+
+  /* Parse the whowas request */
+  if (!silc_server_command_whowas_parse(cmd, &nick, &server_name, &count))
+    return 0;
+
+  /* Get all clients matching that nickname from local list */
+  if (!silc_idlist_get_clients_by_nickname(server->local_list, 
+                                          nick, server_name,
+                                          &clients, &clients_count))
+    silc_idlist_get_clients_by_hash(server->local_list, 
+                                   nick, server->md5hash,
+                                   &clients, &clients_count);
+  
+  /* Check global list as well */
+  if (check_global) {
+    if (!silc_idlist_get_clients_by_nickname(server->global_list, 
+                                            nick, server_name,
+                                            &clients, &clients_count))
+      silc_idlist_get_clients_by_hash(server->global_list, 
+                                     nick, server->md5hash,
+                                     &clients, &clients_count);
+  }
+  
+  if (!clients) {
+    /* Such a client really does not exist in the SILC network. */
+    silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOWAS,
+                                        SILC_STATUS_ERR_NO_SUCH_NICK,
+                                        3, nick, strlen(nick));
+    goto out;
+  }
+
+  if (!silc_server_command_whowas_check(cmd, clients, clients_count)) {
+    ret = -1;
+    goto out;
+  }
+
+  /* Send the command reply to the client */
+  silc_server_command_whowas_send_reply(cmd, clients, clients_count);
+
+ out:
+  silc_free(clients);
+  silc_free(nick);
+  silc_free(server_name);
+  return ret;
+}
+
+/* Server side of command WHOWAS. */
+
+SILC_SERVER_CMD_FUNC(whowas)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  int ret = 0;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_WHOWAS, cmd, 1, 2);
+
+  ret = silc_server_command_whowas_process(cmd);
+  silc_server_command_free(cmd);
+}
+
+/******************************************************************************
+
+                              IDENTIFY Functions
+
+******************************************************************************/
+
+static void 
+silc_server_command_identify_send_router(SilcServerCommandContext cmd)
+{
+  SilcServer server = cmd->server;
+  SilcBuffer tmpbuf;
+  uint16 old_ident;
+
+  old_ident = silc_command_get_ident(cmd->payload);
+  silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+  tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+  /* Send IDENTIFY command to our router */
+  silc_server_packet_send(server, (SilcSocketConnection)
+                         server->router->connection,
+                         SILC_PACKET_COMMAND, cmd->packet->flags,
+                         tmpbuf->data, tmpbuf->len, TRUE);
+
+  /* Reprocess this packet after received reply from router */
+  silc_server_command_pending(server, SILC_COMMAND_IDENTIFY, 
+                             silc_command_get_ident(cmd->payload),
+                             silc_server_command_identify,
+                             silc_server_command_dup(cmd));
+  cmd->pending = TRUE;
+  silc_command_set_ident(cmd->payload, old_ident);
+  silc_buffer_free(tmpbuf);
+}
+
+static int
+silc_server_command_identify_parse(SilcServerCommandContext cmd,
+                                  SilcClientEntry **clients,
+                                  uint32 *clients_count,
+                                  SilcServerEntry **servers,
+                                  uint32 *servers_count,
+                                  SilcChannelEntry **channels,
+                                  uint32 *channels_count,
+                                  uint32 *count)
+{
+  SilcServer server = cmd->server;
+  unsigned char *tmp;
+  uint32 len;
+  uint32 argc = silc_argument_get_arg_num(cmd->args);
+  SilcIDPayload idp;
+  bool check_global = FALSE;
+  void *entry;
+  int i;
+  bool error = FALSE;
+
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT)
+    check_global = TRUE;
+  else if (server->server_type != SILC_SERVER)
+    check_global = TRUE;
+
+  /* If ID Payload is in the command it must be used instead of names */
+  tmp = silc_argument_get_arg_type(cmd->args, 5, &len);
+  if (!tmp) {
+    /* No ID, get the names. */
+
+    /* If we are normal server and have not resolved information from
+       router yet, do so now. */
+    if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT && 
+       server->server_type == SILC_SERVER && !cmd->pending && 
+       !server->standalone) {
+      silc_server_command_identify_send_router(cmd);
+      return -1;
+    }
+
+    /* Try to get nickname@server. */
+    tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+    if (tmp) {
+      char *nick = NULL;
+      char *nick_server = NULL;
+
+      silc_parse_userfqdn(tmp, &nick, &nick_server);
+
+      if (!silc_idlist_get_clients_by_hash(server->local_list, 
+                                          nick, server->md5hash,
+                                          clients, clients_count))
+       silc_idlist_get_clients_by_nickname(server->local_list, 
+                                           nick, nick_server,
+                                           clients, clients_count);
+      if (check_global) {
+       if (!silc_idlist_get_clients_by_hash(server->global_list, 
+                                            nick, server->md5hash,
+                                            clients, clients_count))
+         silc_idlist_get_clients_by_nickname(server->global_list, 
+                                             nick, nick_server,
+                                             clients, clients_count);
+      }
+
+      silc_free(nick);
+      silc_free(nick_server);
+
+      if (!(*clients)) {
+       /* the nickname does not exist, send error reply */
+       silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+                                            SILC_STATUS_ERR_NO_SUCH_NICK,
+                                            3, tmp, strlen(tmp));
+       return 0;
+      }
+    }
+
+    /* Try to get server name */
+    tmp = silc_argument_get_arg_type(cmd->args, 2, NULL);
+    if (tmp) {
+      entry = silc_idlist_find_server_by_name(server->local_list,
+                                             tmp, TRUE, NULL);
+      if (!entry && check_global)
+       entry = silc_idlist_find_server_by_name(server->global_list,
+                                               tmp, TRUE, NULL);
+      if (entry) {
+       *servers = silc_realloc(*servers, sizeof(**servers) * 
+                               (*servers_count + 1));
+       (*servers)[(*servers_count)++] = entry;
+      }
+
+      if (!(*servers)) {
+       /* the server does not exist, send error reply */
+       silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+                                            SILC_STATUS_ERR_NO_SUCH_SERVER,
+                                            3, tmp, strlen(tmp));
+       return 0;
+      }
+    }
+
+    /* Try to get channel name */
+    tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
+    if (tmp) {
+      entry = silc_idlist_find_channel_by_name(server->local_list,
+                                              tmp, NULL);
+      if (!entry && check_global)
+       entry = silc_idlist_find_channel_by_name(server->global_list,
+                                                tmp, NULL);
+      if (entry) {
+       *channels = silc_realloc(*channels, sizeof(**channels) * 
+                                (*channels_count + 1));
+       (*channels)[(*channels_count)++] = entry;
+      }
+
+      if (!(*channels)) {
+       /* The channel does not exist, send error reply */
+       silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+                                            SILC_STATUS_ERR_NO_SUCH_CHANNEL,
+                                            3, tmp, strlen(tmp));
+       return 0;
+      }
+    }
+
+    if (!(*clients) && !(*servers) && !(*channels)) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY,
+                                           SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      return 0;
+    }
+  } else {
+    /* Command includes ID, we must use that.  Also check whether the command
+       has more than one ID set - take them all. */
+
+    /* Take all ID's from the command packet */
+    for (i = 0; i < argc; i++) {
+      void *id;
+
+      tmp = silc_argument_get_arg_type(cmd->args, i + 5, &len);
+      if (!tmp)
+       continue;
+      
+      idp = silc_id_payload_parse(tmp, len);
+      if (!idp) {
+       silc_free(*clients);
+       silc_free(*servers);
+       silc_free(*channels);
+       silc_server_command_send_status_reply(
+                                      cmd, SILC_COMMAND_IDENTIFY,
+                                      SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       return 0;
+      }
+
+      id = silc_id_payload_get_id(idp);
+      
+      switch (silc_id_payload_get_type(idp)) {
+       
+      case SILC_ID_CLIENT:
+       entry = (void *)silc_idlist_find_client_by_id(server->local_list, 
+                                                     id, TRUE, NULL);
+       if (!entry && check_global)
+         entry = (void *)silc_idlist_find_client_by_id(server->global_list, 
+                                                       id, TRUE, NULL);
+       if (entry) {
+         *clients = silc_realloc(*clients, sizeof(**clients) * 
+                                 (*clients_count + 1));
+         (*clients)[(*clients_count)++] = (SilcClientEntry)entry;
+       } else {
+         /* If we are normal server and have not resolved information from
+            router yet, do so now. */
+         if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT && 
+             server->server_type == SILC_SERVER && !cmd->pending && 
+             !server->standalone) {
+           silc_server_command_identify_send_router(cmd);
+           silc_free(*clients);
+           silc_free(*servers);
+           silc_free(*channels);
+           return -1;
+         } else {
+           silc_server_command_send_status_data(
+                                       cmd, SILC_COMMAND_IDENTIFY,
+                                       SILC_STATUS_ERR_NO_SUCH_CLIENT_ID,
+                                       2, tmp, len);
+           error = TRUE;
+         }
+       }
+
+       break;
+       
+      case SILC_ID_SERVER:
+       entry = (void *)silc_idlist_find_server_by_id(server->local_list, 
+                                                     id, TRUE, NULL);
+       if (!entry && check_global)
+         entry = (void *)silc_idlist_find_server_by_id(server->global_list, 
+                                                       id, TRUE, NULL);
+       if (entry) {
+         *servers = silc_realloc(*servers, sizeof(**servers) * 
+                                 (*servers_count + 1));
+         (*servers)[(*servers_count)++] = (SilcServerEntry)entry;
+       } else {
+         /* If we are normal server and have not resolved information from
+            router yet, do so now. */
+         if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT && 
+             server->server_type == SILC_SERVER && !cmd->pending && 
+             !server->standalone) {
+           silc_server_command_identify_send_router(cmd);
+           silc_free(*clients);
+           silc_free(*servers);
+           silc_free(*channels);
+           return -1;
+         } else {
+           silc_server_command_send_status_data(
+                                        cmd, SILC_COMMAND_IDENTIFY,
+                                        SILC_STATUS_ERR_NO_SUCH_SERVER_ID,
+                                        2, tmp, len);
+           error = TRUE;
+         }
+       }
+       break;
+       
+      case SILC_ID_CHANNEL:
+       entry = (void *)silc_idlist_find_channel_by_id(server->local_list, 
+                                                      id, NULL);
+       if (!entry && check_global)
+         entry = (void *)silc_idlist_find_channel_by_id(server->global_list, 
+                                                        id, NULL);
+       if (entry) {
+         *channels = silc_realloc(*channels, sizeof(**channels) * 
+                                  (*channels_count + 1));
+         (*channels)[(*channels_count)++] = (SilcChannelEntry)entry;
+       } else {
+         /* If we are normal server and have not resolved information from
+            router yet, do so now. */
+         if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT && 
+             server->server_type == SILC_SERVER && !cmd->pending && 
+             !server->standalone) {
+           silc_server_command_identify_send_router(cmd);
+           silc_free(*clients);
+           silc_free(*servers);
+           silc_free(*channels);
+           return -1;
+         } else {
+           silc_server_command_send_status_data(
+                                        cmd, SILC_COMMAND_IDENTIFY,
+                                        SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID,
+                                        2, tmp, len);
+           error = TRUE;
+         }
+       }
+       break;
+      }
+
+      silc_free(id);
+    }
+  }
+
+  if (error) {
+    silc_free(*clients);
+    silc_free(*servers);
+    silc_free(*channels);
+    return FALSE;
+  }
+  
+  /* Get the max count of reply messages allowed */
+  tmp = silc_argument_get_arg_type(cmd->args, 4, NULL);
+  if (tmp)
+    *count = atoi(tmp);
+  else
+    *count = 0;
+
+  return 1;
+}
+
+/* Checks that all mandatory fields in client entry are present. If not
+   then send WHOIS request to the server who owns the client. We use
+   WHOIS because we want to get as much information as possible at once. */
+
+static bool
+silc_server_command_identify_check_client(SilcServerCommandContext cmd,
+                                         SilcClientEntry *clients,
+                                         uint32 clients_count)
+{
+  SilcServer server = cmd->server;
+  SilcClientEntry entry;
+  SilcServerResolveContext resolve = NULL, r = NULL;
+  uint32 resolve_count = 0;
+  int i, k;
+  bool no_res = TRUE;
+
+  for (i = 0; i < clients_count; i++) {
+    entry = clients[i];
+    if (!entry)
+      continue;
+
+    if (entry->nickname || 
+       !(entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) {
+      if (!entry->router)
+       continue;
+
+      /* If we are normal server, and we've not resolved this client from
+        router and it is global client, we'll check whether it is on some
+        channel.  If not then we cannot be sure about its validity, and
+        we'll resolve it from router. */
+      if (cmd->server->server_type != SILC_SERVER || cmd->pending ||
+         entry->connection || silc_hash_table_count(entry->channels))
+       continue;
+    }
+
+    /* We need to resolve this entry since it is not complete */
+
+    if (!cmd->pending && entry->data.status & SILC_IDLIST_STATUS_RESOLVING) {
+      /* The entry is being resolved (and we are not the resolver) so attach
+        to the command reply and we're done with this one. */
+      silc_server_command_pending(server, SILC_COMMAND_NONE, 
+                                 entry->resolve_cmd_ident,
+                                 silc_server_command_identify,
+                                 silc_server_command_dup(cmd));
+      no_res = FALSE;
+    } else {
+      if (entry->data.status & SILC_IDLIST_STATUS_RESOLVING) {
+       /* We've resolved this and it still is not ready.  We'll return
+          and are that this will be handled again after it is resolved. */
+       for (i = 0; i < resolve_count; i++) {
+         for (k = 0; k < r->res_argc; k++)
+           silc_free(r->res_argv[k]);
+         silc_free(r->res_argv);
+         silc_free(r->res_argv_lens);
+         silc_free(r->res_argv_types);
+       }
+       silc_free(resolve);
+       return FALSE;
+      } else {
+       /* We'll resolve this client */
+       SilcBuffer idp;
+
+       r = NULL;
+       for (k = 0; k < resolve_count; k++) {
+         if (resolve[k].router == entry->router) {
+           r = &resolve[k];
+           break;
+         }
+       }
+
+       if (!r) {
+         resolve = silc_realloc(resolve, sizeof(*resolve) * 
+                                (resolve_count + 1));
+         r = &resolve[resolve_count];
+         memset(r, 0, sizeof(*r));
+         r->router = entry->router;
+         r->ident = ++server->cmd_ident;
+         resolve_count++;
+       }
+
+       r->res_argv = silc_realloc(r->res_argv, sizeof(*r->res_argv) *
+                                  (r->res_argc + 1));
+       r->res_argv_lens = silc_realloc(r->res_argv_lens, 
+                                       sizeof(*r->res_argv_lens) *
+                                       (r->res_argc + 1));
+       r->res_argv_types = silc_realloc(r->res_argv_types, 
+                                        sizeof(*r->res_argv_types) *
+                                        (r->res_argc + 1));
+       idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+       r->res_argv[r->res_argc] = silc_calloc(idp->len, 
+                                              sizeof(**r->res_argv));
+       memcpy(r->res_argv[r->res_argc], idp->data, idp->len);
+       r->res_argv_lens[r->res_argc] = idp->len;
+       r->res_argv_types[r->res_argc] = r->res_argc + 3;
+       r->res_argc++;
+       silc_buffer_free(idp);
+
+       entry->resolve_cmd_ident = r->ident;
+       entry->data.status |= SILC_IDLIST_STATUS_RESOLVING;
+       entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVED;
+      }
+    }
+  }
+
+  /* Do the resolving */
+  for (i = 0; i < resolve_count; i++) {
+    SilcBuffer res_cmd;
+
+    r = &resolve[i];
+
+    /* Send WHOIS request. We send WHOIS since we're doing the requesting
+       now anyway so make it a good one. */
+    res_cmd = silc_command_payload_encode(SILC_COMMAND_WHOIS,
+                                         r->res_argc, r->res_argv, 
+                                         r->res_argv_lens,
+                                         r->res_argv_types, 
+                                         r->ident);
+    silc_server_packet_send(server, r->router->connection,
+                           SILC_PACKET_COMMAND, cmd->packet->flags,
+                           res_cmd->data, res_cmd->len, FALSE);
+
+    /* Reprocess this packet after received reply */
+    silc_server_command_pending(server, SILC_COMMAND_WHOIS, 
+                               r->ident,
+                               silc_server_command_identify,
+                               silc_server_command_dup(cmd));
+    cmd->pending = TRUE;
+
+    silc_buffer_free(res_cmd);
+    for (k = 0; k < r->res_argc; k++)
+      silc_free(r->res_argv[k]);
+    silc_free(r->res_argv);
+    silc_free(r->res_argv_lens);
+    silc_free(r->res_argv_types);
+    no_res = FALSE;
+  }
+  silc_free(resolve);
+
+  return no_res;
+}
+
+static void
+silc_server_command_identify_send_reply(SilcServerCommandContext cmd,
+                                       SilcClientEntry *clients,
+                                       uint32 clients_count,
+                                       SilcServerEntry *servers,
+                                       uint32 servers_count,
+                                       SilcChannelEntry *channels,
+                                       uint32 channels_count,
+                                       int count)
+{
+  SilcServer server = cmd->server;
+  int i, k, len, valid_count;
+  SilcBuffer packet, idp;
+  SilcCommandStatus status;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  char nh[256], uh[256];
+  SilcSocketConnection hsock;
+
+  status = SILC_STATUS_OK;
+
+  if (clients) {
+    SilcClientEntry entry;
+
+    /* Process only valid entries. */
+    valid_count = 0;
+    for (i = 0; i < clients_count; i++) {
+      if (clients[i]->data.status & SILC_IDLIST_STATUS_REGISTERED)
+       valid_count++;
+      else
+       clients[i] = NULL;
+    }
+
+    if (!valid_count) {
+      /* No valid entries found at all, just send error */
+      unsigned char *tmp;
+
+      tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+      if (tmp) {
+       silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+                                            SILC_STATUS_ERR_NO_SUCH_NICK,
+                                            3, tmp, strlen(tmp));
+      } else {
+       tmp = silc_argument_get_arg_type(cmd->args, 5, (uint32 *)&len);
+       silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+                                            SILC_STATUS_ERR_NO_SUCH_CLIENT_ID,
+                                            2, tmp, len);
+      }
+      return;
+    }
+
+    /* Process all valid client entries and send command replies */
+
+    if (valid_count > 1)
+      status = SILC_STATUS_LIST_START;
+
+    for (i = 0, k = 0; i < clients_count; i++) {
+      entry = clients[i];
+      if (!entry)
+       continue;
+
+      if (k >= 1)
+       status = SILC_STATUS_LIST_ITEM;
+      if (valid_count > 1 && k == valid_count - 1 
+         && !servers_count && !channels_count)
+       status = SILC_STATUS_LIST_END;
+      if (count && k - 1 == count)
+       status = SILC_STATUS_LIST_END;
+      if (count && k - 1 > count)
+       break;
+
+      /* Send IDENTIFY reply */
+
+      idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+      memset(uh, 0, sizeof(uh));
+      memset(nh, 0, sizeof(nh));
+      strncat(nh, entry->nickname, strlen(entry->nickname));
+      if (!strchr(entry->nickname, '@')) {
+       strncat(nh, "@", 1);
+       if (entry->servername) {
+         strncat(nh, entry->servername, strlen(entry->servername));
+       } else {
+         len = entry->router ? strlen(entry->router->server_name) :
+           strlen(server->server_name);
+         strncat(nh, entry->router ? entry->router->server_name :
+                 server->server_name, len);
+       }
+      }
+
+      if (!entry->username) {
+       packet = silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY,
+                                                     status, ident, 2,
+                                                     2, idp->data, idp->len, 
+                                                     3, nh, strlen(nh));
+      } else {
+       strncat(uh, entry->username, strlen(entry->username));
+       if (!strchr(entry->username, '@')) {
+         strncat(uh, "@", 1);
+         hsock = (SilcSocketConnection)entry->connection;
+         len = strlen(hsock->hostname);
+         strncat(uh, hsock->hostname, len);
+       }
+       
+       packet = silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY,
+                                                     status, ident, 3,
+                                                     2, idp->data, idp->len, 
+                                                     3, nh, strlen(nh),
+                                                     4, uh, strlen(uh));
+      }
+      
+      silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
+                             0, packet->data, packet->len, FALSE);
+      
+      silc_buffer_free(packet);
+      silc_buffer_free(idp);
+      
+      k++;
+    }
+  }
+
+  if (servers) {
+    SilcServerEntry entry;
+
+    if (status == SILC_STATUS_OK && servers_count > 1)
+      status = SILC_STATUS_LIST_START;
+
+    for (i = 0, k = 0; i < servers_count; i++) {
+      entry = servers[i];
+      
+      if (k >= 1)
+       status = SILC_STATUS_LIST_ITEM;
+      if (servers_count > 1 && k == servers_count - 1 && !channels_count)
+       status = SILC_STATUS_LIST_END;
+      if (count && k - 1 == count)
+       status = SILC_STATUS_LIST_END;
+      if (count && k - 1 > count)
+       break;
+      
+      /* Send IDENTIFY reply */
+      idp = silc_id_payload_encode(entry->id, SILC_ID_SERVER);
+      packet = 
+       silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY,
+                                            status, ident, 2,
+                                            2, idp->data, idp->len, 
+                                            3, entry->server_name, 
+                                            entry->server_name ? 
+                                            strlen(entry->server_name) : 0);
+      silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
+                             0, packet->data, packet->len, FALSE);
+      
+      silc_buffer_free(packet);
+      silc_buffer_free(idp);
+      
+      k++;
+    }
+  }
+
+  if (channels) {
+    SilcChannelEntry entry;
+
+    if (status == SILC_STATUS_OK && channels_count > 1)
+      status = SILC_STATUS_LIST_START;
+
+    for (i = 0, k = 0; i < channels_count; i++) {
+      entry = channels[i];
+      
+      if (k >= 1)
+       status = SILC_STATUS_LIST_ITEM;
+      if (channels_count > 1 && k == channels_count - 1)
+       status = SILC_STATUS_LIST_END;
+      if (count && k - 1 == count)
+       status = SILC_STATUS_LIST_END;
+      if (count && k - 1 > count)
+       break;
+      
+      /* Send IDENTIFY reply */
+      idp = silc_id_payload_encode(entry->id, SILC_ID_CHANNEL);
+      packet = 
+       silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY,
+                                            status, ident, 2,
+                                            2, idp->data, idp->len, 
+                                            3, entry->channel_name, 
+                                            entry->channel_name ? 
+                                            strlen(entry->channel_name): 0);
+      silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
+                             0, packet->data, packet->len, FALSE);
+      
+      silc_buffer_free(packet);
+      silc_buffer_free(idp);
+      
+      k++;
+    }
+  }
+}
+
+static int
+silc_server_command_identify_process(SilcServerCommandContext cmd)
+{
+  uint32 count = 0;
+  int ret = 0;
+  SilcClientEntry *clients = NULL;
+  SilcServerEntry *servers = NULL;
+  SilcChannelEntry *channels = NULL;
+  uint32 clients_count = 0, servers_count = 0, channels_count = 0;
+
+  /* Parse the IDENTIFY request */
+  ret = silc_server_command_identify_parse(cmd,
+                                          &clients, &clients_count,
+                                          &servers, &servers_count,
+                                          &channels, &channels_count,
+                                          &count);
+  if (ret < 1)
+    return ret;
+  ret = 0;
+
+  /* Check that all mandatory fields are present and request those data
+     from the server who owns the client if necessary. */
+  if (clients && !silc_server_command_identify_check_client(cmd, clients, 
+                                                           clients_count)) {
+    ret = -1;
+    goto out;
+  }
+
+  /* Send the command reply to the client */
+  silc_server_command_identify_send_reply(cmd, 
+                                         clients, clients_count,
+                                         servers, servers_count,
+                                         channels, channels_count, 
+                                         count);
+
+ out:
+  silc_free(clients);
+  silc_free(servers);
+  silc_free(channels);
+  return ret;
+}
+
+SILC_SERVER_CMD_FUNC(identify)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  int ret = 0;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_IDENTIFY, cmd, 1, 3328);
+
+  ret = silc_server_command_identify_process(cmd);
+  silc_server_command_free(cmd);
+}
+
+/* Server side of command NICK. Sets nickname for user. Setting
+   nickname causes generation of a new client ID for the client. The
+   new client ID is sent to the client after changing the nickname. */
+
+SILC_SERVER_CMD_FUNC(nick)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcServer server = cmd->server;
+  SilcBuffer packet, nidp, oidp = NULL;
+  SilcClientID *new_id;
+  uint32 nick_len;
+  char *nick;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  int nickfail = 0;
+
+  if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_NICK, cmd, 1, 1);
+
+  /* Check nickname */
+  nick = silc_argument_get_arg_type(cmd->args, 1, &nick_len);
+  if (nick_len > 128)
+    nick[128] = '\0';
+  if (silc_server_name_bad_chars(nick, nick_len) == TRUE) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_NICK,
+                                         SILC_STATUS_ERR_BAD_NICKNAME);
+    goto out;
+  }
+
+  /* Check for same nickname */
+  if (!strcmp(client->nickname, nick)) {
+    nidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+    goto send_reply;
+  }
+
+  /* Create new Client ID */
+  while (!silc_id_create_client_id(cmd->server, cmd->server->id, 
+                                  cmd->server->rng, 
+                                  cmd->server->md5hash, nick,
+                                  &new_id)) {
+    nickfail++;
+    snprintf(&nick[strlen(nick) - 1], 1, "%d", nickfail);
+  }
+
+  /* Send notify about nickname change to our router. We send the new
+     ID and ask to replace it with the old one. If we are router the
+     packet is broadcasted. Send NICK_CHANGE notify. */
+  if (!server->standalone)
+    silc_server_send_notify_nick_change(server, server->router->connection, 
+                                       server->server_type == SILC_SERVER ? 
+                                       FALSE : TRUE, client->id,
+                                       new_id);
+
+  oidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+
+  /* Remove old cache entry */
+  silc_idcache_del_by_context(server->local_list->clients, client);
+
+  /* Free old ID */
+  silc_free(client->id);
+
+  /* Save the nickname as this client is our local client */
+  silc_free(client->nickname);
+
+  client->nickname = strdup(nick);
+  client->id = new_id;
+
+  /* Update client cache */
+  silc_idcache_add(server->local_list->clients, client->nickname, 
+                  client->id, (void *)client, 0, NULL);
+
+  nidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+
+  /* Send NICK_CHANGE notify to the client's channels */
+  silc_server_send_notify_on_channels(server, NULL, client, 
+                                     SILC_NOTIFY_TYPE_NICK_CHANGE, 2,
+                                     oidp->data, oidp->len, 
+                                     nidp->data, nidp->len);
+
+ send_reply:
+  /* Send the new Client ID as reply command back to client */
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_NICK, 
+                                               SILC_STATUS_OK, ident, 1, 
+                                               2, nidp->data, nidp->len);
+  silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
+                         0, packet->data, packet->len, FALSE);
+
+  silc_buffer_free(packet);
+  silc_buffer_free(nidp);
+  if (oidp)
+    silc_buffer_free(oidp);
+  
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Sends the LIST command reply */
+
+static void
+silc_server_command_list_send_reply(SilcServerCommandContext cmd,
+                                   SilcChannelEntry *lch, 
+                                   uint32 lch_count,
+                                   SilcChannelEntry *gch,
+                                   uint32 gch_count)
+{
+  int i, k;
+  SilcBuffer packet, idp;
+  SilcChannelEntry entry;
+  SilcCommandStatus status;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  char *topic;
+  unsigned char usercount[4];
+  uint32 users;
+  int valid_lcount = 0, valid_rcount = 0;
+
+  for (i = 0; i < lch_count; i++) {
+    if (lch[i]->mode & SILC_CHANNEL_MODE_SECRET)
+      lch[i] = NULL;
+    else
+      valid_lcount++;
+  }
+  for (i = 0; i < gch_count; i++) {
+    if (gch[i]->mode & SILC_CHANNEL_MODE_SECRET)
+      gch[i] = NULL;
+    else
+      valid_rcount++;
+  }
+
+  status = SILC_STATUS_OK;
+  if ((lch_count + gch_count) > 1)
+    status = SILC_STATUS_LIST_START;
+
+  /* Local list */
+  for (i = 0, k = 0; i < lch_count; i++) {
+    entry = lch[i];
+    if (!entry)
+      continue;
+
+    if (k >= 1)
+      status = SILC_STATUS_LIST_ITEM;
+    if (valid_lcount > 1 && k == valid_lcount - 1 && !valid_rcount)
+      status = SILC_STATUS_LIST_END;
+
+    idp = silc_id_payload_encode(entry->id, SILC_ID_CHANNEL);
+
+    if (entry->mode & SILC_CHANNEL_MODE_PRIVATE) {
+      topic = "*private*";
+      memset(usercount, 0, sizeof(usercount));
+    } else {
+      topic = entry->topic;
+      users = silc_hash_table_count(entry->user_list);
+      SILC_PUT32_MSB(users, usercount);
+    }
+
+    /* Send the reply */
+    packet = 
+      silc_command_reply_payload_encode_va(SILC_COMMAND_LIST, 
+                                          status, ident, 4,
+                                          2, idp->data, idp->len,
+                                          3, entry->channel_name, 
+                                          strlen(entry->channel_name),
+                                          4, topic, topic ? strlen(topic) : 0,
+                                          5, usercount, 4);
+    silc_server_packet_send(cmd->server, cmd->sock, 
+                           SILC_PACKET_COMMAND_REPLY, 0, packet->data, 
+                           packet->len, FALSE);
+    silc_buffer_free(packet);
+    silc_buffer_free(idp);
+    k++;
+  }
+
+  /* Global list */
+  for (i = 0, k = 0; i < gch_count; i++) {
+    entry = gch[i];
+    if (!entry)
+      continue;
+
+    if (k >= 1)
+      status = SILC_STATUS_LIST_ITEM;
+    if (valid_rcount > 1 && k == valid_rcount - 1)
+      status = SILC_STATUS_LIST_END;
+
+    idp = silc_id_payload_encode(entry->id, SILC_ID_CHANNEL);
+
+    if (entry->mode & SILC_CHANNEL_MODE_PRIVATE) {
+      topic = "*private*";
+      memset(usercount, 0, sizeof(usercount));
+    } else {
+      topic = entry->topic;
+      users = silc_hash_table_count(entry->user_list);
+      SILC_PUT32_MSB(users, usercount);
+    }
+
+    /* Send the reply */
+    packet = 
+      silc_command_reply_payload_encode_va(SILC_COMMAND_LIST, 
+                                          status, ident, 4,
+                                          2, idp->data, idp->len,
+                                          3, entry->channel_name, 
+                                          strlen(entry->channel_name),
+                                          4, topic, topic ? strlen(topic) : 0,
+                                          5, usercount, 4);
+    silc_server_packet_send(cmd->server, cmd->sock, 
+                           SILC_PACKET_COMMAND_REPLY, 0, packet->data, 
+                           packet->len, FALSE);
+    silc_buffer_free(packet);
+    silc_buffer_free(idp);
+    k++;
+  }
+}
+
+/* Server side of LIST command. This lists the channel of the requested
+   server. Secret channels are not listed. */
+
 SILC_SERVER_CMD_FUNC(list)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcChannelID *channel_id = NULL;
+  unsigned char *tmp;
+  uint32 tmp_len;
+  SilcChannelEntry *lchannels = NULL, *gchannels = NULL;
+  uint32 lch_count = 0, gch_count = 0;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_LIST, cmd, 0, 1);
+
+  /* If we are normal server, send the command to router, since we
+     want to know all channels in the network. */
+  if (!cmd->pending && server->server_type == SILC_SERVER && 
+      !server->standalone) {
+    SilcBuffer tmpbuf;
+    uint16 old_ident;
+    
+    old_ident = silc_command_get_ident(cmd->payload);
+    silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+    tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+    silc_server_packet_send(server, server->router->connection,
+                           SILC_PACKET_COMMAND, cmd->packet->flags,
+                           tmpbuf->data, tmpbuf->len, TRUE);
+
+    /* Reprocess this packet after received reply from router */
+    silc_server_command_pending(server, SILC_COMMAND_LIST, 
+                               silc_command_get_ident(cmd->payload),
+                               silc_server_command_list, 
+                               silc_server_command_dup(cmd));
+    cmd->pending = TRUE;
+    silc_command_set_ident(cmd->payload, old_ident);
+    silc_buffer_free(tmpbuf);
+    goto out;
+  }
+
+  /* Get Channel ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (tmp) {
+    channel_id = silc_id_payload_parse_id(tmp, tmp_len);
+    if (!channel_id) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_LIST,
+                                           SILC_STATUS_ERR_NO_CHANNEL_ID);
+      goto out;
+    }
+  }
+
+  /* Get the channels from local list */
+  lchannels = silc_idlist_get_channels(server->local_list, channel_id,
+                                      &lch_count);
+  
+  /* Get the channels from global list */
+  gchannels = silc_idlist_get_channels(server->global_list, channel_id,
+                                      &gch_count);
+
+  /* Send the reply */
+  silc_server_command_list_send_reply(cmd, lchannels, lch_count, 
+                                     gchannels, gch_count);
+
+  silc_free(lchannels);
+  silc_free(gchannels);
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Server side of TOPIC command. Sets topic for channel and/or returns
+   current topic to client. */
+
+SILC_SERVER_CMD_FUNC(topic)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcChannelID *channel_id;
+  SilcChannelEntry channel;
+  SilcChannelClientEntry chl;
+  SilcBuffer packet, idp;
+  unsigned char *tmp;
+  uint32 argc, tmp_len;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_TOPIC, cmd, 1, 2);
+
+  argc = silc_argument_get_arg_num(cmd->args);
+
+  /* Get Channel ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+  channel_id = silc_id_payload_parse_id(tmp, tmp_len);
+  if (!channel_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+
+  /* Check whether the channel exists */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
+  if (!channel) {
+    channel = silc_idlist_find_channel_by_id(server->global_list, 
+                                            channel_id, NULL);
+    if (!channel) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
+    }
+  }
+
+  if (argc > 1) {
+    /* Get the topic */
+    tmp = silc_argument_get_arg_type(cmd->args, 2, NULL);
+    if (!tmp) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC,
+                                           SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      goto out;
+    }
+
+    if (strlen(tmp) > 256) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC,
+                                           SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      goto out;
+    }
+
+    /* See whether the client is on channel and has rights to change topic */
+    if (!silc_hash_table_find(channel->user_list, client, NULL, 
+                             (void *)&chl)) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC,
+                                           SILC_STATUS_ERR_NOT_ON_CHANNEL);
+      goto out;
+    }
+
+    if (chl->mode == SILC_CHANNEL_UMODE_NONE) {
+      if (channel->mode & SILC_CHANNEL_MODE_TOPIC) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC,
+                                             SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+       goto out;
+      }
+    }
+
+    /* Set the topic for channel */
+    silc_free(channel->topic);
+    channel->topic = strdup(tmp);
+
+    /* Send TOPIC_SET notify type to the network */
+    if (!server->standalone)
+      silc_server_send_notify_topic_set(server, server->router->connection,
+                                       server->server_type == SILC_ROUTER ?
+                                       TRUE : FALSE, channel, 
+                                       client->id, SILC_ID_CLIENT,
+                                       channel->topic);
+
+    idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+
+    /* Send notify about topic change to all clients on the channel */
+    silc_server_send_notify_to_channel(server, NULL, channel, FALSE, 
+                                      SILC_NOTIFY_TYPE_TOPIC_SET, 2,
+                                      idp->data, idp->len,
+                                      channel->topic, strlen(channel->topic));
+    silc_buffer_free(idp);
+  }
+
+  /* Send the topic to client as reply packet */
+  idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL);
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_TOPIC, 
+                                               SILC_STATUS_OK, ident, 2, 
+                                               2, idp->data, idp->len,
+                                               3, channel->topic, 
+                                               channel->topic ? 
+                                               strlen(channel->topic) : 0);
+  silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
+                         0, packet->data, packet->len, FALSE);
+
+  silc_buffer_free(packet);
+  silc_buffer_free(idp);
+  silc_free(channel_id);
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Server side of INVITE command. Invites some client to join some channel. 
+   This command is also used to manage the invite list of the channel. */
+
+SILC_SERVER_CMD_FUNC(invite)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcSocketConnection sock = cmd->sock, dest_sock;
+  SilcChannelClientEntry chl;
+  SilcClientEntry sender, dest;
+  SilcClientID *dest_id = NULL;
+  SilcChannelEntry channel;
+  SilcChannelID *channel_id = NULL;
+  SilcIDListData idata;
+  SilcBuffer idp, idp2, packet;
+  unsigned char *tmp, *add, *del;
+  uint32 len;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_INVITE, cmd, 1, 4);
+
+  /* Get Channel ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+  channel_id = silc_id_payload_parse_id(tmp, len);
+  if (!channel_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+
+  /* Get the channel entry */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
+  if (!channel) {
+    channel = silc_idlist_find_channel_by_id(server->global_list, 
+                                            channel_id, NULL);
+    if (!channel) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
+    }
+  }
+
+  /* Check whether the sender of this command is on the channel. */
+  sender = (SilcClientEntry)sock->user_data;
+  if (!silc_server_client_on_channel(sender, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
+  }
+
+  /* Check whether the channel is invite-only channel. If yes then the
+     sender of this command must be at least channel operator. */
+  if (channel->mode & SILC_CHANNEL_MODE_INVITE) {
+    silc_hash_table_find(channel->user_list, sender, NULL, (void *)&chl);
+    if (chl->mode == SILC_CHANNEL_UMODE_NONE) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                           SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+      goto out;
+    }
+  }
+
+  /* Get destination client ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
+  if (tmp) {
+    char invite[512];
+    bool resolve;
+
+    dest_id = silc_id_payload_parse_id(tmp, len);
+    if (!dest_id) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                           SILC_STATUS_ERR_NO_CLIENT_ID);
+      goto out;
+    }
+
+    /* Get the client entry */
+    dest = silc_server_get_client_resolve(server, dest_id, &resolve);
+    if (!dest) {
+      if (server->server_type != SILC_SERVER || !resolve) {
+       silc_server_command_send_status_reply(
+                                       cmd, SILC_COMMAND_INVITE,
+                                       SILC_STATUS_ERR_NO_SUCH_CLIENT_ID);
+       goto out;
+      }
+      
+      /* The client info is being resolved. Reprocess this packet after
+        receiving the reply to the query. */
+      silc_server_command_pending(server, SILC_COMMAND_WHOIS, 
+                                 server->cmd_ident,
+                                 silc_server_command_invite, 
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_free(channel_id);
+      silc_free(dest_id);
+      goto out;
+    }
+
+    /* Check whether the requested client is already on the channel. */
+    if (silc_server_client_on_channel(dest, channel)) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                           SILC_STATUS_ERR_USER_ON_CHANNEL);
+      goto out;
+    }
+    
+    /* Get route to the client */
+    dest_sock = silc_server_get_client_route(server, NULL, 0, dest_id, &idata);
+    if (!dest_sock) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                           SILC_STATUS_ERR_NO_SUCH_CLIENT_ID);
+      goto out;
+    }
+
+    memset(invite, 0, sizeof(invite));
+    strncat(invite, dest->nickname, strlen(dest->nickname));
+    strncat(invite, "!", 1);
+    strncat(invite, dest->username, strlen(dest->username));
+    if (!strchr(dest->username, '@')) {
+      strncat(invite, "@", 1);
+      strncat(invite, cmd->sock->hostname, strlen(cmd->sock->hostname));
+    }
+
+    len = strlen(invite);
+    if (!channel->invite_list)
+      channel->invite_list = silc_calloc(len + 2, 
+                                        sizeof(*channel->invite_list));
+    else
+      channel->invite_list = silc_realloc(channel->invite_list, 
+                                         sizeof(*channel->invite_list) * 
+                                         (len + 
+                                          strlen(channel->invite_list) + 2));
+    strncat(channel->invite_list, invite, len);
+    strncat(channel->invite_list, ",", 1);
+
+    /* Send notify to the client that is invited to the channel */
+    idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL);
+    idp2 = silc_id_payload_encode(sender->id, SILC_ID_CLIENT);
+    silc_server_send_notify_dest(server, dest_sock, FALSE, dest_id, 
+                                SILC_ID_CLIENT,
+                                SILC_NOTIFY_TYPE_INVITE, 3, 
+                                idp->data, idp->len, 
+                                channel->channel_name, 
+                                strlen(channel->channel_name),
+                                idp2->data, idp2->len);
+    silc_buffer_free(idp);
+    silc_buffer_free(idp2);
+  }
+
+  /* Add the client to the invite list of the channel */
+  add = silc_argument_get_arg_type(cmd->args, 3, &len);
+  if (add) {
+    if (!channel->invite_list)
+      channel->invite_list = silc_calloc(len + 2, 
+                                        sizeof(*channel->invite_list));
+    else
+      channel->invite_list = silc_realloc(channel->invite_list, 
+                                         sizeof(*channel->invite_list) * 
+                                         (len + 
+                                          strlen(channel->invite_list) + 2));
+    if (add[len - 1] == ',')
+      add[len - 1] = '\0';
+    
+    strncat(channel->invite_list, add, len);
+    strncat(channel->invite_list, ",", 1);
+  }
+
+  /* Get the invite to be removed and remove it from the list */
+  del = silc_argument_get_arg_type(cmd->args, 4, &len);
+  if (del && channel->invite_list) {
+    char *start, *end, *n;
+
+    if (!strncmp(channel->invite_list, del, 
+                strlen(channel->invite_list) - 1)) {
+      silc_free(channel->invite_list);
+      channel->invite_list = NULL;
+    } else {
+      start = strstr(channel->invite_list, del);
+      if (start && strlen(start) >= len) {
+       end = start + len;
+       n = silc_calloc(strlen(channel->invite_list) - len, sizeof(*n));
+       strncat(n, channel->invite_list, start - channel->invite_list);
+       strncat(n, end + 1, ((channel->invite_list + 
+                             strlen(channel->invite_list)) - end) - 1);
+       silc_free(channel->invite_list);
+       channel->invite_list = n;
+      }
+    }
+  }
+
+  /* Send notify to the primary router */
+  if (!server->standalone)
+    silc_server_send_notify_invite(server, server->router->connection,
+                                  server->server_type == SILC_ROUTER ?
+                                  TRUE : FALSE, channel,
+                                  sender->id, add, del);
+
+  /* Send command reply */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &len);
+
+  if (add || del)
+    packet = 
+      silc_command_reply_payload_encode_va(SILC_COMMAND_INVITE,
+                                          SILC_STATUS_OK, ident, 2,
+                                          2, tmp, len,
+                                          3, channel->invite_list,
+                                          channel->invite_list ?
+                                          strlen(channel->invite_list) : 0);
+  else
+    packet = 
+      silc_command_reply_payload_encode_va(SILC_COMMAND_INVITE,
+                                          SILC_STATUS_OK, ident, 1,
+                                          2, tmp, len);
+  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         packet->data, packet->len, FALSE);
+  silc_buffer_free(packet);
+
+ out:
+  silc_free(dest_id);
+  silc_free(channel_id);
+  silc_server_command_free(cmd);
+}
+
+typedef struct {
+  SilcServer server;
+  SilcSocketConnection sock;
+  char *signoff;
+} *QuitInternal;
+
+/* Quits connection to client. This gets called if client won't
+   close the connection even when it has issued QUIT command. */
+
+SILC_TASK_CALLBACK(silc_server_command_quit_cb)
+{
+  QuitInternal q = (QuitInternal)context;
+
+  /* Free all client specific data, such as client entry and entires
+     on channels this client may be on. */
+  silc_server_free_client_data(q->server, q->sock, q->sock->user_data,
+                              TRUE, q->signoff);
+  q->sock->user_data = NULL;
+
+  /* Close the connection on our side */
+  silc_server_close_connection(q->server, q->sock);
+
+  silc_free(q->signoff);
+  silc_free(q);
+}
+
+/* Quits SILC session. This is the normal way to disconnect client. */
+SILC_SERVER_CMD_FUNC(quit)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcSocketConnection sock = cmd->sock;
+  QuitInternal q;
+  unsigned char *tmp = NULL;
+  uint32 len = 0;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_QUIT, cmd, 0, 1);
+
+  if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
+
+  /* Get destination ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &len);
+  if (len > 128)
+    tmp = NULL;
+
+  q = silc_calloc(1, sizeof(*q));
+  q->server = server;
+  q->sock = sock;
+  q->signoff = tmp ? strdup(tmp) : NULL;
+
+  /* We quit the connection with little timeout */
+  silc_schedule_task_add(server->schedule, sock->sock,
+                    silc_server_command_quit_cb, (void *)q,
+                    0, 200000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Server side of command KILL. This command is used by router operator
+   to remove an client from the SILC Network temporarily. */
+
+SILC_SERVER_CMD_FUNC(kill)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcClientEntry remote_client;
+  SilcClientID *client_id;
+  unsigned char *tmp, *comment;
+  uint32 tmp_len, tmp_len2;
+  bool local;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_KILL, cmd, 1, 2);
+
+  if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
+
+  /* KILL command works only on router */
+  if (server->server_type != SILC_ROUTER) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL,
+                                         SILC_STATUS_ERR_NO_ROUTER_PRIV);
+    goto out;
+  }
+
+  /* Check whether client has the permissions. */
+  if (!(client->mode & SILC_UMODE_ROUTER_OPERATOR)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL,
+                                         SILC_STATUS_ERR_NO_ROUTER_PRIV);
+    goto out;
+  }
+
+  /* Get the client ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+  client_id = silc_id_payload_parse_id(tmp, tmp_len);
+  if (!client_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL,
+                                         SILC_STATUS_ERR_NO_SUCH_CLIENT_ID);
+    goto out;
+  }
+
+  /* Get the client entry */
+  remote_client = silc_idlist_find_client_by_id(server->local_list, 
+                                               client_id, TRUE, NULL);
+  local = TRUE;
+  if (!remote_client) {
+    remote_client = silc_idlist_find_client_by_id(server->global_list, 
+                                                 client_id, TRUE, NULL);
+    local = FALSE;
+    if (!remote_client) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL,
+                                           SILC_STATUS_ERR_NO_SUCH_CLIENT_ID);
+      goto out;
+    }
+  }
+
+  /* Get comment */
+  comment = silc_argument_get_arg_type(cmd->args, 2, &tmp_len2);
+  if (tmp_len2 > 128)
+    comment = NULL;
+
+  /* Send reply to the sender */
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL,
+                                       SILC_STATUS_OK);
+
+  /* Send the KILL notify packets. First send it to the channel, then
+     to our primary router and then directly to the client who is being
+     killed right now. */
+
+  /* Send KILLED notify to the channels. It is not sent to the client
+     as it will be sent differently destined directly to the client and not
+     to the channel. */
+  silc_server_send_notify_on_channels(server, remote_client, 
+                                     remote_client, SILC_NOTIFY_TYPE_KILLED,
+                                     comment ? 2 : 1,
+                                     tmp, tmp_len,
+                                     comment, comment ? tmp_len2 : 0);
+
+  /* Send KILLED notify to primary route */
+  if (!server->standalone)
+    silc_server_send_notify_killed(server, server->router->connection, TRUE,
+                                  remote_client->id, comment);
+
+  /* Send KILLED notify to the client directly */
+  silc_server_send_notify_killed(server, remote_client->connection ? 
+                                remote_client->connection : 
+                                remote_client->router->connection, FALSE,
+                                remote_client->id, comment);
+
+  /* Remove the client from all channels. This generates new keys to the
+     channels as well. */
+  silc_server_remove_from_channels(server, NULL, remote_client, FALSE, 
+                                  NULL, TRUE);
+
+  /* Remove the client entry, If it is locally connected then we will also
+     disconnect the client here */
+  if (remote_client->connection) {
+    /* Remove locally conneted client */
+    SilcSocketConnection sock = remote_client->connection;
+    silc_server_free_client_data(server, sock, remote_client, FALSE, NULL);
+    silc_server_close_connection(server, sock);
+  } else {
+    /* Update statistics */
+    if (remote_client->connection)
+      server->stat.my_clients--;
+    if (server->server_type == SILC_ROUTER)
+      server->stat.cell_clients--;
+    SILC_OPER_STATS_UPDATE(remote_client, server, SILC_UMODE_SERVER_OPERATOR);
+    SILC_OPER_STATS_UPDATE(remote_client, router, SILC_UMODE_ROUTER_OPERATOR);
+
+    /* Remove remote client */
+    silc_idlist_del_client(local ? server->local_list :
+                          server->global_list, remote_client);
+  }
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Server side of command INFO. This sends information about us to 
+   the client. If client requested specific server we will send the 
+   command to that server. */
+
+SILC_SERVER_CMD_FUNC(info)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcBuffer packet, idp;
+  unsigned char *tmp;
+  uint32 tmp_len;
+  char *dest_server, *server_info = NULL, *server_name;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  SilcServerEntry entry = NULL;
+  SilcServerID *server_id = NULL;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_INFO, cmd, 0, 2);
+
+  /* Get server name */
+  dest_server = silc_argument_get_arg_type(cmd->args, 1, NULL);
+
+  /* Get Server ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (tmp) {
+    server_id = silc_id_payload_parse_id(tmp, tmp_len);
+    if (!server_id) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO,
+                                           SILC_STATUS_ERR_NO_SERVER_ID);
+      goto out;
+    }
+  }
+
+  if (server_id) {
+    /* Check whether we have this server cached */
+    entry = silc_idlist_find_server_by_id(server->local_list,
+                                         server_id, TRUE, NULL);
+    if (!entry) {
+      entry = silc_idlist_find_server_by_id(server->global_list,
+                                           server_id, TRUE, NULL);
+      if (!entry && server->server_type != SILC_SERVER) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO,
+                                             SILC_STATUS_ERR_NO_SUCH_SERVER);
+       goto out;
+      }
+    }
+  }
+
+  /* Some buggy servers has sent request to router about themselves. */
+  if (server->server_type != SILC_SERVER && cmd->sock->user_data == entry)
+    goto out;
+
+  if ((!dest_server && !server_id && !entry) || (entry && 
+                                                entry == server->id_entry) ||
+      (dest_server && !cmd->pending && 
+       !strncasecmp(dest_server, server->server_name, strlen(dest_server)))) {
+    /* Send our reply */
+    char info_string[256];
+
+    memset(info_string, 0, sizeof(info_string));
+    snprintf(info_string, sizeof(info_string), 
+            "location: %s server: %s admin: %s <%s>",
+            server->config->server_info->location,
+            server->config->server_info->server_type,
+            server->config->server_info->admin,
+            server->config->server_info->email);
+
+    server_info = info_string;
+    entry = server->id_entry;
+  } else {
+    /* Check whether we have this server cached */
+    if (!entry && dest_server) {
+      entry = silc_idlist_find_server_by_name(server->global_list,
+                                             dest_server, TRUE, NULL);
+      if (!entry) {
+       entry = silc_idlist_find_server_by_name(server->local_list,
+                                               dest_server, TRUE, NULL);
+      }
+    }
+
+    if (!cmd->pending &&
+       server->server_type != SILC_SERVER && entry && !entry->server_info) {
+      /* Send to the server */
+      SilcBuffer tmpbuf;
+      uint16 old_ident;
+
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+      silc_server_packet_send(server, entry->connection,
+                             SILC_PACKET_COMMAND, cmd->packet->flags,
+                             tmpbuf->data, tmpbuf->len, TRUE);
+
+      /* Reprocess this packet after received reply from router */
+      silc_server_command_pending(server, SILC_COMMAND_INFO, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_info,
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_command_set_ident(cmd->payload, old_ident);
+      silc_buffer_free(tmpbuf);
+      goto out;
+    }
+
+    if (!entry && !cmd->pending && !server->standalone) {
+      /* Send to the primary router */
+      SilcBuffer tmpbuf;
+      uint16 old_ident;
+
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+      silc_server_packet_send(server, server->router->connection,
+                             SILC_PACKET_COMMAND, cmd->packet->flags,
+                             tmpbuf->data, tmpbuf->len, TRUE);
+
+      /* Reprocess this packet after received reply from router */
+      silc_server_command_pending(server, SILC_COMMAND_INFO, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_info,
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_command_set_ident(cmd->payload, old_ident);
+      silc_buffer_free(tmpbuf);
+      goto out;
+    }
+  }
+
+  silc_free(server_id);
+
+  if (!entry) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO,
+                                         SILC_STATUS_ERR_NO_SUCH_SERVER);
+    goto out;
+  }
+
+  idp = silc_id_payload_encode(entry->id, SILC_ID_SERVER);
+  if (!server_info)
+    server_info = entry->server_info;
+  server_name = entry->server_name;
+
+  /* Send the reply */
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_INFO,
+                                               SILC_STATUS_OK, ident, 3,
+                                               2, idp->data, idp->len,
+                                               3, server_name, 
+                                               strlen(server_name),
+                                               4, server_info, 
+                                               server_info ? 
+                                               strlen(server_info) : 0);
+  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         packet->data, packet->len, FALSE);
+    
+  silc_buffer_free(packet);
+  silc_buffer_free(idp);
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Server side of command PING. This just replies to the ping. */
+
+SILC_SERVER_CMD_FUNC(ping)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcServerID *id;
+  uint32 len;
+  unsigned char *tmp;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_INFO, cmd, 1, 2);
+
+  /* Get Server ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING,
+                                         SILC_STATUS_ERR_NO_SERVER_ID);
+    goto out;
+  }
+  id = silc_id_str2id(tmp, len, SILC_ID_SERVER);
+  if (!id)
+    goto out;
+
+  if (SILC_ID_SERVER_COMPARE(id, server->id)) {
+    /* Send our reply */
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING,
+                                         SILC_STATUS_OK);
+  } else {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING,
+                                         SILC_STATUS_ERR_NO_SUCH_SERVER);
+    goto out;
+  }
+
+  silc_free(id);
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Internal routine to join channel. The channel sent to this function
+   has been either created or resolved from ID lists. This joins the sent
+   client to the channel. */
+
+static void silc_server_command_join_channel(SilcServer server, 
+                                            SilcServerCommandContext cmd,
+                                            SilcChannelEntry channel,
+                                            SilcClientID *client_id,
+                                            bool created,
+                                            bool create_key,
+                                            uint32 umode,
+                                            const unsigned char *auth,
+                                            uint32 auth_len)
+{
+  SilcSocketConnection sock = cmd->sock;
+  unsigned char *tmp;
+  uint32 tmp_len, user_count;
+  unsigned char *passphrase = NULL, mode[4], tmp2[4], tmp3[4];
+  SilcClientEntry client;
+  SilcChannelClientEntry chl;
+  SilcBuffer reply, chidp, clidp, keyp = NULL, user_list, mode_list;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  char check[512], check2[512];
+  bool founder = FALSE;
+  bool resolve;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  if (!channel)
+    return;
+
+  /* Get the client entry */
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) {
+    client = (SilcClientEntry)sock->user_data;
+  } else {
+    client = silc_server_get_client_resolve(server, client_id, &resolve);
+    if (!client) {
+      if (cmd->pending)
+       goto out;
+
+      if (!resolve) {
+       silc_server_command_send_status_reply(
+                                        cmd, SILC_COMMAND_JOIN,
+                                        SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       goto out;
+      }
+
+      /* The client info is being resolved. Reprocess this packet after
+        receiving the reply to the query. */
+      silc_server_command_pending(server, SILC_COMMAND_WHOIS, 
+                                 server->cmd_ident,
+                                 silc_server_command_join, 
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      goto out;
+    }
+
+    cmd->pending = FALSE;
+  }
+
+  /*
+   * Check founder auth payload if provided.  If client can gain founder
+   * privileges it can override various conditions on joining the channel,
+   * and can have directly the founder mode set on the channel.
+   */
+  if (auth && auth_len && channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+    SilcIDListData idata = (SilcIDListData)client;
+
+    if (channel->founder_key && idata->public_key &&
+       silc_pkcs_public_key_compare(channel->founder_key, 
+                                    idata->public_key)) {
+      void *auth_data = (channel->founder_method == SILC_AUTH_PASSWORD ?
+                        (void *)channel->founder_passwd : 
+                        (void *)channel->founder_key);
+      uint32 auth_data_len = (channel->founder_method == SILC_AUTH_PASSWORD ?
+                             channel->founder_passwd_len : 0);
+
+      /* Check whether the client is to become founder */
+      if (silc_auth_verify_data(auth, auth_len, channel->founder_method, 
+                               auth_data, auth_data_len,
+                               idata->hash, client->id, SILC_ID_CLIENT)) {
+       umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO);
+       founder = TRUE;
+      }
+    }
+  }
+
+  /*
+   * Check channel modes
+   */
+
+  if (!umode) {
+    memset(check, 0, sizeof(check));
+    memset(check2, 0, sizeof(check2));
+    strncat(check, client->nickname, strlen(client->nickname));
+    strncat(check, "!", 1);
+    strncat(check, client->username, strlen(client->username));
+    if (!strchr(client->username, '@')) {
+      strncat(check, "@", 1);
+      strncat(check, cmd->sock->hostname, strlen(cmd->sock->hostname));
+    }
+
+    strncat(check2, client->nickname, strlen(client->nickname));
+    if (!strchr(client->nickname, '@')) {
+      strncat(check2, "@", 1);
+      strncat(check2, server->server_name, strlen(server->server_name));
+    }
+    strncat(check2, "!", 1);
+    strncat(check2, client->username, strlen(client->username));
+    if (!strchr(client->username, '@')) {
+      strncat(check2, "@", 1);
+      strncat(check2, cmd->sock->hostname, strlen(cmd->sock->hostname));
+    }
+    
+    /* Check invite list if channel is invite-only channel */
+    if (channel->mode & SILC_CHANNEL_MODE_INVITE) {
+      if (!channel->invite_list ||
+         (!silc_string_match(channel->invite_list, check) &&
+          !silc_string_match(channel->invite_list, check2))) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                             SILC_STATUS_ERR_NOT_INVITED);
+       goto out;
+      }
+    }
+
+    /* Check ban list if it exists. If the client's nickname, server,
+       username and/or hostname is in the ban list the access to the
+       channel is denied. */
+    if (channel->ban_list) {
+      if (silc_string_match(channel->ban_list, check) ||
+         silc_string_match(channel->ban_list, check2)) {
+       silc_server_command_send_status_reply(
+                                     cmd, SILC_COMMAND_JOIN,
+                                     SILC_STATUS_ERR_BANNED_FROM_CHANNEL);
+       goto out;
+      }
+    }
+    
+    /* Check user count limit if set. */
+    if (channel->mode & SILC_CHANNEL_MODE_ULIMIT) {
+      if (silc_hash_table_count(channel->user_list) + 1 > 
+         channel->user_limit) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                             SILC_STATUS_ERR_CHANNEL_IS_FULL);
+       goto out;
+      }
+    }
+  }
+
+  /* Check the channel passphrase if set. */
+  if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) {
+    /* Get passphrase */
+    tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+    if (tmp) {
+      passphrase = silc_calloc(tmp_len, sizeof(*passphrase));
+      memcpy(passphrase, tmp, tmp_len);
+    }
+  
+    if (!passphrase || !channel->passphrase ||
+        memcmp(passphrase, channel->passphrase, strlen(channel->passphrase))) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                           SILC_STATUS_ERR_BAD_PASSWORD);
+      goto out;
+    }
+  }
+
+  /*
+   * Client is allowed to join to the channel. Make it happen.
+   */
+
+  /* Check whether the client already is on the channel */
+  if (silc_server_client_on_channel(client, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                         SILC_STATUS_ERR_USER_ON_CHANNEL);
+    goto out;
+  }
+
+  /* Generate new channel key as protocol dictates */
+  if (create_key) {
+    if (!silc_server_create_channel_key(server, channel, 0))
+      goto out;
+
+    /* Send the channel key. This is broadcasted to the channel but is not
+       sent to the client who is joining to the channel. */
+    if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY))
+      silc_server_send_channel_key(server, NULL, channel, 
+                                  server->server_type == SILC_ROUTER ? 
+                                  FALSE : !server->standalone);
+  }
+
+  /* Join the client to the channel by adding it to channel's user list.
+     Add also the channel to client entry's channels list for fast cross-
+     referencing. */
+  chl = silc_calloc(1, sizeof(*chl));
+  chl->mode = umode;
+  chl->client = client;
+  chl->channel = channel;
+  silc_hash_table_add(channel->user_list, client, chl);
+  silc_hash_table_add(client->channels, channel, chl);
+
+  /* Get users on the channel */
+  silc_server_get_users_on_channel(server, channel, &user_list, &mode_list,
+                                  &user_count);
+
+  /* Encode Client ID Payload of the original client who wants to join */
+  clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+
+  /* Encode command reply packet */
+  chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+  SILC_PUT32_MSB(channel->mode, mode);
+  SILC_PUT32_MSB(created, tmp2);
+  SILC_PUT32_MSB(user_count, tmp3);
+
+  if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
+    tmp = silc_id_id2str(channel->id, SILC_ID_CHANNEL);
+    keyp = silc_channel_key_payload_encode(SILC_ID_CHANNEL_LEN, tmp, 
+                                          strlen(channel->channel_key->
+                                                 cipher->name),
+                                          channel->channel_key->cipher->name,
+                                          channel->key_len / 8, channel->key);
+    silc_free(tmp);
+  }
+
+  reply = 
+    silc_command_reply_payload_encode_va(SILC_COMMAND_JOIN,
+                                        SILC_STATUS_OK, ident, 13,
+                                        2, channel->channel_name,
+                                        strlen(channel->channel_name),
+                                        3, chidp->data, chidp->len,
+                                        4, clidp->data, clidp->len,
+                                        5, mode, 4,
+                                        6, tmp2, 4,
+                                        7, keyp ? keyp->data : NULL, 
+                                        keyp ? keyp->len : 0,
+                                        8, channel->ban_list, 
+                                        channel->ban_list ?
+                                        strlen(channel->ban_list) : 0,
+                                        9, channel->invite_list,
+                                        channel->invite_list ?
+                                        strlen(channel->invite_list) : 0,
+                                        10, channel->topic,
+                                        channel->topic ?
+                                        strlen(channel->topic) : 0,
+                                        11, silc_hmac_get_name(channel->hmac),
+                                        strlen(silc_hmac_get_name(channel->
+                                                                  hmac)),
+                                        12, tmp3, 4,
+                                        13, user_list->data, user_list->len,
+                                        14, mode_list->data, 
+                                        mode_list->len);
+
+  /* Send command reply */
+  silc_server_packet_send(server, sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         reply->data, reply->len, FALSE);
+
+  /* Send JOIN notify to locally connected clients on the channel. If
+     we are normal server then router will send or have sent JOIN notify
+     already. However since we've added the client already to our channel
+     we'll ignore it (in packet_receive.c) so we must send it here. If
+     we are router then this will send it to local clients and local
+     servers. */
+  silc_server_send_notify_to_channel(server, NULL, channel, FALSE, 
+                                    SILC_NOTIFY_TYPE_JOIN, 2,
+                                    clidp->data, clidp->len,
+                                    chidp->data, chidp->len);
+
+  if (!cmd->pending) {
+    /* Send JOIN notify packet to our primary router */
+    if (!server->standalone)
+      silc_server_send_notify_join(server, server->router->connection,
+                                  server->server_type == SILC_ROUTER ?
+                                  TRUE : FALSE, channel, client->id);
+
+    if (keyp)
+      /* Distribute the channel key to all backup routers. */
+      silc_server_backup_send(server, NULL, SILC_PACKET_CHANNEL_KEY, 0,
+                             keyp->data, keyp->len, FALSE, TRUE);
+  }
+
+  /* If client became founder by providing correct founder auth data
+     notify the mode change to the channel. */
+  if (founder) {
+    SILC_PUT32_MSB(chl->mode, mode);
+    silc_server_send_notify_to_channel(server, NULL, channel, FALSE, 
+                                      SILC_NOTIFY_TYPE_CUMODE_CHANGE, 3,
+                                      clidp->data, clidp->len,
+                                      mode, 4, clidp->data, clidp->len);
+      
+    /* Set CUMODE notify type to network */
+    if (!server->standalone)
+      silc_server_send_notify_cumode(server, server->router->connection,
+                                    server->server_type == SILC_ROUTER ? 
+                                    TRUE : FALSE, channel,
+                                    chl->mode, client->id, SILC_ID_CLIENT,
+                                    client->id);
+  }
+
+  silc_buffer_free(reply);
+  silc_buffer_free(clidp);
+  silc_buffer_free(chidp);
+  silc_buffer_free(keyp);
+  silc_buffer_free(user_list);
+  silc_buffer_free(mode_list);
+
+ out:
+  silc_free(passphrase);
+}
+
+/* Server side of command JOIN. Joins client into requested channel. If 
+   the channel does not exist it will be created. */
+
+SILC_SERVER_CMD_FUNC(join)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  unsigned char *auth;
+  uint32 tmp_len, auth_len;
+  char *tmp, *channel_name = NULL, *cipher, *hmac;
+  SilcChannelEntry channel;
+  uint32 umode = 0;
+  bool created = FALSE, create_key = TRUE;
+  SilcClientID *client_id;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_JOIN, cmd, 2, 6);
+
+  /* Get channel name */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+  channel_name = tmp;
+
+  if (tmp_len > 256)
+    channel_name[255] = '\0';
+
+  if (silc_server_name_bad_chars(channel_name, tmp_len) == TRUE) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                         SILC_STATUS_ERR_BAD_CHANNEL);
+    goto out;
+  }
+
+  /* Get Client ID of the client who is joining to the channel */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+  client_id = silc_id_payload_parse_id(tmp, tmp_len);
+  if (!client_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+
+  /* Get cipher, hmac name and auth payload */
+  cipher = silc_argument_get_arg_type(cmd->args, 4, NULL);
+  hmac = silc_argument_get_arg_type(cmd->args, 5, NULL);
+  auth = silc_argument_get_arg_type(cmd->args, 6, &auth_len);
+
+  /* See if the channel exists */
+  channel = silc_idlist_find_channel_by_name(server->local_list, 
+                                            channel_name, NULL);
+
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) {
+    SilcClientEntry entry = (SilcClientEntry)cmd->sock->user_data;
+    client_id = silc_id_dup(entry->id, SILC_ID_CLIENT);
+
+    if (!channel || channel->disabled) {
+      /* Channel not found */
+
+      /* If we are standalone server we don't have a router, we just create 
+        the channel by ourselves. */
+      if (server->standalone) {
+       channel = silc_server_create_new_channel(server, server->id, cipher, 
+                                                hmac, channel_name, TRUE);
+       if (!channel) {
+         silc_server_command_send_status_reply(
+                                        cmd, SILC_COMMAND_JOIN,
+                                        SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
+         goto out;
+       }
+       
+       umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO);
+       created = TRUE;
+       create_key = FALSE;
+       
+      } else {
+
+       /* The channel does not exist on our server. If we are normal server 
+          we will send JOIN command to our router which will handle the
+          joining procedure (either creates the channel if it doesn't exist 
+          or joins the client to it). */
+       if (server->server_type != SILC_ROUTER) {
+         SilcBuffer tmpbuf;
+         uint16 old_ident;
+
+         /* If this is pending command callback then we've resolved
+            it and it didn't work, return since we've notified the
+            client already in the command reply callback. */
+         if (cmd->pending)
+           goto out;
+         
+         old_ident = silc_command_get_ident(cmd->payload);
+         silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+         tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+         
+         /* Send JOIN command to our router */
+         silc_server_packet_send(server, (SilcSocketConnection)
+                                 server->router->connection,
+                                 SILC_PACKET_COMMAND, cmd->packet->flags,
+                                 tmpbuf->data, tmpbuf->len, TRUE);
+         
+         /* Reprocess this packet after received reply from router */
+         silc_server_command_pending(server, SILC_COMMAND_JOIN, 
+                                     silc_command_get_ident(cmd->payload),
+                                     silc_server_command_join,
+                                     silc_server_command_dup(cmd));
+         cmd->pending = TRUE;
+          silc_command_set_ident(cmd->payload, old_ident);
+         silc_buffer_free(tmpbuf);
+         goto out;
+       }
+       
+       /* We are router and the channel does not seem exist so we will check
+          our global list as well for the channel. */
+       channel = silc_idlist_find_channel_by_name(server->global_list, 
+                                                  channel_name, NULL);
+       if (!channel) {
+         /* Channel really does not exist, create it */
+         channel = silc_server_create_new_channel(server, server->id, cipher, 
+                                                  hmac, channel_name, TRUE);
+         if (!channel) {
+           silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                      SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
+           goto out;
+         }
+
+         umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO);
+         created = TRUE;
+         create_key = FALSE;
+       }
+      }
+    }
+  } else {
+    if (!channel) {
+      /* Channel not found */
+
+      /* If the command came from router and we are normal server then
+        something went wrong with the joining as the channel was not found.
+        We can't do anything else but ignore this. */
+      if (cmd->sock->type == SILC_SOCKET_TYPE_ROUTER ||
+         server->server_type != SILC_ROUTER)
+       goto out;
+      
+      /* We are router and the channel does not seem exist so we will check
+        our global list as well for the channel. */
+      channel = silc_idlist_find_channel_by_name(server->global_list, 
+                                                channel_name, NULL);
+      if (!channel) {
+       /* Channel really does not exist, create it */
+       channel = silc_server_create_new_channel(server, server->id, cipher, 
+                                                hmac, channel_name, TRUE);
+       if (!channel) {
+         silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                      SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
+         goto out;
+       }
+
+       umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO);
+       created = TRUE;
+       create_key = FALSE;
+      }
+    }
+  }
+
+  /* Check whether the channel was created by our router */
+  if (cmd->pending && context2) {
+    SilcServerCommandReplyContext reply = 
+      (SilcServerCommandReplyContext)context2;
+
+    if (silc_command_get(reply->payload) == SILC_COMMAND_JOIN) {
+      tmp = silc_argument_get_arg_type(reply->args, 6, NULL);
+      SILC_GET32_MSB(created, tmp);
+      if (silc_argument_get_arg_type(reply->args, 7, NULL))
+       create_key = FALSE;     /* Router returned the key already */
+    }
+
+    if (silc_command_get(reply->payload) == SILC_COMMAND_WHOIS &&
+       !silc_hash_table_count(channel->user_list))
+      created = TRUE;
+  }
+
+  /* If the channel does not have global users and is also empty the client
+     will be the channel founder and operator. */
+  if (!channel->global_users && !silc_hash_table_count(channel->user_list))
+    umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO);
+
+  /* Join to the channel */
+  silc_server_command_join_channel(server, cmd, channel, client_id,
+                                  created, create_key, umode,
+                                  auth, auth_len);
+
+  silc_free(client_id);
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Server side of command MOTD. Sends server's current "message of the
+   day" to the client. */
+
+SILC_SERVER_CMD_FUNC(motd)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcBuffer packet, idp;
+  char *motd, *dest_server;
+  uint32 motd_len;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_MOTD, cmd, 1, 1);
+
+  /* Get server name */
+  dest_server = silc_argument_get_arg_type(cmd->args, 1, NULL);
+  if (!dest_server) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_MOTD,
+                                         SILC_STATUS_ERR_NO_SUCH_SERVER);
+    goto out;
+  }
+
+  if (!strncasecmp(dest_server, server->server_name, strlen(dest_server))) {
+    /* Send our MOTD */
+
+    idp = silc_id_payload_encode(server->id_entry->id, SILC_ID_SERVER);
+
+    if (server->config && server->config->server_info &&
+       server->config->server_info->motd_file) {
+      /* Send motd */
+      motd = silc_file_readfile(server->config->server_info->motd_file, &motd_len);
+      if (!motd)
+       goto out;
+      
+      motd[motd_len] = 0;
+      packet = silc_command_reply_payload_encode_va(SILC_COMMAND_MOTD,
+                                                   SILC_STATUS_OK, ident, 2,
+                                                   2, idp, idp->len,
+                                                   3, motd, motd_len);
+    } else {
+      /* No motd */
+      packet = silc_command_reply_payload_encode_va(SILC_COMMAND_MOTD,
+                                                   SILC_STATUS_OK, ident, 1,
+                                                   2, idp, idp->len);
+    }
+
+    silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                           packet->data, packet->len, FALSE);
+    silc_buffer_free(packet);
+    silc_buffer_free(idp);
+  } else {
+    SilcServerEntry entry;
+
+    /* Check whether we have this server cached */
+    entry = silc_idlist_find_server_by_name(server->global_list,
+                                           dest_server, TRUE, NULL);
+    if (!entry) {
+      entry = silc_idlist_find_server_by_name(server->local_list,
+                                             dest_server, TRUE, NULL);
+    }
+
+    if (server->server_type != SILC_SERVER && !cmd->pending && 
+       entry && !entry->motd) {
+      /* Send to the server */
+      SilcBuffer tmpbuf;
+      uint16 old_ident;
+
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+      silc_server_packet_send(server, entry->connection,
+                             SILC_PACKET_COMMAND, cmd->packet->flags,
+                             tmpbuf->data, tmpbuf->len, TRUE);
+
+      /* Reprocess this packet after received reply from router */
+      silc_server_command_pending(server, SILC_COMMAND_MOTD, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_motd,
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_command_set_ident(cmd->payload, old_ident);
+      silc_buffer_free(tmpbuf);
+      goto out;
+    }
+
+    if (!entry && !cmd->pending && !server->standalone) {
+      /* Send to the primary router */
+      SilcBuffer tmpbuf;
+      uint16 old_ident;
+
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+      silc_server_packet_send(server, server->router->connection,
+                             SILC_PACKET_COMMAND, cmd->packet->flags,
+                             tmpbuf->data, tmpbuf->len, TRUE);
+
+      /* Reprocess this packet after received reply from router */
+      silc_server_command_pending(server, SILC_COMMAND_MOTD, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_motd,
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_command_set_ident(cmd->payload, old_ident);
+      silc_buffer_free(tmpbuf);
+      goto out;
+    }
+
+    if (!entry) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO,
+                                           SILC_STATUS_ERR_NO_SUCH_SERVER);
+      goto out;
+    }
+
+    idp = silc_id_payload_encode(server->id_entry->id, SILC_ID_SERVER);
+    packet = silc_command_reply_payload_encode_va(SILC_COMMAND_MOTD,
+                                                 SILC_STATUS_OK, ident, 2,
+                                                 2, idp, idp->len,
+                                                 3, entry->motd,
+                                                 entry->motd ? 
+                                                 strlen(entry->motd) : 0);
+    silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                           packet->data, packet->len, FALSE);
+    silc_buffer_free(packet);
+    silc_buffer_free(idp);
+  }
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Server side of command UMODE. Client can use this command to set/unset
+   user mode. Client actually cannot set itself to be as server/router
+   operator so this can be used only to unset the modes. */
+
+SILC_SERVER_CMD_FUNC(umode)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcBuffer packet;
+  unsigned char *tmp_mask;
+  uint32 mask;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+
+  if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_UMODE, cmd, 2, 2);
+
+  /* Get the client's mode mask */
+  tmp_mask = silc_argument_get_arg_type(cmd->args, 2, NULL);
+  if (!tmp_mask) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_UMODE,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+  SILC_GET32_MSB(mask, tmp_mask);
+
+  /* 
+   * Change the mode 
+   */
+
+  if (mask & SILC_UMODE_SERVER_OPERATOR) {
+    if (!(client->mode & SILC_UMODE_SERVER_OPERATOR)) {
+      /* Cannot operator mode */
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_UMODE,
+                                           SILC_STATUS_ERR_PERM_DENIED);
+      goto out;
+    }
+  } else {
+    /* Remove the server operator rights */
+    if (client->mode & SILC_UMODE_SERVER_OPERATOR) {
+      client->mode &= ~SILC_UMODE_SERVER_OPERATOR;
+      if (client->connection)
+       server->stat.my_server_ops--;
+      if (server->server_type == SILC_ROUTER)
+       server->stat.server_ops--;
+    }
+  }
+
+  if (mask & SILC_UMODE_ROUTER_OPERATOR) {
+    if (!(client->mode & SILC_UMODE_ROUTER_OPERATOR)) {
+      /* Cannot operator mode */
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_UMODE,
+                                           SILC_STATUS_ERR_PERM_DENIED);
+      goto out;
+    }
+  } else {
+    /* Remove the router operator rights */
+    if (client->mode & SILC_UMODE_ROUTER_OPERATOR) {
+      client->mode &= ~SILC_UMODE_ROUTER_OPERATOR;
+      if (client->connection)
+       server->stat.my_router_ops--;
+      if (server->server_type == SILC_ROUTER)
+       server->stat.router_ops--;
+    }
+  }
+
+  if (mask & SILC_UMODE_GONE) {
+    client->mode |= SILC_UMODE_GONE;
+  } else {
+    if (client->mode & SILC_UMODE_GONE)
+      /* Remove the gone status */
+      client->mode &= ~SILC_UMODE_GONE;
+  }
+
+  /* Send UMODE change to primary router */
+  if (!server->standalone)
+    silc_server_send_notify_umode(server, server->router->connection, TRUE,
+                                 client->id, client->mode);
+
+  /* Send command reply to sender */
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_UMODE,
+                                               SILC_STATUS_OK, ident, 1,
+                                               2, tmp_mask, 4);
+  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         packet->data, packet->len, FALSE);
+  silc_buffer_free(packet);
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Checks that client has rights to add or remove channel modes. If any
+   of the checks fails FALSE is returned. */
+
+int silc_server_check_cmode_rights(SilcChannelEntry channel,
+                                  SilcChannelClientEntry client,
+                                  uint32 mode)
+{
+  int is_op = client->mode & SILC_CHANNEL_UMODE_CHANOP;
+  int is_fo = client->mode & SILC_CHANNEL_UMODE_CHANFO;
+
+  /* Check whether has rights to change anything */
+  if (!is_op && !is_fo)
+    return FALSE;
+
+  /* Check whether has rights to change everything */
+  if (is_op && is_fo)
+    return TRUE;
+
+  /* We know that client is channel operator, check that they are not
+     changing anything that requires channel founder rights. Rest of the
+     modes are available automatically for channel operator. */
+
+  if (mode & SILC_CHANNEL_MODE_PRIVKEY) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  if (mode & SILC_CHANNEL_MODE_PASSPHRASE) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_PASSPHRASE))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+
+  if (mode & SILC_CHANNEL_MODE_CIPHER) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_CIPHER))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_CIPHER) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  return TRUE;
+}
+
+/* Server side command of CMODE. Changes channel mode */
+
+SILC_SERVER_CMD_FUNC(cmode)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcIDListData idata = (SilcIDListData)client;
+  SilcChannelID *channel_id;
+  SilcChannelEntry channel;
+  SilcChannelClientEntry chl;
+  SilcBuffer packet, cidp;
+  unsigned char *tmp, *tmp_id, *tmp_mask;
+  char *cipher = NULL, *hmac = NULL, *passphrase = NULL;
+  uint32 mode_mask, tmp_len, tmp_len2;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_CMODE, cmd, 2, 7);
+
+  /* Get Channel ID */
+  tmp_id = silc_argument_get_arg_type(cmd->args, 1, &tmp_len2);
+  if (!tmp_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+  channel_id = silc_id_payload_parse_id(tmp_id, tmp_len2);
+  if (!channel_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+
+  /* Get the channel mode mask */
+  tmp_mask = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!tmp_mask) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+  SILC_GET32_MSB(mode_mask, tmp_mask);
+
+  /* Get channel entry */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
+  if (!channel) {
+    channel = silc_idlist_find_channel_by_id(server->global_list, 
+                                            channel_id, NULL);
+    if (!channel) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
+    }
+  }
+
+  /* Check whether this client is on the channel */
+  if (!silc_server_client_on_channel(client, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
+  }
+
+  /* Get entry to the channel user list */
+  silc_hash_table_find(channel->user_list, client, NULL, (void *)&chl);
+
+  /* Check that client has rights to change any requested channel modes */
+  if (!silc_server_check_cmode_rights(channel, chl, mode_mask)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+    goto out;
+  }
+
+  /*
+   * Check the modes. Modes that requires nothing special operation are
+   * not checked here.
+   */
+
+  if (mode_mask & SILC_CHANNEL_MODE_PRIVKEY) {
+    /* Channel uses private keys to protect traffic. Client(s) has set the
+       key locally they want to use, server does not know that key. */
+    /* Nothing interesting to do here */
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) {
+      /* The mode is removed and we need to generate and distribute
+        new channel key. Clients are not using private channel keys
+        anymore after this. */
+      
+      /* Re-generate channel key */
+      if (!silc_server_create_channel_key(server, channel, 0))
+       goto out;
+       
+      /* Send the channel key. This sends it to our local clients and if
+        we are normal server to our router as well. */
+      silc_server_send_channel_key(server, NULL, channel, 
+                                  server->server_type == SILC_ROUTER ? 
+                                  FALSE : !server->standalone);
+       
+      cipher = channel->channel_key->cipher->name;
+      hmac = (char *)silc_hmac_get_name(channel->hmac);
+    }
+  }
+
+  if (mode_mask & SILC_CHANNEL_MODE_ULIMIT) {
+    /* User limit is set on channel */
+    uint32 user_limit;
+      
+    /* Get user limit */
+    tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
+    if (!tmp) {
+      if (!(channel->mode & SILC_CHANNEL_MODE_ULIMIT)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                  SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       goto out;
+      }
+    } else {
+      SILC_GET32_MSB(user_limit, tmp);
+      channel->user_limit = user_limit;
+    }
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_ULIMIT)
+      /* User limit mode is unset. Remove user limit */
+      channel->user_limit = 0;
+  }
+
+  if (mode_mask & SILC_CHANNEL_MODE_PASSPHRASE) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_PASSPHRASE)) {
+      /* Passphrase has been set to channel */
+      
+      /* Get the passphrase */
+      tmp = silc_argument_get_arg_type(cmd->args, 4, NULL);
+      if (!tmp) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                  SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       goto out;
+      }
+
+      /* Save the passphrase */
+      passphrase = channel->passphrase = strdup(tmp);
+    }
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) {
+      /* Passphrase mode is unset. remove the passphrase */
+      if (channel->passphrase) {
+       silc_free(channel->passphrase);
+       channel->passphrase = NULL;
+      }
+    }
+  }
+
+  if (mode_mask & SILC_CHANNEL_MODE_CIPHER) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_CIPHER)) {
+      /* Cipher to use protect the traffic */
+      SilcCipher newkey, oldkey;
+
+      /* Get cipher */
+      cipher = silc_argument_get_arg_type(cmd->args, 5, NULL);
+      if (!cipher) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                  SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       goto out;
+      }
+
+      /* Delete old cipher and allocate the new one */
+      if (!silc_cipher_alloc(cipher, &newkey)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                      SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
+       goto out;
+      }
+
+      oldkey = channel->channel_key;
+      channel->channel_key = newkey;
+
+      /* Re-generate channel key */
+      if (!silc_server_create_channel_key(server, channel, 0)) {
+       /* We don't have new key, revert to old one */
+       channel->channel_key = oldkey;
+       goto out;
+      }
+
+      /* Remove old channel key for good */
+      silc_cipher_free(oldkey);
+
+      /* Send the channel key. This sends it to our local clients and if
+        we are normal server to our router as well. */
+      silc_server_send_channel_key(server, NULL, channel, 
+                                  server->server_type == SILC_ROUTER ? 
+                                  FALSE : !server->standalone);
+    }
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_CIPHER) {
+      /* Cipher mode is unset. Remove the cipher and revert back to 
+        default cipher */
+      SilcCipher newkey, oldkey;
+      cipher = channel->cipher;
+
+      /* Delete old cipher and allocate default one */
+      if (!silc_cipher_alloc(cipher ? cipher : SILC_DEFAULT_CIPHER, &newkey)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                  SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
+       goto out;
+      }
+
+      oldkey = channel->channel_key;
+      channel->channel_key = newkey;
+
+      /* Re-generate channel key */
+      if (!silc_server_create_channel_key(server, channel, 0)) {
+       /* We don't have new key, revert to old one */
+       channel->channel_key = oldkey;
+       goto out;
+      }
+      
+      /* Remove old channel key for good */
+      silc_cipher_free(oldkey);
+
+      /* Send the channel key. This sends it to our local clients and if
+        we are normal server to our router as well. */
+      silc_server_send_channel_key(server, NULL, channel, 
+                                  server->server_type == SILC_ROUTER ? 
+                                  FALSE : !server->standalone);
+    }
+  }
+
+  if (mode_mask & SILC_CHANNEL_MODE_HMAC) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_HMAC)) {
+      /* HMAC to use protect the traffic */
+      unsigned char hash[32];
+      SilcHmac newhmac;
+
+      /* Get hmac */
+      hmac = silc_argument_get_arg_type(cmd->args, 6, NULL);
+      if (!hmac) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                  SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       goto out;
+      }
+
+      /* Delete old hmac and allocate the new one */
+      if (!silc_hmac_alloc(hmac, NULL, &newhmac)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                      SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
+       goto out;
+      }
+
+      silc_hmac_free(channel->hmac);
+      channel->hmac = newhmac;
+
+      /* Set the HMAC key out of current channel key. The client must do
+        this locally. */
+      silc_hash_make(silc_hmac_get_hash(channel->hmac), channel->key, 
+                    channel->key_len / 8, hash);
+      silc_hmac_set_key(channel->hmac, hash, 
+                       silc_hash_len(silc_hmac_get_hash(channel->hmac)));
+      memset(hash, 0, sizeof(hash));
+    }
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_HMAC) {
+      /* Hmac mode is unset. Remove the hmac and revert back to 
+        default hmac */
+      SilcHmac newhmac;
+      unsigned char hash[32];
+      hmac = channel->hmac_name;
+
+      /* Delete old hmac and allocate default one */
+      silc_hmac_free(channel->hmac);
+      if (!silc_hmac_alloc(hmac ? hmac : SILC_DEFAULT_HMAC, NULL, &newhmac)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                      SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
+       goto out;
+      }
+
+      silc_hmac_free(channel->hmac);
+      channel->hmac = newhmac;
+
+      /* Set the HMAC key out of current channel key. The client must do
+        this locally. */
+      silc_hash_make(silc_hmac_get_hash(channel->hmac), channel->key, 
+                    channel->key_len / 8, 
+                    hash);
+      silc_hmac_set_key(channel->hmac, hash, 
+                       silc_hash_len(silc_hmac_get_hash(channel->hmac)));
+      memset(hash, 0, sizeof(hash));
+    }
+  }
+
+  if (mode_mask & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+    if (chl->mode & SILC_CHANNEL_UMODE_CHANFO) {
+      if (!(channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)) {
+       /* Set the founder authentication */
+       SilcAuthPayload auth;
+       
+       tmp = silc_argument_get_arg_type(cmd->args, 7, &tmp_len);
+       if (!tmp) {
+         silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                    SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+         goto out;
+       }
+
+       auth = silc_auth_payload_parse(tmp, tmp_len);
+       if (!auth) {
+         silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                    SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+         goto out;
+       }
+
+       /* Save the public key */
+       tmp = silc_pkcs_public_key_encode(idata->public_key, &tmp_len);
+       silc_pkcs_public_key_decode(tmp, tmp_len, &channel->founder_key);
+       silc_free(tmp);
+       
+       channel->founder_method = silc_auth_get_method(auth);
+
+       if (channel->founder_method == SILC_AUTH_PASSWORD) {
+         tmp = silc_auth_get_data(auth, &tmp_len);
+         channel->founder_passwd = 
+           silc_calloc(tmp_len + 1, sizeof(*channel->founder_passwd));
+         memcpy(channel->founder_passwd, tmp, tmp_len);
+         channel->founder_passwd_len = tmp_len;
+       } else {
+         /* Verify the payload before setting the mode */
+         if (!silc_auth_verify(auth, channel->founder_method, 
+                               channel->founder_key, 0, idata->hash,
+                               client->id, SILC_ID_CLIENT)) {
+           silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                                 SILC_STATUS_ERR_AUTH_FAILED);
+           goto out;
+         }
+       }
+
+       silc_auth_payload_free(auth);
+      }
+    }
+  } else {
+    if (chl->mode & SILC_CHANNEL_UMODE_CHANFO) {
+      if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+       if (channel->founder_key)
+         silc_pkcs_public_key_free(channel->founder_key);
+       if (channel->founder_passwd) {
+         silc_free(channel->founder_passwd);
+         channel->founder_passwd = NULL;
+       }
+      }
+    }
+  }
+
+  /* Finally, set the mode */
+  channel->mode = mode_mask;
+
+  /* Send CMODE_CHANGE notify. */
+  cidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+  silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
+                                    SILC_NOTIFY_TYPE_CMODE_CHANGE, 5,
+                                    cidp->data, cidp->len, 
+                                    tmp_mask, 4,
+                                    cipher, cipher ? strlen(cipher) : 0,
+                                    hmac, hmac ? strlen(hmac) : 0,
+                                    passphrase, passphrase ? 
+                                    strlen(passphrase) : 0);
+
+  /* Set CMODE notify type to network */
+  if (!server->standalone)
+    silc_server_send_notify_cmode(server, server->router->connection,
+                                 server->server_type == SILC_ROUTER ? 
+                                 TRUE : FALSE, channel,
+                                 mode_mask, client->id, SILC_ID_CLIENT,
+                                 cipher, hmac, passphrase);
+
+  /* Send command reply to sender */
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_CMODE,
+                                               SILC_STATUS_OK, ident, 2,
+                                               2, tmp_id, tmp_len2,
+                                               3, tmp_mask, 4);
+  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         packet->data, packet->len, FALSE);
+    
+  silc_buffer_free(packet);
+  silc_free(channel_id);
+  silc_buffer_free(cidp);
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+/* Server side of CUMODE command. Changes client's mode on a channel. */
+
+SILC_SERVER_CMD_FUNC(cumode)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcIDListData idata = (SilcIDListData)client;
+  SilcChannelID *channel_id;
+  SilcClientID *client_id;
+  SilcChannelEntry channel;
+  SilcClientEntry target_client;
+  SilcChannelClientEntry chl;
+  SilcBuffer packet, idp;
+  unsigned char *tmp_id, *tmp_ch_id, *tmp_mask;
+  uint32 target_mask, sender_mask = 0, tmp_len, tmp_ch_len;
+  int notify = FALSE;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_CUMODE, cmd, 3, 4);
+
+  /* Get Channel ID */
+  tmp_ch_id = silc_argument_get_arg_type(cmd->args, 1, &tmp_ch_len);
+  if (!tmp_ch_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+  channel_id = silc_id_payload_parse_id(tmp_ch_id, tmp_ch_len);
+  if (!channel_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+
+  /* Get channel entry */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
+  if (!channel) {
+    channel = silc_idlist_find_channel_by_id(server->global_list, 
+                                            channel_id, NULL);
+    if (!channel) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
+    }
+  }
+
+  /* Check whether sender is on the channel */
+  if (!silc_server_client_on_channel(client, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
+  }
+
+  /* Check that client has rights to change other's rights */
+  silc_hash_table_find(channel->user_list, client, NULL, (void *)&chl);
+  sender_mask = chl->mode;
+  
+  /* Get the target client's channel mode mask */
+  tmp_mask = silc_argument_get_arg_type(cmd->args, 2, NULL);
+  if (!tmp_mask) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+  SILC_GET32_MSB(target_mask, tmp_mask);
+
+  /* Get target Client ID */
+  tmp_id = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+  if (!tmp_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NO_CLIENT_ID);
+    goto out;
+  }
+  client_id = silc_id_payload_parse_id(tmp_id, tmp_len);
+  if (!client_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NO_CLIENT_ID);
+    goto out;
+  }
+
+  /* Get target client's entry */
+  target_client = silc_idlist_find_client_by_id(server->local_list, 
+                                               client_id, TRUE, NULL);
+  if (!target_client) {
+    target_client = silc_idlist_find_client_by_id(server->global_list, 
+                                                 client_id, TRUE, NULL);
+  }
+
+  if (target_client != client &&
+      !(sender_mask & SILC_CHANNEL_UMODE_CHANFO) &&
+      !(sender_mask & SILC_CHANNEL_UMODE_CHANOP)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+    goto out;
+  }
+
+  /* Check whether target client is on the channel */
+  if (target_client != client) {
+    if (!silc_server_client_on_channel(target_client, channel)) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                SILC_STATUS_ERR_USER_NOT_ON_CHANNEL);
+      goto out;
+    }
+
+    /* Get entry to the channel user list */
+    silc_hash_table_find(channel->user_list, target_client, NULL, 
+                        (void *)&chl);
+  }
+
+  /* 
+   * Change the mode 
+   */
+
+  /* If the target client is founder, no one else can change their mode
+     but themselves. */
+  if (chl->mode & SILC_CHANNEL_UMODE_CHANFO && client != target_client) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+    goto out;
+  }
+
+  if (target_mask & SILC_CHANNEL_UMODE_CHANFO) {
+    if (!(chl->mode & SILC_CHANNEL_UMODE_CHANFO)) {
+      /* The client tries to claim the founder rights. */
+      unsigned char *tmp_auth;
+      uint32 tmp_auth_len, auth_len;
+      void *auth;
+      
+      if (target_client != client) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                             SILC_STATUS_ERR_NOT_YOU);
+       goto out;
+      }
+
+      if (!(channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) ||
+         !channel->founder_key || !idata->public_key ||
+         !silc_pkcs_public_key_compare(channel->founder_key, 
+                                       idata->public_key)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                             SILC_STATUS_ERR_NOT_YOU);
+       goto out;
+      }
+
+      tmp_auth = silc_argument_get_arg_type(cmd->args, 4, &tmp_auth_len);
+      if (!tmp_auth) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                    SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       goto out;
+      }
+
+      auth = (channel->founder_method == SILC_AUTH_PASSWORD ?
+             (void *)channel->founder_passwd : (void *)channel->founder_key);
+      auth_len = (channel->founder_method == SILC_AUTH_PASSWORD ?
+                 channel->founder_passwd_len : 0);
+      
+      if (!silc_auth_verify_data(tmp_auth, tmp_auth_len,
+                                channel->founder_method, auth, auth_len,
+                                idata->hash, client->id, SILC_ID_CLIENT)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                             SILC_STATUS_ERR_AUTH_FAILED);
+       goto out;
+      }
+      
+      sender_mask = chl->mode |= SILC_CHANNEL_UMODE_CHANFO;
+      notify = TRUE;
+    }
+  } else {
+    if (chl->mode & SILC_CHANNEL_UMODE_CHANFO) {
+      if (target_client == client) {
+       /* Remove channel founder rights from itself */
+       chl->mode &= ~SILC_CHANNEL_UMODE_CHANFO;
+       notify = TRUE;
+      } else {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                             SILC_STATUS_ERR_NOT_YOU);
+       goto out;
+      }
+    }
+  }
+
+  if (target_mask & SILC_CHANNEL_UMODE_CHANOP) {
+    /* Promote to operator */
+    if (!(chl->mode & SILC_CHANNEL_UMODE_CHANOP)) {
+      if (!(sender_mask & SILC_CHANNEL_UMODE_CHANOP) &&
+         !(sender_mask & SILC_CHANNEL_UMODE_CHANFO)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                             SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+       goto out;
+      }
+
+      chl->mode |= SILC_CHANNEL_UMODE_CHANOP;
+      notify = TRUE;
+    }
+  } else {
+    if (chl->mode & SILC_CHANNEL_UMODE_CHANOP) {
+      if (!(sender_mask & SILC_CHANNEL_UMODE_CHANOP) &&
+         !(sender_mask & SILC_CHANNEL_UMODE_CHANFO)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                             SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+       goto out;
+      }
+
+      /* Demote to normal user */
+      chl->mode &= ~SILC_CHANNEL_UMODE_CHANOP;
+      notify = TRUE;
+    }
+  }
+
+  idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+  tmp_id = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+
+  /* Send notify to channel, notify only if mode was actually changed. */
+  if (notify) {
+    silc_server_send_notify_to_channel(server, NULL, channel, FALSE, 
+                                      SILC_NOTIFY_TYPE_CUMODE_CHANGE, 3,
+                                      idp->data, idp->len,
+                                      tmp_mask, 4, 
+                                      tmp_id, tmp_len);
+
+    /* Set CUMODE notify type to network */
+    if (!server->standalone)
+      silc_server_send_notify_cumode(server, server->router->connection,
+                                    server->server_type == SILC_ROUTER ? 
+                                    TRUE : FALSE, channel,
+                                    target_mask, client->id, 
+                                    SILC_ID_CLIENT,
+                                    target_client->id);
+  }
+
+  /* Send command reply to sender */
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_CUMODE,
+                                               SILC_STATUS_OK, ident, 3,
+                                               2, tmp_mask, 4,
+                                               3, tmp_ch_id, tmp_ch_len,
+                                               4, tmp_id, tmp_len);
+  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         packet->data, packet->len, FALSE);
+    
+  silc_buffer_free(packet);
+  silc_free(channel_id);
+  silc_free(client_id);
+  silc_buffer_free(idp);
+
+ out:
+  silc_server_command_free(cmd);
 }
 
-SILC_SERVER_CMD_FUNC(topic)
+/* Server side of KICK command. Kicks client out of channel. */
+
+SILC_SERVER_CMD_FUNC(kick)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcClientEntry target_client;
+  SilcChannelID *channel_id;
+  SilcClientID *client_id;
+  SilcChannelEntry channel;
+  SilcChannelClientEntry chl;
+  SilcBuffer idp;
+  uint32 tmp_len, target_idp_len;
+  unsigned char *tmp, *comment, *target_idp;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_LEAVE, cmd, 1, 3);
+
+  /* Get Channel ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+  channel_id = silc_id_payload_parse_id(tmp, tmp_len);
+  if (!channel_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+
+  /* Get channel entry */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
+  if (!channel) {
+    channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                            channel_id, NULL);
+    if (!channel) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
+    }
+  }
+
+  /* Check whether sender is on the channel */
+  if (!silc_server_client_on_channel(client, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
+  }
+
+  /* Check that the kicker is channel operator or channel founder */
+  silc_hash_table_find(channel->user_list, client, NULL, (void *)&chl);
+  if (chl->mode == SILC_CHANNEL_UMODE_NONE) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                         SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+    goto out;
+  }
+  
+  /* Get target Client ID */
+  target_idp = silc_argument_get_arg_type(cmd->args, 2, &target_idp_len);
+  if (!target_idp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                         SILC_STATUS_ERR_NO_CLIENT_ID);
+    goto out;
+  }
+  client_id = silc_id_payload_parse_id(target_idp, target_idp_len);
+  if (!client_id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                         SILC_STATUS_ERR_NO_CLIENT_ID);
+    goto out;
+  }
+
+  /* Get target client's entry */
+  target_client = silc_idlist_find_client_by_id(server->local_list, 
+                                               client_id, TRUE, NULL);
+  if (!target_client) {
+    target_client = silc_idlist_find_client_by_id(server->global_list, 
+                                                 client_id, TRUE, NULL);
+  }
+
+  /* Check that the target client is not channel founder. Channel founder
+     cannot be kicked from the channel. */
+  silc_hash_table_find(channel->user_list, target_client, NULL, (void *)&chl);
+  if (chl->mode & SILC_CHANNEL_UMODE_CHANFO) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                         SILC_STATUS_ERR_NO_CHANNEL_FOPRIV);
+    goto out;
+  }
+  
+  /* Check whether target client is on the channel */
+  if (!silc_server_client_on_channel(target_client, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK,
+                                         SILC_STATUS_ERR_USER_NOT_ON_CHANNEL);
+    goto out;
+  }
+
+  /* Get comment */
+  tmp_len = 0;
+  comment = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+  if (tmp_len > 128)
+    comment = NULL;
+
+  /* Send command reply to sender */
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, 
+                                       SILC_STATUS_OK);
+
+  /* Send KICKED notify to local clients on the channel */
+  idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+  silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
+                                    SILC_NOTIFY_TYPE_KICKED, 3,
+                                    target_idp, target_idp_len,
+                                    comment, comment ? strlen(comment) : 0,
+                                    idp->data, idp->len);
+  silc_buffer_free(idp);
+
+  /* Remove the client from the channel. If the channel does not exist
+     after removing the client then the client kicked itself off the channel
+     and we don't have to send anything after that. */
+  if (!silc_server_remove_from_one_channel(server, NULL, channel, 
+                                          target_client, FALSE))
+    goto out;
+
+  /* Send KICKED notify to primary route */
+  if (!server->standalone)
+    silc_server_send_notify_kicked(server, server->router->connection,
+                                  server->server_type == SILC_ROUTER ?
+                                  TRUE : FALSE, channel,
+                                  target_client->id, client->id, comment);
+
+  if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
+    /* Re-generate channel key */
+    if (!silc_server_create_channel_key(server, channel, 0))
+      goto out;
+    
+    /* Send the channel key to the channel. The key of course is not sent
+       to the client who was kicked off the channel. */
+    silc_server_send_channel_key(server, target_client->connection, channel, 
+                                server->server_type == SILC_ROUTER ? 
+                                FALSE : !server->standalone);
+  }
+
+ out:
+  silc_server_command_free(cmd);
 }
 
-SILC_SERVER_CMD_FUNC(invite)
+/* Server side of OPER command. Client uses this comand to obtain server
+   operator privileges to this server/router. */
+
+SILC_SERVER_CMD_FUNC(oper)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  unsigned char *username, *auth;
+  uint32 tmp_len;
+  SilcServerConfigSectionAdmin *admin;
+  SilcIDListData idata = (SilcIDListData)client;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_OPER, cmd, 1, 2);
+
+  if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
+
+  /* Get the username */
+  username = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!username) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+
+  /* Get the admin configuration */
+  admin = silc_server_config_find_admin(server->config, cmd->sock->ip,
+                                       username, client->nickname);
+  if (!admin) {
+    admin = silc_server_config_find_admin(server->config, cmd->sock->hostname,
+                                         username, client->nickname);
+    if (!admin) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER,
+                                           SILC_STATUS_ERR_AUTH_FAILED);
+      goto out;
+    }
+  }
+
+  /* Get the authentication payload */
+  auth = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!auth) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+
+  /* Verify the authentication data */
+  if (!silc_auth_verify_data(auth, tmp_len, admin->auth_meth, 
+                            admin->auth_data, admin->auth_data_len,
+                            idata->hash, client->id, SILC_ID_CLIENT)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER,
+                                         SILC_STATUS_ERR_AUTH_FAILED);
+    goto out;
+  }
+
+  /* Client is now server operator */
+  client->mode |= SILC_UMODE_SERVER_OPERATOR;
+
+  /* Update statistics */
+  if (client->connection)
+    server->stat.my_server_ops++;
+  if (server->server_type == SILC_ROUTER)
+    server->stat.server_ops++;
+
+  /* Send UMODE change to primary router */
+  if (!server->standalone)
+    silc_server_send_notify_umode(server, server->router->connection, TRUE,
+                                 client->id, client->mode);
+
+  /* Send reply to the sender */
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER,
+                                       SILC_STATUS_OK);
+
+ out:
+  silc_server_command_free(cmd);
 }
 
-/* Quits connection to client. This gets called if client won't
-   close the connection even when it has issued QUIT command. */
+/* Server side of SILCOPER command. Client uses this comand to obtain router
+   operator privileges to this router. */
+
+SILC_SERVER_CMD_FUNC(silcoper)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  unsigned char *username, *auth;
+  uint32 tmp_len;
+  SilcServerConfigSectionAdmin *admin;
+  SilcIDListData idata = (SilcIDListData)client;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_SILCOPER, cmd, 1, 2);
+
+  if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
+
+  if (server->server_type != SILC_ROUTER) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER,
+                                         SILC_STATUS_ERR_AUTH_FAILED);
+    goto out;
+  }
+
+  /* Get the username */
+  username = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!username) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+
+  /* Get the admin configuration */
+  admin = silc_server_config_find_admin(server->config, cmd->sock->ip,
+                                       username, client->nickname);
+  if (!admin) {
+    admin = silc_server_config_find_admin(server->config, cmd->sock->hostname,
+                                         username, client->nickname);
+    if (!admin) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER,
+                                           SILC_STATUS_ERR_AUTH_FAILED);
+      goto out;
+    }
+  }
+
+  /* Get the authentication payload */
+  auth = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!auth) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+
+  /* Verify the authentication data */
+  if (!silc_auth_verify_data(auth, tmp_len, admin->auth_meth, 
+                            admin->auth_data, admin->auth_data_len,
+                            idata->hash, client->id, SILC_ID_CLIENT)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER,
+                                         SILC_STATUS_ERR_AUTH_FAILED);
+    goto out;
+  }
+
+  /* Client is now router operator */
+  client->mode |= SILC_UMODE_ROUTER_OPERATOR;
+
+  /* Update statistics */
+  if (client->connection)
+    server->stat.my_router_ops++;
+  if (server->server_type == SILC_ROUTER)
+    server->stat.router_ops++;
 
-SILC_TASK_CALLBACK(silc_server_command_quit_cb)
-{
-  SilcServer server = (SilcServer)context;
-  SilcSocketConnection sock = server->sockets[fd];
+  /* Send UMODE change to primary router */
+  if (!server->standalone)
+    silc_server_send_notify_umode(server, server->router->connection, TRUE,
+                                 client->id, client->mode);
 
-  /* Free all client specific data, such as client entry and entires
-     on channels this client may be on. */
-  silc_server_free_sock_user_data(server, sock);
+  /* Send reply to the sender */
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER,
+                                       SILC_STATUS_OK);
 
-  /* Close the connection on our side */
-  silc_server_close_connection(server, sock);
+ out:
+  silc_server_command_free(cmd);
 }
 
-/* Quits SILC session. This is the normal way to disconnect client. */
-SILC_SERVER_CMD_FUNC(quit)
+/* Server side command of CONNECT. Connects us to the specified remote
+   server or router. */
+
+SILC_SERVER_CMD_FUNC(connect)
 {
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
   SilcServer server = cmd->server;
-  SilcSocketConnection sock = cmd->sock;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  unsigned char *tmp, *host;
+  uint32 tmp_len;
+  uint32 port = SILC_PORT;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_CONNECT, cmd, 1, 2);
 
-  /* We quit the connection with little timeout */
-  silc_task_register(server->timeout_queue, sock->sock,
-                    silc_server_command_quit_cb, server,
-                    0, 300000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
+  if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
+
+  /* Check whether client has the permissions. */
+  if (client->mode == SILC_UMODE_NONE) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CONNECT,
+                                         SILC_STATUS_ERR_NO_SERVER_PRIV);
+    goto out;
+  }
+
+  if (server->server_type == SILC_ROUTER && 
+      client->mode & SILC_UMODE_SERVER_OPERATOR) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CONNECT,
+                                         SILC_STATUS_ERR_NO_ROUTER_PRIV);
+    goto out;
+  }
+
+  /* Get the remote server */
+  host = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!host) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CONNECT,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+
+  /* Get port */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (tmp)
+    SILC_GET32_MSB(port, tmp);
 
+  /* Create the connection. It is done with timeout and is async. */
+  silc_server_create_connection(server, host, port);
+
+  /* Send reply to the sender */
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_CONNECT,
+                                       SILC_STATUS_OK);
+
+ out:
   silc_server_command_free(cmd);
 }
 
-SILC_SERVER_CMD_FUNC(kill)
-{
-}
+/* Server side of command BAN. This is used to manage the ban list of the
+   channel. To add clients and remove clients from the ban list. */
 
-SILC_SERVER_CMD_FUNC(info)
+SILC_SERVER_CMD_FUNC(ban)
 {
-}
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcBuffer packet;
+  SilcChannelEntry channel;
+  SilcChannelClientEntry chl;
+  SilcChannelID *channel_id = NULL;
+  unsigned char *id, *add, *del;
+  uint32 id_len, tmp_len;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+
+  if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
 
-SILC_SERVER_CMD_FUNC(connect)
-{
-}
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_BAN, cmd, 0, 3);
 
-SILC_SERVER_CMD_FUNC(ping)
-{
-}
+  /* Get Channel ID */
+  id = silc_argument_get_arg_type(cmd->args, 1, &id_len);
+  if (id) {
+    channel_id = silc_id_payload_parse_id(id, id_len);
+    if (!channel_id) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN,
+                                           SILC_STATUS_ERR_NO_CHANNEL_ID);
+      goto out;
+    }
+  }
 
-SILC_SERVER_CMD_FUNC(oper)
-{
-}
+  /* Get channel entry. The server must know about the channel since the
+     client is expected to be on the channel. */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
+  if (!channel) {
+    channel = silc_idlist_find_channel_by_id(server->global_list, 
+                                            channel_id, NULL);
+    if (!channel) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
+    }
+  }
 
-typedef struct {
-  char *channel_name;
-  char *nickname;
-  char *username;
-  char *hostname;
-  SilcChannelList *channel;
-  SilcServer server;
-} JoinInternalContext;
+  /* Check whether this client is on the channel */
+  if (!silc_server_client_on_channel(client, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
+  }
 
-SILC_TASK_CALLBACK(silc_server_command_join_notify)
-{
-  JoinInternalContext *ctx = (JoinInternalContext *)context;
+  /* Get entry to the channel user list */
+  if (!silc_hash_table_find(channel->user_list, client, NULL, (void *)&chl)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
+  }
 
-  if (ctx->channel->key && ctx->channel->key_len) {
-    silc_server_send_notify_to_channel(ctx->server, ctx->channel,
-                                      "%s (%s@%s) has joined channel %s",
-                                      ctx->nickname, ctx->username,
-                                      ctx->hostname, ctx->channel_name);
-    silc_free(ctx);
-  } else {
-    silc_task_register(ctx->server->timeout_queue, fd,
-                      silc_server_command_join_notify, context,
-                      0, 300000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
+  /* The client must be at least channel operator. */
+  if (!(chl->mode & SILC_CHANNEL_UMODE_CHANOP)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN,
+                                         SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+    goto out;
   }
-}
 
-/* Server side of command JOIN. Joins client into requested channel. If 
-   the channel does not exist it will be created. */
+  /* Get the new ban and add it to the ban list */
+  add = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (add) {
+    if (!channel->ban_list)
+      channel->ban_list = silc_calloc(tmp_len + 2, sizeof(*channel->ban_list));
+    else
+      channel->ban_list = silc_realloc(channel->ban_list, 
+                                      sizeof(*channel->ban_list) * 
+                                      (tmp_len + 
+                                       strlen(channel->ban_list) + 2));
+    if (add[tmp_len - 1] == ',')
+      add[tmp_len - 1] = '\0';
+
+    strncat(channel->ban_list, add, tmp_len);
+    strncat(channel->ban_list, ",", 1);
+  }
 
-SILC_SERVER_CMD_FUNC(join)
+  /* Get the ban to be removed and remove it from the list */
+  del = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+  if (del && channel->ban_list) {
+    char *start, *end, *n;
+
+    if (!strncmp(channel->ban_list, del, strlen(channel->ban_list) - 1)) {
+      silc_free(channel->ban_list);
+      channel->ban_list = NULL;
+    } else {
+      start = strstr(channel->ban_list, del);
+      if (start && strlen(start) >= tmp_len) {
+       end = start + tmp_len;
+       n = silc_calloc(strlen(channel->ban_list) - tmp_len, sizeof(*n));
+       strncat(n, channel->ban_list, start - channel->ban_list);
+       strncat(n, end + 1, ((channel->ban_list + strlen(channel->ban_list)) - 
+                            end) - 1);
+       silc_free(channel->ban_list);
+       channel->ban_list = n;
+      }
+    }
+  }
+
+  /* Send the BAN notify type to our primary router. */
+  if (!server->standalone && (add || del))
+    silc_server_send_notify_ban(server, server->router->connection,
+                               server->server_type == SILC_ROUTER ?
+                               TRUE : FALSE, channel, add, del);
+
+  /* Send the reply back to the client */
+  packet = 
+    silc_command_reply_payload_encode_va(SILC_COMMAND_BAN,
+                                        SILC_STATUS_OK, ident, 2,
+                                        2, id, id_len,
+                                        3, channel->ban_list, 
+                                        channel->ban_list ? 
+                                        strlen(channel->ban_list) -1 : 0);
+  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         packet->data, packet->len, FALSE);
+    
+  silc_buffer_free(packet);
+
+ out:
+  silc_free(channel_id);
+  silc_server_command_free(cmd);
+}
+
+/* Server side command of CLOSE. Closes connection to a specified server. */
+SILC_SERVER_CMD_FUNC(close)
 {
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
   SilcServer server = cmd->server;
-  SilcSocketConnection sock = cmd->sock;
-  SilcBuffer buffer = cmd->packet->buffer;
-  int argc, i, tmp_len;
-  char *tmp, *channel_name = NULL, *cipher = NULL, *id_string = NULL;
-  unsigned char *passphrase;
-  SilcChannelList *channel;
-  SilcServerID *router_id;
-  SilcIDCache *id_cache;
-  SilcBuffer packet, sp_buf;
-  SilcClientList *client;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcServerEntry server_entry;
+  SilcSocketConnection sock;
+  unsigned char *tmp;
+  uint32 tmp_len;
+  unsigned char *name;
+  uint32 port = SILC_PORT;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_CLOSE, cmd, 1, 2);
 
-#define LCC(x) server->local_list->channel_cache[(x) - 32]
-#define LCCC(x) server->local_list->channel_cache_count[(x) - 32]
+  if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
 
-  /* Check number of parameters */
-  argc = silc_command_get_arg_num(cmd->payload);
-  if (argc < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+  /* Check whether client has the permissions. */
+  if (client->mode == SILC_UMODE_NONE) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CLOSE,
+                                         SILC_STATUS_ERR_NO_SERVER_PRIV);
     goto out;
   }
-  if (argc > 3) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
-                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);
+
+  /* Get the remote server */
+  name = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!name) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CLOSE,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
 
-  /* Get channel name */
-  tmp = silc_command_get_arg_type(cmd->payload, 1, NULL);
-  if (silc_server_command_bad_chars(tmp) == TRUE) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
-                                         SILC_STATUS_ERR_BAD_CHANNEL);
+  /* Get port */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (tmp)
+    SILC_GET32_MSB(port, tmp);
+
+  server_entry = silc_idlist_find_server_by_conn(server->local_list,
+                                                name, port, FALSE, NULL);
+  if (!server_entry)
+    server_entry = silc_idlist_find_server_by_conn(server->global_list,
+                                                  name, port, FALSE, NULL);
+  if (!server_entry) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CLOSE,
+                                         SILC_STATUS_ERR_NO_SERVER_ID);
     goto out;
   }
-  channel_name = strdup(tmp);
 
-  /* Get passphrase */
-  tmp = silc_command_get_arg_type(cmd->payload, 2, &tmp_len);
-  if (tmp) {
-    passphrase = silc_calloc(tmp_len, sizeof(*passphrase));
-    memcpy(passphrase, tmp, tmp_len);
+  /* Send reply to the sender */
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_CLOSE,
+                                       SILC_STATUS_OK);
+
+  /* Close the connection to the server */
+  sock = (SilcSocketConnection)server_entry->connection;
+
+  /* If we shutdown primary router connection manually then don't trigger
+     any reconnect or backup router connections, by setting the router
+     to NULL here. */
+  if (server->router == server_entry) {
+    server->id_entry->router = NULL;
+    server->router = NULL;
+    server->standalone = TRUE;
   }
+  silc_server_free_sock_user_data(server, sock, NULL);
+  silc_server_close_connection(server, sock);
   
-  /* Get cipher name */
-  cipher = silc_command_get_arg_type(cmd->payload, 3, NULL);
+ out:
+  silc_server_command_free(cmd);
+}
 
-  /* See if the channel exists */
-  if (silc_idcache_find_by_data(LCC(channel_name[0]), LCCC(channel_name[0]), 
-                               channel_name, &id_cache) == FALSE) {
-    /* Channel not found */
-    id_cache = NULL;
-
-    /* If we are standalone server we don't have a router, we just create 
-       the channel by  ourselves. */
-    if (server->standalone) {
-      router_id = server->id;
-      channel = silc_server_new_channel(server, router_id, 
-                                       cipher, channel_name);
-      goto join_channel;
-    }
-
-    /* No channel ID found, the channel does not exist on our server.
-       We send JOIN command to our router which will handle the joining
-       procedure (either creates the channel if it doesn't exist or
-       joins the client to it) - if we are normal server. */
-    if (server->server_type == SILC_SERVER) {
-
-      /* Forward the received JOIN command to the router */
-      silc_buffer_push(buffer, buffer->data - buffer->head);
-      silc_server_packet_forward(server, (SilcSocketConnection)
-                                server->id_entry->router->connection,
-                                buffer->data, buffer->len,
-                                TRUE);
-      
-      /* Add the command to be pending. It will be re-executed after
-        router has replied back to us. */
-      cmd->pending = TRUE;
-      silc_server_command_pending(SILC_COMMAND_JOIN, 
-                                 silc_server_command_join, context);
-      return;
-    }
+/* Server side command of SHUTDOWN. Shutdowns the server and closes all
+   active connections. */
+SILC_SERVER_CMD_FUNC(shutdown)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_SHUTDOWN, cmd, 0, 0);
+
+  if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT)
+    goto out;
+
+  /* Check whether client has the permission. */
+  if (client->mode == SILC_UMODE_NONE) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_SHUTDOWN,
+                                         SILC_STATUS_ERR_NO_SERVER_PRIV);
+    goto out;
   }
 
-  /* If we are router and the channel does not exist we will check our
-     global list for the channel. */
-  if (!id_cache && server->server_type == SILC_ROUTER) {
+  /* Send reply to the sender */
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_SHUTDOWN,
+                                       SILC_STATUS_OK);
+
+  /* Then, gracefully, or not, bring the server down. */
+  silc_server_stop(server);
+  exit(0);
+
+ out:
+  silc_server_command_free(cmd);
+}
+/* Server side command of LEAVE. Removes client from a channel. */
+
+SILC_SERVER_CMD_FUNC(leave)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcSocketConnection sock = cmd->sock;
+  SilcClientEntry id_entry = (SilcClientEntry)cmd->sock->user_data;
+  SilcChannelID *id = NULL;
+  SilcChannelEntry channel;
+  uint32 len;
+  unsigned char *tmp;
+
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_LEAVE, cmd, 1, 2);
+
+  /* Get Channel ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
+  id = silc_id_payload_parse_id(tmp, len);
+  if (!id) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
+  }
 
-    /* Notify all routers about the new channel in SILC network. */
-    if (!server->standalone) {
-#if 0
-      silc_server_send_new_id(server, server->id_entry->router->connection, 
-                             TRUE,
-                             xxx, SILC_ID_CHANNEL, SILC_ID_CHANNEL_LEN);
-#endif
+  /* Get channel entry */
+  channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
+  if (!channel) {
+    channel = silc_idlist_find_channel_by_id(server->global_list, id, NULL);
+    if (!channel) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
     }
+  }
 
+  /* Check whether this client is on the channel */
+  if (!silc_server_client_on_channel(id_entry, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
   }
 
-  channel = (SilcChannelList *)id_cache->context;
+  /* Notify routers that they should remove this client from their list
+     of clients on the channel. Send LEAVE notify type. */
+  if (!server->standalone)
+    silc_server_send_notify_leave(server, server->router->connection,
+                                 server->server_type == SILC_ROUTER ?
+                                 TRUE : FALSE, channel, id_entry->id);
 
- join_channel:
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
+                                       SILC_STATUS_OK);
 
-  /* XXX must check whether the client already is on the channel */
+  /* Remove client from channel */
+  if (!silc_server_remove_from_one_channel(server, sock, channel, id_entry,
+                                          TRUE))
+    /* If the channel does not exist anymore we won't send anything */
+    goto out;
 
-  /* Join the client to the channel */
-  i = channel->user_list_count;
-  channel->user_list = silc_realloc(channel->user_list, 
-                                   sizeof(*channel->user_list) * (i + 1));
-  channel->user_list[i].mode = SILC_CHANNEL_UMODE_NONE;
+  if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
+    /* Re-generate channel key */
+    if (!silc_server_create_channel_key(server, channel, 0))
+      goto out;
 
-  /* If the JOIN request was forwarded to us we will make a bit slower
-     query to get the client pointer. Otherwise, we get the client pointer
-     real easy. */
-  if (!(cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED)) {
-    client = (SilcClientList *)sock->user_data;
-    channel->user_list[i].client = client;
-  } else {
-    void *id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type);
-    client = silc_idlist_find_client_by_id(server->local_list->clients, id);
-    channel->user_list[i].client = client;
-    silc_free(id);
+    /* Send the channel key */
+    silc_server_send_channel_key(server, NULL, channel, 
+                                server->server_type == SILC_ROUTER ? 
+                                FALSE : !server->standalone);
   }
-  channel->user_list_count++;
 
-  i = client->channel_count;
-  client->channel = silc_realloc(client->channel, 
-                                sizeof(*client->channel) * (i + 1));
-  client->channel[i] = channel;
-  client->channel_count++;
+ out:
+  silc_free(id);
+  silc_server_command_free(cmd);
+}
 
-  /* Notify router about new user on channel. If we are normal server
-     we send it to our router, if we are router we send it to our
-     primary route. */
-  if (!server->standalone) {
+/* Server side of command USERS. Resolves clients and their USERS currently
+   joined on the requested channel. The list of Client ID's and their modes
+   on the channel is sent back. */
 
-  }
+SILC_SERVER_CMD_FUNC(users)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcChannelEntry channel;
+  SilcChannelID *id = NULL;
+  SilcBuffer packet, idp;
+  unsigned char *channel_id;
+  uint32 channel_id_len;
+  SilcBuffer client_id_list;
+  SilcBuffer client_mode_list;
+  unsigned char lc[4];
+  uint32 list_count = 0;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  char *channel_name;
 
-  /* Send Channel ID to the client */
-  if (!cmd->pending) {
-    id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL);
-    sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0);
-    if (!channel->topic)
-      packet = 
-       silc_command_encode_payload_va(SILC_COMMAND_JOIN, 3,
-                                      sp_buf->data, sp_buf->len,
-                                      channel_name, strlen(channel_name),
-                                      id_string, SILC_ID_CHANNEL_LEN);
-    else
-      packet = 
-       silc_command_encode_payload_va(SILC_COMMAND_JOIN, 4,
-                                      sp_buf->data, sp_buf->len,
-                                      channel_name, strlen(channel_name),
-                                      id_string, SILC_ID_CHANNEL_LEN,
-                                      channel->topic, 
-                                      strlen(channel->topic));
-
-    if (cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED) {
-      void *id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type);
-      silc_server_packet_send_dest(cmd->server, cmd->sock, 
-                                  SILC_PACKET_COMMAND_REPLY, 0,
-                                  id, cmd->packet->src_id_type,
-                                  packet->data, packet->len, FALSE);
-      silc_free(id);
-    } else
-      silc_server_packet_send(server, sock, SILC_PACKET_COMMAND_REPLY, 0, 
-                             packet->data, packet->len, FALSE);
-    
-    silc_buffer_free(packet);
-    silc_free(sp_buf);
+  SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_USERS, cmd, 1, 2);
+
+  /* Get Channel ID */
+  channel_id = silc_argument_get_arg_type(cmd->args, 1, &channel_id_len);
+
+  /* Get channel name */
+  channel_name = silc_argument_get_arg_type(cmd->args, 2, NULL);
+
+  if (!channel_id && !channel_name) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS,
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
+    goto out;
   }
 
-  /* Send channel key to the client. Client cannot start transmitting
-     to the channel until we have sent the key. */
-  if (!cmd->pending) {
-    tmp_len = strlen(channel->channel_key->cipher->name);
-    packet = 
-      silc_channel_key_encode_payload(SILC_ID_CHANNEL_LEN, 
-                                     id_string, tmp_len, 
-                                     channel->channel_key->cipher->name,
-                                     channel->key_len, channel->key);
-    
-    silc_server_packet_send(server, sock, SILC_PACKET_CHANNEL_KEY, 0, 
-                           packet->data, packet->len, FALSE);
-    silc_buffer_free(packet);
+  if (channel_id) {
+    id = silc_id_payload_parse_id(channel_id, channel_id_len);
+    if (!id) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS,
+                                           SILC_STATUS_ERR_NO_CHANNEL_ID);
+      goto out;
+    }
   }
 
-  if (id_string)
-    silc_free(id_string);
+  /* If we are server and we don't know about this channel we will send
+     the command to our router. If we know about the channel then we also
+     have the list of users already. */
+  if (id)
+    channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
+  else
+    channel = silc_idlist_find_channel_by_name(server->local_list, 
+                                              channel_name, NULL);
+
+  if (!channel || channel->disabled) {
+    if (server->server_type != SILC_ROUTER && !server->standalone &&
+       !cmd->pending) {
+      SilcBuffer tmpbuf;
+      
+      silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+      
+      /* Send USERS command */
+      silc_server_packet_send(server, server->router->connection,
+                             SILC_PACKET_COMMAND, cmd->packet->flags,
+                             tmpbuf->data, tmpbuf->len, TRUE);
+      
+      /* Reprocess this packet after received reply */
+      silc_server_command_pending(server, SILC_COMMAND_USERS, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_users,
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_command_set_ident(cmd->payload, ident);
+      silc_buffer_free(tmpbuf);
+      silc_free(id);
+      goto out;
+    }
 
-  /* Finally, send notify message to all clients on the channel about
-     new user on the channel. */
-  if (!(cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED)) {
-    if (!cmd->pending) {
-      silc_server_send_notify_to_channel(server, channel,
-                                        "%s (%s@%s) has joined channel %s",
-                                        client->nickname, client->username,
-                                        sock->hostname ? sock->hostname :
-                                        sock->ip, channel_name);
-    } else {
-      /* This is pending command request. Send the notify after we have
-        received the key for the channel from the router. */
-      JoinInternalContext *ctx = silc_calloc(1, sizeof(*ctx));
-      ctx->channel_name = channel_name;
-      ctx->nickname = client->nickname;
-      ctx->username = client->username;
-      ctx->hostname = sock->hostname ? sock->hostname : sock->ip;
-      ctx->channel = channel;
-      ctx->server = server;
-      silc_task_register(server->timeout_queue, sock->sock,
-                        silc_server_command_join_notify, ctx,
-                        0, 100000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
+    /* Check the global list as well. */
+    if (id)
+      channel = silc_idlist_find_channel_by_id(server->global_list, id, NULL);
+    else
+      channel = silc_idlist_find_channel_by_name(server->global_list, 
+                                                channel_name, NULL);
+    if (!channel) {
+      /* Channel really does not exist */
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
     }
   }
 
+  /* If the channel is private or secret do not send anything, unless the
+     user requesting this command is on the channel. */
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) {
+    if (channel->mode & (SILC_CHANNEL_MODE_PRIVATE | SILC_CHANNEL_MODE_SECRET)
+       && !silc_server_client_on_channel(cmd->sock->user_data, channel)) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
+    }
+  } else {
+    if (channel->mode & 
+       (SILC_CHANNEL_MODE_PRIVATE | SILC_CHANNEL_MODE_SECRET)) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS,
+                                           SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+      goto out;
+    }
+  }
+
+  /* Get the users list */
+  silc_server_get_users_on_channel(server, channel, &client_id_list,
+                                  &client_mode_list, &list_count);
+
+  /* List count */
+  SILC_PUT32_MSB(list_count, lc);
+
+  /* Send reply */
+  idp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_USERS,
+                                               SILC_STATUS_OK, ident, 4,
+                                               2, idp->data, idp->len,
+                                               3, lc, 4,
+                                               4, client_id_list->data,
+                                               client_id_list->len,
+                                               5, client_mode_list->data,
+                                               client_mode_list->len);
+  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         packet->data, packet->len, FALSE);
+    
+  silc_buffer_free(idp);
+  silc_buffer_free(packet);
+  silc_buffer_free(client_id_list);
+  silc_buffer_free(client_mode_list);
+  silc_free(id);
+
  out:
   silc_server_command_free(cmd);
-#undef LCC
-#undef LCCC
 }
 
-/* Server side of command MOTD. Sends servers current "message of the
-   day" to the client. */
+/* Server side of command GETKEY. This fetches the client's public key
+   from the server where to the client is connected. */
 
-SILC_SERVER_CMD_FUNC(motd)
+SILC_SERVER_CMD_FUNC(getkey)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcBuffer packet;
+  SilcClientEntry client;
+  SilcServerEntry server_entry;
+  SilcClientID *client_id = NULL;
+  SilcServerID *server_id = NULL;
+  SilcIDPayload idp = NULL;
+  uint16 ident = silc_command_get_ident(cmd->payload);
+  unsigned char *tmp, *pkdata;
+  uint32 tmp_len, pklen;
+  SilcBuffer pk = NULL;
+  SilcIdType id_type;
+  SilcPublicKey public_key;
 
   SILC_LOG_DEBUG(("Start"));
 
-}
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!tmp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_GETKEY,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
+  idp = silc_id_payload_parse(tmp, tmp_len);
+  if (!idp) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_GETKEY,
+                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    goto out;
+  }
 
-SILC_SERVER_CMD_FUNC(umode)
-{
-}
+  id_type = silc_id_payload_get_type(idp);
+  if (id_type == SILC_ID_CLIENT) {
+    client_id = silc_id_payload_get_id(idp);
+
+    /* If the client is not found from local list there is no chance it
+       would be locally connected client so send the command further. */
+    client = silc_idlist_find_client_by_id(server->local_list, 
+                                          client_id, TRUE, NULL);
+    if (!client)
+      client = silc_idlist_find_client_by_id(server->global_list, 
+                                            client_id, TRUE, NULL);
+    
+    if ((!client && !cmd->pending && !server->standalone) ||
+       (client && !client->connection && !cmd->pending) ||
+       (client && !client->data.public_key && !cmd->pending)) {
+      SilcBuffer tmpbuf;
+      uint16 old_ident;
+      SilcSocketConnection dest_sock;
+      
+      dest_sock = silc_server_get_client_route(server, NULL, 0, 
+                                              client_id, NULL);
+      if (!dest_sock)
+       goto out;
+      
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+      
+      silc_server_packet_send(server, dest_sock,
+                             SILC_PACKET_COMMAND, cmd->packet->flags,
+                             tmpbuf->data, tmpbuf->len, TRUE);
+      
+      /* Reprocess this packet after received reply from router */
+      silc_server_command_pending(server, SILC_COMMAND_GETKEY, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_getkey,
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_command_set_ident(cmd->payload, old_ident);
+      silc_buffer_free(tmpbuf);
+      goto out;
+    }
 
-SILC_SERVER_CMD_FUNC(cmode)
-{
-}
+    if (!client) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_GETKEY,
+                                           SILC_STATUS_ERR_NO_SUCH_CLIENT_ID);
+      goto out;
+    }
 
-SILC_SERVER_CMD_FUNC(kick)
-{
-}
+    /* The client is locally connected, just get the public key and
+       send it back. If they key does not exist then do not send it, 
+       send just OK reply */
+    public_key = client->data.public_key;
+    if (!public_key) {
+      pkdata = NULL;
+      pklen = 0;
+    } else {
+      tmp = silc_pkcs_public_key_encode(public_key, &tmp_len);
+      pk = silc_buffer_alloc(4 + tmp_len);
+      silc_buffer_pull_tail(pk, SILC_BUFFER_END(pk));
+      silc_buffer_format(pk,
+                        SILC_STR_UI_SHORT(tmp_len),
+                        SILC_STR_UI_SHORT(SILC_SKE_PK_TYPE_SILC),
+                        SILC_STR_UI_XNSTRING(tmp, tmp_len),
+                        SILC_STR_END);
+      silc_free(tmp);
+      pkdata = pk->data;
+      pklen = pk->len;
+    }
+  } else if (id_type == SILC_ID_SERVER) {
+    server_id = silc_id_payload_get_id(idp);
+
+    /* If the server is not found from local list there is no chance it
+       would be locally connected server so send the command further. */
+    server_entry = silc_idlist_find_server_by_id(server->local_list, 
+                                                server_id, TRUE, NULL);
+    if (!server_entry)
+      server_entry = silc_idlist_find_server_by_id(server->global_list, 
+                                                  server_id, TRUE, NULL);
+    
+    if (server_entry != server->id_entry &&
+       ((!server_entry && !cmd->pending && !server->standalone) ||
+        (server_entry && !server_entry->connection && !cmd->pending &&
+         !server->standalone) ||
+        (server_entry && !server_entry->data.public_key && !cmd->pending &&
+         !server->standalone))) {
+      SilcBuffer tmpbuf;
+      uint16 old_ident;
+      
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, ++server->cmd_ident);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+      
+      silc_server_packet_send(server, server->router->connection,
+                             SILC_PACKET_COMMAND, cmd->packet->flags,
+                             tmpbuf->data, tmpbuf->len, TRUE);
+      
+      /* Reprocess this packet after received reply from router */
+      silc_server_command_pending(server, SILC_COMMAND_GETKEY, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_getkey,
+                                 silc_server_command_dup(cmd));
+      cmd->pending = TRUE;
+      silc_command_set_ident(cmd->payload, old_ident);
+      silc_buffer_free(tmpbuf);
+      goto out;
+    }
 
-SILC_SERVER_CMD_FUNC(restart)
-{
-}
-SILC_SERVER_CMD_FUNC(close)
-{
-}
-SILC_SERVER_CMD_FUNC(die)
-{
-}
-SILC_SERVER_CMD_FUNC(silcoper)
-{
-}
+    if (!server_entry) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_GETKEY,
+                                           SILC_STATUS_ERR_NO_SUCH_SERVER_ID);
+      goto out;
+    }
 
-SILC_SERVER_CMD_FUNC(leave)
-{
-}
+    /* If they key does not exist then do not send it, send just OK reply */
+    public_key = (!server_entry->data.public_key ? 
+                 (server_entry == server->id_entry ? server->public_key :
+                  NULL) : server_entry->data.public_key);
+    if (!public_key) {
+      pkdata = NULL;
+      pklen = 0;
+    } else {
+      tmp = silc_pkcs_public_key_encode(public_key, &tmp_len);
+      pk = silc_buffer_alloc(4 + tmp_len);
+      silc_buffer_pull_tail(pk, SILC_BUFFER_END(pk));
+      silc_buffer_format(pk,
+                        SILC_STR_UI_SHORT(tmp_len),
+                        SILC_STR_UI_SHORT(SILC_SKE_PK_TYPE_SILC),
+                        SILC_STR_UI_XNSTRING(tmp, tmp_len),
+                        SILC_STR_END);
+      silc_free(tmp);
+      pkdata = pk->data;
+      pklen = pk->len;
+    }
+  } else {
+    goto out;
+  }
 
-SILC_SERVER_CMD_FUNC(names)
-{
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_GETKEY,
+                                               SILC_STATUS_OK, ident, 
+                                               pkdata ? 2 : 1,
+                                               2, tmp, tmp_len,
+                                               3, pkdata, pklen);
+  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                         packet->data, packet->len, FALSE);
+  silc_buffer_free(packet);
+
+  if (pk)
+    silc_buffer_free(pk);
+
+ out:
+  if (idp)
+    silc_id_payload_free(idp);
+  silc_free(client_id);
+  silc_free(server_id);
+  silc_server_command_free(cmd);
 }