Added preliminary Symbian support.
[silc.git] / lib / silcclient / client_keyagr.c
index b21a7dc737af22d7f228d827d67adfd70f5226a1..a0c791d81cbe90fb316f904e8095e4941a095137 100644 (file)
 
   client_keyagr.c
 
-  Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
+  Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2001 Pekka Riikonen
+  Copyright (C) 2001 - 2006 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$ */
-/* This file includes the Key Agreement packet processing and actual
-   key agreement routines. This file has nothing to do with the actual
-   connection key exchange protocol, it is implemented in the client.c
-   and in protocol.c. This file implements the client-to-client key 
-   agreement as defined by the SILC protocol. */
-
-#include "clientlibincludes.h"
+
+#include "silc.h"
+#include "silcclient.h"
 #include "client_internal.h"
 
-SILC_TASK_CALLBACK(silc_client_key_agreement_final);
-SILC_TASK_CALLBACK(silc_client_process_key_agreement);
-SILC_TASK_CALLBACK(silc_client_key_agreement_timeout);
-SILC_TASK_CALLBACK(silc_client_perform_key_agreement_start);
+/************************** Types and definitions ***************************/
 
-/* Key agreement context */
+/* Key agreement context, used by responder */
 struct SilcClientKeyAgreementStruct {
-  SilcClient client;
-  SilcClientConnection conn;
-  int fd;                                /* Listening/connection socket */
-  SilcSocketConnection sock;             /* Remote socket connection */
-  SilcClientEntry client_entry;                  /* Destination client */
+  SilcClient client;                     /* Client */
+  SilcClientConnection conn;             /* Server connection */
   SilcKeyAgreementCallback completion;   /* Key agreement completion */
   void *context;                         /* User context */
-  SilcTask timeout;                      /* Timeout task */
-  SilcClientKEInternalContext *proto_ctx; /* Key Exchange protocol context */
+  SilcClientConnectionParams params;      /* Connection parameters */
+  SilcPublicKey public_key;              /* Responder public key */
+  SilcPrivateKey private_key;            /* Responder private key */
+  SilcNetListener tcp_listener;                  /* TCP listener */
+  SilcPacketStream udp_listener;         /* UDP listener */
+  SilcPacketStream stream;               /* Remote connection (TCP or UDP) */
+  SilcAsyncOperation op;                 /* SKE operation */
+  SilcSKE ske;                           /* SKE */
 };
 
-/* Packet sending function used by the SKE in the key agreement process. */
+/************************ Static utility functions **************************/
+
+/* Destroyes key agreement session */
 
-static void silc_client_key_agreement_send_packet(SilcSKE ske,
-                                                 SilcBuffer packet,
-                                                 SilcPacketType type,
-                                                 void *context)
+static void silc_client_keyagr_free(SilcClient client,
+                                   SilcClientConnection conn,
+                                   SilcClientEntry client_entry)
 {
-  SilcProtocol protocol = (SilcProtocol)context;
-  SilcClientKEInternalContext *ctx = 
-    (SilcClientKEInternalContext *)protocol->context;
-  void *tmp;
-
-  /* Send the packet immediately. We will assure that the packet is not
-     encrypted by setting the socket's user_data pointer to NULL. The
-     silc_client_packet_send would take the keys (wrong keys that is,
-     because user_data is the current SilcClientConnection) from it and
-     we cannot allow that. The packets are never encrypted when doing SKE
-     with another client. */
-  tmp = ske->sock->user_data;
-  ske->sock->user_data = NULL;
-  silc_client_packet_send(ctx->client, ske->sock, type, NULL, 0, NULL, NULL,
-                         packet->data, packet->len, TRUE);
-  ske->sock->user_data = tmp;
+  SilcClientKeyAgreement ke = client_entry->internal.ke;
+
+  silc_schedule_task_del_by_context(conn->internal->schedule, client_entry);
+
+  if (ke->op)
+    silc_async_abort(ke->op, NULL, NULL);
+  if (ke->ske)
+    silc_ske_free(ke->ske);
+  if (ke->tcp_listener)
+    silc_net_close_listener(ke->tcp_listener);
+  silc_packet_stream_destroy(ke->stream);
+  silc_packet_stream_destroy(ke->udp_listener);
+
+  client_entry->internal.ke = NULL;
+  client_entry->internal.prv_resp = FALSE;
+  silc_client_unref_client(client, conn, client_entry);
+
+  silc_free(ke);
 }
 
-/* Timeout callback that is called to close the connection and free the
-   socket connection data. */
+/* Key agreement timeout callback */
 
-SILC_TASK_CALLBACK(silc_client_key_agreement_close)
+SILC_TASK_CALLBACK(silc_client_keyagr_timeout)
 {
-  SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context;
+  SilcClientEntry client_entry = context;
+  SilcClientKeyAgreement ke = client_entry->internal.ke;
 
-  silc_schedule_unset_listen_fd(ke->client->schedule, ke->sock->sock);
-  silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd);
-  silc_net_close_connection(ke->sock->sock);
-  silc_net_close_connection(ke->fd);
-  silc_socket_free(ke->sock);
-  silc_free(ke);
+  SILC_LOG_DEBUG(("Key agreement %p timeout", ke));
+
+  ke->completion(ke->client, ke->conn, client_entry,
+                SILC_KEY_AGREEMENT_TIMEOUT, NULL, ke->context);
+
+  silc_client_keyagr_free(ke->client, ke->conn, client_entry);
+}
+
+/* Client resolving callback.  Continues with the key agreement processing */
+
+static void silc_client_keyagr_resolved(SilcClient client,
+                                       SilcClientConnection conn,
+                                       SilcStatus status,
+                                       SilcDList clients,
+                                       void *context)
+{
+  /* If no client found, ignore the packet, a silent error */
+  if (!clients)
+    silc_fsm_next(context, silc_client_key_agreement_error);
+
+  /* Continue processing the packet */
+  SILC_FSM_CALL_CONTINUE(context);
 }
 
