Created SILC Client Libary by moving stuff from silc/ directory.
[silc.git] / apps / silcd / command.c
index 2849045d9fafc159b64021b77ec60d665d803493..0c52c4e251509ce9dfe2eecbf2ec04c5711643d9 100644 (file)
 /*
  * $Id$
  * $Log$
+ * Revision 1.13  2000/08/21 14:21:21  priikone
+ *     Fixed channel joining and channel message sending inside a
+ *     SILC cell. Added silc_server_send_remove_channel_user and
+ *     silc_server_remove_channel_user functions.
+ *
+ * Revision 1.12  2000/07/26 07:05:11  priikone
+ *     Fixed the server to server (server to router actually) connections
+ *     and made the private message work inside a cell. Added functin
+ *     silc_server_replace_id.
+ *
+ * Revision 1.11  2000/07/19 07:08:09  priikone
+ *     Added version detection support to SKE.
+ *
+ * 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.
  *
@@ -27,7 +68,7 @@
  *     Shorter timeout for channel joining notify.
  *
  * Revision 1.1.1.1  2000/06/27 11:36:56  priikone
- *     Importet from internal CVS/Added Log headers.
+ *     Imported from internal CVS/Added Log headers.
  *
  *
  */
 #include "serverincludes.h"
 #include "server_internal.h"
 
+static int silc_server_is_registered(SilcServer server,
+                                    SilcSocketConnection sock,
+                                    SilcServerCommandContext cmd,
+                                    SilcCommand command);
+static void 
+silc_server_command_send_status_reply(SilcServerCommandContext cmd,
+                                     SilcCommand command,
+                                     SilcCommandStatus status);
+static void 
+silc_server_command_send_status_data(SilcServerCommandContext cmd,
+                                    SilcCommand command,
+                                    SilcCommandStatus status,
+                                    unsigned int arg_type,
+                                    unsigned char *arg,
+                                    unsigned int arg_len);
+static void silc_server_command_free(SilcServerCommandContext cmd);
+
 /* Server command list. */
 SilcServerCommand silc_command_list[] =
 {
@@ -73,6 +131,108 @@ SilcServerCommand silc_command_list[] =
 /* List of pending commands. */
 SilcServerCommandPending *silc_command_pending = NULL;
 
+/* Returns TRUE if the connection is registered. Unregistered connections
+   usually cannot send commands hence the check. */
+
+static int silc_server_is_registered(SilcServer server,
+                                    SilcSocketConnection sock,
+                                    SilcServerCommandContext cmd,
+                                    SilcCommand command)
+{
+  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;
+  }
+
+  silc_server_command_send_status_reply(cmd, command,
+                                       SILC_STATUS_ERR_NOT_REGISTERED);
+  silc_server_command_free(cmd);
+  return FALSE;
+}
+
+/* Processes received command packet. */
+
+void silc_server_command_process(SilcServer server,
+                                SilcSocketConnection sock,
+                                SilcPacketContext *packet)
+{
+  SilcServerCommandContext ctx;
+  SilcServerCommand *cmd;
+
+  /* Check whether it is allowed for this connection to execute any
+     command. */
+  if (sock->type == SILC_SOCKET_TYPE_CLIENT) {
+    time_t curtime;
+    SilcClientEntry client = (SilcClientEntry)sock->user_data;
+
+    if (!client)
+      goto out;
+
+    /* Allow only one command executed in 2 seconds. */
+    curtime = time(NULL);
+    if (client->last_command && (curtime - client->last_command) < 2)
+      goto out;
+
+    /* Update access time */
+    client->last_command = curtime;
+  }
+  
+  /* 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 */
+  
+  /* Parse the command payload in the packet */
+  ctx->payload = silc_command_parse_payload(packet->buffer);
+  if (!ctx->payload) {
+    SILC_LOG_ERROR(("Bad command payload, packet dropped"));
+    silc_buffer_free(packet->buffer);
+    silc_free(ctx);
+    return;
+  }
+  
+  /* Execute command. If this fails the packet is dropped. */
+  for (cmd = silc_command_list; cmd->cb; cmd++)
+    if (cmd->cmd == silc_command_get(ctx->payload)) {
+
+      if (!(cmd->flags & SILC_CF_REG)) {
+       cmd->cb(ctx);
+       break;
+      }
+      
+      if (silc_server_is_registered(server, sock, ctx, cmd->cmd)) {
+       cmd->cb(ctx);
+       break;
+      }
+    }
+
+  if (cmd == NULL) {
+    SILC_LOG_ERROR(("Unknown command, packet dropped"));
+    silc_free(ctx);
+    goto out;
+  }
+
+ 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.
@@ -138,48 +298,48 @@ static void silc_server_command_free(SilcServerCommandContext cmd)
   }
 }
 
