updates
[silc.git] / apps / silcd / command.c
index f0d2447062af5f93a32047e888a7d0e54435b9c1..c951889e53f0e37377bf1678c14fba17ef040a36 100644 (file)
   GNU General Public License for more details.
 
 */
-/*
- * $Id$
- * $Log$
- * Revision 1.10  2000/07/17 11:47:30  priikone
- *     Added command lagging support. Added idle counting support.
- *
- * Revision 1.9  2000/07/12 05:59:41  priikone
- *     Major rewrite of ID Cache system. Support added for the new
- *     ID cache system. Major rewrite of ID List stuff on server.  All
- *     SilcXXXList's are now called SilcXXXEntry's and they are pointers
- *     by default. A lot rewritten ID list functions.
- *
- * Revision 1.8  2000/07/10 05:42:59  priikone
- *     Removed command packet processing from server.c and added it to
- *     command.c.
- *     Implemented INFO command. Added support for testing that
- *     connections are registered before executing commands.
- *
- * Revision 1.7  2000/07/07 06:55:24  priikone
- *     Do not allow client to join twice on same channel.
- *
- * Revision 1.6  2000/07/06 10:20:59  priikone
- *     Cipher name in joining is not mandatory, removed check.
- *
- * Revision 1.5  2000/07/06 07:16:43  priikone
- *     Fixed a wrong way of sending command replies. The fixed way
- *     does comply with the protocol.
- *
- * Revision 1.4  2000/07/05 06:13:38  priikone
- *     Added PING, INVITE and NAMES command.
- *
- * Revision 1.3  2000/07/03 05:52:22  priikone
- *     Implemented LEAVE command.
- *
- * Revision 1.2  2000/06/28 05:06:38  priikone
- *     Shorter timeout for channel joining notify.
- *
- * Revision 1.1.1.1  2000/06/27 11:36:56  priikone
- *     Imported from internal CVS/Added Log headers.
- *
- *
- */
+/* $Id$ */
 
 #include "serverincludes.h"
 #include "server_internal.h"
@@ -79,6 +38,9 @@ silc_server_command_send_status_data(SilcServerCommandContext cmd,
                                     unsigned char *arg,
                                     unsigned int arg_len);
 static void silc_server_command_free(SilcServerCommandContext cmd);
+void silc_server_command_send_names(SilcServer server,
+                                   SilcSocketConnection sock,
+                                   SilcChannelEntry channel);
 
 /* Server command list. */
 SilcServerCommand silc_command_list[] =
@@ -101,6 +63,7 @@ SilcServerCommand silc_command_list[] =
   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(cumode, CUMODE, 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),
@@ -115,8 +78,25 @@ SilcServerCommand silc_command_list[] =
   { NULL, 0 },
 };
 
-/* List of pending commands. */
-SilcServerCommandPending *silc_command_pending = NULL;
+#define SILC_SERVER_COMMAND_CHECK_ARGC(command, context, min, max)           \
+do {                                                                         \
+  unsigned int _argc = silc_argument_get_arg_num(cmd->args);                 \
+                                                                             \
+  SILC_LOG_DEBUG(("Start"));                                                 \
+                                                                             \
+  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. */
@@ -126,25 +106,9 @@ static int silc_server_is_registered(SilcServer server,
                                     SilcServerCommandContext cmd,
                                     SilcCommand command)
 {
-  switch(sock->type) {
-  case SILC_SOCKET_TYPE_CLIENT:
-    {
-      SilcClientEntry client = (SilcClientEntry)sock->user_data;
-      if (client->registered)
-       return TRUE;
-      break;
-    }
-  case SILC_SOCKET_TYPE_SERVER:
-  case SILC_SOCKET_TYPE_ROUTER:
-    {
-      SilcServerEntry serv = (SilcServerEntry)sock->user_data;
-      if (serv->registered)
-       return TRUE;
-      break;
-    }
-  default:
-    break;
-  }
+  SilcIDListData idata = (SilcIDListData)sock->user_data;
+  if (idata->registered)
+    return TRUE;
 
   silc_server_command_send_status_reply(cmd, command,
                                        SILC_STATUS_ERR_NOT_REGISTERED);
@@ -161,6 +125,10 @@ void silc_server_command_process(SilcServer server,
   SilcServerCommandContext ctx;
   SilcServerCommand *cmd;
 
+#if 0
+  /* XXX allow commands in but do not execute them more than once per
+     two seconds. */
+
   /* Check whether it is allowed for this connection to execute any
      command. */
   if (sock->type == SILC_SOCKET_TYPE_CLIENT) {
@@ -168,32 +136,35 @@ void silc_server_command_process(SilcServer server,
     SilcClientEntry client = (SilcClientEntry)sock->user_data;
 
     if (!client)
-      goto out;
+      return;
 
     /* Allow only one command executed in 2 seconds. */
     curtime = time(NULL);
     if (client->last_command && (curtime - client->last_command) < 2)
-      goto out;
+      return;
 
     /* Update access time */
     client->last_command = curtime;
   }
+#endif
   
   /* Allocate command context. This must be free'd by the
      command routine receiving it. */
   ctx = silc_calloc(1, sizeof(*ctx));
   ctx->server = server;
   ctx->sock = sock;
-  ctx->packet = packet;        /* Save original packet */
+  ctx->packet = silc_packet_context_dup(packet); /* Save original packet */
   
   /* Parse the command payload in the packet */
-  ctx->payload = silc_command_parse_payload(packet->buffer);
+  ctx->payload = silc_command_payload_parse(packet->buffer);
   if (!ctx->payload) {
     SILC_LOG_ERROR(("Bad command payload, packet dropped"));
     silc_buffer_free(packet->buffer);
+    silc_packet_context_free(packet);
     silc_free(ctx);
     return;
   }
+  ctx->args = silc_command_get_args(ctx->payload);
   
   /* Execute command. If this fails the packet is dropped. */
   for (cmd = silc_command_list; cmd->cb; cmd++)
@@ -212,67 +183,71 @@ void silc_server_command_process(SilcServer server,
 
   if (cmd == NULL) {
     SILC_LOG_ERROR(("Unknown command, packet dropped"));
-    silc_free(ctx);
+    silc_server_command_free(ctx);
     return;
   }
-
- out:
-  silc_buffer_free(packet->buffer);
 }
 
-/* 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.
+/* Add new pending command to be executed when reply to a command has been
+   received. The `reply_cmd' is the command that will call the `callback'
+   with `context' when reply has been received.  If `ident' is non-zero
+   the `callback' will be executed when received reply with command
+   identifier `ident'. */
 
-   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. */
-
-void silc_server_command_pending(SilcCommand reply_cmd,
+void silc_server_command_pending(SilcServer server,
+                                SilcCommand reply_cmd,
+                                unsigned short ident,
                                 SilcCommandCb callback,
                                 void *context)
 {
-  SilcServerCommandPending *reply, *r;
+  SilcServerCommandPending *reply;
 
   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);
+}
 
-  if (silc_command_pending == NULL) {
-    silc_command_pending = reply;
-    return;
-  }
+/* Deletes pending command by reply command type. */
+
+void silc_server_command_pending_del(SilcServer server,
+                                    SilcCommand reply_cmd,
+                                    unsigned short ident)
+{
+  SilcServerCommandPending *r;
 
-  for (r = silc_command_pending; r; r = r->next) {
-    if (r->next == NULL) {
-      r->next = reply;
+  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;
     }
   }
 }
 
-/* Deletes pending command by reply command type. */
+/* Checks for pending commands and marks callbacks to be called from
+   the command reply function. Returns TRUE if there were pending command. */
 
-void silc_server_command_pending_del(SilcCommand reply_cmd)
+int silc_server_command_pending_check(SilcServer server,
+                                     SilcServerCommandReplyContext ctx,
+                                     SilcCommand command, 
+                                     unsigned short 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;
-    }
-
-    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;
-      }
+  SilcServerCommandPending *r;
+
+  silc_dlist_start(server->pending_commands);
+  while ((r = silc_dlist_get(server->pending_commands)) != SILC_LIST_END) {
+    if (r->reply_cmd == command && r->ident == ident) {
+      ctx->context = r->context;
+      ctx->callback = r->callback;
+      ctx->ident = ident;
+      return TRUE;
     }
   }
+
+  return FALSE;
 }
 
 /* Free's the command context allocated before executing the command */
@@ -280,14 +255,14 @@ void silc_server_command_pending_del(SilcCommand reply_cmd)
 static void silc_server_command_free(SilcServerCommandContext cmd)
 {
   if (cmd) {
-    silc_command_free_payload(cmd->payload);
+    if (cmd->payload)
+      silc_command_free_payload(cmd->payload);
+    if (cmd->packet)
+      silc_packet_context_free(cmd->packet);
     silc_free(cmd);
   }
 }
 
-#define SILC_COMMAND_STATUS_DATA(x) \
-  (
-
 /* Sends simple status message as command reply packet */
 
 static void 
@@ -299,7 +274,7 @@ silc_server_command_send_status_reply(SilcServerCommandContext cmd,
 
   SILC_LOG_DEBUG(("Sending command status %d", status));
 
-  buffer = silc_command_encode_reply_payload_va(command, status, 0);
+  buffer = silc_command_reply_payload_encode_va(command, status, 0, 0);
   silc_server_packet_send(cmd->server, cmd->sock,
                          SILC_PACKET_COMMAND_REPLY, 0, 
                          buffer->data, buffer->len, FALSE);
@@ -321,7 +296,7 @@ silc_server_command_send_status_data(SilcServerCommandContext cmd,
 
   SILC_LOG_DEBUG(("Sending command status %d", status));
 
-  buffer = silc_command_encode_reply_payload_va(command, status, 1,
+  buffer = silc_command_reply_payload_encode_va(command, status, 0, 1,
                                                arg_type, arg, arg_len);
   silc_server_packet_send(cmd->server, cmd->sock,
                          SILC_PACKET_COMMAND_REPLY, 0, 
@@ -329,401 +304,931 @@ silc_server_command_send_status_data(SilcServerCommandContext cmd,
   silc_buffer_free(buffer);
 }
 
-/* Server side of command WHOIS. Processes user's query and sends found 
-   results as command replies back to the client. */
+/******************************************************************************
 
-SILC_SERVER_CMD_FUNC(whois)
-{
-  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  SilcServer server = cmd->server;
-  char *tmp, *nick = NULL, *server_name = NULL;
-  unsigned int argc, count = 0, len;
-  SilcClientEntry entry;
-  SilcBuffer packet;
-  unsigned char *id_string;
+                              WHOIS Functions
 
-  SILC_LOG_DEBUG(("Start"));
+******************************************************************************/
 
-  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;
-  }
+static int
+silc_server_command_whois_parse(SilcServerCommandContext cmd,
+                               SilcClientID **client_id,
+                               char **nickname,
+                               char **server_name,
+                               int *count)
+{
+  unsigned char *tmp;
+  unsigned int len;
+  unsigned int argc = silc_argument_get_arg_num(cmd->args);
 
-  /* 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_name = silc_calloc(strlen(tmp) - len, sizeof(char));
-      memcpy(server_name, tmp + len + 1, strlen(tmp) - len - 1);
+  /* If client ID is in the command it must be used instead of nickname */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &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) {
+      if (strchr(tmp, '@')) {
+       len = strcspn(tmp, "@");
+       *nickname = silc_calloc(len + 1, sizeof(char));
+       memcpy(*nickname, tmp, len);
+       *server_name = silc_calloc(strlen(tmp) - len, sizeof(char));
+       memcpy(*server_name, tmp + len + 1, strlen(tmp) - len - 1);
+      } else {
+       *nickname = strdup(tmp);
+      }
     } else {
-      nick = strdup(tmp);
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS,
+                                           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, use that */
+    *client_id = silc_id_payload_parse_id(tmp, len);
   }
 
   /* Get the max count of reply messages allowed */
-  if (argc == 2) {
-    tmp = silc_command_get_next_arg(cmd->payload, NULL);
+  if (argc == 3) {
+    tmp = silc_argument_get_arg_type(cmd->args, 3, 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_name)
-       silc_free(server_name);
-      goto out;
+      if (*nickname)
+       silc_free(*nickname);
+      if (*server_name)
+       silc_free(*server_name);
+
+      return FALSE;
     }
-    count = atoi(tmp);
+    *count = atoi(tmp);
   }
 
-  /* Then, make the query from our local client list */
-  entry = silc_idlist_find_client_by_nickname(server->local_list, 
-                                             nick, server_name);
-  if (!entry) {
+  return TRUE;
+}
 
-    /* If we are normal server and are connected to a router we will
-       make global query from the router. */
-    if (server->server_type == SILC_SERVER && !server->standalone) {
+static char
+silc_server_command_whois_check(SilcServerCommandContext cmd,
+                               SilcClientEntry *clients,
+                               unsigned int clients_count)
+{
+  SilcServer server = cmd->server;
+  int i;
+  SilcClientEntry entry;
 
-      goto ok;
-    }
-    
-    /* If we are router then we will check our global list as well. */
-    if (server->server_type == SILC_ROUTER) {
-      entry =
-       silc_idlist_find_client_by_nickname(server->global_list,
-                                           nick, server_name);
-      if (!entry) {
-       silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
-                                            SILC_STATUS_ERR_NO_SUCH_NICK,
-                                            3, tmp, strlen(tmp));
-       goto out;
-      }
-      goto ok;
-    }
+  for (i = 0; i < clients_count; i++) {
+    entry = clients[i];
 
-    silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
-                                        SILC_STATUS_ERR_NO_SUCH_NICK,
-                                        3, tmp, strlen(tmp));
-    goto out;
-  }
+    if (!entry->nickname || !entry->username || !entry->userinfo) {
+      SilcBuffer tmpbuf;
+      unsigned short old_ident;
+      
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng));
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+
+      /* Send WHOIS 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_WHOIS, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_whois, (void *)cmd);
+      cmd->pending = TRUE;
+      
+      silc_command_set_ident(cmd->payload, old_ident);
 
- ok:
-  /* XXX, works only for local server info */
+      silc_buffer_free(tmpbuf);
+      return FALSE;
+    }
+  }
 
-  /* Send WHOIS reply */
-  id_string = silc_id_id2str(entry->id, SILC_ID_CLIENT);
-  tmp = silc_command_get_first_arg(cmd->payload, NULL);
+  return TRUE;
+}
 
-  /* XXX */
-  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) {
-    char nh[256], uh[256];
-    unsigned char idle[4];
-    SilcSocketConnection hsock;
+static void
+silc_server_command_whois_send_reply(SilcServerCommandContext cmd,
+                                    SilcClientEntry *clients,
+                                    unsigned int clients_count)
+{
+  SilcServer server = cmd->server;
+  char *tmp;
+  int i, count = 0, len;
+  SilcBuffer packet, idp;
+  SilcClientEntry entry;
+  SilcCommandStatus status;
+  unsigned short ident = silc_command_get_ident(cmd->payload);
 
-    memset(uh, 0, sizeof(uh));
-    memset(nh, 0, sizeof(nh));
+  status = SILC_STATUS_OK;
+  if (clients_count > 1)
+    status = SILC_STATUS_LIST_START;
 
-    strncat(nh, entry->nickname, strlen(entry->nickname));
-    strncat(nh, "@", 1);
-    len = entry->router ? strlen(entry->router->server_name) :
-      strlen(server->server_name);
-    strncat(nh, entry->router ? entry->router->server_name :
-           server->server_name, len);
+  for (i = 0; i < clients_count; i++) {
+    entry = clients[i];
 
-    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 (count && i - 1 == count)
+      break;
 
-    SILC_PUT32_MSB((time(NULL) - entry->last_receive), idle);
+    if (clients_count > 2)
+      status = SILC_STATUS_LIST_ITEM;
 
-    /* XXX */
-    if (entry->userinfo)
-      packet = 
-        silc_command_encode_reply_payload_va(SILC_COMMAND_WHOIS,
-                                            SILC_STATUS_OK, 5, 
-                                            2, id_string, SILC_ID_CLIENT_LEN,
-                                            3, nh, strlen(nh),
-                                            4, uh, strlen(uh),
-                                            5, entry->userinfo, 
-                                            strlen(entry->userinfo),
-                                            7, idle, 4);
-    else
-      packet = 
-        silc_command_encode_reply_payload_va(SILC_COMMAND_WHOIS,
-                                            SILC_STATUS_OK, 4, 
-                                            2, id_string, SILC_ID_CLIENT_LEN,
-                                            3, nh, strlen(nh),
-                                            4, uh, strlen(uh),
-                                            7, idle, 4);
+    if (clients_count > 1 && i == clients_count - 1)
+      status = SILC_STATUS_LIST_END;
 
-  } else {
+    /* Send WHOIS reply */
+    idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+    tmp = silc_argument_get_first_arg(cmd->args, NULL);
+    
     /* XXX */
-    packet = 
-      silc_command_encode_reply_payload_va(SILC_COMMAND_WHOIS, 
-                                          SILC_STATUS_OK, 3, 
-                                          2, id_string, SILC_ID_CLIENT_LEN,
-                                          3, entry->nickname, 
-                                          strlen(entry->nickname),
-                                          4, tmp, strlen(tmp)); /* XXX */
-  }
-  silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
-                         0, packet->data, packet->len, FALSE);
-
-  silc_free(id_string);
-  silc_buffer_free(packet);
-
- out:
-  silc_server_command_free(cmd);
-}
+    {
+      char nh[256], uh[256];
+      unsigned char idle[4];
+      SilcSocketConnection hsock;
 
-SILC_SERVER_CMD_FUNC(whowas)
-{
+      memset(uh, 0, sizeof(uh));
+      memset(nh, 0, sizeof(nh));
+      
+      strncat(nh, entry->nickname, strlen(entry->nickname));
+      if (!strchr(entry->nickname, '@')) {
+       strncat(nh, "@", 1);
+       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);
+       hsock = (SilcSocketConnection)entry->connection;
+       len = strlen(hsock->hostname);
+       strncat(uh, hsock->hostname, len);
+      }
+      
+      SILC_PUT32_MSB((time(NULL) - entry->data.last_receive), idle);
+      
+      /* XXX */
+      if (entry->userinfo)
+       packet = 
+         silc_command_reply_payload_encode_va(SILC_COMMAND_WHOIS,
+                                              status, ident, 5, 
+                                              2, idp->data, idp->len,
+                                              3, nh, strlen(nh),
+                                              4, uh, strlen(uh),
+                                              5, entry->userinfo, 
+                                              strlen(entry->userinfo),
+                                              7, idle, 4);
+      else
+       packet = 
+         silc_command_reply_payload_encode_va(SILC_COMMAND_WHOIS,
+                                              status, ident, 4, 
+                                              2, idp->data, idp->len,
+                                              3, nh, strlen(nh),
+                                              4, uh, strlen(uh),
+                                              7, idle, 4);
+    }
+    
+    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);
+  }
 }
 
