More client library rewrites.
[silc.git] / lib / silcclient / command_reply.c
index 0fd7990366b3d69fb79c74154dc7b6dee2438617..f5adc9c231b95e6ed856c9d0e55a6fc7033ce092 100644 (file)
@@ -32,20 +32,22 @@ do {                                                                \
     silc_status_get_args(cmd->status, args, &arg1, &arg2);     \
   else                                                         \
     cmd->status = error;                                       \
-   silc_client_command_callback(cmd, arg1, arg2);              \
+  SILC_LOG_DEBUG(("Error in command reply: %s",                        \
+                silc_get_status_message(cmd->status)));        \
+  silc_client_command_callback(cmd, arg1, arg2);               \
 } while(0)
 
 /* Check for error */
 #define CHECK_STATUS(msg)                                              \
-  SILC_LOG_DEBUG(("Start"));                                           \
+  SILC_LOG_DEBUG(("%s", silc_get_command_name(cmd->cmd)));             \
   if (cmd->error != SILC_STATUS_OK) {                                  \
     if (cmd->verbose)                                                  \
       SAY(cmd->conn->client, cmd->conn, SILC_CLIENT_MESSAGE_ERROR,     \
          msg "%s", silc_get_status_message(cmd->error));               \
     ERROR_CALLBACK(cmd->error);                                                \
     silc_client_command_process_error(cmd, state_context, cmd->error); \
-    silc_fsm_next(fsm, silc_client_command_reply_process);             \
-    return SILC_FSM_YIELD;                                             \
+    silc_fsm_next(fsm, silc_client_command_reply_processed);           \
+    return SILC_FSM_CONTINUE;                                          \
   }
 
 /* Check for correct arguments */
@@ -53,8 +55,8 @@ do {                                                          \
   if (silc_argument_get_arg_num(args) < min ||                 \
       silc_argument_get_arg_num(args) > max) {                 \
     ERROR_CALLBACK(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);         \
-    silc_fsm_next(fsm, silc_client_command_reply_process);     \
-    return SILC_FSM_YIELD;                                     \
+    silc_fsm_next(fsm, silc_client_command_reply_processed);   \
+    return SILC_FSM_CONTINUE;                                  \
   }
 
 #define SAY cmd->conn->client->internal->ops->say
@@ -85,8 +87,8 @@ silc_client_command_callback(SilcClientCommandContext cmd, ...)
   while ((cb = silc_list_get(cmd->reply_callbacks)))
     if (!cb->do_not_call) {
       silc_va_copy(cp, ap);
-      cb->do_not_call = cb->reply(cmd->conn->client, cmd->conn, cmd->cmd,
-                                 cmd->status, cmd->error, cb->context, cp);
+      cb->do_not_call = !cb->reply(cmd->conn->client, cmd->conn, cmd->cmd,
+                                  cmd->status, cmd->error, cb->context, cp);
       va_end(cp);
     }
 
@@ -111,8 +113,10 @@ static void silc_client_command_process_error(SilcClientCommandContext cmd,
       return;
 
     client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (client_entry)
+    if (client_entry) {
+      silc_client_unref_client(client, conn, client_entry);
       silc_client_del_client(client, conn, client_entry);
+    }
   }
 }
 
@@ -133,10 +137,8 @@ SILC_FSM_STATE(silc_client_command_reply)
   payload = silc_command_payload_parse(silc_buffer_datalen(&packet->buffer));
   silc_packet_free(packet);
   if (!payload) {
-    /** Bad reply payload */
     SILC_LOG_DEBUG(("Bad command reply packet"));
-    silc_fsm_next(fsm, silc_client_connection_st_packet);
-    return SILC_FSM_CONTINUE;
+    return SILC_FSM_FINISH;
   }
 
   cmd_ident = silc_command_get_ident(payload);
@@ -145,17 +147,20 @@ SILC_FSM_STATE(silc_client_command_reply)
   /* Find the command pending reply */
   silc_mutex_lock(conn->internal->lock);
   silc_list_start(conn->internal->pending_commands);
-  while ((cmd = silc_list_get(conn->internal->pending_commands)))
-    if (cmd->cmd == command && cmd->cmd_ident == cmd_ident)
+  while ((cmd = silc_list_get(conn->internal->pending_commands))) {
+    if ((cmd->cmd == command || cmd->cmd == SILC_COMMAND_NONE)
+       && cmd->cmd_ident == cmd_ident) {
+      silc_list_del(conn->internal->pending_commands, cmd);
       break;
+    }
+  }
   silc_mutex_unlock(conn->internal->lock);
 
   if (!cmd) {
-    /** Unknown command reply */
-    SILC_LOG_DEBUG(("Unknown command reply"));
+    SILC_LOG_DEBUG(("Unknown command reply %s, ident %d",
+                   silc_get_command_name(command), cmd_ident));
     silc_command_payload_free(payload);
-    silc_fsm_next(fsm, silc_client_connection_st_packet);
-    return SILC_FSM_CONTINUE;
+    return SILC_FSM_FINISH;
   }
 
   /* Signal command thread that command reply has arrived */
