/*

  client_keyagr.c

  Author: Pekka Riikonen <priikone@poseidon.pspt.fi>

  Copyright (C) 2001 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.
  
  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: client_keyagr.c,v 1.22 2002/01/22 18:31:15 priikone Exp $ */
/* 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 "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);

/* Key agreement context */
struct SilcClientKeyAgreementStruct {
  SilcClient client;
  SilcClientConnection conn;
  int fd;			          /* Listening/connection socket */
  SilcSocketConnection sock;		  /* Remote socket connection */
  SilcClientEntry client_entry;		  /* Destination client */
  SilcKeyAgreementCallback completion;	  /* Key agreement completion */
  void *context;			  /* User context */
  SilcTask timeout;			  /* Timeout task */
  SilcClientKEInternalContext *proto_ctx; /* Key Exchange protocol context */
};

/* Packet sending function used by the SKE in the key agreement process. */

static void silc_client_key_agreement_send_packet(SilcSKE ske,
						  SilcBuffer packet,
						  SilcPacketType type,
						  void *context)
{
  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;
}

/* Timeout callback that is called to close the connection and free the
   socket connection data. */

SILC_TASK_CALLBACK(silc_client_key_agreement_close)
{
  SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context;

  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);
}

/* This callback is called after the key agreement protocol has been
   performed. This calls the final completion callback for the application. */

SILC_TASK_CALLBACK(silc_client_key_agreement_final)
{
  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;
  }

  /* 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);
}

/* 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. */

SILC_TASK_CALLBACK(silc_client_process_key_agreement)
{
  SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context;
  SilcClient client = ke->client;
  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->internal->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);
    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->internal->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);
    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);
}

/* 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. */

SILC_TASK_CALLBACK(silc_client_key_agreement_timeout)
{
  SilcClientKeyAgreement ke = (SilcClientKeyAgreement)context;

  ke->client_entry->ke = NULL;
  ke->completion(ke->client, ke->conn, ke->client_entry, 
		 SILC_KEY_AGREEMENT_TIMEOUT, NULL, ke->context);

  if (ke->sock) {
    silc_client_del_socket(ke->client, ke->sock);
    silc_socket_free(ke->sock);
  }
  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);
}

/* 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. */

void silc_client_send_key_agreement(SilcClient client,
				    SilcClientConnection conn,
				    SilcClientEntry client_entry,
				    const char *hostname,
				    const char *bindhost,
				    int port,
				    uint32 timeout_secs,
				    SilcKeyAgreementCallback completion,
				    void *context)
{
  SilcSocketConnection sock = conn->sock;
  SilcClientKeyAgreement ke = NULL;
  SilcBuffer buffer;

  if (!client_entry || client_entry->ke)
    return;

  /* Create the listener if hostname and port was provided.
   * also, use bindhost if it was specified.
   */
   
  if (hostname) {
    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->internal->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,
		 NULL, context);
      silc_free(ke);

      return;
    }

    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);
  }

  /* Encode the key agreement payload */
  buffer = silc_key_agreement_payload_encode(hostname, 
					     !ke ? port : 
					     silc_net_get_local_port(ke->fd));

  /* 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->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
				 "Could not connect to client %s: %s",
				 ctx->host, strerror(opt));
      client->internal->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->internal->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);
    }
    return;
  }

  silc_schedule_unset_listen_fd(client->schedule, fd);
  silc_schedule_task_del(client->schedule, ctx->task);

  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);
}

/* 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. */

void silc_client_perform_key_agreement(SilcClient client,
				       SilcClientConnection conn,
				       SilcClientEntry client_entry,
				       char *hostname,
				       int port,
				       SilcKeyAgreementCallback completion,
				       void *context)
{
  SilcClientKeyAgreement ke;

  SILC_LOG_DEBUG(("Start"));

  if (!client_entry || !hostname || !port)
    return;

  ke = silc_calloc(1, sizeof(*ke));
  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,
	       NULL, context);
    silc_free(ke);
    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)
{
  SilcClientKeyAgreement ke;
  SilcClientKEInternalContext *proto_ctx;
  SilcProtocol protocol;

  SILC_LOG_DEBUG(("Start"));

  if (!client_entry)
    return;

  ke = silc_calloc(1, sizeof(*ke));
  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);
}

/* This function can be called to unbind the hostname and the port for
   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 
   request. */

void silc_client_abort_key_agreement(SilcClient client,
				     SilcClientConnection conn,
				     SilcClientEntry client_entry)
{
  if (!client_entry)
    return;

  if (client_entry->ke) {
    SilcClientKeyAgreement ke;

    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);
  }
}

/* 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. */

static void 
silc_client_key_agreement_resolve_cb(SilcClient client,
				     SilcClientConnection conn,
				     SilcClientEntry *clients,
				     uint32 clients_count,
				     void *context)
{
  SilcPacketContext *packet = (SilcPacketContext *)context;
  SilcKeyAgreementPayload payload;
  int ret;
  SilcKeyAgreementCallback completion;
  void *completion_context;

  if (!clients)
    goto out;

  /* Parse the key agreement payload */
  payload = silc_key_agreement_payload_parse(packet->buffer->data,
					     packet->buffer->len);
  if (!payload)
    goto out;

  /* Call the key_agreement client operation */
  ret = client->internal->ops->key_agreement(
				   client, conn, clients[0], 
				   silc_key_agreement_get_hostname(payload),
				   silc_key_agreement_get_port(payload),
				   &completion, &completion_context);

  /* 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);

  silc_key_agreement_payload_free(payload);

 out:
  silc_packet_context_free(packet);
}

/* 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. */

void silc_client_key_agreement(SilcClient client,
			       SilcSocketConnection sock,
			       SilcPacketContext *packet)
{
  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);
}