-SILC_SERVER_CMD_FUNC(identify)
+static int
+silc_server_command_whois_from_client(SilcServerCommandContext cmd)
 {
-  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
   SilcServer server = cmd->server;
-  char *tmp, *nick = NULL, *server_name = NULL;
-  unsigned int argc, count = 0, len;
-  SilcClientEntry entry;
-  SilcBuffer 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);
+  char *nick = NULL, *server_name = NULL;
+  int count = 0, clients_count;
+  SilcClientID *client_id = NULL;
+  SilcClientEntry *clients = NULL, entry;
+  int ret = 0;
+
+  /* Parse the whois request */
+  if (!silc_server_command_whois_parse(cmd, &client_id, &nick, 
+                                      &server_name, &count))
+    return 0;
+
+  /* Protocol dictates that we must always send the received WHOIS 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 (server->server_type == SILC_SERVER && 
+      !cmd->pending && !server->standalone) {
+    SilcBuffer tmpbuf;
+    unsigned short old_ident;
+
+    old_ident = silc_command_get_ident(cmd->payload);
+    silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng));
+    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, (void *)cmd);
+    cmd->pending = TRUE;
+
+    silc_command_set_ident(cmd->payload, old_ident);
+
+    silc_buffer_free(tmpbuf);
+    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_name = silc_calloc(strlen(tmp) - len, sizeof(char));
-      memcpy(server_name, tmp + len + 1, strlen(tmp) - len - 1);
-    } else {
-      nick = strdup(tmp);
+  /* We are ready to process the command request. Let's search for the
+     requested client and send reply to the requesting client. */
+
+  /* Get all clients matching that ID or nickname from local list */
+  if (client_id) {
+    entry = silc_idlist_find_client_by_id(server->local_list, client_id, NULL);
+    if (entry) {
+      clients = silc_calloc(1, sizeof(*clients));
+      clients[0] = entry;
+      clients_count = 1;
     }
   } else {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+    clients = silc_idlist_get_clients_by_nickname(server->local_list, 
+                                                 nick, server_name,
+                                                 &clients_count);
+  }
+  
+  /* Check global list as well */
+  if (!clients) {
+    if (client_id) {
+      entry = silc_idlist_find_client_by_id(server->global_list, 
+                                           client_id, NULL);
+      if (entry) {
+       clients = silc_calloc(1, sizeof(*clients));
+       clients[0] = entry;
+       clients_count = 1;
+      }
+    } else {
+      clients = silc_idlist_get_clients_by_nickname(server->global_list, 
+                                                   nick, server_name,
+                                                   &clients_count);
+    }
+  }
+  
+  if (!clients) {
+    /* Such a client really does not exist in the SILC network. */
+    silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
+                                        SILC_STATUS_ERR_NO_SUCH_NICK,
+                                        3, nick, strlen(nick));
     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;