@@ -163,56 +168,45 @@ SILC_FSM_STATE(silc_client_command_reply)
   silc_fsm_next(&cmd->thread, silc_client_command_reply_process);
   silc_fsm_continue_sync(&cmd->thread);
 
-  /** Packet processed */
-  silc_fsm_next(fsm, silc_client_connection_st_packet);
-  return SILC_FSM_CONTINUE;
+  return SILC_FSM_FINISH;
 }
 
 /* Wait here for command reply to arrive from remote host */
 
 SILC_FSM_STATE(silc_client_command_reply_wait)
 {
-  SilcClientCommandContext cmd = fsm_context;
-
   SILC_LOG_DEBUG(("Wait for command reply"));
 
   /** Wait for command reply */
-  cmd->processed = FALSE;
   silc_fsm_set_state_context(fsm, NULL);
-  silc_fsm_next_later(fsm, silc_client_command_reply_process, 20, 0);
+  silc_fsm_next_later(fsm, silc_client_command_reply_timeout, 20, 0);
   return SILC_FSM_WAIT;
 }
 
-/* Process received command reply payload */
+/* Timeout occurred while waiting command reply */
 
-SILC_FSM_STATE(silc_client_command_reply_process)
+SILC_FSM_STATE(silc_client_command_reply_timeout)
 {
   SilcClientCommandContext cmd = fsm_context;
-  SilcCommandPayload payload = state_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcArgumentPayload args = NULL;
 
-  if (!payload) {
-    /* Timeout, reply not received in timely fashion */
-    SilcArgumentPayload args = NULL;
-    ERROR_CALLBACK(SILC_STATUS_ERR_TIMEDOUT);
-    return SILC_FSM_FINISH;
-  }
+  SILC_LOG_DEBUG(("Command %s timeout", silc_get_command_name(cmd->cmd)));
 
-  if (cmd->processed) {
-    /* Command reply processed */
-    silc_command_payload_free(payload);
+  /* Timeout, reply not received in timely fashion */
+  silc_list_del(conn->internal->pending_commands, cmd);
+  ERROR_CALLBACK(SILC_STATUS_ERR_TIMEDOUT);
+  return SILC_FSM_FINISH;
+}
 
-    if (cmd->status == SILC_STATUS_OK || cmd->status == SILC_STATUS_LIST_END ||
-       SILC_STATUS_IS_ERROR(cmd->status))
-      return SILC_FSM_FINISH;
+/* Process received command reply payload */
 
-    /** Wait more command payloads */
-    silc_fsm_next(fsm, silc_client_command_reply_wait);
-    return SILC_FSM_CONTINUE;
-  }
+SILC_FSM_STATE(silc_client_command_reply_process)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcCommandPayload payload = state_context;
 
   silc_command_get_status(payload, &cmd->status, &cmd->error);
-  silc_fsm_set_state_context(fsm, payload);
-  cmd->processed = TRUE;
 
   switch (cmd->cmd) {
   case SILC_COMMAND_WHOIS:
@@ -288,7 +282,7 @@ SILC_FSM_STATE(silc_client_command_reply_process)
     silc_fsm_next(fsm, silc_client_command_reply_cumode);
     break;
   case SILC_COMMAND_KICK:
-    /** kick */
+    /** KICK */
     silc_fsm_next(fsm, silc_client_command_reply_kick);
     break;
   case SILC_COMMAND_BAN:
@@ -330,6 +324,31 @@ SILC_FSM_STATE(silc_client_command_reply_process)
   return SILC_FSM_CONTINUE;
 }
 
+/* Completes command reply processing */
+
+SILC_FSM_STATE(silc_client_command_reply_processed)
+{
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+  SilcCommandPayload payload = state_context;
+
+  silc_command_payload_free(payload);
+
+  if (cmd->status == SILC_STATUS_OK || cmd->status == SILC_STATUS_LIST_END ||
+      SILC_STATUS_IS_ERROR(cmd->status))
+    return SILC_FSM_FINISH;
+
+  /* Add back to pending command reply list */
+  silc_mutex_lock(conn->internal->lock);
+  cmd->resolved = FALSE;
+  silc_list_add(conn->internal->pending_commands, cmd);
+  silc_mutex_unlock(conn->internal->lock);
+
+  /** Wait more command payloads */
+  silc_fsm_next(fsm, silc_client_command_reply_wait);
+  return SILC_FSM_CONTINUE;
+}
+
 /******************************** WHOIS *************************************/
 
 /* Received reply for WHOIS command. */
@@ -408,6 +427,7 @@ SILC_FSM_STATE(silc_client_command_reply_whois)
       ERROR_CALLBACK(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
       goto out;
     }
