/*

  protocol.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 1997 - 2003 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.

*/
/*
 * Server side of the protocols.
 */
/* $Id: protocol.c,v 1.91.2.4 2005/11/19 18:41:07 priikone Exp $ */

#include "serverincludes.h"
#include "server_internal.h"

SILC_TASK_CALLBACK(silc_server_protocol_connection_auth);
SILC_TASK_CALLBACK(silc_server_protocol_key_exchange);
SILC_TASK_CALLBACK(silc_server_protocol_rekey);

/*
 * Key Exhange protocol functions
 */

static bool
silc_verify_public_key_internal(SilcServer server, SilcSocketConnection sock,
				SilcSocketType conn_type,
				unsigned char *pk, SilcUInt32 pk_len,
				SilcSKEPKType pk_type)
{
  char file[256], filename[256], *fingerprint;
  struct stat st;

  if (pk_type != SILC_SKE_PK_TYPE_SILC) {
    SILC_LOG_WARNING(("We don't support %s (%s) port %d public key type %d",
		      sock->hostname, sock->ip, sock->port, pk_type));
    return FALSE;
  }

  /* Accept client keys without verification */
  if (conn_type == SILC_SOCKET_TYPE_CLIENT) {
    SILC_LOG_DEBUG(("Accepting client public key without verification"));
    return TRUE;
  }

  /* XXX For now, accept server keys without verification too. We are
     currently always doing mutual authentication so the proof of posession
     of the private key is verified, and if server is authenticated in
     conn auth protocol with public key we MUST have the key already. */
  return TRUE;
  /* Rest is unreachable code! */

  memset(filename, 0, sizeof(filename));
  memset(file, 0, sizeof(file));
  snprintf(file, sizeof(file) - 1, "serverkey_%s_%d.pub", sock->hostname,
	   sock->port);
  snprintf(filename, sizeof(filename) - 1, SILC_ETCDIR "/serverkeys/%s",
	   file);

  /* Create serverkeys directory if it doesn't exist. */
  if (stat(SILC_ETCDIR "/serverkeys", &st) < 0) {
    /* If dir doesn't exist */
    if (errno == ENOENT) {
      if (mkdir(SILC_ETCDIR "/serverkeys", 0755) < 0) {
	SILC_LOG_ERROR(("Couldn't create `%s' directory\n",
			SILC_ETCDIR "/serverkeys"));
	return TRUE;
      }
    } else {
      SILC_LOG_ERROR(("%s\n", strerror(errno)));
      return TRUE;
    }
  }

  /* Take fingerprint of the public key */
  fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
  SILC_LOG_DEBUG(("Received server %s (%s) port %d public key (%s)",
		  sock->hostname, sock->ip, sock->port, fingerprint));
  silc_free(fingerprint);

  /* Check whether this key already exists */
  if (stat(filename, &st) < 0) {
    /* We don't have it, then cache it. */
    SILC_LOG_DEBUG(("New public key from server"));

    silc_pkcs_save_public_key_data(filename, pk, pk_len,
				   SILC_PKCS_FILE_PEM);
    return TRUE;
  } else {
    /* The key already exists, verify it. */
    SilcPublicKey public_key;
    unsigned char *encpk;
    SilcUInt32 encpk_len;

    SILC_LOG_DEBUG(("We have the public key saved locally"));

    /* Load the key file */
    if (!silc_pkcs_load_public_key(filename, &public_key,
				   SILC_PKCS_FILE_PEM))
      if (!silc_pkcs_load_public_key(filename, &public_key,
				     SILC_PKCS_FILE_BIN)) {
	SILC_LOG_WARNING(("Could not load local copy of the %s (%s) port %d "
			  "server public key", sock->hostname, sock->ip,
			  sock->port));

	/* Save the key for future checking */
	unlink(filename);
	silc_pkcs_save_public_key_data(filename, pk, pk_len,
				       SILC_PKCS_FILE_PEM);
	return TRUE;
      }

    /* Encode the key data */
    encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
    if (!encpk) {
      SILC_LOG_WARNING(("Local copy of the server %s (%s) port %d public key "
			"is malformed", sock->hostname, sock->ip, sock->port));

      /* Save the key for future checking */
      unlink(filename);
      silc_pkcs_save_public_key_data(filename, pk, pk_len, SILC_PKCS_FILE_PEM);
      return TRUE;
    }

    if (memcmp(pk, encpk, encpk_len)) {
      SILC_LOG_WARNING(("%s (%s) port %d server public key does not match "
			"with local copy", sock->hostname, sock->ip,
			sock->port));
      SILC_LOG_WARNING(("It is possible that the key has expired or changed"));
      SILC_LOG_WARNING(("It is also possible that some one is performing "
			"man-in-the-middle attack"));
      SILC_LOG_WARNING(("Will not accept the server %s (%s) port %d public "
			"key",
			sock->hostname, sock->ip, sock->port));
      return FALSE;
    }

    /* Local copy matched */
    return TRUE;
  }
}

/* Callback that is called when we have received KE2 payload from
   responder. We try to verify the public key now. */

static void
silc_server_protocol_ke_verify_key(SilcSKE ske,
				   unsigned char *pk_data,
				   SilcUInt32 pk_len,
				   SilcSKEPKType pk_type,
				   void *context,
				   SilcSKEVerifyCbCompletion completion,
				   void *completion_context)
{
  SilcProtocol protocol = (SilcProtocol)context;
  SilcServerKEInternalContext *ctx =
    (SilcServerKEInternalContext *)protocol->context;
  SilcServer server = (SilcServer)ctx->server;

  SILC_LOG_DEBUG(("Verifying received public key"));

  if (silc_verify_public_key_internal(
		  server, ctx->sock,
		  (ctx->responder == FALSE ?
		   SILC_SOCKET_TYPE_ROUTER:
		   ctx->sconfig.ref_ptr ? SILC_SOCKET_TYPE_SERVER :
		   ctx->rconfig.ref_ptr ? SILC_SOCKET_TYPE_ROUTER :
		   SILC_SOCKET_TYPE_CLIENT),
		  pk_data, pk_len, pk_type))
    completion(ske, SILC_SKE_STATUS_OK, completion_context);
  else
    completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY,
	       completion_context);
}

/* Packet sending callback. This function is provided as packet sending
   routine to the Key Exchange functions. */

static void silc_server_protocol_ke_send_packet(SilcSKE ske,
						SilcBuffer packet,
						SilcPacketType type,
						void *context)
{
  SilcProtocol protocol = (SilcProtocol)context;
  SilcServerKEInternalContext *ctx =
    (SilcServerKEInternalContext *)protocol->context;
  SilcServer server = (SilcServer)ctx->server;

  /* Send the packet immediately */
  silc_server_packet_send(server, ske->sock,
			  type, 0, packet->data, packet->len, TRUE);
}

/* Sets the negotiated key material into use for particular connection. */