-/* Sends command status message as command reply packet. */
+#define SILC_COMMAND_STATUS_DATA(x) \
+  (
+
+/* Sends simple status message as command reply packet */
 
 static void 
-silc_server_command_send_status_msg(SilcServerCommandContext cmd,
-                                   SilcCommand command,
-                                   SilcCommandStatus status,
-                                   unsigned char *msg,
-                                   unsigned int msg_len)
+silc_server_command_send_status_reply(SilcServerCommandContext cmd,
+                                     SilcCommand command,
+                                     SilcCommandStatus status)
 {
-  SilcBuffer sp_buf, buffer;
+  SilcBuffer buffer;
 
   SILC_LOG_DEBUG(("Sending command status %d", status));
 
-  sp_buf = silc_command_encode_status_payload(status, msg, msg_len);
-  buffer = silc_command_encode_payload_va(command, 1, 
-                                         sp_buf->data, sp_buf->len);
+  buffer = silc_command_encode_reply_payload_va(command, status, 0);
   silc_server_packet_send(cmd->server, cmd->sock,
                          SILC_PACKET_COMMAND_REPLY, 0, 
                          buffer->data, buffer->len, FALSE);
   silc_buffer_free(buffer);
-  silc_buffer_free(sp_buf);
 }
 
-/* Sends simple status message as command reply packet */
+/* Sends command status reply with one extra argument. The argument
+   type must be sent as argument. */
 
 static void 
-silc_server_command_send_status_reply(SilcServerCommandContext cmd,
-                                     SilcCommand command,
-                                     SilcCommandStatus status)
+silc_server_command_send_status_data(SilcServerCommandContext cmd,
+                                    SilcCommand command,
+                                    SilcCommandStatus status,
+                                    unsigned int arg_type,
+                                    unsigned char *arg,
+                                    unsigned int arg_len)
 {
-  SilcBuffer sp_buf, buffer;
+  SilcBuffer buffer;
 
   SILC_LOG_DEBUG(("Sending command status %d", status));
 
-  sp_buf = silc_command_encode_status_payload(status, NULL, 0);
-  buffer = silc_command_encode_payload_va(command, 1, 
-                                         sp_buf->data, sp_buf->len);
+  buffer = silc_command_encode_reply_payload_va(command, status, 1,
+                                               arg_type, arg, arg_len);
   silc_server_packet_send(cmd->server, cmd->sock,
                          SILC_PACKET_COMMAND_REPLY, 0, 
                          buffer->data, buffer->len, FALSE);
   silc_buffer_free(buffer);
-  silc_buffer_free(sp_buf);
 }
 
 /* Server side of command WHOIS. Processes user's query and sends found 
@@ -188,10 +348,11 @@ silc_server_command_send_status_reply(SilcServerCommandContext cmd,
 SILC_SERVER_CMD_FUNC(whois)
 {
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  char *tmp, *nick = NULL, *server = NULL;
+  SilcServer server = cmd->server;
+  char *tmp, *nick = NULL, *server_name = NULL;
   unsigned int argc, count = 0, len;
-  SilcClientList *entry;
-  SilcBuffer sp_buf, packet;
+  SilcClientEntry entry;
+  SilcBuffer packet;
   unsigned char *id_string;
 
   SILC_LOG_DEBUG(("Start"));
@@ -215,8 +376,8 @@ SILC_SERVER_CMD_FUNC(whois)
       len = strcspn(tmp, "@");
       nick = silc_calloc(len + 1, sizeof(char));
       memcpy(nick, tmp, len);
-      server = silc_calloc(strlen(tmp) - len, sizeof(char));
-      memcpy(server, tmp + len + 1, strlen(tmp) - len - 1);
+      server_name = silc_calloc(strlen(tmp) - len, sizeof(char));
+      memcpy(server_name, tmp + len + 1, strlen(tmp) - len - 1);
     } else {
       nick = strdup(tmp);
     }
@@ -234,42 +395,42 @@ SILC_SERVER_CMD_FUNC(whois)
                                            SILC_STATUS_ERR_TOO_MANY_PARAMS);
       if (nick)
        silc_free(nick);
-      if (server)
-       silc_free(server);
+      if (server_name)
+       silc_free(server_name);
       goto out;
     }
     count = atoi(tmp);
   }
 
   /* Then, make the query from our local client list */
-  entry = silc_idlist_find_client_by_nickname(cmd->server->local_list->clients,
-                                             nick, server);
+  entry = silc_idlist_find_client_by_nickname(server->local_list, 
+                                             nick, server_name);
   if (!entry) {
 
     /* If we are normal server and are connected to a router we will
        make global query from the router. */
-    if (cmd->server->server_type == SILC_SERVER && !cmd->server->standalone) {
+    if (server->server_type == SILC_SERVER && !server->standalone) {
 
       goto ok;
     }
     
     /* If we are router then we will check our global list as well. */
-    if (cmd->server->server_type == SILC_ROUTER) {
+    if (server->server_type == SILC_ROUTER) {
       entry =
-       silc_idlist_find_client_by_nickname(cmd->server->global_list->clients,
-                                           nick, server);
+       silc_idlist_find_client_by_nickname(server->global_list,
+                                           nick, server_name);
       if (!entry) {
-       silc_server_command_send_status_msg(cmd, SILC_COMMAND_WHOIS,
-                                           SILC_STATUS_ERR_NO_SUCH_NICK,
-                                           tmp, strlen(tmp));
+       silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
+                                            SILC_STATUS_ERR_NO_SUCH_NICK,
+                                            3, tmp, strlen(tmp));
        goto out;
       }
       goto ok;
     }
 
-    silc_server_command_send_status_msg(cmd, SILC_COMMAND_WHOIS,
-                                       SILC_STATUS_ERR_NO_SUCH_NICK,
-                                       tmp, strlen(tmp));
+    silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS,
+                                        SILC_STATUS_ERR_NO_SUCH_NICK,
+                                        3, tmp, strlen(tmp));
     goto out;
   }
 
@@ -278,12 +439,12 @@ SILC_SERVER_CMD_FUNC(whois)
 
   /* Send WHOIS reply */
   id_string = silc_id_id2str(entry->id, SILC_ID_CLIENT);
-  tmp = silc_command_get_first_arg(cmd->payload, NULL),
-  sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0);
+  tmp = silc_command_get_first_arg(cmd->payload, NULL);
 
   /* XXX */
   if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) {
     char nh[256], uh[256];
+    unsigned char idle[4];
     SilcSocketConnection hsock;
 
     memset(uh, 0, sizeof(uh));
@@ -292,9 +453,9 @@ SILC_SERVER_CMD_FUNC(whois)
     strncat(nh, entry->nickname, strlen(entry->nickname));
     strncat(nh, "@", 1);
     len = entry->router ? strlen(entry->router->server_name) :
-      strlen(cmd->server->server_name);
+      strlen(server->server_name);
     strncat(nh, entry->router ? entry->router->server_name :
-           cmd->server->server_name, len);
+           server->server_name, len);
 
     strncat(uh, entry->username, strlen(entry->username));
     strncat(uh, "@", 1);