+    silc_client_ref_client(client, conn, client_entry);
   } else {
     silc_client_update_client(client, conn, client_entry,
                              nickname, username, realname, mode);
@@ -438,13 +458,14 @@ SILC_FSM_STATE(silc_client_command_reply_whois)
                               realname, channel_list, mode, idle, fingerprint,
                               umodes, client_entry->attrs);
 
+  silc_client_unref_client(client, conn, client_entry);
   if (has_channels) {
     silc_dlist_uninit(channel_list);
     silc_free(umodes);
   }
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -459,7 +480,7 @@ SILC_FSM_STATE(silc_client_command_reply_whowas)
   SilcClient client = conn->client;
   SilcCommandPayload payload = state_context;
   SilcArgumentPayload args = silc_command_get_args(payload);
-  SilcClientEntry client_entry;
+  SilcClientEntry client_entry = NULL;
   SilcID id;
   char *nickname, *username;
   char *realname = NULL;
@@ -491,7 +512,8 @@ SILC_FSM_STATE(silc_client_command_reply_whowas)
                               realname);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_client_unref_client(client, conn, client_entry);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -541,6 +563,7 @@ SILC_FSM_STATE(silc_client_command_reply_identify)
        ERROR_CALLBACK(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
        goto out;
       }
+      silc_client_ref_client(client, conn, client_entry);
     } else {
       silc_client_update_client(client, conn, client_entry,
                                name, info, NULL, 0);
@@ -548,6 +571,7 @@ SILC_FSM_STATE(silc_client_command_reply_identify)
 
     /* Notify application */
     silc_client_command_callback(cmd, client_entry, name, info);
+    silc_client_unref_client(client, conn, client_entry);
     break;
 
   case SILC_ID_SERVER:
@@ -563,13 +587,15 @@ SILC_FSM_STATE(silc_client_command_reply_identify)
        ERROR_CALLBACK(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
        goto out;
       }
+      silc_client_ref_server(client, conn, server_entry);
     } else {
       silc_client_update_server(client, conn, server_entry, name, info);
     }
-    server_entry->resolve_cmd_ident = 0;
+    server_entry->internal.resolve_cmd_ident = 0;
 
     /* Notify application */
     silc_client_command_callback(cmd, server_entry, name, info);
+    silc_client_unref_server(client, conn, server_entry);
     break;
 
   case SILC_ID_CHANNEL:
@@ -593,15 +619,17 @@ SILC_FSM_STATE(silc_client_command_reply_identify)
        ERROR_CALLBACK(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
        goto out;
       }
+      silc_client_ref_channel(client, conn, channel_entry);
     }
 
     /* Notify application */
     silc_client_command_callback(cmd, channel_entry, name, info);
+    silc_client_unref_channel(client, conn, channel_entry);
     break;
   }
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -653,28 +681,30 @@ SILC_FSM_STATE(silc_client_command_reply_nick)
   }
 
   /* Update the client entry */
+  silc_mutex_lock(conn->internal->lock);
   if (!silc_idcache_update(conn->internal->client_cache,
                           conn->internal->local_entry,
-                          &conn->local_entry->id,
-                          &id.u.client_id,
-                          conn->local_entry->nickname_normalized,
-                          tmp, TRUE)) {
+                          &id.u.client_id, tmp, TRUE)) {
     silc_free(tmp);
+    silc_mutex_unlock(conn->internal->lock);
     ERROR_CALLBACK(SILC_STATUS_ERR_BAD_NICKNAME);
     goto out;
   }
-  memcpy(conn->local_entry->nickname, nick, strlen(nick));
+  silc_mutex_unlock(conn->internal->lock);
+  memset(conn->local_entry->nickname, 0, sizeof(conn->local_entry->nickname));
+  memcpy(conn->local_entry->nickname, nick, len);
   conn->local_entry->nickname_normalized = tmp;
-  silc_buffer_enlarge(conn->local_idp, idp_len);
-  silc_buffer_put(conn->local_idp, idp, idp_len);
+  silc_buffer_enlarge(conn->internal->local_idp, idp_len);
+  silc_buffer_put(conn->internal->local_idp, idp, idp_len);
   silc_client_nickname_format(client, conn, conn->local_entry);
+  silc_packet_set_ids(conn->stream, SILC_ID_CLIENT, conn->local_id, 0, NULL);
 
   /* Notify application */
   silc_client_command_callback(cmd, conn->local_entry,
                               conn->local_entry->nickname, &old_client_id);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -691,7 +721,7 @@ SILC_FSM_STATE(silc_client_command_reply_list)
   SilcArgumentPayload args = silc_command_get_args(payload);
   unsigned char *tmp, *name, *topic;
   SilcUInt32 usercount = 0;
-  SilcChannelEntry channel_entry;
+  SilcChannelEntry channel_entry = NULL;
   SilcID id;
 
   /* Sanity checks */