int silc_server_protocol_ke_set_keys(SilcServer server,
				     SilcSKE ske,
				     SilcSocketConnection sock,
				     SilcSKEKeyMaterial *keymat,
				     SilcCipher cipher,
				     SilcPKCS pkcs,
				     SilcHash hash,
				     SilcHmac hmac,
				     SilcSKEDiffieHellmanGroup group,
				     bool is_responder)
{
  SilcUnknownEntry conn_data;
  SilcIDListData idata;
  const char *cname = silc_cipher_get_name(cipher);

  SILC_LOG_DEBUG(("Setting new keys into use"));

  conn_data = silc_calloc(1, sizeof(*conn_data));
  idata = (SilcIDListData)conn_data;

  /* Allocate cipher to be used in the communication */
  if (!silc_cipher_alloc((char *)cname, &idata->send_key)) {
    silc_free(conn_data);
    SILC_LOG_ERROR(("Cannot allocate algorithm: %s", cname));
    return FALSE;
  }
  if (!silc_cipher_alloc((char *)cname, &idata->receive_key)) {
    silc_free(conn_data);
    SILC_LOG_ERROR(("Cannot allocate algorithm: %s", cname));
    return FALSE;
  }

  if (!silc_hmac_alloc((char *)silc_hmac_get_name(hmac), NULL,
		       &idata->hmac_send)) {
    silc_cipher_free(idata->send_key);
    silc_cipher_free(idata->receive_key);
    silc_free(conn_data);
    SILC_LOG_ERROR(("Cannot allocate algorithm: %s",
		    silc_hmac_get_name(hmac)));
    return FALSE;
  }

  if (!silc_hmac_alloc((char *)silc_hmac_get_name(hmac), NULL,
		       &idata->hmac_receive)) {
    silc_cipher_free(idata->send_key);
    silc_cipher_free(idata->receive_key);
    silc_hmac_free(idata->hmac_send);
    silc_free(conn_data);
    SILC_LOG_ERROR(("Cannot allocate algorithm: %s",
		    silc_hmac_get_name(hmac)));
    return FALSE;
  }

  if (is_responder == TRUE) {
    silc_cipher_set_key(idata->send_key, keymat->receive_enc_key,
			keymat->enc_key_len);
    silc_cipher_set_iv(idata->send_key, keymat->receive_iv);
    silc_cipher_set_key(idata->receive_key, keymat->send_enc_key,
			keymat->enc_key_len);
    silc_cipher_set_iv(idata->receive_key, keymat->send_iv);
    silc_hmac_set_key(idata->hmac_send, keymat->receive_hmac_key,
		      keymat->hmac_key_len);
    silc_hmac_set_key(idata->hmac_receive, keymat->send_hmac_key,
		      keymat->hmac_key_len);
  } else {
    silc_cipher_set_key(idata->send_key, keymat->send_enc_key,
			keymat->enc_key_len);
    silc_cipher_set_iv(idata->send_key, keymat->send_iv);
    silc_cipher_set_key(idata->receive_key, keymat->receive_enc_key,
			keymat->enc_key_len);
    silc_cipher_set_iv(idata->receive_key, keymat->receive_iv);
    silc_hmac_set_key(idata->hmac_send, keymat->send_hmac_key,
		      keymat->hmac_key_len);
    silc_hmac_set_key(idata->hmac_receive, keymat->receive_hmac_key,
		      keymat->hmac_key_len);
  }

  idata->rekey = silc_calloc(1, sizeof(*idata->rekey));
  idata->rekey->send_enc_key = silc_memdup(keymat->send_enc_key,
					   keymat->enc_key_len / 8);
  idata->rekey->enc_key_len = keymat->enc_key_len / 8;

  if (ske->prop->flags & SILC_SKE_SP_FLAG_PFS)
    idata->rekey->pfs = TRUE;
  idata->rekey->ske_group = silc_ske_group_get_number(group);

  /* Save the hash */
  if (!silc_hash_alloc(silc_hash_get_name(hash), &idata->hash)) {
    silc_cipher_free(idata->send_key);
    silc_cipher_free(idata->receive_key);
    silc_hmac_free(idata->hmac_send);
    silc_hmac_free(idata->hmac_receive);
    silc_free(conn_data);
    SILC_LOG_ERROR(("Cannot allocate algorithm: %s",
		    silc_hash_get_name(hash)));
    return FALSE;
  }

  /* Save the remote host's public key */
  silc_pkcs_public_key_decode(ske->ke1_payload->pk_data,
			      ske->ke1_payload->pk_len, &idata->public_key);
  if (ske->prop->flags & SILC_SKE_SP_FLAG_MUTUAL)
    silc_hash_make(server->sha1hash, ske->ke1_payload->pk_data,
		   ske->ke1_payload->pk_len, idata->fingerprint);

  sock->user_data = (void *)conn_data;

  SILC_LOG_INFO(("%s (%s) security properties: %s %s %s %s",
		 sock->hostname, sock->ip,
		 silc_cipher_get_name(idata->send_key),
		 (char *)silc_hmac_get_name(idata->hmac_send),
		 silc_hash_get_name(idata->hash),
		 ske->prop->flags & SILC_SKE_SP_FLAG_PFS ? "PFS" : ""));

  return TRUE;
}

/* Check remote host version string */

SilcSKEStatus silc_ske_check_version(SilcSKE ske, unsigned char *version,
				     SilcUInt32 len, void *context)
{
  SilcUInt32 l_protocol_version = 0, r_protocol_version = 0;

  SILC_LOG_INFO(("%s (%s) is version %s", ske->sock->hostname,
		 ske->sock->ip, version));

  if (!silc_parse_version_string(version, &r_protocol_version, NULL, NULL,
				 NULL, NULL)) {
    SILC_LOG_ERROR(("%s (%s) %s is not allowed/supported version",
		    ske->sock->hostname, ske->sock->ip, version));
    return SILC_SKE_STATUS_BAD_VERSION;
  }

  if (!silc_parse_version_string(silc_version_string,
				 &l_protocol_version, NULL, NULL,
				 NULL, NULL)) {
    SILC_LOG_ERROR(("%s (%s) %s is not allowed/supported version",
		    ske->sock->hostname, ske->sock->ip, version));
    return SILC_SKE_STATUS_BAD_VERSION;
  }

  /* If remote is too new, don't connect */
  if (l_protocol_version < r_protocol_version) {
    SILC_LOG_ERROR(("%s (%s) %s is not allowed/supported version",
		    ske->sock->hostname, ske->sock->ip, version));
    return SILC_SKE_STATUS_BAD_VERSION;
  }

  ske->sock->version = r_protocol_version;

  return SILC_SKE_STATUS_OK;
}

/* Callback that is called by the SKE to indicate that it is safe to
   continue the execution of the protocol. This is used only if we are
   initiator.  Is given as argument to the silc_ske_initiator_finish or
   silc_ske_responder_phase_2 functions. This is called due to the fact
   that the public key verification process is asynchronous and we must
   not continue the protocl until the public key has been verified and
   this callback is called. */

static void silc_server_protocol_ke_continue(SilcSKE ske, void *context)
{
  SilcProtocol protocol = (SilcProtocol)context;
  SilcServerKEInternalContext *ctx =
    (SilcServerKEInternalContext *)protocol->context;
  SilcServer server = (SilcServer)ctx->server;

  if (ske->status != SILC_SKE_STATUS_OK) {
    SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
		    silc_ske_map_status(ske->status), ctx->sock->hostname,
		    ctx->sock->ip));

    protocol->state = SILC_PROTOCOL_STATE_ERROR;
    silc_protocol_execute(protocol, server->schedule, 0, 300000);
    return;
  }

  /* Send Ok to the other end. We will end the protocol as responder
     sends Ok to us when we will take the new keys into use. */
  if (ctx->responder == FALSE) {
    SILC_LOG_DEBUG(("Ending key exchange protocol"));
    silc_ske_end(ctx->ske);

    /* End the protocol on the next round */
    protocol->state = SILC_PROTOCOL_STATE_END;
  }

  /* Advance protocol state and call the next state if we are responder.
     This happens when this callback was sent to silc_ske_responder_phase_2
     function. */
  if (ctx->responder == TRUE) {
    protocol->state++;
    silc_protocol_execute(protocol, server->schedule, 0, 100000);
  }
}

