updates.
[silc.git] / lib / silcclient / command_reply.c
index 5ed0a3b1fe9935d3003ecd6ece3c66927e179ce0..014ba8ce39a141ef9391cfd99f911ec368d056bd 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
 
-  Copyright (C) 1997 - 2000 Pekka Riikonen
+  Copyright (C) 1997 - 2001 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@
 /* $Id$ */
 
 #include "clientlibincludes.h"
+#include "client_internal.h"
 
 /* Client command reply list. */
 SilcClientCommandReply silc_command_reply_list[] =
@@ -45,7 +46,6 @@ SilcClientCommandReply silc_command_reply_list[] =
   SILC_CLIENT_CMD_REPLY(list, LIST),
   SILC_CLIENT_CMD_REPLY(topic, TOPIC),
   SILC_CLIENT_CMD_REPLY(invite, INVITE),
-  SILC_CLIENT_CMD_REPLY(quit, QUIT),
   SILC_CLIENT_CMD_REPLY(kill, KILL),
   SILC_CLIENT_CMD_REPLY(info, INFO),
   SILC_CLIENT_CMD_REPLY(connect, CONNECT),
@@ -59,10 +59,10 @@ SilcClientCommandReply silc_command_reply_list[] =
   SILC_CLIENT_CMD_REPLY(kick, KICK),
   SILC_CLIENT_CMD_REPLY(restart, RESTART),
   SILC_CLIENT_CMD_REPLY(close, CLOSE),
-  SILC_CLIENT_CMD_REPLY(die, DIE),
+  SILC_CLIENT_CMD_REPLY(shutdown, SHUTDOWN),
   SILC_CLIENT_CMD_REPLY(silcoper, SILCOPER),
   SILC_CLIENT_CMD_REPLY(leave, LEAVE),
-  SILC_CLIENT_CMD_REPLY(names, NAMES),
+  SILC_CLIENT_CMD_REPLY(users, USERS),
 
   { NULL, 0 },
 };
@@ -107,11 +107,13 @@ const SilcCommandStatusMessage silc_command_status_messages[] = {
   { STAT(UNKNOWN_MODE),    "Unknown mode" },
   { STAT(NOT_YOU),         "Cannot change mode for other users" },
   { STAT(NO_CHANNEL_PRIV), "Permission denied. You are not channel operator" },
+  { STAT(NO_CHANNEL_FOPRIV),"Permission denied. You are not channel founder" },
   { STAT(NO_SERVER_PRIV),  "Permission denied. You are not server operator" },
   { STAT(NO_ROUTER_PRIV),  "Permission denied. You are not SILC operator" },
   { STAT(BAD_NICKNAME),    "Bad nickname" },
   { STAT(BAD_CHANNEL),     "Bad channel name" },
   { STAT(AUTH_FAILED),     "Authentication failed" },
+  { STAT(UNKNOWN_ALGORITHM), "Unsupported algorithm" },
 
   { 0, NULL }
 };
@@ -134,8 +136,11 @@ void silc_client_command_reply_process(SilcClient client,
                                       SilcPacketContext *packet)
 {
   SilcBuffer buffer = packet->buffer;
+  SilcClientCommandReply *cmd;
   SilcClientCommandReplyContext ctx;
   SilcCommandPayload payload;
+  SilcCommand command;
+  unsigned short ident;
 
   /* Get command reply payload from packet */
   payload = silc_command_payload_parse(buffer);
@@ -153,12 +158,24 @@ void silc_client_command_reply_process(SilcClient client,
   ctx->payload = payload;
   ctx->args = silc_command_get_args(ctx->payload);
   ctx->packet = packet;
+  ident = silc_command_get_ident(ctx->payload);
       
   /* Check for pending commands and mark to be exeucted */
-  SILC_CLIENT_COMMAND_CHECK_PENDING(ctx);
-  
+  silc_client_command_pending_check(sock->user_data, ctx, 
+                                   silc_command_get(ctx->payload), ident);
+
   /* Execute command reply */
-  SILC_CLIENT_COMMAND_REPLY_EXEC(ctx);
+  command = silc_command_get(ctx->payload);
+  for (cmd = silc_command_reply_list; cmd->cb; cmd++)
+    if (cmd->cmd == command)
+      break;
+
+  if (cmd == NULL || !cmd->cb) {
+    silc_free(ctx);
+    return;
+  }
+
+  cmd->cb(ctx);
 }
 
 /* Returns status message string */
@@ -190,20 +207,18 @@ void silc_client_command_reply_free(SilcClientCommandReplyContext cmd)
 }
 
 static void 