-    }
-    count = atoi(tmp);
+  /* 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 (server->server_type == SILC_ROUTER &&
+      !silc_server_command_whois_check(cmd, clients, clients_count)) {
+    ret = -1;
+    goto out;
   }
 
-  /* Then, make the query from our local client list */
-  entry = silc_idlist_find_client_by_nickname(server->local_list,
-                                             nick, NULL);
-  if (!entry) {
+  /* Send the command reply to the client */
+  silc_server_command_whois_send_reply(cmd, clients, clients_count);
 
-    /* If we are normal server and are connected to a router we will
-       make global query from the router. */
-    if (server->server_type == SILC_SERVER && !server->standalone) {
-      SilcBuffer buffer = cmd->packet->buffer;
+ out:
+  if (clients)
+    silc_free(clients);
+  if (client_id)
+    silc_free(client_id);
+  if (nick)
+    silc_free(nick);
+  if (server_name)
+    silc_free(server_name);
 
-      SILC_LOG_DEBUG(("Requesting identify from router"));
+  return ret;
+}
 
-      /* Send IDENTIFY command to our 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);
-      goto out;
+static int
+silc_server_command_whois_from_server(SilcServerCommandContext cmd)
+{
+  SilcServer server = cmd->server;
+  char *nick = NULL, *server_name = NULL;
+  int count = 0, clients_count;
+  SilcClientID *client_id = NULL;
+  SilcClientEntry *clients = NULL, entry;
+  int ret = 0;
+
+  /* Parse the whois request */
+  if (!silc_server_command_whois_parse(cmd, &client_id, &nick, 
+                                      &server_name, &count))
+    return 0;
+
+  /* Process the command request. Let's search for the requested client and
+     send reply to the requesting server. */
+
+  if (client_id) {
+    entry = silc_idlist_find_client_by_id(server->local_list, client_id, NULL);
+    if (entry) {
+      clients = silc_calloc(1, sizeof(*clients));
+      clients[0] = entry;
+      clients_count = 1;
     }
-    
-    /* If we are router then we will check our global list as well. */
-    if (server->server_type == SILC_ROUTER) {
-      entry = 
-       silc_idlist_find_client_by_nickname(server->global_list,
-                                           nick, NULL);
-      if (!entry) {
-       silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
-                                            SILC_STATUS_ERR_NO_SUCH_NICK,
-                                            3, tmp, strlen(tmp));
-       goto out;
+  } else {
+    clients = silc_idlist_get_clients_by_nickname(server->local_list, 
+                                                 nick, server_name,
+                                                 &clients_count);
+    if (!clients)
+      clients = silc_idlist_get_clients_by_hash(server->local_list, 
+                                               nick, server->md5hash,
+                                               &clients_count);
+  }
+  
+  /* If we are router we will check our global list as well. */
+  if (!clients && server->server_type == SILC_ROUTER) {
+    if (client_id) {
+      entry = silc_idlist_find_client_by_id(server->global_list, 
+                                           client_id, NULL);
+      if (entry) {
+       clients = silc_calloc(1, sizeof(*clients));
+       clients[0] = entry;
+       clients_count = 1;
       }
-      goto ok;
+    } else {
+      clients = silc_idlist_get_clients_by_nickname(server->global_list, 
+                                                   nick, server_name,
+                                                   &clients_count);
+      if (!clients)
+       clients = silc_idlist_get_clients_by_hash(server->global_list, 
+                                                 nick, server->md5hash,
+                                                 &clients_count);
     }
+  }
 
-    silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+  if (!clients) {
+    /* Such a client really does not exist in the SILC network. */
+    silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
                                         SILC_STATUS_ERR_NO_SUCH_NICK,
-                                        3, tmp, strlen(tmp));
+                                        3, nick, strlen(nick));
     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);
-  packet = silc_command_encode_reply_payload_va(SILC_COMMAND_IDENTIFY,
-                                               SILC_STATUS_OK, 2,
-                                               2, id_string, 
-                                               SILC_ID_CLIENT_LEN,
-                                               3, nick, strlen(nick));
-#if 0
-  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(server, cmd->sock, 
-                                SILC_PACKET_COMMAND_REPLY, 0,
-                                id, cmd->packet->src_id_type,
-                                packet->data, packet->len, FALSE);
-    silc_free(id);
-  } else
-#endif
-    silc_server_packet_send(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 (server->server_type == SILC_ROUTER &&
+      !silc_server_command_whois_check(cmd, clients, clients_count)) {
+    ret = -1;
+    goto out;
+  }
 
-  silc_free(id_string);
-  silc_buffer_free(packet);
+  /* Send the command reply to the client */
+  silc_server_command_whois_send_reply(cmd, clients, clients_count);
 
  out:
+  if (clients)
+    silc_free(clients);
+  if (client_id)
+    silc_free(client_id);
   if (nick)
     silc_free(nick);
   if (server_name)
     silc_free(server_name);
-  silc_server_command_free(cmd);
+
+  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;
 
-  return FALSE;
-}
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_WHOIS, cmd, 1, 3);
 
-/* 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. */
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT)
+    ret = silc_server_command_whois_from_client(cmd);
+  else
+    ret = silc_server_command_whois_from_server(cmd);
 
-SILC_SERVER_CMD_FUNC(nick)
+  if (!ret)
+    silc_server_command_free(cmd);
+}
+
+SILC_SERVER_CMD_FUNC(whowas)
 {
-  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  SilcClientEntry id_entry = (SilcClientEntry)cmd->sock->user_data;
-  SilcServer server = cmd->server;
-  SilcBuffer packet;
-  SilcClientID *new_id;
-  char *id_string;
-  char *nick;
+}
 
-  SILC_LOG_DEBUG(("Start"));
+/******************************************************************************
 
-  /* Check number of arguments */
-  if (silc_command_get_arg_num(cmd->payload) < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_NICK,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
+                              IDENTIFY Functions
 
-  /* 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;
-  }
+******************************************************************************/
 
-  /* Create new Client ID */
-  silc_id_create_client_id(cmd->server->id, cmd->server->rng, 
-                          cmd->server->md5hash, nick,
-                          &new_id);
+static int
+silc_server_command_identify_parse(SilcServerCommandContext cmd,
+                                  SilcClientID **client_id,
+                                  char **nickname,
+                                  char **server_name,
+                                  int *count)
+{
+  unsigned char *tmp;
+  unsigned int len;
+  unsigned int argc = silc_argument_get_arg_num(cmd->args);
 
-  /* 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 client ID is in the command it must be used instead of nickname */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &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) {
+      if (strchr(tmp, '@')) {
+       len = strcspn(tmp, "@");
+       *nickname = silc_calloc(len + 1, sizeof(char));
+       memcpy(*nickname, tmp, len);
+       *server_name = silc_calloc(strlen(tmp) - len, sizeof(char));
+       memcpy(*server_name, tmp + len + 1, strlen(tmp) - len - 1);
+      } else {
+       *nickname = strdup(tmp);
+      }
+    } else {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY,
+                                           SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+      return FALSE;
+    }
+  } else {
+    /* Command includes ID, use that */
+    *client_id = silc_id_payload_parse_id(tmp, 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 */
+  if (argc == 3) {
+    tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
+    if (!tmp) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY,
+                                           SILC_STATUS_ERR_TOO_MANY_PARAMS);
+      if (*nickname)
+       silc_free(*nickname);
+      if (*server_name)
+       silc_free(*server_name);
 
-  /* Remove old cache entry */
-  silc_idcache_del_by_id(server->local_list->clients, 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 FALSE;
+    }
+    *count = atoi(tmp);
   }
 
-  /* Save the nickname as this client is our local client */
-  if (id_entry->nickname)
-    silc_free(id_entry->nickname);
+  return TRUE;
+}
 
-  id_entry->nickname = strdup(nick);
-  id_entry->id = new_id;
+/* Checks that all mandatory fields 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. */
 