/* Performs key exchange protocol. This is used for both initiator
   and responder key exchange. This is performed always when accepting
   new connection to the server. This may be called recursively. */

SILC_TASK_CALLBACK(silc_server_protocol_key_exchange)
{
  SilcProtocol protocol = (SilcProtocol)context;
  SilcServerKEInternalContext *ctx =
    (SilcServerKEInternalContext *)protocol->context;
  SilcServer server = (SilcServer)ctx->server;
  SilcSKEStatus status = SILC_SKE_STATUS_OK;

  if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN)
    protocol->state = SILC_PROTOCOL_STATE_START;

  SILC_LOG_DEBUG(("Current protocol state %d", protocol->state));

  switch(protocol->state) {
  case SILC_PROTOCOL_STATE_START:
    {
      /*
       * Start protocol
       */
      SilcSKE ske;

      /* Allocate Key Exchange object */
      ctx->ske = ske = silc_ske_alloc(server->rng, server);

      silc_ske_set_callbacks(ske, silc_server_protocol_ke_send_packet, NULL,
			     silc_server_protocol_ke_verify_key,
			     silc_server_protocol_ke_continue,
			     silc_ske_check_version, context);

      if (ctx->responder == TRUE) {
	if (!ctx->packet) {
	  SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
			  silc_ske_map_status(status), ctx->sock->hostname,
			  ctx->sock->ip));

	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	/* Start the key exchange by processing the received security
	   properties packet from initiator. */
	SILC_LOG_DEBUG(("Process security property list (KE)"));
	status = silc_ske_responder_start(ske, ctx->rng, ctx->sock,
					  silc_version_string,
					  ctx->packet->buffer, ctx->flags);
      } else {
	SilcSKEStartPayload *start_payload;

	SILC_LOG_DEBUG(("Send security property list (KE)"));

	/* Assemble security properties. */
	silc_ske_assemble_security_properties(ske, ctx->flags,
					      silc_version_string,
					      &start_payload);

	/* Start the key exchange by sending our security properties
	   to the remote end. */
	status = silc_ske_initiator_start(ske, ctx->rng, ctx->sock,
					  start_payload);
      }

      /* Return now if the procedure is pending. */
      if (status == SILC_SKE_STATUS_PENDING)
	return;

      if (status != SILC_SKE_STATUS_OK) {
	SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
			silc_ske_map_status(status), ctx->sock->hostname,
			ctx->sock->ip));

	protocol->state = SILC_PROTOCOL_STATE_ERROR;
	silc_protocol_execute(protocol, server->schedule, 0, 300000);
	return;
      }

      /* Advance protocol state and call the next state if we are responder */
      protocol->state++;
      if (ctx->responder == TRUE)
	silc_protocol_execute(protocol, server->schedule, 0, 100000);
    }
    break;
  case 2:
    {
      /*
       * Phase 1
       */
      if (ctx->responder == TRUE) {
	/* Sends the selected security properties to the initiator. */
	SILC_LOG_DEBUG(("Send security property list reply (KE)"));
	status = silc_ske_responder_phase_1(ctx->ske);
      } else {
	if (!ctx->packet) {
	  SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
			  silc_ske_map_status(status), ctx->sock->hostname,
			  ctx->sock->ip));

	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	/* Call Phase-1 function. This processes the Key Exchange Start
	   paylaod reply we just got from the responder. The callback
	   function will receive the processed payload where we will
	   save it. */
	SILC_LOG_DEBUG(("Process security property list reply (KE)"));
	status = silc_ske_initiator_phase_1(ctx->ske, ctx->packet->buffer);
      }

      /* Return now if the procedure is pending. */
      if (status == SILC_SKE_STATUS_PENDING)
	return;

      if (status != SILC_SKE_STATUS_OK) {
	SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
			silc_ske_map_status(status), ctx->sock->hostname,
			ctx->sock->ip));

	protocol->state = SILC_PROTOCOL_STATE_ERROR;
	silc_protocol_execute(protocol, server->schedule, 0, 300000);
	return;
      }

      /* Advance protocol state and call next state if we are initiator */
      protocol->state++;
      if (ctx->responder == FALSE)
	silc_protocol_execute(protocol, server->schedule, 0, 100000);
    }
    break;
  case 3:
    {
      /*
       * Phase 2
       */
      if (ctx->responder == TRUE) {
	if (!ctx->packet) {
	  SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
			  silc_ske_map_status(status), ctx->sock->hostname,
			  ctx->sock->ip));

	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	/* Process the received Key Exchange 1 Payload packet from
	   the initiator. This also creates our parts of the Diffie
	   Hellman algorithm. The silc_server_protocol_ke_continue
	   will be called after the public key has been verified. */
	SILC_LOG_DEBUG(("Process KE1 packet"));
	status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer);
      } else {
	/* Call the Phase-2 function. This creates Diffie Hellman
	   key exchange parameters and sends our public part inside
	   Key Exhange 1 Payload to the responder. */
	SILC_LOG_DEBUG(("Send KE1 packet"));
	status = silc_ske_initiator_phase_2(ctx->ske,
					    server->public_key,
					    server->private_key,
					    SILC_SKE_PK_TYPE_SILC);
	protocol->state++;
      }

      /* Return now if the procedure is pending. */
      if (status == SILC_SKE_STATUS_PENDING)
	return;

      if (status != SILC_SKE_STATUS_OK) {
	SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
			silc_ske_map_status(status), ctx->sock->hostname,
			ctx->sock->ip));

	protocol->state = SILC_PROTOCOL_STATE_ERROR;
	silc_protocol_execute(protocol, server->schedule, 0, 300000);
	return;
      }
    }
    break;
  case 4:
    {
      /*
       * Finish protocol
       */
      if (ctx->responder == TRUE) {
	/* This creates the key exchange material and sends our
	   public parts to the initiator inside Key Exchange 2 Payload. */
	SILC_LOG_DEBUG(("Process KE2 packet"));
	status = silc_ske_responder_finish(ctx->ske,
					   server->public_key,
					   server->private_key,
					   SILC_SKE_PK_TYPE_SILC);

	/* End the protocol on the next round */
	protocol->state = SILC_PROTOCOL_STATE_END;
      } else {
	if (!ctx->packet) {
	  SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
			  silc_ske_map_status(status), ctx->sock->hostname,
			  ctx->sock->ip));

	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	/* Finish the protocol. This verifies the Key Exchange 2 payload
	   sent by responder. The silc_server_protocol_ke_continue will
	   be called after the public key has been verified. */
	SILC_LOG_DEBUG(("Send KE2 packet"));
	status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer);
      }

      /* Return now if the procedure is pending. */
      if (status == SILC_SKE_STATUS_PENDING)
	return;

      if (status != SILC_SKE_STATUS_OK) {
	SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
			silc_ske_map_status(status), ctx->sock->hostname,
			ctx->sock->ip));

	protocol->state = SILC_PROTOCOL_STATE_ERROR;
	silc_protocol_execute(protocol, server->schedule, 0, 300000);
	return;
      }
    }
    break;

  case SILC_PROTOCOL_STATE_END:
    {
      /*
       * End protocol
       */
      SilcSKEKeyMaterial *keymat;
      int key_len = silc_cipher_get_key_len(ctx->ske->prop->cipher);
      int hash_len = silc_hash_len(ctx->ske->prop->hash);

      SILC_LOG_DEBUG(("Process computed key material"));

      /* Process the key material */
      keymat = silc_calloc(1, sizeof(*keymat));
      status = silc_ske_process_key_material(ctx->ske, 16, key_len, hash_len,
					     keymat);
      if (status != SILC_SKE_STATUS_OK) {
	SILC_LOG_ERROR(("Error during Key Exchange protocol: "
			"could not process key material"));

	protocol->state = SILC_PROTOCOL_STATE_ERROR;
	silc_protocol_execute(protocol, server->schedule, 0, 300000);
	silc_ske_free_key_material(keymat);
	return;
      }
      ctx->keymat = keymat;

      /* Send Ok to the other end if we are responder. If we are initiator
	 we have sent this already. */
      if (ctx->responder == TRUE) {
	SILC_LOG_DEBUG(("Ending key exchange protocol"));
	silc_ske_end(ctx->ske);
      }

      /* Unregister the timeout task since the protocol has ended.
	 This was the timeout task to be executed if the protocol is
	 not completed fast enough. */
      if (ctx->timeout_task)
	silc_schedule_task_del(server->schedule, ctx->timeout_task);

      /* Assure that after calling final callback there cannot be pending
	 executions for this protocol anymore. This just unregisters any
	 timeout callbacks for this protocol. */
      silc_protocol_cancel(protocol, server->schedule);

      /* Call the final callback */
      if (protocol->final_callback)
	silc_protocol_execute_final(protocol, server->schedule);
      else
	silc_protocol_free(protocol);
    }
    break;

  case SILC_PROTOCOL_STATE_ERROR:
    /*
     * Error occured
     */

    /* Send abort notification */
    silc_ske_abort(ctx->ske, ctx->ske->status);

    /* Unregister the timeout task since the protocol has ended.
       This was the timeout task to be executed if the protocol is
       not completed fast enough. */
    if (ctx->timeout_task)
      silc_schedule_task_del(server->schedule, ctx->timeout_task);

    /* Assure that after calling final callback there cannot be pending
       executions for this protocol anymore. This just unregisters any
       timeout callbacks for this protocol. */
    silc_protocol_cancel(protocol, server->schedule);

    /* On error the final callback is always called. */
    if (protocol->final_callback)
      silc_protocol_execute_final(protocol, server->schedule);
    else
      silc_protocol_free(protocol);
    break;

  case SILC_PROTOCOL_STATE_FAILURE:
    /*
     * We have received failure from remote
     */

    /* Unregister the timeout task since the protocol has ended.
       This was the timeout task to be executed if the protocol is
       not completed fast enough. */
    if (ctx->timeout_task)
      silc_schedule_task_del(server->schedule, ctx->timeout_task);

    /* Assure that after calling final callback there cannot be pending
       executions for this protocol anymore. This just unregisters any
       timeout callbacks for this protocol. */
    silc_protocol_cancel(protocol, server->schedule);

    /* On error the final callback is always called. */
    if (protocol->final_callback)
      silc_protocol_execute_final(protocol, server->schedule);
    else
      silc_protocol_free(protocol);
    break;

  case SILC_PROTOCOL_STATE_UNKNOWN:
    break;
  }
}