@@ -700,7 +730,7 @@ SILC_FSM_STATE(silc_client_command_reply_list)
   if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id, NULL)) {
     /* There were no channels in the network. */
     silc_client_command_callback(cmd, NULL, NULL, NULL, 0);
-    silc_fsm_next(fsm, silc_client_command_reply_process);
+    silc_fsm_next(fsm, silc_client_command_reply_processed);
     return SILC_FSM_CONTINUE;
   }
 
@@ -728,13 +758,15 @@ SILC_FSM_STATE(silc_client_command_reply_list)
       ERROR_CALLBACK(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
       goto out;
     }
+    silc_client_ref_channel(client, conn, channel_entry);
   }
 
   /* Notify application */
   silc_client_command_callback(cmd, channel_entry, name, topic, usercount);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_client_unref_channel(client, conn, channel_entry);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -782,7 +814,7 @@ SILC_FSM_STATE(silc_client_command_reply_topic)
   silc_client_command_callback(cmd, channel, channel->topic);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -800,7 +832,7 @@ SILC_FSM_STATE(silc_client_command_reply_invite)
   SilcChannelEntry channel;
   unsigned char *tmp;
   SilcUInt32 len;
-  SilcBufferStruct buf;
+  SilcArgumentPayload invite_args = NULL;
   SilcID id;
 
   /* Sanity checks */
@@ -823,13 +855,16 @@ SILC_FSM_STATE(silc_client_command_reply_invite)
   /* Get the invite list */
   tmp = silc_argument_get_arg_type(args, 3, &len);
   if (tmp)
-    silc_buffer_set(&buf, tmp, len);
+    invite_args = silc_argument_list_parse(tmp, len);
 
   /* Notify application */
-  silc_client_command_callback(cmd, channel, tmp ? &buf : NULL);
+  silc_client_command_callback(cmd, channel, invite_args);
+
+  if (invite_args)
+    silc_argument_payload_free(invite_args);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -863,11 +898,13 @@ SILC_FSM_STATE(silc_client_command_reply_kill)
   silc_client_command_callback(cmd, client_entry);
 
   /* Remove the client from all channels and free it */
-  if (client_entry)
+  if (client_entry) {
     silc_client_del_client(client, conn, client_entry);
+    silc_client_unref_client(client, conn, client_entry);
+  }
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -914,19 +951,21 @@ SILC_FSM_STATE(silc_client_command_reply_info)
   /* See whether we have this server cached. If not create it. */
   server = silc_client_get_server_by_id(client, conn, &id.u.server_id);
   if (!server) {
-    SILC_LOG_DEBUG(("New server entry"));
+    SILC_LOG_DEBUG(("Add new server entry (INFO)"));
     server = silc_client_add_server(client, conn, server_name,
                                    server_info, &id.u.server_id);
     if (!server)
       goto out;
+    silc_client_ref_server(client, conn, server);
   }
 
   /* Notify application */
   silc_client_command_callback(cmd, server, server->server_name,
                               server->server_info);
+  silc_client_unref_server(client, conn, server);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -983,7 +1022,7 @@ SILC_FSM_STATE(silc_client_command_reply_stats)
   silc_client_command_callback(cmd, &stats);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -999,19 +1038,39 @@ SILC_FSM_STATE(silc_client_command_reply_ping)
   SilcInt64 diff;
 
   diff = silc_time() - SILC_PTR_TO_64(cmd->context);
-  SAY(client, conn, SILC_CLIENT_MESSAGE_INFO,
-      "Ping reply from %s: %d second%s", conn->remote_host,
-      (int)diff, diff == 1 ? "" : "s");
+  if (cmd->verbose)
+    SAY(client, conn, SILC_CLIENT_MESSAGE_INFO,
+       "Ping reply from %s: %d second%s", conn->remote_host,
+       (int)diff, diff == 1 ? "" : "s");
 
   /* Notify application */
   silc_client_command_callback(cmd);
 
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
 /********************************** JOIN ************************************/
 
+/* Continue JOIN command reply processing after resolving unknown users */
+
+static void
+silc_client_command_reply_join_resolved(SilcClient client,
+                                       SilcClientConnection conn,
+                                       SilcStatus status,
+                                       SilcDList clients,
+                                       void *context)
+{
+  SilcClientCommandContext cmd = context;
+  SilcChannelEntry channel = cmd->context;
+
+  channel->internal.resolve_cmd_ident = 0;
+  silc_client_unref_channel(client, conn, channel);
+
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
+}
+
+
 /* Received reply for JOIN command. */
 
 SILC_FSM_STATE(silc_client_command_reply_join)
@@ -1022,11 +1081,12 @@ SILC_FSM_STATE(silc_client_command_reply_join)
   SilcCommandPayload payload = state_context;
   SilcArgumentPayload args = silc_command_get_args(payload);
   SilcChannelEntry channel;
-  SilcChannelUser chu;
   SilcUInt32 mode = 0, len, list_count;
   char *topic, *tmp, *channel_name = NULL, *hmac;