-silc_client_command_reply_whois_print(SilcClientCommandReplyContext cmd,
-                                     SilcCommandStatus status)
+silc_client_command_reply_whois_save(SilcClientCommandReplyContext cmd,
+                                    SilcCommandStatus status)
 {
-  char buf[256];
-  int argc, len;
-  unsigned char *id_data;
-  char *nickname = NULL, *username = NULL;
-  char *realname = NULL;
+  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
   SilcClientID *client_id;
   SilcIDCacheEntry id_cache = NULL;
   SilcClientEntry client_entry = NULL;
-  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
-  
-  memset(buf, 0, sizeof(buf));
+  int argc, len;
+  unsigned char *id_data, *tmp;
+  char *nickname = NULL, *username = NULL;
+  char *realname = NULL;
+  unsigned int idle = 0;
   
   argc = silc_argument_get_arg_num(cmd->args);
 
@@ -214,48 +229,68 @@ silc_client_command_reply_whois_print(SilcClientCommandReplyContext cmd,
   }
   
   client_id = silc_id_payload_parse_id(id_data, len);
-  
-  nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
-  if (nickname) {
-    strncat(buf, nickname, len);
-    strncat(buf, " is ", 4);
+  if (!client_id) {
+    COMMAND_REPLY_ERROR;
+    return;
   }
   
+  nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
   username = silc_argument_get_arg_type(cmd->args, 4, &len);
-  if (username) {
-    strncat(buf, username, len);
-  }
-  
   realname = silc_argument_get_arg_type(cmd->args, 5, &len);
-  if (realname) {
-    strncat(buf, " (", 2);
-    strncat(buf, realname, len);
-    strncat(buf, ")", 1);
+  if (!nickname || !username || !realname) {
+    COMMAND_REPLY_ERROR;
+    return;
   }
-  
+
+  tmp = silc_argument_get_arg_type(cmd->args, 7, &len);
+  if (tmp)
+    SILC_GET32_MSB(idle, tmp);
+
   /* Check if we have this client cached already. */
   if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id,
                                   SILC_ID_CLIENT, &id_cache)) {
+    SILC_LOG_DEBUG(("Adding new client entry"));
+
     client_entry = silc_calloc(1, sizeof(*client_entry));
     client_entry->id = client_id;
     silc_parse_nickname(nickname, &client_entry->nickname, 
                        &client_entry->server, &client_entry->num);
     client_entry->username = strdup(username);
+    if (realname)
+      client_entry->realname = strdup(realname);
     
     /* Add client to cache */
     silc_idcache_add(conn->client_cache, client_entry->nickname,
                     SILC_ID_CLIENT, client_id, (void *)client_entry, TRUE);
   } else {
     client_entry = (SilcClientEntry)id_cache->context;
+    if (client_entry->nickname)
+      silc_free(client_entry->nickname);
+    if (client_entry->server)
+      silc_free(client_entry->server);
+    if (client_entry->username)
+      silc_free(client_entry->username);
+    if (client_entry->realname)
+      silc_free(client_entry->realname);
+
+    SILC_LOG_DEBUG(("Updating client entry"));
+
+    silc_parse_nickname(nickname, &client_entry->nickname, 
+                       &client_entry->server, &client_entry->num);
+    client_entry->username = strdup(username);
+    if (realname)
+      client_entry->realname = strdup(realname);
+
+    id_cache->data = client_entry->nickname;
+    silc_idcache_sort_by_data(conn->client_cache);
+
     silc_free(client_id);
   }
 
-  if (!cmd->callback)
-    cmd->client->ops->say(cmd->client, conn, "%s", buf);
-
   /* Notify application */
-  COMMAND_REPLY((ARGS, client_entry, nickname, 
-                username, realname, NULL, NULL));
+  if (!cmd->callback)
+    COMMAND_REPLY((ARGS, client_entry, nickname, username, realname, 
+                  NULL, idle));
 }
 
 /* Received reply for WHOIS command. This maybe called several times
@@ -296,26 +331,27 @@ SILC_CLIENT_CMD_REPLY_FUNC(whois)
   }
 
   /* Display one whois reply */
-  if (status == SILC_STATUS_OK) {
-    silc_client_command_reply_whois_print(cmd, status);
-  }
-
-  /* XXX list should not be displayed untill all items has been received. */
-  if (status == SILC_STATUS_LIST_START) {
-    silc_client_command_reply_whois_print(cmd, status);
-  }
+  if (status == SILC_STATUS_OK)
+    silc_client_command_reply_whois_save(cmd, status);
 
-  if (status == SILC_STATUS_LIST_ITEM) {
-    silc_client_command_reply_whois_print(cmd, status);
-  }
+  /* List */
+  if (status == SILC_STATUS_LIST_START ||
+      status == SILC_STATUS_LIST_ITEM ||
+      status == SILC_STATUS_LIST_END)
+    silc_client_command_reply_whois_save(cmd, status);
 
-  if (status == SILC_STATUS_LIST_END) {
-    silc_client_command_reply_whois_print(cmd, status);
+  /* Pending callbacks are not executed if this was an list entry */
+  if (status != SILC_STATUS_OK &&
+      status != SILC_STATUS_LIST_END) {
+    silc_client_command_reply_free(cmd);
+    return;
   }
 
-  SILC_CLIENT_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_WHOIS);
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_WHOIS);
 
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_WHOIS);
   silc_client_command_reply_free(cmd);
 }
 
@@ -326,6 +362,77 @@ SILC_CLIENT_CMD_REPLY_FUNC(whowas)
 
 }
 