/*
 * Connection Authentication protocol functions
 */

static int
silc_server_password_authentication(SilcServer server, char *local_auth,
				    char *remote_auth)
{
  if (!remote_auth || !local_auth || strlen(local_auth) != strlen(remote_auth))
    return FALSE;

  if (!memcmp(remote_auth, local_auth, strlen(local_auth)))
    return TRUE;

  return FALSE;
}

static int
silc_server_public_key_authentication(SilcServer server,
				      SilcPublicKey pub_key,
				      unsigned char *sign,
				      SilcUInt32 sign_len,
				      SilcSKE ske)
{
  SilcPKCS pkcs;
  int len;
  SilcBuffer auth;

  if (!pub_key || !sign)
    return FALSE;

  silc_pkcs_alloc(pub_key->name, &pkcs);
  if (!silc_pkcs_public_key_set(pkcs, pub_key)) {
    silc_pkcs_free(pkcs);
    return FALSE;
  }

  /* Make the authentication data. Protocol says it is HASH plus
     KE Start Payload. */
  len = ske->hash_len + ske->start_payload_copy->len;
  auth = silc_buffer_alloc(len);
  silc_buffer_pull_tail(auth, len);
  silc_buffer_format(auth,
		     SILC_STR_UI_XNSTRING(ske->hash, ske->hash_len),
		     SILC_STR_UI_XNSTRING(ske->start_payload_copy->data,
					  ske->start_payload_copy->len),
		     SILC_STR_END);

  /* Verify signature */
  if (silc_pkcs_verify_with_hash(pkcs, ske->prop->hash, sign, sign_len,
				 auth->data, auth->len)) {
    silc_pkcs_free(pkcs);
    silc_buffer_free(auth);
    return TRUE;
  }

  silc_pkcs_free(pkcs);
  silc_buffer_free(auth);
  return FALSE;
}

static int
silc_server_get_public_key_auth(SilcServer server,
				unsigned char **auth_data,
				SilcUInt32 *auth_data_len,
				SilcSKE ske)
{
  int len;
  SilcPKCS pkcs;
  SilcBuffer auth;

  pkcs = server->pkcs;

  /* Make the authentication data. Protocol says it is HASH plus
     KE Start Payload. */
  len = ske->hash_len + ske->start_payload_copy->len;
  auth = silc_buffer_alloc(len);
  silc_buffer_pull_tail(auth, len);
  silc_buffer_format(auth,
		     SILC_STR_UI_XNSTRING(ske->hash, ske->hash_len),
		     SILC_STR_UI_XNSTRING(ske->start_payload_copy->data,
					  ske->start_payload_copy->len),
		     SILC_STR_END);

  *auth_data = silc_calloc((silc_pkcs_get_key_len(pkcs) / 8) + 1,
			   sizeof(**auth_data));
  if (silc_pkcs_sign_with_hash(pkcs, ske->prop->hash, auth->data,
			       auth->len, *auth_data, auth_data_len)) {
    silc_buffer_free(auth);
    return TRUE;
  }

  SILC_LOG_ERROR(("Error computing signature"));

  silc_free(*auth_data);
  silc_buffer_free(auth);
  return FALSE;
}

/* Function that actually performs the authentication to the remote. This
   supports both passphrase and public key authentication. */

static bool
silc_server_get_authentication(SilcServerConnAuthInternalContext *ctx,
			       char *local_passphrase,
			       SilcHashTable local_publickeys,
			       unsigned char *remote_auth,
			       SilcUInt32 remote_auth_len)
{
  SilcServer server = (SilcServer)ctx->server;
  SilcSKE ske = ctx->ske;
  bool result = FALSE;

  /* If we don't have authentication data set at all we do not require
     authentication at all */
  if (!local_passphrase && (!local_publickeys ||
			    !silc_hash_table_count(local_publickeys))) {
    SILC_LOG_DEBUG(("No authentication required"));
    return TRUE;
  }

  /* If both passphrase and public key is provided then we'll try both of
     them and see which one of them authenticates.  If only one of them is
     set, then try only that. */

  /* Try first passphrase (as it is faster to check) */
  if (local_passphrase) {
    SILC_LOG_DEBUG(("Password authentication"));
    result = silc_server_password_authentication(server, local_passphrase,
						 remote_auth);
  }

  /* Try public key authenetication */
  if (!result && local_publickeys) {
    SilcPublicKey cached_key;
    SilcPublicKey remote_key =
      ((SilcIDListData)ctx->sock->user_data)->public_key;

    SILC_LOG_DEBUG(("Public key authentication"));

    /* Find the public key to be used in authentication */
    cached_key = silc_server_find_public_key(server, local_publickeys,
					     remote_key);
    if (!cached_key)
      return FALSE;

    result = silc_server_public_key_authentication(server, cached_key,
						   remote_auth,
						   remote_auth_len, ske);
  }

  SILC_LOG_DEBUG(("Authentication %s", result ? "successful" : "failed"));

  return result;
}