-/* This callback is called after the key agreement protocol has been
-   performed. This calls the final completion callback for the application. */
+/* Called after application has verified remote host's public key.  Responder
+   function. */
 
-SILC_TASK_CALLBACK(silc_client_key_agreement_final)
+static void silc_client_keyagr_verify_key_cb(SilcBool success, void *context)
 {
-  SilcProtocol protocol = (SilcProtocol)context;
-  SilcClientKEInternalContext *ctx = 
-    (SilcClientKEInternalContext *)protocol->context;
-  SilcClient client = (SilcClient)ctx->client;
-  SilcClientKeyAgreement ke = (SilcClientKeyAgreement)ctx->context;
-
-  SILC_LOG_DEBUG(("Start"));
-
-  if (protocol->state == SILC_PROTOCOL_STATE_ERROR ||
-      protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
-    /* Error occured during protocol */
-    ke->client_entry->ke = NULL;
-    ke->completion(ke->client, ke->conn, ke->client_entry, 
-                  SILC_KEY_AGREEMENT_ERROR, NULL, ke->context);
-    silc_ske_free_key_material(ctx->keymat);
-    goto out;
+  VerifyKeyContext verify = context;
+
+  /* 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.  Responder function. */
+
+static void silc_client_keyagr_verify_key(SilcSKE ske,
+                                         SilcPublicKey public_key,
+                                         void *context,
+                                         SilcSKEVerifyCbCompletion completion,
+                                         void *completion_context)
+{
+  SilcClientEntry client_entry = context;
+  SilcClientKeyAgreement ke = client_entry->internal.ke;
+  SilcClientConnection conn = ke->conn;
+  SilcClient client = conn->client;
+  VerifyKeyContext verify;
+
+  /* If we provided repository for SKE and we got here the key was not
+     found from the repository. */
+  if (ke->params.repository && !ke->params.verify_notfound) {
+    completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY,
+              completion_context);
+    return;
   }
 
-  /* Pass the negotiated key material to the application. The application
-     is responsible of freeing the key material. */
-  ke->client_entry->ke = NULL;
-  ke->completion(ke->client, ke->conn, ke->client_entry, 
-                SILC_KEY_AGREEMENT_OK, ctx->keymat, ke->context);
-
- out:
-  silc_protocol_free(protocol);
-  if (ctx->ske)
-    silc_ske_free(ctx->ske);
-  if (ctx->dest_id)
-    silc_free(ctx->dest_id);
-  silc_schedule_task_del_by_fd(client->schedule, ke->fd);
-  silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd);
-  silc_net_close_connection(ke->fd);
-  if (ke->timeout)
-    silc_schedule_task_del(client->schedule, ke->timeout);
-  silc_client_del_socket(ke->client, ke->sock);
-
-  silc_schedule_task_add(client->schedule, 0, 
-                    silc_client_key_agreement_close,
-                    (void *)ke, 0, 1, 
-                    SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
-
-  silc_free(ctx);
+  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,
+                                          SILC_CONN_CLIENT, public_key,
+                                          silc_client_keyagr_verify_key_cb,
+                                          verify);
 }
 
-/* Key agreement callback that is called when remote end has initiated
-   the key agreement protocol. This accepts the incoming TCP/IP connection
-   for the key agreement protocol. */
+/* Key exchange protocol completion callback.  Responder function. */
 
-SILC_TASK_CALLBACK(silc_client_process_key_agreement)
+static void silc_client_keyagr_completion(SilcSKE ske,
+                                         SilcSKEStatus status,
+                                         SilcSKESecurityProperties prop,
+                                         SilcSKEKeyMaterial keymat,
+                                         SilcSKERekeyMaterial rekey,
+                                         void *context)
 {
-  SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context;
-  SilcClient client = ke->client;
+  SilcClientEntry client_entry = context;
+  SilcClientKeyAgreement ke = client_entry->internal.ke;
   SilcClientConnection conn = ke->conn;
-  SilcSocketConnection newsocket;
-  SilcClientKEInternalContext *proto_ctx;
-  int sock;
-
-  SILC_LOG_DEBUG(("Start"));
-
-  sock = silc_net_accept_connection(ke->fd);
-  if (sock < 0) {
-    client->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT,
-                    "Could not accept key agreement connection: ", 
-                    strerror(errno));
-    ke->client_entry->ke = NULL;
-    ke->completion(ke->client, ke->conn, ke->client_entry, 
-                  SILC_KEY_AGREEMENT_ERROR, NULL, ke->context);
-    silc_schedule_task_del_by_fd(client->schedule, ke->fd);
-    silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd);
-    silc_net_close_connection(ke->fd);
-    if (ke->timeout)
-      silc_schedule_task_del(client->schedule, ke->timeout);
-    silc_free(ke);
+  SilcClient client = conn->client;
+
+  if (status != SILC_SKE_STATUS_OK) {
+    /* Key exchange failed */
+    ke->completion(client, conn, client_entry,
+                  status == SILC_SKE_STATUS_TIMEOUT ?
+                  SILC_KEY_AGREEMENT_TIMEOUT :
+                  SILC_KEY_AGREEMENT_FAILURE, NULL, ke->context);
+    silc_client_keyagr_free(client, conn, client_entry);
+    return;
+  }
+
+  /* Returns the negotiated key material to application.  Key agreement
+     was successful. */
+  ke->completion(client, conn, client_entry, SILC_KEY_AGREEMENT_OK,
+                keymat, ke->context);
+
+  silc_client_keyagr_free(client, conn, client_entry);
+}
+
+/* Starts key agreement as responder.  */
+
+static void silc_client_process_key_agreement(SilcClient client,
+                                             SilcClientConnection conn,
+                                             SilcClientEntry client_entry)
+{
+  SilcClientKeyAgreement ke = client_entry->internal.ke;
+  SilcSKEParamsStruct params;
+
+  SILC_LOG_DEBUG(("Processing key agrement %p session", ke));
+
+  /* Allocate SKE */
+  ke->ske = silc_ske_alloc(client->rng, conn->internal->schedule,
+                          ke->params.repository, ke->public_key,
+                          ke->private_key, client_entry);
+  if (!ke->ske) {
+    ke->completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY,
+                  NULL, ke->context);
+    silc_client_keyagr_free(client, conn, client_entry);
     return;
   }
 