+static void 
+silc_client_command_reply_identify_save(SilcClientCommandReplyContext cmd,
+                                       SilcCommandStatus status)
+{
+  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
+  SilcClientID *client_id;
+  SilcIDCacheEntry id_cache = NULL;
+  SilcClientEntry client_entry = NULL;
+  int argc, len;
+  unsigned char *id_data;
+  char *nickname = NULL, *username = NULL;
+  
+  argc = silc_argument_get_arg_num(cmd->args);
+
+  id_data = silc_argument_get_arg_type(cmd->args, 2, &len);
+  if (!id_data) {
+    COMMAND_REPLY_ERROR;
+    return;
+  }
+  
+  client_id = silc_id_payload_parse_id(id_data, len);
+  if (!client_id) {
+    COMMAND_REPLY_ERROR;
+    return;
+  }
+  
+  nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
+  username = silc_argument_get_arg_type(cmd->args, 4, &len);
+
+  /* Check if we have this client cached already. */
+  if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id,
+                                  SILC_ID_CLIENT, &id_cache)) {
+    SILC_LOG_DEBUG(("Adding new client entry"));
+
+    client_entry = silc_calloc(1, sizeof(*client_entry));
+    client_entry->id = client_id;
+    silc_parse_nickname(nickname, &client_entry->nickname, 
+                       &client_entry->server, &client_entry->num);
+    if (username)
+      client_entry->username = strdup(username);
+    
+    /* Add client to cache */
+    silc_idcache_add(conn->client_cache, client_entry->nickname,
+                    SILC_ID_CLIENT, client_id, (void *)client_entry, TRUE);
+  } else {
+    client_entry = (SilcClientEntry)id_cache->context;
+    if (client_entry->nickname)
+      silc_free(client_entry->nickname);
+    if (client_entry->server)
+      silc_free(client_entry->server);
+    if (username && client_entry->username)
+      silc_free(client_entry->username);
+    
+    SILC_LOG_DEBUG(("Updating client entry"));
+
+    silc_parse_nickname(nickname, &client_entry->nickname, 
+                       &client_entry->server, &client_entry->num);
+    
+    if (username)
+      client_entry->username = strdup(username);
+    
+    id_cache->data = client_entry->nickname;
+    silc_idcache_sort_by_data(conn->client_cache);
+    
+    silc_free(client_id);
+  }
+
+  /* Notify application */
+  COMMAND_REPLY((ARGS, client_entry, nickname, username));
+}
+
 /* Received reply for IDENTIFY command. This maybe called several times
    for one IDENTIFY command as server may reply with list of results. 
    This is totally silent and does not print anything on screen. */
@@ -334,7 +441,6 @@ SILC_CLIENT_CMD_REPLY_FUNC(identify)
 {
   SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
   SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
-  SilcClientEntry client_entry;
   SilcCommandStatus status;
   unsigned char *tmp;
 
@@ -342,7 +448,10 @@ SILC_CLIENT_CMD_REPLY_FUNC(identify)
 
   tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
   SILC_GET16_MSB(status, tmp);
-  if (status != SILC_STATUS_OK) {
+  if (status != SILC_STATUS_OK && 
+      status != SILC_STATUS_LIST_START &&
+      status != SILC_STATUS_LIST_ITEM &&
+      status != SILC_STATUS_LIST_END) {
     if (status == SILC_STATUS_ERR_NO_SUCH_NICK) {
       /* Take nickname which may be provided */
       tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
@@ -362,42 +471,28 @@ SILC_CLIENT_CMD_REPLY_FUNC(identify)
     }
   }
 
-  /* Display one whois reply */
-  if (status == SILC_STATUS_OK) {
-    unsigned int len;
-    unsigned char *id_data;
-    char *nickname;
-    char *username;
+  /* Save one IDENTIFY entry */
+  if (status == SILC_STATUS_OK)
+    silc_client_command_reply_identify_save(cmd, status);
 
-    id_data = silc_argument_get_arg_type(cmd->args, 2, &len);
-    nickname = silc_argument_get_arg_type(cmd->args, 3, NULL);
-    username = silc_argument_get_arg_type(cmd->args, 4, NULL);
-
-    /* Allocate client entry */
-    client_entry = silc_calloc(1, sizeof(*client_entry));
-    client_entry->id = silc_id_payload_parse_id(id_data, len);
-    if (nickname)
-      silc_parse_nickname(nickname, &client_entry->nickname, 
-                         &client_entry->server, &client_entry->num);
-    if (username)
-      client_entry->username = strdup(username);
-
-    /* Save received Client ID to ID cache */
-    silc_idcache_add(conn->client_cache, client_entry->nickname,
-                    SILC_ID_CLIENT, client_entry->id, client_entry, TRUE);
-  }
-
-  if (status == SILC_STATUS_LIST_START) {
-
-  }
-
-  if (status == SILC_STATUS_LIST_END) {
+  /* List */
+  if (status == SILC_STATUS_LIST_START ||
+      status == SILC_STATUS_LIST_ITEM ||
+      status == SILC_STATUS_LIST_END)
+    silc_client_command_reply_identify_save(cmd, status);
 
+  /* Pending callbacks are not executed if this was an list entry */
+  if (status != SILC_STATUS_OK &&
+      status != SILC_STATUS_LIST_END) {
+    silc_client_command_reply_free(cmd);
+    return;
   }
 
-  SILC_CLIENT_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_IDENTIFY);
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_IDENTIFY);
 
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_IDENTIFY);
   silc_client_command_reply_free(cmd);
 }
 
@@ -434,12 +529,20 @@ SILC_CLIENT_CMD_REPLY_FUNC(nick)
   /* Take received Client ID */
   tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
   idp = silc_id_payload_parse_data(tmp, len);
+  if (!idp) {
+    COMMAND_REPLY_ERROR;
+    goto out;
+  }
   silc_client_receive_new_id(cmd->client, cmd->sock, idp);
     
   /* Notify application */
   COMMAND_REPLY((ARGS, conn->local_entry));
 
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_NICK);
+
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_NICK);
   silc_client_command_reply_free(cmd);
 }
 
@@ -466,6 +569,7 @@ SILC_CLIENT_CMD_REPLY_FUNC(topic)
     cmd->client->ops->say(cmd->client, conn,
             "%s", silc_client_command_status_message(status));
     COMMAND_REPLY_ERROR;
+    SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_TOPIC);
     silc_client_command_reply_free(cmd);
     return;
   }