/* Performs connection authentication protocol. If responder, we
   authenticate the remote data received. If initiator, we will send
   authentication data to the remote end. */

SILC_TASK_CALLBACK(silc_server_protocol_connection_auth)
{
  SilcProtocol protocol = (SilcProtocol)context;
  SilcServerConnAuthInternalContext *ctx =
    (SilcServerConnAuthInternalContext *)protocol->context;
  SilcServer server = (SilcServer)ctx->server;

  if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN)
    protocol->state = SILC_PROTOCOL_STATE_START;

  SILC_LOG_DEBUG(("Current protocol state %d", protocol->state));

  switch(protocol->state) {
  case SILC_PROTOCOL_STATE_START:
    {
      /*
       * Start protocol.
       */

      if (ctx->responder == TRUE) {
	/*
	 * We are receiving party
	 */
	int ret;
	SilcUInt16 payload_len;
	SilcUInt16 conn_type;
	unsigned char *auth_data = NULL;

	SILC_LOG_INFO(("Performing authentication protocol for %s (%s)",
		       ctx->sock->hostname, ctx->sock->ip));

	if (!ctx->packet) {
	  SILC_LOG_ERROR(("Bad authentication protocol request"));
	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	/* Parse the received authentication data packet. The received
	   payload is Connection Auth Payload. */
	ret = silc_buffer_unformat(ctx->packet->buffer,
				   SILC_STR_UI_SHORT(&payload_len),
				   SILC_STR_UI_SHORT(&conn_type),
				   SILC_STR_END);
	if (ret == -1) {
	  SILC_LOG_ERROR(("Bad payload in authentication packet"));
	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	if (payload_len != ctx->packet->buffer->len) {
	  SILC_LOG_ERROR(("Bad payload length in authentication packet"));
	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	payload_len -= 4;

	if (conn_type < SILC_SOCKET_TYPE_CLIENT ||
	    conn_type > SILC_SOCKET_TYPE_ROUTER) {
	  SILC_LOG_ERROR(("Bad connection type (%d) in authentication packet",
			  conn_type));
	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	if (payload_len > 0) {
	  /* Get authentication data */
	  silc_buffer_pull(ctx->packet->buffer, 4);
	  ret = silc_buffer_unformat(ctx->packet->buffer,
				     SILC_STR_UI_XNSTRING_ALLOC(&auth_data,
								payload_len),
				     SILC_STR_END);
	  if (ret == -1) {
	    SILC_LOG_DEBUG(("Bad payload in authentication payload"));
	    protocol->state = SILC_PROTOCOL_STATE_ERROR;
	    silc_protocol_execute(protocol, server->schedule, 0, 300000);
	    return;
	  }
	}

	/*
	 * Check the remote connection type and make sure that we have
	 * configured this connection. If we haven't allowed this connection
	 * the authentication must be failed.
	 */

	SILC_LOG_DEBUG(("Remote connection type %d", conn_type));

	/* Remote end is client */
	if (conn_type == SILC_SOCKET_TYPE_CLIENT) {
	  SilcServerConfigClient *client = ctx->cconfig.ref_ptr;

	  if (client) {
	    ret = silc_server_get_authentication(ctx, client->passphrase,
						 client->publickeys,
						 auth_data, payload_len);
	    if (!ret) {
	      /* Authentication failed */
	      SILC_LOG_ERROR(("Authentication failed"));
	      silc_free(auth_data);
	      protocol->state = SILC_PROTOCOL_STATE_ERROR;
	      silc_protocol_execute(protocol, server->schedule, 0, 300000);
	      return;
	    }
	  } else {
	    SILC_LOG_ERROR(("Remote client connection not configured"));
	    SILC_LOG_ERROR(("Authentication failed"));
	    silc_free(auth_data);
	    protocol->state = SILC_PROTOCOL_STATE_ERROR;
	    silc_protocol_execute(protocol, server->schedule,
				  0, 300000);
	    return;
	  }
	}

	/* Remote end is server */
	if (conn_type == SILC_SOCKET_TYPE_SERVER) {
	  SilcServerConfigServer *serv = ctx->sconfig.ref_ptr;

	  if (serv) {
	    ret = silc_server_get_authentication(ctx, serv->passphrase,
						 serv->publickeys,
						 auth_data, payload_len);
	    if (!ret) {
	      /* Authentication failed */
	      SILC_LOG_ERROR(("Authentication failed"));
	      silc_free(auth_data);
	      protocol->state = SILC_PROTOCOL_STATE_ERROR;
	      silc_protocol_execute(protocol, server->schedule, 0, 300000);
	      return;
	    }
	  } else {
	    SILC_LOG_ERROR(("Remote server connection not configured"));
	    SILC_LOG_ERROR(("Authentication failed"));
	    protocol->state = SILC_PROTOCOL_STATE_ERROR;
	    silc_protocol_execute(protocol, server->schedule,
				  0, 300000);
	    silc_free(auth_data);
	    return;
	  }
	}

	/* Remote end is router */
	if (conn_type == SILC_SOCKET_TYPE_ROUTER) {
	  SilcServerConfigRouter *serv = ctx->rconfig.ref_ptr;

	  if (serv) {
	    ret = silc_server_get_authentication(ctx, serv->passphrase,
						 serv->publickeys,
						 auth_data, payload_len);
	    if (!ret) {
	      /* Authentication failed */
	      SILC_LOG_ERROR(("Authentication failed"));
	      silc_free(auth_data);
	      protocol->state = SILC_PROTOCOL_STATE_ERROR;
	      silc_protocol_execute(protocol, server->schedule, 0, 300000);
	      return;
	    }
	  } else {
	    SILC_LOG_ERROR(("Remote router connection not configured"));
	    SILC_LOG_ERROR(("Authentication failed"));
	    silc_free(auth_data);
	    protocol->state = SILC_PROTOCOL_STATE_ERROR;
	    silc_protocol_execute(protocol, server->schedule,
				  0, 300000);
	    return;
	  }
	}

	silc_free(auth_data);

	/* Save connection type. This is later used to create the
	   ID for the connection. */
	ctx->conn_type = conn_type;

	/* Advance protocol state. */
	protocol->state = SILC_PROTOCOL_STATE_END;
	silc_protocol_execute(protocol, server->schedule, 0, 0);

      } else {
	/*
	 * We are initiator. We are authenticating ourselves to a
	 * remote server. We will send the authentication data to the
	 * other end for verify.
	 */
	SilcBuffer packet;
	int payload_len = 0;
	unsigned char *auth_data = NULL;
	SilcUInt32 auth_data_len = 0;

	switch(ctx->auth_meth) {
	case SILC_AUTH_NONE:
	  /* No authentication required */
	  break;

	case SILC_AUTH_PASSWORD:
	  /* Password authentication */
	  if (ctx->auth_data && ctx->auth_data_len) {
	    auth_data = strdup(ctx->auth_data);
	    auth_data_len = ctx->auth_data_len;
	    break;
	  }
	  break;

	case SILC_AUTH_PUBLIC_KEY:
	  {
	    /* Public key authentication */
	    silc_server_get_public_key_auth(server, &auth_data, &auth_data_len,
					    ctx->ske);
	    break;
	  }
	}

	payload_len = 4 + auth_data_len;
	packet = silc_buffer_alloc(payload_len);
	silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet));
	silc_buffer_format(packet,
			   SILC_STR_UI_SHORT(payload_len),
			   SILC_STR_UI_SHORT(server->server_type
					      == SILC_SERVER ?
					      SILC_SOCKET_TYPE_SERVER :
					      SILC_SOCKET_TYPE_ROUTER),
			   SILC_STR_UI_XNSTRING(auth_data, auth_data_len),
			   SILC_STR_END);

	/* Send the packet to server */
	silc_server_packet_send(server, ctx->sock,
				SILC_PACKET_CONNECTION_AUTH, 0,
				packet->data, packet->len, TRUE);

	if (auth_data) {
	  memset(auth_data, 0, auth_data_len);
	  silc_free(auth_data);
	}
	silc_buffer_free(packet);

	/* Next state is end of protocol */
	protocol->state = SILC_PROTOCOL_STATE_END;
      }
    }
    break;

  case SILC_PROTOCOL_STATE_END:
    {
      /*
       * End protocol
       */
      unsigned char ok[4];

      SILC_PUT32_MSB(SILC_AUTH_OK, ok);

      /* Authentication successful */
      silc_server_packet_send(server, ctx->sock, SILC_PACKET_SUCCESS,
			      0, ok, 4, TRUE);

      /* Unregister the timeout task since the protocol has ended.
	 This was the timeout task to be executed if the protocol is
	 not completed fast enough. */
      if (ctx->timeout_task)
	silc_schedule_task_del(server->schedule, ctx->timeout_task);

      /* Assure that after calling final callback there cannot be pending
	 executions for this protocol anymore. This just unregisters any
	 timeout callbacks for this protocol. */
      silc_protocol_cancel(protocol, server->schedule);

      /* Protocol has ended, call the final callback */
      if (protocol->final_callback)
	silc_protocol_execute_final(protocol, server->schedule);
      else
	silc_protocol_free(protocol);
    }
    break;
  case SILC_PROTOCOL_STATE_ERROR:
    {
      /*
       * Error. Send notify to remote.
       */
      unsigned char error[4];

      /* Authentication failed */
      SILC_PUT32_MSB(SILC_AUTH_FAILED, error);
      silc_server_packet_send(server, ctx->sock, SILC_PACKET_FAILURE,
			      0, error, 4, TRUE);

      /* Unregister the timeout task since the protocol has ended.
	 This was the timeout task to be executed if the protocol is
	 not completed fast enough. */
      if (ctx->timeout_task)
	silc_schedule_task_del(server->schedule, ctx->timeout_task);

      /* Assure that after calling final callback there cannot be pending
	 executions for this protocol anymore. This just unregisters any
	 timeout callbacks for this protocol. */
      silc_protocol_cancel(protocol, server->schedule);

      /* On error the final callback is always called. */
      if (protocol->final_callback)
	silc_protocol_execute_final(protocol, server->schedule);
      else
	silc_protocol_free(protocol);
    }
    break;

  case SILC_PROTOCOL_STATE_FAILURE:
    /*
     * We have received failure from remote
     */

    SILC_LOG_ERROR(("Received Authentication Failure"));

    /* Unregister the timeout task since the protocol has ended.
       This was the timeout task to be executed if the protocol is
       not completed fast enough. */
    if (ctx->timeout_task)
      silc_schedule_task_del(server->schedule, ctx->timeout_task);

    /* Assure that after calling final callback there cannot be pending
       executions for this protocol anymore. This just unregisters any
       timeout callbacks for this protocol. */
    silc_protocol_cancel(protocol, server->schedule);

    /* On error the final callback is always called. */
    if (protocol->final_callback)
      silc_protocol_execute_final(protocol, server->schedule);
    else
      silc_protocol_free(protocol);
    break;

  case SILC_PROTOCOL_STATE_UNKNOWN:
    break;
  }
}

/*
 * Re-key protocol routines
 */

/* Actually takes the new keys into use. */

static void
silc_server_protocol_rekey_validate(SilcServer server,
				    SilcServerRekeyInternalContext *ctx,
				    SilcIDListData idata,
				    SilcSKEKeyMaterial *keymat,
				    bool send)
{
  if (ctx->responder == TRUE) {
    if (send) {
      silc_cipher_set_key(idata->send_key, keymat->receive_enc_key,
			  keymat->enc_key_len);
      silc_cipher_set_iv(idata->send_key, keymat->receive_iv);
      silc_hmac_set_key(idata->hmac_send, keymat->receive_hmac_key,
			keymat->hmac_key_len);
    } else {
      silc_cipher_set_key(idata->receive_key, keymat->send_enc_key,
			  keymat->enc_key_len);
      silc_cipher_set_iv(idata->receive_key, keymat->send_iv);
      silc_hmac_set_key(idata->hmac_receive, keymat->send_hmac_key,
			keymat->hmac_key_len);
    }
  } else {
    if (send) {
      silc_cipher_set_key(idata->send_key, keymat->send_enc_key,
			  keymat->enc_key_len);
      silc_cipher_set_iv(idata->send_key, keymat->send_iv);
      silc_hmac_set_key(idata->hmac_send, keymat->send_hmac_key,
			keymat->hmac_key_len);
    } else {
      silc_cipher_set_key(idata->receive_key, keymat->receive_enc_key,
			  keymat->enc_key_len);
      silc_cipher_set_iv(idata->receive_key, keymat->receive_iv);
      silc_hmac_set_key(idata->hmac_receive, keymat->receive_hmac_key,
			keymat->hmac_key_len);
    }
  }

  /* Save the current sending encryption key */
  if (!send) {
    memset(idata->rekey->send_enc_key, 0, idata->rekey->enc_key_len);
    silc_free(idata->rekey->send_enc_key);
    idata->rekey->send_enc_key = silc_memdup(keymat->send_enc_key,
					     keymat->enc_key_len / 8);
    idata->rekey->enc_key_len = keymat->enc_key_len / 8;
  }
}

/* This function actually re-generates (when not using PFS) the keys and
   takes them into use. */

void silc_server_protocol_rekey_generate(SilcServer server,
					 SilcServerRekeyInternalContext *ctx,
					 bool send)
{
  SilcIDListData idata = (SilcIDListData)ctx->sock->user_data;
  SilcSKEKeyMaterial *keymat;
  SilcUInt32 key_len = silc_cipher_get_key_len(idata->send_key);
  SilcUInt32 hash_len = silc_hash_len(idata->hash);

  SILC_LOG_DEBUG(("Generating new %s session keys (no PFS)",
		  send ? "sending" : "receiving"));

  /* Generate the new key */
  keymat = silc_calloc(1, sizeof(*keymat));
  silc_ske_process_key_material_data(idata->rekey->send_enc_key,
				     idata->rekey->enc_key_len,
				     16, key_len, hash_len,
				     idata->hash, keymat);

  /* Set the keys into use */
  silc_server_protocol_rekey_validate(server, ctx, idata, keymat, send);

  silc_ske_free_key_material(keymat);
}

/* This function actually re-generates (with PFS) the keys and
   takes them into use. */

void
silc_server_protocol_rekey_generate_pfs(SilcServer server,
					SilcServerRekeyInternalContext *ctx,
					bool send)
{
  SilcIDListData idata = (SilcIDListData)ctx->sock->user_data;
  SilcSKEKeyMaterial *keymat;
  SilcUInt32 key_len = silc_cipher_get_key_len(idata->send_key);
  SilcUInt32 hash_len = silc_hash_len(idata->hash);
  unsigned char *tmpbuf;
  SilcUInt32 klen;

  SILC_LOG_DEBUG(("Generating new %s session keys (with PFS)",
		  send ? "sending" : "receiving"));

  /* Encode KEY to binary data */
  tmpbuf = silc_mp_mp2bin(ctx->ske->KEY, 0, &klen);

  /* Generate the new key */
  keymat = silc_calloc(1, sizeof(*keymat));
  silc_ske_process_key_material_data(tmpbuf, klen, 16, key_len, hash_len,
				     idata->hash, keymat);

  /* Set the keys into use */
  silc_server_protocol_rekey_validate(server, ctx, idata, keymat, send);

  memset(tmpbuf, 0, klen);
  silc_free(tmpbuf);
  silc_ske_free_key_material(keymat);
}

/* Packet sending callback. This function is provided as packet sending
   routine to the Key Exchange functions. */

static void
silc_server_protocol_rekey_send_packet(SilcSKE ske,
				       SilcBuffer packet,
				       SilcPacketType type,
				       void *context)
{
  SilcProtocol protocol = (SilcProtocol)context;
  SilcServerRekeyInternalContext *ctx =
    (SilcServerRekeyInternalContext *)protocol->context;
  SilcServer server = (SilcServer)ctx->server;

  /* Send the packet immediately */
  silc_server_packet_send(server, ctx->sock,
			  type, 0, packet->data, packet->len, FALSE);
}

/* Performs re-key as defined in the SILC protocol specification. */

SILC_TASK_CALLBACK(silc_server_protocol_rekey)
{
  SilcProtocol protocol = (SilcProtocol)context;
  SilcServerRekeyInternalContext *ctx =
    (SilcServerRekeyInternalContext *)protocol->context;
  SilcServer server = (SilcServer)ctx->server;
  SilcIDListData idata = (SilcIDListData)ctx->sock->user_data;
  SilcSKEStatus status;

  if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN)
    protocol->state = SILC_PROTOCOL_STATE_START;

  SILC_LOG_DEBUG(("Current protocol state %d", protocol->state));

  switch(protocol->state) {
  case SILC_PROTOCOL_STATE_START:
    {
      /*
       * Start protocol.
       */

      if (ctx->responder == TRUE) {
	/*
	 * We are receiving party
	 */

	if (ctx->pfs == TRUE) {
	  /*
	   * Use Perfect Forward Secrecy, ie. negotiate the key material
	   * using the SKE protocol.
	   */

	  if (!ctx->packet) {
	    SILC_LOG_ERROR(("Error during Re-key, with %s (%s)",
			    ctx->sock->hostname, ctx->sock->ip));
	    protocol->state = SILC_PROTOCOL_STATE_ERROR;
	    silc_protocol_execute(protocol, server->schedule, 0, 300000);
	    return;
	  }

	  if (ctx->packet->type != SILC_PACKET_KEY_EXCHANGE_1) {
	    SILC_LOG_ERROR(("Error during Re-key (R PFS): re-key state is "
			    "incorrect (received %d, expected %d packet), "
			    "with %s (%s)", ctx->packet->type,
			    SILC_PACKET_KEY_EXCHANGE_1, ctx->sock->hostname,
			    ctx->sock->ip));
	    protocol->state = SILC_PROTOCOL_STATE_ERROR;
	    silc_protocol_execute(protocol, server->schedule, 0, 300000);
	    return;
	  }

	  ctx->ske = silc_ske_alloc(server->rng, server);
	  ctx->ske->prop = silc_calloc(1, sizeof(*ctx->ske->prop));
	  silc_ske_group_get_by_number(idata->rekey->ske_group,
				       &ctx->ske->prop->group);

	  silc_ske_set_callbacks(ctx->ske,
				 silc_server_protocol_rekey_send_packet,
				 NULL, NULL, NULL, silc_ske_check_version,
				 context);

	  status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer);
	  if (status != SILC_SKE_STATUS_OK) {
	    SILC_LOG_ERROR(("Error (%s) during Re-key (R PFS), with %s (%s)",
			    silc_ske_map_status(status), ctx->sock->hostname,
			    ctx->sock->ip));
	    protocol->state = SILC_PROTOCOL_STATE_ERROR;
	    silc_protocol_execute(protocol, server->schedule, 0, 300000);
	    return;
	  }

	  /* Advance the protocol state */
	  protocol->state++;
	  silc_protocol_execute(protocol, server->schedule, 0, 0);
	} else {
	  /*
	   * Do normal and simple re-key.
	   */

	  /* Send the REKEY_DONE to indicate we will take new keys into use */
	  silc_server_packet_queue_purge(server, ctx->sock);
	  silc_server_packet_send(server, ctx->sock, SILC_PACKET_REKEY_DONE,
				  0, NULL, 0, FALSE);

	  /* After we send REKEY_DONE we must set the sending encryption
	     key to the new key since all packets after this packet must
	     encrypted with the new key. */
	  silc_server_protocol_rekey_generate(server, ctx, TRUE);
	  silc_server_packet_queue_purge(server, ctx->sock);

	  /* The protocol ends in next stage. */
	  protocol->state = SILC_PROTOCOL_STATE_END;
	}

      } else {
	/*
	 * We are the initiator of this protocol
	 */

	/* Start the re-key by sending the REKEY packet */
	silc_server_packet_send(server, ctx->sock, SILC_PACKET_REKEY,
				0, NULL, 0, FALSE);

	if (ctx->pfs == TRUE) {
	  /*
	   * Use Perfect Forward Secrecy, ie. negotiate the key material
	   * using the SKE protocol.
	   */
	  ctx->ske = silc_ske_alloc(server->rng, server);
	  ctx->ske->prop = silc_calloc(1, sizeof(*ctx->ske->prop));
	  silc_ske_group_get_by_number(idata->rekey->ske_group,
				       &ctx->ske->prop->group);

	  silc_ske_set_callbacks(ctx->ske,
				 silc_server_protocol_rekey_send_packet,
				 NULL, NULL, NULL, silc_ske_check_version,
				 context);

	  status = silc_ske_initiator_phase_2(ctx->ske, NULL, NULL, 0);
	  if (status != SILC_SKE_STATUS_OK) {
	    SILC_LOG_ERROR(("Error (%s) during Re-key (I PFS), with %s (%s)",
			    silc_ske_map_status(status), ctx->sock->hostname,
			    ctx->sock->ip));
	    protocol->state = SILC_PROTOCOL_STATE_ERROR;
	    silc_protocol_execute(protocol, server->schedule, 0, 300000);
	    return;
	  }

	  /* Advance the protocol state */
	  protocol->state++;
	} else {
	  /*
	   * Do normal and simple re-key.
	   */

	  /* Send the REKEY_DONE to indicate we will take new keys into use
	     now. */
	  silc_server_packet_queue_purge(server, ctx->sock);
	  silc_server_packet_send(server, ctx->sock, SILC_PACKET_REKEY_DONE,
				  0, NULL, 0, FALSE);

	  /* After we send REKEY_DONE we must set the sending encryption
	     key to the new key since all packets after this packet must
	     encrypted with the new key. */
	  silc_server_protocol_rekey_generate(server, ctx, TRUE);
	  silc_server_packet_queue_purge(server, ctx->sock);

	  /* The protocol ends in next stage. */
	  protocol->state = SILC_PROTOCOL_STATE_END;
	}
      }
    }
    break;

  case 2:
    /*
     * Second state, used only when doing re-key with PFS.
     */
    if (ctx->responder == TRUE) {
      if (ctx->pfs == TRUE) {
	/*
	 * Send our KE packet to the initiator now that we've processed
	 * the initiator's KE packet.
	 */
	status = silc_ske_responder_finish(ctx->ske, NULL, NULL,
					   SILC_SKE_PK_TYPE_SILC);
	if (status != SILC_SKE_STATUS_OK) {
	  SILC_LOG_ERROR(("Error (%s) during Re-key (R PFS), with %s (%s)",
			  silc_ske_map_status(status), ctx->sock->hostname,
			  ctx->sock->ip));
	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}
      }

    } else {
      if (ctx->pfs == TRUE) {
	/*
	 * The packet type must be KE packet
	 */
	if (!ctx->packet) {
	  SILC_LOG_ERROR(("Error during Re-key, with %s (%s)",
			  ctx->sock->hostname, ctx->sock->ip));
	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	if (ctx->packet->type != SILC_PACKET_KEY_EXCHANGE_2) {
	  SILC_LOG_ERROR(("Error during Re-key (I PFS): re-key state is "
			  "incorrect (received %d, expected %d packet), "
			  "with %s (%s)", ctx->packet->type,
			  SILC_PACKET_KEY_EXCHANGE_2, ctx->sock->hostname,
			  ctx->sock->ip));
	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}

	status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer);
	if (status != SILC_SKE_STATUS_OK) {
	  SILC_LOG_ERROR(("Error (%s) during Re-key (I PFS), with %s (%s)",
			  silc_ske_map_status(status), ctx->sock->hostname,
			  ctx->sock->ip));
	  protocol->state = SILC_PROTOCOL_STATE_ERROR;
	  silc_protocol_execute(protocol, server->schedule, 0, 300000);
	  return;
	}
      }
    }

    /* Send the REKEY_DONE to indicate we will take new keys into use
       now. */
    silc_server_packet_queue_purge(server, ctx->sock);
    silc_server_packet_send(server, ctx->sock, SILC_PACKET_REKEY_DONE,
			    0, NULL, 0, FALSE);

    /* After we send REKEY_DONE we must set the sending encryption
       key to the new key since all packets after this packet must
       encrypted with the new key. */
    silc_server_protocol_rekey_generate_pfs(server, ctx, TRUE);
    silc_server_packet_queue_purge(server, ctx->sock);

    /* The protocol ends in next stage. */
    protocol->state = SILC_PROTOCOL_STATE_END;
    break;

  case SILC_PROTOCOL_STATE_END:
    /*
     * End protocol
     */

    if (!ctx->packet) {
      SILC_LOG_ERROR(("Error during Re-key, with %s (%s)",
		     ctx->sock->hostname, ctx->sock->ip));
      protocol->state = SILC_PROTOCOL_STATE_ERROR;
      silc_protocol_execute(protocol, server->schedule, 0, 300000);
      return;
    }

    if (ctx->packet->type != SILC_PACKET_REKEY_DONE) {
      SILC_LOG_ERROR(("Error during Re-key (%s PFS): re-key state is "
		      "incorrect (received %d, expected %d packet), "
		      "with %s (%s)", ctx->responder ? "R" : "I",
		      ctx->packet->type, SILC_PACKET_REKEY_DONE,
		      ctx->sock->hostname, ctx->sock->ip));
      protocol->state = SILC_PROTOCOL_STATE_ERROR;
      silc_protocol_execute(protocol, server->schedule, 0, 300000);
      return;
    }

    /* We received the REKEY_DONE packet and all packets after this is
       encrypted with the new key so set the decryption key to the new key */
    if (ctx->pfs == TRUE)
      silc_server_protocol_rekey_generate_pfs(server, ctx, FALSE);
    else
      silc_server_protocol_rekey_generate(server, ctx, FALSE);
    silc_server_packet_queue_purge(server, ctx->sock);

    /* Assure that after calling final callback there cannot be pending
       executions for this protocol anymore. This just unregisters any
       timeout callbacks for this protocol. */
    silc_protocol_cancel(protocol, server->schedule);

    /* Protocol has ended, call the final callback */
    if (protocol->final_callback)
      silc_protocol_execute_final(protocol, server->schedule);
    else
      silc_protocol_free(protocol);
    break;

  case SILC_PROTOCOL_STATE_ERROR:
    /*
     * Error occured
     */

    if (ctx->pfs == TRUE)
      /* Send abort notification */
      silc_ske_abort(ctx->ske, ctx->ske->status);

    /* Assure that after calling final callback there cannot be pending
       executions for this protocol anymore. This just unregisters any
       timeout callbacks for this protocol. */
    silc_protocol_cancel(protocol, server->schedule);

    /* On error the final callback is always called. */
    if (protocol->final_callback)
      silc_protocol_execute_final(protocol, server->schedule);
    else
      silc_protocol_free(protocol);
    break;

  case SILC_PROTOCOL_STATE_FAILURE:
    /*
     * We have received failure from remote
     */

    SILC_LOG_ERROR(("Error during Re-Key: received Failure"));

    /* Assure that after calling final callback there cannot be pending
       executions for this protocol anymore. This just unregisters any
       timeout callbacks for this protocol. */
    silc_protocol_cancel(protocol, server->schedule);

    /* On error the final callback is always called. */
    if (protocol->final_callback)
      silc_protocol_execute_final(protocol, server->schedule);
    else
      silc_protocol_free(protocol);
    break;

  case SILC_PROTOCOL_STATE_UNKNOWN:
    break;
  }

}

/* Registers protocols used in server. */

void silc_server_protocols_register(void)
{
  silc_protocol_register(SILC_PROTOCOL_SERVER_CONNECTION_AUTH,
			 silc_server_protocol_connection_auth);
  silc_protocol_register(SILC_PROTOCOL_SERVER_KEY_EXCHANGE,
			 silc_server_protocol_key_exchange);
  silc_protocol_register(SILC_PROTOCOL_SERVER_REKEY,
			 silc_server_protocol_rekey);
  silc_protocol_register(SILC_PROTOCOL_SERVER_BACKUP,
			 silc_server_protocol_backup);
}

/* Unregisters protocols */

void silc_server_protocols_unregister(void)
{
  silc_protocol_unregister(SILC_PROTOCOL_SERVER_CONNECTION_AUTH,
			   silc_server_protocol_connection_auth);
  silc_protocol_unregister(SILC_PROTOCOL_SERVER_KEY_EXCHANGE,
			   silc_server_protocol_key_exchange);
  silc_protocol_unregister(SILC_PROTOCOL_SERVER_REKEY,
			   silc_server_protocol_rekey);
  silc_protocol_unregister(SILC_PROTOCOL_SERVER_BACKUP,
			   silc_server_protocol_backup);
}