-  /* Set socket options */
-  silc_net_set_socket_nonblock(sock);
-  silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
-
-  /* Create socket for this connection (it is of type UNKNOWN since this
-     really is not a real SILC connection. It is only for the key
-     agreement protocol). */
-  silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, (void *)conn, &newsocket);
-  ke->sock = newsocket;
-
-  /* Perform name and address lookups for the remote host. */
-  silc_net_check_host_by_sock(sock, &newsocket->hostname, &newsocket->ip);
-  if (!newsocket->hostname && !newsocket->ip) {
-    client->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, 
-                    "Could not resolve the remote IP or hostname");
-    ke->client_entry->ke = NULL;
-    ke->completion(ke->client, ke->conn, ke->client_entry, 
-                  SILC_KEY_AGREEMENT_ERROR, NULL, ke->context);
-    silc_schedule_task_del_by_fd(client->schedule, ke->fd);
-    silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd);
-    silc_net_close_connection(ke->fd);
-    if (ke->timeout)
-      silc_schedule_task_del(client->schedule, ke->timeout);
-    silc_free(ke);
+  /* Set SKE parameters */
+  params.version = client->internal->silc_client_version;
+  params.flags = SILC_SKE_SP_FLAG_MUTUAL;
+  if (ke->params.udp) {
+    params.flags |= SILC_SKE_SP_FLAG_IV_INCLUDED;
+    params.session_port = ke->params.local_port;
+  }
+
+  silc_ske_set_callbacks(ke->ske, silc_client_keyagr_verify_key,
+                        silc_client_keyagr_completion, client_entry);
+
+  /* Start key exchange as responder */
+  ke->op = silc_ske_responder(ke->ske, ke->stream, &params);
+  if (!ke->op) {
+    ke->completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR,
+                  NULL, ke->context);
+    silc_client_keyagr_free(client, conn, client_entry);
+  }
+}
+
+/* TCP network listener callback.  Accepts new key agreement connection.
+   Responder function. */
+
+static void silc_client_tcp_accept(SilcNetStatus status,
+                                  SilcStream stream,
+                                  void *context)
+{
+  SilcClientEntry client_entry = context;
+  SilcClientKeyAgreement ke = client_entry->internal.ke;
+
+  /* Create packet stream */
+  ke->stream = silc_packet_stream_create(ke->client->internal->packet_engine,
+                                        ke->conn->internal->schedule, stream);
+  if (!ke->stream) {
+    silc_stream_destroy(stream);
     return;
   }
-  if (!newsocket->hostname)
-    newsocket->hostname = strdup(newsocket->ip);
-  newsocket->port = silc_net_get_remote_port(sock);
-  silc_client_add_socket(client, newsocket);
-
-  /* Allocate internal context for key exchange protocol. This is
-     sent as context for the protocol. */
-  proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
-  proto_ctx->client = client;
-  proto_ctx->sock = silc_socket_dup(newsocket);
-  proto_ctx->rng = client->rng;
-  proto_ctx->responder = TRUE;
-  proto_ctx->context = context;
-  proto_ctx->send_packet = silc_client_key_agreement_send_packet;
-  proto_ctx->verify = silc_client_protocol_ke_verify_key;
-  ke->proto_ctx = proto_ctx;
-
-  /* Prepare the connection for key exchange protocol. We allocate the
-     protocol but will not start it yet. The connector will be the
-     initiator of the protocol thus we will wait for initiation from 
-     there before we start the protocol. */
-  silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, 
-                     &newsocket->protocol, proto_ctx, 
-                     silc_client_key_agreement_final);
-
-  /* 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(sock);
+
+  /* Process session */
+  silc_client_process_key_agreement(ke->client, ke->conn, client_entry);
 }
 
-/* Timeout occured during key agreement. This means that the key agreement
-   protocol was not completed in the specified timeout. We will call the 
-   completion callback. */
+/* UDP network listener callback.  Accepts new key agreement session.
+   Responder function. */
 
-SILC_TASK_CALLBACK(silc_client_key_agreement_timeout)
+static SilcBool silc_client_udp_accept(SilcPacketEngine engine,
+                                       SilcPacketStream stream,
+                                       SilcPacket packet,
+                                       void *callback_context,
+                                       void *stream_context)
 {
-  SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context;
+  SilcClientEntry client_entry = callback_context;
+  SilcClientKeyAgreement ke = client_entry->internal.ke;
+  SilcUInt16 port;
+  const char *ip;
+
+  /* We want only key exchange packet.  Eat other packets so that default
+     packet callback doesn't get them. */
+  if (packet->type != SILC_PACKET_KEY_EXCHANGE) {
+    silc_packet_free(packet);
+    return TRUE;
+  }
 
-  ke->client_entry->ke = NULL;
-  ke->completion(ke->client, ke->conn, ke->client_entry, 
-                SILC_KEY_AGREEMENT_TIMEOUT, NULL, ke->context);
+  /* Create packet stream for this remote UDP session */
+  if (!silc_packet_get_sender(packet, &ip, &port)) {
+    silc_packet_free(packet);
+    return TRUE;
+  }
+  ke->stream = silc_packet_stream_add_remote(stream, ip, port, packet);
+  if (!ke->stream) {
+    silc_packet_free(packet);
+    return TRUE;
+  }
+
+  /* Process session */
+  silc_client_process_key_agreement(ke->client, ke->conn, client_entry);
+  return TRUE;
+}
 
