Merged from silc_1_0_branch.
[silc.git] / lib / silcclient / client.c
index c5b13236e391a85aadde434ec493ee7d1b6e8aa6..745cb698b52daf343e7233769ab7df0f770f7c00 100644 (file)
@@ -26,7 +26,6 @@
 SILC_TASK_CALLBACK(silc_client_connect_to_server_start);
 SILC_TASK_CALLBACK(silc_client_connect_to_server_second);
 SILC_TASK_CALLBACK(silc_client_connect_to_server_final);
-SILC_TASK_CALLBACK(silc_client_rekey_callback);
 SILC_TASK_CALLBACK(silc_client_rekey_final);
 
 static bool silc_client_packet_parse(SilcPacketParserContext *parser_context,
@@ -47,7 +46,7 @@ void silc_client_resolve_auth_method(bool success,
 SilcClient silc_client_alloc(SilcClientOperations *ops, 
                             SilcClientParams *params,
                             void *application,
-                            const char *silc_version)
+                            const char *version_string)
 {
   SilcClient new_client;
 
@@ -58,7 +57,9 @@ SilcClient silc_client_alloc(SilcClientOperations *ops,
   new_client->internal->ops = ops;
   new_client->internal->params = 
     silc_calloc(1, sizeof(*new_client->internal->params));
-  new_client->internal->silc_client_version = strdup(silc_version);
+  if (!version_string)
+    version_string = silc_version_string;
+  new_client->internal->silc_client_version = strdup(version_string);
 
   if (params)
     memcpy(new_client->internal->params, params, sizeof(*params));
@@ -87,6 +88,16 @@ void silc_client_free(SilcClient client)
     if (client->rng)
       silc_rng_free(client->rng);
 
+    silc_cipher_unregister_all();
+    silc_pkcs_unregister_all();
+    silc_hash_unregister_all();
+    silc_hmac_unregister_all();
+
+    silc_hash_free(client->md5hash);
+    silc_hash_free(client->sha1hash);
+    silc_hmac_free(client->internal->md5hmac);
+    silc_hmac_free(client->internal->sha1hmac);
+    silc_cipher_free(client->internal->none_cipher);
     silc_free(client->internal->params);
     silc_free(client->internal->silc_client_version);
     silc_free(client->internal);
@@ -98,13 +109,26 @@ void silc_client_free(SilcClient client)
    the client ready to be run. One must call silc_client_run to run the
    client. Returns FALSE if error occured, TRUE otherwise. */
 
-int silc_client_init(SilcClient client)
+bool silc_client_init(SilcClient client)
 {
   SILC_LOG_DEBUG(("Initializing client"));
 
+  assert(client);
+  assert(client->username);
+  assert(client->hostname);
+  assert(client->realname);
+
+  /* Initialize the crypto library.  If application has done this already
+     this has no effect.  Also, we will not be overriding something
+     application might have registered earlier. */
+  silc_cipher_register_default();
+  silc_pkcs_register_default();
+  silc_hash_register_default();
+  silc_hmac_register_default();
+
   /* Initialize hash functions for client to use */
-  silc_hash_alloc("md5", &client->internal->md5hash);
-  silc_hash_alloc("sha1", &client->internal->sha1hash);
+  silc_hash_alloc("md5", &client->md5hash);
+  silc_hash_alloc("sha1", &client->sha1hash);
 
   /* Initialize none cipher */
   silc_cipher_alloc("none", &client->internal->none_cipher);
@@ -120,7 +144,7 @@ int silc_client_init(SilcClient client)
   /* Initialize the scheduler */
   client->schedule = 
     silc_schedule_init(client->internal->params->task_max ?
-                      client->internal->params->task_max : 200);
+                      client->internal->params->task_max : 200, client);
   if (!client->schedule)
     return FALSE;
 
@@ -153,6 +177,11 @@ void silc_client_run(SilcClient client)
 {
   SILC_LOG_DEBUG(("Running client"));
 
+  assert(client);
+  assert(client->pkcs);
+  assert(client->public_key);
+  assert(client->private_key);
+
   /* Start the scheduler, the heart of the SILC client. When this returns
      the program will be terminated. */
   silc_schedule(client->schedule);
@@ -194,25 +223,29 @@ silc_client_add_connection(SilcClient client,
   SilcClientConnection conn;
   int i;
 
+  SILC_LOG_DEBUG(("Adding new connection to %s:%d", hostname, port));
+
   conn = silc_calloc(1, sizeof(*conn));
+  conn->internal = silc_calloc(1, sizeof(*conn->internal));
 
   /* Initialize ID caches */
-  conn->client_cache = silc_idcache_alloc(0, SILC_ID_CLIENT, 
-                                         silc_client_entry_destructor);
-  conn->channel_cache = silc_idcache_alloc(0, SILC_ID_CHANNEL, NULL);
-  conn->server_cache = silc_idcache_alloc(0, SILC_ID_SERVER, NULL);
   conn->client = client;
   conn->remote_host = strdup(hostname);
   conn->remote_port = port;
   conn->context = context;
-  conn->pending_commands = silc_dlist_init();
-  conn->ftp_sessions = silc_dlist_init();
+  conn->internal->client_cache =
+    silc_idcache_alloc(0, SILC_ID_CLIENT, silc_client_entry_destructor);
+  conn->internal->channel_cache = silc_idcache_alloc(0, SILC_ID_CHANNEL, NULL);
+  conn->internal->server_cache = silc_idcache_alloc(0, SILC_ID_SERVER, NULL);
+  conn->internal->pending_commands = silc_dlist_init();
+  conn->internal->ftp_sessions = silc_dlist_init();
 
   if (params) {
     if (params->detach_data)
-      conn->params.detach_data = silc_memdup(params->detach_data,
-                                            params->detach_data_len);
-    conn->params.detach_data_len = params->detach_data_len;
+      conn->internal->params.detach_data =
+       silc_memdup(params->detach_data,
+                   params->detach_data_len);
+    conn->internal->params.detach_data_len = params->detach_data_len;
   }
 
   /* Add the connection to connections table */
@@ -239,14 +272,79 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
 
   for (i = 0; i < client->internal->conns_count; i++)
     if (client->internal->conns[i] == conn) {
+      /* Free all cache entries */
+      SilcIDCacheList list;
+      SilcIDCacheEntry entry;
+      SilcClientCommandPending *r;
+      bool ret;
+
+      if (silc_idcache_get_all(conn->internal->client_cache, &list)) {
+       ret = silc_idcache_list_first(list, &entry);
+       while (ret) {
+         silc_client_del_client(client, conn, entry->context);
+         ret = silc_idcache_list_next(list, &entry);
+       }
+       silc_idcache_list_free(list);
+      }
+
+      if (silc_idcache_get_all(conn->internal->channel_cache, &list)) {
+       ret = silc_idcache_list_first(list, &entry);
+       while (ret) {
+         silc_client_del_channel(client, conn, entry->context);
+         ret = silc_idcache_list_next(list, &entry);
+       }
+       silc_idcache_list_free(list);
+      }
 
-      silc_idcache_free(conn->client_cache);
-      silc_idcache_free(conn->channel_cache);
-      silc_idcache_free(conn->server_cache);
-      if (conn->pending_commands)
-       silc_dlist_uninit(conn->pending_commands);
+      if (silc_idcache_get_all(conn->internal->server_cache, &list)) {
+       ret = silc_idcache_list_first(list, &entry);
+       while (ret) {
+         silc_client_del_server(client, conn, entry->context);
+         ret = silc_idcache_list_next(list, &entry);
+       }
+       silc_idcache_list_free(list);
+      }
+
+      /* Clear ID caches */
+      if (conn->internal->client_cache)
+       silc_idcache_free(conn->internal->client_cache);
+      if (conn->internal->channel_cache)
+       silc_idcache_free(conn->internal->channel_cache);
+      if (conn->internal->server_cache)
+       silc_idcache_free(conn->internal->server_cache);
+
+      /* Free data (my ID is freed in above silc_client_del_client).
+        conn->nickname is freed when freeing the local_entry->nickname. */
       silc_free(conn->remote_host);
-      silc_dlist_uninit(conn->ftp_sessions);
+      silc_free(conn->local_id_data);
+      if (conn->internal->send_key)
+       silc_cipher_free(conn->internal->send_key);
+      if (conn->internal->receive_key)
+       silc_cipher_free(conn->internal->receive_key);
+      if (conn->internal->hmac_send)
+       silc_hmac_free(conn->internal->hmac_send);
+      if (conn->internal->hmac_receive)
+       silc_hmac_free(conn->internal->hmac_receive);
+      silc_free(conn->internal->rekey);
+
+      if (conn->internal->active_session) {
+       conn->sock->user_data = NULL;
+       silc_client_ftp_session_free(conn->internal->active_session);
+       conn->internal->active_session = NULL;
+      }
+
+      silc_client_ftp_free_sessions(client, conn);
+
+      if (conn->internal->pending_commands) {
+       silc_dlist_start(conn->internal->pending_commands);
+       while ((r = silc_dlist_get(conn->internal->pending_commands))
+              != SILC_LIST_END)
+         silc_dlist_del(conn->internal->pending_commands, r);
+       silc_dlist_uninit(conn->internal->pending_commands);
+      }
+
+      silc_free(conn->internal);
+      memset(conn, 0, sizeof(*conn));
       silc_free(conn);
 
       client->internal->conns[i] = NULL;
@@ -322,7 +420,8 @@ silc_client_connect_to_server_internal(SilcClientInternalConnectContext *ctx)
                                     (void *)ctx, 0, 0, 
                                     SILC_TASK_FD,
                                     SILC_TASK_PRI_NORMAL);
-  silc_schedule_set_listen_fd(ctx->client->schedule, sock, SILC_TASK_WRITE);
+  silc_schedule_set_listen_fd(ctx->client->schedule, sock, SILC_TASK_WRITE,
+                             FALSE);
 
   ctx->sock = sock;
 
@@ -337,7 +436,7 @@ silc_client_connect_to_server_internal(SilcClientInternalConnectContext *ctx)
    case then this function is not used at all. When the connecting is
    done the `connect' client operation is called. */
 
-int silc_client_connect_to_server(SilcClient client,
+bool silc_client_connect_to_server(SilcClient client,
                                   SilcClientConnectionParams *params,
                                   int port, char *host, void *context)
 {
@@ -359,7 +458,7 @@ int silc_client_connect_to_server(SilcClient client,
   ctx->client = client;
   ctx->conn = conn;
   ctx->host = strdup(host);
-  ctx->port = port;
+  ctx->port = port ? port : 706;
   ctx->tries = 0;
 
   /* Do the actual connecting process */
@@ -383,12 +482,14 @@ static void silc_client_start_key_exchange_cb(SilcSocketConnection sock,
 
   SILC_LOG_DEBUG(("Start"));
 
-  /* XXX We should most likely use the resolved host name instead of the
-     one user provided for us. */
-  silc_free(conn->sock->hostname);
-  conn->sock->hostname = strdup(conn->remote_host);
+  if (conn->sock->hostname) {
+    silc_free(conn->remote_host);
+    conn->remote_host = strdup(conn->sock->hostname);
+  } else {
+    conn->sock->hostname = strdup(conn->remote_host);
+  }
   if (!conn->sock->ip)
-    conn->sock->ip = strdup(conn->remote_host);
+    conn->sock->ip = strdup(conn->sock->hostname);
   conn->sock->port = conn->remote_port;
 
   /* Allocate internal Key Exchange context. This is sent to the
@@ -410,7 +511,7 @@ static void silc_client_start_key_exchange_cb(SilcSocketConnection sock,
     client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
                               "Error: Could not start key exchange protocol");
     silc_net_close_connection(conn->sock->sock);
-    client->internal->ops->connect(client, conn, FALSE);
+    client->internal->ops->connected(client, conn, SILC_CLIENT_CONN_ERROR);
     return;
   }
   conn->sock->protocol = protocol;
@@ -439,6 +540,10 @@ void silc_client_start_key_exchange(SilcClient client,
                                    SilcClientConnection conn,
                                    int fd)
 {
+  assert(client->pkcs);
+  assert(client->public_key);
+  assert(client->private_key);
+
   /* Allocate new socket connection object */
   silc_socket_alloc(fd, SILC_SOCKET_TYPE_SERVER, (void *)conn, &conn->sock);
 
@@ -456,8 +561,8 @@ void silc_client_start_key_exchange(SilcClient client,
                          conn, client->schedule);
 }
 
-/* Callback called when error has occurred during connecting to the server.
-   The `connect' client operation will be called. */
+/* Callback called when error has occurred during connecting (KE) to
+   the server.  The `connect' client operation will be called. */
 
 SILC_TASK_CALLBACK(silc_client_connect_failure)
 {
@@ -465,12 +570,27 @@ SILC_TASK_CALLBACK(silc_client_connect_failure)
     (SilcClientKEInternalContext *)context;
   SilcClient client = (SilcClient)ctx->client;
 
-  client->internal->ops->connect(client, ctx->sock->user_data, FALSE);
+  client->internal->ops->connected(client, ctx->sock->user_data, 
+                                  SILC_CLIENT_CONN_ERROR);
   if (ctx->packet)
     silc_packet_context_free(ctx->packet);
   silc_free(ctx);
 }
 
+/* Callback called when error has occurred during connecting (auth) to
+   the server.  The `connect' client operation will be called. */
+
+SILC_TASK_CALLBACK(silc_client_connect_failure_auth)
+{
+  SilcClientConnAuthInternalContext *ctx =
+    (SilcClientConnAuthInternalContext *)context;
+  SilcClient client = (SilcClient)ctx->client;
+
+  client->internal->ops->connected(client, ctx->sock->user_data, 
+                                  SILC_CLIENT_CONN_ERROR);
+  silc_free(ctx);
+}
+
 /* Start of the connection to the remote server. This is called after
    succesful TCP/IP connection has been established to the remote host. */
 
@@ -515,7 +635,7 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_start)
       silc_free(ctx);
 
       /* Notify application of failure */
-      client->internal->ops->connect(client, conn, FALSE);
+      client->internal->ops->connected(client, conn, SILC_CLIENT_CONN_ERROR);
       silc_client_del_connection(client, conn);
     }
     return;
@@ -587,6 +707,7 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_second)
   silc_protocol_free(protocol);
   if (ctx->packet)
     silc_packet_context_free(ctx->packet);
+  ctx->packet = NULL;
   silc_free(ctx);
   sock->protocol = NULL;
 
@@ -619,7 +740,21 @@ void silc_client_resolve_auth_method(bool success,
 
   proto_ctx->auth_meth = auth_meth;
 
-  if (auth_data && auth_data_len) {
+  if (success && auth_data && auth_data_len) {
+
+    /* Passphrase must be UTF-8 encoded, if it isn't encode it */
+    if (auth_meth == SILC_AUTH_PASSWORD && 
+       !silc_utf8_valid(auth_data, auth_data_len)) {
+      int payload_len = 0;
+      unsigned char *autf8 = NULL;
+      payload_len = silc_utf8_encoded_len(auth_data, auth_data_len, 
+                                         SILC_STRING_ASCII);
+      autf8 = silc_calloc(payload_len, sizeof(*autf8));
+      auth_data_len = silc_utf8_encode(auth_data, auth_data_len, 
+                                      SILC_STRING_ASCII, autf8, payload_len);
+      auth_data = autf8;
+    }
+
     proto_ctx->auth_data = silc_memdup(auth_data, auth_data_len);
     proto_ctx->auth_data_len = auth_data_len;
   }
@@ -653,43 +788,75 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_final)
       protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
     /* Error occured during protocol */
     SILC_LOG_DEBUG(("Error during authentication protocol"));
-    silc_protocol_free(protocol);
-    if (ctx->auth_data)
-      silc_free(ctx->auth_data);
-    if (ctx->ske)
-      silc_ske_free(ctx->ske);
-    if (ctx->dest_id)
-      silc_free(ctx->dest_id);
-    conn->sock->protocol = NULL;
-    silc_socket_free(ctx->sock);
-
-    /* Notify application of failure */
-    silc_schedule_task_add(client->schedule, ctx->sock->sock,
-                          silc_client_connect_failure, ctx,
-                          0, 1, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-    return;
+    goto err;
   }
 
-  /* Send NEW_CLIENT packet to the server. We will become registered
-     to the SILC network after sending this packet and we will receive
-     client ID from the server. */
-  packet = silc_buffer_alloc(2 + 2 + strlen(client->username) + 
-                            strlen(client->realname));
-  silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet));
-  silc_buffer_format(packet,
-                    SILC_STR_UI_SHORT(strlen(client->username)),
-                    SILC_STR_UI_XNSTRING(client->username,
-                                         strlen(client->username)),
-                    SILC_STR_UI_SHORT(strlen(client->realname)),
-                    SILC_STR_UI_XNSTRING(client->realname,
-                                         strlen(client->realname)),
-                    SILC_STR_END);
+  if (conn->internal->params.detach_data) {
+    /* Send RESUME_CLIENT packet to the server, which is used to resume
+       old detached session back. */
+    SilcBuffer auth;
+    SilcClientID *old_client_id;
+    unsigned char *old_id;
+    SilcUInt16 old_id_len;
+
+    if (!silc_client_process_detach_data(client, conn, &old_id, &old_id_len))
+      goto err;
+
+    old_client_id = silc_id_str2id(old_id, old_id_len, SILC_ID_CLIENT);
+    if (!old_client_id) {
+      silc_free(old_id);
+      goto err;
+    }
 
-  /* Send the packet */
-  silc_client_packet_send(client, ctx->sock, SILC_PACKET_NEW_CLIENT,
-                         NULL, 0, NULL, NULL, 
-                         packet->data, packet->len, TRUE);
-  silc_buffer_free(packet);
+    /* Generate authentication data that server will verify */
+    auth = silc_auth_public_key_auth_generate(client->public_key,
+                                             client->private_key,
+                                             client->rng,
+                                             conn->internal->hash,
+                                             old_client_id, SILC_ID_CLIENT);
+    if (!auth) {
+      silc_free(old_client_id);
+      silc_free(old_id);
+      goto err;
+    }
+
+    packet = silc_buffer_alloc_size(2 + old_id_len + auth->len);
+    silc_buffer_format(packet,
+                      SILC_STR_UI_SHORT(old_id_len),
+                      SILC_STR_UI_XNSTRING(old_id, old_id_len),
+                      SILC_STR_UI_XNSTRING(auth->data, auth->len),
+                      SILC_STR_END);
+
+    /* Send the packet */
+    silc_client_packet_send(client, ctx->sock, SILC_PACKET_RESUME_CLIENT,
+                           NULL, 0, NULL, NULL, 
+                           packet->data, packet->len, TRUE);
+    silc_buffer_free(packet);
+    silc_buffer_free(auth);
+    silc_free(old_client_id);
+    silc_free(old_id);
+  } else {
+    /* Send NEW_CLIENT packet to the server. We will become registered
+       to the SILC network after sending this packet and we will receive
+       client ID from the server. */
+    packet = silc_buffer_alloc(2 + 2 + strlen(client->username) + 
+                              strlen(client->realname));
+    silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet));
+    silc_buffer_format(packet,
+                      SILC_STR_UI_SHORT(strlen(client->username)),
+                      SILC_STR_UI_XNSTRING(client->username,
+                                           strlen(client->username)),
+                      SILC_STR_UI_SHORT(strlen(client->realname)),
+                      SILC_STR_UI_XNSTRING(client->realname,
+                                           strlen(client->realname)),
+                      SILC_STR_END);
+
+    /* Send the packet */
+    silc_client_packet_send(client, ctx->sock, SILC_PACKET_NEW_CLIENT,
+                           NULL, 0, NULL, NULL, 
+                           packet->data, packet->len, TRUE);
+    silc_buffer_free(packet);
+  }
 
   /* Save remote ID. */
   conn->remote_id = ctx->dest_id;
@@ -697,28 +864,42 @@ SILC_TASK_CALLBACK(silc_client_connect_to_server_final)
   conn->remote_id_data_len = silc_id_get_len(ctx->dest_id, SILC_ID_SERVER);
 
   /* Register re-key timeout */
-  conn->rekey->timeout = client->internal->params->rekey_secs;
-  conn->rekey->context = (void *)client;
+  conn->internal->rekey->timeout = client->internal->params->rekey_secs;
+  conn->internal->rekey->context = (void *)client;
   silc_schedule_task_add(client->schedule, conn->sock->sock, 
                         silc_client_rekey_callback,
-                        (void *)conn->sock, conn->rekey->timeout, 0,
+                        (void *)conn->sock, conn->internal->rekey->timeout, 0,
                         SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
 
   silc_protocol_free(protocol);
-  if (ctx->auth_data)
-    silc_free(ctx->auth_data);
+  silc_free(ctx->auth_data);
   if (ctx->ske)
     silc_ske_free(ctx->ske);
   silc_socket_free(ctx->sock);
   silc_free(ctx);
   conn->sock->protocol = NULL;
+  return;
+
+ err:
+  silc_protocol_free(protocol);
+  silc_free(ctx->auth_data);
+  silc_free(ctx->dest_id);
+  if (ctx->ske)
+    silc_ske_free(ctx->ske);
+  conn->sock->protocol = NULL;
+  silc_socket_free(ctx->sock);
+
+  /* Notify application of failure */
+  silc_schedule_task_add(client->schedule, ctx->sock->sock,
+                        silc_client_connect_failure_auth, ctx,
+                        0, 1, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
 }
 
 /* Internal routine that sends packet or marks packet to be sent. This
    is used directly only in special cases. Normal cases should use
    silc_server_packet_send. Returns < 0 on error. */
 
-int silc_client_packet_send_real(SilcClient client,
+bool silc_client_packet_send_real(SilcClient client,
                                 SilcSocketConnection sock,
                                 bool force_send)
 {
@@ -813,14 +994,14 @@ SILC_TASK_CALLBACK_GLOBAL(silc_client_packet_process)
         close the connection */
       if (SILC_IS_DISCONNECTING(sock)) {
        if (sock == conn->sock && sock->type != SILC_SOCKET_TYPE_CLIENT)
-         client->internal->ops->disconnect(client, conn);
+         client->internal->ops->disconnected(client, conn, 0, NULL);
        silc_client_close_connection_real(client, sock, conn);
        return;
       }
       
       SILC_LOG_DEBUG(("EOF from connection %d", sock->sock));
       if (sock == conn->sock && sock->type != SILC_SOCKET_TYPE_CLIENT)
-       client->internal->ops->disconnect(client, conn);
+       client->internal->ops->disconnected(client, conn, 0, NULL);
       silc_client_close_connection_real(client, sock, conn);
       return;
     }
@@ -828,8 +1009,9 @@ SILC_TASK_CALLBACK_GLOBAL(silc_client_packet_process)
     /* Process the packet. This will call the parser that will then
        decrypt and parse the packet. */
     if (sock->type != SILC_SOCKET_TYPE_UNKNOWN)
-      silc_packet_receive_process(sock, FALSE, conn->receive_key, 
-                                 conn->hmac_receive, conn->psn_receive,
+      silc_packet_receive_process(sock, FALSE, conn->internal->receive_key, 
+                                 conn->internal->hmac_receive,
+                                 conn->internal->psn_receive,
                                  silc_client_packet_parse, client);
     else
       silc_packet_receive_process(sock, FALSE, NULL, NULL, 0, 
@@ -849,14 +1031,14 @@ static bool silc_client_packet_parse(SilcPacketParserContext *parser_context,
   SilcPacketContext *packet = parser_context->packet;
   SilcPacketType ret;
 
-  if (conn && conn->hmac_receive && conn->sock == sock)
-    conn->psn_receive = parser_context->packet->sequence + 1;
+  if (conn && conn->internal->hmac_receive && conn->sock == sock)
+    conn->internal->psn_receive = parser_context->packet->sequence + 1;
 
   /* Parse the packet immediately */
   if (parser_context->normal)
-    ret = silc_packet_parse(packet, conn->receive_key);
+    ret = silc_packet_parse(packet, conn->internal->receive_key);
   else
-    ret = silc_packet_parse_special(packet, conn->receive_key);
+    ret = silc_packet_parse_special(packet, conn->internal->receive_key);
 
   if (ret == SILC_PACKET_NONE) {
     silc_packet_context_free(packet);
@@ -879,11 +1061,12 @@ static bool silc_client_packet_parse(SilcPacketParserContext *parser_context,
     silc_free(parser_context);
 
     /* Reprocess the buffer since we'll return FALSE. This is because
-       the `conn->receive_key' might have become valid by processing
+       the `conn->internal->receive_key' might have become valid by processing
        the previous packet */
     if (sock->type != SILC_SOCKET_TYPE_UNKNOWN)
-      silc_packet_receive_process(sock, FALSE, conn->receive_key, 
-                                 conn->hmac_receive, conn->psn_receive,
+      silc_packet_receive_process(sock, FALSE, conn->internal->receive_key, 
+                                 conn->internal->hmac_receive,
+                                 conn->internal->psn_receive,
                                  silc_client_packet_parse, client);
     else
       silc_packet_receive_process(sock, FALSE, NULL, NULL, 0, 
@@ -909,13 +1092,15 @@ void silc_client_packet_parse_type(SilcClient client,
   SilcBuffer buffer = packet->buffer;
   SilcPacketType type = packet->type;
 
-  SILC_LOG_DEBUG(("Parsing packet type %d", type));
+  SILC_LOG_DEBUG(("Parsing %s packet", silc_get_packet_name(type)));
 
   /* Parse the packet type */
   switch(type) {
+
   case SILC_PACKET_DISCONNECT:
     silc_client_disconnected_by_server(client, sock, buffer);
     break;
+
   case SILC_PACKET_SUCCESS:
     /*
      * Success received for something. For now we can have only
@@ -925,6 +1110,7 @@ void silc_client_packet_parse_type(SilcClient client,
     if (sock->protocol)
       silc_protocol_execute(sock->protocol, client->schedule, 0, 0);
     break;
+
   case SILC_PACKET_FAILURE:
     /*
      * Failure received for some protocol. Set the protocol state to 
@@ -933,6 +1119,7 @@ void silc_client_packet_parse_type(SilcClient client,
      */
     silc_client_process_failure(client, sock, packet);
     break;
+
   case SILC_PACKET_REJECT:
     break;
 
@@ -956,6 +1143,7 @@ void silc_client_packet_parse_type(SilcClient client,
      */
     silc_client_channel_message(client, sock, packet);
     break;
+
   case SILC_PACKET_CHANNEL_KEY:
     /*
      * Received key for a channel. By receiving this key the client will be
@@ -971,12 +1159,21 @@ void silc_client_packet_parse_type(SilcClient client,
      */
     silc_client_private_message(client, sock, packet);
     break;
+
   case SILC_PACKET_PRIVATE_MESSAGE_KEY:
     /*
      * Received private message key
      */
     break;
 
+  case SILC_PACKET_COMMAND:
+    /*
+     * Received command packet, a special case since normally client
+     * does not receive commands.
+     */
+    silc_client_command_process(client, sock, packet);
+    break;
+
   case SILC_PACKET_COMMAND_REPLY:
     /*
      * Recived reply for a command
@@ -1043,6 +1240,7 @@ void silc_client_packet_parse_type(SilcClient client,
                      "protocol active, packet dropped."));
     }
     break;
+
   case SILC_PACKET_KEY_EXCHANGE_2:
     if (sock->protocol && sock->protocol->protocol && 
        (sock->protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE ||
@@ -1186,7 +1384,7 @@ void silc_client_packet_send(SilcClient client,
                             SilcHmac hmac,
                             unsigned char *data, 
                             SilcUInt32 data_len, 
-                            int force_send)
+                            bool force_send)
 {
   SilcPacketContext packetdata;
   const SilcBufferStruct packet;
@@ -1200,11 +1398,11 @@ void silc_client_packet_send(SilcClient client,
 
   /* Get data used in the packet sending, keys and stuff */
   if ((!cipher || !hmac || !dst_id) && sock->user_data) {
-    if (!cipher && ((SilcClientConnection)sock->user_data)->send_key)
-      cipher = ((SilcClientConnection)sock->user_data)->send_key;
+    if (!cipher && ((SilcClientConnection)sock->user_data)->internal->send_key)
+      cipher = ((SilcClientConnection)sock->user_data)->internal->send_key;
 
-    if (!hmac && ((SilcClientConnection)sock->user_data)->hmac_send)
-      hmac = ((SilcClientConnection)sock->user_data)->hmac_send;
+    if (!hmac && ((SilcClientConnection)sock->user_data)->internal->hmac_send)
+      hmac = ((SilcClientConnection)sock->user_data)->internal->hmac_send;
 
     if (!dst_id && ((SilcClientConnection)sock->user_data)->remote_id) {
       dst_id = ((SilcClientConnection)sock->user_data)->remote_id;
@@ -1212,7 +1410,13 @@ void silc_client_packet_send(SilcClient client,
     }
 
     if (hmac)
-      sequence = ((SilcClientConnection)sock->user_data)->psn_send++;
+      sequence = ((SilcClientConnection)sock->user_data)->internal->psn_send++;
+
+    /* Check for mandatory rekey */
+    if (sequence == SILC_CLIENT_REKEY_THRESHOLD)
+      silc_schedule_task_add(client->schedule, sock->sock,
+                            silc_client_rekey_callback, sock, 0, 1,
+                            SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
   }
 
   block_len = cipher ? silc_cipher_get_block_len(cipher) : 0;
@@ -1245,7 +1449,10 @@ void silc_client_packet_send(SilcClient client,
                                            packetdata.dst_id_len));
   packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + 
     packetdata.src_id_len + packetdata.dst_id_len;
-  packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen, block_len);
+  if (type == SILC_PACKET_CONNECTION_AUTH)
+    SILC_PACKET_PADLEN_MAX(packetdata.truelen, block_len, packetdata.padlen);
+  else
+    SILC_PACKET_PADLEN(packetdata.truelen, block_len, packetdata.padlen);
 
   /* Create the outgoing packet */
   if (!silc_packet_assemble(&packetdata, client->rng, cipher, hmac, sock, 
@@ -1266,6 +1473,25 @@ void silc_client_packet_send(SilcClient client,
   silc_client_packet_send_real(client, sock, force_send);
 }
 
+/* Packet sending routine for application.  This is the only routine that
+   is provided for application to send SILC packets. */
+
+bool silc_client_send_packet(SilcClient client,
+                            SilcClientConnection conn,
+                            SilcPacketType type,
+                            const unsigned char *data,
+                            SilcUInt32 data_len)
+{
+
+  assert(client);
+  if (!conn)
+    return FALSE;
+
+  silc_client_packet_send(client, conn->sock, type, NULL, 0, NULL, NULL,
+                         (unsigned char *)data, data_len, TRUE);
+  return TRUE;
+}
+
 void silc_client_packet_queue_purge(SilcClient client,
                                    SilcSocketConnection sock)
 {
@@ -1294,6 +1520,9 @@ void silc_client_close_connection_real(SilcClient client,
 
   SILC_LOG_DEBUG(("Start"));
 
+  if (!sock && !conn)
+    return;
+
   if (!sock || (sock && conn->sock == sock))
     del = TRUE;
   if (!sock)
@@ -1304,7 +1533,6 @@ void silc_client_close_connection_real(SilcClient client,
 
   /* Unregister all tasks */
   silc_schedule_task_del_by_fd(client->schedule, sock->sock);
-  silc_schedule_task_del_by_fd(client->schedule, sock->sock);
 
   /* Close the actual connection */
   silc_net_close_connection(sock->sock);
@@ -1318,7 +1546,7 @@ void silc_client_close_connection_real(SilcClient client,
       sock->protocol->state = SILC_PROTOCOL_STATE_ERROR;
       silc_protocol_execute_final(sock->protocol, client->schedule);
       /* The application will recall this function with these protocols
-        (the ops->connect client operation). */
+        (the ops->connected client operation). */
       return;
     } else {
       sock->protocol->state = SILC_PROTOCOL_STATE_ERROR;
@@ -1328,77 +1556,8 @@ void silc_client_close_connection_real(SilcClient client,
   }
 
   /* Free everything */
-  if (del && sock->user_data) {
-    /* Free all cache entries */
-    SilcIDCacheList list;
-    SilcIDCacheEntry entry;
-    bool ret;
-
-    if (silc_idcache_get_all(conn->client_cache, &list)) {
-      ret = silc_idcache_list_first(list, &entry);
-      while (ret) {
-       silc_client_del_client(client, conn, entry->context);
-       ret = silc_idcache_list_next(list, &entry);
-      }
-      silc_idcache_list_free(list);
-    }
-
-    if (silc_idcache_get_all(conn->channel_cache, &list)) {
-      ret = silc_idcache_list_first(list, &entry);
-      while (ret) {
-       silc_client_del_channel(client, conn, entry->context);
-       ret = silc_idcache_list_next(list, &entry);
-      }
-      silc_idcache_list_free(list);
-    }
-
-    if (silc_idcache_get_all(conn->server_cache, &list)) {
-      ret = silc_idcache_list_first(list, &entry);
-      while (ret) {
-       silc_client_del_server(client, conn, entry->context);
-       ret = silc_idcache_list_next(list, &entry);
-      }
-      silc_idcache_list_free(list);
-    }
-
-    /* Clear ID caches */
-    if (conn->client_cache)
-      silc_idcache_free(conn->client_cache);
-    if (conn->channel_cache)
-      silc_idcache_free(conn->channel_cache);
-    if (conn->server_cache)
-      silc_idcache_free(conn->server_cache);
-
-    /* Free data (my ID is freed in above silc_client_del_client).
-       conn->nickname is freed when freeing the local_entry->nickname. */
-    if (conn->remote_host)
-      silc_free(conn->remote_host);
-    if (conn->local_id_data)
-      silc_free(conn->local_id_data);
-    if (conn->send_key)
-      silc_cipher_free(conn->send_key);
-    if (conn->receive_key)
-      silc_cipher_free(conn->receive_key);
-    if (conn->hmac_send)
-      silc_hmac_free(conn->hmac_send);
-    if (conn->hmac_receive)
-      silc_hmac_free(conn->hmac_receive);
-    if (conn->pending_commands)
-      silc_dlist_uninit(conn->pending_commands);
-    if (conn->rekey)
-      silc_free(conn->rekey);
-
-    if (conn->active_session) {
-      sock->user_data = NULL;
-      silc_client_ftp_session_free(conn->active_session);
-      conn->active_session = NULL;
-    }
-
-    silc_client_ftp_free_sessions(client, conn);
-
-    memset(conn, 0, sizeof(*conn));
+  if (del && sock->user_data)
     silc_client_del_connection(client, conn);
-  }
 
   silc_socket_free(sock);
 }
@@ -1433,16 +1592,28 @@ SILC_TASK_CALLBACK(silc_client_disconnected_by_server_later)
 
 void silc_client_disconnected_by_server(SilcClient client,
                                        SilcSocketConnection sock,
-                                       SilcBuffer message)
+                                       SilcBuffer packet)
 {
-  char *msg;
+  SilcClientConnection conn;
+  SilcStatus status;
+  char *message = NULL;
 
   SILC_LOG_DEBUG(("Server disconnected us, sock %d", sock->sock));
 
-  msg = silc_memdup(message->data, message->len);
-  client->internal->ops->say(client, sock->user_data, 
-                            SILC_CLIENT_MESSAGE_AUDIT, msg);
-  silc_free(msg);
+  if (packet->len < 1)
+    return;
+
+  status = (SilcStatus)packet->data[0];
+
+  if (packet->len > 1 &&
+      silc_utf8_valid(packet->data + 1, packet->len - 1))
+    message = silc_memdup(packet->data + 1, packet->len - 1);
+
+  conn = (SilcClientConnection)sock->user_data;
+  if (sock == conn->sock && sock->type != SILC_SOCKET_TYPE_CLIENT)
+    client->internal->ops->disconnected(client, conn, status, message);
+
+  silc_free(message);
 
   SILC_SET_DISCONNECTED(sock);
 
@@ -1474,10 +1645,40 @@ SILC_TASK_CALLBACK(silc_client_send_auto_nick)
 {
   SilcClientConnection conn = (SilcClientConnection)context;
   SilcClient client = conn->client;
+  if (client)
+    silc_client_command_send(client, conn, SILC_COMMAND_NICK, 
+                            ++conn->cmd_ident, 1, 1, 
+                            client->nickname, strlen(client->nickname));
+}
+
+/* Client session resuming callback.  If the session was resumed
+   this callback is called after the resuming is completed.  This
+   will call the `connect' client operation to the application
+   since it has not been called yet. */
+
+static void silc_client_resume_session_cb(SilcClient client,
+                                         SilcClientConnection conn,
+                                         bool success,
+                                         void *context)
+{
+  SilcBuffer sidp;
+
+  /* Notify application that connection is created to server */
+  client->internal->ops->connected(client, conn, success ?
+                                  SILC_CLIENT_CONN_SUCCESS_RESUME :
+                                  SILC_CLIENT_CONN_ERROR);
 
-  silc_client_command_send(client, conn, SILC_COMMAND_NICK, 
-                          ++conn->cmd_ident, 1, 1, 
-                          client->nickname, strlen(client->nickname));
+  if (success) {
+    /* Issue INFO command to fetch the real server name and server
+       information and other stuff. */
+    silc_client_command_register(client, SILC_COMMAND_INFO, NULL, NULL,
+                                silc_client_command_reply_info_i, 0, 
+                                ++conn->cmd_ident);
+    sidp = silc_id_payload_encode(conn->remote_id, SILC_ID_SERVER);
+    silc_client_command_send(client, conn, SILC_COMMAND_INFO,
+                            conn->cmd_ident, 1, 2, sidp->data, sidp->len);
+    silc_buffer_free(sidp);
+  }
 }
 
 /* Processes the received new Client ID from server. Old Client ID is
@@ -1490,7 +1691,6 @@ void silc_client_receive_new_id(SilcClient client,
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
   int connecting = FALSE;
   SilcClientID *client_id = silc_id_payload_get_id(idp);
-  SilcBuffer sidp;
 
   if (!conn->local_entry)
     connecting = TRUE;
@@ -1503,7 +1703,8 @@ void silc_client_receive_new_id(SilcClient client,
       return;
     }
 
-    silc_idcache_del_by_context(conn->client_cache, conn->local_entry);
+    silc_idcache_del_by_context(conn->internal->client_cache,
+                               conn->local_entry);
     silc_free(conn->local_id);
   }
   
@@ -1522,8 +1723,6 @@ void silc_client_receive_new_id(SilcClient client,
   conn->local_entry->nickname = conn->nickname;
   if (!conn->local_entry->username)
     conn->local_entry->username = strdup(client->username);
-  if (!conn->local_entry->hostname)
-    conn->local_entry->hostname = strdup(client->hostname);
   if (!conn->local_entry->server)
     conn->local_entry->server = strdup(conn->remote_host);
   conn->local_entry->id = conn->local_id;
@@ -1535,30 +1734,52 @@ void silc_client_receive_new_id(SilcClient client,
                                                        TRUE);
 
   /* Put it to the ID cache */
-  silc_idcache_add(conn->client_cache, strdup(conn->nickname), conn->local_id, 
+  silc_idcache_add(conn->internal->client_cache,
+                  strdup(conn->nickname), conn->local_id, 
                   (void *)conn->local_entry, 0, NULL);
 
   if (connecting) {
-    /* 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 && strcmp(client->nickname, client->username))
-      silc_schedule_task_add(client->schedule, 0,
-                            silc_client_send_auto_nick, conn,
-                            1, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-
-    /* Issue INFO command to fetch the real server name and server information
-       and other stuff. */
-    silc_client_command_register(client, SILC_COMMAND_INFO, NULL, NULL,
-                                silc_client_command_reply_info_i, 0, 
+    SilcBuffer sidp;
+
+    /* Issue IDENTIFY command for itself to get resolved hostname
+       correctly from server. */
+    silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
+                                silc_client_command_reply_identify_i, 0, 
                                 ++conn->cmd_ident);
-    sidp = silc_id_payload_encode(conn->remote_id, SILC_ID_SERVER);
-    silc_client_command_send(client, conn, SILC_COMMAND_INFO,
-                            conn->cmd_ident, 1, 2, sidp->data, sidp->len);
+    sidp = silc_id_payload_encode(conn->local_entry->id, SILC_ID_CLIENT);
+    silc_client_command_send(client, conn, SILC_COMMAND_IDENTIFY,
+                            conn->cmd_ident, 1, 5, sidp->data, sidp->len);
     silc_buffer_free(sidp);
 
-    /* Notify application of successful connection. We do it here now that
-       we've received the Client ID and are allowed to send traffic. */
-    client->internal->ops->connect(client, conn, TRUE);
+    if (!conn->internal->params.detach_data) {
+      /* 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 && strcmp(client->nickname, client->username))
+       silc_schedule_task_add(client->schedule, 0,
+                              silc_client_send_auto_nick, conn,
+                              1, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+
+      /* Notify application of successful connection. We do it here now that
+        we've received the Client ID and are allowed to send traffic. */
+      client->internal->ops->connected(client, conn, SILC_CLIENT_CONN_SUCCESS);
+
+      /* Issue INFO command to fetch the real server name and server
+        information and other stuff. */
+      silc_client_command_register(client, SILC_COMMAND_INFO, NULL, NULL,
+                                  silc_client_command_reply_info_i, 0, 
+                                  ++conn->cmd_ident);
+      sidp = silc_id_payload_encode(conn->remote_id, SILC_ID_SERVER);
+      silc_client_command_send(client, conn, SILC_COMMAND_INFO,
+                              conn->cmd_ident, 1, 2, sidp->data, sidp->len);
+      silc_buffer_free(sidp);
+    } else {
+      /* We are resuming session.  Start resolving informations from the
+        server we need to set the client libary in the state before
+        detaching the session.  The connect client operation is called
+        after this is successfully completed */
+      silc_client_resume_session(client, conn, silc_client_resume_session_cb,
+                                NULL);
+    }
   }
 }
 
@@ -1572,7 +1793,7 @@ void silc_client_remove_from_channels(SilcClient client,
   SilcChannelUser chu;
 
   silc_hash_table_list(client_entry->channels, &htl);
-  while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+  while (silc_hash_table_get(&htl, NULL, (void **)&chu)) {
     silc_hash_table_del(chu->client->channels, chu->channel);
     silc_hash_table_del(chu->channel->user_list, chu->client);
     silc_free(chu);
@@ -1595,7 +1816,7 @@ void silc_client_replace_from_channels(SilcClient client,
   SilcChannelUser chu;
 
   silc_hash_table_list(old->channels, &htl);
-  while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+  while (silc_hash_table_get(&htl, NULL, (void **)&chu)) {
     /* Replace client entry */
     silc_hash_table_del(chu->client->channels, chu->channel);
     silc_hash_table_del(chu->channel->user_list, chu->client);
@@ -1629,11 +1850,11 @@ void silc_client_process_failure(SilcClient client,
 /* A timeout callback for the re-key. We will be the initiator of the
    re-key protocol. */
 
-SILC_TASK_CALLBACK(silc_client_rekey_callback)
+SILC_TASK_CALLBACK_GLOBAL(silc_client_rekey_callback)
 {
   SilcSocketConnection sock = (SilcSocketConnection)context;
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcClient client = (SilcClient)conn->rekey->context;
+  SilcClient client = (SilcClient)conn->internal->rekey->context;
   SilcProtocol protocol;
   SilcClientRekeyInternalContext *proto_ctx;
 
@@ -1645,7 +1866,7 @@ SILC_TASK_CALLBACK(silc_client_rekey_callback)
   proto_ctx->client = (void *)client;
   proto_ctx->sock = silc_socket_dup(sock);
   proto_ctx->responder = FALSE;
-  proto_ctx->pfs = conn->rekey->pfs;
+  proto_ctx->pfs = conn->internal->rekey->pfs;
       
   /* Perform rekey protocol. Will call the final callback after the
      protocol is over. */
@@ -1659,7 +1880,7 @@ SILC_TASK_CALLBACK(silc_client_rekey_callback)
   /* Re-register re-key timeout */
   silc_schedule_task_add(client->schedule, sock->sock, 
                         silc_client_rekey_callback,
-                        context, conn->rekey->timeout, 0,
+                        context, conn->internal->rekey->timeout, 0,
                         SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
 }
 
@@ -1720,7 +1941,7 @@ void silc_client_connection_auth_request(SilcClient client,
   int ret;
 
   /* If we haven't send our request then ignore this one. */
-  if (!conn->connauth)
+  if (!conn->internal->connauth)
     return;
 
   /* Parse the payload */
@@ -1733,14 +1954,14 @@ void silc_client_connection_auth_request(SilcClient client,
 
   /* Call the request callback to notify application for received 
      authentication method information. */
-  if (conn->connauth->callback)
-    (*conn->connauth->callback)(client, conn, auth_meth,
-                               conn->connauth->context);
+  if (conn->internal->connauth->callback)
+    (*conn->internal->connauth->callback)(client, conn, auth_meth,
+                                         conn->internal->connauth->context);
 
-  silc_schedule_task_del(client->schedule, conn->connauth->timeout);
+  silc_schedule_task_del(client->schedule, conn->internal->connauth->timeout);
 
-  silc_free(conn->connauth);
-  conn->connauth = NULL;
+  silc_free(conn->internal->connauth);
+  conn->internal->connauth = NULL;
 }
 
 /* Timeout task callback called if the server does not reply to our 
@@ -1751,16 +1972,16 @@ SILC_TASK_CALLBACK(silc_client_request_authentication_method_timeout)
   SilcClientConnection conn = (SilcClientConnection)context;
   SilcClient client = conn->client;
 
-  if (!conn->connauth)
+  if (!conn->internal->connauth)
     return;
 
   /* Call the request callback to notify application */
-  if (conn->connauth->callback)
-    (*conn->connauth->callback)(client, conn, SILC_AUTH_NONE,
-                               conn->connauth->context);
+  if (conn->internal->connauth->callback)
+    (*conn->internal->connauth->callback)(client, conn, SILC_AUTH_NONE,
+                                         conn->internal->connauth->context);
 
-  silc_free(conn->connauth);
-  conn->connauth = NULL;
+  silc_free(conn->internal->connauth);
+  conn->internal->connauth = NULL;
 }
 
 /* This function can be used to request the current authentication method
@@ -1780,14 +2001,15 @@ silc_client_request_authentication_method(SilcClient client,
   SilcClientConnAuthRequest connauth;
   SilcBuffer packet;
 
+  assert(client && conn);
   connauth = silc_calloc(1, sizeof(*connauth));
   connauth->callback = callback;
   connauth->context = context;
 
-  if (conn->connauth)
-    silc_free(conn->connauth);
+  if (conn->internal->connauth)
+    silc_free(conn->internal->connauth);
 
-  conn->connauth = connauth;
+  conn->internal->connauth = connauth;
 
   /* Assemble the request packet and send it to the server */
   packet = silc_buffer_alloc(4);