@@ -487,6 +591,8 @@ SILC_CLIENT_CMD_REPLY_FUNC(topic)
     goto out;
 
   channel_id = silc_id_payload_parse_id(tmp, len);
+  if (!channel_id)
+    goto out;
 
   /* Get the channel name */
   if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
@@ -505,7 +611,11 @@ SILC_CLIENT_CMD_REPLY_FUNC(topic)
   /* Notify application */
   COMMAND_REPLY((ARGS, channel, topic));
 
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_TOPIC);
+
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_TOPIC);
   silc_client_command_reply_free(cmd);
 }
 
@@ -524,6 +634,7 @@ SILC_CLIENT_CMD_REPLY_FUNC(invite)
     cmd->client->ops->say(cmd->client, conn,
             "%s", silc_client_command_status_message(status));
     COMMAND_REPLY_ERROR;
+    SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_INVITE);
     silc_client_command_reply_free(cmd);
     return;
   }
@@ -531,13 +642,13 @@ SILC_CLIENT_CMD_REPLY_FUNC(invite)
   /* Notify application */
   COMMAND_REPLY((ARGS));
 
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_INVITE);
+
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_INVITE);
   silc_client_command_reply_free(cmd);
 }
  
-SILC_CLIENT_CMD_REPLY_FUNC(quit)
-{
-}
-
 SILC_CLIENT_CMD_REPLY_FUNC(kill)
 {
 }
@@ -559,6 +670,7 @@ SILC_CLIENT_CMD_REPLY_FUNC(info)
     cmd->client->ops->say(cmd->client, conn,
             "%s", silc_client_command_status_message(status));
     COMMAND_REPLY_ERROR;
+    SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_INFO);
     silc_client_command_reply_free(cmd);
     return;
   }
@@ -580,14 +692,14 @@ SILC_CLIENT_CMD_REPLY_FUNC(info)
   /* Notify application */
   COMMAND_REPLY((ARGS, NULL, (char *)tmp));
 
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_INFO);
+
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_INFO);
   silc_client_command_reply_free(cmd);
 }
 
-SILC_CLIENT_CMD_REPLY_FUNC(connect)
-{
-}
-
 /* Received reply to PING command. The reply time is shown to user. */
 
 SILC_CLIENT_CMD_REPLY_FUNC(ping)
@@ -608,7 +720,12 @@ SILC_CLIENT_CMD_REPLY_FUNC(ping)
   }
 
   curtime = time(NULL);
-  id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type);
+  id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_len,
+                     cmd->packet->src_id_type);
+  if (!id) {
+    COMMAND_REPLY_ERROR;
+    goto out;
+  }
 
   for (i = 0; i < conn->ping_count; i++) {
     if (!SILC_ID_SERVER_COMPARE(conn->ping[i].dest_id, id)) {
@@ -617,40 +734,44 @@ SILC_CLIENT_CMD_REPLY_FUNC(ping)
                            "Ping reply from %s: %d second%s", 
                            conn->ping[i].dest_name, diff, 
                            diff == 1 ? "" : "s");
-
+      
       conn->ping[i].start_time = 0;
       silc_free(conn->ping[i].dest_id);
       conn->ping[i].dest_id = NULL;
       silc_free(conn->ping[i].dest_name);
       conn->ping[i].dest_name = NULL;
-
-      /* Notify application */
-      COMMAND_REPLY((ARGS));
       break;
     }
   }
 
   silc_free(id);
 
+  /* Notify application */
+  COMMAND_REPLY((ARGS));
+
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_PING);
+
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_PING);
   silc_client_command_reply_free(cmd);
 }
 
-SILC_CLIENT_CMD_REPLY_FUNC(oper)
-{
-}
-
 /* Received reply for JOIN command. */
 
 SILC_CLIENT_CMD_REPLY_FUNC(join)
 {
   SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
   SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
-  SilcClient client = cmd->client;
   SilcCommandStatus status;
-  SilcIDPayload idp;
-  unsigned int argc, mode, len;
-  char *topic, *tmp, *channel_name;
+  SilcIDPayload idp = NULL;
+  SilcChannelEntry channel;
+  SilcIDCacheEntry id_cache = NULL;
+  SilcChannelUser chu;
+  unsigned int argc, mode, len, list_count;
+  char *topic, *tmp, *channel_name = NULL, *hmac;
+  SilcBuffer keyp, client_id_list, client_mode_list;
+  int i;
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -663,7 +784,7 @@ SILC_CLIENT_CMD_REPLY_FUNC(join)
   }
 
   argc = silc_argument_get_arg_num(cmd->args);
