Fix reference count bug leading to memory corruption on duplicate deletions.
[silc.git] / lib / silcclient / client.c
index f52ff134d72d7d6223284e02c7480d6aaa6a5f44..3ab7ba17b4aa96fc872496dce38c17ae078227d7 100644 (file)
@@ -22,9 +22,6 @@
 #include "silcclient.h"
 #include "client_internal.h"
 
-/************************** Types and definitions ***************************/
-
-
 /************************ Static utility functions **************************/
 
 /* Connection machine FSM destructor.  This will finish the thread where
@@ -42,7 +39,8 @@ static void silc_client_connection_destructor(SilcFSM fsm,
   /* 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);
 }
 
@@ -57,7 +55,7 @@ static void silc_client_connection_finished(SilcFSMThread fsm,
   SilcClient client = silc_fsm_get_state_context(fsm);
 
   /* Signal client that we have finished */
-  silc_atomic_sub_int16(&client->internal->conns, 1);
+  silc_atomic_sub_int32(&client->internal->conns, 1);
   client->internal->connection_closed = TRUE;
   SILC_FSM_EVENT_SIGNAL(&client->internal->wait_event);
 
@@ -180,11 +178,16 @@ static void silc_client_connect_abort(SilcAsyncOperation op, void *context)
 
   /* 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;
-    SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+
+    /* 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);
   }
 }
 
@@ -208,7 +211,9 @@ SILC_FSM_STATE(silc_client_connection_st_start)
   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_EVENT_SIGNAL(&conn->internal->wait_event);
   if (conn->internal->key_exchange)
@@ -233,9 +238,17 @@ SILC_FSM_STATE(silc_client_connection_st_run)
   /* 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,
@@ -247,6 +260,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,
@@ -258,6 +272,7 @@ SILC_FSM_STATE(silc_client_connection_st_run)
   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,
@@ -266,13 +281,6 @@ SILC_FSM_STATE(silc_client_connection_st_run)
     return SILC_FSM_CONTINUE;
   }
 
-  if (conn->internal->disconnected) {
-    /** Event: disconnected */
-    SILC_LOG_DEBUG(("Event: disconnected"));
-    silc_fsm_next(fsm, silc_client_connection_st_close);
-    return SILC_FSM_YIELD;
-  }
-
   /* NOT REACHED */
   SILC_ASSERT(FALSE);
   return SILC_FSM_CONTINUE;
@@ -302,7 +310,7 @@ SILC_FSM_STATE(silc_client_connection_st_packet)
 
   case SILC_PACKET_FTP:
     /* File transfer packet */
-//    silc_fsm_next(fsm, silc_client_ftp);
+    silc_fsm_next(fsm, silc_client_ftp);
     break;
 
   case SILC_PACKET_CHANNEL_KEY:
@@ -377,7 +385,7 @@ SILC_FSM_STATE(silc_client_connection_st_packet)
 /* 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 is called
-   in this state if it set. */
+   in this state if it is set. */
 
 SILC_FSM_STATE(silc_client_connection_st_close)
 {
@@ -424,8 +432,9 @@ SILC_FSM_STATE(silc_client_connection_st_close)
 
   SILC_LOG_DEBUG(("Closing remote connection"));
 
-  /* Close connection */
-  silc_packet_stream_destroy(conn->stream);
+  /* Close connection. */
+  if (conn->stream)
+    silc_packet_stream_destroy(conn->stream);
 
   SILC_LOG_DEBUG(("Finishing connection machine"));
   return SILC_FSM_FINISH;
@@ -506,11 +515,13 @@ SILC_FSM_STATE(silc_client_st_run)
 
   /* Process events */
 
-  if (client->internal->run_callback && client->internal->running) {
+  if (client->internal->run_callback) {
     /* Call running callbcak back to application */
-    SILC_LOG_DEBUG(("We are up, call running callback"));
     client->internal->run_callback = FALSE;
-    client->internal->running(client, client->internal->running_context);
+    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;
   }
 
@@ -518,7 +529,7 @@ SILC_FSM_STATE(silc_client_st_run)
     /* A connection finished */
     SILC_LOG_DEBUG(("Event: connection closed"));
     client->internal->connection_closed = FALSE;
-    if (silc_atomic_get_int16(&client->internal->conns) == 0 &&
+    if (silc_atomic_get_int32(&client->internal->conns) == 0 &&
        client->internal->stop)
       SILC_FSM_EVENT_SIGNAL(&client->internal->wait_event);
     return SILC_FSM_CONTINUE;
@@ -527,9 +538,10 @@ SILC_FSM_STATE(silc_client_st_run)
   if (client->internal->stop) {
     /* Stop client libarry.  If we have running connections, wait until
        they finish first. */
-    SILC_LOG_DEBUG(("Event: stop"));
-    if (silc_atomic_get_int16(&client->internal->conns) == 0)
+    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;
   }
 
@@ -561,9 +573,10 @@ SILC_FSM_STATE(silc_client_st_stop)
 
 /* 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,
@@ -623,31 +636,33 @@ silc_client_add_connection(SilcClient client,
   silc_list_init(conn->internal->thread_pool, SilcFSMThreadStruct, next);
 
   /* Allocate client, channel and serve caches */
-  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;
+  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();
-
-  /* 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;
+  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_connection_finished, NULL,
                                 client->internal->params->threads);
@@ -659,7 +674,7 @@ silc_client_add_connection(SilcClient client,
   silc_fsm_start(thread, silc_client_connection_st_start);
 
   SILC_LOG_DEBUG(("New connection %p", conn));
-  silc_atomic_add_int16(&client->internal->conns, 1);
+  silc_atomic_add_int32(&client->internal->conns, 1);
 
   return conn;
 }
@@ -678,22 +693,28 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
   silc_schedule_task_del_by_context(conn->internal->schedule, conn);
 
   /* Free all cache entries */
-  if (silc_idcache_get_all(conn->internal->server_cache, &list)) {
-    silc_list_start(list);
-    while ((entry = silc_list_get(list)))
-      silc_client_del_server(client, conn, entry->context);
+  if (conn->internal->server_cache) {
+    if (silc_idcache_get_all(conn->internal->server_cache, &list)) {
+      silc_list_start(list);
+      while ((entry = silc_list_get(list)))
+       silc_client_del_server(client, conn, entry->context);
+    }
   }
-  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);
+  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);
+      }
     }
   }
-  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);
+  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);
+    }
   }
 
   /* Free ID caches */
@@ -718,6 +739,11 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
   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));
@@ -746,8 +772,14 @@ silc_client_connect_to_server(SilcClient client,
   if (!client || !remote_host)
     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) {
@@ -783,8 +815,17 @@ silc_client_connect_to_client(SilcClient client,
   if (!client || !remote_host)
     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) {
@@ -819,6 +860,12 @@ silc_client_key_exchange(SilcClient client,
   if (!client || !stream)
     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 (!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);
@@ -826,14 +873,14 @@ silc_client_key_exchange(SilcClient client,
   }
 
   /* 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 NULL;
   }
-  conn->stream = (void *)stream;
+  conn->internal->user_stream = stream;
 
   /* Signal connection to start key exchange */
   conn->internal->key_exchange = TRUE;
@@ -848,6 +895,7 @@ void silc_client_close_connection(SilcClient client,
   SILC_LOG_DEBUG(("Closing connection %p", conn));
 
   /* 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);
@@ -890,7 +938,7 @@ SilcClient silc_client_alloc(SilcClientOperations *ops,
     nickname_format[sizeof(new_client->internal->
                           params->nickname_format) - 1] = 0;
 
-  silc_atomic_init16(&new_client->internal->conns, 0);
+  silc_atomic_init32(&new_client->internal->conns, 0);
 
   return new_client;
 }
@@ -899,7 +947,8 @@ SilcClient silc_client_alloc(SilcClientOperations *ops,
 
 void silc_client_free(SilcClient client)
 {
-  silc_schedule_uninit(client->schedule);
+  if (client->schedule)
+    silc_schedule_uninit(client->schedule);
 
   if (client->rng)
     silc_rng_free(client->rng);
@@ -911,7 +960,13 @@ void silc_client_free(SilcClient client)
     silc_hmac_unregister_all();
   }
 
-  silc_atomic_uninit16(&client->internal->conns);
+  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);
@@ -968,6 +1023,10 @@ SilcBool silc_client_init(SilcClient client, const char *username,
   if (!username || !hostname || !realname)
     return FALSE;
 
+  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
@@ -1044,6 +1103,12 @@ void silc_client_stop(SilcClient client, SilcClientStopped stopped,
 {
   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;