-  SilcBuffer keyp = NULL, client_id_list = NULL, client_mode_list = NULL;
-  SilcBufferStruct chpklist;
+  const char *cipher;
+  SilcBufferStruct client_id_list, client_mode_list, keyp;
+  SilcHashTableList htl;
+  SilcDList chpks = NULL;
   SilcID id;
   int i;
 
@@ -1047,26 +1107,10 @@ SILC_FSM_STATE(silc_client_command_reply_join)
     goto out;
   }
 
-  /* Get channel mode */
-  tmp = silc_argument_get_arg_type(args, 5, NULL);
-  if (tmp)
-    SILC_GET32_MSB(mode, tmp);
-
-  /* Get channel key */
-  tmp = silc_argument_get_arg_type(args, 7, &len);
-  if (tmp) {
-    keyp = silc_buffer_alloc_size(len);
-    if (keyp)
-      silc_buffer_put(keyp, tmp, len);
-  }
-
-  /* Get topic */
-  topic = silc_argument_get_arg_type(args, 10, NULL);
-
   /* Check whether we have this channel entry already. */
   channel = silc_client_get_channel(client, conn, channel_name);
   if (channel) {
-    if (!SILC_ID_CHANNEL_COMPARE(channel->id, &id.u.channel_id))
+    if (!SILC_ID_CHANNEL_COMPARE(&channel->id, &id.u.channel_id))
       silc_client_replace_channel_id(client, conn, channel, &id.u.channel_id);
   } else {
     /* Create new channel entry */
@@ -1076,21 +1120,7 @@ SILC_FSM_STATE(silc_client_command_reply_join)
       ERROR_CALLBACK(SILC_STATUS_ERR_BAD_CHANNEL);
       goto out;
     }