-  if (argc < 3 || argc > 5) {
+  if (argc < 7 || argc > 14) {
     cmd->client->ops->say(cmd->client, conn,
             "Cannot join channel: Bad reply packet");
     COMMAND_REPLY_ERROR;
@@ -686,34 +807,141 @@ SILC_CLIENT_CMD_REPLY_FUNC(join)
     cmd->client->ops->say(cmd->client, conn, 
                          "Cannot join channel: Bad reply packet");
     COMMAND_REPLY_ERROR;
+    silc_free(channel_name);
     goto out;
   }
   idp = silc_id_payload_parse_data(tmp, len);
+  if (!idp) {
+    COMMAND_REPLY_ERROR;
+    silc_free(channel_name);
+    goto out;
+  }
 
   /* Get channel mode */
-  tmp = silc_argument_get_arg_type(cmd->args, 4, NULL);
+  tmp = silc_argument_get_arg_type(cmd->args, 5, NULL);
   if (tmp)
     SILC_GET32_MSB(mode, tmp);
   else
     mode = 0;
 
+  /* Get channel key */
+  tmp = silc_argument_get_arg_type(cmd->args, 7, &len);
+  if (!tmp) {
+    silc_id_payload_free(idp);
+    silc_free(channel_name);
+    goto out;
+  }
+  keyp = silc_buffer_alloc(len);
+  silc_buffer_pull_tail(keyp, SILC_BUFFER_END(keyp));
+  silc_buffer_put(keyp, tmp, len);
+
   /* Get topic */
-  topic = silc_argument_get_arg_type(cmd->args, 5, NULL);
+  topic = silc_argument_get_arg_type(cmd->args, 10, NULL);
 
-  /* Save received Channel ID */
-  silc_client_new_channel_id(cmd->client, cmd->sock, channel_name, 
-                            mode, idp);
+  /* Save received Channel ID. This actually creates the channel */
+  channel = silc_client_new_channel_id(cmd->client, cmd->sock, channel_name, 
+                                      mode, idp);
   silc_id_payload_free(idp);
 
-  if (topic)
-    client->ops->say(cmd->client, conn, 
-                    "Topic for %s: %s", channel_name, topic);
+  /* Get hmac */
+  hmac = silc_argument_get_arg_type(cmd->args, 11, NULL);
+  if (hmac) {
+    if (!silc_hmac_alloc(hmac, NULL, &channel->hmac)) {
+      cmd->client->ops->say(cmd->client, conn, 
+                           "Cannot join channel: Unsupported HMAC `%s'",
+                           hmac);
+      COMMAND_REPLY_ERROR;
+      silc_free(channel_name);
+      goto out;
+    }
+  }
+
+  /* Get the list count */
+  tmp = silc_argument_get_arg_type(cmd->args, 12, &len);
+  if (!tmp)
+    goto out;
+  SILC_GET32_MSB(list_count, tmp);
+
+  /* Get Client ID list */
+  tmp = silc_argument_get_arg_type(cmd->args, 13, &len);
+  if (!tmp)
+    goto out;
+
+  client_id_list = silc_buffer_alloc(len);
+  silc_buffer_pull_tail(client_id_list, len);
+  silc_buffer_put(client_id_list, tmp, len);
+
+  /* Get client mode list */
+  tmp = silc_argument_get_arg_type(cmd->args, 14, &len);
+  if (!tmp)
+    goto out;
+
+  client_mode_list = silc_buffer_alloc(len);
+  silc_buffer_pull_tail(client_mode_list, len);
+  silc_buffer_put(client_mode_list, tmp, len);
+
+  /* Add clients we received in the reply to the channel */
+  for (i = 0; i < list_count; i++) {
+    unsigned short idp_len;
+    unsigned int mode;
+    SilcClientID *client_id;
+    SilcClientEntry client_entry;
+
+    /* Client ID */
+    SILC_GET16_MSB(idp_len, client_id_list->data + 2);
+    idp_len += 4;
+    client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
+    if (!client_id)
+      continue;
+
+    /* Mode */
+    SILC_GET32_MSB(mode, client_mode_list->data);
+
+    /* Check if we have this client cached already. */
+    if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id,
+                                    SILC_ID_CLIENT, &id_cache)) {
+      /* No, we don't have it, add entry for it. */
+      client_entry = silc_calloc(1, sizeof(*client_entry));
+      client_entry->id = silc_id_dup(client_id, SILC_ID_CLIENT);
+      silc_idcache_add(conn->client_cache, NULL, SILC_ID_CLIENT, 
+                      client_entry->id, (void *)client_entry, FALSE);
+    } else {
+      /* Yes, we have it already */
+      client_entry = (SilcClientEntry)id_cache->context;
+    }
+
+    /* Join the client to the channel */
+    chu = silc_calloc(1, sizeof(*chu));
+    chu->client = client_entry;
+    chu->mode = mode;
+    silc_list_add(channel->clients, chu);
+    silc_free(client_id);
+
+    silc_buffer_pull(client_id_list, idp_len);
+    silc_buffer_pull(client_mode_list, 4);
+  }
+  silc_buffer_push(client_id_list, client_id_list->data - 
+                  client_id_list->head);
+  silc_buffer_push(client_mode_list, client_mode_list->data - 
+                  client_mode_list->head);
+
+  /* Save channel key */
+  silc_client_save_channel_key(conn, keyp, channel);
 
   /* Notify application */
-  COMMAND_REPLY((ARGS, channel_name, conn->current_channel, mode,
-                NULL, NULL, topic));
+  COMMAND_REPLY((ARGS, channel_name, channel, mode, 0, keyp->head, NULL,
+                NULL, topic, hmac, list_count, client_id_list, 
+                client_mode_list));
+
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_JOIN);
+
+  silc_buffer_free(keyp);
+  silc_buffer_free(client_id_list);
+  silc_buffer_free(client_mode_list);
 
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_JOIN);
   silc_client_command_reply_free(cmd);
 }
 
@@ -773,7 +1001,11 @@ SILC_CLIENT_CMD_REPLY_FUNC(motd)
   /* Notify application */
   COMMAND_REPLY((ARGS, motd));
 
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_MOTD);
+
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_MOTD);
   silc_client_command_reply_free(cmd);
 }
 
@@ -808,7 +1040,11 @@ SILC_CLIENT_CMD_REPLY_FUNC(cmode)
   /* Notify application */
   COMMAND_REPLY((ARGS, tmp));
 
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_CMODE);
+
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_CMODE);
   silc_client_command_reply_free(cmd);
 }
 
