updates.
[silc.git] / apps / silcd / command_reply.c
index 853a52ff79dd3c372d188be634ac140c3a30b280..79eb2bbeabebbc518b25719608d2e336f2e17d5f 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
@@ -25,6 +25,7 @@
 
 #define COMMAND_CHECK_STATUS                                             \
 do {                                                                     \
+  SILC_LOG_DEBUG(("Start"));                                             \
   SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); \
   if (status != SILC_STATUS_OK) {                                        \
     silc_server_command_reply_free(cmd);                                 \
@@ -34,6 +35,7 @@ do {                                                                    \
 
 #define COMMAND_CHECK_STATUS_LIST                                        \
 do {                                                                     \
+  SILC_LOG_DEBUG(("Start"));                                             \
   SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); \
   if (status != SILC_STATUS_OK &&                                        \
       status != SILC_STATUS_LIST_START &&                                \
@@ -48,9 +50,13 @@ do {                                                                   \
    they are never sent by server. More maybe added later if need appears. */
 SilcServerCommandReply silc_command_reply_list[] =
 {
-  SILC_SERVER_CMD_REPLY(join, JOIN),
-  SILC_SERVER_CMD_REPLY(identify, WHOIS),
+  SILC_SERVER_CMD_REPLY(whois, WHOIS),
+  SILC_SERVER_CMD_REPLY(whowas, WHOWAS),
   SILC_SERVER_CMD_REPLY(identify, IDENTIFY),
+  SILC_SERVER_CMD_REPLY(info, INFO),
+  SILC_SERVER_CMD_REPLY(motd, MOTD),
+  SILC_SERVER_CMD_REPLY(join, JOIN),
+  SILC_SERVER_CMD_REPLY(users, USERS),
 
   { NULL, 0 },
 };
@@ -67,6 +73,8 @@ void silc_server_command_reply_process(SilcServer server,
   SilcCommand command;
   unsigned short ident;
 
+  SILC_LOG_DEBUG(("Start"));
+
   /* Get command reply payload from packet */
   payload = silc_command_payload_parse(buffer);
   if (!payload) {
@@ -79,14 +87,14 @@ void silc_server_command_reply_process(SilcServer server,
      command reply routine receiving it. */
   ctx = silc_calloc(1, sizeof(*ctx));
   ctx->server = server;
-  ctx->sock = sock;
+  ctx->sock = silc_socket_dup(sock);
   ctx->payload = payload;
   ctx->args = silc_command_get_args(ctx->payload);
   ident = silc_command_get_ident(ctx->payload);
       
   /* Check for pending commands and mark to be exeucted */
-  silc_server_command_pending_check(ctx, silc_command_get(ctx->payload),
-                                   ident);
+  silc_server_command_pending_check(server, ctx, 
+                                   silc_command_get(ctx->payload), ident);
 
   /* Execute command reply */
   command = silc_command_get(ctx->payload);
@@ -94,8 +102,8 @@ void silc_server_command_reply_process(SilcServer server,
     if (cmd->cmd == command)
       break;
 
-  if (cmd == NULL) {
-    silc_free(ctx);
+  if (cmd == NULL || !cmd->cb) {
+    silc_server_command_reply_free(ctx);
     return;
   }
 
@@ -108,33 +116,120 @@ void silc_server_command_reply_free(SilcServerCommandReplyContext cmd)
 {
   if (cmd) {
     silc_command_free_payload(cmd->payload);
+    if (cmd->sock)
+      silc_socket_free(cmd->sock); /* Decrease the reference counter */
     silc_free(cmd);
   }
 }
 
-/* Caches the received WHOIS information. If we are normal server currently
-   we cache global information only for short period of time.  If we are
-   router we want to cache them a bit longer since we can receive information
-   if any of the information becomes invalid. Normal server cannot receive
-   that information. Returns FALSE if something was wrong with the reply. */
+/* Caches the received WHOIS information. */
 
 static char
 silc_server_command_reply_whois_save(SilcServerCommandReplyContext cmd)
 {
+  SilcServer server = cmd->server;
   int len, id_len;
-  unsigned char *id_data;
+  unsigned char *tmp, *id_data;
   char *nickname, *username, *realname;
   SilcClientID *client_id;
+  SilcClientEntry client;
+  SilcIDCacheEntry cache = NULL;
+  char global = FALSE;
+  char *nick;
+  unsigned int mode = 0;
 
   id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len);
   nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
   username = silc_argument_get_arg_type(cmd->args, 4, &len);
   realname = silc_argument_get_arg_type(cmd->args, 5, &len);
-  if (!id_data || !nickname || !username || !realname) 
+  if (!id_data || !nickname || !username || !realname) {
+    SILC_LOG_ERROR(("Incomplete WHOIS info: %s %s %s",
+                   nickname ? nickname : "",
+                   username ? username : "",
+                   realname ? realname : ""));
     return FALSE;
+  }
+
+  tmp = silc_argument_get_arg_type(cmd->args, 7, &len);
+  if (tmp)
+    SILC_GET32_MSB(mode, tmp);
 
   client_id = silc_id_payload_parse_id(id_data, id_len);
+  if (!client_id)
+    return FALSE;
+
+  /* Check if we have this client cached already. */
 
+  client = silc_idlist_find_client_by_id(server->local_list, client_id,
+                                        &cache);
+  if (!client) {
+    client = silc_idlist_find_client_by_id(server->global_list, 
+                                          client_id, &cache);
+    global = TRUE;
+  }
+
+  if (!client) {
+    /* If router did not find such Client ID in its lists then this must
+       be bogus client or some router in the net is buggy. */
+    if (server->server_type == SILC_ROUTER)
+      return FALSE;
+
+    /* Take hostname out of nick string if it includes it. */
+    if (strchr(nickname, '@')) {
+      int len = strcspn(nickname, "@");
+      nick = silc_calloc(len + 1, sizeof(char));
+      memcpy(nick, nickname, len);
+    } else {
+      nick = strdup(nickname);
+    }
+
+    /* We don't have that client anywhere, add it. The client is added
+       to global list since server didn't have it in the lists so it must be 
+       global. */
+    client = silc_idlist_add_client(server->global_list, nick, strlen(nick),
+                                   strdup(username), 
+                                   strdup(realname), client_id, 
+                                   cmd->sock->user_data, NULL);
+    if (!client)
+      return FALSE;
+
+    client->data.registered = TRUE;
+    client->mode = mode;
+  } else {
+    /* We have the client already, update the data */
+
+    SILC_LOG_DEBUG(("Updating client data"));
+
+    /* Take hostname out of nick string if it includes it. */
+    if (strchr(nickname, '@')) {
+      int len = strcspn(nickname, "@");
+      nick = silc_calloc(len + 1, sizeof(char));
+      memcpy(nick, nickname, len);
+    } else {
+      nick = strdup(nickname);
+    }
+
+    if (client->nickname)
+      silc_free(client->nickname);
+    if (client->username)
+      silc_free(client->username);
+    if (client->userinfo)
+      silc_free(client->userinfo);
+    
+    client->nickname = nick;
+    client->username = strdup(username);
+    client->userinfo = strdup(realname);
+    client->mode = mode;
+
+    if (cache) {
+      cache->data = nick;
+      cache->data_len = strlen(nick);
+      silc_idcache_sort_by_data(global ? server->global_list->clients : 
+                               server->local_list->clients);
+    }
+
+    silc_free(client_id);
+  }
 
   return TRUE;
 }
@@ -148,38 +243,240 @@ silc_server_command_reply_whois_save(SilcServerCommandReplyContext cmd)
 SILC_SERVER_CMD_REPLY_FUNC(whois)
 {
   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
-  SilcServer server = cmd->server;
   SilcCommandStatus status;
 
-  SILC_LOG_DEBUG(("Start"));
-
   COMMAND_CHECK_STATUS_LIST;
 
-  /* Process one identify reply */
-  if (status == SILC_STATUS_OK) {
+  if (!silc_server_command_reply_whois_save(cmd))
+    goto out;
 
-  }
+  /* Execute any pending commands */
+  SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_WHOIS);
 
-  if (status == SILC_STATUS_LIST_START) {
+ out:
+  SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_WHOIS);
+  silc_server_command_reply_free(cmd);
+}
 
-  }
+/* Caches the received WHOWAS information for a short period of time. */
 
-  if (status == SILC_STATUS_LIST_ITEM) {
+static char
+silc_server_command_reply_whowas_save(SilcServerCommandReplyContext cmd)
+{
+  SilcServer server = cmd->server;
+  int len, id_len;
+  unsigned char *id_data;
+  char *nickname, *username, *realname;
+  SilcClientID *client_id;
+  SilcClientEntry client;
+  SilcIDCacheEntry cache = NULL;
+  char *nick;
+  int global = FALSE;
 
-  }
+  id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len);
+  nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
+  username = silc_argument_get_arg_type(cmd->args, 4, &len);
+  if (!id_data || !nickname || !username)
+    return FALSE;
+
+  realname = silc_argument_get_arg_type(cmd->args, 5, &len);
+
+  client_id = silc_id_payload_parse_id(id_data, id_len);
+  if (!client_id)
+    return FALSE;
 
-  if (status == SILC_STATUS_LIST_END) {
+  /* Check if we have this client cached already. */
 
+  client = silc_idlist_find_client_by_id(server->local_list, client_id,
+                                        &cache);
+  if (!client) {
+    client = silc_idlist_find_client_by_id(server->global_list, 
+                                          client_id, &cache);
+    global = TRUE;
   }
 
-  /* Execute pending IDENTIFY command so that the client who originally
-     requested the identify information will get it after all. */
-  SILC_SERVER_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_IDENTIFY);
+  if (!client) {
+    /* If router did not find such Client ID in its lists then this must
+       be bogus client or some router in the net is buggy. */
+    if (server->server_type == SILC_ROUTER)
+      return FALSE;
+
+    /* Take hostname out of nick string if it includes it. */
+    if (strchr(nickname, '@')) {
+      int len = strcspn(nickname, "@");
+      nick = silc_calloc(len + 1, sizeof(char));
+      memcpy(nick, nickname, len);
+    } else {
+      nick = strdup(nickname);
+    }
+
+    /* We don't have that client anywhere, add it. The client is added
+       to global list since server didn't have it in the lists so it must be 
+       global. */
+    client = silc_idlist_add_client(server->global_list, nick, strlen(nick),
+                                   strdup(username), strdup(realname), 
+                                   silc_id_dup(client_id, SILC_ID_CLIENT), 
+                                   cmd->sock->user_data, NULL);
+    if (!client)
+      return FALSE;
+
+    client->data.registered = FALSE;
+    client = silc_idlist_find_client_by_id(server->global_list, 
+                                          client_id, &cache);
+    cache->expire = SILC_ID_CACHE_EXPIRE_DEF;
+  } else {
+    /* We have the client already, update the data */
+
+    /* Take hostname out of nick string if it includes it. */
+    if (strchr(nickname, '@')) {
+      int len = strcspn(nickname, "@");
+      nick = silc_calloc(len + 1, sizeof(char));
+      memcpy(nick, nickname, len);
+    } else {
+      nick = strdup(nickname);
+    }
+
+    if (client->nickname)
+      silc_free(client->nickname);
+    if (client->username)
+      silc_free(client->username);
+    
+    client->nickname = nick;
+    client->username = strdup(username);
+
+    if (cache) {
+      cache->data = nick;
+      cache->data_len = strlen(nick);
+      silc_idcache_sort_by_data(global ? server->global_list->clients : 
+                               server->local_list->clients);
+    }
+  }
+
+  silc_free(client_id);
+
+  return TRUE;
+}
+
+/* Received reply for WHOWAS command. Cache the client information only for
+   a short period of time. */
+
+SILC_SERVER_CMD_REPLY_FUNC(whowas)
+{
+  SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
+  SilcCommandStatus status;
+
+  COMMAND_CHECK_STATUS_LIST;
+
+  if (!silc_server_command_reply_whowas_save(cmd))
+    goto out;
+
+  /* Execute any pending commands */
+  SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_WHOWAS);
 
  out:
+  SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_WHOWAS);
   silc_server_command_reply_free(cmd);
 }
 
+/* Caches the received IDENTIFY information. */
+
+static char
+silc_server_command_reply_identify_save(SilcServerCommandReplyContext cmd)
+{
+  SilcServer server = cmd->server;
+  int len, id_len;
+  unsigned char *id_data;
+  char *nickname, *username;
+  SilcClientID *client_id;
+  SilcClientEntry client;
+  SilcIDCacheEntry cache = NULL;
+  char global = FALSE;
+  char *nick = NULL;
+
+  id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len);
+  nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
+  username = silc_argument_get_arg_type(cmd->args, 4, &len);
+  if (!id_data)
+    return FALSE;
+
+  client_id = silc_id_payload_parse_id(id_data, id_len);
+  if (!client_id)
+    return FALSE;
+
+  /* Check if we have this client cached already. */
+
+  client = silc_idlist_find_client_by_id(server->local_list, client_id,
+                                        &cache);
+  if (!client) {
+    client = silc_idlist_find_client_by_id(server->global_list, 
+                                          client_id, &cache);
+    global = TRUE;
+  }
+
+  if (!client) {
+    /* If router did not find such Client ID in its lists then this must
+       be bogus client or some router in the net is buggy. */
+    if (server->server_type == SILC_ROUTER)
+      return FALSE;
+
+    /* Take hostname out of nick string if it includes it. */
+    if (nickname) {
+      if (strchr(nickname, '@')) {
+       int len = strcspn(nickname, "@");
+       nick = silc_calloc(len + 1, sizeof(char));
+       memcpy(nick, nickname, len);
+      } else {
+       nick = strdup(nickname);
+      }
+    }
+
+    /* We don't have that client anywhere, add it. The client is added
+       to global list since server didn't have it in the lists so it must be 
+       global. */
+    client = silc_idlist_add_client(server->global_list, nick, strlen(nick),
+                                   username ? strdup(username) : NULL, NULL,
+                                   client_id, cmd->sock->user_data, NULL);
+    client->data.registered = TRUE;
+  } else {
+    /* We have the client already, update the data */
+
+    SILC_LOG_DEBUG(("Updating client data"));
+
+    /* Take hostname out of nick string if it includes it. */
+    if (nickname) {
+      if (strchr(nickname, '@')) {
+       int len = strcspn(nickname, "@");
+       nick = silc_calloc(len + 1, sizeof(char));
+       memcpy(nick, nickname, len);
+      } else {
+       nick = strdup(nickname);
+      }
+    }
+
+    if (nickname && client->nickname)
+      silc_free(client->nickname);
+
+    if (nickname)
+      client->nickname = nick;
+
+    if (username && client->username) {
+      silc_free(client->username);
+      client->username = strdup(username);
+    }
+
+    if (nickname && cache) {
+      cache->data = nick;
+      cache->data_len = strlen(nick);
+      silc_idcache_sort_by_data(global ? server->global_list->clients : 
+                               server->local_list->clients);
+    }
+
+    silc_free(client_id);
+  }
+
+  return TRUE;
+}
+
 /* Received reply for forwarded IDENTIFY command. We have received the
    requested identify information now and we will cache it. After this we
    will call the pending command so that the requestee gets the information
@@ -188,52 +485,123 @@ SILC_SERVER_CMD_REPLY_FUNC(whois)
 SILC_SERVER_CMD_REPLY_FUNC(identify)
 {
   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
-  SilcServer server = cmd->server;
   SilcCommandStatus status;
 
-  SILC_LOG_DEBUG(("Start"));
-
   COMMAND_CHECK_STATUS_LIST;
 
-  /* Process one identify reply */
-  if (status == SILC_STATUS_OK) {
-    SilcClientID *client_id;
-    unsigned int len;
-    unsigned char *id_data;
-    char *nickname, *username;
+  if (!silc_server_command_reply_identify_save(cmd))
+    goto out;
 
-    id_data = silc_argument_get_arg_type(cmd->args, 2, &len);
-    nickname = silc_argument_get_arg_type(cmd->args, 3, NULL);
-    if (!id_data || !nickname)
-      goto out;
+  /* Execute any pending commands */
+  SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_IDENTIFY);
 
-    username = silc_argument_get_arg_type(cmd->args, 4, NULL);
-    client_id = silc_id_payload_parse_id(id_data, len);
+ out:
+  SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_IDENTIFY);
+  silc_server_command_reply_free(cmd);
+}
 