-  /* Update client cache */
-  silc_idcache_add(server->local_list->clients, id_entry->nickname, 
-                  SILC_ID_CLIENT, id_entry->id, (void *)id_entry, TRUE);
+static char
+silc_server_command_identify_check(SilcServerCommandContext cmd,
+                                  SilcClientEntry *clients,
+                                  unsigned int clients_count)
+{
+  SilcServer server = cmd->server;
+  int i;
+  SilcClientEntry entry;
 
-  /* Send the new Client ID as reply command back to client */
-  id_string = silc_id_id2str(id_entry->id, SILC_ID_CLIENT);
-  packet = silc_command_encode_reply_payload_va(SILC_COMMAND_NICK, 
-                                               SILC_STATUS_OK, 1, 
-                                               2, id_string, 
-                                               SILC_ID_CLIENT_LEN);
-  silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
-                         0, packet->data, packet->len, FALSE);
+  for (i = 0; i < clients_count; i++) {
+    entry = clients[i];
 
-  silc_free(id_string);
-  silc_buffer_free(packet);
+    if (!entry->nickname) {
+      SilcBuffer tmpbuf;
+      unsigned short old_ident;
+      
+      old_ident = silc_command_get_ident(cmd->payload);
+      silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng));
+      silc_command_set_command(cmd->payload, SILC_COMMAND_WHOIS);
+      tmpbuf = silc_command_payload_encode_payload(cmd->payload);
+      
+      /* Send WHOIS request. We send WHOIS since we're doing the requesting
+        now anyway so make it a good one. */
+      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_WHOIS, 
+                                 silc_command_get_ident(cmd->payload),
+                                 silc_server_command_identify, (void *)cmd);
+      cmd->pending = TRUE;
+      
+      /* Put old data back to the Command Payload we just changed */
+      silc_command_set_ident(cmd->payload, old_ident);
+      silc_command_set_command(cmd->payload, SILC_COMMAND_IDENTIFY);
+
+      silc_buffer_free(tmpbuf);
+      return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+static void
+silc_server_command_identify_send_reply(SilcServerCommandContext cmd,
+                                       SilcClientEntry *clients,
+                                       unsigned int clients_count)
+{
+  SilcServer server = cmd->server;
+  char *tmp;
+  int i, count = 0, len;
+  SilcBuffer packet, idp;
+  SilcClientEntry entry;
+  SilcCommandStatus status;
+  unsigned short ident = silc_command_get_ident(cmd->payload);
+
+  status = SILC_STATUS_OK;
+  if (clients_count > 1)
+    status = SILC_STATUS_LIST_START;
+
+  for (i = 0; i < clients_count; i++) {
+    entry = clients[i];
+
+    if (count && i - 1 == count)
+      break;
+
+    if (clients_count > 2)
+      status = SILC_STATUS_LIST_ITEM;
+
+    if (clients_count > 1 && i == clients_count - 1)
+      status = SILC_STATUS_LIST_END;
+
+    /* Send IDENTIFY reply */
+    idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+    tmp = silc_argument_get_first_arg(cmd->args, NULL);
+    
+    /* XXX */
+    {
+      char nh[256], uh[256];
+      SilcSocketConnection hsock;
+
+      memset(uh, 0, sizeof(uh));
+      memset(nh, 0, sizeof(nh));
+      
+      strncat(nh, entry->nickname, strlen(entry->nickname));
+      if (!strchr(entry->nickname, '@')) {
+       strncat(nh, "@", 1);
+       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,
+                                                     SILC_STATUS_OK, 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,
+                                                     SILC_STATUS_OK, 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);
+    }
+  }
+}
+
+static int
+silc_server_command_identify_from_client(SilcServerCommandContext cmd)
+{
+  SilcServer server = cmd->server;
+  char *nick = NULL, *server_name = NULL;
+  int count = 0, clients_count;
+  SilcClientID *client_id = NULL;
+  SilcClientEntry *clients = NULL, entry;
+  int ret = 0;
+
+  /* Parse the IDENTIFY request */
+  if (!silc_server_command_identify_parse(cmd, &client_id, &nick, 
+                                         &server_name, &count))
+    return 0;
+
+  /* Protocol dictates that we must always send the received IDENTIFY 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 (server->server_type == SILC_SERVER && 
+      !cmd->pending && !server->standalone) {
+    SilcBuffer tmpbuf;
+    unsigned short old_ident;
+
+    old_ident = silc_command_get_ident(cmd->payload);
+    silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng));
+    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, (void *)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. */
+
+  /* Get all clients matching that ID or nickname from local list */
+  if (client_id) {
+    entry = silc_idlist_find_client_by_id(server->local_list, client_id, NULL);
+    if (entry) {
+      clients = silc_calloc(1, sizeof(*clients));
+      clients[0] = entry;
+      clients_count = 1;
+    }
+  } else {
+    clients = silc_idlist_get_clients_by_nickname(server->local_list, 
+                                                 nick, server_name,
+                                                 &clients_count);
+  }
+  
+  /* Check global list as well */
+  if (!clients) {
+    if (client_id) {
+      entry = silc_idlist_find_client_by_id(server->global_list, 
+                                           client_id, NULL);
+      if (entry) {
+       clients = silc_calloc(1, sizeof(*clients));
+       clients[0] = entry;
+       clients_count = 1;
+      }
+    } else {
+      clients = silc_idlist_get_clients_by_nickname(server->global_list, 
+                                                   nick, server_name,
+                                                   &clients_count);
+    }
+  }
+  
+  if (!clients) {
+    /* Such a client really does not exist in the SILC network. */
+    silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+                                        SILC_STATUS_ERR_NO_SUCH_NICK,
+                                        3, nick, strlen(nick));
+    goto out;
+  }
+
+  /* Check that all mandatory fields are present and request those data
+     from the server who owns the client if necessary. */
+  if (!cmd->pending && server->server_type == SILC_ROUTER &&
+      !silc_server_command_identify_check(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);
+
+ out:
+  if (clients)
+    silc_free(clients);
+  if (client_id)
+    silc_free(client_id);
+  if (nick)
+    silc_free(nick);
+  if (server_name)
+    silc_free(server_name);
+
+  return ret;
+}
+
+static int
+silc_server_command_identify_from_server(SilcServerCommandContext cmd)
+{
+  SilcServer server = cmd->server;
+  char *nick = NULL, *server_name = NULL;
+  int count = 0, clients_count;
+  SilcClientID *client_id = NULL;
+  SilcClientEntry *clients = NULL, entry;
+  int ret = 0;
+
+  /* Parse the IDENTIFY request */
+  if (!silc_server_command_identify_parse(cmd, &client_id, &nick, 
+                                         &server_name, &count))
+    return 0;
+
+  /* Process the command request. Let's search for the requested client and
+     send reply to the requesting server. */
+
+  if (client_id) {
+    entry = silc_idlist_find_client_by_id(server->local_list, client_id, NULL);
+    if (entry) {
+      clients = silc_calloc(1, sizeof(*clients));
+      clients[0] = entry;
+      clients_count = 1;
+    }
+  } else {
+    clients = silc_idlist_get_clients_by_nickname(server->local_list, 
+                                                 nick, server_name,
+                                                 &clients_count);
+    if (!clients)
+      clients = silc_idlist_get_clients_by_hash(server->local_list, 
+                                               nick, server->md5hash,
+                                               &clients_count);
+  }
+  
+  /* If we are router we will check our global list as well. */
+  if (!clients && server->server_type == SILC_ROUTER) {
+    if (client_id) {
+      entry = silc_idlist_find_client_by_id(server->global_list, 
+                                           client_id, NULL);
+      if (entry) {
+       clients = silc_calloc(1, sizeof(*clients));
+       clients[0] = entry;
+       clients_count = 1;
+      }
+    } else {
+      clients = silc_idlist_get_clients_by_nickname(server->global_list, 
+                                                   nick, server_name,
+                                                   &clients_count);
+      if (!clients)
+       clients = silc_idlist_get_clients_by_hash(server->global_list, 
+                                                 nick, server->md5hash,
+                                                 &clients_count);
+    }
+  }
+
+  if (!clients) {
+    /* Such a client really does not exist in the SILC network. */
+    silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+                                        SILC_STATUS_ERR_NO_SUCH_NICK,
+                                        3, nick, strlen(nick));
+    goto out;
+  }
+
+  /* Check that all mandatory fields are present and request those data
+     from the server who owns the client if necessary. */
+  if (!cmd->pending && server->server_type == SILC_ROUTER &&
+      !silc_server_command_identify_check(cmd, clients, clients_count)) {
+    ret = -1;
+    goto out;
+  }
+
+  /* Send the command reply */
+  silc_server_command_identify_send_reply(cmd, clients, clients_count);
+
+ out:
+  if (clients)
+    silc_free(clients);
+  if (client_id)
+    silc_free(client_id);
+  if (nick)
+    silc_free(nick);
+  if (server_name)
+    silc_free(server_name);
+
+  return ret;
+}
+
+SILC_SERVER_CMD_FUNC(identify)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  int ret;
+
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_IDENTIFY, cmd, 1, 3);
+
+  if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT)
+    ret = silc_server_command_identify_from_client(cmd);
+  else
+    ret = silc_server_command_identify_from_server(cmd);
+
+  if (!ret)
+    silc_server_command_free(cmd);
+}
+
+/* Checks string for bad characters and returns TRUE if they are found. */
+
+static int silc_server_command_bad_chars(char *nick)
+{
+  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;
+
+  return FALSE;
+}
+
+/* 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;
+  SilcClientID *new_id;
+  char *nick;
 
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_NICK, cmd, 1, 1);
+
+  /* Check nickname */
+  nick = silc_argument_get_arg_type(cmd->args, 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;
+  }
+
+  /* Create new Client ID */
+  silc_id_create_client_id(cmd->server->id, cmd->server->rng, 
+                          cmd->server->md5hash, nick,
+                          &new_id);
+
+  /* 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. */
+  if (!cmd->server->standalone)
+    silc_server_send_replace_id(server, server->router->connection, 
+                               server->server_type == SILC_SERVER ? 
+                               FALSE : TRUE, client->id,
+                               SILC_ID_CLIENT, SILC_ID_CLIENT_LEN,
+                               new_id, SILC_ID_CLIENT, SILC_ID_CLIENT_LEN);
+
+  /* Remove old cache entry */
+  silc_idcache_del_by_id(server->local_list->clients, SILC_ID_CLIENT, 
+                        client->id); 
+
+  oidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+
+  /* Free old ID */
+  if (client->id) {
+    memset(client->id, 0, SILC_ID_CLIENT_LEN);
+    silc_free(client->id);
+  }
+
+  /* Save the nickname as this client is our local client */
+  if (client->nickname)
+    silc_free(client->nickname);
+
+  client->nickname = strdup(nick);
+  client->id = new_id;
+
+  /* Update client cache */
+  silc_idcache_add(server->local_list->clients, client->nickname, 
+                  SILC_ID_CLIENT, client->id, (void *)client, TRUE);
+
+  nidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+
+  /* Send NICK_CHANGE notify */
+  silc_server_send_notify_on_channels(server, client, 
+                                     SILC_NOTIFY_TYPE_NICK_CHANGE, 2,
+                                     oidp->data, oidp->len, 
+                                     nidp->data, nidp->len);
+
+  /* Send the new Client ID as reply command back to client */
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_NICK, 
+                                               SILC_STATUS_OK, 0, 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);
+  silc_buffer_free(oidp);
+  
  out:
   silc_server_command_free(cmd);
 }
@@ -732,8 +1237,108 @@ SILC_SERVER_CMD_FUNC(list)
 {
 }
 
+/* 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;
+  unsigned int argc, tmp_len;
+
+  SILC_SERVER_COMMAND_CHECK_ARGC(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);
+
+  /* Check whether the channel exists */
+  channel = silc_idlist_find_channel_by_id(server->local_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 has rights to change topic */
+    silc_list_start(channel->user_list);
+    while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) {
+      if (chl->client == client) {
+       if (chl->mode == SILC_CHANNEL_UMODE_NONE) {
+         silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC,
+                                               SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+         goto out;
+       } else {
+         break;
+       }
+      }
+    }
+
+    /* Set the topic for channel */
+    if (channel->topic)
+      silc_free(channel->topic);
+    channel->topic = strdup(tmp);
+
+    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, channel, TRUE,
+                                      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);
+  if (channel->topic)
+    packet = silc_command_reply_payload_encode_va(SILC_COMMAND_TOPIC, 
+                                                 SILC_STATUS_OK, 0, 2, 
+                                                 2, idp->data, idp->len,
+                                                 3, channel->topic, 
+                                                 strlen(channel->topic));
+  else
+    packet = silc_command_reply_payload_encode_va(SILC_COMMAND_TOPIC, 
+                                                 SILC_STATUS_OK, 0, 1, 
+                                                 2, idp->data, idp->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(idp);
+  silc_free(channel_id);
+
+ out:
+  silc_server_command_free(cmd);
 }
 
 /* Server side of INVITE command. Invites some client to join some channel. */
@@ -747,42 +1352,33 @@ SILC_SERVER_CMD_FUNC(invite)
   SilcClientID *dest_id;
   SilcChannelEntry channel;
   SilcChannelID *channel_id;
-  unsigned int argc, len;
-  unsigned char *id_string;
+  SilcBuffer sidp;
+  unsigned char *tmp;
+  unsigned int len;
 
-  /* Check number of arguments */
-  argc = silc_command_get_arg_num(cmd->payload);
-  if (argc < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-  if (argc > 2) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
-                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);
-    goto out;
-  }
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_INVITE, cmd, 1, 2);
 
   /* Get destination ID */
-  id_string = silc_command_get_arg_type(cmd->payload, 1, &len);
-  if (!id_string) {
+  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_CLIENT_ID);
     goto out;
   }
-  dest_id = silc_id_str2id(id_string, SILC_ID_CLIENT);
+  dest_id = silc_id_payload_parse_id(tmp, len);
 
   /* Get Channel ID */
-  id_string = silc_command_get_arg_type(cmd->payload, 2, &len);
-  if (!id_string) {
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &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_str2id(id_string, SILC_ID_CHANNEL);
+  channel_id = silc_id_payload_parse_id(tmp, len);
 
   /* Check whether the channel exists */
-  channel = silc_idlist_find_channel_by_id(server->local_list, channel_id);
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
   if (!channel) {
     silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
                                          SILC_STATUS_ERR_NO_SUCH_CHANNEL);
@@ -790,7 +1386,7 @@ SILC_SERVER_CMD_FUNC(invite)
   }
 
   /* Check whether the sender of this command is on the channel. */