@@ -846,6 +1082,10 @@ SILC_CLIENT_CMD_REPLY_FUNC(cumode)
     goto out;
   }
   client_id = silc_id_payload_parse_id(id, len);
+  if (!client_id) {
+    COMMAND_REPLY_ERROR;
+    goto out;
+  }
   
   /* Get client entry */
   if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id,
@@ -858,30 +1098,157 @@ SILC_CLIENT_CMD_REPLY_FUNC(cumode)
   COMMAND_REPLY((ARGS, tmp, (SilcClientEntry)id_cache->context));
   silc_free(client_id);
   
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_CUMODE);
+
  out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_CUMODE);
   silc_client_command_reply_free(cmd);
 }
 
 SILC_CLIENT_CMD_REPLY_FUNC(kick)
+{
+  SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
+  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
+  SilcCommandStatus status;
+  unsigned char *tmp;
+
+  tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+  SILC_GET16_MSB(status, tmp);
+  if (status != SILC_STATUS_OK) {
+    cmd->client->ops->say(cmd->client, conn,
+            "%s", silc_client_command_status_message(status));
+    COMMAND_REPLY_ERROR;
+    goto out;
+  }
+
+  /* Notify application */
+  COMMAND_REPLY((ARGS));
+
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_KICK);
+
+ out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_KICK);
+  silc_client_command_reply_free(cmd);
+}
+
+SILC_CLIENT_CMD_REPLY_FUNC(silcoper)
+{
+}
+
+SILC_CLIENT_CMD_REPLY_FUNC(oper)
 {
 }
 
+SILC_CLIENT_CMD_REPLY_FUNC(connect)
+{
+  SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
+  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
+  SilcCommandStatus status;
+  unsigned char *tmp;
+
+  tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+  SILC_GET16_MSB(status, tmp);
+  if (status != SILC_STATUS_OK) {
+    cmd->client->ops->say(cmd->client, conn,
+            "%s", silc_client_command_status_message(status));
+    COMMAND_REPLY_ERROR;
+    goto out;
+  }
+
+  /* Notify application */
+  COMMAND_REPLY((ARGS));
+
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_CONNECT);
+
+ out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_CONNECT);
+  silc_client_command_reply_free(cmd);
+}
+
 SILC_CLIENT_CMD_REPLY_FUNC(restart)
 {
+  SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
+  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
+  SilcCommandStatus status;
+  unsigned char *tmp;
+
+  tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+  SILC_GET16_MSB(status, tmp);
+  if (status != SILC_STATUS_OK) {
+    cmd->client->ops->say(cmd->client, conn,
+            "%s", silc_client_command_status_message(status));
+    COMMAND_REPLY_ERROR;
+    goto out;
+  }
+
+  /* Notify application */
+  COMMAND_REPLY((ARGS));
+
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_RESTART);
+
+ out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_RESTART);
+  silc_client_command_reply_free(cmd);
 }
  
 SILC_CLIENT_CMD_REPLY_FUNC(close)
 {
+  SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
+  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
+  SilcCommandStatus status;
+  unsigned char *tmp;
+
+  tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+  SILC_GET16_MSB(status, tmp);
+  if (status != SILC_STATUS_OK) {
+    cmd->client->ops->say(cmd->client, conn,
+            "%s", silc_client_command_status_message(status));
+    COMMAND_REPLY_ERROR;
+    goto out;
+  }
+
+  /* Notify application */
+  COMMAND_REPLY((ARGS));
+
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_CLOSE);
+
+ out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_CLOSE);
+  silc_client_command_reply_free(cmd);
 }
  
-SILC_CLIENT_CMD_REPLY_FUNC(die)
+SILC_CLIENT_CMD_REPLY_FUNC(shutdown)
 {
+  SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
+  SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
+  SilcCommandStatus status;
+  unsigned char *tmp;
+
+  tmp = silc_argument_get_arg_type(cmd->args, 1, NULL);
+  SILC_GET16_MSB(status, tmp);
+  if (status != SILC_STATUS_OK) {
+    cmd->client->ops->say(cmd->client, conn,
+            "%s", silc_client_command_status_message(status));
+    COMMAND_REPLY_ERROR;
+    goto out;
+  }
+
+  /* Notify application */
+  COMMAND_REPLY((ARGS));
+
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_SHUTDOWN);
+
+ out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_SHUTDOWN);
+  silc_client_command_reply_free(cmd);
 }
  
-SILC_CLIENT_CMD_REPLY_FUNC(silcoper)
-{
-}
-
 /* Reply to LEAVE command. */
 
 SILC_CLIENT_CMD_REPLY_FUNC(leave)
@@ -897,31 +1264,39 @@ SILC_CLIENT_CMD_REPLY_FUNC(leave)
     cmd->client->ops->say(cmd->client, conn,
             "%s", silc_client_command_status_message(status));
     COMMAND_REPLY_ERROR;
-    return;
+    goto out;
   }
 
   /* Notify application */
   COMMAND_REPLY((ARGS));
 
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_LEAVE);
+
+ out:
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_LEAVE);
   silc_client_command_reply_free(cmd);
 }
 
-/* Reply to NAMES command. Received list of client names on the channel 
-   we requested. */
+/* Reply to USERS command. Received list of client ID's and theirs modes
+   on the channel we requested. */
 
