Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client_register.c
index 98a86f513f228519c0b0d14be21231f09a862a9b..efbeaa24d9cae4534d01d65ad53e221b9ec83fb7 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2006 Pekka Riikonen
+  Copyright (C) 2006 - 2007 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
 
 /************************** Types and definitions ***************************/
 
+/* Resume session context */
+typedef struct {
+  SilcClient client;
+  SilcClientConnection conn;
+  SilcBufferStruct detach;
+  SilcBuffer auth;
+  char *nickname;
+  unsigned char *id;
+  SilcUInt32 id_len;
+  SilcUInt32 channel_count;
+} *SilcClientResumeSession;
 
 /************************ Static utility functions **************************/
 
+/* Continues resuming after resolving.  Continue after last reply. */
+
+static SilcBool
+silc_client_resume_continue(SilcClient client,
+                           SilcClientConnection conn,
+                           SilcCommand command,
+                           SilcStatus status,
+                           SilcStatus error,
+                           void *context,
+                           va_list ap)
+{
+  if (status == SILC_STATUS_OK || status == SILC_STATUS_LIST_END ||
+      SILC_STATUS_IS_ERROR(status)) {
+    silc_fsm_continue(&conn->internal->event_thread);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/* Function used to call command replies back to application in resuming. */
+
+static void
+silc_client_resume_command_callback(SilcClient client,
+                                   SilcClientConnection conn,
+                                   SilcCommand command, ...)
+{
+  va_list ap;
+  va_start(ap, command);
+  client->internal->ops->command_reply(client, conn, command,
+                                      SILC_STATUS_OK, SILC_STATUS_OK, ap);
+  va_end(ap);
+}
+
+/* Resume authentication data generation callback */
+
+static void silc_client_resume_auth_generated(const SilcBuffer data,
+                                             void *context)
+{
+  SilcClientConnection conn = context;
+  SilcClientResumeSession resume =
+    silc_fsm_get_state_context(&conn->internal->event_thread);
+
+  if (!data)
+    silc_fsm_next(&conn->internal->event_thread, silc_client_st_resume_error);
+  else
+    resume->auth = silc_buffer_copy(data);
+
+  SILC_FSM_CALL_CONTINUE_SYNC(&conn->internal->event_thread);
+}
+
 
 /****************************** NEW_ID packet *******************************/
 
@@ -36,6 +98,7 @@ SILC_FSM_STATE(silc_client_new_id)
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
   SilcPacket packet = state_context;
+  char *nick;
   SilcID id;
 
   if (conn->local_id)
@@ -47,37 +110,45 @@ SILC_FSM_STATE(silc_client_new_id)
                                silc_buffer_len(&packet->buffer), &id))
     goto out;
 
+  SILC_LOG_DEBUG(("New ID %s", silc_id_render(&id.u.client_id,
+                                             SILC_ID_CLIENT)));
+
+  /* From SILC protocol version 1.3, nickname is in NEW_CLIENT packet */
+  if (conn->internal->remote_version >= 13)
+    nick = (conn->internal->params.nickname ?
+           conn->internal->params.nickname : client->username);
+  else
+    nick = client->username;
+
   /* Create local client entry */