-  sender = (SilcClientEntry )sock->user_data;
+  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);
@@ -799,15 +1395,28 @@ SILC_SERVER_CMD_FUNC(invite)
 
   /* Check whether the channel is invite-only channel. If yes then the
      sender of this command must be at least channel operator. */
-  /* XXX */
+  if (channel->mode & SILC_CHANNEL_MODE_INVITE) {
+    SilcChannelClientEntry chl;
+
+    silc_list_start(channel->user_list);
+    while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END)
+      if (chl->client == sender) {
+       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;
+       }
+       break;
+      }
+  }
 
   /* Find the connection data for the destination. If it is local we will
      send it directly otherwise we will send it to router for routing. */
-  dest = silc_idlist_find_client_by_id(server->local_list, dest_id);
+  dest = silc_idlist_find_client_by_id(server->local_list, dest_id, NULL);
   if (dest)
     dest_sock = (SilcSocketConnection)dest->connection;
   else
-    dest_sock = silc_server_get_route(server, dest_id, SILC_ID_CLIENT);
+    dest_sock = silc_server_route_get(server, dest_id, SILC_ID_CLIENT);
 
   /* Check whether the requested client is already on the channel. */
   /* XXX if we are normal server we don't know about global clients on
@@ -819,15 +1428,19 @@ SILC_SERVER_CMD_FUNC(invite)
     goto out;
   }
 
+  sidp = silc_id_payload_encode(sender->id, SILC_ID_CLIENT);
+
   /* Send notify to the client that is invited to the channel */
   silc_server_send_notify_dest(server, dest_sock, dest_id, SILC_ID_CLIENT,
-                              "%s invites you to channel %s",
-                              sender->nickname, channel->channel_name);
+                              SILC_NOTIFY_TYPE_INVITE, 2, 
+                              sidp->data, sidp->len, tmp, len);
 
   /* Send command reply */
   silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
                                        SILC_STATUS_OK);
 
+  silc_buffer_free(sidp);
+
  out:
   silc_server_command_free(cmd);
 }
@@ -878,25 +1491,13 @@ SILC_SERVER_CMD_FUNC(info)
 {
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
   SilcServer server = cmd->server;
-  SilcBuffer packet;
-  unsigned int argc;
-  unsigned char *id_string;
+  SilcBuffer packet, idp;
   char info_string[256], *dest_server;
 
-  argc = silc_command_get_arg_num(cmd->payload);
-  if (argc < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-  if (argc > 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO,
-                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);
-    goto out;
-  }
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_INFO, cmd, 1, 1);
 
   /* Get server name */
-  dest_server = silc_command_get_arg_type(cmd->payload, 1, NULL);
+  dest_server = silc_argument_get_arg_type(cmd->args, 1, NULL);
   if (!dest_server) {
     silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO,
                                          SILC_STATUS_ERR_NO_SUCH_SERVER);
@@ -913,19 +1514,18 @@ SILC_SERVER_CMD_FUNC(info)
             server->config->admin_info->admin_name,
             server->config->admin_info->admin_email);
 
-    id_string = silc_id_id2str(server->id, SILC_ID_SERVER);
+    idp = silc_id_payload_encode(server->id, SILC_ID_SERVER);
 
-    packet = 
-      silc_command_encode_reply_payload_va(SILC_COMMAND_INFO,
-                                          SILC_STATUS_OK, 2,
-                                          2, id_string, SILC_ID_SERVER_LEN,
-                                          3, info_string, 
-                                          strlen(info_string));
+    packet = silc_command_reply_payload_encode_va(SILC_COMMAND_INFO,
+                                                 SILC_STATUS_OK, 0, 2,
+                                                 2, idp->data, idp->len,
+                                                 3, info_string, 
+                                                 strlen(info_string));
     silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, 
                            packet->data, packet->len, FALSE);
     
-    silc_free(id_string);
     silc_buffer_free(packet);
+    silc_buffer_free(idp);
   } else {
     /* Send this command to the requested server */
 
@@ -953,29 +1553,21 @@ SILC_SERVER_CMD_FUNC(ping)
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
   SilcServer server = cmd->server;
   SilcServerID *id;
-  unsigned int argc;
-  unsigned char *id_string;
+  unsigned int len;
+  unsigned char *tmp;
 
-  argc = silc_command_get_arg_num(cmd->payload);
-  if (argc < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-  if (argc > 2) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING,
-                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);
-    goto out;
-  }
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_INFO, cmd, 1, 2);
 
   /* Get Server ID */
-  id_string = silc_command_get_arg_type(cmd->payload, 1, NULL);
-  if (!id_string) {
+  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(id_string, SILC_ID_SERVER);
+  id = silc_id_str2id(tmp, SILC_ID_SERVER);
+  if (!id)
+    goto out;
 
   if (!SILC_ID_SERVER_COMPARE(id, server->id)) {
     /* Send our reply */
@@ -1004,6 +1596,7 @@ typedef struct {
   char *hostname;
   SilcChannelEntry channel;
   SilcServer server;
+  SilcClientEntry client;
 } JoinInternalContext;
 
 SILC_TASK_CALLBACK(silc_server_command_join_notify)
@@ -1011,307 +1604,1030 @@ SILC_TASK_CALLBACK(silc_server_command_join_notify)
   JoinInternalContext *ctx = (JoinInternalContext *)context;
 
   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);
+    SilcBuffer clidp;
+
+    clidp = silc_id_payload_encode(ctx->client->id, SILC_ID_CLIENT);
+
+    silc_server_send_notify_to_channel(ctx->server, ctx->channel, FALSE,
+                                      SILC_NOTIFY_TYPE_JOIN, 1,
+                                      clidp->data, clidp->len);
+#if 0
+    /* Send NEW_CHANNEL_USER packet to primary route */
+    silc_server_send_new_channel_user(server, server->router->connection,
+                                     server->server_type == SILC_SERVER ?
+                                     FALSE : TRUE,
+                                     channel->id, SILC_ID_CHANNEL_LEN,
+                                     client->id, SILC_ID_CLIENT_LEN);
+#endif
+
+    /* Send NAMES command reply to the joined channel so the user sees who
+       is currently on the channel. */
+    silc_server_command_send_names(ctx->server, ctx->client->connection, 
+                                  ctx->channel);
+
+    silc_buffer_free(clidp);
     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);
+                      0, 200000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
   }
 }
 