-SILC_CLIENT_CMD_REPLY_FUNC(names)
+SILC_CLIENT_CMD_REPLY_FUNC(users)
 {
   SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
   SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data;
   SilcCommandStatus status;
   SilcIDCacheEntry id_cache = NULL;
   SilcChannelEntry channel;
+  SilcChannelUser chu;
   SilcChannelID *channel_id = NULL;
   SilcBuffer client_id_list;
   SilcBuffer client_mode_list;
   unsigned char *tmp;
-  char *name_list, *cp;
-  int i, k, len1, len2, list_count = 0;
+  unsigned int tmp_len, list_count;
+  int i;
+  unsigned char **res_argv = NULL;
+  unsigned int *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -935,202 +1310,143 @@ SILC_CLIENT_CMD_REPLY_FUNC(names)
   }
 
   /* Get channel ID */
-  tmp = silc_argument_get_arg_type(cmd->args, 2, &len1);
-  if (!tmp) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Cannot Channel ID: Bad reply packet");
-    COMMAND_REPLY_ERROR;
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!tmp)
+    goto out;
+  channel_id = silc_id_payload_parse_id(tmp, tmp_len);
+  if (!channel_id)
     goto out;
-  }
-  channel_id = silc_id_payload_parse_id(tmp, len1);
 
-  /* Get the name list of the channel */
-  name_list = silc_argument_get_arg_type(cmd->args, 3, &len1);
-  if (!name_list) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Cannot get user list: Bad reply packet");
-    COMMAND_REPLY_ERROR;
+  /* Get the list count */
+  tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+  if (!tmp)
     goto out;
-  }
+  SILC_GET32_MSB(list_count, tmp);
 
   /* Get Client ID list */
-  tmp = silc_argument_get_arg_type(cmd->args, 4, &len2);
-  if (!tmp) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Cannot get user list: Bad reply packet");
-    COMMAND_REPLY_ERROR;
+  tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
+  if (!tmp)
     goto out;
-  }
 
-  client_id_list = silc_buffer_alloc(len2);
-  silc_buffer_pull_tail(client_id_list, len2);
-  silc_buffer_put(client_id_list, tmp, len2);
+  client_id_list = silc_buffer_alloc(tmp_len);
+  silc_buffer_pull_tail(client_id_list, tmp_len);
+  silc_buffer_put(client_id_list, tmp, tmp_len);
 
   /* Get client mode list */
-  tmp = silc_argument_get_arg_type(cmd->args, 5, &len2);
-  if (!tmp) {
-    cmd->client->ops->say(cmd->client, conn, 
-                         "Cannot get user list: Bad reply packet");
-    COMMAND_REPLY_ERROR;
+  tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
+  if (!tmp)
     goto out;
-  }
 
-  client_mode_list = silc_buffer_alloc(len2);
-  silc_buffer_pull_tail(client_mode_list, len2);
-  silc_buffer_put(client_mode_list, tmp, len2);
+  client_mode_list = silc_buffer_alloc(tmp_len);
+  silc_buffer_pull_tail(client_mode_list, tmp_len);
+  silc_buffer_put(client_mode_list, tmp, tmp_len);
 
-  /* Get the channel name */
+  /* Get channel entry */
   if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
-                                  SILC_ID_CHANNEL, &id_cache)) {
+                                   SILC_ID_CHANNEL, &id_cache)) {
     COMMAND_REPLY_ERROR;
     goto out;
   }
-  
   channel = (SilcChannelEntry)id_cache->context;
 
-  /* If there is pending command we know that user has called this command
-     and we will handle the name list differently. */
-  if (cmd->callback) {
-    /* We will resolve all the necessary information about the people
-       on the channel. Only after that will we display the user list. */
-    for (i = 0; i < len1; i++) {
-      /* XXX */
-
-    }
-    silc_client_command_pending_del(SILC_COMMAND_NAMES);
-  } else {
-    /* there is no pending callback it means that this command reply
-       has been received without calling the command, ie. server has sent
-       the reply without getting the command from us first. This happens
-       with SILC servers that sends NAMES reply after joining to a channel. */
-
-    /* Remove commas from list */
-    for (i = 0; i < len1; i++)
-      if (name_list[i] == ',') {
-       name_list[i] = ' ';
-       list_count++;
-      }
-    list_count++;
-  }
-
-  /* Remove old client list from channel, if exists */
-  if (channel->clients) {
-    silc_free(channel->clients);
-    channel->clients = NULL;
-    channel->clients_count = 0;
+  /* Remove old client list from channel. */
+  silc_list_start(channel->clients);
+  while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
+    silc_list_del(channel->clients, chu);
+    silc_free(chu);
   }
 