@@ -302,39 +463,43 @@ SILC_SERVER_CMD_FUNC(whois)
     len = hsock->hostname ? strlen(hsock->hostname) : strlen(hsock->ip);
     strncat(uh, hsock->hostname ? hsock->hostname : hsock->ip, len);
 
+    SILC_PUT32_MSB((time(NULL) - entry->last_receive), idle);
+
     /* XXX */
     if (entry->userinfo)
       packet = 
-        silc_command_encode_payload_va(SILC_COMMAND_WHOIS, 5, 
-                                      sp_buf->data, sp_buf->len,
-                                      id_string, SILC_ID_CLIENT_LEN,
-                                      nh, strlen(nh),
-                                      uh, strlen(uh),
-                                      entry->userinfo, 
-                                      strlen(entry->userinfo));
+        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_payload_va(SILC_COMMAND_WHOIS, 4,
-                                      sp_buf->data, sp_buf->len,
-                                      id_string, SILC_ID_CLIENT_LEN,
-                                      nh, strlen(nh),
-                                      uh, strlen(uh));
+        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);
 
   } else {
     /* XXX */
     packet = 
-      silc_command_encode_payload_va(SILC_COMMAND_WHOIS, 4, 
-                                    sp_buf->data, sp_buf->len,
-                                    id_string, SILC_ID_CLIENT_LEN,
-                                    entry->nickname, strlen(entry->nickname),
-                                    tmp, strlen(tmp)); /* XXX */
+      silc_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(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY,
+  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_free(sp_buf);
 
  out:
   silc_server_command_free(cmd);
@@ -347,10 +512,11 @@ SILC_SERVER_CMD_FUNC(whowas)
 SILC_SERVER_CMD_FUNC(identify)
 {
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  char *tmp, *nick = NULL, *server = NULL;
+  SilcServer server = cmd->server;
+  char *tmp, *nick = NULL, *server_name = NULL;
   unsigned int argc, count = 0, len;
-  SilcClientList *entry;
-  SilcBuffer sp_buf, packet;
+  SilcClientEntry entry;
+  SilcBuffer packet;
   unsigned char *id_string;
 
   SILC_LOG_DEBUG(("Start"));
@@ -374,8 +540,8 @@ SILC_SERVER_CMD_FUNC(identify)
       len = strcspn(tmp, "@");
       nick = silc_calloc(len + 1, sizeof(char));
       memcpy(nick, tmp, len);
-      server = silc_calloc(strlen(tmp) - len, sizeof(char));
-      memcpy(server, tmp + len + 1, strlen(tmp) - len - 1);
+      server_name = silc_calloc(strlen(tmp) - len, sizeof(char));
+      memcpy(server_name, tmp + len + 1, strlen(tmp) - len - 1);
     } else {
       nick = strdup(tmp);
     }
@@ -396,76 +562,73 @@ SILC_SERVER_CMD_FUNC(identify)
     count = atoi(tmp);
   }
 
-  /* Then, make the query from our local client list */
-  entry = silc_idlist_find_client_by_hash(cmd->server->local_list->clients,
-                                         nick, cmd->server->md5hash);
-  if (!entry) {
-
-    /* If we are normal server and are connected to a router we will
-       make global query from the router. */
-    if (cmd->server->server_type == SILC_SERVER && !cmd->server->standalone) {
-      SilcBuffer buffer = cmd->packet->buffer;
-
-      /* Send IDENTIFY command to our router */
-      silc_buffer_push(buffer, buffer->data - buffer->head);
-      silc_server_packet_forward(cmd->server, (SilcSocketConnection)
-                                cmd->server->id_entry->router->connection,
-                                buffer->data, buffer->len, TRUE);
-      goto out;
-    }
+  /* Find client */
+  entry = silc_idlist_find_client_by_nickname(server->local_list,
+                                             nick, NULL);
+  if (!entry)
+    entry = silc_idlist_find_client_by_hash(server->global_list,
+                                           nick, server->md5hash);
+
+  /* If client was not found and if we are normal server and are connected
+     to a router we will make global query from the router. */
+  if (!entry && server->server_type == SILC_SERVER && !server->standalone &&
+      !cmd->pending) {
+    SilcBuffer buffer = cmd->packet->buffer;
     
-    /* If we are router then we will check our global list as well. */
-    if (cmd->server->server_type == SILC_ROUTER) {
-      entry = 
-       silc_idlist_find_client_by_hash(cmd->server->global_list->clients,
-                                       nick, cmd->server->md5hash);
-      if (!entry) {
-       silc_server_command_send_status_msg(cmd, SILC_COMMAND_IDENTIFY,
-                                           SILC_STATUS_ERR_NO_SUCH_NICK,
-                                           tmp, strlen(tmp));
-       goto out;
-      }
-      goto ok;
-    }
+    SILC_LOG_DEBUG(("Requesting identify from router"));
+    
+    /* 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);
+    return;
+  }
+
+  /* If we are router we have checked our local list by nickname and our
+     global list by hash so far. It is possible that the client is still not
+     found and we'll check it from local list by hash. */
+  if (!entry && server->server_type == SILC_ROUTER)
+    entry = silc_idlist_find_client_by_hash(server->local_list,
+                                           nick, server->md5hash);
 
-    silc_server_command_send_status_msg(cmd, SILC_COMMAND_IDENTIFY,
-                                       SILC_STATUS_ERR_NO_SUCH_NICK,
-                                       tmp, strlen(tmp));
+  if (!entry) {
+    /* The client definitely does not exist */
+    silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY,
+                                        SILC_STATUS_ERR_NO_SUCH_NICK,
+                                        3, tmp, strlen(tmp));
     goto out;
   }
 
- ok:
   /* Send IDENTIFY reply */
   id_string = silc_id_id2str(entry->id, SILC_ID_CLIENT);
   tmp = silc_command_get_first_arg(cmd->payload, NULL);
-  sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0);
-  packet = silc_command_encode_payload_va(SILC_COMMAND_IDENTIFY, 3,
-                                         sp_buf->data, sp_buf->len,
-                                         id_string, SILC_ID_CLIENT_LEN,
-                                         nick, strlen(nick));
-#if 0
+  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 (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_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(cmd->server, cmd->sock, 
+  } else {
+    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_free(sp_buf);
 
  out:
   if (nick)
     silc_free(nick);
-  if (server)
-    silc_free(server);
+  if (server_name)
+    silc_free(server_name);
   silc_server_command_free(cmd);
 }
 
@@ -492,18 +655,15 @@ static int silc_server_command_bad_chars(char *nick)
 SILC_SERVER_CMD_FUNC(nick)
 {
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  SilcClientList *id_entry = (SilcClientList *)cmd->sock->user_data;
+  SilcClientEntry id_entry = (SilcClientEntry)cmd->sock->user_data;
   SilcServer server = cmd->server;
-  SilcBuffer packet, sp_buf;
+  SilcBuffer packet;
   SilcClientID *new_id;
   char *id_string;
   char *nick;
 
   SILC_LOG_DEBUG(("Start"));
 
-#define LCC(x) server->local_list->client_cache[(x) - 32]
-#define LCCC(x) server->local_list->client_cache_count[(x) - 32]
-
   /* Check number of arguments */
   if (silc_command_get_arg_num(cmd->payload) < 1) {
     silc_server_command_send_status_reply(cmd, SILC_COMMAND_NICK,
@@ -541,9 +701,8 @@ SILC_SERVER_CMD_FUNC(nick)
                                new_id, SILC_ID_CLIENT, SILC_ID_CLIENT_LEN);
 
   /* Remove old cache entry */
-  silc_idcache_del_by_id(LCC(id_entry->nickname[0]),
-                        LCCC(id_entry->nickname[0]), 
-                        SILC_ID_CLIENT, id_entry->id); 
+  silc_idcache_del_by_id(server->local_list->clients, SILC_ID_CLIENT, 
+                        id_entry->id); 
   
   /* Free old ID */
   if (id_entry->id) {
@@ -559,27 +718,23 @@ SILC_SERVER_CMD_FUNC(nick)
   id_entry->id = new_id;
 
   /* Update client cache */
-  LCCC(nick[0]) = silc_idcache_add(&LCC(nick[0]), LCCC(nick[0]),
-                                  id_entry->nickname, SILC_ID_CLIENT, 
-                                  id_entry->id, (void *)id_entry);
+  silc_idcache_add(server->local_list->clients, id_entry->nickname, 
+                  SILC_ID_CLIENT, id_entry->id, (void *)id_entry, TRUE);
 
   /* Send the new Client ID as reply command back to client */
   id_string = silc_id_id2str(id_entry->id, SILC_ID_CLIENT);
-  sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0);
-  packet = silc_command_encode_payload_va(SILC_COMMAND_NICK, 2
-                                         sp_buf->data, sp_buf->len,
-                                         id_string, SILC_ID_CLIENT_LEN);
+  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);
 
   silc_free(id_string);
   silc_buffer_free(packet);
-  silc_free(sp_buf);
 
  out:
   silc_server_command_free(cmd);
-#undef LCC
-#undef LCCC
 }
 
 SILC_SERVER_CMD_FUNC(list)
@@ -590,8 +745,100 @@ SILC_SERVER_CMD_FUNC(topic)
 {
 }
 
+/* Server side of INVITE command. Invites some client to join some channel. */
+
 SILC_SERVER_CMD_FUNC(invite)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcSocketConnection sock = cmd->sock, dest_sock;
+  SilcClientEntry sender, dest;
+  SilcClientID *dest_id;
+  SilcChannelEntry channel;
+  SilcChannelID *channel_id;
+  unsigned int argc, len;
+  unsigned char *id_string;
+
+  /* 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;
+  }
+
+  /* Get destination ID */
+  id_string = silc_command_get_arg_type(cmd->payload, 1, &len);
+  if (!id_string) {
+    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);
+
+  /* Get Channel ID */
+  id_string = silc_command_get_arg_type(cmd->payload, 2, &len);
+  if (!id_string) {
+    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);
+
+  /* Check whether the channel exists */
+  channel = silc_idlist_find_channel_by_id(server->local_list, channel_id);
+  if (!channel) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                         SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+    goto out;
+  }
+
+  /* Check whether the sender of this command is on the channel. */
+  sender = (SilcClientEntry )sock->user_data;
+  if (!silc_server_client_on_channel(sender, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
+  }
+
+  /* Check whether the channel is invite-only channel. If yes then the
+     sender of this command must be at least channel operator. */
+  /* XXX */
+
+  /* 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);
+  if (dest)
+    dest_sock = (SilcSocketConnection)dest->connection;
+  else
+    dest_sock = silc_server_get_route(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
+     the channel thus we must request it (NAMES command), check from
+     local cache as well. */
+  if (silc_server_client_on_channel(dest, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                         SILC_STATUS_ERR_USER_ON_CHANNEL);
+    goto out;
+  }
+
+  /* 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);
+
+  /* Send command reply */
+  silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                       SILC_STATUS_OK);
+
+ out:
+  silc_server_command_free(cmd);
 }
 
 /* Quits connection to client. This gets called if client won't
@@ -632,16 +879,127 @@ SILC_SERVER_CMD_FUNC(kill)
 {
 }
 
+/* Server side of command INFO. This sends information about us to 
+   the client. If client requested specific server we will send the 
+   command to that server. */
+
 SILC_SERVER_CMD_FUNC(info)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcBuffer packet;
