Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client.c
index 72a4800fb1b591338e257251d7a9c00562d3cd5b..449fc375ce152b33bca4e0c3bbd06ce53870766d 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2007 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
@@ -42,7 +38,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 +54,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);
 
@@ -151,7 +148,15 @@ static void silc_client_packet_error(SilcPacketEngine engine,
                                     void *callback_context,
                                     void *stream_context)
 {
-  /* Nothing */
+  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 */
@@ -180,11 +185,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 +218,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,6 +245,13 @@ 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;
@@ -269,13 +288,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;
@@ -305,7 +317,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:
@@ -427,8 +439,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;
@@ -509,11 +522,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;
   }
 
@@ -521,7 +536,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;
@@ -530,9 +545,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;
   }
 
@@ -564,9 +580,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,
@@ -626,31 +643,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);
@@ -662,7 +681,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;
 }
@@ -681,22 +700,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 */
@@ -721,6 +746,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));
@@ -749,8 +779,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) {
@@ -786,8 +822,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) {
@@ -822,6 +867,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);
@@ -829,14 +880,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;
@@ -894,7 +945,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;
 }
@@ -903,19 +954,22 @@ 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);
 
-  if (!client->internal->params->dont_register_crypto_library) {
-    silc_cipher_unregister_all();
-    silc_pkcs_unregister_all();
-    silc_hash_unregister_all();
-    silc_hmac_unregister_all();
-  }
+  if (!client->internal->params->dont_register_crypto_library)
+    silc_crypto_uninit();
 
-  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);
@@ -972,15 +1026,14 @@ 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();
@@ -990,7 +1043,7 @@ SilcBool silc_client_init(SilcClient client, const char *username,
   silc_rng_global_init(client->rng);
 
   /* Initialize the scheduler */
-  client->schedule = silc_schedule_init(0, client);
+  client->schedule = silc_schedule_init(0, client, NULL, NULL);
   if (!client->schedule)
     return FALSE;
 
@@ -1048,6 +1101,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;