-  /* Allocate room for clients in the channel */
-  channel->clients = silc_calloc(list_count, sizeof(*channel->clients));
-
-  /* Cache the received name list, client ID's and modes. This cache expires
+  /* Cache the received Client ID's and modes. This cache expires
      whenever server sends notify message to channel. It means two things;
-     some user has joined or leaved the channel. */
-  cp = name_list;
+     some user has joined or leaved the channel. XXX! */
   for (i = 0; i < list_count; i++) {
-    int nick_len = strcspn(name_list, " ");
     unsigned short idp_len;
     unsigned int mode;
-    char *nickname = silc_calloc(nick_len + 1, sizeof(*nickname));
     SilcClientID *client_id;
     SilcClientEntry client;
 
-    memcpy(nickname, name_list, nick_len);
+    /* Client ID */
     SILC_GET16_MSB(idp_len, client_id_list->data + 2);
     idp_len += 4;
     client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
-    silc_buffer_pull(client_id_list, idp_len);
-    
+    if (!client_id)
+      continue;
+
+    /* Mode */
     SILC_GET32_MSB(mode, client_mode_list->data);
-    silc_buffer_pull(client_mode_list, 4);
 
     /* Check if we have this client cached already. */
     if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id,
                                     SILC_ID_CLIENT, &id_cache)) {
-      client = silc_calloc(1, sizeof(*client));
-      client->id = client_id;
-      silc_parse_nickname(nickname, &client->nickname, &client->server, 
-                         &client->num);
-      silc_free(nickname);
-
-      /* Add client to cache */
-      silc_idcache_add(conn->client_cache, client->nickname, SILC_ID_CLIENT,
-                      client_id, (void *)client, TRUE);
+      /* No we don't have it, query it from the server. Assemble argument
+        table that will be sent fr the IDENTIFY command later. */
+      res_argv = silc_realloc(res_argv, sizeof(*res_argv) *
+                             (res_argc + 1));
+      res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) *
+                                  (res_argc + 1));
+      res_argv_types = silc_realloc(res_argv_types, sizeof(*res_argv_types) *
+                                   (res_argc + 1));
+      res_argv[res_argc] = client_id_list->data;
+      res_argv_lens[res_argc] = idp_len;
+      res_argv_types[res_argc] = res_argc + 3;
+      res_argc++;
     } else {
+      /* Found the client, join it to the channel */
       client = (SilcClientEntry)id_cache->context;
+      chu = silc_calloc(1, sizeof(*chu));
+      chu->client = client;
+      chu->mode = mode;
+      silc_list_add(channel->clients, chu);
+
       silc_free(client_id);
-      silc_free(nickname);
       id_cache = NULL;
     }
 
-    channel->clients[channel->clients_count].client = client;
-    channel->clients[channel->clients_count].mode = mode;
-    channel->clients_count++;
-
-    name_list += nick_len + 1;
-  }
-
-  name_list = cp;
-  for (i = 0; i < list_count; i++) {
-    int c;
-    int nick_len = strcspn(name_list, " ");
-    char *nickname = silc_calloc(nick_len + 1, sizeof(*nickname));
-    memcpy(nickname, name_list, nick_len);
-
-    for (c = 0, k = 0; k < channel->clients_count; k++) {
-      if (channel->clients[k].client && 
-         !strncmp(channel->clients[k].client->nickname, 
-                  nickname, strlen(channel->clients[k].client->nickname))) {
-       char t[8];
-       
-       if (!c) {
-         c++;
-         continue;
-       }
-       
-       memset(t, 0, sizeof(t));
-       channel->clients[k].client->nickname = 
-         silc_calloc(strlen(nickname) + 8, sizeof(*channel->clients[k].
-                                                  client->nickname));
-       strncat(channel->clients[k].client->nickname, nickname, 
-               strlen(nickname));
-       snprintf(t, sizeof(t), " [%d]", c++);
-       strncat(channel->clients[k].client->nickname, t, strlen(t));
-      }
-    }
-
-    silc_free(nickname);
+    silc_buffer_pull(client_id_list, idp_len);
+    silc_buffer_pull(client_mode_list, 4);
   }
 
-  name_list = NULL;
-  len1 = 0;
-  for (k = 0; k < channel->clients_count; k++) {
-    char *n = channel->clients[k].client->nickname;
-    len2 = strlen(n);
-    len1 += len2;
-    name_list = silc_realloc(name_list, sizeof(*name_list) * (len1 + 1));
-    memcpy(name_list + (len1 - len2), n, len2);
-    name_list[len1] = 0;
-    
-    if (k == channel->clients_count - 1)
-      break;
-    memcpy(name_list + len1, " ", 1);
-    len1++;
+  /* Query the client information from server if the list included clients
+     that we don't know about. */
+  if (res_argc) {
+    SilcBuffer res_cmd;
+
+    /* Send the IDENTIFY command to server */
+    res_cmd = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
+                                         res_argc, res_argv, res_argv_lens,
+                                         res_argv_types, ++conn->cmd_ident);
+    silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, 
+                           NULL, 0, NULL, NULL, res_cmd->data, res_cmd->len,
+                           TRUE);
+
+    /* Register pending command callback. After we've received the IDENTIFY
+       command reply we will reprocess this command reply by re-calling this
+       USERS command reply callback. */
+    silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, conn->cmd_ident,
+                               NULL, silc_client_command_reply_users, cmd);
+
+    silc_buffer_free(res_cmd);
+    if (channel_id)
+      silc_free(channel_id);
+
+    silc_free(res_argv);
+    silc_free(res_argv_lens);
+    silc_free(res_argv_types);
+    return;
   }
 
-  cmd->client->ops->say(cmd->client, conn,
-                       "Users on %s: %s", channel->channel_name, name_list);
-
   /* Notify application */
-  COMMAND_REPLY((ARGS, channel, name_list, client_id_list->head,
-                client_mode_list->head));
+  COMMAND_REPLY((ARGS, channel, list_count, client_id_list, client_mode_list));
+
+  /* Execute any pending command callbacks */
+  SILC_CLIENT_PENDING_EXEC(cmd, SILC_COMMAND_USERS);
 
-  silc_free(name_list);
   silc_buffer_free(client_id_list);
   silc_buffer_free(client_mode_list);
 
  out:
   if (channel_id)
     silc_free(channel_id);
+  SILC_CLIENT_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_USERS);
   silc_client_command_reply_free(cmd);
 }