-/* Assembles NAMES command and executes it. This is called when client
-   joins to a channel and we wan't to send NAMES command reply to the 
-   client. */
+/* Assembles NAMES command and executes it. This is called when client
+   joins to a channel and we wan't to send NAMES command reply to the 
+   client. */
+
+void silc_server_command_send_names(SilcServer server,
+                                   SilcSocketConnection sock,
+                                   SilcChannelEntry channel)
+{
+  SilcServerCommandContext cmd;
+  SilcBuffer buffer, idp;
+
+  idp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+  buffer = silc_command_payload_encode_va(SILC_COMMAND_NAMES, 0, 1,
+                                         1, idp->data, idp->len);
+
+  cmd = silc_calloc(1, sizeof(*cmd));
+  cmd->payload = silc_command_payload_parse(buffer);
+  cmd->args = silc_command_get_args(cmd->payload);
+  cmd->server = server;
+  cmd->sock = sock;
+  cmd->pending = FALSE;
+
+  silc_server_command_names((void *)cmd);
+  silc_free(buffer);
+  silc_free(idp);
+}
+
+/* Internal routine that is called after router has replied to server's
+   JOIN command it forwarded to the router. The route has joined and possibly
+   creaetd the channel. This function adds the client to the channel's user
+   list. */
+
+SILC_SERVER_CMD_FUNC(add_to_channel)
+{
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client;
+  SilcChannelEntry channel;
+  SilcChannelClientEntry chl;
+  char *channel_name;
+
+  /* Get channel name */
+  channel_name = silc_argument_get_arg_type(cmd->args, 1, NULL);
+
+  /* Get client entry */
+  client = (SilcClientEntry)cmd->sock->user_data;
+
+  /* Get channel entry */
+  channel = silc_idlist_find_channel_by_name(server->local_list, 
+                                            channel_name, NULL);
+  if (channel) {
+    /* 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 = SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO;
+    chl->client = client;
+    chl->channel = channel;
+    silc_list_add(channel->user_list, chl);
+    silc_list_add(client->channels, chl);
+  }
+
+  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,
+                                            int created,
+                                            unsigned int umode)
+{
+  SilcSocketConnection sock = cmd->sock;
+  unsigned char *tmp;
+  unsigned int tmp_len;
+  unsigned char *passphrase = NULL, mode[4];
+  SilcClientEntry client;
+  SilcChannelClientEntry chl;
+  SilcBuffer reply, chidp, clidp;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  if (!channel)
+    return;
+
+  /* Get passphrase */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (tmp) {
+    passphrase = silc_calloc(tmp_len, sizeof(*passphrase));
+    memcpy(passphrase, tmp, tmp_len);
+  }
+  
+  /*
+   * Check channel modes
+   */
+
+  /* Check invite list if channel is invite-only channel */
+  if (channel->mode & SILC_CHANNEL_MODE_INVITE) {
+    if (channel->mode & SILC_CHANNEL_MODE_INVITE_LIST) {
+      /* Invite list is specified. Check whether client is invited in the
+        list. If not, then check whether it has been invited otherwise. */
+
+    } else {
+      /* XXX client must be invited to be able to join the channel */
+    }
+  }
+
+  /* Check ban list if set */
+  if (channel->mode & SILC_CHANNEL_MODE_BAN) {
+
+  }
+
+  /* Check the channel passphrase if set. */
+  if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) {
+    if (!passphrase || memcmp(channel->mode_data.passphrase, passphrase,
+                             strlen(channel->mode_data.passphrase))) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                           SILC_STATUS_ERR_BAD_PASSWORD);
+      goto out;
+    }
+  }
+
+  /* Check user count limit if set. */
+  if (channel->mode & SILC_CHANNEL_MODE_ULIMIT) {
+    if (silc_list_count(channel->user_list) + 1 > 
+       channel->mode_data.user_limit) {
+      silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                           SILC_STATUS_ERR_CHANNEL_IS_FULL);
+      goto out;
+    }
+  }
+
+  /*
+   * Client is allowed to join to the channel. Make it happen.
+   */
+
+  /* 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) {
+    void *id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type);
+    client = silc_idlist_find_client_by_id(server->local_list, id, NULL);
+    if (!client) {
+      /* XXX */
+      SILC_LOG_ERROR(("Forwarded join command did not find the client who "
+                     "wanted to join the channel"));
+      goto out;
+    }
+    silc_free(id);
+  } else {
+    client = (SilcClientEntry)sock->user_data;
+  }
+
+  /* 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 (!created)
+    silc_server_create_channel_key(server, channel, 0);
+
+  /* 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_list_add(channel->user_list, chl);
+  silc_list_add(client->channels, chl);
+
+  /* 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);
+  if (!channel->topic) {
+    reply = 
+      silc_command_reply_payload_encode_va(SILC_COMMAND_JOIN,
+                                          SILC_STATUS_OK, 0, 3,
+                                          2, channel->channel_name,
+                                          strlen(channel->channel_name),
+                                          3, chidp->data, chidp->len,
+                                          4, mode, 4);
+  } else {
+    reply = 
+      silc_command_reply_payload_encode_va(SILC_COMMAND_JOIN,
+                                          SILC_STATUS_OK, 0, 4, 
+                                          2, channel->channel_name, 
+                                          strlen(channel->channel_name),
+                                          3, chidp->data, chidp->len,
+                                          4, mode, 4,
+                                          5, channel->topic, 
+                                          strlen(channel->topic));
+  }
+    
+  if (server->server_type == SILC_ROUTER && 
+      cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED) {
+    /* We are router and server has forwarded this command to us. Send
+       all replys to the server. */
+    void *tmpid;
+
+    /* Send command reply destined to the original client */
+    tmpid = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type);
+    silc_server_packet_send_dest(cmd->server, sock, 
+                                SILC_PACKET_COMMAND_REPLY, 0,
+                                tmpid, cmd->packet->src_id_type,
+                                reply->data, reply->len, FALSE);
+
+    /* Distribute new channel key to local cell and local clients. */
+    silc_server_send_channel_key(server, channel, FALSE);
+    
+    /* Distribute JOIN notify into the cell for everbody on the channel */
+    silc_server_send_notify_to_channel(server, channel, FALSE,
+                                      SILC_NOTIFY_TYPE_JOIN, 1,
+                                      clidp->data, clidp->len);
+
+    /* Broadcast NEW_CHANNEL_USER packet to primary route */
+    silc_server_send_new_channel_user(server, server->router->connection,
+                                     TRUE, channel->id, SILC_ID_CHANNEL_LEN,
+                                     client->id, SILC_ID_CLIENT_LEN);
+
+    silc_free(tmpid);
+  } else {
+    /* Client sent the command. Send all replies directly to the client. */
+
+    /* Send command reply */
+    silc_server_packet_send(server, sock, SILC_PACKET_COMMAND_REPLY, 0, 
+                           reply->data, reply->len, FALSE);
+
+    /* Send the channel key. Channel key is sent before any other packet
+       to the channel. */
+    silc_server_send_channel_key(server, channel, server->standalone ?
+                                FALSE : TRUE);
+    
+    /* Send JOIN notify to locally connected clients on the channel */
+    silc_server_send_notify_to_channel(server, channel, FALSE,
+                                      SILC_NOTIFY_TYPE_JOIN, 1,
+                                      clidp->data, clidp->len);
+
+    /* Send NEW_CHANNEL_USER packet to our primary router */
+    if (!server->standalone)
+      silc_server_send_new_channel_user(server, server->router->connection,
+                                       FALSE, 
+                                       channel->id, SILC_ID_CHANNEL_LEN,
+                                       client->id, SILC_ID_CLIENT_LEN);
+
+    /* Send NAMES command reply to the joined channel so the user sees who
+       is currently on the channel. */
+    silc_server_command_send_names(server, sock, channel);
+  }
+
+  silc_buffer_free(reply);
+  silc_buffer_free(clidp);
+  silc_buffer_free(chidp);
+
+ out:
+  if (passphrase)
+    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;
+  int tmp_len;
+  char *tmp, *channel_name = NULL, *cipher = NULL;
+  SilcChannelEntry channel;
+  unsigned int umode = 0;
+  int created = FALSE;
+
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_JOIN, cmd, 1, 3);
+
+  /* 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 (silc_server_command_bad_chars(channel_name) == TRUE) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+                                         SILC_STATUS_ERR_BAD_CHANNEL);
+    silc_free(channel_name);
+    goto out;
+  }
+
+  /* Get cipher name */
+  cipher = silc_argument_get_arg_type(cmd->args, 3, NULL);
+
+  /* See if the channel exists */
+  channel = silc_idlist_find_channel_by_name(server->local_list, 
+                                            channel_name, NULL);
+  if (!channel) {
+    /* 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, 
+                                              channel_name);
+      umode |= SILC_CHANNEL_UMODE_CHANOP;
+      umode |= SILC_CHANNEL_UMODE_CHANFO;
+      created = TRUE;
+    } 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_SERVER) {
+       /* Forward the original JOIN command to the router */
+       silc_buffer_push(cmd->packet->buffer, 
+                        cmd->packet->buffer->data - 
+                        cmd->packet->buffer->head);
+       silc_server_packet_forward(server, (SilcSocketConnection)
+                                  server->router->connection,
+                                  cmd->packet->buffer->data, 
+                                  cmd->packet->buffer->len, TRUE);
+
+       /* Register handler that will be called after the router has replied
+          to us. We will add the client to the new channel in this callback
+          function. Will be called from JOIN command reply. */
+       silc_server_command_pending(server, SILC_COMMAND_JOIN, 
+                                   ++server->router->data.cmd_ident,
+                                   silc_server_command_add_to_channel,
+                                   context);
+       return;
+      }
+      
+      /* 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, 
+                                                channel_name);
+       umode |= SILC_CHANNEL_UMODE_CHANOP;
+       umode |= SILC_CHANNEL_UMODE_CHANFO;
+       created = TRUE;
+      }
+    }
+  }
+
+  /* Join to the channel */
+  silc_server_command_join_channel(server, cmd, channel, created, umode);
+
+ 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;
+  char *motd;
+  int motd_len;
+  
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_MOTD, cmd, 1, 2);
+
+  /* XXX show currently only our motd */
+
+  if (server->config && server->config->motd && 
+      server->config->motd->motd_file) {
+
+    /* Send motd */
+    motd = silc_file_read(server->config->motd->motd_file, &motd_len);
+    if (!motd)
+      goto out;
+
+    motd[motd_len] = 0;
+    silc_server_command_send_status_data(cmd, SILC_COMMAND_MOTD,
+                                        SILC_STATUS_OK,
+                                        2, motd, motd_len);
+    goto out;
+  } else {
+    /* No motd */
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_MOTD,
+                                         SILC_STATUS_OK);
+  }
+
+ out:
+  silc_server_command_free(cmd);
+}
+
+SILC_SERVER_CMD_FUNC(umode)
+{
+}
+
+/* Checks that client has rights to add or remove channel modes. If any
+   of the checks fails FALSE is returned. */
 
-void silc_server_command_send_names(SilcServer server,
-                                   SilcSocketConnection sock,
-                                   SilcChannelEntry channel)
+int silc_server_check_cmode_rights(SilcChannelEntry channel,
+                                  SilcChannelClientEntry client,
+                                  unsigned int mode)
 {
-  SilcServerCommandContext cmd;
-  SilcBuffer buffer;
-  unsigned char *id_string;
+  int is_op = client->mode & SILC_CHANNEL_UMODE_CHANOP;
+  int is_fo = client->mode & SILC_CHANNEL_UMODE_CHANFO;
 
-  id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL);
-  buffer = silc_command_encode_payload_va(SILC_COMMAND_NAMES, 1,
-                                         1, id_string, SILC_ID_CHANNEL_LEN);
+  /* Check whether has rights to change anything */
+  if (!is_op && !is_fo)
+    return FALSE;
 
-  cmd = silc_calloc(1, sizeof(*cmd));
-  cmd->payload = silc_command_parse_payload(buffer);
-  cmd->server = server;
-  cmd->sock = sock;
-  cmd->pending = FALSE;
+  /* Check whether has rights to change everything */
+  if (is_op && is_fo)
+    return TRUE;
 
-  silc_server_command_names((void *)cmd);
-  silc_free(id_string);
-  silc_free(buffer);
+  /* 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 (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 (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 (is_op && !is_fo)
+      return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_CIPHER) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  return TRUE;
 }
 
-/* Server side of command JOIN. Joins client into requested channel. If 
-   the channel does not exist it will be created. */
+/* Server side command of CMODE. Changes channel mode */
 
-SILC_SERVER_CMD_FUNC(join)
+SILC_SERVER_CMD_FUNC(cmode)
 {
   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, mode[4];
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcChannelID *channel_id;
   SilcChannelEntry channel;
-  SilcServerID *router_id;
-  SilcBuffer packet;
-  SilcClientEntry client;
+  SilcChannelClientEntry chl;
+  SilcBuffer packet, cidp;
+  unsigned char *tmp, *tmp_id, *tmp_mask;
+  unsigned int argc, mode_mask, tmp_len, tmp_len2;
 
   SILC_LOG_DEBUG(("Start"));
 
-  /* 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,
+  argc = silc_argument_get_arg_num(cmd->args);
+  if (argc < 2) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
                                          SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
-  if (argc > 3) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
+  if (argc > 8) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
                                          SILC_STATUS_ERR_TOO_MANY_PARAMS);
     goto out;
   }
 
-  /* Get channel name */
-  tmp = silc_command_get_arg_type(cmd->payload, 1, &tmp_len);
-  channel_name = silc_calloc(tmp_len + 1, sizeof(*channel_name));
-  memcpy(channel_name, tmp, tmp_len);
-  if (silc_server_command_bad_chars(tmp) == TRUE) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN,
-                                         SILC_STATUS_ERR_BAD_CHANNEL);
-    silc_free(channel_name);
+  /* 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);
 
-  /* 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);
+  /* 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;
   }
-  
-  /* Get cipher name */
-  cipher = silc_command_get_arg_type(cmd->payload, 3, NULL);
+  SILC_GET32_MSB(mode_mask, tmp_mask);
 
-  /* See if the channel exists */
-  channel = 
-    silc_idlist_find_channel_by_name(server->local_list, channel_name);
+  /* Get channel entry */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
   if (!channel) {
-    /* Channel not found */
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                         SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+    goto out;
+  }
 
-    /* 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);
-      if (!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;
+  }
 
-      goto join_channel;
-    }
+  /* Get entry to the channel user list */
+  silc_list_start(channel->user_list);
+  while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END)
+    if (chl->client == client)
+      break;
+
+  /* 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;
+  }
 
-    /* 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 original 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);
+  /*
+   * 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 now */
+  } 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. */
+
+      /* XXX Duplicated code, make own function for this!! LEAVE uses this
+        as well */
+
+      /* Re-generate channel key */
+      silc_server_create_channel_key(server, channel, 0);
       
