client.c
- Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
+ Author: Pekka Riikonen <priikone@silcnet.org>
- Copyright (C) 1997 - 2001 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
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
+ the Free Software Foundation; version 2 of the License.
+
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
-/* $Id$ */
-#include "clientlibincludes.h"
+#include "silc.h"
+#include "silcclient.h"
#include "client_internal.h"
-/* Static task callback prototypes */
-SILC_TASK_CALLBACK(silc_client_connect_to_server_start);
-SILC_TASK_CALLBACK(silc_client_connect_to_server_second);
-SILC_TASK_CALLBACK(silc_client_connect_to_server_final);
-SILC_TASK_CALLBACK(silc_client_packet_parse_real);
-
-static void silc_client_packet_parse(SilcPacketParserContext *parser_context);
-static void silc_client_packet_parse_type(SilcClient client,
- SilcSocketConnection sock,
- SilcPacketContext *packet);
+/************************ Static utility functions **************************/
-/* 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
- and caller must free it. */
+/* Connection machine FSM destructor. This will finish the thread where
+ the machine was running and deletes the connection context. */
-SilcClient silc_client_alloc(SilcClientOperations *ops, void *application)
+static void silc_client_connection_destructor(SilcFSM fsm,
+ void *fsm_context,
+ void *destructor_context)
{
- SilcClient new_client;
+ SilcClientConnection conn = fsm_context;
+ SilcFSMThread thread = destructor_context;
- new_client = silc_calloc(1, sizeof(*new_client));
- new_client->application = application;
- new_client->ops = ops;
+ SILC_LOG_DEBUG(("Connection %p finished", conn));
- return new_client;
+ /* Delete connection */
+ silc_client_del_connection(conn->client, conn);
+
+ /* Finish the thread were this machine was running. Its destructor is the
+ silc_client_connection_finished. */
+ silc_fsm_finish(thread);
}
-/* Frees client object and its internals. */
+/* 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. */
-void silc_client_free(SilcClient client)
+static void silc_client_connection_finished(SilcFSMThread fsm,
+ void *fsm_context,
+ void *destructor_context)
{
- if (client) {
- if (client->rng)
- silc_rng_free(client->rng);
+ SilcClient client = silc_fsm_get_state_context(fsm);
- silc_free(client);
- }
+ /* 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);
}
-/* Initializes the client. This makes all the necessary steps to make
- the client ready to be run. One must call silc_client_run to run the
- client. Returns FALSE if error occured, TRUE otherwise. */
+/* Packet FSM thread destructor */
-int silc_client_init(SilcClient client)
+static void silc_client_packet_destructor(SilcFSMThread thread,
+ void *thread_context,
+ void *destructor_context)
{
- SILC_LOG_DEBUG(("Initializing client"));
+ SilcClientConnection conn = thread_context;
- /* Initialize hash functions for client to use */
- silc_hash_alloc("md5", &client->md5hash);
- silc_hash_alloc("sha1", &client->sha1hash);
+ /* Add thread back to thread pool */
+ silc_list_add(conn->internal->thread_pool, thread);
+ if (silc_list_count(conn->internal->thread_pool) == 1)
+ silc_list_start(conn->internal->thread_pool);
+}
- /* Initialize none cipher */
- silc_cipher_alloc("none", &client->none_cipher);
+/* Packet engine callback to receive a packet */
- /* Initialize random number generator */
- client->rng = silc_rng_alloc();
- silc_rng_init(client->rng);
- silc_rng_global_init(client->rng);
+static SilcBool silc_client_packet_receive(SilcPacketEngine engine,
+ SilcPacketStream stream,
+ SilcPacket packet,
+ void *callback_context,
+ void *stream_context)
+{
+ SilcClientConnection conn = stream_context;
+ SilcFSMThread thread;
+
+ /* Packets we do not handle */
+ switch (packet->type) {
+ case SILC_PACKET_HEARTBEAT:
+ case SILC_PACKET_SUCCESS:
+ case SILC_PACKET_FAILURE:
+ case SILC_PACKET_REJECT:
+ case SILC_PACKET_KEY_EXCHANGE:
+ case SILC_PACKET_KEY_EXCHANGE_1:
+ case SILC_PACKET_KEY_EXCHANGE_2:
+ case SILC_PACKET_REKEY_DONE:
+ case SILC_PACKET_CONNECTION_AUTH:
+ return FALSE;
+ break;
+ }
- /* Register protocols */
- silc_client_protocols_register();
+ /* Get packet processing thread */
+ thread = silc_list_get(conn->internal->thread_pool);
+ if (!thread) {
+ thread = silc_fsm_thread_alloc(&conn->internal->fsm, conn,
+ silc_client_packet_destructor, NULL, FALSE);
+ if (!thread)
+ return FALSE;
+ } else {
+ silc_list_del(conn->internal->thread_pool, thread);
+ silc_fsm_thread_init(thread, &conn->internal->fsm, conn,
+ silc_client_packet_destructor, NULL, FALSE);
+ }
- /* Initialize the scheduler */
- silc_schedule_init(&client->io_queue, &client->timeout_queue,
- &client->generic_queue, 5000);
+ /* Process packet in thread */
+ silc_fsm_set_state_context(thread, packet);
+ silc_fsm_start_sync(thread, silc_client_connection_st_packet);
return TRUE;
}
-/* Stops the client. This is called to stop the client and thus to stop
- the program. */
+/* Packet engine callback to indicate end of stream */
-void silc_client_stop(SilcClient client)
+static void silc_client_packet_eos(SilcPacketEngine engine,
+ SilcPacketStream stream,
+ void *callback_context,
+ void *stream_context)
{
- SILC_LOG_DEBUG(("Stopping client"));
-
- /* Stop the scheduler, although it might be already stopped. This
- doesn't hurt anyone. This removes all the tasks and task queues,
- as well. */
- silc_schedule_stop();
- silc_schedule_uninit();
+ SilcClientConnection conn = stream_context;
- silc_client_protocols_unregister();
+ SILC_LOG_DEBUG(("Remote disconnected connection"));
- SILC_LOG_DEBUG(("Client stopped"));
+ /* 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);
+ }
}
-/* Runs the client. This starts the scheduler from the utility library.
- When this functions returns the execution of the appliation is over. */
+/* Packet engine callback to indicate error */
-void silc_client_run(SilcClient client)
+static void silc_client_packet_error(SilcPacketEngine engine,
+ SilcPacketStream stream,
+ SilcPacketError error,
+ void *callback_context,
+ void *stream_context)
{
- SILC_LOG_DEBUG(("Running client"));
+ SilcClient client = callback_context;
+ SilcClientConnection conn = stream_context;
- /* Start the scheduler, the heart of the SILC client. When this returns
- the program will be terminated. */
- silc_schedule();
-}
-
-/* Allocates and adds new connection to the client. This adds the allocated
- connection to the connection table and returns a pointer to it. A client
- can have multiple connections to multiple servers. Every connection must
- be added to the client using this function. User data `context' may
- be sent as argument. This function is normally used only if the
- application performed the connecting outside the library. The library
- however may use this internally. */
-
-SilcClientConnection silc_client_add_connection(SilcClient client,
- char *hostname,
- int port,
- void *context)
-{
- SilcClientConnection conn;
- int i;
-
- conn = silc_calloc(1, sizeof(*conn));
-
- /* Initialize ID caches */
- conn->client_cache = silc_idcache_alloc(0, NULL);
- conn->channel_cache = silc_idcache_alloc(0, NULL);
- conn->server_cache = silc_idcache_alloc(0, NULL);
- conn->client = client;
- conn->remote_host = strdup(hostname);
- conn->remote_port = port;
- conn->context = context;
- conn->pending_commands = silc_dlist_init();
-
- /* Add the connection to connections table */
- for (i = 0; i < client->conns_count; i++)
- if (client->conns && !client->conns[i]) {
- client->conns[i] = conn;
- return conn;
- }
-
- client->conns = silc_realloc(client->conns, sizeof(*client->conns)
- * (client->conns_count + 1));
- client->conns[client->conns_count] = conn;
- client->conns_count++;
+ /* Read and write errors are silent */
+ if (error == SILC_PACKET_ERR_READ || error == SILC_PACKET_ERR_WRITE)
+ return;
- return conn;
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+ (char *)silc_packet_error_string(error));
}
-/* Removes connection from client. Frees all memory. */
-
-void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
+/* Packet stream callbacks */
+static SilcPacketCallbacks silc_client_stream_cbs =
{
- int i;
-
- for (i = 0; i < client->conns_count; i++)
- if (client->conns[i] == conn) {
- if (conn->pending_commands)
- silc_dlist_uninit(conn->pending_commands);
- silc_free(conn);
- client->conns[i] = NULL;
- }
-}
+ silc_client_packet_receive,
+ silc_client_packet_eos,
+ silc_client_packet_error
+};
-static int
-silc_client_connect_to_server_internal(SilcClientInternalConnectContext *ctx)
+/* FSM destructor */
+
+void silc_client_fsm_destructor(SilcFSM fsm, void *fsm_context,
+ void *destructor_context)
{
- int sock;
-
- /* XXX In the future we should give up this non-blocking connect all
- together and use threads instead. */
- /* Create connection to server asynchronously */
- sock = silc_net_create_connection_async(ctx->port, ctx->host);
- if (sock < 0)
- return -1;
-
- /* Register task that will receive the async connect and will
- read the result. */
- ctx->task = silc_task_register(ctx->client->io_queue, sock,
- silc_client_connect_to_server_start,
- (void *)ctx, 0, 0,
- SILC_TASK_FD,
- SILC_TASK_PRI_NORMAL);
- silc_task_reset_iotype(ctx->task, SILC_TASK_WRITE);
- silc_schedule_set_listen_fd(sock, ctx->task->iomask);
-
- ctx->sock = sock;
-
- return sock;
+ silc_fsm_free(fsm);
}
-/* Connects to remote server. This is the main routine used to connect
- to SILC server. Returns -1 on error and the created socket otherwise.
- The `context' is user context that is saved into the SilcClientConnection
- that is created after the connection is created. Note that application
- may handle the connecting process outside the library. If this is the
- case then this function is not used at all. When the connecting is
- done the `connect' client operation is called. */
+/* Connect abort operation */
-int silc_client_connect_to_server(SilcClient client, int port,
- char *host, void *context)
+static void silc_client_connect_abort(SilcAsyncOperation op, void *context)
{
- SilcClientInternalConnectContext *ctx;
- SilcClientConnection conn;
- int sock;
-
- SILC_LOG_DEBUG(("Connecting to port %d of server %s",
- port, host));
+ SilcClientConnection conn = context;
- conn = silc_client_add_connection(client, host, port, context);
+ SILC_LOG_DEBUG(("Connection %p aborted by application", conn));
- client->ops->say(client, conn,
- "Connecting to port %d of server %s", port, host);
+ /* Connection callback will not be called after user aborted connecting */
+ conn->callback = NULL;
+ conn->internal->cop = NULL;
- /* Allocate internal context for connection process. This is
- needed as we are doing async connecting. */
- ctx = silc_calloc(1, sizeof(*ctx));
- ctx->client = client;
- ctx->conn = conn;
- ctx->host = strdup(host);
- ctx->port = port;
- ctx->tries = 0;
+ /* Signal to close connection */
+ if (!conn->internal->disconnected) {
+ conn->internal->disconnected = TRUE;
- /* Do the actual connecting process */
- sock = silc_client_connect_to_server_internal(ctx);
- if (sock == -1)
- silc_client_del_connection(client, conn);
- return sock;
+ /* 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);
+ }
}
-/* Start SILC Key Exchange (SKE) protocol to negotiate shared secret
- key material between client and server. This function can be called
- directly if application is performing its own connecting and does not
- use the connecting provided by this library. This function is normally
- used only if the application performed the connecting outside the library.
- The library however may use this internally. */
+/************************** Connection's machine ****************************/
-int silc_client_start_key_exchange(SilcClient client,
- SilcClientConnection conn,
- int fd)
+/* Start the connection's state machine. If threads are in use the machine
+ is always executed in a real thread. */
+
+SILC_FSM_STATE(silc_client_connection_st_start)
{
- SilcProtocol protocol;
- SilcClientKEInternalContext *proto_ctx;
- void *context;
-
- /* Allocate new socket connection object */
- silc_socket_alloc(fd, SILC_SOCKET_TYPE_SERVER, (void *)conn, &conn->sock);
-
- conn->nickname = strdup(client->username);
- conn->sock->hostname = conn->remote_host;
- conn->sock->ip = strdup(conn->remote_host);
- conn->sock->port = conn->remote_port;
-
- /* Allocate internal Key Exchange context. This is sent to the
- protocol as context. */
- proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
- proto_ctx->client = (void *)client;
- proto_ctx->sock = conn->sock;
- proto_ctx->rng = client->rng;
- proto_ctx->responder = FALSE;
- proto_ctx->send_packet = silc_client_protocol_ke_send_packet;
- proto_ctx->verify = silc_client_protocol_ke_verify_key;
-
- /* Perform key exchange protocol. silc_client_connect_to_server_final
- will be called after the protocol is finished. */
- silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE,
- &protocol, (void *)proto_ctx,
- silc_client_connect_to_server_second);
- if (!protocol) {
- client->ops->say(client, conn,
- "Error: Could not start authentication protocol");
- return FALSE;
- }
- conn->sock->protocol = protocol;
-
- /* Register the connection for network input and output. This sets
- that scheduler will listen for incoming packets for this connection
- and sets that outgoing packets may be sent to this connection as well.
- However, this doesn't set the scheduler for outgoing traffic, it will
- be set separately by calling SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT,
- later when outgoing data is available. */
- context = (void *)client;
- SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(fd);
-
- /* Execute the protocol */
- protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0);
- return TRUE;
+ SilcClientConnection conn = fsm_context;
+ SilcFSM connfsm;
+
+ /* Take scheduler for connection */
+ conn->internal->schedule = silc_fsm_get_schedule(fsm);
+
+ /*** Run connection machine */
+ connfsm = &conn->internal->fsm;
+ silc_fsm_init(connfsm, conn, silc_client_connection_destructor,
+ fsm, conn->internal->schedule);
+ silc_fsm_event_init(&conn->internal->wait_event, connfsm);
+ silc_fsm_start_sync(connfsm, silc_client_connection_st_run);
+
+ /* 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)
+ SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+
+ /* Wait until this thread is terminated from the machine destructor */
+ return SILC_FSM_WAIT;
}
-/* Start of the connection to the remote server. This is called after
- succesful TCP/IP connection has been established to the remote host. */
+/* Connection machine main state. This handles various connection related
+ events, but not packet processing. It's done in dedicated packet
+ processing FSM thread. */
-SILC_TASK_CALLBACK(silc_client_connect_to_server_start)
+SILC_FSM_STATE(silc_client_connection_st_run)
{
- SilcClientInternalConnectContext *ctx =
- (SilcClientInternalConnectContext *)context;
- SilcClient client = ctx->client;
- SilcClientConnection conn = ctx->conn;
- int opt, opt_len = sizeof(opt);
-
- SILC_LOG_DEBUG(("Start"));
-
- /* Check the socket status as it might be in error */
- getsockopt(fd, SOL_SOCKET, SO_ERROR, &opt, &opt_len);
- if (opt != 0) {
- if (ctx->tries < 2) {
- /* Connection failed but lets try again */
- client->ops->say(client, conn, "Could not connect to server %s: %s",
- ctx->host, strerror(opt));
- client->ops->say(client, conn,
- "Connecting to port %d of server %s resumed",
- ctx->port, ctx->host);
-
- /* Unregister old connection try */
- silc_schedule_unset_listen_fd(fd);
- silc_net_close_connection(fd);
- silc_task_unregister(client->io_queue, ctx->task);
-
- /* Try again */
- silc_client_connect_to_server_internal(ctx);
- ctx->tries++;
- } else {
- /* Connection failed and we won't try anymore */
- client->ops->say(client, conn, "Could not connect to server %s: %s",
- ctx->host, strerror(opt));
- silc_schedule_unset_listen_fd(fd);
- silc_net_close_connection(fd);
- silc_task_unregister(client->io_queue, ctx->task);
- silc_free(ctx);
-
- /* Notify application of failure */
- client->ops->connect(client, conn, FALSE);
- silc_client_del_connection(client, conn);
- }
- return;
- }
+ SilcClientConnection conn = fsm_context;
+ SilcFSMThread thread;
- silc_schedule_unset_listen_fd(fd);
- silc_task_unregister(client->io_queue, ctx->task);
- silc_free(ctx);
+ /* Wait for events */
+ SILC_FSM_EVENT_WAIT(&conn->internal->wait_event);
- if (!silc_client_start_key_exchange(client, conn, fd)) {
- silc_net_close_connection(fd);
- client->ops->connect(client, conn, FALSE);
+ /* 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;
}
-}
-/* Second part of the connecting to the server. This executed
- authentication protocol. */
+ if (conn->internal->connect) {
+ SILC_LOG_DEBUG(("Event: connect"));
+ conn->internal->connect = FALSE;
+ SILC_ASSERT(silc_fsm_is_started(thread) == FALSE);
-SILC_TASK_CALLBACK(silc_client_connect_to_server_second)
-{
- SilcProtocol protocol = (SilcProtocol)context;
- SilcClientKEInternalContext *ctx =
- (SilcClientKEInternalContext *)protocol->context;
- SilcClient client = (SilcClient)ctx->client;
- SilcSocketConnection sock = NULL;
- SilcClientConnAuthInternalContext *proto_ctx;
-
- 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 KE protocol"));
- silc_protocol_free(protocol);
- silc_ske_free_key_material(ctx->keymat);
- if (ctx->ske)
- silc_ske_free(ctx->ske);
- if (ctx->dest_id)
- silc_free(ctx->dest_id);
- ctx->sock->protocol = NULL;
- silc_task_unregister_by_callback(client->timeout_queue,
- silc_client_failure_callback);
-
- /* Notify application of failure */
- client->ops->connect(client, ctx->sock->user_data, FALSE);
- silc_free(ctx);
- return;
+ /*** Event: connect */
+ silc_fsm_thread_init(thread, &conn->internal->fsm, conn,
+ NULL, NULL, FALSE);
+ silc_fsm_start_sync(thread, silc_client_st_connect);
+ return SILC_FSM_CONTINUE;
}
- /* We now have the key material as the result of the key exchange
- protocol. Take the key material into use. Free the raw key material
- as soon as we've set them into use. */
- silc_client_protocol_ke_set_keys(ctx->ske, ctx->sock, ctx->keymat,
- ctx->ske->prop->cipher,
- ctx->ske->prop->pkcs,
- ctx->ske->prop->hash,
- ctx->ske->prop->hmac);
- silc_ske_free_key_material(ctx->keymat);
-
- /* Allocate internal context for the authentication protocol. This
- is sent as context for the protocol. */
- proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
- proto_ctx->client = (void *)client;
- proto_ctx->sock = sock = ctx->sock;
- proto_ctx->ske = ctx->ske; /* Save SKE object from previous protocol */
- proto_ctx->dest_id_type = ctx->dest_id_type;
- proto_ctx->dest_id = ctx->dest_id;
-
- /* Resolve the authentication method to be used in this connection */
- if (!client->ops->get_auth_method(client, sock->user_data, sock->hostname,
- sock->port, &proto_ctx->auth_meth,
- &proto_ctx->auth_data,
- &proto_ctx->auth_data_len))
- {
- /* XXX do AUTH_REQUEST resolcing with server */
- proto_ctx->auth_meth = SILC_AUTH_NONE;
- }
+ if (conn->internal->key_exchange) {
+ SILC_LOG_DEBUG(("Event: key exchange"));
+ conn->internal->key_exchange = FALSE;
+ SILC_ASSERT(silc_fsm_is_started(thread) == FALSE);
- /* Free old protocol as it is finished now */
- silc_protocol_free(protocol);
- if (ctx->packet)
- silc_packet_context_free(ctx->packet);
- silc_free(ctx);
- /* silc_free(ctx->keymat....); */
- sock->protocol = NULL;
-
- /* Allocate the authentication protocol. This is allocated here
- but we won't start it yet. We will be receiving party of this
- protocol thus we will wait that connecting party will make
- their first move. */
- silc_protocol_alloc(SILC_PROTOCOL_CLIENT_CONNECTION_AUTH,
- &sock->protocol, (void *)proto_ctx,
- silc_client_connect_to_server_final);
-
- /* Execute the protocol */
- sock->protocol->execute(client->timeout_queue, 0, sock->protocol, fd, 0, 0);
-}
+ /*** Event: key exchange */
+ silc_fsm_thread_init(thread, &conn->internal->fsm, conn,
+ NULL, NULL, FALSE);
+ silc_fsm_start_sync(thread, silc_client_st_connect_set_stream);
+ return SILC_FSM_CONTINUE;
+ }
-/* 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. */
+ if (conn->internal->rekeying) {
+ SILC_LOG_DEBUG(("Event: rekey"));
+ conn->internal->rekeying = FALSE;
+ SILC_ASSERT(silc_fsm_is_started(thread) == FALSE);
-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"));
- silc_protocol_free(protocol);
- if (ctx->auth_data)
- silc_free(ctx->auth_data);
- if (ctx->ske)
- silc_ske_free(ctx->ske);
- if (ctx->dest_id)
- silc_free(ctx->dest_id);
- conn->sock->protocol = NULL;
- silc_task_unregister_by_callback(client->timeout_queue,
- silc_client_failure_callback);
-
- /* Notify application of failure */
- client->ops->connect(client, ctx->sock->user_data, FALSE);
- silc_free(ctx);
- return;
+ /*** 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;
}
- /* 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_SERVER_LEN;
-
- silc_task_unregister_by_callback(client->timeout_queue,
- silc_client_failure_callback);
- silc_protocol_free(protocol);
- if (ctx->auth_data)
- silc_free(ctx->auth_data);
- if (ctx->ske)
- silc_ske_free(ctx->ske);
- silc_free(ctx);
- conn->sock->protocol = NULL;
+ /* NOT REACHED */
+ SILC_ASSERT(FALSE);
+ return SILC_FSM_CONTINUE;
}
-/* Internal routine that sends packet or marks packet to be sent. This
- is used directly only in special cases. Normal cases should use
- silc_server_packet_send. Returns < 0 on error. */
+/* Packet processor thread. Each incoming packet is processed in FSM
+ thread in this state. The thread is run in the connection machine. */
-int silc_client_packet_send_real(SilcClient client,
- SilcSocketConnection sock,
- int force_send)
+SILC_FSM_STATE(silc_client_connection_st_packet)
{
- int ret;
+ SilcClientConnection conn = fsm_context;
+ SilcPacket packet = state_context;
- /* Send the packet */
- ret = silc_packet_send(sock, force_send);
- if (ret != -2)
- return ret;
+ SILC_LOG_DEBUG(("Parsing %s packet", silc_get_packet_name(packet->type)));
- /* Mark that there is some outgoing data available for this connection.
- This call sets the connection both for input and output (the input
- is set always and this call keeps the input setting, actually).
- Actual data sending is performed by silc_client_packet_process. */
- SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT(sock->sock);
+ switch (packet->type) {
- /* Mark to socket that data is pending in outgoing buffer. This flag
- is needed if new data is added to the buffer before the earlier
- put data is sent to the network. */
- SILC_SET_OUTBUF_PENDING(sock);
+ case SILC_PACKET_PRIVATE_MESSAGE:
+ /** Private message */
+ silc_fsm_next(fsm, silc_client_private_message);
+ break;
- return 0;
-}
+ case SILC_PACKET_CHANNEL_MESSAGE:
+ /** Channel message */
+ silc_fsm_next(fsm, silc_client_channel_message);
+ break;
-/* Packet processing callback. This is used to send and receive packets
- from network. This is generic task. */
+ case SILC_PACKET_FTP:
+ /* File transfer packet */
+ silc_fsm_next(fsm, silc_client_ftp);
+ break;
-SILC_TASK_CALLBACK_GLOBAL(silc_client_packet_process)
-{
- SilcClient client = (SilcClient)context;
- SilcSocketConnection sock = NULL;
- SilcClientConnection conn;
- int ret;
+ case SILC_PACKET_CHANNEL_KEY:
+ /** Channel key */
+ silc_fsm_next(fsm, silc_client_channel_key);
+ break;
- SILC_LOG_DEBUG(("Processing packet"));
+ case SILC_PACKET_COMMAND_REPLY:
+ /** Command reply */
+ silc_fsm_next(fsm, silc_client_command_reply);
+ break;
- SILC_CLIENT_GET_SOCK(client, fd, sock);
- if (sock == NULL)
- return;
+ case SILC_PACKET_NOTIFY:
+ /** Notify */
+ silc_fsm_next(fsm, silc_client_notify);
+ break;
- conn = (SilcClientConnection)sock->user_data;
+ case SILC_PACKET_PRIVATE_MESSAGE_KEY:
+ /* Private message key indicator */
+ silc_fsm_next(fsm, silc_client_private_message_key);
+ break;
- /* Packet sending */
- if (type == SILC_TASK_WRITE) {
- SILC_LOG_DEBUG(("Writing data to connection"));
+ case SILC_PACKET_DISCONNECT:
+ /** Disconnect */
+ silc_fsm_next(fsm, silc_client_disconnect);
+ break;
- if (sock->outbuf->data - sock->outbuf->head)
- silc_buffer_push(sock->outbuf,
- sock->outbuf->data - sock->outbuf->head);
+ case SILC_PACKET_ERROR:
+ /* Error by server */
+ silc_fsm_next(fsm, silc_client_error);
+ break;
- ret = silc_client_packet_send_real(client, sock, TRUE);
+ case SILC_PACKET_KEY_AGREEMENT:
+ /** Key agreement */
+ silc_fsm_next(fsm, silc_client_key_agreement);
+ break;
- /* If returned -2 could not write to connection now, will do
- it later. */
- if (ret == -2)
- return;
-
- /* The packet has been sent and now it is time to set the connection
- back to only for input. When there is again some outgoing data
- available for this connection it will be set for output as well.
- This call clears the output setting and sets it only for input. */
- SILC_CLIENT_SET_CONNECTION_FOR_INPUT(fd);
- SILC_UNSET_OUTBUF_PENDING(sock);
+ case SILC_PACKET_COMMAND:
+ /** Command packet */
+ silc_fsm_next(fsm, silc_client_command);
+ break;
- silc_buffer_clear(sock->outbuf);
- return;
- }
+ case SILC_PACKET_NEW_ID:
+ /** New ID */
+ silc_fsm_next(fsm, silc_client_new_id);
+ break;
- /* Packet receiving */
- if (type == SILC_TASK_READ) {
- SILC_LOG_DEBUG(("Reading data from connection"));
-
- /* Read data from network */
- ret = silc_packet_receive(sock);
- if (ret < 0)
- return;
-
- /* EOF */
- if (ret == 0) {
- SILC_LOG_DEBUG(("Read EOF"));
-
- /* If connection is disconnecting already we will finally
- close the connection */
- if (SILC_IS_DISCONNECTING(sock)) {
- client->ops->disconnect(client, conn);
- silc_client_close_connection(client, conn);
- return;
- }
-
- SILC_LOG_DEBUG(("EOF from connection %d", sock->sock));
- client->ops->disconnect(client, conn);
- silc_client_close_connection(client, conn);
- return;
- }
+ case SILC_PACKET_CONNECTION_AUTH_REQUEST:
+ /** Connection auth resolve reply */
+ silc_fsm_next(fsm, silc_client_connect_auth_request);
+ break;
- /* Process the packet. This will call the parser that will then
- decrypt and parse the packet. */
- silc_packet_receive_process(sock, conn->receive_key, conn->hmac,
- silc_client_packet_parse, client);
- }
-}
+ 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);
-/* Callback function that the silc_packet_decrypt will call to make the
- decision whether the packet is normal or special packet. We will
- return TRUE if it is normal and FALSE if it is special */
+ silc_packet_free(packet);
+ return SILC_FSM_FINISH;
+ break;
-static int silc_client_packet_decrypt_check(SilcPacketType packet_type,
- SilcBuffer buffer,
- SilcPacketContext *packet,
- void *context)
-{
+ default:
+ silc_packet_free(packet);
+ return SILC_FSM_FINISH;
+ break;
+ }
- /* Packet is normal packet, if:
+ return SILC_FSM_CONTINUE;
+}
- 1) packet is private message packet and does not have private key set
- 2) is other packet than channel message 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 is set. */
- all other packets are special packets
- */
- if ((packet_type == SILC_PACKET_PRIVATE_MESSAGE &&
- !(buffer->data[2] & SILC_PACKET_FLAG_PRIVMSG_KEY)) ||
- packet_type != SILC_PACKET_CHANNEL_MESSAGE)
- return TRUE;
+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);
+ }
+ }
- return FALSE;
-}
+ /* Give threads time to finish */
+ return SILC_FSM_YIELD;
+ }
-/* Parses whole packet, received earlier. */
+ /* Abort ongoing event */
+ if (conn->internal->op) {
+ SILC_LOG_DEBUG(("Abort event"));
+ silc_async_abort(conn->internal->op, NULL, NULL);
+ conn->internal->op = NULL;
+ }
-SILC_TASK_CALLBACK(silc_client_packet_parse_real)
-{
- SilcPacketParserContext *parse_ctx = (SilcPacketParserContext *)context;
- SilcClient client = (SilcClient)parse_ctx->context;
- SilcPacketContext *packet = parse_ctx->packet;
- SilcBuffer buffer = packet->buffer;
- SilcSocketConnection sock = parse_ctx->sock;
- SilcClientConnection conn = (SilcClientConnection)sock->user_data;
- int ret;
-
- SILC_LOG_DEBUG(("Start"));
-
- /* Decrypt the received packet */
- ret = silc_packet_decrypt(conn->receive_key, conn->hmac, buffer, packet,
- silc_client_packet_decrypt_check, parse_ctx);
- if (ret < 0)
- goto out;
-
- if (ret == 0) {
- /* Parse the packet. Packet type is returned. */
- ret = silc_packet_parse(packet);
- } else {
- /* Parse the packet header in special way as this is "special"
- packet type. */
- ret = silc_packet_parse_special(packet);
+ /* 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;
}
- if (ret == SILC_PACKET_NONE)
- goto out;
+ /* 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);
- /* Parse the incoming packet type */
- silc_client_packet_parse_type(client, sock, packet);
+ SILC_LOG_DEBUG(("Closing remote connection"));
- out:
- silc_buffer_clear(sock->inbuf);
- silc_packet_context_free(packet);
- silc_free(parse_ctx);
+ /* Close connection. */
+ if (conn->stream)
+ silc_packet_stream_destroy(conn->stream);
+
+ SILC_LOG_DEBUG(("Finishing connection machine"));
+ return SILC_FSM_FINISH;
}
-/* Parser callback called by silc_packet_receive_process. Thie merely
- registers timeout that will handle the actual parsing when appropriate. */
+/* Received error packet from server. Send it to application. */
-void silc_client_packet_parse(SilcPacketParserContext *parser_context)
+SILC_FSM_STATE(silc_client_error)
{
- SilcClient client = (SilcClient)parser_context->context;
-
- /* Parse the packet */
- silc_task_register(client->timeout_queue, parser_context->sock->sock,
- silc_client_packet_parse_real,
- (void *)parser_context, 0, 1,
- SILC_TASK_TIMEOUT,
- SILC_TASK_PRI_NORMAL);
-}
-
-/* Parses the packet type and calls what ever routines the packet type
- requires. This is done for all incoming packets. */
+ SilcClientConnection conn = fsm_context;
+ SilcClient client = conn->client;
+ SilcPacket packet = state_context;
+ char *msg;
-void silc_client_packet_parse_type(SilcClient client,
- SilcSocketConnection sock,
- SilcPacketContext *packet)
-{
- SilcBuffer buffer = packet->buffer;
- SilcPacketType type = packet->type;
+ msg = silc_memdup(silc_buffer_data(&packet->buffer),
+ silc_buffer_len(&packet->buffer));
+ if (msg)
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, msg);
- SILC_LOG_DEBUG(("Parsing packet type %d", type));
+ silc_free(msg);
+ silc_packet_free(packet);
- /* Parse the packet type */
- switch(type) {
- case SILC_PACKET_DISCONNECT:
- silc_client_disconnected_by_server(client, sock, buffer);
- break;
- case SILC_PACKET_SUCCESS:
- /*
- * Success received for something. For now we can have only
- * one protocol for connection executing at once hence this
- * success message is for whatever protocol is executing currently.
- */
- if (sock->protocol) {
- sock->protocol->execute(client->timeout_queue, 0,
- sock->protocol, sock->sock, 0, 0);
- }
- break;
- case SILC_PACKET_FAILURE:
- /*
- * Failure received for some protocol. Set the protocol state to
- * error and call the protocol callback. This fill cause error on
- * protocol and it will call the final callback.
- */
- silc_client_process_failure(client, sock, packet);
- break;
- case SILC_PACKET_REJECT:
- break;
+ return SILC_FSM_FINISH;
+}
- case SILC_PACKET_NOTIFY:
- /*
- * Received notify message
- */
- silc_client_notify_by_server(client, sock, packet);
- break;
+/* Received disconnect packet from server. We close the connection and
+ send the disconnect message to application. */
- case SILC_PACKET_ERROR:
- /*
- * Received error message
- */
- silc_client_error_by_server(client, sock, buffer);
- break;
+SILC_FSM_STATE(silc_client_disconnect)
+{
+ SilcClientConnection conn = fsm_context;
+ SilcPacket packet = state_context;
+ SilcStatus status;
+ char *message = NULL;
- case SILC_PACKET_CHANNEL_MESSAGE:
- /*
- * Received message to (from, actually) a channel
- */
- silc_client_channel_message(client, sock, packet);
- break;
- case SILC_PACKET_CHANNEL_KEY:
- /*
- * Received key for a channel. By receiving this key the client will be
- * able to talk to the channel it has just joined. This can also be
- * a new key for existing channel as keys expire peridiocally.
- */
- silc_client_receive_channel_key(client, sock, buffer);
- break;
+ SILC_LOG_DEBUG(("Server disconnected"));
- case SILC_PACKET_PRIVATE_MESSAGE:
- /*
- * Received private message
- */
- silc_client_private_message(client, sock, packet);
- break;
- case SILC_PACKET_PRIVATE_MESSAGE_KEY:
- /*
- * Received private message key
- */
- break;
+ if (silc_buffer_len(&packet->buffer) < 1) {
+ silc_packet_free(packet);
+ return SILC_FSM_FINISH;
+ }
- case SILC_PACKET_COMMAND_REPLY:
- /*
- * Recived reply for a command
- */
- silc_client_command_reply_process(client, sock, packet);
- break;
+ status = (SilcStatus)packet->buffer.data[0];
- case SILC_PACKET_KEY_EXCHANGE:
- if (sock->protocol) {
- SilcClientKEInternalContext *proto_ctx =
- (SilcClientKEInternalContext *)sock->protocol->context;
-
- proto_ctx->packet = silc_packet_context_dup(packet);
- proto_ctx->dest_id_type = packet->src_id_type;
- proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
- packet->src_id_type);
- if (!proto_ctx->dest_id)
- break;
-
- /* Let the protocol handle the packet */
- sock->protocol->execute(client->timeout_queue, 0,
- sock->protocol, sock->sock, 0, 0);
- } else {
- SILC_LOG_ERROR(("Received Key Exchange packet but no key exchange "
- "protocol active, packet dropped."));
-
- /* XXX Trigger KE protocol?? Rekey actually! */
- }
- break;
+ silc_buffer_pull(&packet->buffer, 1);
+ if (silc_buffer_len(&packet->buffer) > 1 &&
+ silc_utf8_valid(silc_buffer_data(&packet->buffer),
+ silc_buffer_len(&packet->buffer)))
+ message = silc_memdup(silc_buffer_data(&packet->buffer),
+ silc_buffer_len(&packet->buffer));
- case SILC_PACKET_KEY_EXCHANGE_1:
- if (sock->protocol) {
+ /* Call connection callback */
+ conn->internal->status = SILC_CLIENT_CONN_DISCONNECTED;
+ conn->internal->error = status;
+ conn->internal->disconnect_message = message;
- } else {
- SILC_LOG_ERROR(("Received Key Exchange 1 packet but no key exchange "
- "protocol active, packet dropped."));
- }
- break;
- case SILC_PACKET_KEY_EXCHANGE_2:
- if (sock->protocol) {
- SilcClientKEInternalContext *proto_ctx =
- (SilcClientKEInternalContext *)sock->protocol->context;
-
- if (proto_ctx->packet)
- silc_packet_context_free(proto_ctx->packet);
-
- proto_ctx->packet = silc_packet_context_dup(packet);
- proto_ctx->dest_id_type = packet->src_id_type;
- proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
- packet->src_id_type);
- if (!proto_ctx->dest_id)
- break;
-
- /* Let the protocol handle the packet */
- sock->protocol->execute(client->timeout_queue, 0,
- sock->protocol, sock->sock, 0, 0);
- } else {
- SILC_LOG_ERROR(("Received Key Exchange 2 packet but no key exchange "
- "protocol active, packet dropped."));
- }
- break;
+ /* Signal to close connection */
+ if (!conn->internal->disconnected) {
+ conn->internal->disconnected = TRUE;
+ SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event);
+ }
- case SILC_PACKET_NEW_ID:
- {
- /*
- * Received new ID from server. This packet is received at
- * the connection to the server. New ID is also received when
- * user changes nickname but in that case the new ID is received
- * as command reply and not as this packet type.
- */
- SilcIDPayload idp;
-
- idp = silc_id_payload_parse(buffer);
- if (!idp)
- break;
- if (silc_id_payload_get_type(idp) != SILC_ID_CLIENT)
- break;
-
- silc_client_receive_new_id(client, sock, idp);
- silc_id_payload_free(idp);
- break;
- }
+ silc_packet_free(packet);
- case SILC_PACKET_HEARTBEAT:
- /*
- * Received heartbeat packet
- */
- SILC_LOG_DEBUG(("Heartbeat packet"));
- break;
+ return SILC_FSM_FINISH;
+}
- case SILC_PACKET_KEY_AGREEMENT:
- /*
- * Received key agreement packet
- */
- SILC_LOG_DEBUG(("Key agreement packet"));
- silc_client_key_agreement(client, sock, packet);
- break;
+/*************************** Main client machine ****************************/
- default:
- SILC_LOG_DEBUG(("Incorrect packet type %d, packet dropped", type));
- break;
- }
-}
+/* The client's main state where we wait for various events */
-/* Sends packet. This doesn't actually send the packet instead it assembles
- it and marks it to be sent. However, if force_send is TRUE the packet
- is sent immediately. if dst_id, cipher and hmac are NULL those parameters
- will be derived from sock argument. Otherwise the valid arguments sent
- are used. */
-
-void silc_client_packet_send(SilcClient client,
- SilcSocketConnection sock,
- SilcPacketType type,
- void *dst_id,
- SilcIdType dst_id_type,
- SilcCipher cipher,
- SilcHmac hmac,
- unsigned char *data,
- unsigned int data_len,
- int force_send)
+SILC_FSM_STATE(silc_client_st_run)
{
- SilcPacketContext packetdata;
-
- SILC_LOG_DEBUG(("Sending packet, type %d", type));
+ SilcClient client = fsm_context;
- /* Get data used in the packet sending, keys and stuff */
- if ((!cipher || !hmac || !dst_id) && sock->user_data) {
- if (!cipher && ((SilcClientConnection)sock->user_data)->send_key)
- cipher = ((SilcClientConnection)sock->user_data)->send_key;
+ /* Wait for events */
+ SILC_FSM_EVENT_WAIT(&client->internal->wait_event);
- if (!hmac && ((SilcClientConnection)sock->user_data)->hmac)
- hmac = ((SilcClientConnection)sock->user_data)->hmac;
+ /* Process events */
- if (!dst_id && ((SilcClientConnection)sock->user_data)->remote_id) {
- dst_id = ((SilcClientConnection)sock->user_data)->remote_id;
- dst_id_type = SILC_ID_SERVER;
+ if (client->internal->run_callback) {
+ /* Call running callbcak back to application */
+ client->internal->run_callback = FALSE;
+ 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;
}
- /* Set the packet context pointers */
- packetdata.flags = 0;
- packetdata.type = type;
- if (((SilcClientConnection)sock->user_data)->local_id_data)
- packetdata.src_id = ((SilcClientConnection)sock->user_data)->local_id_data;
- else
- packetdata.src_id = silc_calloc(SILC_ID_CLIENT_LEN, sizeof(unsigned char));
- packetdata.src_id_len = SILC_ID_CLIENT_LEN;
- packetdata.src_id_type = SILC_ID_CLIENT;
- if (dst_id) {
- packetdata.dst_id = silc_id_id2str(dst_id, dst_id_type);
- packetdata.dst_id_len = silc_id_get_len(dst_id_type);
- packetdata.dst_id_type = dst_id_type;
- } else {
- packetdata.dst_id = NULL;
- packetdata.dst_id_len = 0;
- packetdata.dst_id_type = SILC_ID_NONE;
+ 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;
}
- packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len + packetdata.dst_id_len;
- packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen);
- /* Prepare outgoing data buffer for packet sending */
- silc_packet_send_prepare(sock,
- SILC_PACKET_HEADER_LEN +
- packetdata.src_id_len +
- packetdata.dst_id_len,
- packetdata.padlen,
- data_len);
+ 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;
+ }
- SILC_LOG_DEBUG(("Putting data to outgoing buffer, len %d", data_len));
+ /* NOT REACHED */
+ SILC_ASSERT(FALSE);
+ return SILC_FSM_CONTINUE;
+}
- packetdata.buffer = sock->outbuf;
+/* Stop event. Stops the client library. */
- /* Put the data to the buffer */
- if (data && data_len)
- silc_buffer_put(sock->outbuf, data, data_len);
+SILC_FSM_STATE(silc_client_st_stop)
+{
+ SilcClient client = fsm_context;
- /* Create the outgoing packet */
- silc_packet_assemble(&packetdata);
+ SILC_LOG_DEBUG(("Client stopped"));
- /* Encrypt the packet */
- if (cipher)
- silc_packet_encrypt(cipher, hmac, sock->outbuf, sock->outbuf->len);
+ /* Stop scheduler */
+ silc_schedule_stop(client->schedule);
+ silc_client_commands_unregister(client);
- SILC_LOG_HEXDUMP(("Packet, len %d", sock->outbuf->len),
- sock->outbuf->data, sock->outbuf->len);
+ /* Call stopped callback to application */
+ if (client->internal->running)
+ client->internal->running(client, client->internal->running_context);
- /* Now actually send the packet */
- silc_client_packet_send_real(client, sock, force_send);
+ return SILC_FSM_FINISH;
}
-/* Closes connection to remote end. Free's all allocated data except
- for some information such as nickname etc. that are valid at all time. */
+/******************************* Private API ********************************/
-void silc_client_close_connection(SilcClient client,
- SilcClientConnection conn)
+/* Adds new connection. Creates the connection context and returns it. */
+
+SilcClientConnection
+silc_client_add_connection(SilcClient client,
+ SilcConnectionType conn_type,
+ SilcBool connect,
+ SilcClientConnectionParams *params,
+ SilcPublicKey public_key,
+ SilcPrivateKey private_key,
+ char *remote_host, int port,
+ SilcClientConnectCallback callback,
+ void *context)
{
- SilcSocketConnection sock = conn->sock;
-
- /* We won't listen for this connection anymore */
- silc_schedule_unset_listen_fd(sock->sock);
-
- /* Unregister all tasks */
- silc_task_unregister_by_fd(client->io_queue, sock->sock);
- silc_task_unregister_by_fd(client->timeout_queue, sock->sock);
-
- /* Close the actual connection */
- silc_net_close_connection(sock->sock);
-
- client->ops->say(client, sock->user_data,
- "Closed connection to host %s", sock->hostname);
-
- /* Free everything */
- if (sock->user_data) {
- /* XXX Free all client entries and channel entries. */
-
- /* Clear ID caches */
- silc_idcache_del_all(conn->client_cache);
- silc_idcache_del_all(conn->channel_cache);
-
- /* Free data */
- if (conn->remote_host)
- silc_free(conn->remote_host);
- if (conn->local_id)
- silc_free(conn->local_id);
- if (conn->local_id_data)
- silc_free(conn->local_id_data);
- if (conn->send_key)
- silc_cipher_free(conn->send_key);
- if (conn->receive_key)
- silc_cipher_free(conn->receive_key);
- if (conn->hmac)
- silc_hmac_free(conn->hmac);
- if (conn->hmac_key) {
- memset(conn->hmac_key, 0, conn->hmac_key_len);
- silc_free(conn->hmac_key);
- }
- if (conn->pending_commands)
- silc_dlist_uninit(conn->pending_commands);
-
- conn->sock = NULL;
- conn->remote_port = 0;
- conn->remote_type = 0;
- conn->send_key = NULL;
- conn->receive_key = NULL;
- conn->hmac = NULL;
- conn->hmac_key = NULL;
- conn->hmac_key_len = 0;
- conn->local_id = NULL;
- conn->local_id_data = NULL;
- conn->remote_host = NULL;
- conn->current_channel = NULL;
- conn->pending_commands = NULL;
+ SilcClientConnection conn;
+ SilcFSMThread thread;
- silc_client_del_connection(client, conn);
+ if (!callback)
+ return NULL;
+
+ SILC_LOG_DEBUG(("Adding new connection to %s:%d", remote_host, port));
+
+ conn = silc_calloc(1, sizeof(*conn));
+ if (!conn)
+ return NULL;
+
+ conn->client = client;
+ conn->public_key = public_key;
+ conn->private_key = private_key;
+ conn->remote_host = strdup(remote_host);
+ conn->remote_port = port ? port : 706;
+ conn->type = conn_type;
+ conn->callback = callback;
+ conn->callback_context = context;
+
+ conn->internal = silc_calloc(1, sizeof(*conn->internal));
+ if (!conn->internal) {
+ 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 (sock->protocol) {
- silc_protocol_free(sock->protocol);
- sock->protocol = NULL;
+ if (!silc_hash_alloc("sha1", &conn->internal->sha1hash)) {
+ silc_free(conn);
+ silc_free(conn->internal);
+ return NULL;
}
- silc_socket_free(sock);
-}
-/* Called when we receive disconnection packet from server. This
- closes our end properly and displays the reason of the disconnection
- on the screen. */
+ /* 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);
+
+ /* 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;
+ }
+ }
-void silc_client_disconnected_by_server(SilcClient client,
- SilcSocketConnection sock,
- SilcBuffer message)
-{
- char *msg;
+ 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;
+ }
+ }
- SILC_LOG_DEBUG(("Server disconnected us, sock %d", sock->sock));
+ /* 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);
+ 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);
- msg = silc_calloc(message->len + 1, sizeof(char));
- memcpy(msg, message->data, message->len);
- client->ops->say(client, sock->user_data, msg);
- silc_free(msg);
+ SILC_LOG_DEBUG(("New connection %p", conn));
+ silc_atomic_add_int32(&client->internal->conns, 1);
- SILC_SET_DISCONNECTED(sock);
- silc_client_close_connection(client, sock->user_data);
+ return conn;
}
-/* Received error message from server. Display it on the screen.
- We don't take any action what so ever of the error message. */
+/* Deletes connection. This is always called from the connection machine
+ destructor. Do not call this directly other places. */
-void silc_client_error_by_server(SilcClient client,
- SilcSocketConnection sock,
- SilcBuffer message)
+void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
{
- char *msg;
+ SilcList list;
+ SilcIDCacheEntry entry;
+ SilcFSMThread thread;
- msg = silc_calloc(message->len + 1, sizeof(char));
- memcpy(msg, message->data, message->len);
- client->ops->say(client, sock->user_data, msg);
- silc_free(msg);
-}
+ SILC_LOG_DEBUG(("Freeing connection %p", conn));
-/* Processes the received new Client ID from server. Old Client ID is
- deleted from cache and new one is added. */
+ silc_schedule_task_del_by_context(conn->internal->schedule, conn);
-void silc_client_receive_new_id(SilcClient client,
- SilcSocketConnection sock,
- SilcIDPayload idp)
-{
- SilcClientConnection conn = (SilcClientConnection)sock->user_data;
- int connecting = FALSE;
-
- if (!conn->local_entry)
- connecting = TRUE;
-
- /* Delete old ID from ID cache */
- silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, conn->local_id);
-
- /* Save the new ID */
- if (conn->local_id)
- silc_free(conn->local_id);
- if (conn->local_id_data)
- silc_free(conn->local_id_data);
-
- conn->local_id = silc_id_payload_get_id(idp);
- conn->local_id_data = silc_id_payload_get_data(idp);
- conn->local_id_data_len = silc_id_payload_get_len(idp);;
-
- if (!conn->local_entry)
- conn->local_entry = silc_calloc(1, sizeof(*conn->local_entry));
-
- conn->local_entry->nickname = conn->nickname;
- if (!conn->local_entry->username) {
- conn->local_entry->username =
- silc_calloc(strlen(client->username) + strlen(client->hostname) + 1,
- sizeof(conn->local_entry->username));
- sprintf(conn->local_entry->username, "%s@%s", client->username,
- client->hostname);
+ /* Free all cache entries */
+ 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);
+ }
}
- conn->local_entry->server = strdup(conn->remote_host);
- conn->local_entry->id = conn->local_id;
-
- /* Put it to the ID cache */
- silc_idcache_add(conn->client_cache, conn->nickname, SILC_ID_CLIENT,
- conn->local_id, (void *)conn->local_entry, TRUE, FALSE);
-
- /* Notify application of successful connection. We do it here now that
- we've received the Client ID and are allowed to send traffic. */
- if (connecting)
- client->ops->connect(client, conn, TRUE);
+ 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 (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 */
+ 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);
}
-/* Processed received Channel ID for a channel. This is called when client
- joins to channel and server replies with channel ID. The ID is cached.
- Returns the created channel entry. */
+/******************************* Client API *********************************/
+
+/* Connects to remote server. This is the main routine used to connect
+ to remote SILC server. Performs key exchange also. Returns the
+ connection context to the connection callback. */
-SilcChannelEntry silc_client_new_channel_id(SilcClient client,
- SilcSocketConnection sock,
- char *channel_name,
- unsigned int mode,
- SilcIDPayload idp)
+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 = (SilcClientConnection)sock->user_data;
- SilcChannelEntry channel;
+ SilcClientConnection conn;
- SILC_LOG_DEBUG(("New channel ID"));
+ SILC_LOG_DEBUG(("Connecting to server"));
+
+ if (!client || !remote_host)
+ return NULL;
- channel = silc_calloc(1, sizeof(*channel));
- channel->channel_name = channel_name;
- channel->id = silc_id_payload_get_id(idp);
- channel->mode = mode;
- silc_list_init(channel->clients, struct SilcChannelUserStruct, next);
+ if (client->internal->run_callback) {
+ SILC_LOG_ERROR(("Client library is not started yet. SilcClientRunning "
+ "callback has not been called yet."));
+ return NULL;
+ }
- conn->current_channel = channel;
+ /* Add new connection */
+ 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 NULL;
+ }
- /* Put it to the ID cache */
- silc_idcache_add(conn->channel_cache, channel_name, SILC_ID_CHANNEL,
- (void *)channel->id, (void *)channel, TRUE, FALSE);
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT,
+ "Connecting to port %d of server %s",
+ port, remote_host);
- return channel;
+ /* Signal connection machine to start connecting */
+ conn->internal->connect = TRUE;
+ return conn->internal->cop;
}
-/* Removes a client entry from all channel it has joined. This really is
- a performance killer (client_entry should have pointers to channel
- entry list). */
-
-void silc_client_remove_from_channels(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry client_entry)
+/* Connects to remote client. Performs key exchange also. Returns the
+ connection context to the connection callback. */
+
+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)
{
- SilcIDCacheEntry id_cache;
- SilcIDCacheList list;
- SilcChannelEntry channel;
- SilcChannelUser chu;
+ SilcClientConnection conn;
- if (!silc_idcache_find_by_id(conn->channel_cache, SILC_ID_CACHE_ANY,
- SILC_ID_CHANNEL, &list))
- return;
+ SILC_LOG_DEBUG(("Connecting to client"));
- silc_idcache_list_first(list, &id_cache);
- channel = (SilcChannelEntry)id_cache->context;
-
- while (channel) {
-
- /* Remove client from channel */
- silc_list_start(channel->clients);
- while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
- if (chu->client == client_entry) {
- silc_list_del(channel->clients, chu);
- silc_free(chu);
- break;
- }
- }
+ if (!client || !remote_host)
+ return NULL;
- if (!silc_idcache_list_next(list, &id_cache))
- break;
-
- channel = (SilcChannelEntry)id_cache->context;
+ if (client->internal->run_callback) {
+ SILC_LOG_ERROR(("Client library is not started yet. SilcClientRunning "
+ "callback has not been called yet."));
+ return NULL;
}
- silc_idcache_list_free(list);
-}
+ if (params)
+ params->no_authentication = TRUE;
+
+ /* Add new connection */
+ 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 NULL;
+ }
-/* Replaces `old' client entries from all channels to `new' client entry.
- This can be called for example when nickname changes and old ID entry
- is replaced from ID cache with the new one. If the old ID entry is only
- updated, then this fucntion needs not to be called. */
+ /* Signal connection machine to start connecting */
+ conn->internal->connect = TRUE;
+ return conn->internal->cop;
+}
-void silc_client_replace_from_channels(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry old,
- SilcClientEntry new)
+/* Starts key exchange in the remote stream indicated by `stream'. This
+ creates the connection context and returns it in the connection callback. */
+
+SilcAsyncOperation
+silc_client_key_exchange(SilcClient client,
+ SilcClientConnectionParams *params,
+ SilcPublicKey public_key,
+ SilcPrivateKey private_key,
+ SilcStream stream,
+ SilcConnectionType conn_type,
+ SilcClientConnectCallback callback,
+ void *context)
{
- SilcIDCacheEntry id_cache;
- SilcIDCacheList list;
- SilcChannelEntry channel;
- SilcChannelUser chu;
+ SilcClientConnection conn;
+ const char *host;
+ SilcUInt16 port;
- if (!silc_idcache_find_by_id(conn->channel_cache, SILC_ID_CACHE_ANY,
- SILC_ID_CHANNEL, &list))
- return;
+ SILC_LOG_DEBUG(("Performing key exchange"));
- silc_idcache_list_first(list, &id_cache);
- channel = (SilcChannelEntry)id_cache->context;
-
- while (channel) {
-
- /* Replace client entry */
- silc_list_start(channel->clients);
- while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
- if (chu->client == old) {
- chu->client = new;
- break;
- }
- }
+ if (!client || !stream)
+ return NULL;
- if (!silc_idcache_list_next(list, &id_cache))
- break;
-
- channel = (SilcChannelEntry)id_cache->context;
+ 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, 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->internal->user_stream = stream;
- silc_idcache_list_free(list);
+ /* Signal connection to start key exchange */
+ conn->internal->key_exchange = TRUE;
+ return conn->internal->cop;
}
-/* Parses mode mask and returns the mode as string. */
+/* Closes remote connection */
-char *silc_client_chmode(unsigned int mode)
+void silc_client_close_connection(SilcClient client,
+ SilcClientConnection conn)
{
- char string[20];
+ SILC_LOG_DEBUG(("Closing connection %p", conn));
- if (!mode)
- return NULL;
+ /* 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);
+ }
+}
- memset(string, 0, sizeof(string));
+/* 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
+ and caller must free it. */
- if (mode & SILC_CHANNEL_MODE_PRIVATE)
- strncat(string, "p", 1);
+SilcClient silc_client_alloc(SilcClientOperations *ops,
+ SilcClientParams *params,
+ void *application,
+ const char *version_string)
+{
+ SilcClient new_client;
- if (mode & SILC_CHANNEL_MODE_SECRET)
- strncat(string, "s", 1);
+ new_client = silc_calloc(1, sizeof(*new_client));
+ if (!new_client)
+ return NULL;
+ new_client->application = application;
- if (mode & SILC_CHANNEL_MODE_PRIVKEY)
- strncat(string, "k", 1);
+ new_client->internal = silc_calloc(1, sizeof(*new_client->internal));
+ if (!new_client->internal) {
+ silc_free(new_client);
+ return NULL;
+ }
+ new_client->internal->ops = ops;
+ new_client->internal->params =
+ silc_calloc(1, sizeof(*new_client->internal->params));
+ if (!version_string)
+ version_string = silc_version_string;
+ new_client->internal->silc_client_version = strdup(version_string);
- if (mode & SILC_CHANNEL_MODE_INVITE)
- strncat(string, "i", 1);
+ if (params)
+ memcpy(new_client->internal->params, params, sizeof(*params));
- if (mode & SILC_CHANNEL_MODE_TOPIC)
- strncat(string, "t", 1);
+ new_client->internal->params->
+ nickname_format[sizeof(new_client->internal->
+ params->nickname_format) - 1] = 0;
- if (mode & SILC_CHANNEL_MODE_ULIMIT)
- strncat(string, "l", 1);
+ silc_atomic_init32(&new_client->internal->conns, 0);
- if (mode & SILC_CHANNEL_MODE_PASSPHRASE)
- strncat(string, "a", 1);
+ return new_client;
+}
- /* Rest of mode is ignored */
+/* Frees client object and its internals. */
- return strdup(string);
+void silc_client_free(SilcClient 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);
}
-/* Parses channel user mode mask and returns te mode as string */
+/* Initializes the client. This makes all the necessary steps to make
+ the client ready to be run. One must call silc_client_run to run the
+ client. Returns FALSE if error occured, TRUE otherwise. */
-char *silc_client_chumode(unsigned int mode)
+SilcBool silc_client_init(SilcClient client, const char *username,
+ const char *hostname, const char *realname,
+ SilcClientRunning running, void *context)
{
- char string[4];
+ SILC_LOG_DEBUG(("Initializing client"));
- if (!mode)
- return NULL;
+ if (!client)
+ return FALSE;
+
+ 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),
+ SILC_STRING_UTF8, 128)) {
+ SILC_LOG_ERROR(("Malformed username '%s'. Username must be UTF-8 string",
+ client->username));
+ return FALSE;
+ }
+ if (!silc_identifier_verify(hostname, strlen(hostname),
+ SILC_STRING_UTF8, 256)) {
+ SILC_LOG_ERROR(("Malformed hostname '%s'. Hostname must be UTF-8 string",
+ client->hostname));
+ return FALSE;
+ }
+ if (!silc_utf8_valid(realname, strlen(realname))) {
+ SILC_LOG_ERROR(("Malformed realname '%s'. Realname must be UTF-8 string",
+ client->realname));
+ return FALSE;
+ }
- memset(string, 0, sizeof(string));
+ /* Take the name strings */
+ client->username = strdup(username);
+ client->hostname = strdup(hostname);
+ client->realname = strdup(realname);
+ if (!username || !hostname || !realname)
+ return FALSE;
- if (mode & SILC_CHANNEL_UMODE_CHANFO)
- strncat(string, "f", 1);
+ client->internal->ftp_sessions = silc_dlist_init();
+ if (!client->internal->ftp_sessions)
+ return FALSE;
- if (mode & SILC_CHANNEL_UMODE_CHANOP)
- strncat(string, "o", 1);
+ if (!client->internal->params->dont_register_crypto_library)
+ /* Initialize the crypto library. If application has done this already
+ this has no effect. */
+ silc_crypto_init(NULL);
- return strdup(string);
-}
+ /* 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);
-/* Parses channel user mode and returns it as special mode character. */
+ /* Initialize the scheduler */
+ client->schedule = silc_schedule_init(0, client, NULL, NULL);
+ if (!client->schedule)
+ return FALSE;
-char *silc_client_chumode_char(unsigned int mode)
-{
- char string[4];
+ /* Allocate client lock */
+ silc_mutex_alloc(&client->internal->lock);
- if (!mode)
- return NULL;
+ /* Register commands */
+ silc_client_commands_register(client);
- memset(string, 0, sizeof(string));
+ /* Start packet engine */
+ client->internal->packet_engine =
+ silc_packet_engine_start(client->rng, FALSE, &silc_client_stream_cbs,
+ client);
+ if (!client->internal->packet_engine)
+ return FALSE;
- if (mode & SILC_CHANNEL_UMODE_CHANFO)
- strncat(string, "*", 1);
+ /* 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);
- if (mode & SILC_CHANNEL_UMODE_CHANOP)
- strncat(string, "@", 1);
+ /* Signal the application when we are running */
+ client->internal->run_callback = TRUE;
+ SILC_FSM_EVENT_SIGNAL(&client->internal->wait_event);
- return strdup(string);
+ return TRUE;
}
-/* Failure timeout callback. If this is called then we will immediately
- process the received failure. We always process the failure with timeout
- since we do not want to blindly trust to received failure packets.
- This won't be called (the timeout is cancelled) if the failure was
- bogus (it is bogus if remote does not close the connection after sending
- the failure). */
+/* Starts the SILC client FSM machine and blocks here. When this returns
+ the client has ended. */
-SILC_TASK_CALLBACK_GLOBAL(silc_client_failure_callback)
+void silc_client_run(SilcClient client)
{
- SilcClientFailureContext *f = (SilcClientFailureContext *)context;
-
- if (f->sock->protocol) {
- f->sock->protocol->state = SILC_PROTOCOL_STATE_FAILURE;
- f->sock->protocol->execute(f->client->timeout_queue, 0,
- f->sock->protocol, f->sock->sock, 0, 0);
-
- /* Notify application */
- f->client->ops->failure(f->client, f->sock->user_data, f->sock->protocol,
- (void *)f->failure);
- }
+ SILC_LOG_DEBUG(("Starting SILC client"));
+
+ /* Run the scheduler */
+ silc_schedule(client->schedule);
+}
+
+/* Call scheduler one iteration and return. */
- silc_free(f);
+void silc_client_run_one(SilcClient client)
+{
+ if (silc_fsm_is_started(&client->internal->fsm))
+ silc_schedule_one(client->schedule, 0);
}
-/* Registers failure timeout to process the received failure packet
- with timeout. */
+/* Stops the client. This is called to stop the client and thus to stop
+ the program. */
-void silc_client_process_failure(SilcClient client,
- SilcSocketConnection sock,
- SilcPacketContext *packet)
+void silc_client_stop(SilcClient client, SilcClientStopped stopped,
+ void *context)
{
- SilcClientFailureContext *f;
- unsigned int failure = 0;
-
- if (sock->protocol) {
- if (packet->buffer->len >= 4)
- SILC_GET32_MSB(failure, packet->buffer->data);
-
- f = silc_calloc(1, sizeof(*f));
- f->client = client;
- f->sock = sock;
- f->failure = failure;
-
- /* We will wait 5 seconds to process this failure packet */
- silc_task_register(client->timeout_queue, sock->sock,
- silc_client_failure_callback, (void *)f, 5, 0,
- SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+ 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);
}