/* client_st_connect.c Author: Pekka Riikonen Copyright (C) 2006 - 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; 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. */ #include "silc.h" #include "silcclient.h" #include "client_internal.h" /************************ Static utility functions **************************/ /* Callback called after connected to remote host */ static void silc_client_connect_callback(SilcResult status, SilcStream stream, void *context) { SilcFSMThread fsm = context; SilcClientConnection conn = silc_fsm_get_context(fsm); SilcClient client = conn->client; conn->internal->op = NULL; if (conn->internal->verbose) { switch (status) { case SILC_OK: break; case SILC_ERR_UNKNOWN_IP: client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Could not connect to host %s: unknown IP address", conn->remote_host); break; case SILC_ERR_UNKNOWN_HOST: client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Could not connect to host %s: unknown host name", conn->remote_host); break; case SILC_ERR_UNREACHABLE: client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Could not connect to host %s: network unreachable", conn->remote_host); break; case SILC_ERR_REFUSED: client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Could not connect to host %s: connection refused", conn->remote_host); break; case SILC_ERR_TIMEOUT: client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Could not connect to host %s: connection timeout", conn->remote_host); break; default: client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Could not connect to host %s", conn->remote_host); break; } } if (status != SILC_OK) { /* Notify application of failure */ SILC_LOG_DEBUG(("Connecting failed")); conn->internal->status = SILC_CLIENT_CONN_ERROR; silc_fsm_next(fsm, silc_client_st_connect_error); SILC_FSM_CALL_CONTINUE(fsm); return; } /* Connection created successfully */ SILC_LOG_DEBUG(("Connected")); conn->internal->user_stream = stream; SILC_FSM_CALL_CONTINUE(fsm); } /* Called after application has verified remote host's public key */ static void silc_client_ke_verify_key_cb(SilcBool success, void *context) { SilcVerifyKeyContext verify = context; SILC_LOG_DEBUG(("Start")); /* Call the completion callback back to the SKE */ verify->completion(verify->ske, success ? SILC_SKE_STATUS_OK : SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY, verify->completion_context); silc_free(verify); } /* Verify remote host's public key */ static void silc_client_ke_verify_key(SilcSKE ske, SilcPublicKey public_key, void *context, SilcSKEVerifyCbCompletion completion, void *completion_context) { SilcFSMThread fsm = context; SilcClientConnection conn = silc_fsm_get_context(fsm); SilcClient client = conn->client; SilcVerifyKeyContext verify; /* If we provided repository for SKE and we got here the key was not found from the repository. */ if (conn->internal->params.repository && !conn->internal->params.verify_notfound) { completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY, completion_context); return; } SILC_LOG_DEBUG(("Verify remote public key")); verify = silc_calloc(1, sizeof(*verify)); if (!verify) { completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY, completion_context); return; } verify->ske = ske; verify->completion = completion; verify->completion_context = completion_context; /* Verify public key in application */ client->internal->ops->verify_public_key(client, conn, conn->type, public_key, silc_client_ke_verify_key_cb, verify); } /* Key exchange protocol completion callback */ static void silc_client_ke_completion(SilcSKE ske, SilcSKEStatus status, SilcSKESecurityProperties prop, SilcSKEKeyMaterial keymat, SilcSKERekeyMaterial rekey, void *context) { SilcFSMThread fsm = context; SilcClientConnection conn = silc_fsm_get_context(fsm); SilcClient client = conn->client; SilcCipher send_key, receive_key; SilcHmac hmac_send, hmac_receive; conn->internal->op = NULL; if (status != SILC_SKE_STATUS_OK) { /* Key exchange failed */ SILC_LOG_DEBUG(("Error during key exchange with %s: %s (%d)", conn->remote_host, silc_ske_map_status(status), status)); if (conn->internal->verbose) client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, "Error during key exchange with %s: %s", conn->remote_host, silc_ske_map_status(status)); conn->internal->status = SILC_CLIENT_CONN_ERROR_KE; silc_ske_free_rekey_material(rekey); silc_fsm_next(fsm, silc_client_st_connect_error); SILC_FSM_CALL_CONTINUE_SYNC(fsm); return; } SILC_LOG_DEBUG(("Setting keys into use")); /* Allocate the cipher and HMAC contexts */ if (!silc_ske_set_keys(ske, keymat, prop, &send_key, &receive_key, &hmac_send, &hmac_receive, &conn->internal->hash)) { /* Error setting keys */ SILC_LOG_DEBUG(("Could not set keys into use")); if (conn->internal->verbose) client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Error during key exchange with %s: cannot use keys", conn->remote_host); conn->internal->status = SILC_CLIENT_CONN_ERROR_KE; silc_ske_free_rekey_material(rekey); silc_fsm_next(fsm, silc_client_st_connect_error); SILC_FSM_CALL_CONTINUE_SYNC(fsm); return; } /* Set the keys into the packet stream. After this call packets will be encrypted with these keys. */ if (!silc_packet_set_keys(conn->stream, send_key, receive_key, hmac_send, hmac_receive, FALSE)) { /* Error setting keys */ SILC_LOG_DEBUG(("Could not set keys into use")); if (conn->internal->verbose) client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Error during key exchange with %s: cannot use keys", conn->remote_host); conn->internal->status = SILC_CLIENT_CONN_ERROR_KE; silc_ske_free_rekey_material(rekey); silc_fsm_next(fsm, silc_client_st_connect_error); SILC_FSM_CALL_CONTINUE_SYNC(fsm); return; } conn->internal->rekey = rekey; SILC_LOG_DEBUG(("Key Exchange completed")); /* Key exchange done */ SILC_FSM_CALL_CONTINUE_SYNC(fsm); } /* Rekey protocol completion callback */ static void silc_client_rekey_completion(SilcSKE ske, SilcSKEStatus status, SilcSKESecurityProperties prop, SilcSKEKeyMaterial keymat, SilcSKERekeyMaterial rekey, void *context) { SilcFSMThread fsm = context; SilcClientConnection conn = silc_fsm_get_context(fsm); SilcClient client = conn->client; conn->internal->op = NULL; if (status != SILC_SKE_STATUS_OK) { /* Rekey failed */ SILC_LOG_DEBUG(("Error during rekey with %s: %s (%d)", conn->remote_host, silc_ske_map_status(status), status)); if (conn->internal->verbose) client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, "Error during rekey with %s: %s", conn->remote_host, silc_ske_map_status(status)); silc_ske_free(conn->internal->ske); conn->internal->ske = NULL; silc_fsm_finish(fsm); return; } silc_ske_free_rekey_material(conn->internal->rekey); conn->internal->rekey = rekey; silc_ske_free(conn->internal->ske); conn->internal->ske = NULL; SILC_LOG_DEBUG(("Rekey completed conn %p", conn)); /* Rekey done */ silc_fsm_finish(fsm); } /* Callback called by application to return authentication data */ static void silc_client_connect_auth_method(SilcAuthMethod auth_meth, const void *auth, SilcUInt32 auth_len, void *context) { SilcFSMThread fsm = context; SilcClientConnection conn = silc_fsm_get_context(fsm); conn->internal->params.auth_method = auth_meth; if (auth_meth == SILC_AUTH_PASSWORD) conn->internal->params.auth = silc_memdup(auth, auth_len); else conn->internal->params.auth = (void *)auth; conn->internal->params.auth_len = auth_len; SILC_FSM_CALL_CONTINUE(fsm); } /* Connection authentication completion callback */ static void silc_client_connect_auth_completion(SilcConnAuth connauth, SilcBool success, void *context) { SilcFSMThread fsm = context; SilcClientConnection conn = silc_fsm_get_context(fsm); SilcClient client = conn->client; conn->internal->op = NULL; silc_connauth_free(connauth); if (!success) { if (conn->internal->verbose) client->internal->ops->say( client, conn, SILC_CLIENT_MESSAGE_ERROR, "Authentication failed"); conn->internal->status = SILC_CLIENT_CONN_ERROR_AUTH; conn->internal->error = SILC_STATUS_ERR_AUTH_FAILED; silc_fsm_next(fsm, silc_client_st_connect_error); } SILC_FSM_CALL_CONTINUE_SYNC(fsm); } /********************** CONNECTION_AUTH_REQUEST packet **********************/ /* Received connection authentication request packet. We get the required authentication method here. */ SILC_FSM_STATE(silc_client_connect_auth_request) { SilcClientConnection conn = fsm_context; SilcPacket packet = state_context; SilcUInt16 conn_type, auth_meth; if (!conn->internal->auth_request) { silc_packet_free(packet); return SILC_FSM_FINISH; } /* Parse the payload */ if (silc_buffer_unformat(&packet->buffer, SILC_STR_UI_SHORT(&conn_type), SILC_STR_UI_SHORT(&auth_meth), SILC_STR_END) < 0) auth_meth = SILC_AUTH_NONE; silc_packet_free(packet); SILC_LOG_DEBUG(("Resolved authentication method: %s", (auth_meth == SILC_AUTH_NONE ? "none" : auth_meth == SILC_AUTH_PASSWORD ? "passphrase" : "public key"))); conn->internal->params.auth_method = auth_meth; /* Continue authentication */ silc_fsm_continue_sync(&conn->internal->event_thread); return SILC_FSM_FINISH; } /*************************** Connect remote host ****************************/ /* Connection timeout callback */ SILC_TASK_CALLBACK(silc_client_connect_timeout) { SilcClientConnection conn = context; SILC_LOG_DEBUG(("Connection timeout")); conn->internal->status = SILC_CLIENT_CONN_ERROR_TIMEOUT; conn->internal->error = SILC_STATUS_ERR_TIMEDOUT; silc_fsm_next(&conn->internal->event_thread, silc_client_st_connect_error); silc_fsm_continue_sync(&conn->internal->event_thread); } /* Creates a connection to remote host */ SILC_FSM_STATE(silc_client_st_connect) { SilcClientConnection conn = fsm_context; SILC_LOG_DEBUG(("Connecting to %s:%d", conn->remote_host, conn->remote_port)); /** Connect */ silc_fsm_next(fsm, silc_client_st_connect_set_stream); /* Add connection timeout */ if (conn->internal->params.timeout_secs) silc_schedule_task_add_timeout(conn->internal->schedule, silc_client_connect_timeout, conn, conn->internal->params.timeout_secs, 0); if (conn->internal->params.udp) { SilcStream stream; if (!conn->internal->params.local_ip) { /** IP address not given */ SILC_LOG_ERROR(("Local UDP IP address not specified")); conn->internal->status = SILC_CLIENT_CONN_ERROR; silc_fsm_next(fsm, silc_client_st_connect_error); return SILC_FSM_CONTINUE; } /* Connect (UDP) */ stream = silc_net_udp_connect(conn->internal->params.bind_ip ? conn->internal->params.bind_ip : conn->internal->params.local_ip, conn->internal->params.local_port, conn->remote_host, conn->remote_port, conn->internal->schedule); SILC_FSM_CALL(silc_client_connect_callback(stream ? SILC_OK : SILC_ERR_UNREACHABLE, stream, fsm)); } else { /* Connect (TCP) */ SILC_FSM_CALL(conn->internal->op = silc_net_tcp_connect( NULL, conn->remote_host, conn->remote_port, conn->internal->schedule, silc_client_connect_callback, fsm)); } } /* Sets the new connection stream into use and creates packet stream */ 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, conn->internal->user_stream); if (!conn->stream) { /** Cannot create packet stream */ SILC_LOG_DEBUG(("Could not create packet stream")); conn->internal->status = SILC_CLIENT_CONN_ERROR; silc_fsm_next(fsm, silc_client_st_connect_error); return SILC_FSM_CONTINUE; } silc_packet_set_context(conn->stream, conn); /* Save socket stream and socket into connection context */ conn->socket_stream = silc_packet_stream_get_stream(conn->stream); silc_socket_stream_get_info(conn->socket_stream, &conn->sock, NULL, NULL, NULL); /** Start key exchange */ silc_fsm_next(fsm, silc_client_st_connect_key_exchange); return SILC_FSM_CONTINUE; } /* Starts key exchange protocol with remote host */ SILC_FSM_STATE(silc_client_st_connect_key_exchange) { SilcClientConnection conn = fsm_context; SilcClient client = conn->client; SilcSKEParamsStruct params; SilcClientID cid; SILC_LOG_DEBUG(("Starting key exchange protocol")); /* Allocate SKE */ conn->internal->ske = silc_ske_alloc(client->rng, conn->internal->schedule, conn->internal->params.repository, conn->public_key, conn->private_key, fsm); if (!conn->internal->ske) { /** Out of memory */ conn->internal->status = SILC_CLIENT_CONN_ERROR_KE; silc_fsm_next(fsm, silc_client_st_connect_error); return SILC_FSM_CONTINUE; } /* Set SKE callbacks */ silc_ske_set_callbacks(conn->internal->ske, silc_client_ke_verify_key, silc_client_ke_completion, fsm); /* Set up key exchange parameters */ params.version = client->internal->silc_client_version; params.timeout_secs = conn->internal->params.timeout_secs; params.flags = SILC_SKE_SP_FLAG_MUTUAL; if (conn->internal->params.pfs) params.flags |= SILC_SKE_SP_FLAG_PFS; if (conn->internal->params.udp) { params.flags |= SILC_SKE_SP_FLAG_IV_INCLUDED; params.session_port = conn->internal->params.local_port; } if (conn->internal->params.no_authentication) /** Run key exchange (no auth) */ silc_fsm_next(fsm, silc_client_st_connected); else if (conn->internal->params.udp) /** Run key exchange (UDP)*/ silc_fsm_next(fsm, silc_client_st_connect_setup_udp); else /** Run key exchange (TCP) */ silc_fsm_next(fsm, silc_client_st_connect_auth_resolve); /* Old server version requires empty Client ID in packets. Remove this backwards support somepoint after 1.1 server is released. */ memset(&cid, 0, sizeof(cid)); cid.ip.data_len = 4; silc_packet_set_ids(conn->stream, SILC_ID_CLIENT, &cid, 0, NULL); SILC_FSM_CALL(conn->internal->op = silc_ske_initiator(conn->internal->ske, conn->stream, ¶ms, NULL)); } /* For UDP/IP connections, set up the UDP session after successful key exchange protocol */ SILC_FSM_STATE(silc_client_st_connect_setup_udp) { SilcClientConnection conn = fsm_context; SilcStream stream, old; SilcSKESecurityProperties prop; 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, conn->internal->params.local_port, conn->remote_host, prop->remote_port, conn->internal->schedule); if (!stream) { /** Cannot create UDP stream */ conn->internal->status = SILC_CLIENT_CONN_ERROR; silc_fsm_next(fsm, silc_client_st_connect_error); return SILC_FSM_CONTINUE; } /* Set the new stream to packet stream */ old = silc_packet_stream_get_stream(conn->stream); silc_packet_stream_set_stream(conn->stream, stream); conn->socket_stream = stream; silc_socket_stream_get_info(conn->socket_stream, &conn->sock, NULL, NULL, NULL); silc_packet_stream_set_iv_included(conn->stream); silc_packet_set_sid(conn->stream, 0); /* Delete the old stream */ silc_stream_destroy(old); /** Start authentication */ silc_fsm_next(fsm, silc_client_st_connect_auth_resolve); return SILC_FSM_CONTINUE; } /* Resolve authentication method to be used in authentication protocol */ SILC_FSM_STATE(silc_client_st_connect_auth_resolve) { SilcClientConnection conn = fsm_context; SILC_LOG_DEBUG(("Resolve authentication method")); if (conn->internal->disconnected) { /** Disconnected */ silc_fsm_next(fsm, silc_client_st_connect_error); return SILC_FSM_CONTINUE; } /* If authentication method and data is set, use them */ if (conn->internal->params.auth_set) { /** Got authentication data */ silc_fsm_next(fsm, silc_client_st_connect_auth_start); return SILC_FSM_CONTINUE; } /* Send connection authentication request packet */ silc_packet_send_va(conn->stream, SILC_PACKET_CONNECTION_AUTH_REQUEST, 0, SILC_STR_UI_SHORT(SILC_CONN_CLIENT), SILC_STR_UI_SHORT(SILC_AUTH_NONE), SILC_STR_END); /** Wait for authentication method */ conn->internal->auth_request = TRUE; conn->internal->params.auth_method = SILC_AUTH_NONE; silc_fsm_next_later(fsm, silc_client_st_connect_auth_data, 2, 0); return SILC_FSM_WAIT; } /* Get authentication data to be used in authentication protocol */ SILC_FSM_STATE(silc_client_st_connect_auth_data) { SilcClientConnection conn = fsm_context; SilcClient client = conn->client; SILC_LOG_DEBUG(("Get authentication data")); if (conn->internal->disconnected) { /** Disconnected */ silc_fsm_next(fsm, silc_client_st_connect_error); return SILC_FSM_CONTINUE; } conn->internal->auth_request = FALSE; /** Get authentication data */ silc_fsm_next(fsm, silc_client_st_connect_auth_start); SILC_FSM_CALL(client->internal->ops->get_auth_method( client, conn, conn->remote_host, conn->remote_port, conn->internal->params.auth_method, silc_client_connect_auth_method, fsm)); } /* Start connection authentication with remote host */ SILC_FSM_STATE(silc_client_st_connect_auth_start) { SilcClientConnection conn = fsm_context; SilcConnAuth connauth; 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; } /* We always use the same key for connection authentication and SKE */ if (conn->internal->params.auth_method == SILC_AUTH_PUBLIC_KEY) conn->internal->params.auth = conn->private_key; /* Allocate connection authentication protocol */ connauth = silc_connauth_alloc(conn->internal->schedule, conn->internal->ske, conn->internal->params.rekey_secs); if (!connauth) { /** Out of memory */ conn->internal->status = SILC_CLIENT_CONN_ERROR_AUTH; conn->internal->error = SILC_STATUS_ERR_AUTH_FAILED; silc_fsm_next(fsm, silc_client_st_connect_error); return SILC_FSM_CONTINUE; } /** Start connection authentication */ silc_fsm_next(fsm, silc_client_st_connected); SILC_FSM_CALL(conn->internal->op = silc_connauth_initiator( connauth, SILC_CONN_CLIENT, conn->internal->params.auth_method, conn->internal->params.auth, conn->internal->params.auth_len, silc_client_connect_auth_completion, fsm)); } /* Connection fully established */ SILC_FSM_STATE(silc_client_st_connected) { SilcClientConnection conn = fsm_context; SilcClient client = conn->client; /* Get SILC protocol version remote supports */ silc_ske_parse_version(conn->internal->ske, &conn->internal->remote_version, NULL, NULL, NULL, NULL); silc_ske_free(conn->internal->ske); conn->internal->ske = NULL; if (!conn->internal->params.auth_set && conn->internal->params.auth_method == SILC_AUTH_PASSWORD && conn->internal->params.auth) { silc_free(conn->internal->params.auth); conn->internal->params.auth = 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 */ if (conn->type != SILC_CONN_CLIENT) silc_schedule_task_add_timeout(conn->internal->schedule, silc_client_rekey_timer, conn, conn->internal->params.rekey_secs, 0); /* If we connected to server, now register to network. */ if (conn->type == SILC_CONN_SERVER && !conn->internal->params.no_authentication) { /* If detach data is provided, resume the session. */ if (conn->internal->params.detach_data && conn->internal->params.detach_data_len) { /** Resume detached session */ silc_fsm_next(fsm, silc_client_st_resume); } else { /** Register to network */ silc_fsm_next(fsm, silc_client_st_register); } return SILC_FSM_CONTINUE; } silc_schedule_task_del_by_all(conn->internal->schedule, 0, silc_client_connect_timeout, conn); /* Call connection callback */ conn->callback(client, conn, SILC_CLIENT_CONN_SUCCESS, 0, NULL, conn->callback_context); silc_async_free(conn->internal->cop); conn->internal->cop = NULL; return SILC_FSM_FINISH; } /* Error during connecting */ SILC_FSM_STATE(silc_client_st_connect_error) { SilcClientConnection conn = fsm_context; if (conn->internal->ske) { silc_ske_free(conn->internal->ske); conn->internal->ske = NULL; } /* Signal to close connection */ if (!conn->internal->disconnected) { conn->internal->disconnected = TRUE; SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event); } silc_schedule_task_del_by_all(conn->internal->schedule, 0, silc_client_connect_timeout, conn); return SILC_FSM_FINISH; } /****************************** Connect rekey *******************************/ /* Connection rekey timer callback */ SILC_TASK_CALLBACK(silc_client_rekey_timer) { SilcClientConnection conn = context; /* Signal to start rekey */ if (!silc_fsm_is_started(&conn->internal->event_thread)) { conn->internal->rekey_responder = FALSE; conn->internal->rekeying = TRUE; SILC_FSM_EVENT_SIGNAL(&conn->internal->wait_event); } /* Reinstall rekey timer */ silc_schedule_task_add_timeout(conn->internal->schedule, silc_client_rekey_timer, conn, conn->internal->params.rekey_secs, 0); } /* Performs rekey */ SILC_FSM_STATE(silc_client_st_rekey) { SilcClientConnection conn = fsm_context; SilcClient client = conn->client; SILC_LOG_DEBUG(("Rekey conn %p", conn)); if (conn->internal->disconnected) return SILC_FSM_FINISH; /* Allocate SKE */ conn->internal->ske = silc_ske_alloc(client->rng, conn->internal->schedule, NULL, conn->public_key, NULL, fsm); if (!conn->internal->ske) return SILC_FSM_FINISH; /* Set SKE callbacks */ silc_ske_set_callbacks(conn->internal->ske, NULL, silc_client_rekey_completion, fsm); /** Perform rekey */ if (!conn->internal->rekey_responder) SILC_FSM_CALL(conn->internal->op = silc_ske_rekey_initiator( conn->internal->ske, conn->stream, conn->internal->rekey)); else SILC_FSM_CALL(conn->internal->op = silc_ske_rekey_responder( conn->internal->ske, conn->stream, conn->internal->rekey, NULL)); }