-  }
-
-  conn->current_channel = channel;
-  channel->mode = mode;
-
-  /* Get hmac */
-  hmac = silc_argument_get_arg_type(args, 11, NULL);
-  if (hmac) {
-    if (!silc_hmac_alloc(hmac, NULL, &channel->hmac)) {
-      if (cmd->verbose)
-       SAY(client, conn, SILC_CLIENT_MESSAGE_ERROR,
-           "Cannot join channel: Unsupported HMAC `%s'", hmac);
-      ERROR_CALLBACK(SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
-      goto out;
-    }
+    silc_client_ref_channel(client, conn, channel);
   }
 
   /* Get the list count */
@@ -1107,10 +1137,18 @@ SILC_FSM_STATE(silc_client_command_reply_join)
     ERROR_CALLBACK(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
+  silc_buffer_set(&client_id_list, tmp, len);
 
-  client_id_list = silc_buffer_alloc_size(len);
-  if (client_id_list)
-    silc_buffer_put(client_id_list, tmp, len);
+  /* Resolve users we do not know about */
+  if (!cmd->resolved) {
+    cmd->resolved = TRUE;
+    cmd->context = channel;
+    SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                 silc_client_get_clients_by_list(
+                         client, conn, list_count, &client_id_list,
+                         silc_client_command_reply_join_resolved, cmd));
+    /* NOT REACHED */
+  }
 
   /* Get client mode list */
   tmp = silc_argument_get_arg_type(args, 14, &len);
@@ -1118,10 +1156,7 @@ SILC_FSM_STATE(silc_client_command_reply_join)
     ERROR_CALLBACK(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
     goto out;
   }
-
-  client_mode_list = silc_buffer_alloc_size(len);
-  if (client_mode_list)
-    silc_buffer_put(client_mode_list, tmp, len);
+  silc_buffer_set(&client_mode_list, tmp, len);
 
   /* Add clients we received in the reply to the channel */
   for (i = 0; i < list_count; i++) {
@@ -1131,48 +1166,60 @@ SILC_FSM_STATE(silc_client_command_reply_join)
     SilcClientEntry client_entry;
 
     /* Client ID */
-    SILC_GET16_MSB(idp_len, client_id_list->data + 2);
+    SILC_GET16_MSB(idp_len, client_id_list.data + 2);
     idp_len += 4;
-    if (!silc_id_payload_parse_id(client_id_list->data, idp_len, &id))
-      goto out;
+    if (!silc_id_payload_parse_id(client_id_list.data, idp_len, &id))
+      continue;
 
     /* Mode */
-    SILC_GET32_MSB(mode, client_mode_list->data);
+    SILC_GET32_MSB(mode, client_mode_list.data);
 
-    /* Check if we have this client cached already. */
+    /* Get client entry */
     client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (!client_entry) {
-      /* No, we don't have it, add entry for it. */
-      client_entry =
-       silc_client_add_client(client, conn, NULL, NULL, NULL,
-                              &id.u.client_id, 0);
-      if (!client_entry)
-       goto out;
-    }
+    if (!client_entry)
+      continue;
 
     /* Join client to the channel */
-    if (!silc_client_on_channel(channel, client_entry)) {
-      chu = silc_calloc(1, sizeof(*chu));
-      if (!chu)
-       goto out;
-      chu->client = client_entry;
-      chu->channel = channel;
-      chu->mode = mode;
-      silc_hash_table_add(channel->user_list, client_entry, chu);
-      silc_hash_table_add(client_entry->channels, channel, chu);
+    silc_client_add_to_channel(client, conn, channel, client_entry, mode);
+    silc_client_unref_client(client, conn, client_entry);
+
+    if (!silc_buffer_pull(&client_id_list, idp_len))
+      goto out;
+    if (!silc_buffer_pull(&client_mode_list, 4))
+      goto out;
+  }
+
+  /* Get hmac */
+  hmac = silc_argument_get_arg_type(args, 11, NULL);
+  if (hmac) {
+    if (!silc_hmac_alloc(hmac, NULL, &channel->internal.hmac)) {
+      if (cmd->verbose)
+       SAY(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+           "Cannot join channel: Unsupported HMAC `%s'", hmac);
+      ERROR_CALLBACK(SILC_STATUS_ERR_UNKNOWN_ALGORITHM);
+      goto out;
     }
+  }
+
+  /* Get channel mode */
+  tmp = silc_argument_get_arg_type(args, 5, NULL);
+  if (tmp)
+    SILC_GET32_MSB(mode, tmp);
+  channel->mode = mode;
 
-    silc_buffer_pull(client_id_list, idp_len);
-    silc_buffer_pull(client_mode_list, 4);
+  /* Get channel key and save it */
+  tmp = silc_argument_get_arg_type(args, 7, &len);
+  if (tmp) {
+    silc_buffer_set(&keyp, tmp, len);
+    silc_client_save_channel_key(client, conn, &keyp, channel);
   }
-  silc_buffer_start(client_id_list);
-  silc_buffer_start(client_mode_list);
 
-  /* Save channel key */
-#if 0
-  if (keyp)
-    silc_client_save_channel_key(client, conn, keyp, channel);
-#endif /* 0 */
+  /* Get topic */
+  topic = silc_argument_get_arg_type(args, 10, NULL);
+  if (topic) {
+    silc_free(channel->topic);
+    channel->topic = silc_memdup(topic, strlen(topic));
+  }
 
   /* Get founder key */
   tmp = silc_argument_get_arg_type(args, 15, &len);
@@ -1193,25 +1240,28 @@ SILC_FSM_STATE(silc_client_command_reply_join)
   /* Get channel public key list */
   tmp = silc_argument_get_arg_type(args, 16, &len);
   if (tmp)
-    silc_buffer_set(&chpklist, tmp, len);
+    chpks = silc_argument_list_parse_decoded(tmp, len,
+                                            SILC_ARGUMENT_PUBLIC_KEY);
 
-  if (topic) {
-    silc_free(channel->topic);
-    channel->topic = silc_memdup(topic, strlen(topic));
-  }
+  /* Set current channel */
+  conn->current_channel = channel;
+
+  cipher = (channel->internal.channel_key ?
+           silc_cipher_get_name(channel->internal.channel_key) : NULL);
+  silc_hash_table_list(channel->user_list, &htl);
 
   /* Notify application */
-  silc_client_command_callback(cmd, channel_name, channel, mode, 0,
-                              keyp ? keyp->head : NULL, NULL,
-                              NULL, topic, hmac, list_count, client_id_list,
-                              client_mode_list, channel->founder_key,
-                              tmp ? &chpklist : NULL, channel->user_limit);
+  silc_client_command_callback(cmd, channel_name, channel, mode, &htl,
+                              topic, cipher, hmac, channel->founder_key,
+                              chpks, channel->user_limit);
+
+  if (chpks)
+    silc_argument_list_free(chpks, SILC_ARGUMENT_PUBLIC_KEY);
+  silc_hash_table_list_reset(&htl);
+  silc_client_unref_channel(client, conn, channel);
 
  out:
-  silc_buffer_free(keyp);
-  silc_buffer_free(client_id_list);
-  silc_buffer_free(client_mode_list);
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1265,13 +1315,13 @@ SILC_FSM_STATE(silc_client_command_reply_motd)
   silc_client_command_callback(cmd, motd);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
 /********************************** UMODE ***********************************/
 
-/* Received reply tohe UMODE command. Save the current user mode */
+/* Received reply to the UMODE command. Save the current user mode */
 
 SILC_FSM_STATE(silc_client_command_reply_umode)
 {
@@ -1299,7 +1349,7 @@ SILC_FSM_STATE(silc_client_command_reply_umode)
   silc_client_command_callback(cmd, mode);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1319,7 +1369,7 @@ SILC_FSM_STATE(silc_client_command_reply_cmode)
   SilcChannelEntry channel;
   SilcUInt32 len;
   SilcPublicKey public_key = NULL;
-  SilcBufferStruct channel_pubkeys;
+  SilcDList channel_pubkeys = NULL;
   SilcID id;
 
   /* Sanity checks */
@@ -1365,17 +1415,19 @@ SILC_FSM_STATE(silc_client_command_reply_cmode)
   /* Get channel public key(s) */
   tmp = silc_argument_get_arg_type(args, 5, &len);
   if (tmp)
-    silc_buffer_set(&channel_pubkeys, tmp, len);
+    channel_pubkeys =
+      silc_argument_list_parse_decoded(tmp, len, SILC_ARGUMENT_PUBLIC_KEY);
 
   /* Notify application */
   silc_client_command_callback(cmd, channel, mode, public_key,
-                              tmp ? &channel_pubkeys : NULL,
-                              channel->user_limit);
+                              channel_pubkeys, channel->user_limit);
+
+  silc_argument_list_free(channel_pubkeys, SILC_ARGUMENT_PUBLIC_KEY);
 
  out:
   if (public_key)
     silc_pkcs_public_key_free(public_key);
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1443,8 +1495,10 @@ SILC_FSM_STATE(silc_client_command_reply_cumode)
   /* Notify application */
   silc_client_command_callback(cmd, mode, channel, client_entry);
 
+  silc_client_unref_client(client, conn, client_entry);
+
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1494,8 +1548,10 @@ SILC_FSM_STATE(silc_client_command_reply_kick)
   /* Notify application */
   silc_client_command_callback(cmd, channel, client_entry);
 
+  silc_client_unref_client(client, conn, client_entry);
+
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1514,7 +1570,7 @@ SILC_FSM_STATE(silc_client_command_reply_silcoper)
   /* Notify application */
   silc_client_command_callback(cmd);
 
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1533,7 +1589,7 @@ SILC_FSM_STATE(silc_client_command_reply_oper)
   /* Notify application */
   silc_client_command_callback(cmd);
 
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1566,7 +1622,7 @@ SILC_FSM_STATE(silc_client_command_reply_detach)
   }
 #endif /* 0 */
 
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1585,7 +1641,7 @@ SILC_FSM_STATE(silc_client_command_reply_watch)
   /* Notify application */
   silc_client_command_callback(cmd);
 
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1601,7 +1657,7 @@ SILC_FSM_STATE(silc_client_command_reply_ban)
   SilcChannelEntry channel;
   unsigned char *tmp;
   SilcUInt32 len;
-  SilcBufferStruct buf;
+  SilcArgumentPayload invite_args = NULL;
   SilcID id;
 
   /* Sanity checks */
@@ -1621,16 +1677,19 @@ SILC_FSM_STATE(silc_client_command_reply_ban)
     goto out;
   }
 