-      /* 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;
+      /* Encode channel key payload to be distributed on the channel */
+      packet = 
+       silc_channel_key_payload_encode(tmp_len2, tmp_id,
+                                       strlen(channel->channel_key->
+                                              cipher->name),
+                                       channel->channel_key->cipher->name,
+                                       channel->key_len / 8, channel->key);
+      
+      /* If we are normal server then we will send it to our router.  If we
+        are router we will send it to all local servers that has clients on
+        the channel */
+      if (server->server_type == SILC_SERVER) {
+       if (!server->standalone)
+         silc_server_packet_send(server, 
+                                 cmd->server->router->connection,
+                                 SILC_PACKET_CHANNEL_KEY, 0, packet->data,
+                                 packet->len, TRUE);
+      } else {
+       
+      }
+      
+      /* Send to locally connected clients on the channel */
+      silc_server_packet_send_local_channel(server, channel, 
+                                           SILC_PACKET_CHANNEL_KEY, 0,
+                                           packet->data, packet->len, FALSE);
+      silc_buffer_free(packet);
+    }
+  }
+  
+  if (mode_mask & SILC_CHANNEL_MODE_ULIMIT) {
+    /* User limit is set on channel */
+    unsigned int 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->mode_data.user_limit = user_limit;
     }
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_ULIMIT)
+      /* User limit mode is unset. Remove user limit */
+      channel->mode_data.user_limit = 0;
   }
 
-  /* If we are router and the channel does not exist we will check our
-     global list for the channel. */
-  if (!channel && server->server_type == SILC_ROUTER) {
+  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;
+      }
 
-    /* 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
+      /* Save the passphrase */
+      channel->mode_data.passphrase = strdup(tmp);
+    }
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) {
+      /* Passphrase mode is unset. remove the passphrase */
+      if (channel->mode_data.passphrase) {
+       silc_free(channel->mode_data.passphrase);
+       channel->mode_data.passphrase = NULL;
+      }
     }
-
   }
 
- join_channel:
+  if (mode_mask & SILC_CHANNEL_MODE_BAN) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_BAN)) {
+      /* Ban list is specified for channel */
 
-  /* 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 = (SilcClientEntry)sock->user_data;
+      /* Get ban list */
+      tmp = silc_argument_get_arg_type(cmd->args, 5, NULL);
+      if (!tmp) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                             SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       goto out;
+      }
+
+      /* XXX check that channel founder is not banned */
+
+      /* Save the ban list */
+      channel->mode_data.ban_list = strdup(tmp);
+    }
   } 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, id);
-    if (!client) {
-      /* XXX */
-      goto out;
+    if (channel->mode & SILC_CHANNEL_MODE_BAN) {
+      /* Ban mode is unset. Remove the entire ban list */
+      if (channel->mode_data.ban_list) {
+       silc_free(channel->mode_data.ban_list);
+       channel->mode_data.ban_list = NULL;
+      }
     }
-    silc_free(id);
   }
 
-  /* 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);
-    silc_free(channel_name);
-    goto out;
-  }
+  if (mode_mask & SILC_CHANNEL_MODE_INVITE_LIST) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_INVITE_LIST)) {
+      /* Invite list is specified for channel */
+
+      /* Get invite list */
+      tmp = silc_argument_get_arg_type(cmd->args, 6, NULL);
+      if (!tmp) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                             SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       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;
-  channel->user_list[i].client = client;
-  channel->user_list_count++;
+      /* Save the invite linst */
+      channel->mode_data.invite_list = strdup(tmp);
+    }
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_INVITE_LIST) {
+      /* Invite list mode is unset. Remove the entire invite list */
+      if (channel->mode_data.invite_list) {
+       silc_free(channel->mode_data.invite_list);
+       channel->mode_data.invite_list = NULL;
+      }
+    }
+  }
 
-  /* Add the channel to client's channel list */
-  i = client->channel_count;
-  client->channel = silc_realloc(client->channel, 
-                                sizeof(*client->channel) * (i + 1));
-  client->channel[i] = channel;
-  client->channel_count++;
+  if (mode_mask & SILC_CHANNEL_MODE_CIPHER) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_CIPHER)) {
+      /* Cipher to use protect the traffic */
+      unsigned int key_len = 128;
+      char *cp;
+
+      /* Get cipher */
+      tmp = silc_argument_get_arg_type(cmd->args, 8, NULL);
+      if (!tmp) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE,
+                                             SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+       goto out;
+      }
 
-  /* 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) {
+      cp = strchr(tmp, ':');
+      if (cp) {
+       key_len = atoi(cp);
+       *cp = '\0';
+      }
 
-  }
+      /* XXX Duplicated code, make own function for this!! */
+    
+      /* Delete old cipher and allocate the new one */
+      silc_cipher_free(channel->channel_key);
+      silc_cipher_alloc(tmp, &channel->channel_key);
 
-  /* Send command reply to the client. Client receives the Channe ID,
-     channel mode and possibly other information in this reply packet. */
-  if (!cmd->pending) {
-    id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL);
-    SILC_PUT32_MSB(channel->mode, mode);
+      key_len /= 8;
+      if (key_len > 32)
+       key_len = 32;
 
-    if (!channel->topic)
-      packet = 
-       silc_command_encode_reply_payload_va(SILC_COMMAND_JOIN,
-                                            SILC_STATUS_OK, 3,
-                                            2, channel_name, 
-                                            strlen(channel_name),
-                                            3, id_string, SILC_ID_CHANNEL_LEN,
-                                            4, mode, 4);
-    else
+      /* Re-generate channel key */
+      silc_server_create_channel_key(server, channel, key_len);
+    
+      /* Encode channel key payload to be distributed on the channel */
       packet = 
-       silc_command_encode_reply_payload_va(SILC_COMMAND_JOIN,
-                                            SILC_STATUS_OK, 4, 
-                                            2, channel_name, 
-                                            strlen(channel_name),
-                                            3, id_string, SILC_ID_CHANNEL_LEN,
-                                            4, mode, 4,
-                                            5, 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_channel_key_payload_encode(tmp_len2, tmp_id,
+                                       strlen(channel->channel_key->
+                                              cipher->name),
+                                       channel->channel_key->cipher->name,
+                                       channel->key_len / 8, channel->key);
     
-    silc_buffer_free(packet);
-  }
-
-  /* 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 / 8, channel->key);
+      /* If we are normal server then we will send it to our router.  If we
+        are router we will send it to all local servers that has clients on
+        the channel */
+      if (server->server_type == SILC_SERVER) {
+       if (!server->standalone)
+         silc_server_packet_send(server, 
+                                 cmd->server->router->connection,
+                                 SILC_PACKET_CHANNEL_KEY, 0, packet->data,
+                                 packet->len, TRUE);
+      } else {
+       
+      }
     
-    silc_server_packet_send(server, sock, SILC_PACKET_CHANNEL_KEY, 0, 
-                           packet->data, packet->len, FALSE);
-    silc_buffer_free(packet);
-  }
+      /* Send to locally connected clients on the channel */
+      silc_server_packet_send_local_channel(server, channel, 
+                                           SILC_PACKET_CHANNEL_KEY, 0,
+                                         packet->data, packet->len, FALSE);
+      silc_buffer_free(packet);
+    }
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_CIPHER) {
+      /* Cipher mode is unset. Remove the cipher and revert back to 
+        default cipher */
+
+      if (channel->mode_data.cipher) {
+       silc_free(channel->mode_data.cipher);
+       channel->mode_data.cipher = NULL;
+       channel->mode_data.key_len = 0;
+      }
 
-  if (id_string)
-    silc_free(id_string);
+      /* Generate new cipher and key for the channel */
 
-  /* 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, 10000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
+      /* XXX Duplicated code, make own function for this!! */
+
+      /* Delete old cipher and allocate default one */
+      silc_cipher_free(channel->channel_key);
+      if (!channel->cipher)
+       silc_cipher_alloc("twofish", &channel->channel_key);
+      else
+       silc_cipher_alloc(channel->cipher, &channel->channel_key);
+
+      /* Re-generate channel key */
+      silc_server_create_channel_key(server, channel, 0);
+      
+      /* Encode channel key payload to be distributed on the channel */
+      packet = 
+       silc_channel_key_payload_encode(tmp_len2, tmp_id,
+                                       strlen(channel->channel_key->
+                                              cipher->name),
+                                       channel->channel_key->cipher->name,
+                                       channel->key_len / 8, channel->key);
+      
+      /* If we are normal server then we will send it to our router.  If we
+        are router we will send it to all local servers that has clients on
+        the channel */
+      if (server->server_type == SILC_SERVER) {
+       if (!server->standalone)
+         silc_server_packet_send(server, 
+                                 cmd->server->router->connection,
+                                 SILC_PACKET_CHANNEL_KEY, 0, packet->data,
+                                 packet->len, TRUE);
+      } else {
+       
+      }
+      
+      /* Send to locally connected clients on the channel */
+      silc_server_packet_send_local_channel(server, channel, 
+                                           SILC_PACKET_CHANNEL_KEY, 0,
+                                           packet->data, packet->len, FALSE);
+      silc_buffer_free(packet);
     }
   }
 
-  /* Send NAMES command reply to the joined channel so the user sees who
-     is currently on the channel. */
-  silc_server_command_send_names(server, sock, channel);
+  /* 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, channel, TRUE,
+                                    SILC_NOTIFY_TYPE_CMODE_CHANGE, 2,
+                                    cidp->data, cidp->len, 
+                                    tmp_mask, tmp_len);
+  silc_free(cidp);
+
+  /* Send command reply to sender */
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_CMODE,
+                                               SILC_STATUS_OK, 0, 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);
+  silc_free(channel_id);
 
  out:
   silc_server_command_free(cmd);
 }
 
-/* Server side of command MOTD. Sends servers current "message of the
-   day" to the client. */
+/* Server side of CUMODE command. Changes client's mode on a channel. */
 
-SILC_SERVER_CMD_FUNC(motd)
+SILC_SERVER_CMD_FUNC(cumode)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data;
+  SilcChannelID *channel_id;
+  SilcClientID *client_id;
+  SilcChannelEntry channel;
+  SilcClientEntry target_client;
+  SilcChannelClientEntry chl;
+  SilcBuffer packet, idp;
+  unsigned char *tmp_id, *tmp_mask;
+  unsigned int target_mask, sender_mask, tmp_len;
+  int notify = FALSE;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_CUMODE, cmd, 3, 3);
 
-}
+  /* Get Channel ID */
+  tmp_id = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
+  if (!tmp_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_id, tmp_len);
 
-SILC_SERVER_CMD_FUNC(umode)
-{
-}
+  /* Get channel entry */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
+  if (!channel) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+    goto out;
+  }
 