-  if (ke->sock) {
-    silc_client_del_socket(ke->client, ke->sock);
-    silc_socket_free(ke->sock);
+/* Client connect completion callback.  Initiator function. */
+
+static void silc_client_keyagr_perform_cb(SilcClient client,
+                                         SilcClientConnection conn,
+                                         SilcClientConnectionStatus status,
+                                         SilcStatus error,
+                                         const char *message,
+                                         void *context)
+{
+  SilcClientEntry client_entry = context;
+  SilcClientKeyAgreement ke = client_entry->internal.ke;
+  SilcSKEKeyMaterial keymat;
+
+  ke->op = NULL;
+
+  switch (status) {
+  case SILC_CLIENT_CONN_SUCCESS:
+    SILC_LOG_DEBUG(("Key agreement %p successful", ke));
+
+    keymat = silc_ske_get_key_material(conn->internal->ske);
+    ke->completion(ke->client, ke->conn, client_entry, SILC_KEY_AGREEMENT_OK,
+                  keymat, ke->context);
+    break;
+
+  case SILC_CLIENT_CONN_ERROR_TIMEOUT:
+    SILC_LOG_DEBUG(("Key agreement %p timeout", ke));
+    ke->completion(ke->client, ke->conn, client_entry,
+                  SILC_KEY_AGREEMENT_TIMEOUT, NULL, ke->context);
+    break;
+
+  default:
+    SILC_LOG_DEBUG(("Key agreement %p error %d", ke, status));
+    ke->completion(ke->client, ke->conn, client_entry,
+                  SILC_KEY_AGREEMENT_FAILURE, NULL, ke->context);
+    break;
   }
-  if (ke->proto_ctx && ke->proto_ctx->ske)
-    silc_ske_free(ke->proto_ctx->ske);
-  ke->client_entry->ke = NULL;
-  if (ke->fd)
-    silc_schedule_task_del_by_fd(ke->client->schedule, ke->fd);
-  silc_schedule_unset_listen_fd(ke->client->schedule, ke->fd);
-  silc_net_close_connection(ke->fd);
-  silc_free(ke);
+
+  /* Close the created connection */
+  if (conn)
+    silc_client_close_connection(ke->client, conn);
+
+  silc_client_keyagr_free(ke->client, ke->conn, client_entry);
 }
 
-/* Sends key agreement request to the remote client indicated by the
-   `client_entry'. If the caller provides the `hostname' and the `port'
-   arguments then the library will bind the client to that hostname and
-   that port for the key agreement protocol. It also sends the `hostname'
-   and the `port' in the key agreement packet to the remote client. This
-   would indicate that the remote client may initiate the key agreement
-   protocol to the `hostname' on the `port'.  If port is zero then the
-   bound port is undefined (the operating system defines it).
-
-   If the `hostname' and `port' is not provided then empty key agreement
-   packet is sent to the remote client. The remote client may reply with
-   the same packet including its hostname and port. If the library receives
-   the reply from the remote client the `key_agreement' client operation
-   callback will be called to verify whether the user wants to perform the
-   key agreement or not. 
-
-   NOTE: If the application provided the `hostname' and the `port' and the 
-   remote side initiates the key agreement protocol it is not verified
-   from the user anymore whether the protocol should be executed or not.
-   By setting the `hostname' and `port' the user gives permission to
-   perform the protocol (we are responder in this case).
-
-   NOTE: If the remote side decides not to initiate the key agreement
-   or decides not to reply with the key agreement packet then we cannot
-   perform the key agreement at all. If the key agreement protocol is
-   performed the `completion' callback with the `context' will be called.
-   If remote side decides to ignore the request the `completion' will be
-   called after the specified timeout, `timeout_secs'. 
-
-   NOTE: If the `hostname' and the `port' was not provided the `completion'
-   will not be called at all since this does nothing more than sending
-   a packet to the remote host.
-
-   NOTE: There can be only one active key agreement for one client entry.
-   Before setting new one, the old one must be finished (it is finished
-   after calling the completion callback) or the function 
-   silc_client_abort_key_agreement must be called. */
+/* Packet stream callbacks */
+static SilcPacketCallbacks silc_client_keyagr_stream_cb =
+{
+  silc_client_udp_accept, NULL, NULL
+};
+
+/*************************** Key Agreement API ******************************/
+
+/* Sends key agreement packet to remote client.  If IP addresses are provided
+   creates also listener for Ã­ncoming key agreement connection.  Supports
+   both TCP and UDP transports. */
 
 void silc_client_send_key_agreement(SilcClient client,
                                    SilcClientConnection conn,
                                    SilcClientEntry client_entry,
-                                   const char *hostname,
-                                   const char *bindhost,
-                                   int port,
-                                   uint32 timeout_secs,
+                                   SilcClientConnectionParams *params,
+                                   SilcPublicKey public_key,
+                                   SilcPrivateKey private_key,
                                    SilcKeyAgreementCallback completion,
                                    void *context)
 {
-  SilcSocketConnection sock = conn->sock;
   SilcClientKeyAgreement ke = NULL;
   SilcBuffer buffer;
+  SilcUInt16 port = 0, protocol = 0;
+  char *local_ip = NULL;
+  SilcStream stream;
 
-  assert(client_entry);
+  SILC_LOG_DEBUG(("Sending key agreement"));
 
-  if (client_entry->ke)
+  if (!client_entry)
+    return;
+  if (conn->internal->disconnected)
     return;
 
-  /* Create the listener if hostname and port was provided.
-   * also, use bindhost if it was specified.
-   */
-   
-  if (hostname) {
+  if (client_entry->internal.ke) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ALREADY_STARTED,
+              NULL, context);
+    return;
+  }
+
+  if (client_entry == conn->local_entry) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_SELF_DENIED,
+              NULL, context);
+    return;
+  }
+
+  /* If local IP is provided, create listener */
+  if (params && (params->local_ip || params->bind_ip)) {
     ke = silc_calloc(1, sizeof(*ke));
-    
-    if (bindhost)
-      ke->fd = silc_net_create_server(port, bindhost);
-    else
-      ke->fd = silc_net_create_server(port, hostname);
-
-    if (ke->fd < 0) {
-      client->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, 
-                      "Cannot create listener on %s on port %d: %s", 
-                      (bindhost) ? bindhost:hostname, port, strerror(errno));
-      completion(client, conn, client_entry, SILC_KEY_AGREEMENT_FAILURE,
+    if (!ke) {
+      completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY,
                 NULL, context);
-      silc_free(ke);
-
       return;
     }
 
+    /* Create network listener */
+    if (params->udp) {
+      /* UDP listener */
+      stream = silc_net_udp_connect(params->bind_ip ? params->bind_ip :
+                                   params->local_ip, params->local_port,
+                                   NULL, 0, conn->internal->schedule);
+      ke->udp_listener =
+       silc_packet_stream_create(client->internal->packet_engine,
+                                 conn->internal->schedule, stream);
+      if (!ke->udp_listener) {
+       client->internal->ops->say(
+                    client, conn, SILC_CLIENT_MESSAGE_ERROR,
+                    "Cannot create UDP listener on %s on port %d: %s",
+                    params->bind_ip ? params->bind_ip :
+                    params->local_ip, params->local_port, strerror(errno));
+       completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR,
+                  NULL, context);
+       if (stream)
+         silc_stream_destroy(stream);
+       silc_free(ke);
+       return;
+      }
+      silc_packet_stream_link(ke->udp_listener,
+                             &silc_client_keyagr_stream_cb,
+                             client_entry, 1000000,
+                             SILC_PACKET_ANY, -1);
+
+      port = params->local_port;
+      if (!port) {
+       /* Get listener port */
+       SilcSocket sock;
+       silc_socket_stream_get_info(stream, &sock, NULL, NULL, NULL);
+       port = silc_net_get_local_port(sock);
+      }
+    } else {
+      /* TCP listener */
+      ke->tcp_listener =
+       silc_net_tcp_create_listener(params->bind_ip ?
+                                    (const char **)&params->bind_ip :
+                                    (const char **)&params->local_ip,
+                                    1, params->local_port, FALSE, FALSE,
+                                    conn->internal->schedule,
+                                    silc_client_tcp_accept,
+                                    client_entry);
+      if (!ke->tcp_listener) {
+       client->internal->ops->say(
+                    client, conn, SILC_CLIENT_MESSAGE_ERROR,
+                    "Cannot create listener on %s on port %d: %s",
+                    params->bind_ip ? params->bind_ip :
+                    params->local_ip, params->local_port, strerror(errno));
+       completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR,
+                  NULL, context);
+       silc_free(ke);
+       return;
+      }
+
+      port = params->local_port;
+      if (!port) {
+       /* Get listener port */
+       SilcUInt16 *ports;
+       ports = silc_net_listener_get_port(ke->tcp_listener, NULL);
+       port = ports[0];
+       silc_free(ports);
+      }
+    }
+
+    local_ip = params->local_ip;
+    protocol = params->udp;
+
     ke->client = client;
     ke->conn = conn;