-  /* Get the ban list */
+  /* Get the invite list */
   tmp = silc_argument_get_arg_type(args, 3, &len);
   if (tmp)
-    silc_buffer_set(&buf, tmp, len);
+    invite_args = silc_argument_list_parse(tmp, len);
 
   /* Notify application */
-  silc_client_command_callback(cmd, channel, tmp ? &buf : NULL);
+  silc_client_command_callback(cmd, channel, invite_args);
+
+  if (invite_args)
+    silc_argument_payload_free(invite_args);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1646,7 +1705,6 @@ SILC_FSM_STATE(silc_client_command_reply_leave)
   SilcCommandPayload payload = state_context;
   SilcArgumentPayload args = silc_command_get_args(payload);
   SilcChannelEntry channel;
-  SilcChannelUser chu;
   SilcID id;
 
   /* Sanity checks */
@@ -1667,12 +1725,7 @@ SILC_FSM_STATE(silc_client_command_reply_leave)
   }
 
   /* Remove us from this channel. */
-  chu = silc_client_on_channel(channel, conn->local_entry);
-  if (chu) {
-    silc_hash_table_del(chu->client->channels, chu->channel);
-    silc_hash_table_del(chu->channel->user_list, chu->client);
-    silc_free(chu);
-  }
+  silc_client_remove_from_channel(client, conn, channel, conn->local_entry);
 
   /* Notify application */
   silc_client_command_callback(cmd, channel);
@@ -1681,37 +1734,47 @@ SILC_FSM_STATE(silc_client_command_reply_leave)
   silc_client_del_channel(client, conn, channel);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
 /********************************* USERS ************************************/
 
-static SilcBool
-silc_client_command_reply_users_continue(SilcClient client,
+/* Continue USERS command reply processing after resolving unknown users */
+
+static void
+silc_client_command_reply_users_resolved(SilcClient client,
                                         SilcClientConnection conn,
-                                        SilcCommand command,
                                         SilcStatus status,
-                                        SilcStatus error,
-                                        void *context,
-                                        va_list ap)
+                                        SilcDList clients,
+                                        void *context)
 {
   SilcClientCommandContext cmd = context;
-
-  return TRUE;
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
 }
 
-/* Continue USERS command after resolving unknown users */
+
+/* Continue USERS command after resolving unknown channel */
 
 static void