+  unsigned int argc;
+  unsigned char *id_string;
+  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;
+  }
+
+  /* Get server name */
+  dest_server = silc_command_get_arg_type(cmd->payload, 1, NULL);
+  if (!dest_server) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO,
+                                         SILC_STATUS_ERR_NO_SUCH_SERVER);
+    goto out;
+  }
+
+  if (!strncasecmp(dest_server, server->server_name, strlen(dest_server))) {
+    /* Send our reply */
+    memset(info_string, 0, sizeof(info_string));
+    snprintf(info_string, sizeof(info_string), 
+            "location: %s server: %s admin: %s <%s>",
+            server->config->admin_info->location,
+            server->config->admin_info->server_type,
+            server->config->admin_info->admin_name,
+            server->config->admin_info->admin_email);
+
+    id_string = silc_id_id2str(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));
+    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);
+  } else {
+    /* Send this command to the requested server */
+
+    if (server->server_type == SILC_SERVER && !server->standalone) {
+
+    }
+
+    if (server->server_type == SILC_ROUTER) {
+
+    }
+  }
+  
+ out:
+  silc_server_command_free(cmd);
 }
 
 SILC_SERVER_CMD_FUNC(connect)
 {
 }
 
+/* Server side of command PING. This just replies to the ping. */
+
 SILC_SERVER_CMD_FUNC(ping)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcServerID *id;