-    ke->client_entry = client_entry;
     ke->completion = completion;
     ke->context = context;
-
-    /* Add listener task to the scheduler. This task receives the key 
-       negotiations. */
-    silc_schedule_task_add(client->schedule, ke->fd,
-                          silc_client_process_key_agreement,
-                          (void *)ke, 0, 0, 
-                          SILC_TASK_FD,
-                          SILC_TASK_PRI_NORMAL);
-
-    /* Register a timeout task that will be executed if the connector
-       will not start the key exchange protocol within the specified 
-       timeout. */
-    ke->timeout = silc_schedule_task_add(client->schedule, 0, 
-                                        silc_client_key_agreement_timeout,
-                                        (void *)ke, timeout_secs, 0, 
-                                        SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
+    ke->params = *params;
+    ke->public_key = public_key;
+    ke->private_key = private_key;
+    silc_client_ref_client(client, conn, client_entry);
+    client_entry->internal.ke = ke;
+    client_entry->internal.prv_resp = TRUE;
   }
 
   /* Encode the key agreement payload */
-  buffer = silc_key_agreement_payload_encode(hostname, 
-                                            !ke ? port : 
-                                            silc_net_get_local_port(ke->fd));
+  buffer = silc_key_agreement_payload_encode(local_ip, protocol, port);
+  if (!buffer) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY,
+              NULL, context);
+    silc_client_keyagr_free(client, conn, client_entry);
+    return;
+  }
 
   /* Send the key agreement packet to the client */