-    /* Add the client always to our global list. If normal or router server
-       ever gets here it means they don't have this client's information
-       in their cache. */
-    silc_idlist_add_client(server->global_list, strdup(nickname),
-                          username, NULL, client_id, NULL, NULL);
-  }
+/* Received reply fro INFO command. Cache the server and its information */
 
-  if (status == SILC_STATUS_LIST_START) {
+SILC_SERVER_CMD_REPLY_FUNC(info)
+{
+  SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
+  SilcServer server = cmd->server;
+  SilcCommandStatus status;
+  SilcServerEntry entry;
+  SilcServerID *server_id;
+  unsigned int tmp_len;
+  unsigned char *tmp, *name;
 
-  }
+  COMMAND_CHECK_STATUS;
+
+  /* Get Server ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!tmp)
+    goto out;
+  server_id = silc_id_payload_parse_id(tmp, tmp_len);
+  if (!server_id)
+    goto out;
 
-  if (status == SILC_STATUS_LIST_ITEM) {
+  /* Get the name */
+  name = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+  if (tmp_len > 256)
+    goto out;
 
+  entry = silc_idlist_find_server_by_id(server->local_list, server_id, NULL);
+  if (!entry) {
+    entry = silc_idlist_find_server_by_id(server->global_list, server_id, 
+                                         NULL);
+    if (!entry) {
+      /* Add the server to global list */
+      server_id = silc_id_dup(server_id, SILC_ID_SERVER);
+      entry = silc_idlist_add_server(server->global_list, name, 0,
+                                    server_id, NULL, NULL);
+      if (!entry) {
+       silc_free(server_id);
+       goto out;
+      }
+    }
   }
 
-  if (status == SILC_STATUS_LIST_END) {
+  /* Get the info string */
+  tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
+  if (tmp_len > 256)
+    tmp = NULL;
+
+  entry->server_info = tmp ? strdup(tmp) : NULL;
+
+  /* Execute any pending commands */
+  SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_INFO);
 
+ out:
+  SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_INFO);
+  silc_server_command_reply_free(cmd);
+}
+
+/* Received reply fro MOTD command. */
+
+SILC_SERVER_CMD_REPLY_FUNC(motd)
+{
+  SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
+  SilcServer server = cmd->server;
+  SilcCommandStatus status;
+  SilcServerEntry entry;
+  SilcServerID *server_id;
+  unsigned int tmp_len;
+  unsigned char *tmp;
+
+  COMMAND_CHECK_STATUS;
+
+  /* Get Server ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!tmp)
+    goto out;
+  server_id = silc_id_payload_parse_id(tmp, tmp_len);
+  if (!server_id)
+    goto out;
+
+  entry = silc_idlist_find_server_by_id(server->local_list, server_id, NULL);
+  if (!entry) {
+    entry = silc_idlist_find_server_by_id(server->global_list, server_id, 
+                                         NULL);
+    if (!entry)
+      goto out;
   }
 
-  /* Execute pending IDENTIFY command so that the client who originally
-     requested the identify information will get it after all. */
-  SILC_SERVER_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_IDENTIFY);
+  /* Get the motd */
+  tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+  if (tmp_len > 256)
+    tmp = NULL;
+
+  entry->motd = tmp;
+
+  /* Execute any pending commands */
+  SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_MOTD);
+
+  entry->motd = NULL;
 
  out:
+  SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_MOTD);
   silc_server_command_reply_free(cmd);
 }
 
@@ -245,46 +613,272 @@ SILC_SERVER_CMD_REPLY_FUNC(join)
 {
   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
   SilcServer server = cmd->server;
+  SilcIDCacheEntry cache = NULL;
   SilcCommandStatus status;
   SilcChannelID *id;
+  SilcClientID *client_id = NULL;
   SilcChannelEntry entry;
-  unsigned int len;
+  SilcHmac hmac = NULL;
+  unsigned int id_len, len, list_count;
   unsigned char *id_string;
   char *channel_name, *tmp;
-
-  SILC_LOG_DEBUG(("Start"));
+  unsigned int mode, created;
+  SilcBuffer keyp = NULL, client_id_list, client_mode_list;
 
   COMMAND_CHECK_STATUS;
 
   /* Get channel name */
-  tmp = silc_argument_get_arg_type(cmd->args, 2, NULL);
-  if (!tmp)
+  channel_name = silc_argument_get_arg_type(cmd->args, 2, NULL);
+  if (!channel_name)
     goto out;
 
   /* Get channel ID */
-  id_string = silc_argument_get_arg_type(cmd->args, 3, &len);
+  id_string = silc_argument_get_arg_type(cmd->args, 3, &id_len);
   if (!id_string)
     goto out;
 
-  channel_name = strdup(tmp);
+  /* Get client ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 4, &len);
+  if (!tmp)
+    goto out;
+  client_id = silc_id_payload_parse_id(tmp, len);
+  if (!client_id)
+    goto out;
+
+  /* Get mode mask */
+  tmp = silc_argument_get_arg_type(cmd->args, 5, NULL);
+  if (!tmp)
+    goto out;
+  SILC_GET32_MSB(mode, tmp);
+
+  /* Get created boolean value */
+  tmp = silc_argument_get_arg_type(cmd->args, 6, NULL);
+  if (!tmp)
+    goto out;
+  SILC_GET32_MSB(created, tmp);
+  if (created != 0 && created != 1)
+    goto out;
+
+  /* Get channel key */
+  tmp = silc_argument_get_arg_type(cmd->args, 7, &len);
+  if (tmp) {
+    keyp = silc_buffer_alloc(len);
+    silc_buffer_pull_tail(keyp, SILC_BUFFER_END(keyp));
+    silc_buffer_put(keyp, tmp, len);
+  }
+
+  id = silc_id_payload_parse_id(id_string, id_len);
+  if (!id)
+    goto out;
+
+  /* Get hmac */
+  tmp = silc_argument_get_arg_type(cmd->args, 11, NULL);
+  if (tmp) {
+    if (!silc_hmac_alloc(tmp, NULL, &hmac))
+      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 the channel to our local list. */
-  id = silc_id_payload_parse_id(id_string, len);
-  entry = silc_idlist_add_channel(server->local_list, channel_name, 
-                                 SILC_CHANNEL_MODE_NONE, id, 
-                                 server->id_entry->router, NULL);
+  /* See whether we already have the channel. */
+  entry = silc_idlist_find_channel_by_name(server->local_list, 
+                                          channel_name, &cache);
   if (!entry) {
-    silc_free(channel_name);
-    silc_free(id);
+    /* Add new channel */
+
+    SILC_LOG_DEBUG(("Adding new [%s] channel %s id(%s)", 
+                   (created == 0 ? "existing" : "created"), channel_name,
+                   silc_id_render(id, SILC_ID_CHANNEL)));
+
+    /* Add the channel to our local list. */
+    entry = silc_idlist_add_channel(server->local_list, strdup(channel_name), 
+                                   SILC_CHANNEL_MODE_NONE, id, 
+                                   server->router, NULL, hmac);
+    if (!entry) {
+      silc_free(id);
+      goto out;
+    }
+  } else {
+    /* The entry exists. */
+    if (entry->id)
+      silc_free(entry->id);
+    entry->id = id;
+    cache->id = entry->id;
+
+    /* Remove the founder auth data if the mode is not set but we have
+       them in the entry */
+    if (!(mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) && entry->founder_key) {
+      silc_pkcs_public_key_free(entry->founder_key);
+      if (entry->founder_passwd) {
+       silc_free(entry->founder_passwd);
+       entry->founder_passwd = NULL;
+      }
+    }
+  }
+
+  if (entry->hmac_name && hmac) {
+    silc_free(entry->hmac_name);
+    entry->hmac_name = strdup(hmac->hmac->name);
+  }
+
+  /* Get the ban list */
+  tmp = silc_argument_get_arg_type(cmd->args, 8, &len);
+  if (tmp) {
+    if (entry->ban_list)
+      silc_free(entry->ban_list);
+    entry->ban_list = silc_calloc(len, sizeof(*entry->ban_list));
+    memcpy(entry->ban_list, tmp, len);
+  }
+
+  /* Get the invite list */
+  tmp = silc_argument_get_arg_type(cmd->args, 9, &len);
+  if (tmp) {
+    if (entry->invite_list)
+      silc_free(entry->invite_list);
+    entry->invite_list = silc_calloc(len, sizeof(*entry->invite_list));
+    memcpy(entry->invite_list, tmp, len);
+  }
+
+  /* Get the topic */
+  tmp = silc_argument_get_arg_type(cmd->args, 10, &len);
+  if (tmp) {
+    if (entry->topic)
+      silc_free(entry->topic);
+    entry->topic = strdup(tmp);
+  }
+
+  /* If channel was not created we know there is global users on the 
+     channel. */
+  entry->global_users = (created == 0 ? TRUE : FALSE);
+
+  /* If channel was just created the mask must be zero */
+  if (!entry->global_users && mode) {
+    SILC_LOG_DEBUG(("Buggy router `%s' sent non-zero mode mask for "
+                   "new channel, forcing it to zero", cmd->sock->hostname));
+    mode = 0;
+  }
+
+  /* Save channel mode */
+  entry->mode = mode;
+
+  /* Save channel key */
+  if (!(entry->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
+    silc_server_save_channel_key(server, keyp, entry);
+  }
+  if (keyp)
+    silc_buffer_free(keyp);
+
+  /* Save the users to the channel */
+  silc_server_save_users_on_channel(server, cmd->sock, entry, 
+                                   client_id, client_id_list,
+                                   client_mode_list, list_count);
+
+  silc_buffer_free(client_id_list);
+  silc_buffer_free(client_mode_list);
+
+  /* Execute any pending commands */
+  SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_JOIN);
+
+ out:
+  SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_JOIN);
+  if (client_id)
+    silc_free(client_id);
+  silc_server_command_reply_free(cmd);
+}
+
+SILC_SERVER_CMD_REPLY_FUNC(users)
+{
+  SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
+  SilcServer server = cmd->server;
+  SilcCommandStatus status;
+  SilcChannelEntry channel;
+  SilcChannelID *channel_id = NULL;
+  SilcBuffer client_id_list;
+  SilcBuffer client_mode_list;
+  unsigned char *tmp;
+  unsigned int tmp_len;
+  unsigned int list_count;
+
+  COMMAND_CHECK_STATUS;
+
+  /* Get channel ID */
+  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;
+
+  /* Get channel entry */
+  channel = silc_idlist_find_channel_by_id(server->local_list, 
+                                          channel_id, NULL);
+  if (!channel) {
+    channel = silc_idlist_find_channel_by_id(server->global_list, 
+                                            channel_id, NULL);
+    if (!channel)
+      goto out;
   }
 
-  entry->global_users = TRUE;
+  /* 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, &tmp_len);
+  if (!tmp)
+    goto out;
+
+  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, &tmp_len);
+  if (!tmp)
+    goto out;
+
+  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);
+
+  /* Save the users to the channel */
+  silc_server_save_users_on_channel(server, cmd->sock, channel, NULL,
+                                   client_id_list, client_mode_list, 
+                                   list_count);
+
+  silc_buffer_free(client_id_list);
+  silc_buffer_free(client_mode_list);
 
-  /* Execute pending JOIN command so that the client who originally
-     wanted to join the channel will be joined after all. */
-  SILC_SERVER_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_JOIN);
+  /* Execute any pending commands */
+  SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_USERS);
 
  out:
+  SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_USERS);
+  if (channel_id)
+    silc_free(channel_id);
   silc_server_command_reply_free(cmd);
 }