-  conn->local_entry = silc_client_add_client(client, conn,
-                                            (client->nickname ?
-                                             client->nickname :
-                                             client->username),
+  conn->local_entry = silc_client_add_client(client, conn, nick,
                                             client->username,
                                             client->realname,
                                             &id.u.client_id, 0);
   if (!conn->local_entry)
     goto out;
 
-  /* Save the ID */
+  /* Save the ID.  Take reference to conn->local_id. */
   conn->local_id = &conn->local_entry->id;
-  conn->local_idp = silc_buffer_copy(&packet->buffer);
-
-  /* Save cache entry */
-  silc_idcache_find_by_id_one(conn->internal->client_cache, conn->local_id,
-                             &conn->internal->local_entry);
+  conn->internal->local_idp = silc_buffer_copy(&packet->buffer);
 
   /* Save remote ID */
   if (packet->src_id_len) {
-    conn->remote_idp = silc_id_payload_encode_data(packet->src_id,
-                                                  packet->src_id_len,
-                                                  packet->src_id_type);
-    if (!conn->remote_idp)
+    conn->internal->remote_idp =
+      silc_id_payload_encode_data(packet->src_id,
+                                 packet->src_id_len,
+                                 packet->src_id_type);
+    if (!conn->internal->remote_idp)
       goto out;
-    silc_id_payload_parse_id(silc_buffer_data(conn->remote_idp),
-                            silc_buffer_len(conn->remote_idp),
+    silc_id_payload_parse_id(silc_buffer_data(conn->internal->remote_idp),
+                            silc_buffer_len(conn->internal->remote_idp),
                             &conn->remote_id);
   }
 
+  /* Set IDs to the packet stream */
+  silc_packet_set_ids(conn->stream, SILC_ID_CLIENT, conn->local_id,
+                     conn->remote_id.type, SILC_ID_GET_ID(conn->remote_id));
+
   /* Signal connection that new ID was received so it can continue
      with the registering. */
   if (conn->internal->registering)
@@ -98,9 +169,15 @@ SILC_FSM_STATE(silc_client_st_register)
 {
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
+  char *nick = NULL;
 
   SILC_LOG_DEBUG(("Register to network"));
 
+  /* From SILC protocol version 1.3, nickname is in NEW_CLIENT packet */
+  if (conn->internal->remote_version >= 13)
+    nick = (conn->internal->params.nickname ?
+           conn->internal->params.nickname : client->username);
+
   /* Send NEW_CLIENT packet to register to network */
   if (!silc_packet_send_va(conn->stream, SILC_PACKET_NEW_CLIENT, 0,
                           SILC_STR_UI_SHORT(strlen(client->username)),
@@ -109,6 +186,8 @@ SILC_FSM_STATE(silc_client_st_register)
                           SILC_STR_UI_SHORT(strlen(client->realname)),
                           SILC_STR_DATA(client->realname,
                                         strlen(client->realname)),
+                          SILC_STR_UI_SHORT(nick ? strlen(nick) : 0),
+                          SILC_STR_DATA(nick, nick ? strlen(nick) : 0),
                           SILC_STR_END)) {
     /** Error sending packet */
     silc_fsm_next(fsm, silc_client_st_register_error);
@@ -117,7 +196,8 @@ SILC_FSM_STATE(silc_client_st_register)
 
   /** Wait for new ID */
   conn->internal->registering = TRUE;
-  silc_fsm_next_later(fsm, silc_client_st_register_complete, 15, 0);
+  silc_fsm_next_later(fsm, silc_client_st_register_complete,
+                     conn->internal->retry_timer, 0);
   return SILC_FSM_WAIT;
 }
 
@@ -128,77 +208,518 @@ SILC_FSM_STATE(silc_client_st_register_complete)
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
 
-  if (!conn->local_id) {
-    /* Timeout, ID not received */
-    conn->internal->registering = FALSE;
+  if (conn->internal->disconnected) {
+    /** Disconnected */
     silc_fsm_next(fsm, silc_client_st_register_error);
     return SILC_FSM_CONTINUE;
   }
 
+  if (!conn->local_id) {
+    if (conn->internal->retry_count++ >= SILC_CLIENT_RETRY_COUNT) {
+      /** Timeout, ID not received */
+      conn->internal->registering = FALSE;
+      conn->internal->retry_count = 0;
+      conn->internal->retry_timer = SILC_CLIENT_RETRY_MIN;
+      silc_fsm_next(fsm, silc_client_st_register_error);
+      return SILC_FSM_CONTINUE;
+    }
+
+    /** Resend registering packet */
+    silc_fsm_next(fsm, silc_client_st_register);
+    conn->internal->retry_timer = ((conn->internal->retry_timer *
+                                   SILC_CLIENT_RETRY_MUL) +
+                                  (silc_rng_get_rn16(client->rng) %
+                                   SILC_CLIENT_RETRY_RAND));
+    return SILC_FSM_CONTINUE;
+  }
+
   SILC_LOG_DEBUG(("Registered to network"));
 
   /* Issue IDENTIFY command for itself to get resolved hostname
      correctly from server. */
-  silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY, NULL, NULL,
-                          1, 5, silc_buffer_data(conn->local_idp),
-                          silc_buffer_len(conn->local_idp));
-
-  /* Send NICK command if the nickname was set by the application (and is
-     not same as the username).  Send this with little timeout. */
-  if (client->nickname &&
-      !silc_utf8_strcasecmp(client->nickname, client->username))
-    silc_client_command_send(client, conn, SILC_COMMAND_NICK, NULL, NULL,
-                            1, 1, client->nickname, strlen(client->nickname));
+  silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY,
+                          silc_client_command_called_dummy, NULL,
+                          1, 5, silc_buffer_data(conn->internal->local_idp),
+                          silc_buffer_len(conn->internal->local_idp));
+
+  /* With SILC protocol version 1.2 call NICK command if the nickname was
+     set by the application. */
+  if (conn->internal->params.nickname && conn->internal->remote_version < 13 &&
+      !silc_utf8_strcasecmp(conn->internal->params.nickname, client->username))
+    silc_client_command_call(client, conn, NULL,
+                            "NICK", conn->internal->params.nickname, NULL);
 
   /* Issue INFO command to fetch the real server name and server
      information and other stuff. */
-  silc_client_command_send(client, conn, SILC_COMMAND_INFO, NULL, NULL,
-                          1, 2, silc_buffer_data(conn->remote_idp),
-                          silc_buffer_len(conn->remote_idp));
+  silc_client_command_send(client, conn, SILC_COMMAND_INFO,
+                          silc_client_command_called_dummy, NULL,
+                          1, 2, silc_buffer_data(conn->internal->remote_idp),
+                          silc_buffer_len(conn->internal->remote_idp));
 
   /* Call connection callback.  We are now inside SILC network. */
   conn->callback(client, conn, SILC_CLIENT_CONN_SUCCESS, 0, NULL,
-                conn->context);
+                conn->callback_context);
 
   conn->internal->registering = FALSE;
+  silc_schedule_task_del_by_all(conn->internal->schedule, 0,
+                               silc_client_connect_timeout, conn);
+  silc_async_free(conn->internal->cop);
+  conn->internal->cop = NULL;
+
   return SILC_FSM_FINISH;
 }
 
+/* Error registering to network */
+
 SILC_FSM_STATE(silc_client_st_register_error)
 {
   SilcClientConnection conn = fsm_context;
-  SilcClient client = conn->client;
 
-  /* XXX */
-  /* Close connection */
+  SILC_LOG_DEBUG(("Error registering to network"));
 
-  conn->callback(client, conn, SILC_CLIENT_CONN_ERROR, 0, NULL, conn->context);
+  /* Signal to close connection */
+  conn->internal->status = SILC_CLIENT_CONN_ERROR;
+  if (!conn->internal->disconnected) {
+    conn->internal->disconnected = TRUE;
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+  }
+
+  silc_schedule_task_del_by_all(conn->internal->schedule, 0,
+                               silc_client_connect_timeout, conn);
 
   return SILC_FSM_FINISH;
 }
 
-
 /************************* Resume detached session **************************/
 
 /* Resume detached session */
 
 SILC_FSM_STATE(silc_client_st_resume)
 {
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcClientResumeSession resume;
+  unsigned char *id;
+  SilcUInt16 id_len;
+  SilcClientID client_id;
+  int ret;
+
+  SILC_LOG_DEBUG(("Resuming detached session"));
+
+  resume = silc_calloc(1, sizeof(*resume));
+  if (!resume) {
+    /** Out of memory */
+    silc_fsm_next(fsm, silc_client_st_resume_error);
+    return SILC_FSM_CONTINUE;
+  }
+  silc_fsm_set_state_context(fsm, resume);
+
+  silc_buffer_set(&resume->detach, conn->internal->params.detach_data,
+                 conn->internal->params.detach_data_len);
+  SILC_LOG_HEXDUMP(("Detach data"), silc_buffer_data(&resume->detach),
+                  silc_buffer_len(&resume->detach));
+
+  /* Take the old client ID from the detachment data */
+  ret = silc_buffer_unformat(&resume->detach,
+                            SILC_STR_ADVANCE,
+                            SILC_STR_UI16_NSTRING_ALLOC(&resume->nickname,
+                                                        NULL),
+                            SILC_STR_UI16_NSTRING(&id, &id_len),
+                            SILC_STR_UI_INT(NULL),
+                            SILC_STR_UI_INT(&resume->channel_count),
+                            SILC_STR_END);
+  if (ret < 0) {
+    /** Malformed detach data */
+    SILC_LOG_DEBUG(("Malformed detachment data"));
+    silc_fsm_next(fsm, silc_client_st_resume_error);
+    return SILC_FSM_CONTINUE;
+  }
 
-  return SILC_FSM_FINISH;
+  if (!silc_id_str2id(id, id_len, SILC_ID_CLIENT, &client_id,
+                     sizeof(client_id))) {
+    /** Malformed ID */
+    SILC_LOG_DEBUG(("Malformed ID"));
+    silc_fsm_next(fsm, silc_client_st_resume_error);
+    return SILC_FSM_CONTINUE;
+  }
+  resume->id = id;
+  resume->id_len = id_len;
+
+  /* Generate authentication data that server will verify */
+  silc_fsm_next(fsm, silc_client_st_resume_send);
+  SILC_FSM_CALL(silc_auth_public_key_auth_generate(
+                                conn->public_key, conn->private_key,
+                                client->rng, conn->internal->hash,
+                                &client_id, SILC_ID_CLIENT,
+                                silc_client_resume_auth_generated, conn));
+  /* NOT REACHED */
 }
 
-SILC_FSM_STATE(silc_client_st_resume_new_id)
+/* Send RESUME_CLIENT packet */
+
+SILC_FSM_STATE(silc_client_st_resume_send)
 {
   SilcClientConnection conn = fsm_context;
+  SilcClientResumeSession resume = state_context;
+
+  SILC_LOG_DEBUG(("Send RESUME_CLIENT packet"));
+
+  /* Send RESUME_CLIENT packet to resume to network */
+  if (!silc_packet_send_va(conn->stream, SILC_PACKET_RESUME_CLIENT, 0,
+                          SILC_STR_UI_SHORT(resume->id_len),
+                          SILC_STR_DATA(resume->id, resume->id_len),
+                          SILC_STR_DATA(silc_buffer_data(resume->auth),
+                                        silc_buffer_len(resume->auth)),
+                          SILC_STR_END)) {
+    /** Error sending packet */
+    SILC_LOG_DEBUG(("Error sending packet"));
+    silc_fsm_next(fsm, silc_client_st_resume_error);
+    return SILC_FSM_CONTINUE;
+  }
+
+  /** Wait for new ID */
+  conn->internal->registering = TRUE;
+  silc_fsm_next_later(fsm, silc_client_st_resume_resolve_channels, 15, 0);
+  return SILC_FSM_WAIT;
+}
+
+/* Resolve the old session information, user mode and joined channels. */
+
+SILC_FSM_STATE(silc_client_st_resume_resolve_channels)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcClientResumeSession resume = state_context;
+  SilcUInt32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
+  unsigned char **res_argv = NULL;
+  int i;
+
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_resume_error);
+    return SILC_FSM_CONTINUE;
+  }
+
+  if (!conn->local_id) {
+    /** Timeout, ID not received */
+    conn->internal->registering = FALSE;
+    silc_fsm_next(fsm, silc_client_st_resume_error);
+    return SILC_FSM_CONTINUE;
+  }
+
+  /** Wait for channels */
+  silc_fsm_next(fsm, silc_client_st_resume_resolve_cmodes);
+
+  /* Change our nickname */
+  silc_client_change_nickname(client, conn, conn->local_entry,
+                             resume->nickname, NULL, NULL, 0);
+
+  /* Send UMODE command to get our own user mode in the network */
+  SILC_LOG_DEBUG(("Resolving user mode"));
+  silc_client_command_send(client, conn, SILC_COMMAND_UMODE,
+                          silc_client_command_called_dummy, NULL,
+                          1, 1, silc_buffer_data(conn->internal->local_idp),
+                          silc_buffer_len(conn->internal->local_idp));
+
+  if (!resume->channel_count)
+    return SILC_FSM_YIELD;
+
+  /* Send IDENTIFY command for all channels we know about.  These are the
+     channels we've joined to according our detachment data. */
+  for (i = 0; i < resume->channel_count; i++) {
+    SilcChannelEntry channel;
+    unsigned char *chid;
+    SilcUInt16 chid_len;
+    SilcBuffer idp;
+    SilcChannelID channel_id;
+    char *name;
+
+    if (silc_buffer_unformat(&resume->detach,
+                            SILC_STR_ADVANCE,
+                            SILC_STR_UI16_NSTRING(&name, NULL),
+                            SILC_STR_UI16_NSTRING(&chid, &chid_len),
+                            SILC_STR_UI_INT(NULL),
+                            SILC_STR_END) < 0)
+      continue;
+
+    if (!silc_id_str2id(chid, chid_len, SILC_ID_CHANNEL, &channel_id,
+                       sizeof(channel_id)))
+      continue;
+    idp = silc_id_payload_encode_data(chid, chid_len, SILC_ID_CHANNEL);
+    if (!idp)
+      continue;
+
+    /* Add the channel to cache */
+    channel = silc_client_get_channel_by_id(client, conn, &channel_id);
+    if (!channel)
+      silc_client_add_channel(client, conn, name, 0, &channel_id);
+    else
+      silc_client_unref_channel(client, conn, channel);
+
+    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] = silc_buffer_steal(idp, &res_argv_lens[res_argc]);
+    res_argv_types[res_argc] = res_argc + 5;
+    res_argc++;
+    silc_buffer_free(idp);
+  }
+
+  /* Send IDENTIFY command */
+  SILC_LOG_DEBUG(("Resolving joined channels"));
+  silc_client_command_send_argv(client, conn, SILC_COMMAND_IDENTIFY,
+                               silc_client_resume_continue, conn,
+                               res_argc, res_argv, res_argv_lens,
+                               res_argv_types);
+
+  for (i = 0; i < resume->channel_count; i++)
+    silc_free(res_argv[i]);
+  silc_free(res_argv);
+  silc_free(res_argv_lens);
+  silc_free(res_argv_types);
+
+  return SILC_FSM_WAIT;
+}
+
+/* Resolve joined channel modes, users and topics. */
+
+SILC_FSM_STATE(silc_client_st_resume_resolve_cmodes)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcClientResumeSession resume = state_context;
+  SilcIDCacheEntry entry;
+  SilcChannelEntry channel;
+  SilcList channels;
+  SilcBuffer idp;
+
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_resume_error);
+    return SILC_FSM_CONTINUE;
+  }
+
+  SILC_LOG_DEBUG(("Resolving channel details"));
+
+  /** Wait for channel modes */
+  silc_fsm_next(fsm, silc_client_st_resume_completed);
+
+  if (!silc_idcache_get_all(conn->internal->channel_cache, &channels))
+    return SILC_FSM_YIELD;
+
+  /* Resolve channels' mode, users and topic */
+  resume->channel_count = silc_list_count(channels) * 3;
+  silc_list_start(channels);
+  while ((entry = silc_list_get(channels))) {
+    channel = entry->context;
+    idp = silc_id_payload_encode(&channel->id, SILC_ID_CHANNEL);
+    if (!idp)
+      continue;
+
+    silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
+                            silc_client_resume_continue, conn, 1,
+                            1, silc_buffer_data(idp),
+                            silc_buffer_len(idp));
+    silc_client_command_send(client, conn, SILC_COMMAND_USERS,
+                            silc_client_resume_continue, conn, 1,
+                            1, silc_buffer_data(idp),
+                            silc_buffer_len(idp));
+    silc_client_command_send(client, conn, SILC_COMMAND_TOPIC,
+                            silc_client_resume_continue, conn, 1,
+                            1, silc_buffer_data(idp),
+                            silc_buffer_len(idp));
+    silc_buffer_free(idp);
+  }
+
+  return SILC_FSM_WAIT;
+}
+
+/* Resuming completed */
+
+SILC_FSM_STATE(silc_client_st_resume_completed)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcClientResumeSession resume = state_context;
+  SilcIDCacheEntry entry;
+  SilcChannelEntry channel;
+  SilcList channels;
+
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_resume_error);
+    return SILC_FSM_CONTINUE;
+  }
+
+  if (resume->channel_count > 0) {
+    resume->channel_count--;
+    if (resume->channel_count)
+      return SILC_FSM_WAIT;
+  }
+
+  SILC_LOG_DEBUG(("Resuming completed"));
+
+  /* Issue IDENTIFY command for itself to get resolved hostname
+     correctly from server. */
+  silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY,
+                          silc_client_command_called_dummy, NULL,
+                          1, 5, silc_buffer_data(conn->internal->local_idp),
+                          silc_buffer_len(conn->internal->local_idp));
+
+  /* Issue INFO command to fetch the real server name and server
+     information and other stuff. */
+  silc_client_command_send(client, conn, SILC_COMMAND_INFO,
+                          silc_client_command_called_dummy, NULL,
+                          1, 2, silc_buffer_data(conn->internal->remote_idp),
+                          silc_buffer_len(conn->internal->remote_idp));
+
+  /* Call connection callback.  We have now resumed to SILC network. */
+  conn->callback(client, conn, SILC_CLIENT_CONN_SUCCESS_RESUME, 0, NULL,
+                conn->callback_context);
+
+  /* Call UMODE command reply. */
+  if (conn->local_entry->mode)
+    silc_client_resume_command_callback(client, conn, SILC_COMMAND_UMODE,
+                                       conn->local_entry->mode);
+
+  /* Call NICK command reply. */
+  silc_client_resume_command_callback(client, conn, SILC_COMMAND_NICK,
+                                     conn->local_entry,
+                                     conn->local_entry->nickname,
+                                     &conn->local_entry->id);
+
+  /* Call JOIN command replies for all joined channel */
+  if (silc_idcache_get_all(conn->internal->channel_cache, &channels)) {
+    silc_list_start(channels);
+    while ((entry = silc_list_get(channels))) {
+      SilcHashTableList htl;
+      const char *cipher, *hmac;
+
+      channel = entry->context;
+      cipher = (channel->internal.send_key ?
+               silc_cipher_get_name(channel->internal.send_key) : NULL);
+      hmac = (channel->internal.hmac ?
+             silc_hmac_get_name(channel->internal.hmac) : NULL);
+      silc_hash_table_list(channel->user_list, &htl);
+      silc_client_resume_command_callback(client, conn, SILC_COMMAND_JOIN,
+                                         channel->channel_name, channel,
+                                         channel->mode, &htl, channel->topic,
+                                         cipher, hmac, channel->founder_key,
+                                         channel->channel_pubkeys,
+                                         channel->user_limit);
+      silc_hash_table_list_reset(&htl);
+    }
+  }
+
+  conn->internal->registering = FALSE;
+  silc_schedule_task_del_by_all(conn->internal->schedule, 0,
+                               silc_client_connect_timeout, conn);
+  silc_free(resume->nickname);
+  silc_free(resume);
+  silc_async_free(conn->internal->cop);
+  conn->internal->cop = NULL;
 
   return SILC_FSM_FINISH;
 }
 