+  unsigned int argc;
+  unsigned char *id_string;
+
+  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;
+  }
+
+  /* Get Server ID */
+  id_string = silc_command_get_arg_type(cmd->payload, 1, NULL);
+  if (!id_string) {
+    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);
+
+  if (!SILC_ID_SERVER_COMPARE(id, server->id)) {
+    /* Send our reply */
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING,
+                                         SILC_STATUS_OK);
+  } else {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING,
+                                         SILC_STATUS_ERR_NO_SUCH_SERVER);
+    goto out;
+  }
+
+  silc_free(id);
+
+ out:
+  silc_server_command_free(cmd);
 }
 
 SILC_SERVER_CMD_FUNC(oper)
@@ -653,7 +1011,7 @@ typedef struct {
   char *nickname;
   char *username;
   char *hostname;
-  SilcChannelList *channel;
+  SilcChannelEntry channel;
   SilcServer server;
 } JoinInternalContext;
 
@@ -674,6 +1032,33 @@ SILC_TASK_CALLBACK(silc_server_command_join_notify)
   }
 }
 
+/* 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;
+  unsigned char *id_string;
+
+  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);
+
+  cmd = silc_calloc(1, sizeof(*cmd));
+  cmd->payload = silc_command_parse_payload(buffer);
+  cmd->server = server;
+  cmd->sock = sock;
+  cmd->pending = FALSE;
+
+  silc_server_command_names((void *)cmd);
+  silc_free(id_string);
+  silc_free(buffer);
+}
+
 /* Server side of command JOIN. Joins client into requested channel. If 
    the channel does not exist it will be created. */
 
