From 77336860c5d419c9d25a6366de0269c0edb38889 Mon Sep 17 00:00:00 2001 From: Pekka Riikonen Date: Tue, 12 Dec 2006 18:43:03 +0000 Subject: [PATCH] The silc_client_connect_to_[server|client] and silc_client_key_exchange now returns SilcAsyncOperation. Fixed client library stopping in case of disconnect, abortion, stopping, etc. The silc_client_init now takes the running callback as argument (remove it from client ops). The silc_client_stop now takes stopped callback as argument. --- lib/silcclient/README | 23 +++ lib/silcclient/client.c | 258 +++++++++++++++++++++++-------- lib/silcclient/client_channel.c | 2 + lib/silcclient/client_connect.c | 66 +++++++- lib/silcclient/client_entry.c | 17 +- lib/silcclient/client_internal.h | 11 +- lib/silcclient/client_keyagr.c | 2 + lib/silcclient/client_prvmsg.c | 2 + lib/silcclient/client_register.c | 18 ++- lib/silcclient/command.c | 29 ++-- lib/silcclient/command_reply.c | 6 + lib/silcclient/silcclient.h | 136 ++++++++++------ 12 files changed, 442 insertions(+), 128 deletions(-) diff --git a/lib/silcclient/README b/lib/silcclient/README index 66a14e7d..dc0fcae7 100644 --- a/lib/silcclient/README +++ b/lib/silcclient/README @@ -76,3 +76,26 @@ When to use FSM semaphore signalling? The call cannot fail. Semaphores need not be uninitialized and the same context may be reused. + +Finishing threads when closing connection + + When closing SilcClientConnection all threads must first be finished + before the connection machine is finished. This is done by finishing + all running command threads. That will also finish all waiting packet + threads as they are always waiting for a command. If any thread is + waiting for something else than a command (such as event threads) they + must be explicitly finished. The threads are finished by continuing + them synchronously. The threads will detect that we are disconnected + (see below). SILC_FSM_YIELD must be returned in st_close() as that + gives the FSM scheduler time to finish the threads first. After that + the machine can be finished. + + Also, any thread that runs in SilcClientConnection machine must always + after returning from wait state to check if we are disconnected by doing + + if (conn->internal->disconnected) + xxx; + + If disconnected the thread must finish immediately by returning + SILC_FSM_FINISH. + diff --git a/lib/silcclient/client.c b/lib/silcclient/client.c index dd979744..0c001e82 100644 --- a/lib/silcclient/client.c +++ b/lib/silcclient/client.c @@ -37,6 +37,8 @@ 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); @@ -44,6 +46,25 @@ static void silc_client_connection_destructor(SilcFSM fsm, 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_int16(&client->internal->conns, 1); + client->internal->connection_closed = TRUE; + SILC_FSM_SEMA_POST(&client->internal->wait_event); + + silc_fsm_free(fsm); +} + + /* Packet FSM thread destructor */ static void silc_client_packet_destructor(SilcFSMThread thread, @@ -118,8 +139,10 @@ static void silc_client_packet_eos(SilcPacketEngine engine, SILC_LOG_DEBUG(("Remote disconnected connection")); /* Call connection callback */ - conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, 0, NULL, - conn->callback_context); + if (!conn->internal->callback_called) + conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, 0, NULL, + conn->callback_context); + conn->internal->callback_called = TRUE; /* Signal to close connection */ if (!conn->internal->disconnected) { @@ -155,6 +178,21 @@ 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)); + conn->internal->aborted = TRUE; + + /* Signal to close connection */ + if (!conn->internal->disconnected) { + conn->internal->disconnected = TRUE; + SILC_FSM_SEMA_POST(&conn->internal->wait_event); + } +} /************************** Connection's machine ****************************/ @@ -237,9 +275,8 @@ SILC_FSM_STATE(silc_client_connection_st_run) if (conn->internal->disconnected) { /** Event: disconnected */ SILC_LOG_DEBUG(("Event: disconnected")); - conn->internal->disconnected = FALSE; silc_fsm_next(fsm, silc_client_connection_st_close); - return SILC_FSM_CONTINUE; + return SILC_FSM_YIELD; } /* NOT REACHED */ @@ -344,7 +381,7 @@ 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. */ @@ -352,12 +389,40 @@ SILC_FSM_STATE(silc_client_connection_st_packet) SILC_FSM_STATE(silc_client_connection_st_close) { SilcClientConnection conn = fsm_context; + SilcClientCommandContext cmd; - SILC_LOG_DEBUG(("Closing remote connection")); + /* 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); + } + } + + /* Give threads time to finish */ + return SILC_FSM_YIELD; + } /* Abort ongoing events */ - if (conn->internal->op) + if (conn->internal->op) { + SILC_LOG_DEBUG(("Abort event")); silc_async_abort(conn->internal->op, NULL, NULL); + conn->internal->op = NULL; + } + + /* 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(("Closing remote connection")); /* Close connection */ silc_packet_stream_destroy(conn->stream); @@ -415,8 +480,10 @@ 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); + if (!conn->internal->callback_called) + conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, status, + message, conn->callback_context); + conn->internal->callback_called = TRUE; silc_free(message); silc_packet_free(packet); @@ -443,11 +510,30 @@ SILC_FSM_STATE(silc_client_st_run) /* Process events */ - if (client->internal->run_callback && client->internal->ops->running) { + if (client->internal->run_callback && client->internal->running) { /* Call running callbcak back to application */ - SILC_LOG_DEBUG(("We are running, call running callback")); + SILC_LOG_DEBUG(("We are up, call running callback")); client->internal->run_callback = FALSE; - client->internal->ops->running(client, client->application); + 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_int16(&client->internal->conns) == 0 && + client->internal->stop) + SILC_FSM_SEMA_POST(&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. */ + SILC_LOG_DEBUG(("Event: stop")); + if (silc_atomic_get_int16(&client->internal->conns) == 0) + silc_fsm_next(fsm, silc_client_st_stop); return SILC_FSM_CONTINUE; } @@ -456,6 +542,25 @@ 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. */ @@ -535,18 +640,29 @@ silc_client_add_connection(SilcClient client, conn->internal->ftp_sessions = silc_dlist_init(); + /* Initiatlize our async operation so that application may abort us + while were 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. */ 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_int16(&client->internal->conns, 1); return conn; } @@ -559,7 +675,6 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn) SilcList list; SilcIDCacheEntry entry; SilcFSMThread thread; - SilcClientCommandContext cmd; SILC_LOG_DEBUG(("Freeing connection %p", conn)); @@ -597,11 +712,6 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn) while ((thread = silc_list_get(conn->internal->thread_pool))) silc_fsm_free(thread); - /* Free pending commands */ - silc_list_start(conn->internal->pending_commands); - while ((cmd = silc_list_get(conn->internal->pending_commands))) - silc_client_command_free(cmd); - silc_free(conn->remote_host); silc_buffer_free(conn->internal->local_idp); silc_buffer_free(conn->internal->remote_idp); @@ -624,18 +734,19 @@ 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; if (!client || !remote_host) - return FALSE; + return NULL; /* Add new connection */ conn = silc_client_add_connection(client, SILC_CONN_SERVER, params, @@ -643,7 +754,7 @@ SilcBool silc_client_connect_to_server(SilcClient client, 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, @@ -652,24 +763,25 @@ 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; if (!client || !remote_host) - return FALSE; + return NULL; /* Add new connection */ conn = silc_client_add_connection(client, SILC_CONN_CLIENT, params, @@ -677,37 +789,38 @@ SilcBool silc_client_connect_to_client(SilcClient client, port, callback, context); if (!conn) { callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context); - return FALSE; + return NULL; } /* 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; if (!client || !stream) - return FALSE; + 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 FALSE; + return NULL; } /* Add new connection */ @@ -716,13 +829,13 @@ SilcBool silc_client_key_exchange(SilcClient client, (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; /* Signal connection to start key exchange */ conn->internal->key_exchange = TRUE; - return TRUE; + return conn->internal->cop; } /* Closes remote connection */ @@ -1045,6 +1158,8 @@ 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); + return new_client; } @@ -1052,6 +1167,8 @@ SilcClient silc_client_alloc(SilcClientOperations *ops, void silc_client_free(SilcClient client) { + silc_schedule_uninit(client->schedule); + if (client->rng) silc_rng_free(client->rng); @@ -1062,6 +1179,7 @@ void silc_client_free(SilcClient client) silc_hmac_unregister_all(); } + silc_atomic_uninit16(&client->internal->conns); silc_free(client->username); silc_free(client->hostname); silc_free(client->realname); @@ -1076,7 +1194,8 @@ 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")); @@ -1153,6 +1272,8 @@ SilcBool silc_client_init(SilcClient client, const char *username, silc_client_commands_register(client); /* 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_sema_init(&client->internal->wait_event, &client->internal->fsm, 0); silc_fsm_start_sync(&client->internal->fsm, silc_client_st_run); @@ -1164,20 +1285,6 @@ SilcBool silc_client_init(SilcClient client, const char *username, 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. */ @@ -1194,5 +1301,22 @@ void silc_client_run(SilcClient client) 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")); + + client->internal->running = (SilcClientRunning)stopped; + client->internal->running_context = context; + + /* Signal to stop */ + client->internal->stop = TRUE; + SILC_FSM_SEMA_POST(&client->internal->wait_event); } diff --git a/lib/silcclient/client_channel.c b/lib/silcclient/client_channel.c index c7a41446..bb5c071e 100644 --- a/lib/silcclient/client_channel.c +++ b/lib/silcclient/client_channel.c @@ -47,6 +47,8 @@ SilcBool silc_client_send_channel_message(SilcClient client, return FALSE; if (flags & SILC_MESSAGE_FLAG_SIGNED && !hash) return FALSE; + if (conn->internal->disconnected) + return FALSE; chu = silc_client_on_channel(channel, conn->local_entry); if (!chu) { diff --git a/lib/silcclient/client_connect.c b/lib/silcclient/client_connect.c index 98b4372f..8c149194 100644 --- a/lib/silcclient/client_connect.c +++ b/lib/silcclient/client_connect.c @@ -32,6 +32,12 @@ static void silc_client_connect_callback(SilcNetStatus status, SilcClientConnection conn = silc_fsm_get_context(fsm); SilcClient client = conn->client; + if (conn->internal->aborted) { + silc_fsm_next(fsm, silc_client_st_connect_error); + SILC_FSM_CALL_CONTINUE(fsm); + return; + } + conn->internal->op = NULL; if (conn->internal->verbose) { switch (status) { @@ -121,6 +127,14 @@ static void silc_client_ke_verify_key(SilcSKE ske, SilcClient client = conn->client; VerifyKeyContext verify; + if (conn->internal->aborted) { + completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY, + completion_context); + silc_fsm_next(fsm, silc_client_st_connect_error); + SILC_FSM_CALL_CONTINUE(fsm); + return; + } + /* If we provided repository for SKE and we got here the key was not found from the repository. */ if (conn->internal->params.repository && @@ -164,6 +178,13 @@ static void silc_client_ke_completion(SilcSKE ske, SilcCipher send_key, receive_key; SilcHmac hmac_send, hmac_receive; + if (conn->internal->aborted) { + silc_ske_free_rekey_material(rekey); + silc_fsm_next(fsm, silc_client_st_connect_error); + SILC_FSM_CALL_CONTINUE(fsm); + return; + } + conn->internal->op = NULL; if (status != SILC_SKE_STATUS_OK) { /* Key exchange failed */ @@ -307,6 +328,13 @@ static void silc_client_connect_auth_completion(SilcConnAuth connauth, SilcClientConnection conn = silc_fsm_get_context(fsm); SilcClient client = conn->client; + if (conn->internal->aborted) { + silc_fsm_next(fsm, silc_client_st_connect_error); + SILC_FSM_CALL_CONTINUE(fsm); + return; + } + + conn->internal->op = NULL; silc_connauth_free(connauth); if (!success) { @@ -400,6 +428,12 @@ SILC_FSM_STATE(silc_client_st_connect_set_stream) SilcClientConnection conn = fsm_context; SilcClient client = conn->client; + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_connect_error); + return SILC_FSM_CONTINUE; + } + /* Create packet stream */ conn->stream = silc_packet_stream_create(client->internal->packet_engine, conn->internal->schedule, @@ -484,6 +518,12 @@ SILC_FSM_STATE(silc_client_st_connect_setup_udp) SILC_LOG_DEBUG(("Setup UDP SILC session")); + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_connect_error); + return SILC_FSM_CONTINUE; + } + /* Create new UDP stream */ prop = silc_ske_get_security_properties(conn->internal->ske); stream = silc_net_udp_connect(conn->internal->params.local_ip, @@ -522,6 +562,12 @@ SILC_FSM_STATE(silc_client_st_connect_auth) SILC_LOG_DEBUG(("Get authentication data")); + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_connect_error); + return SILC_FSM_CONTINUE; + } + silc_fsm_next(fsm, silc_client_st_connect_auth_start); /* If authentication data not provided, ask from application */ @@ -549,6 +595,12 @@ SILC_FSM_STATE(silc_client_st_connect_auth_start) SILC_LOG_DEBUG(("Starting connection authentication protocol")); + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_connect_error); + return SILC_FSM_CONTINUE; + } + /* Allocate connection authentication protocol */ connauth = silc_connauth_alloc(conn->internal->schedule, conn->internal->ske, @@ -579,11 +631,17 @@ SILC_FSM_STATE(silc_client_st_connected) SilcClientConnection conn = fsm_context; SilcClient client = conn->client; - SILC_LOG_DEBUG(("Connection established")); - silc_ske_free(conn->internal->ske); conn->internal->ske = NULL; + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_connect_error); + return SILC_FSM_CONTINUE; + } + + SILC_LOG_DEBUG(("Connection established")); + /* Install rekey timer */ silc_schedule_task_add_timeout(conn->internal->schedule, silc_client_rekey_timer, conn, @@ -648,6 +706,7 @@ SILC_TASK_CALLBACK(silc_client_rekey_timer) SilcClientConnection conn = context; /* Signal to start rekey */ + conn->internal->rekey_responder = FALSE; conn->internal->rekeying = TRUE; SILC_FSM_SEMA_POST(&conn->internal->wait_event); @@ -666,6 +725,9 @@ SILC_FSM_STATE(silc_client_st_rekey) SILC_LOG_DEBUG(("Rekey")); + if (conn->internal->disconnected) + return SILC_FSM_FINISH; + /* Allocate SKE */ conn->internal->ske = silc_ske_alloc(client->rng, conn->internal->schedule, diff --git a/lib/silcclient/client_entry.c b/lib/silcclient/client_entry.c index 980daea6..1d3189ba 100644 --- a/lib/silcclient/client_entry.c +++ b/lib/silcclient/client_entry.c @@ -750,7 +750,7 @@ SilcClientEntry silc_client_add_client(SilcClient client, silc_mutex_unlock(conn->internal->lock); silc_client_ref_client(client, conn, client_entry); - SILC_LOG_DEBUG(("Added")); + SILC_LOG_DEBUG(("Added %p", client_entry)); return client_entry; } @@ -1324,7 +1324,7 @@ SilcChannelEntry silc_client_add_channel(SilcClient client, silc_mutex_unlock(conn->internal->lock); silc_client_ref_channel(client, conn, channel); - SILC_LOG_DEBUG(("Added")); + SILC_LOG_DEBUG(("Added %p", channel)); return channel; } @@ -1702,7 +1702,7 @@ SilcServerEntry silc_client_add_server(SilcClient client, silc_mutex_unlock(conn->internal->lock); silc_client_ref_server(client, conn, server_entry); - SILC_LOG_DEBUG(("Added")); + SILC_LOG_DEBUG(("Added %p", server_entry)); return server_entry; } @@ -1784,6 +1784,9 @@ void silc_client_ref_server(SilcClient client, SilcClientConnection conn, SilcServerEntry server_entry) { silc_atomic_add_int8(&server_entry->internal.refcnt, 1); + SILC_LOG_DEBUG(("Server %p refcnt %d->%d", server_entry, + silc_atomic_get_int8(&server_entry->internal.refcnt) - 1, + silc_atomic_get_int8(&server_entry->internal.refcnt))); } /* Release reference of server entry */ @@ -1791,7 +1794,13 @@ void silc_client_ref_server(SilcClient client, SilcClientConnection conn, void silc_client_unref_server(SilcClient client, SilcClientConnection conn, SilcServerEntry server_entry) { - silc_client_del_server(client, conn, server_entry); + if (server_entry) { + SILC_LOG_DEBUG(("Server %p refcnt %d->%d", server_entry, + silc_atomic_get_int8(&server_entry->internal.refcnt), + silc_atomic_get_int8(&server_entry->internal.refcnt) + - 1)); + silc_client_del_server(client, conn, server_entry); + } } /* Free server entry list */ diff --git a/lib/silcclient/client_internal.h b/lib/silcclient/client_internal.h index 0acee44d..d3437edf 100644 --- a/lib/silcclient/client_internal.h +++ b/lib/silcclient/client_internal.h @@ -145,9 +145,14 @@ struct SilcClientInternalStruct { SilcMutex lock; /* Client lock */ SilcList commands; /* Registered commands */ char *silc_client_version; /* Version set by application */ + SilcClientRunning running; /* Running/Stopped callback */ + void *running_context; /* Context for runnign callback */ + SilcAtomic16 conns; /* Number of connections in client */ /* Events */ - unsigned int run_callback : 1; /* Call running callback */ + unsigned int stop : 1; /* Stop client */ + unsigned int run_callback : 1; /* Call running/stopped callback */ + unsigned int connection_closed : 1; /* A connection closed */ }; /* Internal context for conn->internal in SilcClientConnection. */ @@ -169,6 +174,7 @@ struct SilcClientConnectionInternalStruct { SilcBuffer local_idp; /* Local ID Payload */ SilcBuffer remote_idp; /* Remote ID Payload */ SilcAsyncOperation op; /* Protocols async operation */ + SilcAsyncOperation cop; /* Async operation for application */ SilcIDCache client_cache; /* Client entry cache */ SilcIDCache channel_cache; /* Channel entry cache */ @@ -188,6 +194,8 @@ struct SilcClientConnectionInternalStruct { unsigned int verbose : 1; /* Notify application */ unsigned int registering : 1; /* Set when registering to network */ unsigned int rekey_responder : 1; /* Set when rekeying as responder */ + unsigned int callback_called : 1; /* Set when connect callback called */ + unsigned int aborted : 1; /* Set when aborted by application */ SilcClientAway *away; SilcClientConnAuthRequest connauth; @@ -203,6 +211,7 @@ SILC_FSM_STATE(silc_client_connection_st_packet); SILC_FSM_STATE(silc_client_connection_st_close); SILC_FSM_STATE(silc_client_error); SILC_FSM_STATE(silc_client_disconnect); +SILC_FSM_STATE(silc_client_st_stop); void silc_client_del_connection(SilcClient client, SilcClientConnection conn); SilcBool silc_client_del_client(SilcClient client, SilcClientConnection conn, diff --git a/lib/silcclient/client_keyagr.c b/lib/silcclient/client_keyagr.c index 52dca47c..d5f90485 100644 --- a/lib/silcclient/client_keyagr.c +++ b/lib/silcclient/client_keyagr.c @@ -362,6 +362,8 @@ void silc_client_send_key_agreement(SilcClient client, if (!client_entry) return; + if (conn->internal->disconnected) + return; if (client_entry->internal.ke) { completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ALREADY_STARTED, diff --git a/lib/silcclient/client_prvmsg.c b/lib/silcclient/client_prvmsg.c index 3b45718a..deb5d9a8 100644 --- a/lib/silcclient/client_prvmsg.c +++ b/lib/silcclient/client_prvmsg.c @@ -41,6 +41,8 @@ SilcBool silc_client_send_private_message(SilcClient client, return FALSE; if (flags & SILC_MESSAGE_FLAG_SIGNED && !hash) return FALSE; + if (conn->internal->disconnected) + return FALSE; SILC_LOG_DEBUG(("Sending private message")); diff --git a/lib/silcclient/client_register.c b/lib/silcclient/client_register.c index 97711803..42cb3677 100644 --- a/lib/silcclient/client_register.c +++ b/lib/silcclient/client_register.c @@ -186,6 +186,18 @@ SILC_FSM_STATE(silc_client_st_register_complete) SilcClientConnection conn = fsm_context; SilcClient client = conn->client; + if (conn->internal->aborted) { + /** Aborted */ + silc_fsm_next(fsm, silc_client_st_register_error); + return SILC_FSM_CONTINUE; + } + + if (conn->internal->disconnected) { + /** Disconnected */ + silc_fsm_next(fsm, silc_client_st_register_error); + return SILC_FSM_CONTINUE; + } + if (!conn->local_id) { if (conn->internal->retry_count++ >= SILC_CLIENT_RETRY_COUNT) { /** Timeout, ID not received */ @@ -255,8 +267,10 @@ SILC_FSM_STATE(silc_client_st_register_error) } /* Call connect callback */ - conn->callback(client, conn, SILC_CLIENT_CONN_ERROR, 0, NULL, - conn->callback_context); + if (conn->internal->callback_called) + conn->callback(client, conn, SILC_CLIENT_CONN_ERROR, 0, NULL, + conn->callback_context); + conn->internal->callback_called = TRUE; silc_schedule_task_del_by_all(conn->internal->schedule, 0, silc_client_connect_timeout, conn); diff --git a/lib/silcclient/command.c b/lib/silcclient/command.c index 7ffa52fa..602c5278 100644 --- a/lib/silcclient/command.c +++ b/lib/silcclient/command.c @@ -203,7 +203,14 @@ static void silc_client_command_destructor(SilcFSMThread thread, void *fsm_context, void *destructor_context) { - silc_client_command_free(fsm_context); + SilcClientCommandContext cmd = fsm_context; + SilcClientConnection conn = cmd->conn; + + /* Removes commands that aren't waiting for reply but are waiting + for something. They may not have been removed yet. */ + silc_list_del(conn->internal->pending_commands, cmd); + + silc_client_command_free(cmd); } /* Add a command pending a command reply. Used internally by the library. */ @@ -254,6 +261,9 @@ static SilcUInt16 silc_client_command_send_vap(SilcClient client, SILC_LOG_DEBUG(("Send command %s", silc_get_command_name(command))); + if (conn->internal->disconnected) + return 0; + if (!cmd->cmd_ident) cmd->cmd_ident = silc_client_cmd_ident(conn); @@ -297,6 +307,9 @@ silc_client_command_send_arg_array(SilcClient client, SILC_LOG_DEBUG(("Send command %s", silc_get_command_name(command))); + if (conn->internal->disconnected) + return 0; + if (!cmd->cmd_ident) cmd->cmd_ident = silc_client_cmd_ident(conn); @@ -352,12 +365,6 @@ void silc_client_command_free(SilcClientCommandContext cmd) SilcClientCommandReplyCallback cb; int i; - /* If command is running, finish it. Destructor will free the context. */ - if (silc_fsm_is_started(&cmd->thread)) { - silc_fsm_finish(&cmd->thread); - return; - } - for (i = 0; i < cmd->argc; i++) silc_free(cmd->argv[i]); silc_free(cmd->argv); @@ -1077,12 +1084,16 @@ SILC_FSM_STATE(silc_client_command_quit_final) SilcClientConnection conn = cmd->conn; SilcClient client = conn->client; + SILC_LOG_DEBUG(("Quitting")); + /* Notify application */ COMMAND(SILC_STATUS_OK); /* Call connection callback */ - conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, - 0, NULL, conn->callback_context); + if (!conn->internal->callback_called) + conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, + 0, NULL, conn->callback_context); + conn->internal->callback_called = TRUE; /* Signal to close connection */ if (!conn->internal->disconnected) { diff --git a/lib/silcclient/command_reply.c b/lib/silcclient/command_reply.c index ea1f74b0..0e952e9f 100644 --- a/lib/silcclient/command_reply.c +++ b/lib/silcclient/command_reply.c @@ -195,6 +195,12 @@ SILC_FSM_STATE(silc_client_command_reply_timeout) SilcClientConnection conn = cmd->conn; SilcArgumentPayload args = NULL; + if (conn->internal->disconnected) { + SILC_LOG_DEBUG(("Command %s canceled", silc_get_command_name(cmd->cmd))); + silc_list_del(conn->internal->pending_commands, cmd); + return SILC_FSM_FINISH; + } + SILC_LOG_DEBUG(("Command %s timeout", silc_get_command_name(cmd->cmd))); /* Timeout, reply not received in timely fashion */ diff --git a/lib/silcclient/silcclient.h b/lib/silcclient/silcclient.h index f6fdb758..ee1e0edd 100644 --- a/lib/silcclient/silcclient.h +++ b/lib/silcclient/silcclient.h @@ -83,6 +83,39 @@ typedef enum { } SilcClientConnectionStatus; /***/ +/****f* silcclient/SilcClientAPI/SilcClientRunning + * + * SYNOPSIS + * + * typedef void (*SilcClientRunning)(SilcClient client, void *context); + * + * DESCRIPTION + * + * The callback given as argument to silc_client_init function. Once + * this is called the client library is running and application may + * start using the Client library API. + * + ***/ +typedef void (*SilcClientRunning)(SilcClient client, void *context); + +/****f* silcclient/SilcClientAPI/SilcClientStopped + * + * SYNOPSIS + * + * typedef void (*SilcClientStopped)(SilcClient client, void *context); + * + * DESCRIPTION + * + * The callback given as argument to silc_client_stop. Once this is + * called the client library has stopped and can be freed by calling + * silc_client_free. Note that this won't be called if there are + * active connections in the client. Connections must first be closed + * by calling silc_client_close_connection or by sending QUIT command to + * the server connection. + * + ***/ +typedef void (*SilcClientStopped)(SilcClient client, void *context); + /****f* silcclient/SilcClientAPI/SilcClientConnectCallback * * SYNOPSIS @@ -594,11 +627,6 @@ typedef struct { void (*detach)(SilcClient client, SilcClientConnection conn, const unsigned char *detach_data, SilcUInt32 detach_data_len); - - /* Called when the client library is up and running. After this callback - is called the application may start using the client library APIs. */ - void (*running)(SilcClient client, void *application); - } SilcClientOperations; /***/ @@ -763,7 +791,8 @@ void silc_client_free(SilcClient client); * SYNOPSIS * * 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); * * DESCRIPTION * @@ -776,9 +805,15 @@ void silc_client_free(SilcClient client); * in the operating system, `hostname' is the client's host name and * the `realname' is the user's real name. * + * The `running' callback is called after the client is running after + * silc_client_run or silc_client_run_one has been called. Application + * may start using the Client library API after that. Setting the + * callback is optional, but recommended. + * ***/ 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); /****f* silcclient/SilcClientAPI/silc_client_run * @@ -788,7 +823,7 @@ SilcBool silc_client_init(SilcClient client, const char *username, * * DESCRIPTION * - * Runs the client. This starts the scheduler from the utility library. + * Runs the client. This starts the scheduler from the utility library. * When this functions returns the execution of the application is over. * The client must be initialized before calling this. * @@ -814,21 +849,30 @@ void silc_client_run(SilcClient client); ***/ void silc_client_run_one(SilcClient client); + /****f* silcclient/SilcClientAPI/silc_client_stop * * SYNOPSIS * - * void silc_client_stop(SilcClient client); + * void silc_client_stop(SilcClient client, SilcClientStopped stopped, + * void *context); * * DESCRIPTION * * Stops the client. This is called to stop the client and thus to stop * the program. The client context must be freed with the silc_client_free - * function. + * function. All connections that exist in this client must be closed + * before calling this function. Connections can be closed by calling + * silc_client_close_connection. + * + * The `stopped' will be called once the client and all connections have + * finished. The client may be freed after that. Note that the `stopped' + * won't be called before all connections have finished. Setting the + * callback is optional. * ***/ -void silc_client_stop(SilcClient client); - +void silc_client_stop(SilcClient client, SilcClientStopped stopped, + void *context); /* Connecting functions */ @@ -935,7 +979,7 @@ typedef struct { * * SYNOPSIS * - * SilcBool + * SilcAsyncOperation * silc_client_connect_to_server(SilcClient client, * SilcClientConnectionParams *params, * SilcPublicKey public_key, @@ -959,23 +1003,25 @@ typedef struct { * the silc_client_key_exchange after creating the connection to start * key exchange and authentication with the server. * - * Returns when connecting is started and FALSE if connection was not - * created at all. + * Returns SilcAsyncOperation which can be used to cancel the connecting, + * or NULL on error. Note that the returned pointer becomes invalid + * after the `callback' is called. * ***/ -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); /****f* silcclient/SilcClientAPI/silc_client_connect_to_client * * SYNOPSIS * - * SilcBool + * SilcAsyncOperation * silc_client_connect_to_client(SilcClient client, * SilcClientConnectionParams *params, * SilcPublicKey public_key, @@ -998,23 +1044,25 @@ SilcBool silc_client_connect_to_server(SilcClient client, * the silc_client_key_exchange after creating the connection to start * key exchange with the client. * - * Returns when connecting is started and FALSE if connection was not - * created at all. + * Returns SilcAsyncOperation which can be used to cancel the connecting, + * or NULL on error. Note that the returned pointer becomes invalid + * after the `callback' is called. * ***/ -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); /****f* silcclient/SilcClientAPI/silc_client_key_exchange * * SYNOPSIS * - * SilcBool + * SilcAsyncOperation * silc_client_key_exchange(SilcClient client, * SilcClientConnectionParams *params, * SilcPublicKey public_key, @@ -1047,8 +1095,9 @@ SilcBool silc_client_connect_to_client(SilcClient client, * disconnects. The `conn_type' is the type of session this is going to * be. * - * Returns TRUE when key exchange is started and FALSE if it is not started - * at all. + * Returns SilcAsyncOperation which can be used to cancel the connecting, + * or NULL on error. Note that the returned pointer becomes invalid + * after the `callback' is called. * * EXAMPLE * @@ -1074,14 +1123,15 @@ SilcBool silc_client_connect_to_client(SilcClient client, * } * ***/ -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); /****f* silcclient/SilcClientAPI/silc_client_close_connection * -- 2.24.0