-SILC_SERVER_CMD_FUNC(cmode)
-{
+  /* 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_list_start(channel->user_list);
+  while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) {
+    if (chl->client == client) {
+      if (!(chl->mode & SILC_CHANNEL_UMODE_CHANFO) &&
+         !(chl->mode & SILC_CHANNEL_UMODE_CHANOP)) {
+       silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                             SILC_STATUS_ERR_NO_CHANNEL_PRIV);
+       goto out;
+      }
+
+      sender_mask = chl->mode;
+      break;
+    }
+  }
+  
+  /* 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_CHANNEL_ID);
+    goto out;
+  }
+  client_id = silc_id_payload_parse_id(tmp_id, tmp_len);
+
+  /* Get target client's entry */
+  target_client = silc_idlist_find_client_by_id(server->local_list, 
+                                               client_id, NULL);
+  if (!target_client) {
+    /* XXX If target client is not one of mine send to primary route */
+  }
+
+  /* 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_CUMODE,
+                                         SILC_STATUS_ERR_USER_NOT_ON_CHANNEL);
+    goto out;
+  }
+
+  /* Get entry to the channel user list */
+  silc_list_start(channel->user_list);
+  while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END)
+    if (chl->client == target_client)
+      break;
+
+  /* 
+   * 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 && chl->client != target_client) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NOT_YOU);
+    goto out;
+  }
+
+  if (target_mask & SILC_CHANNEL_UMODE_CHANFO) {
+    /* Cannot promote anyone to channel founder */
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE,
+                                         SILC_STATUS_ERR_NOT_YOU);
+    goto out;
+  } 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)) {
+      chl->mode |= SILC_CHANNEL_UMODE_CHANOP;
+      notify = TRUE;
+    }
+  } else {
+    if (chl->mode & SILC_CHANNEL_UMODE_CHANOP) {
+      /* Demote to normal user */
+      chl->mode &= ~SILC_CHANNEL_UMODE_CHANOP;
+      notify = TRUE;
+    }
+  }
+
+  /* Send notify to channel, notify only if mode was actually changed. */
+  if (notify) {
+    idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+    silc_server_send_notify_to_channel(server, channel, TRUE,
+                                      SILC_NOTIFY_TYPE_CUMODE_CHANGE, 3,
+                                      idp->data, idp->len,
+                                      tmp_mask, 4, tmp_id, tmp_len);
+    silc_buffer_free(idp);
+  }
+
+  /* Send command reply to sender */
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_CUMODE,
+                                               SILC_STATUS_OK, 0, 2,
+                                               2, tmp_mask, 4,
+                                               3, 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);
+
+ out:
+  silc_server_command_free(cmd);
 }
 
+/* Server side of KICK command. Kicks client out of channel. */
+
 SILC_SERVER_CMD_FUNC(kick)
 {
 }
@@ -1343,34 +2659,22 @@ SILC_SERVER_CMD_FUNC(leave)
   SilcChannelID *id;
   SilcChannelEntry channel;
   SilcBuffer packet;
-  unsigned int i, argc, key_len;
-  unsigned char *tmp, channel_key[32];
-
-  SILC_LOG_DEBUG(("Start"));
+  unsigned int i, len;
+  unsigned char *tmp;
 
-  argc = silc_command_get_arg_num(cmd->payload);
-  if (argc < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-  if (argc > 2) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
-                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);
-    goto out;
-  }
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_LEAVE, cmd, 1, 2);
 
   /* Get Channel ID */
-  tmp = silc_command_get_arg_type(cmd->payload, 1, NULL);
+  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_str2id(tmp, SILC_ID_CHANNEL);
+  id = silc_id_payload_parse_id(tmp, len);
 
   /* Get channel entry */
-  channel = silc_idlist_find_channel_by_id(server->local_list, id);
+  channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
   if (!channel) {
     silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
                                          SILC_STATUS_ERR_NO_SUCH_CHANNEL);
@@ -1384,8 +2688,17 @@ SILC_SERVER_CMD_FUNC(leave)
     goto out;
   }
 
+  /* Notify routers that they should remove this client from their list
+     of clients on the channel. */
+  if (!server->standalone)
+    silc_server_send_remove_channel_user(server, 
+                                        server->router->connection,
+                                        server->server_type == SILC_ROUTER ?
+                                        TRUE : FALSE, id_entry->id, id);
+
   /* Remove client from channel */
-  i = silc_server_remove_from_one_channel(server, sock, channel, id_entry);
+  i = silc_server_remove_from_one_channel(server, sock, channel, id_entry,
+                                         TRUE);
   silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
                                        SILC_STATUS_OK);
 
@@ -1394,23 +2707,14 @@ SILC_SERVER_CMD_FUNC(leave)
     goto out;
 
   /* Re-generate channel key */
-  key_len = channel->key_len / 8;
-  for (i = 0; i < key_len; i++)
-    channel_key[i] = silc_rng_get_byte(server->rng);
-  channel->channel_key->cipher->set_key(channel->channel_key->context, 
-                                       channel_key, key_len);
-  memset(channel->key, 0, key_len);
-  silc_free(channel->key);
-  channel->key = silc_calloc(key_len, sizeof(*channel->key));
-  memcpy(channel->key, channel_key, key_len);
-  memset(channel_key, 0, sizeof(channel_key));
+  silc_server_create_channel_key(server, channel, 0);
 
   /* Encode channel key payload to be distributed on the channel */
   packet = 
-    silc_channel_key_encode_payload(SILC_ID_CHANNEL_LEN, tmp,
+    silc_channel_key_payload_encode(len, tmp,
                                    strlen(channel->channel_key->cipher->name),
                                    channel->channel_key->cipher->name,
-                                   key_len, channel->key);
+                                   channel->key_len / 8, channel->key);
 
   /* If we are normal server then we will send it to our router.  If we
      are router we will send it to all local servers that has clients on
@@ -1418,7 +2722,7 @@ SILC_SERVER_CMD_FUNC(leave)
   if (server->server_type == SILC_SERVER) {
     if (!server->standalone)
       silc_server_packet_send(server, 
-                             cmd->server->id_entry->router->connection,
+                             cmd->server->router->connection,
                              SILC_PACKET_CHANNEL_KEY, 0, packet->data,
                              packet->len, TRUE);
   } else {
@@ -1446,46 +2750,36 @@ SILC_SERVER_CMD_FUNC(names)
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
   SilcServer server = cmd->server;
   SilcChannelEntry channel;
+  SilcChannelClientEntry chl;
   SilcChannelID *id;
   SilcBuffer packet;
-  unsigned int i, len, len2, argc;
+  unsigned int i, len, len2, tmp_len;
   unsigned char *tmp;
   char *name_list = NULL, *n;
   SilcBuffer client_id_list;
+  SilcBuffer client_mode_list;
 
-  SILC_LOG_DEBUG(("Start"));
-
-  argc = silc_command_get_arg_num(cmd->payload);
-  if (argc < 1) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_NAMES,
-                                         SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
-    goto out;
-  }
-  if (argc > 2) {
-    silc_server_command_send_status_reply(cmd, SILC_COMMAND_NAMES,
-                                         SILC_STATUS_ERR_TOO_MANY_PARAMS);
-    goto out;
-  }
+  SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_NAMES, cmd, 1, 2);
 
   /* Get Channel ID */
-  tmp = silc_command_get_arg_type(cmd->payload, 1, NULL);
+  tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
   if (!tmp) {
     silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
                                          SILC_STATUS_ERR_NO_CHANNEL_ID);
     goto out;
   }
-  id = silc_id_str2id(tmp, SILC_ID_CHANNEL);
+  id = silc_id_payload_parse_id(tmp, tmp_len);
 
   /* Check whether the channel exists. If we are normal server and the
      channel does not exist we will send this same command to our router
      which will know if the channel exists. */
-  channel = silc_idlist_find_channel_by_id(server->local_list, id);
+  channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
   if (!channel) {
     if (server->server_type == SILC_SERVER && !server->standalone) {
       /* XXX Send names command */
 
       cmd->pending = TRUE;
-      silc_server_command_pending(SILC_COMMAND_NAMES, 
+      silc_server_command_pending(server, SILC_COMMAND_NAMES, 0,
                                  silc_server_command_names, context);
       return;
     }
@@ -1498,11 +2792,10 @@ SILC_SERVER_CMD_FUNC(names)
   /* Assemble the name list now */
   name_list = NULL;
   len = 0;
-  for (i = 0; i < channel->user_list_count; i++) {
-    if (!channel->user_list[i].client)
-      continue;
-
-    n = channel->user_list[i].client->nickname;
+  silc_list_start(channel->user_list);
+  i = 0;
+  while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) {
+    n = chl->client->nickname;
     if (n) {
       len2 = strlen(n);
       len += len2;
@@ -1510,49 +2803,63 @@ SILC_SERVER_CMD_FUNC(names)
       memcpy(name_list + (len - len2), n, len2);
       name_list[len] = 0;
 
-      if (i == channel->user_list_count - 1)
+      if (i == silc_list_count(channel->user_list) - 1)
        break;
       memcpy(name_list + len, ",", 1);
       len++;
+      i++;
     }
   }
+  if (!name_list)
+    name_list = "";
 
   /* Assemble the Client ID list now */
-  client_id_list = silc_buffer_alloc(SILC_ID_CLIENT_LEN * 
-                                    channel->user_list_count);
-  silc_buffer_pull_tail(client_id_list, (SILC_ID_CLIENT_LEN *
-                                        channel->user_list_count));
-  for (i = 0; i < channel->user_list_count; i++) {
-    unsigned char *id_string;
-
-    if (!channel->user_list[i].client)
-      continue;
-
-    id_string = silc_id_id2str(channel->user_list[i].client->id,
-                              SILC_ID_CLIENT);
+  client_id_list = silc_buffer_alloc((SILC_ID_CLIENT_LEN + 4) * 
+                                    silc_list_count(channel->user_list));
+  silc_buffer_pull_tail(client_id_list, SILC_BUFFER_END(client_id_list));
+  silc_list_start(channel->user_list);
+  while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) {
+    SilcBuffer idp;
+
+    idp = silc_id_payload_encode(chl->client->id, SILC_ID_CLIENT);
     silc_buffer_format(client_id_list,
-                      SILC_STR_UI_XNSTRING(id_string, SILC_ID_CLIENT_LEN),
+                      SILC_STR_UI_XNSTRING(idp->data, idp->len),
                       SILC_STR_END);
-    silc_buffer_pull(client_id_list, SILC_ID_CLIENT_LEN);
-    silc_free(id_string);
+    silc_buffer_pull(client_id_list, idp->len);
+    silc_buffer_free(idp);
   }
   silc_buffer_push(client_id_list, 
                   client_id_list->data - client_id_list->head);
 
+  /* Assemble mode list */
+  client_mode_list = silc_buffer_alloc(4 * 
+                                      silc_list_count(channel->user_list));
+  silc_buffer_pull_tail(client_mode_list, SILC_BUFFER_END(client_mode_list));
+  silc_list_start(channel->user_list);
+  while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) {
+    SILC_PUT32_MSB(chl->mode, client_mode_list->data);
+    silc_buffer_pull(client_mode_list, 4);
+  }
+  silc_buffer_push(client_mode_list, 
+                  client_mode_list->data - client_mode_list->head);
+
   /* Send reply */
-  packet = silc_command_encode_reply_payload_va(SILC_COMMAND_NAMES,
-                                               SILC_STATUS_OK, 3,
-                                               2, tmp, SILC_ID_CHANNEL_LEN,
+  packet = silc_command_reply_payload_encode_va(SILC_COMMAND_NAMES,
+                                               SILC_STATUS_OK, 0, 4,
+                                               2, tmp, tmp_len,
                                                3, name_list, 
                                                strlen(name_list),
                                                4, client_id_list->data,
-                                               client_id_list->len);
+                                               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(packet);
   silc_free(name_list);
   silc_buffer_free(client_id_list);
+  silc_buffer_free(client_mode_list);
   silc_free(id);
 
  out: