Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client.c
index 9a64a6c0364c6a2282e85d94b9da706857f4381a..449fc375ce152b33bca4e0c3bbd06ce53870766d 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2006 Pekka Riikonen
+  Copyright (C) 1997 - 2008 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
   GNU General Public License for more details.
 
 */
-/* $Id$ */
 
 #include "silc.h"
 #include "silcclient.h"
 #include "client_internal.h"
 
-/************************** Types and definitions ***************************/
-
-
 /************************ Static utility functions **************************/
 
 /* Connection machine FSM destructor.  This will finish the thread where
@@ -37,13 +33,34 @@ static void silc_client_connection_destructor(SilcFSM fsm,
   SilcClientConnection conn = fsm_context;
   SilcFSMThread thread = destructor_context;
 
+  SILC_LOG_DEBUG(("Connection %p finished", conn));
+
   /* Delete connection */
   silc_client_del_connection(conn->client, conn);
 
-  /* Finish the thread were this machine was running */
+  /* Finish the thread were this machine was running.  Its destructor is the
+     silc_client_connection_finished. */
   silc_fsm_finish(thread);
 }
 
+/* Connection thread FSM destructor.  This was the thread where the connection
+   machine was running (may be real thread).  From here we notify client
+   that the connection thread has finished. */
+
+static void silc_client_connection_finished(SilcFSMThread fsm,
+                                           void *fsm_context,
+                                           void *destructor_context)
+{
+  SilcClient client = silc_fsm_get_state_context(fsm);
+
+  /* Signal client that we have finished */
+  silc_atomic_sub_int32(&client->internal->conns, 1);
+  client->internal->connection_closed = TRUE;
+  SILC_FSM_EVENT_SIGNAL(&client->internal->wait_event);
+
+  silc_fsm_free(fsm);
+}
+
 /* Packet FSM thread destructor */
 
 static void silc_client_packet_destructor(SilcFSMThread thread,
@@ -78,10 +95,8 @@ static SilcBool silc_client_packet_receive(SilcPacketEngine engine,
   case SILC_PACKET_KEY_EXCHANGE:
   case SILC_PACKET_KEY_EXCHANGE_1:
   case SILC_PACKET_KEY_EXCHANGE_2:
-  case SILC_PACKET_REKEY:
   case SILC_PACKET_REKEY_DONE:
   case SILC_PACKET_CONNECTION_AUTH:
-  case SILC_PACKET_CONNECTION_AUTH_REQUEST:
     return FALSE;
     break;
   }
@@ -113,7 +128,16 @@ static void silc_client_packet_eos(SilcPacketEngine engine,
                                   void *callback_context,
                                   void *stream_context)
 {
-  SILC_LOG_DEBUG(("End of stream received"));
+  SilcClientConnection conn = stream_context;
+
+  SILC_LOG_DEBUG(("Remote disconnected connection"));
+
+  /* Signal to close connection */
+  conn->internal->status = SILC_CLIENT_CONN_DISCONNECTED;
+  if (!conn->internal->disconnected) {
+    conn->internal->disconnected = TRUE;
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+  }
 }
 
 /* Packet engine callback to indicate error */
@@ -124,7 +148,15 @@ static void silc_client_packet_error(SilcPacketEngine engine,
                                     void *callback_context,
                                     void *stream_context)
 {
+  SilcClient client = callback_context;
+  SilcClientConnection conn = stream_context;
+
+  /* Read and write errors are silent */
+  if (error == SILC_PACKET_ERR_READ || error == SILC_PACKET_ERR_WRITE)
+    return;
 
+  client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+                            (char *)silc_packet_error_string(error));
 }
 
 /* Packet stream callbacks */
@@ -143,6 +175,28 @@ void silc_client_fsm_destructor(SilcFSM fsm, void *fsm_context,
   silc_fsm_free(fsm);
 }
 
+/* Connect abort operation */
+
+static void silc_client_connect_abort(SilcAsyncOperation op, void *context)
+{
+  SilcClientConnection conn = context;
+
+  SILC_LOG_DEBUG(("Connection %p aborted by application", conn));
+
+  /* Connection callback will not be called after user aborted connecting */
+  conn->callback = NULL;
+  conn->internal->cop = NULL;
+
+  /* Signal to close connection */
+  if (!conn->internal->disconnected) {
+    conn->internal->disconnected = TRUE;
+
+    /* If user aborts before connection machine is even up yet, then don't
+       send signal yet.  It will process this event when it comes up. */
+    if (silc_fsm_is_started(&conn->internal->fsm))
+      SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+  }
+}
 
 /************************** Connection's machine ****************************/
 
@@ -161,14 +215,16 @@ SILC_FSM_STATE(silc_client_connection_st_start)
   connfsm = &conn->internal->fsm;
   silc_fsm_init(connfsm, conn, silc_client_connection_destructor,
                fsm, conn->internal->schedule);
-  silc_fsm_sema_init(&conn->internal->wait_event, connfsm, 0);
+  silc_fsm_event_init(&conn->internal->wait_event, connfsm);
   silc_fsm_start_sync(connfsm, silc_client_connection_st_run);
 
-  /* Schedule any events set in initialization */
+  /* Schedule any events possibly set in initialization */
+  if (conn->internal->disconnected)
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
   if (conn->internal->connect)
-    SILC_FSM_SEMA_POST(&conn->internal->wait_event);
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
   if (conn->internal->key_exchange)
-    SILC_FSM_SEMA_POST(&conn->internal->wait_event);
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
 
   /* Wait until this thread is terminated from the machine destructor */
   return SILC_FSM_WAIT;
@@ -184,14 +240,22 @@ SILC_FSM_STATE(silc_client_connection_st_run)
   SilcFSMThread thread;
 
   /* Wait for events */
-  SILC_FSM_SEMA_WAIT(&conn->internal->wait_event);
+  SILC_FSM_EVENT_WAIT(&conn->internal->wait_event);
 
   /* Process events */
   thread = &conn->internal->event_thread;
 
+  if (conn->internal->disconnected) {
+    /** Event: disconnected */
+    SILC_LOG_DEBUG(("Event: disconnected"));
+    silc_fsm_next(fsm, silc_client_connection_st_close);
+    return SILC_FSM_YIELD;
+  }
+
   if (conn->internal->connect) {
     SILC_LOG_DEBUG(("Event: connect"));
     conn->internal->connect = FALSE;
+    SILC_ASSERT(silc_fsm_is_started(thread) == FALSE);
 
     /*** Event: connect */
     silc_fsm_thread_init(thread, &conn->internal->fsm, conn,
@@ -203,6 +267,7 @@ SILC_FSM_STATE(silc_client_connection_st_run)
   if (conn->internal->key_exchange) {
     SILC_LOG_DEBUG(("Event: key exchange"));
     conn->internal->key_exchange = FALSE;
+    SILC_ASSERT(silc_fsm_is_started(thread) == FALSE);
 
     /*** Event: key exchange */
     silc_fsm_thread_init(thread, &conn->internal->fsm, conn,
@@ -211,11 +276,15 @@ SILC_FSM_STATE(silc_client_connection_st_run)
     return SILC_FSM_CONTINUE;
   }
 
-  if (conn->internal->disconnected) {
-    /** Event: disconnected */
-    SILC_LOG_DEBUG(("Event: disconnected"));
-    conn->internal->disconnected = FALSE;
-    silc_fsm_next(fsm, silc_client_connection_st_close);
+  if (conn->internal->rekeying) {
+    SILC_LOG_DEBUG(("Event: rekey"));
+    conn->internal->rekeying = FALSE;
+    SILC_ASSERT(silc_fsm_is_started(thread) == FALSE);
+
+    /*** Event: rekey */
+    silc_fsm_thread_init(thread, &conn->internal->fsm, conn,
+                        NULL, NULL, FALSE);
+    silc_fsm_start_sync(thread, silc_client_st_rekey);
     return SILC_FSM_CONTINUE;
   }
 
@@ -229,6 +298,7 @@ SILC_FSM_STATE(silc_client_connection_st_run)
 
 SILC_FSM_STATE(silc_client_connection_st_packet)
 {
+  SilcClientConnection conn = fsm_context;
   SilcPacket packet = state_context;
 
   SILC_LOG_DEBUG(("Parsing %s packet", silc_get_packet_name(packet->type)));
@@ -247,7 +317,7 @@ SILC_FSM_STATE(silc_client_connection_st_packet)
 
   case SILC_PACKET_FTP:
     /* File transfer packet */
-    //    silc_client_ftp(client, conn, packet);
+    silc_fsm_next(fsm, silc_client_ftp);
     break;
 
   case SILC_PACKET_CHANNEL_KEY:
@@ -281,8 +351,8 @@ SILC_FSM_STATE(silc_client_connection_st_packet)
     break;
 
   case SILC_PACKET_KEY_AGREEMENT:
-    /* Key agreement */
-    //    silc_client_key_agreement(client, conn, packet);
+    /** Key agreement */
+    silc_fsm_next(fsm, silc_client_key_agreement);
     break;
 
   case SILC_PACKET_COMMAND:
@@ -293,11 +363,21 @@ SILC_FSM_STATE(silc_client_connection_st_packet)
   case SILC_PACKET_NEW_ID:
     /** New ID */
     silc_fsm_next(fsm, silc_client_new_id);
+    break;
 
   case SILC_PACKET_CONNECTION_AUTH_REQUEST:
-    /* Reply to connection authentication request to resolve authentication
-       method from server. */
-    //    silc_client_connection_auth_request(client, conn, packet);
+    /** Connection auth resolve reply */
+    silc_fsm_next(fsm, silc_client_connect_auth_request);
+    break;
+
+  case SILC_PACKET_REKEY:
+    /* Signal to start rekey */
+    conn->internal->rekey_responder = TRUE;
+    conn->internal->rekeying = TRUE;
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+
+    silc_packet_free(packet);
+    return SILC_FSM_FINISH;
     break;
 
   default:
@@ -309,24 +389,61 @@ SILC_FSM_STATE(silc_client_connection_st_packet)
   return SILC_FSM_CONTINUE;
 }
 
-/* Disconnection even to close remote connection.  We close the connection
+/* Disconnection event to close remote connection.  We close the connection
    and finish the connection machine in this state.  The connection context
-   is deleted in the machine destructor.  The connection callback must be
-   already called back to application before getting here. */
+   is deleted in the machine destructor.  The connection callback is called
+   in this state if it is set. */
 
 SILC_FSM_STATE(silc_client_connection_st_close)
 {
   SilcClientConnection conn = fsm_context;
+  SilcClientCommandContext cmd;
+
+  /* Finish running command threads.  This will also finish waiting packet
+     thread, as they are always waiting for some command.  If any thread is
+     waiting something else than command, they must be finished explicitly. */
+  if (silc_list_count(conn->internal->pending_commands)) {
+    SILC_LOG_DEBUG(("Finish pending commands"));
+    silc_list_start(conn->internal->pending_commands);
+    while ((cmd = silc_list_get(conn->internal->pending_commands))) {
+      if (silc_fsm_is_started(&cmd->thread)) {
+        cmd->verbose = FALSE;
+        silc_fsm_continue_sync(&cmd->thread);
+      }
+    }
 
-  SILC_LOG_DEBUG(("Closing remote connection"));
+    /* Give threads time to finish */
+    return SILC_FSM_YIELD;
+  }
 
-  /* XXX abort any ongoing events (protocols) */
+  /* Abort ongoing event */
+  if (conn->internal->op) {
+    SILC_LOG_DEBUG(("Abort event"));
+    silc_async_abort(conn->internal->op, NULL, NULL);
+    conn->internal->op = NULL;
+  }
 
-  /* Close connection */
-  silc_packet_stream_destroy(conn->stream);
+  /* If event thread is running, finish it. */
+  if (silc_fsm_is_started(&conn->internal->event_thread)) {
+    SILC_LOG_DEBUG(("Finish event thread"));
+    silc_fsm_continue_sync(&conn->internal->event_thread);
+    return SILC_FSM_YIELD;
+  }
 
-  SILC_LOG_DEBUG(("Finishing connection machine"));
+  /* Call the connection callback */
+  if (conn->callback)
+    conn->callback(conn->client, conn, conn->internal->status,
+                  conn->internal->error, conn->internal->disconnect_message,
+                  conn->callback_context);
+  silc_free(conn->internal->disconnect_message);
+
+  SILC_LOG_DEBUG(("Closing remote connection"));
+
+  /* Close connection. */
+  if (conn->stream)
+    silc_packet_stream_destroy(conn->stream);
 
+  SILC_LOG_DEBUG(("Finishing connection machine"));
   return SILC_FSM_FINISH;
 }
 
@@ -356,7 +473,6 @@ SILC_FSM_STATE(silc_client_error)
 SILC_FSM_STATE(silc_client_disconnect)
 {
   SilcClientConnection conn = fsm_context;
-  SilcClient client = conn->client;
   SilcPacket packet = state_context;
   SilcStatus status;
   char *message = NULL;
@@ -378,15 +494,17 @@ SILC_FSM_STATE(silc_client_disconnect)
                          silc_buffer_len(&packet->buffer));
 
   /* Call connection callback */
-  conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, status,
-                message, conn->callback_context);
-
-  silc_free(message);
-  silc_packet_free(packet);
+  conn->internal->status = SILC_CLIENT_CONN_DISCONNECTED;
+  conn->internal->error = status;
+  conn->internal->disconnect_message = message;
 
   /* Signal to close connection */
-  conn->internal->disconnected = TRUE;
-  SILC_FSM_SEMA_POST(&conn->internal->wait_event);
+  if (!conn->internal->disconnected) {
+    conn->internal->disconnected = TRUE;
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+  }
+
+  silc_packet_free(packet);
 
   return SILC_FSM_FINISH;
 }
@@ -400,15 +518,37 @@ SILC_FSM_STATE(silc_client_st_run)
   SilcClient client = fsm_context;
 
   /* Wait for events */
-  SILC_FSM_SEMA_WAIT(&client->internal->wait_event);
+  SILC_FSM_EVENT_WAIT(&client->internal->wait_event);
 
   /* Process events */
 
-  if (client->internal->run_callback && client->internal->ops->running) {
+  if (client->internal->run_callback) {
     /* Call running callbcak back to application */
-    SILC_LOG_DEBUG(("We are running, call running callback"));
     client->internal->run_callback = FALSE;
-    client->internal->ops->running(client, client->application);
+    if (client->internal->running) {
+      SILC_LOG_DEBUG(("We are up, call running callback"));
+      client->internal->running(client, client->internal->running_context);
+    }
+    return SILC_FSM_CONTINUE;
+  }
+
+  if (client->internal->connection_closed) {
+    /* A connection finished */
+    SILC_LOG_DEBUG(("Event: connection closed"));
+    client->internal->connection_closed = FALSE;
+    if (silc_atomic_get_int32(&client->internal->conns) == 0 &&
+       client->internal->stop)
+      SILC_FSM_EVENT_SIGNAL(&client->internal->wait_event);
+    return SILC_FSM_CONTINUE;
+  }
+
+  if (client->internal->stop) {
+    /* Stop client libarry.  If we have running connections, wait until
+       they finish first. */
+    if (silc_atomic_get_int32(&client->internal->conns) == 0) {
+      SILC_LOG_DEBUG(("Event: stop"));
+      silc_fsm_next(fsm, silc_client_st_stop);
+    }
     return SILC_FSM_CONTINUE;
   }
 
@@ -417,13 +557,33 @@ SILC_FSM_STATE(silc_client_st_run)
   return SILC_FSM_CONTINUE;
 }
 
+/* Stop event.  Stops the client library. */
+
+SILC_FSM_STATE(silc_client_st_stop)
+{
+  SilcClient client = fsm_context;
+
+  SILC_LOG_DEBUG(("Client stopped"));
+
+  /* Stop scheduler */
+  silc_schedule_stop(client->schedule);
+  silc_client_commands_unregister(client);
+
+  /* Call stopped callback to application */
+  if (client->internal->running)
+    client->internal->running(client, client->internal->running_context);
+
+  return SILC_FSM_FINISH;
+}
+
 /******************************* Private API ********************************/
 
 /* Adds new connection.  Creates the connection context and returns it. */
 
-static SilcClientConnection
+SilcClientConnection
 silc_client_add_connection(SilcClient client,
                           SilcConnectionType conn_type,
+                          SilcBool connect,
                           SilcClientConnectionParams *params,
                           SilcPublicKey public_key,
                           SilcPrivateKey private_key,
@@ -457,139 +617,145 @@ silc_client_add_connection(SilcClient client,
     silc_free(conn);
     return NULL;
   }
+  conn->internal->retry_timer = SILC_CLIENT_RETRY_MIN;
+  silc_mutex_alloc(&conn->internal->lock);
+  silc_atomic_init16(&conn->internal->cmd_ident, 0);
 
   if (!silc_hash_alloc("sha1", &conn->internal->sha1hash)) {
     silc_free(conn);
     silc_free(conn->internal);
     return NULL;
   }
+
+  /* Set parameters */
   if (params)
     conn->internal->params = *params;
+  if (!conn->internal->params.rekey_secs)
+    conn->internal->params.rekey_secs = 3600;
+#ifndef SILC_DIST_INPLACE
+  if (conn->internal->params.rekey_secs < 300)
+    conn->internal->params.rekey_secs = 300;
+#endif /* SILC_DIST_INPLACE */
+
   conn->internal->verbose = TRUE;
   silc_list_init(conn->internal->pending_commands,
                 struct SilcClientCommandContextStruct, next);
   silc_list_init(conn->internal->thread_pool, SilcFSMThreadStruct, next);
 
-  conn->internal->client_cache = silc_idcache_alloc(0, SILC_ID_CLIENT,
-                                                   NULL, NULL);
-  conn->internal->channel_cache = silc_idcache_alloc(0, SILC_ID_CHANNEL,
-                                                    NULL, NULL);
-  conn->internal->server_cache = silc_idcache_alloc(0, SILC_ID_SERVER,
-                                                   NULL, NULL);
-  if (!conn->internal->client_cache || !conn->internal->channel_cache ||
-      !conn->internal->server_cache) {
-    silc_client_del_connection(client, conn);
-    return NULL;
+  /* Allocate client, channel and serve caches */
+  if (conn_type != SILC_CONN_CLIENT) {
+    conn->internal->client_cache = silc_idcache_alloc(0, SILC_ID_CLIENT,
+                                                     NULL, NULL);
+    conn->internal->channel_cache = silc_idcache_alloc(0, SILC_ID_CHANNEL,
+                                                      NULL, NULL);
+    conn->internal->server_cache = silc_idcache_alloc(0, SILC_ID_SERVER,
+                                                     NULL, NULL);
+    if (!conn->internal->client_cache || !conn->internal->channel_cache ||
+       !conn->internal->server_cache) {
+      silc_client_del_connection(client, conn);
+      return NULL;
+    }
   }
 
-  conn->internal->ftp_sessions = silc_dlist_init();
+  if (connect) {
+    /* Initialize our async operation so that application may abort us
+       while we're connecting. */
+    conn->internal->cop = silc_async_alloc(silc_client_connect_abort,
+                                          NULL, conn);
+    if (!conn->internal->cop) {
+      silc_client_del_connection(client, conn);
+      return NULL;
+    }
+  }
 
-  /* Run the connection state machine.  If threads are in use the machine
-     is always run in a real thread. */
+  /* Run the connection state machine.  If threads are in use the connection
+     machine is always run in a real thread. */
   thread = silc_fsm_thread_alloc(&client->internal->fsm, conn,
-                                silc_client_fsm_destructor, NULL,
+                                silc_client_connection_finished, NULL,
                                 client->internal->params->threads);
   if (!thread) {
     silc_client_del_connection(client, conn);
     return NULL;
   }
+  silc_fsm_set_state_context(thread, client);
   silc_fsm_start(thread, silc_client_connection_st_start);
 
+  SILC_LOG_DEBUG(("New connection %p", conn));
+  silc_atomic_add_int32(&client->internal->conns, 1);
+
   return conn;
 }
 
-/* Removes connection from client. Frees all memory. */
+/* Deletes connection.  This is always called from the connection machine
+   destructor.  Do not call this directly other places. */
 
 void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
 {
-#if 0
-  SilcClientConnection c;
-  SilcIDCacheList list;
+  SilcList list;
   SilcIDCacheEntry entry;
-  SilcClientCommandPending *r;
-  SilcBool ret;
-
-  silc_dlist_start(client->internal->conns);
-  while ((c = silc_dlist_get(client->internal->conns)) != SILC_LIST_END) {
-    if (c != conn)
-      continue;
+  SilcFSMThread thread;
 
-    /* Free all cache entries */
-    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);
-    }
+  SILC_LOG_DEBUG(("Freeing connection %p", conn));
 
-    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_schedule_task_del_by_context(conn->internal->schedule, conn);
 
+  /* Free all cache entries */
+  if (conn->internal->server_cache) {
     if (silc_idcache_get_all(conn->internal->server_cache, &list)) {
-      ret = silc_idcache_list_first(list, &entry);
-      while (ret) {
+      silc_list_start(list);
+      while ((entry = silc_list_get(list)))
        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_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) {
-      if (conn->sock)
-       conn->sock->user_data = NULL;
-      silc_client_ftp_session_free(conn->internal->active_session);
-      conn->internal->active_session = NULL;
+  }
+  if (conn->internal->channel_cache) {
+    if (silc_idcache_get_all(conn->internal->channel_cache, &list)) {
+      silc_list_start(list);
+      while ((entry = silc_list_get(list))) {
+       silc_client_empty_channel(client, conn, entry->context);
+       silc_client_del_channel(client, conn, entry->context);
+      }
     }
-
-    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);
+  }
+  if (conn->internal->client_cache) {
+    if (silc_idcache_get_all(conn->internal->client_cache, &list)) {
+      silc_list_start(list);
+      while ((entry = silc_list_get(list)))
+       silc_client_del_client(client, conn, entry->context);
     }
-
-    silc_free(conn->internal);
-    memset(conn, 0, sizeof(*conn));
-    silc_free(conn);
-
-    silc_dlist_del(client->internal->conns, conn);
   }
-#endif /* 0 */
-}
 
+  /* Free 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 thread pool */
+  silc_list_start(conn->internal->thread_pool);
+  while ((thread = silc_list_get(conn->internal->thread_pool)))
+    silc_fsm_free(thread);
+
+  silc_free(conn->remote_host);
+  silc_buffer_free(conn->internal->local_idp);
+  silc_buffer_free(conn->internal->remote_idp);
+  silc_mutex_free(conn->internal->lock);
+  if (conn->internal->hash)
+    silc_hash_free(conn->internal->hash);
+  if (conn->internal->sha1hash)
+    silc_hash_free(conn->internal->sha1hash);
+  silc_atomic_uninit16(&conn->internal->cmd_ident);
+  silc_free(conn->internal->away_message);
+  if (conn->internal->rekey)
+    silc_ske_free_rekey_material(conn->internal->rekey);
+  if (conn->internal->cop)
+    silc_async_free(conn->internal->cop);
+
+  silc_free(conn->internal);
+  memset(conn, 'F', sizeof(*conn));
+  silc_free(conn);
+}
 
 /******************************* Client API *********************************/
 
@@ -597,26 +763,35 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
    to remote SILC server.  Performs key exchange also.  Returns the
    connection context to the connection callback. */
 
-SilcBool silc_client_connect_to_server(SilcClient client,
-                                      SilcClientConnectionParams *params,
-                                      SilcPublicKey public_key,
-                                      SilcPrivateKey private_key,
-                                      char *remote_host, int port,
-                                      SilcClientConnectCallback callback,
-                                      void *context)
+SilcAsyncOperation
+silc_client_connect_to_server(SilcClient client,
+                             SilcClientConnectionParams *params,
+                             SilcPublicKey public_key,
+                             SilcPrivateKey private_key,
+                             char *remote_host, int port,
+                             SilcClientConnectCallback callback,
+                             void *context)
 {
   SilcClientConnection conn;
 
+  SILC_LOG_DEBUG(("Connecting to server"));
+
   if (!client || !remote_host)
-    return FALSE;
+    return NULL;
+
+  if (client->internal->run_callback) {
+    SILC_LOG_ERROR(("Client library is not started yet. SilcClientRunning "
+                   "callback has not been called yet."));
+    return NULL;
+  }
 
   /* Add new connection */
-  conn = silc_client_add_connection(client, SILC_CONN_SERVER, params,
+  conn = silc_client_add_connection(client, SILC_CONN_SERVER, TRUE, params,
                                    public_key, private_key, remote_host,
                                    port, callback, context);
   if (!conn) {
     callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context);
-    return FALSE;
+    return NULL;
   }
 
   client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT,
@@ -625,78 +800,98 @@ SilcBool silc_client_connect_to_server(SilcClient client,
 
   /* Signal connection machine to start connecting */
   conn->internal->connect = TRUE;
-  return TRUE;
+  return conn->internal->cop;
 }
 
 /* Connects to remote client.  Performs key exchange also.  Returns the
    connection context to the connection callback. */
 
-SilcBool silc_client_connect_to_client(SilcClient client,
-                                      SilcClientConnectionParams *params,
-                                      SilcPublicKey public_key,
-                                      SilcPrivateKey private_key,
-                                      char *remote_host, int port,
-                                      SilcClientConnectCallback callback,
-                                      void *context)
+SilcAsyncOperation
+silc_client_connect_to_client(SilcClient client,
+                             SilcClientConnectionParams *params,
+                             SilcPublicKey public_key,
+                             SilcPrivateKey private_key,
+                             char *remote_host, int port,
+                             SilcClientConnectCallback callback,
+                             void *context)
 {
   SilcClientConnection conn;
 
+  SILC_LOG_DEBUG(("Connecting to client"));
+
   if (!client || !remote_host)
-    return FALSE;
+    return NULL;
+
+  if (client->internal->run_callback) {
+    SILC_LOG_ERROR(("Client library is not started yet. SilcClientRunning "
+                   "callback has not been called yet."));
+    return NULL;
+  }
+
+  if (params)
+    params->no_authentication = TRUE;
 
   /* Add new connection */
-  conn = silc_client_add_connection(client, SILC_CONN_CLIENT, params,
+  conn = silc_client_add_connection(client, SILC_CONN_CLIENT, TRUE, params,
                                    public_key, private_key, remote_host,
                                    port, callback, context);
   if (!conn) {
     callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context);
-    return FALSE;
+    return NULL;
   }
 
-  client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT,
-                            "Connecting to port %d of client host %s",
-                            port, remote_host);
-
   /* Signal connection machine to start connecting */
   conn->internal->connect = TRUE;
-  return TRUE;
+  return conn->internal->cop;
 }
 
 /* Starts key exchange in the remote stream indicated by `stream'.  This
    creates the connection context and returns it in the connection callback. */
 
-SilcBool silc_client_key_exchange(SilcClient client,
-                                 SilcClientConnectionParams *params,
-                                 SilcPublicKey public_key,
-                                 SilcPrivateKey private_key,
-                                 SilcStream stream,
-                                 SilcConnectionType conn_type,
-                                 SilcClientConnectCallback callback,
-                                 void *context)
+SilcAsyncOperation
+silc_client_key_exchange(SilcClient client,
+                        SilcClientConnectionParams *params,
+                        SilcPublicKey public_key,
+                        SilcPrivateKey private_key,
+                        SilcStream stream,
+                        SilcConnectionType conn_type,
+                        SilcClientConnectCallback callback,
+                        void *context)
 {
   SilcClientConnection conn;
   const char *host;
   SilcUInt16 port;
 
+  SILC_LOG_DEBUG(("Performing key exchange"));
+
   if (!client || !stream)
-    return FALSE;
+    return NULL;
 
-  if (!silc_socket_stream_get_info(stream, NULL, &host, NULL, &port))
-    return FALSE;
+  if (client->internal->run_callback) {
+    SILC_LOG_ERROR(("Client library is not started yet. SilcClientRunning "
+                   "callback has not been called yet."));
+    return NULL;
+  }
+
+  if (!silc_socket_stream_get_info(stream, NULL, &host, NULL, &port)) {
+    SILC_LOG_ERROR(("Socket stream does not have remote host name set"));
+    callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context);
+    return NULL;
+  }
 
   /* Add new connection */
-  conn = silc_client_add_connection(client, conn_type, params,
+  conn = silc_client_add_connection(client, conn_type, TRUE, params,
                                    public_key, private_key,
                                    (char *)host, port, callback, context);
   if (!conn) {
     callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context);
-    return FALSE;
+    return NULL;
   }
-  conn->stream = (void *)stream;
+  conn->internal->user_stream = stream;
 
   /* Signal connection to start key exchange */
   conn->internal->key_exchange = TRUE;
-  return TRUE;
+  return conn->internal->cop;
 }
 
 /* Closes remote connection */
@@ -704,280 +899,16 @@ SilcBool silc_client_key_exchange(SilcClient client,
 void silc_client_close_connection(SilcClient client,
                                  SilcClientConnection conn)
 {
+  SILC_LOG_DEBUG(("Closing connection %p", conn));
 
-}
-
-#if 0
-/* Finalizes the connection to the remote SILC server. This is called
-   after authentication protocol has been completed. This send our
-   user information to the server to receive our client ID from
-   server. */
-
-SILC_TASK_CALLBACK(silc_client_connect_to_server_final)
-{
-  SilcProtocol protocol = (SilcProtocol)context;
-  SilcClientConnAuthInternalContext *ctx =
-    (SilcClientConnAuthInternalContext *)protocol->context;
-  SilcClient client = (SilcClient)ctx->client;
-  SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data;
-  SilcBuffer packet;
-
-  SILC_LOG_DEBUG(("Start"));
-
-  if (protocol->state == SILC_PROTOCOL_STATE_ERROR ||
-      protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
-    /* Error occured during protocol */
-    SILC_LOG_DEBUG(("Error during authentication protocol"));
-    ctx->status = SILC_CLIENT_CONN_ERROR_AUTH;
-    goto err;
-  }
-
-  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)) {
-      ctx->status = SILC_CLIENT_CONN_ERROR_RESUME;
-      goto err;
-    }
-
-    old_client_id = silc_id_str2id(old_id, old_id_len, SILC_ID_CLIENT);
-    if (!old_client_id) {
-      silc_free(old_id);
-      ctx->status = SILC_CLIENT_CONN_ERROR_RESUME;
-      goto err;
-    }
-
-    /* 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);
-      ctx->status = SILC_CLIENT_CONN_ERROR_RESUME;
-      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;
-  conn->remote_id_data = silc_id_id2str(ctx->dest_id, SILC_ID_SERVER);
-  conn->remote_id_data_len = silc_id_get_len(ctx->dest_id, SILC_ID_SERVER);
-
-  /* Register re-key timeout */
-  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->internal->rekey->timeout, 0,
-                        SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-
-  silc_protocol_free(protocol);
-  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);
-}
-
-/* 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,
-                                         SilcBool 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_RESUME);
-
-  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);
+  /* Signal to close connection */
+  conn->internal->status = SILC_CLIENT_CONN_DISCONNECTED;
+  if (!conn->internal->disconnected) {
+    conn->internal->disconnected = TRUE;
+    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
   }
 }
 