@@ -686,17 +1071,13 @@ SILC_SERVER_CMD_FUNC(join)
   int argc, i, tmp_len;
   char *tmp, *channel_name = NULL, *cipher = NULL, *id_string = NULL;
   unsigned char *passphrase, mode[4];
-  SilcChannelList *channel;
+  SilcChannelEntry channel;
   SilcServerID *router_id;
-  SilcIDCache *id_cache;
-  SilcBuffer packet, sp_buf;
-  SilcClientList *client;
+  SilcBuffer packet;
+  SilcClientEntry client;
 
   SILC_LOG_DEBUG(("Start"));
 
-#define LCC(x) server->local_list->channel_cache[(x) - 32]
-#define LCCC(x) server->local_list->channel_cache_count[(x) - 32]
-
   /* Check number of parameters */
   argc = silc_command_get_arg_num(cmd->payload);
   if (argc < 1) {
@@ -732,10 +1113,10 @@ SILC_SERVER_CMD_FUNC(join)
   cipher = silc_command_get_arg_type(cmd->payload, 3, NULL);
 
   /* See if the channel exists */
-  if (silc_idcache_find_by_data(LCC(channel_name[0]), LCCC(channel_name[0]), 
-                               channel_name, &id_cache) == FALSE) {
+  channel = 
+    silc_idlist_find_channel_by_name(server->local_list, channel_name);
+  if (!channel) {
     /* Channel not found */
-    id_cache = NULL;
 
     /* If we are standalone server we don't have a router, we just create 
        the channel by  ourselves. */
@@ -743,6 +1124,9 @@ SILC_SERVER_CMD_FUNC(join)
       router_id = server->id;
       channel = silc_server_new_channel(server, router_id, 
                                        cipher, channel_name);
+      if (!channel)
+       goto out;
+
       goto join_channel;
     }
 
@@ -769,7 +1153,7 @@ SILC_SERVER_CMD_FUNC(join)
 
   /* If we are router and the channel does not exist we will check our
      global list for the channel. */
-  if (!id_cache && server->server_type == SILC_ROUTER) {
+  if (!channel && server->server_type == SILC_ROUTER) {
 
     /* Notify all routers about the new channel in SILC network. */
     if (!server->standalone) {
@@ -782,32 +1166,40 @@ SILC_SERVER_CMD_FUNC(join)
 
   }
 
-  channel = (SilcChannelList *)id_cache->context;
-
  join_channel:
 
-  /* XXX must check whether the client already is on the channel */
-
-  /* Join the client to the channel */
-  i = channel->user_list_count;
-  channel->user_list = silc_realloc(channel->user_list, 
-                                   sizeof(*channel->user_list) * (i + 1));
-  channel->user_list[i].mode = SILC_CHANNEL_UMODE_NONE;
-
   /* If the JOIN request was forwarded to us we will make a bit slower
      query to get the client pointer. Otherwise, we get the client pointer
      real easy. */
   if (!(cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED)) {
-    client = (SilcClientList *)sock->user_data;
-    channel->user_list[i].client = client;
+    client = (SilcClientEntry)sock->user_data;
   } else {
     void *id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type);
-    client = silc_idlist_find_client_by_id(server->local_list->clients, id);
-    channel->user_list[i].client = client;
+    client = silc_idlist_find_client_by_id(server->local_list, id);
+    if (!client) {
+      /* XXX */
+      goto out;
+    }
     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;
+  }
+
+  /* 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++;
 
+  /* Add the channel to client's channel list */
   i = client->channel_count;
   client->channel = silc_realloc(client->channel, 
                                 sizeof(*client->channel) * (i + 1));
@@ -825,24 +1217,26 @@ SILC_SERVER_CMD_FUNC(join)
      channel mode and possibly other information in this reply packet. */
   if (!cmd->pending) {
     id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL);
-    sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0);
     SILC_PUT32_MSB(channel->mode, mode);
 
     if (!channel->topic)
       packet = 
-       silc_command_encode_payload_va(SILC_COMMAND_JOIN, 4,
-                                      sp_buf->data, sp_buf->len,
-                                      channel_name, strlen(channel_name),
-                                      id_string, SILC_ID_CHANNEL_LEN,
-                                      mode, 4);
+       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
       packet = 
-       silc_command_encode_payload_va(SILC_COMMAND_JOIN, 5,
-                                      sp_buf->data, sp_buf->len,
-                                      channel_name, strlen(channel_name),
-                                      id_string, SILC_ID_CHANNEL_LEN,
-                                      mode, 4,
-                                      channel->topic, strlen(channel->topic));
+       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);
@@ -856,7 +1250,6 @@ SILC_SERVER_CMD_FUNC(join)
                              packet->data, packet->len, FALSE);
     
     silc_buffer_free(packet);