-  silc_client_packet_send(client, sock, SILC_PACKET_KEY_AGREEMENT,
-                         client_entry->id, SILC_ID_CLIENT, NULL, NULL,
-                         buffer->data, buffer->len, FALSE);
-  silc_buffer_free(buffer);
-
-}
-
-static int 
-silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx)
-{
-  int sock;
-
-  /* Create connection to server asynchronously */
-  sock = silc_net_create_connection_async(NULL, 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_schedule_task_add(ctx->client->schedule, sock, 
-                                    silc_client_perform_key_agreement_start,
-                                    (void *)ctx, 0, 0, 
-                                    SILC_TASK_FD,
-                                    SILC_TASK_PRI_NORMAL);
-  silc_schedule_set_listen_fd(ctx->client->schedule, sock, SILC_TASK_WRITE);
-
-  ctx->sock = sock;
-
-  return sock;
-}
-
-/* Routine used by silc_client_perform_key_agreement to create connection
-   to the remote client on specified port. */
-
-static int
-silc_client_connect_to_client(SilcClient client, 
-                             SilcClientConnection conn, int port,
-                             char *host, void *context)
-{
-  SilcClientInternalConnectContext *ctx;
-
-  /* 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;
-  ctx->context = context;
-
-  /* Do the actual connecting process */
-  return silc_client_connect_to_client_internal(ctx);
-}
-
-/* Callback that is called after connection has been created. This actually
-   starts the key agreement protocol. This is initiator function. */
-
-SILC_TASK_CALLBACK(silc_client_perform_key_agreement_start)
-{
-  SilcClientInternalConnectContext *ctx =
-    (SilcClientInternalConnectContext *)context;
-  SilcClient client = ctx->client;
-  SilcClientConnection conn = ctx->conn;
-  SilcClientKeyAgreement ke = (SilcClientKeyAgreement)ctx->context;
-  int opt, opt_len = sizeof(opt);
-
-  SILC_LOG_DEBUG(("Start"));
-
-  /* Check the socket status as it might be in error */
-  silc_net_get_socket_opt(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, SILC_CLIENT_MESSAGE_ERROR,
-                      "Could not connect to client %s: %s",
-                      ctx->host, strerror(opt));
-      client->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, 
-                      "Connecting to port %d of client %s resumed", 
-                      ctx->port, ctx->host);
-
-      /* Unregister old connection try */
-      silc_schedule_unset_listen_fd(client->schedule, fd);
-      silc_net_close_connection(fd);
-      silc_schedule_task_del(client->schedule, ctx->task);
-
-      /* Try again */
-      silc_client_connect_to_client_internal(ctx);
-      ctx->tries++;
-    } else {
-      /* Connection failed and we won't try anymore */
-      client->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
-                      "Could not connect to client %s: %s",
-                      ctx->host, strerror(opt));
-      silc_schedule_unset_listen_fd(client->schedule, fd);
-      silc_net_close_connection(fd);
-      silc_schedule_task_del(client->schedule, ctx->task);
-      silc_free(ctx->host);
-      silc_free(ctx);
-
-      /* Call the completion callback */
-      ke->completion(ke->client, ke->conn, ke->client_entry, 
-                    SILC_KEY_AGREEMENT_FAILURE, NULL, ke->context);
-      silc_free(ke);
-    }
+  if (!silc_packet_send_ext(conn->stream, SILC_PACKET_KEY_AGREEMENT, 0,
+                           0, NULL, SILC_ID_CLIENT, &client_entry->id,
+                           silc_buffer_datalen(buffer), NULL, NULL)) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR,
+              NULL, context);
+    silc_client_keyagr_free(client, conn, client_entry);
+    silc_buffer_free(buffer);
     return;
   }
 
-  silc_schedule_unset_listen_fd(client->schedule, fd);
-  silc_schedule_task_del(client->schedule, ctx->task);
+  /* Add key agreement timeout task */
+  if (params && params->timeout_secs)
+    silc_schedule_task_add_timeout(conn->internal->schedule,
+                                  silc_client_keyagr_timeout,
+                                  client_entry, params->timeout_secs, 0);
 
-  ke->fd = fd;
-
-  /* Now actually perform the key agreement protocol */
-  silc_client_perform_key_agreement_fd(ke->client, ke->conn,
-                                      ke->client_entry, ke->fd, ctx->host,
-                                      ke->completion, ke->context);
-  silc_free(ke);
-  silc_free(ctx->host);
-  silc_free(ctx);
+  silc_buffer_free(buffer);
 }
 
-/* Performs the actual key agreement protocol. Application may use this
-   to initiate the key agreement protocol. This can be called for example
-   after the application has received the `key_agreement' client operation,
-   and did not return TRUE from it.
-
-   The `hostname' is the remote hostname (or IP address) and the `port'
-   is the remote port. The `completion' callback with the `context' will
-   be called after the key agreement protocol.
-   
-   NOTE: If the application returns TRUE in the `key_agreement' client
-   operation the library will automatically start the key agreement. In this
-   case the application must not call this function. However, application
-   may choose to just ignore the `key_agreement' client operation (and
-   merely just print information about it on the screen) and call this
-   function when the user whishes to do so (by, for example, giving some
-   specific command). Thus, the API provides both, automatic and manual
-   initiation of the key agreement. Calling this function is the manual
-   initiation and returning TRUE in the `key_agreement' client operation
-   is the automatic initiation. */
+/* Perform key agreement protocol as initiator.  Conneects to remote host. */
 
 void silc_client_perform_key_agreement(SilcClient client,
                                       SilcClientConnection conn,
                                       SilcClientEntry client_entry,
-                                      char *hostname,
-                                      int port,
+                                      SilcClientConnectionParams *params,
+                                      SilcPublicKey public_key,
+                                      SilcPrivateKey private_key,
+                                      char *hostname, int port,
                                       SilcKeyAgreementCallback completion,
                                       void *context)
 {
   SilcClientKeyAgreement ke;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_LOG_DEBUG(("Performing key agreement"));
 
-  assert(client_entry && hostname && port);
+  if (!client_entry || !hostname || !port) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR,
+              NULL, context);
+    return;
+  }
+
+  if (client_entry == conn->local_entry) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_SELF_DENIED,
+              NULL, context);
+    return;
+  }
 
   ke = silc_calloc(1, sizeof(*ke));