+/* Error resuming to network */
+
 SILC_FSM_STATE(silc_client_st_resume_error)
 {
-  /* XXX */
-  /* Close connection */
+  SilcClientConnection conn = fsm_context;
+  SilcClientResumeSession resume = state_context;
+
+  if (conn->internal->disconnected) {
+    if (resume) {
+      silc_free(resume->nickname);
+      silc_free(resume);
+    }
+    return SILC_FSM_FINISH;
+  }
+
+  SILC_LOG_DEBUG(("Error resuming to network"));
+
+  /* Signal to close connection */
+  conn->internal->status = SILC_CLIENT_CONN_ERROR;
+  if (!conn->internal->disconnected) {
+    conn->internal->disconnected = TRUE;
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+  }
+
+  silc_schedule_task_del_by_all(conn->internal->schedule, 0,
+                               silc_client_connect_timeout, conn);
+
+  if (resume) {
+    silc_free(resume->nickname);
+    silc_free(resume);
+  }
 
   return SILC_FSM_FINISH;
 }
+
+/* Generates the session detachment data. This data can be used later
+   to resume back to the server. */
+
+SilcBuffer silc_client_get_detach_data(SilcClient client,
+                                      SilcClientConnection conn)
+{
+  SilcBuffer detach;
+  SilcHashTableList htl;
+  SilcChannelUser chu;
+  unsigned char id[64];
+  SilcUInt32 id_len;
+  int ret, ch_count;
+
+  SILC_LOG_DEBUG(("Creating detachment data"));
+
+  ch_count = silc_hash_table_count(conn->local_entry->channels);
+  silc_id_id2str(conn->local_id, SILC_ID_CLIENT, id, sizeof(id), &id_len);
+
+  /* Save the nickname, Client ID and user mode in SILC network */
+  detach = silc_buffer_alloc(0);
+  if (!detach)
+    return NULL;
+  ret =
+    silc_buffer_format(detach,
+                      SILC_STR_ADVANCE,
+                      SILC_STR_UI_SHORT(strlen(conn->local_entry->nickname)),
+                      SILC_STR_DATA(conn->local_entry->nickname,
+                                    strlen(conn->local_entry->nickname)),
+                      SILC_STR_UI_SHORT(id_len),
+                      SILC_STR_DATA(id, id_len),
+                      SILC_STR_UI_INT(conn->local_entry->mode),
+                      SILC_STR_UI_INT(ch_count),
+                      SILC_STR_END);
+  if (ret < 0) {
+    silc_buffer_free(detach);
+    return NULL;
+  }
+
+  /* Save all joined channels */
+  silc_hash_table_list(conn->local_entry->channels, &htl);
+  while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+    unsigned char chid[32];
+    SilcUInt32 chid_len;
+
+    silc_id_id2str(&chu->channel->id, SILC_ID_CHANNEL, chid, sizeof(chid),
+                  &chid_len);
+    silc_buffer_format(detach,
+                      SILC_STR_ADVANCE,
+                      SILC_STR_UI_SHORT(strlen(chu->channel->channel_name)),
+                      SILC_STR_DATA(chu->channel->channel_name,
+                                    strlen(chu->channel->channel_name)),
+                      SILC_STR_UI_SHORT(chid_len),
+                      SILC_STR_DATA(chid, chid_len),
+                      SILC_STR_UI_INT(chu->channel->mode),
+                      SILC_STR_END);
+  }
+  silc_hash_table_list_reset(&htl);
+
+  silc_buffer_start(detach);
+  SILC_LOG_HEXDUMP(("Detach data"), silc_buffer_data(detach),
+                  silc_buffer_len(detach));
+
+  return detach;
+}