-/* Processes incoming connection authentication method request packet.
-   It is a reply to our previously sent request. The packet can be used
-   to resolve the authentication method for the current session if the
-   client does not know it beforehand. */
-
-void silc_client_connection_auth_request(SilcClient client,
-                                        SilcClientConnection conn,
-                                        SilcPacketContext *packet)
-{
-  SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  SilcUInt16 conn_type, auth_meth;
-  int ret;
-
-  /* If we haven't send our request then ignore this one. */
-  if (!conn->internal->connauth)
-    return;
-
-  /* Parse the payload */
-  ret = silc_buffer_unformat(packet->buffer,
-                            SILC_STR_UI_SHORT(&conn_type),
-                            SILC_STR_UI_SHORT(&auth_meth),
-                            SILC_STR_END);
-  if (ret == -1)
-    auth_meth = SILC_AUTH_NONE;
-
-  /* Call the request callback to notify application for received
-     authentication method information. */
-  if (conn->internal->connauth->callback)
-    (*conn->internal->connauth->callback)(client, conn, auth_meth,
-                                         conn->internal->connauth->context);
-
-  silc_schedule_task_del(client->schedule, conn->internal->connauth->timeout);
-
-  silc_free(conn->internal->connauth);
-  conn->internal->connauth = NULL;
-}
-
-/* Timeout task callback called if the server does not reply to our
-   connection authentication method request in the specified time interval. */
-
-SILC_TASK_CALLBACK(silc_client_request_authentication_method_timeout)
-{
-  SilcClientConnection conn = (SilcClientConnection)context;
-  SilcClient client = conn->client;
-
-  if (!conn->internal->connauth)
-    return;
-
-  /* Call the request callback to notify application */
-  if (conn->internal->connauth->callback)
-    (*conn->internal->connauth->callback)(client, conn, SILC_AUTH_NONE,
-                                         conn->internal->connauth->context);
-
-  silc_free(conn->internal->connauth);
-  conn->internal->connauth = NULL;
-}
-
-/* This function can be used to request the current authentication method
-   from the server. This may be called when connecting to the server
-   and the client library requests the authentication data from the
-   application. If the application does not know the current authentication
-   method it can request it from the server using this function.
-   The `callback' with `context' will be called after the server has
-   replied back with the current authentication method. */
-
-void
-silc_client_request_authentication_method(SilcClient client,
-                                         SilcClientConnection conn,
-                                         SilcConnectionAuthRequest callback,
-                                         void *context)
-{
-  SilcClientConnAuthRequest connauth;
-  SilcBuffer packet;
-
-  assert(client && conn);
-  connauth = silc_calloc(1, sizeof(*connauth));
-  connauth->callback = callback;
-  connauth->context = context;
-
-  if (conn->internal->connauth)
-    silc_free(conn->internal->connauth);
-
-  conn->internal->connauth = connauth;
-
-  /* Assemble the request packet and send it to the server */
-  packet = silc_buffer_alloc(4);
-  silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet));
-  silc_buffer_format(packet,
-                    SILC_STR_UI_SHORT(SILC_SOCKET_TYPE_CLIENT),
-                    SILC_STR_UI_SHORT(SILC_AUTH_NONE),
-                    SILC_STR_END);
-  silc_client_packet_send(client, conn->sock,
-                         SILC_PACKET_CONNECTION_AUTH_REQUEST,
-                         NULL, 0, NULL, NULL,
-                         packet->data, packet->len, FALSE);
-  silc_buffer_free(packet);
-
-  /* Register a timeout in case server does not reply anything back. */
-  connauth->timeout =
-    silc_schedule_task_add(client->schedule, conn->sock->sock,
-                          silc_client_request_authentication_method_timeout,
-                          conn,
-                          client->internal->params->connauth_request_secs, 0,
-                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-}
-#endif /* 0 */
-
-
 /* Allocates new client object. This has to be done before client may
    work. After calling this one must call silc_client_init to initialize
    the client. The `application' is application specific user data pointer
@@ -1010,19 +941,12 @@ SilcClient silc_client_alloc(SilcClientOperations *ops,
   if (params)
     memcpy(new_client->internal->params, params, sizeof(*params));
 
-  if (!new_client->internal->params->task_max)
-    new_client->internal->params->task_max = 200;
-
-  if (!new_client->internal->params->rekey_secs)
-    new_client->internal->params->rekey_secs = 3600;
-
-  if (!new_client->internal->params->connauth_request_secs)
-    new_client->internal->params->connauth_request_secs = 2;
-
   new_client->internal->params->
     nickname_format[sizeof(new_client->internal->
                           params->nickname_format) - 1] = 0;
 
+  silc_atomic_init32(&new_client->internal->conns, 0);
+
   return new_client;
 }
 
@@ -1030,22 +954,29 @@ SilcClient silc_client_alloc(SilcClientOperations *ops,
 
 void silc_client_free(SilcClient client)
 {
-  if (client) {
-    if (client->rng)
-      silc_rng_free(client->rng);
-
-    if (!client->internal->params->dont_register_crypto_library) {
-      silc_cipher_unregister_all();
-      silc_pkcs_unregister_all();
-      silc_hash_unregister_all();
-      silc_hmac_unregister_all();
-    }
-
-    silc_free(client->internal->params);
-    silc_free(client->internal->silc_client_version);
-    silc_free(client->internal);
-    silc_free(client);
-  }
+  if (client->schedule)
+    silc_schedule_uninit(client->schedule);
+
+  if (client->rng)
+    silc_rng_free(client->rng);
+
+  if (!client->internal->params->dont_register_crypto_library)
+    silc_crypto_uninit();
+
+  if (client->internal->packet_engine)
+    silc_packet_engine_stop(client->internal->packet_engine);
+  if (client->internal->ftp_sessions)
+    silc_dlist_uninit(client->internal->ftp_sessions);
+  if (client->internal->lock)
+    silc_mutex_free(client->internal->lock);
+  silc_atomic_uninit32(&client->internal->conns);
+  silc_free(client->username);
+  silc_free(client->hostname);
+  silc_free(client->realname);
+  silc_free(client->internal->params);
+  silc_free(client->internal->silc_client_version);
+  silc_free(client->internal);
+  silc_free(client);
 }
 
 /* Initializes the client. This makes all the necessary steps to make
@@ -1053,18 +984,21 @@ void silc_client_free(SilcClient client)
    client. Returns FALSE if error occured, TRUE otherwise. */
 
 SilcBool silc_client_init(SilcClient client, const char *username,
-                         const char *hostname, const char *realname)
+                         const char *hostname, const char *realname,
+                         SilcClientRunning running, void *context)
 {
   SILC_LOG_DEBUG(("Initializing client"));
 
   if (!client)
     return FALSE;
 
-  if (!username || !hostname || !realname) {
-    SILC_LOG_ERROR(("Username, hostname and realname must be given to "
+  if (!username || !hostname) {
+    SILC_LOG_ERROR(("Username and hostname must be given to "
                    "silc_client_init"));
     return FALSE;
   }
+  if (!realname)
+    realname = username;
 
   /* Validate essential strings */
   if (!silc_identifier_verify(username, strlen(username),
@@ -1092,28 +1026,33 @@ SilcBool silc_client_init(SilcClient client, const char *username,
   if (!username || !hostname || !realname)
     return FALSE;
 
-  if (!client->internal->params->dont_register_crypto_library) {
+  client->internal->ftp_sessions = silc_dlist_init();
+  if (!client->internal->ftp_sessions)
+    return FALSE;
+
+  if (!client->internal->params->dont_register_crypto_library)
     /* 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();
-  }
+       this has no effect. */
+    silc_crypto_init(NULL);
 
   /* Initialize random number generator */
   client->rng = silc_rng_alloc();
+  if (!client->rng)
+    return FALSE;
   silc_rng_init(client->rng);
   silc_rng_global_init(client->rng);
 
   /* Initialize the scheduler */
-  client->schedule =
-    silc_schedule_init(client->internal->params->task_max ?
-                      client->internal->params->task_max : 0, client);
+  client->schedule = silc_schedule_init(0, client, NULL, NULL);
   if (!client->schedule)
     return FALSE;
 
+  /* Allocate client lock */
+  silc_mutex_alloc(&client->internal->lock);
+
+  /* Register commands */
+  silc_client_commands_register(client);
+
   /* Start packet engine */
   client->internal->packet_engine =
     silc_packet_engine_start(client->rng, FALSE, &silc_client_stream_cbs,
@@ -1121,42 +1060,20 @@ SilcBool silc_client_init(SilcClient client, const char *username,
   if (!client->internal->packet_engine)
     return FALSE;
 
-  /* Initialize FSM */
-  if (!silc_fsm_init(&client->internal->fsm, client, NULL, NULL,
-                    client->schedule))
-    return FALSE;
-  silc_fsm_sema_init(&client->internal->wait_event, &client->internal->fsm, 0);
-
-  /* Allocate client lock */
-  silc_mutex_alloc(&client->internal->lock);
-
-  /* Register commands */
-  silc_client_commands_register(client);
-
-  /* Start the client machine */
+  /* Initialize and start the client FSM */
+  client->internal->running = running;
+  client->internal->running_context = context;
+  silc_fsm_init(&client->internal->fsm, client, NULL, NULL, client->schedule);
+  silc_fsm_event_init(&client->internal->wait_event, &client->internal->fsm);
   silc_fsm_start_sync(&client->internal->fsm, silc_client_st_run);
 
   /* Signal the application when we are running */
   client->internal->run_callback = TRUE;
-  SILC_FSM_SEMA_POST(&client->internal->wait_event);
+  SILC_FSM_EVENT_SIGNAL(&client->internal->wait_event);
 
   return TRUE;
 }
 
-/* Stops the client. This is called to stop the client and thus to stop
-   the program. */
-
-void silc_client_stop(SilcClient client)
-{
-  SILC_LOG_DEBUG(("Stopping client"));
-
-  silc_schedule_stop(client->schedule);
-  silc_schedule_uninit(client->schedule);
-  silc_client_commands_unregister(client);
-
-  SILC_LOG_DEBUG(("Client stopped"));
-}
-
 /* Starts the SILC client FSM machine and blocks here.  When this returns
    the client has ended. */
 
@@ -1168,10 +1085,32 @@ void silc_client_run(SilcClient client)
   silc_schedule(client->schedule);
 }
 
-/* Call scheduler one iteration and return.  This cannot be called if threads
-   are in use. */
+/* Call scheduler one iteration and return. */
 
 void silc_client_run_one(SilcClient client)
 {
-  silc_schedule_one(client->schedule, 0);
+  if (silc_fsm_is_started(&client->internal->fsm))
+    silc_schedule_one(client->schedule, 0);
+}
+
+/* Stops the client. This is called to stop the client and thus to stop
+   the program. */
+
+void silc_client_stop(SilcClient client, SilcClientStopped stopped,
+                     void *context)
+{
+  SILC_LOG_DEBUG(("Stopping client"));
+
+  if (!silc_fsm_is_started(&client->internal->fsm)) {
+    SILC_LOG_WARNING(("Attempting to stop client library before it has been "
+                     "started (silc_client_init not called)"));
+    return;
+  }
+
+  client->internal->running = (SilcClientRunning)stopped;
+  client->internal->running_context = context;
+
+  /* Signal to stop */
+  client->internal->stop = TRUE;
+  SILC_FSM_EVENT_SIGNAL(&client->internal->wait_event);
 }