-    silc_free(sp_buf);
   }
 
   /* Send channel key to the client. Client cannot start transmitting
@@ -902,10 +1295,12 @@ SILC_SERVER_CMD_FUNC(join)
     }
   }
 
+  /* 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);
+
  out:
   silc_server_command_free(cmd);
-#undef LCC
-#undef LCCC
 }
 
 /* Server side of command MOTD. Sends servers current "message of the
@@ -951,11 +1346,11 @@ SILC_SERVER_CMD_FUNC(silcoper)
 SILC_SERVER_CMD_FUNC(leave)
 {
   SilcServerCommandContext cmd = (SilcServerCommandContext)context;
-  SilcSocketConnection sock = cmd->sock;
-  SilcClientList *id_entry = (SilcClientList *)cmd->sock->user_data;
   SilcServer server = cmd->server;
+  SilcSocketConnection sock = cmd->sock;
+  SilcClientEntry id_entry = (SilcClientEntry)cmd->sock->user_data;
   SilcChannelID *id;
-  SilcChannelList *channel;
+  SilcChannelEntry channel;
   SilcBuffer packet;
   unsigned int i, argc, key_len;
   unsigned char *tmp, channel_key[32];
@@ -974,21 +1369,41 @@ SILC_SERVER_CMD_FUNC(leave)
     goto out;
   }
 
+  /* Get Channel ID */
   tmp = silc_command_get_arg_type(cmd->payload, 1, NULL);
   if (!tmp) {
     silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
-                                         SILC_STATUS_ERR_BAD_CHANNEL_ID);
+                                         SILC_STATUS_ERR_NO_CHANNEL_ID);
     goto out;
   }