-silc_client_command_reply_users_resolved(SilcClient client,
+silc_client_command_reply_users_continue(SilcClient client,
                                         SilcClientConnection conn,
                                         SilcStatus status,
-                                        SilcDList clients,
+                                        SilcDList channels,
                                         void *context)
 {
   SilcClientCommandContext cmd = context;
-  SILC_FSM_CALL_CONTINUE_SYNC(&cmd->thread);
+
+  if (!channels) {
+    SilcCommandPayload payload = silc_fsm_get_state_context(&cmd->thread);
+    SilcArgumentPayload args = silc_command_get_args(payload);
+
+    cmd->status = SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID;
+    ERROR_CALLBACK(cmd->status);
+    silc_fsm_next(&cmd->thread, silc_client_command_reply_processed);
+  }
+
+  SILC_FSM_CALL_CONTINUE(&cmd->thread);
 }
 
 /* Reply to USERS command. Received list of client ID's and theirs modes
@@ -1731,7 +1794,6 @@ SILC_FSM_STATE(silc_client_command_reply_users)
   SilcBufferStruct client_id_list, client_mode_list;
   SilcChannelEntry channel;
   SilcClientEntry client_entry;
-  SilcChannelUser chu;
   SilcID id;
   int i;
 
@@ -1749,11 +1811,9 @@ SILC_FSM_STATE(silc_client_command_reply_users)
   channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
   if (!channel) {
     /* Resolve the channel from server */
-#if 0
     SILC_FSM_CALL(silc_client_get_channel_by_id_resolve(
                        client, conn, &id.u.channel_id,
                        silc_client_command_reply_users_continue, cmd));
-#endif /* 0 */
     /* NOT REACHED */
   }
 
@@ -1805,16 +1865,9 @@ SILC_FSM_STATE(silc_client_command_reply_users)
     /* Save the client on this channel.  Unknown clients are ignored as they
        clearly do not exist since the resolving didn't find them. */
     client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (client_entry && !silc_client_on_channel(channel, client_entry)) {
-      chu = silc_calloc(1, sizeof(*chu));
-      if (!chu)
-       goto out;
-      chu->client = client_entry;
-      chu->mode = mode;
-      chu->channel = channel;
-      silc_hash_table_add(channel->user_list, client_entry, chu);
-      silc_hash_table_add(client_entry->channels, channel, chu);
-    }
+    if (client_entry)
+      silc_client_add_to_channel(client, conn, channel, client_entry, mode);
+    silc_client_unref_client(client, conn, client_entry);
 
     if (!silc_buffer_pull(&client_id_list, idp_len))
       goto out;
@@ -1828,7 +1881,7 @@ SILC_FSM_STATE(silc_client_command_reply_users)
   silc_hash_table_list_reset(&htl);
 
  out:
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1882,7 +1935,7 @@ SILC_FSM_STATE(silc_client_command_reply_getkey)
 
     /* Save fingerprint */
     if (!client_entry->fingerprint)
-      silc_hash_make(client->sha1hash, tmp + 4, len - 4,
+      silc_hash_make(conn->internal->sha1hash, tmp + 4, len - 4,
                     client_entry->fingerprint);
     if (!client_entry->public_key) {
       client_entry->public_key = public_key;
@@ -1892,6 +1945,7 @@ SILC_FSM_STATE(silc_client_command_reply_getkey)
     /* Notify application */
     silc_client_command_callback(cmd, SILC_ID_CLIENT, client_entry,
                                 client_entry->public_key);
+    silc_client_unref_client(client, conn, client_entry);
   } else if (id.type == SILC_ID_SERVER) {
     /* Received server's public key */
     server_entry = silc_client_get_server_by_id(client, conn, &id.u.server_id);
@@ -1900,15 +1954,21 @@ SILC_FSM_STATE(silc_client_command_reply_getkey)
       goto out;
     }
 
+    if (!server_entry->public_key) {
+      server_entry->public_key = public_key;
+      public_key = NULL;
+    }
+
     /* Notify application */
     silc_client_command_callback(cmd, SILC_ID_SERVER, server_entry,
-                                public_key);
+                                server_entry->public_key);
+    silc_client_unref_server(client, conn, server_entry);
   }
 
  out:
   if (public_key)
     silc_pkcs_public_key_free(public_key);
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1937,7 +1997,7 @@ SILC_FSM_STATE(silc_client_command_reply_service)
   /* Notify application */
   silc_client_command_callback(cmd, service_list, name);
 
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }
 
@@ -1947,6 +2007,6 @@ SILC_FSM_STATE(silc_client_command_reply_service)
 
 SILC_FSM_STATE(silc_client_command_reply_quit)
 {
-  silc_fsm_next(fsm, silc_client_command_reply_process);
+  silc_fsm_next(fsm, silc_client_command_reply_processed);
   return SILC_FSM_CONTINUE;
 }