+  if (!ke) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY,
+              NULL, context);
+    return;
+  }
   ke->client = client;
   ke->conn = conn;
-  ke->client_entry = client_entry;
   ke->completion = completion;
   ke->context = context;
-
-  /* Connect to the remote client */
-  ke->fd = silc_client_connect_to_client(client, conn, port, hostname, ke);
-  if (ke->fd < 0) {
-    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_FAILURE,
+  silc_client_ref_client(client, conn, client_entry);
+  client_entry->internal.ke = ke;
+
+  if (params)
+    params->no_authentication = TRUE;
+
+  /* Connect to the remote client.  Performs key exchange automatically. */
+  if (!silc_client_connect_to_client(client, params, public_key,
+                                    private_key, hostname, port,
+                                    silc_client_keyagr_perform_cb,
+                                    client_entry)) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR,
               NULL, context);
-    silc_free(ke);
+    silc_client_keyagr_free(client, conn, client_entry);
     return;
   }
 }
 
-/* Same as above but application has created already the connection to 
-   the remote host. The `sock' is the socket to the remote connection. 
-   Application can use this function if it does not want the client library
-   to create the connection. */
-
-void silc_client_perform_key_agreement_fd(SilcClient client,
-                                         SilcClientConnection conn,
-                                         SilcClientEntry client_entry,
-                                         int sock,
-                                         char *hostname,
-                                         SilcKeyAgreementCallback completion,
-                                         void *context)
+/* Same as above but caller has created connection. */
+
+void
+silc_client_perform_key_agreement_stream(SilcClient client,
+                                        SilcClientConnection conn,
+                                        SilcClientEntry client_entry,
+                                        SilcClientConnectionParams *params,
+                                        SilcPublicKey public_key,
+                                        SilcPrivateKey private_key,
+                                        SilcStream stream,
+                                        SilcKeyAgreementCallback completion,
+                                        void *context)
 {
   SilcClientKeyAgreement ke;
-  SilcClientKEInternalContext *proto_ctx;
-  SilcProtocol protocol;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_LOG_DEBUG(("Performing key agreement"));
+
+  if (!client_entry || !stream) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR,
+              NULL, context);
+    return;
+  }
 
-  assert(client_entry);
+  if (client_entry == conn->local_entry) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_SELF_DENIED,
+              NULL, context);
+    return;
+  }
 
   ke = silc_calloc(1, sizeof(*ke));
+  if (!ke) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY,
+              NULL, context);
+    return;
+  }
   ke->client = client;
   ke->conn = conn;
-  ke->client_entry = client_entry;
-  ke->fd = sock;
   ke->completion = completion;
   ke->context = context;
-
-  /* Allocate new socket connection object */
-  silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, (void *)conn, &ke->sock);
-  silc_client_add_socket(client, ke->sock);
-  ke->sock->hostname = strdup(hostname);
-  ke->sock->port = silc_net_get_remote_port(sock);
-
-  /* Allocate internal context for key exchange protocol. This is
-     sent as context for the protocol. */
-  proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
-  proto_ctx->client = client;
-  proto_ctx->sock = silc_socket_dup(ke->sock);
-  proto_ctx->rng = client->rng;
-  proto_ctx->responder = FALSE;
-  proto_ctx->context = ke;
-  proto_ctx->send_packet = silc_client_key_agreement_send_packet;
-  proto_ctx->verify = silc_client_protocol_ke_verify_key;
-  ke->proto_ctx = proto_ctx;
-
-  /* Perform key exchange protocol. */
-  silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, 
-                     &protocol, (void *)proto_ctx,
-                     silc_client_key_agreement_final);
-  ke->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(sock);
-
-  /* Execute the protocol */
-  silc_protocol_execute(protocol, client->schedule, 0, 0);
+  silc_client_ref_client(client, conn, client_entry);
+  client_entry->internal.ke = ke;
+
+  if (params)
+    params->no_authentication = TRUE;
+
+  /* Perform key exchange protocol */
+  if (!silc_client_key_exchange(client, params, public_key,
+                               private_key, stream, SILC_CONN_CLIENT,
+                               silc_client_keyagr_perform_cb,
+                               client_entry)) {
+    completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ERROR,
+              NULL, context);
+    silc_client_keyagr_free(client, conn, client_entry);
+    return;
+  }
 }
 
 /* This function can be called to unbind the hostname and the port for
-   the key agreement protocol. However, this function has effect only 
+   the key agreement protocol. However, this function has effect only
    before the key agreement protocol has been performed. After it has
-   been performed the library will automatically unbind the port. The 
-   `client_entry' is the client to which we sent the key agreement 
+   been performed the library will automatically unbind the port. The
+   `client_entry' is the client to which we sent the key agreement
    request. */
 
 void silc_client_abort_key_agreement(SilcClient client,
                                     SilcClientConnection conn,
                                     SilcClientEntry client_entry)
 {
-  assert(client_entry);
+  SilcClientKeyAgreement ke;
 
-  if (client_entry->ke) {
-    SilcClientKeyAgreement ke;
+  if (!client_entry || !client_entry->internal.ke)
+    return;
 
-    if (client_entry->ke->sock) {
-      silc_client_del_socket(client_entry->ke->client, client_entry->ke->sock);
-      silc_socket_free(client_entry->ke->sock);
-    }
-    silc_schedule_task_del_by_fd(client->schedule, client_entry->ke->fd);
-    if (client_entry->ke->timeout)
-      silc_schedule_task_del(client->schedule, 
-                            client_entry->ke->timeout);
-    ke = client_entry->ke;
-    client_entry->ke = NULL;
-    ke->completion(client, conn, client_entry, 
-                  SILC_KEY_AGREEMENT_ABORTED, NULL, ke->context);
-    silc_free(ke);
-  }
+  ke = client_entry->internal.ke;
+
+  SILC_LOG_DEBUG(("Abort key agreement %p"));
+
+  ke->completion(client, conn, client_entry,
+                SILC_KEY_AGREEMENT_ABORTED, NULL, ke->context);
+
+  silc_client_keyagr_free(client, conn, client_entry);
 }
 