-
-  /* Get Channel ID */
   id = silc_id_str2id(tmp, SILC_ID_CHANNEL);
 
   /* Get channel entry */
-  channel = silc_idlist_find_channel_by_id(server->local_list->channels, id);
+  channel = silc_idlist_find_channel_by_id(server->local_list, id);
+  if (!channel) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
+                                         SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+    goto out;
+  }
+
+  /* Check whether this client is on the channel */
+  if (!silc_server_client_on_channel(id_entry, channel)) {
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE,
+                                         SILC_STATUS_ERR_NOT_ON_CHANNEL);
+    goto out;
+  }
+
+  /* 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->id_entry->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);
 
@@ -1040,6 +1455,126 @@ SILC_SERVER_CMD_FUNC(leave)
   silc_server_command_free(cmd);
 }
 
+/* Server side of command NAMES. Resolves clients and their names currently
+   joined on the requested channel. The name list is sent back to the
+   client. */
+
 SILC_SERVER_CMD_FUNC(names)
 {
+  SilcServerCommandContext cmd = (SilcServerCommandContext)context;
+  SilcServer server = cmd->server;
+  SilcChannelEntry channel;
+  SilcChannelID *id;
+  SilcBuffer packet;
+  unsigned int i, len, len2, argc;
+  unsigned char *tmp;
+  char *name_list = NULL, *n;
+  SilcBuffer client_id_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;
+  }
+
+  /* Get Channel ID */
+  tmp = silc_command_get_arg_type(cmd->payload, 1, NULL);
+  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);
+
+  /* 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);
+  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_names, context);
+      return;
+    }
+
+    silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE,
+                                         SILC_STATUS_ERR_NO_SUCH_CHANNEL);
+    goto out;
+  }
+
+  /* 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;
+    if (n) {
+      len2 = strlen(n);
+      len += len2;
+      name_list = silc_realloc(name_list, sizeof(*name_list) * (len + 1));
+      memcpy(name_list + (len - len2), n, len2);
+      name_list[len] = 0;
+
+      if (i == channel->user_list_count - 1)
+       break;
+      memcpy(name_list + len, ",", 1);
+      len++;
+    }
+  }
+  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);
+    silc_buffer_format(client_id_list,
+                      SILC_STR_UI_XNSTRING(id_string, SILC_ID_CLIENT_LEN),
+                      SILC_STR_END);
+    silc_buffer_pull(client_id_list, SILC_ID_CLIENT_LEN);
+    silc_free(id_string);
+  }
+  silc_buffer_push(client_id_list, 
+                  client_id_list->data - client_id_list->head);
+
+  /* Send reply */
+  packet = silc_command_encode_reply_payload_va(SILC_COMMAND_NAMES,
+                                               SILC_STATUS_OK, 3,
+                                               2, tmp, SILC_ID_CHANNEL_LEN,
+                                               3, name_list, 
+                                               strlen(name_list),
+                                               4, client_id_list->data,
+                                               client_id_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_free(id);
+
+ out:
+  silc_server_command_free(cmd);
 }