-/* Callback function that is called after we've resolved the client 
-   information who sent us the key agreement packet from the server.
-   We actually call the key_agreement client operation now. */
+/* Key agreement packet received */
 
-static void 
-silc_client_key_agreement_resolve_cb(SilcClient client,
-                                    SilcClientConnection conn,
-                                    SilcClientEntry *clients,
-                                    uint32 clients_count,
-                                    void *context)
+SILC_FSM_STATE(silc_client_key_agreement)
 {
-  SilcPacketContext *packet = (SilcPacketContext *)context;
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcPacket packet = state_context;
+  SilcClientID remote_id;
+  SilcClientEntry remote_client;
   SilcKeyAgreementPayload payload;
-  int ret;
-  SilcKeyAgreementCallback completion;
-  void *completion_context;
 
-  if (!clients)
-    goto out;
+  if (packet->src_id_type != SILC_ID_CLIENT) {
+    /** Invalid packet */
+    silc_fsm_next(fsm, silc_client_key_agreement_error);
+    SILC_FSM_CONTINUE;
+  }
+
+  if (!silc_id_str2id(packet->src_id, packet->src_id_len, SILC_ID_CLIENT,
+                     &remote_id, sizeof(remote_id))) {
+    /** Invalid source ID */
+    silc_fsm_next(fsm, silc_client_key_agreement_error);
+    SILC_FSM_CONTINUE;
+  }
+
+  /* Check whether we know this client already */
+  remote_client = silc_client_get_client_by_id(client, conn, &remote_id);
+  if (!remote_client || !remote_client->nickname[0]) {
+    /** Resolve client info */
+    silc_client_unref_client(client, conn, remote_client);
+    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
+                                        client, conn, &remote_id, NULL,
+                                        silc_client_keyagr_resolved, fsm));
+    /* NOT REACHED */
+  }
 
   /* Parse the key agreement payload */
-  payload = silc_key_agreement_payload_parse(packet->buffer);
-  if (!payload)
-    goto out;
+  payload = silc_key_agreement_payload_parse(silc_buffer_data(&packet->buffer),
+                                            silc_buffer_len(&packet->buffer));
+  if (!payload) {
+    /** Malformed Payload */
+    SILC_LOG_DEBUG(("Malformed key agreement payload"));
+    silc_fsm_next(fsm, silc_client_key_agreement_error);
+    SILC_FSM_CONTINUE;
+  }
 
-  /* Call the key_agreement client operation */
-  ret = client->ops->key_agreement(client, conn, clients[0], 
-                                  silc_key_agreement_get_hostname(payload),
-                                  silc_key_agreement_get_port(payload),
-                                  &completion, &completion_context);
+  /* If remote did not provide connection endpoint, we will assume that we
+     will provide it and will be responder. */
+  if (!silc_key_agreement_get_hostname(payload))
+    remote_client->internal.prv_resp = TRUE;
+  else
+    remote_client->internal.prv_resp = FALSE;
 
-  /* If the user returned TRUE then we'll start the key agreement right
-     here and right now. */
-  if (ret == TRUE)
-    silc_client_perform_key_agreement(client, conn, clients[0],
-                                     silc_key_agreement_get_hostname(payload),
-                                     silc_key_agreement_get_port(payload),
-                                     completion, completion_context);
+  /* Notify application for key agreement request */
+  client->internal->ops->key_agreement(
+                                  client, conn, remote_client,
+                                  silc_key_agreement_get_hostname(payload),
+                                  silc_key_agreement_get_protocol(payload),
+                                  silc_key_agreement_get_port(payload));
 
   silc_key_agreement_payload_free(payload);
 
- out:
-  silc_packet_context_free(packet);
+  silc_packet_free(packet);
+  SILC_FSM_FINISH;
 }
 
-/* Received Key Agreement packet from remote client. Process the packet
-   and resolve the client information from the server before actually
-   letting the application know that we've received this packet.  Then 
-   call the key_agreement client operation and let the user decide
-   whether we perform the key agreement protocol now or not. */
+/* Key agreement packet processing error */
 
-void silc_client_key_agreement(SilcClient client,
-                              SilcSocketConnection sock,
-                              SilcPacketContext *packet)
+SILC_FSM_STATE(silc_client_key_agreement_error)
 {
-  SilcClientID *remote_id;
-
-  if (packet->src_id_type != SILC_ID_CLIENT)
-    return;
-
-  remote_id = silc_id_str2id(packet->src_id, packet->src_id_len, 
-                            SILC_ID_CLIENT);
-  if (!remote_id)
-    return;
-
-  silc_client_get_client_by_id_resolve(client, sock->user_data, remote_id,
-                                      silc_client_key_agreement_resolve_cb,
-                                      silc_packet_context_dup(packet));
-  silc_free(remote_id);
+  SilcPacket packet = state_context;
+  silc_packet_free(packet);
+  SILC_FSM_FINISH;
 }