/*
 * nasd_security_dr.c
 *
 * Drive-side security calls.
 *
 * Author: Marc Unangst
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_drive_options.h>
#include <nasd/nasd_general.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_drive_types.h>
#include <nasd/nasd_marshall.h>
#include <nasd/nasd_cache.h>
#include <nasd/nasd_od.h>
#include <nasd/nasd_security.h>
#include <nasd/nasd_security_dr.h>
#include <nasd/nasd_keymgmt_dr.h>
#include <nasd/nasd_nonce_mgmt.h>
#include <cryptolib/libcrypt.h>

nasd_sec_kc_q_t nasd_sec_kc_lru;
nasd_sec_kc_q_t nasd_sec_kc_free;
nasd_sec_kc_q_t *nasd_sec_kc_bucket_ents;
int nasd_sec_kc_buckets = NASD_KC_BUCKETS_DEFAULT;
int nasd_sec_kc_size = NASD_KC_CACHESIZE_DEFAULT;
NASD_DECLARE_MUTEX(nasd_sec_kc_lock)

nasd_sec_nonce_mgr_t *nasd_drive_nonce_mgr;

nasd_status_t
nasd_unpack_args(nasd_security_param_otw_t sec_param_otw,
		 nasd_capability_otw_t capability_otw,
		 nasd_digest_nonce_otw_t digest_otw,
		 void *args_otw,
		 int args_otw_len,
		 nasd_security_param_t *sec_param,
		 nasd_capability_t *capability,
		 void *args,
		 void (*unmarshall_fn)(),
		 nasd_iv_t icv,
		 nasd_key_t op_key,
		 nasd_key_t integrity_key,
		 nasd_key_t privacy_key,
		 nasd_security_context_t *context)
{
  nasd_digest_nonce_t *otw_digest;
#if NASD_SECURE_RPCS_ENABLE > 0
  nasd_digest_nonce_t digest;
  nasd_status_t rc;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */

  /* this is sort of hokey, but rpcgen guarantees that the field
     offsets in the in-core and otw structs are the same. */
  otw_digest = (nasd_digest_nonce_t *) digest_otw;

  /* unpack the parts we can (these will never be encrypted) */
  nasd_security_param_t_unmarshall(sec_param_otw, sec_param);

  if(sec_param->actual_protection != NASD_NO_PROTECTION) {
#if NASD_SECURE_RPCS_ENABLE > 0
    /* check to see if our keys have been set yet (although, if they
     haven't been, we have no way of returning a
     correctly-digested/encrypted reply) */
    if(!DISK.initialized) {
      sec_param->actual_protection = NASD_NO_PROTECTION;
      if(context)
	context->protection = NASD_NO_PROTECTION;
      return NASD_DRIVE_UNINITIALIZED;
    }

      nasd_capability_t_unmarshall(capability_otw, capability);

    nasd_timespec_t_unmarshall((nasd_otw_base_t *)&otw_digest->nonce,
			       &digest.nonce);


    /* initialize security context */
    if(context) {
      context->protection = sec_param->actual_protection;
      context->pending_hmac = 0;
    }

    /* get/generate the capability key for this request */
    rc = nasd_sec_get_capability_keys(sec_param, capability,
				      capability_otw, op_key, integrity_key,
				      privacy_key);
    if(rc) {
      /* if we couldn't get a key, we won't be able to do encryption.
	 make sure we don't try to when we send the reply. */
      sec_param->actual_protection = NASD_NO_PROTECTION;
      if(context)
	context->protection = NASD_NO_PROTECTION;
      return rc;
    }

    if(sec_param->actual_protection &
       (NASD_PRIVACY_ARGS | NASD_PRIVACY_DATA)) {
#if NASD_SECURITY_DEBUG_ERRORS > 0
      nasd_printf("WARNING: drive unpack_args: unsupported security protection mask 0x%x\n",
		  sec_param->actual_protection);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
      return NASD_UNSUPPORTED_PROTECTION;
    }

    
    /* Verify the args against the MAC the client sent.  Note that this
     is where validation of the capability happens.  If the client has
     modified the capability after receiving it from the file manager,
     then private credential we calculate from the capability and AV
     will not match the one that the client was given by the FM, and
     we will calculate a different MAC on the arguments (see below)
     than the client gave us. */
    rc = nasd_sec_verify_args(sec_param, capability, digest_otw, &digest,
			      args_otw, args_otw_len, op_key, integrity_key, context);
    if(rc) {
      sec_param->actual_protection = NASD_NO_PROTECTION;
      if(context)
	context->protection = NASD_NO_PROTECTION;
      return rc;
    }

    /* Generic security checks.  We do these here (after verifying args
       and capability) so as to not expose information about the drive
       if the MAC was incorrect. */
    /* make sure client has specified a valid partition number */
    if(!NASD_OD_PARTNUM_VALID(sec_param->partnum)) {
      return NASD_BAD_PARTITION;
    }
    /* if we got a capability, do some capability-specific checks */
    if(NASD_SEC_IS_CAPABILITY(sec_param->type)) {
      /* make sure this capability is for a valid partition (we don't
	 trust the FM) */
      if(!NASD_OD_PARTNUM_VALID(capability->partnum)) {
	return NASD_BAD_PARTITION;
      }
      /* make sure the client is at least as secure as the FM wants it to be */
      if((sec_param->actual_protection & capability->min_protection) !=
	 capability->min_protection) {
	return NASD_CAP_PROTECTION;
      }
      if((sec_param->actual_protection &
	  PART(capability->partnum).min_protection) !=
	 PART(capability->partnum).min_protection) {
	return NASD_PART_PROTECTION;
      }
    }

    /* reinitialize the SHA1 context if we will need it, and we have one */
    if((sec_param->actual_protection & NASD_INTEGRITY_DATA) && context)
      SHA1_Init(0, &context->SHA1_ctx);
#else /* NASD_SECURE_RPCS_ENABLE > 0 */
#if NASD_SECURITY_DEBUG_ERRORS > 0
    nasd_printf("WARNING: drive unpack_args: unsupported security protection mask 0x%x\n",
		sec_param->actual_protection);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
    return NASD_UNSUPPORTED_PROTECTION;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
  } else {
    if(context) {
      bzero(context, sizeof(nasd_security_context_t));
    }
    bzero(capability, sizeof(nasd_capability_t));
  }
  /* finally, we can unmarshall the arguments and return */
  unmarshall_fn(args_otw, args);
  return NASD_SUCCESS;
}

/* Retrieve the secret key used for this request.  If the request
   is using direct key-based authentication (drive key, partition
   key, etc.) then we just copy the requested key out of our
   data structures.  If the request is using a capability, we
   need to calculate the secret key from the appropriate basis
   key, the object's AV, and the contents of the capability. */
nasd_status_t
nasd_sec_get_capability_keys(nasd_security_param_t *sec_param,
			     nasd_capability_t *capability,
			     nasd_capability_otw_t capability_otw,
			     nasd_byte_t *op_key,
			     nasd_byte_t *integrity_key,
			     nasd_byte_t *privacy_key)
{
  HMAC_SHA1_CTX HMAC_context;
  nasd_attribute_t attr;
  nasd_av_otw_t av_otw;
  nasd_key_t basis_key;
  nasd_byte hmac_key[SHA_DIGEST_LENGTH];
  nasd_status_t rc;
  nasd_sec_kc_ent_t kc_ent;

  switch(sec_param->type) {
  case NASD_BLACK_CAPABILITY:
  case NASD_RED_CAPABILITY:
    NASD_ASSERT(capability != NULL);
    rc = nasd_sec_kc_lookup(capability, &kc_ent);
    if(rc) {
      /* cache miss -- need to generate the keys. */

      /* need to get attributes for AV -- this isn't that bad, because
	 we'll need the object's inode soon anyway for the op itself. */
      rc = nasd_obj_getattr(capability->partnum, capability->ni, &attr);
      if(rc) {
#if NASD_SECURITY_DEBUG_ERRORS > 0
	nasd_printf("nasd_sec_get_capability_key: GETATTR for part %d ID %"
		    NASD_ID_FMT " failed: rc=0x%x (%s)\n",
		    capability->partnum, capability->ni, rc,
		    nasd_error_string(rc));
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
	return rc;
      }
      nasd_av_t_marshall(&attr.av, av_otw);

      /* fetch the appropriate basis key */
      if(sec_param->type == NASD_BLACK_CAPABILITY) {
	nasd_sec_getkey(sec_param->partnum, NASD_BLACK_KEY, basis_key);
      } else {
	nasd_sec_getkey(sec_param->partnum, NASD_RED_KEY, basis_key);
      }

      /* build the capability key/private credential.  note that we use
	 the marshalled/otw version of the capability and AV here. */
      HMAC_SHA1_Init(&HMAC_context, (char *) basis_key, sizeof(nasd_key_t));
      HMAC_SHA1_Update(&HMAC_context, (unsigned char *)capability_otw,
		       NASD_CAPABILITY_SZ);
      HMAC_SHA1_Update(&HMAC_context, (unsigned char *)av_otw,
		       NASD_AV_T_SIZE);
      HMAC_SHA1_Final(hmac_key, &HMAC_context);

      bcopy(hmac_key, op_key, NASD_MIN(SHA_DIGEST_LENGTH, NASD_KEY_SZ));
      nasd_sec_integrity_key(op_key, integrity_key);

      /* save the keys in the cache so we don't have to do this again */
      nasd_sec_kc_insert(capability, op_key, integrity_key, privacy_key);
    } else {
      /* cache hit */
      bcopy(kc_ent.op_key, op_key, sizeof(nasd_key_t));
      bcopy(kc_ent.integrity_key, integrity_key, sizeof(nasd_key_t));
      bcopy(kc_ent.privacy_key, privacy_key, sizeof(nasd_key_t));
    }
    break;
  case NASD_RED_KEY:
  case NASD_BLACK_KEY:
  case NASD_PARTITION_KEY:
  case NASD_DRIVE_KEY:
  case NASD_MASTER_KEY:
    nasd_sec_getkey(sec_param->partnum, sec_param->type, op_key);
    nasd_sec_integrity_key(op_key, integrity_key);
    break;
  default:
#if NASD_SECURITY_DEBUG_ERRORS > 0
    nasd_printf("nasd_sec_get_capability_key: ERROR: bad key type %d\n",
		sec_param->type);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
    return NASD_BAD_KEYTYPE;
  }

  return NASD_SUCCESS;
}

/* Verify that the nonce (digest and timestamp) provided by the client
   are valid. */
nasd_status_t
nasd_sec_verify_args(nasd_security_param_t *sec_param,
		     nasd_capability_t *capability,
		     nasd_digest_nonce_otw_t digest_otw,
		     nasd_digest_nonce_t *digest,
		     void *args_otw,
		     int args_otw_len,
		     nasd_key_t op_key,
		     nasd_key_t integrity_key,
		     nasd_security_context_t *context)
{
  HMAC_SHA1_CTX HMAC_context;
  nasd_status_t rc;
  nasd_byte md_out[SHA_DIGEST_LENGTH];
  nasd_timespec_t now;
  nasd_digest_nonce_t *otw_digest;

  if(sec_param->actual_protection & NASD_INTEGRITY_ARGS) {
    /* this is sort of hokey, but rpcgen guarantees that the field
       offsets in the in-core and otw structs are the same. */
    otw_digest = (nasd_digest_nonce_t *) digest_otw;

    /* this should have been taken care of earlier */
    NASD_ASSERT((sec_param->actual_protection & NASD_PRIVACY_CAPABILITY) == 0);

    nasd_gettime(&now);

    /* make sure the capability hasn't expired & check for replay */
    if(NASD_SEC_IS_CAPABILITY(sec_param->type)) {
      if(now.ts_sec > capability->expiration_seconds) {
	return NASD_EXPIRED_CAPABILITY;
      }
      rc = nasd_sec_check_nonce(nasd_drive_nonce_mgr,
        capability, op_key, &digest->nonce, &now);
      if(rc)
	return rc;
    } else {
      rc = nasd_sec_check_nonce(nasd_drive_nonce_mgr,
        NULL, NULL, &digest->nonce, &now);
      if(rc)
	return rc;
    }

    /* generate and check the MAC */
    HMAC_SHA1_Init(&HMAC_context, (char *) integrity_key, sizeof(nasd_key_t));
    HMAC_SHA1_Update(&HMAC_context, args_otw, args_otw_len);
    HMAC_SHA1_Update(&HMAC_context, (nasd_byte *) &otw_digest->nonce,
		     sizeof(nasd_timespec_t));
    if(context) {
      bcopy(&HMAC_context, &context->HMAC_context, sizeof(HMAC_SHA1_CTX));
    }
    HMAC_SHA1_Final(md_out, &HMAC_context);

    if(bcmp(md_out, otw_digest->digest, NASD_MIN(SHA_DIGEST_LENGTH,
						 NASD_DIGEST_SIZE)) != 0) {
#if NASD_SECURITY_DEBUG_ERRORS > 0
      nasd_printf("nasd_sec_verify_args: incorrect digest\n");
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
      return NASD_BAD_DIGEST_REQ;
    }
  } else if((sec_param->actual_protection & NASD_INTEGRITY_DATA) && context) {
    /* need to initialize the security context */
    HMAC_SHA1_Init(&context->HMAC_context, (char *) integrity_key,
		   sizeof(nasd_key_t));
  }

  return NASD_SUCCESS;
}

void
nasd_pack_res(nasd_security_param_t *sec_param,
	      void *res,
	      void *res_otw,
	      int res_otw_len,
	      nasd_digest_nonce_otw_t digest_otw,
	      void (*marshall_fn)(),
	      nasd_iv_t icv,
	      nasd_key_t op_key,
	      nasd_key_t integrity_key,
	      nasd_key_t privacy_key,
	      nasd_security_context_t *context)
{
#if NASD_SECURE_RPCS_ENABLE > 0
  HMAC_SHA1_CTX *HMAC_contextp;
  nasd_timespec_t now;
  nasd_digest_t tdigest;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */

  /* marshall results */
  marshall_fn(res, res_otw);

  if(sec_param->actual_protection == NASD_NO_PROTECTION) {
    /* nothing else to do. */
    bzero(digest_otw, sizeof(digest_otw));
    return;
  }

#if NASD_SECURE_RPCS_ENABLE > 0

  /* finish up data digest if needed */
  if(context && context->pending_hmac) {
    /* use pre-existing security context if it's there */
    NASD_ASSERT(sec_param->actual_protection == context->protection);
    HMAC_contextp = &context->HMAC_context;
    SHA1_Final(0, tdigest, &context->SHA1_ctx);
    HMAC_SHA1_Update(HMAC_contextp, tdigest, sizeof(nasd_digest_t));
    SHA1_Init(0, &context->SHA1_ctx);
    context->pending_hmac = 0;
  }

  /* fill in nonce (digest & timestamp) */
  nasd_gettime(&now);
  nasd_sec_fill_nonce(sec_param, op_key, integrity_key, icv, 1, res_otw,
		      res_otw_len, &now, digest_otw, context);

  if(sec_param->actual_protection & (NASD_PRIVACY_ARGS | NASD_PRIVACY_DATA)) {
#if NASD_SECURITY_DEBUG_ERRORS > 0
    nasd_printf("WARNING: drive pack_res: unsupported security protection mask 0x%x\n",
		sec_param->actual_protection);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
  }

#else /* NASD_SECURE_RPCS_ENABLE > 0 */
#if NASD_SECURITY_DEBUG_ERRORS > 0
  nasd_printf("WARNING: drive pack_res: unsupported security protection mask 0x%x\n",
	      sec_param->actual_protection);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */

  return; 
}

nasd_status_t
nasd_sec_init()
{
  nasd_status_t rc;

  rc = NASD_SUCCESS;

#if NASD_SECURE_RPCS_ENABLE > 0
  /* initialize capability cache */
  rc = nasd_sec_kc_init(nasd_odc_shutdown);
  if(rc)
    return rc;

#if NASD_SECURITY_KEEP_STATS > 0
  /* init encryption/digest stats counters */

  rc = nasd_SHA1_init_stats();
  if (rc) return rc;

  rc = nasd_HMAC_init_stats();
  if (rc) return rc;
#if NASD_SECURITY_PRINT_STATS > 0
  rc = nasd_shutdown_proc(nasd_odc_shutdown,
			  nasd_HMAC_print_stats,NULL);
  if (rc)
    return(rc);
  rc = nasd_shutdown_proc(nasd_odc_shutdown,
			  nasd_SHA1_print_stats,NULL);
  if (rc)
    return(rc);
#endif /* NASD_SECURITY_PRINT_STATS > 0 */
#endif /* NASD_SECURITY_KEEP_STATS > 0 */
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
  return(rc);
}

nasd_status_t
nasd_sec_kc_lookup(nasd_capability_t *capability, nasd_sec_kc_ent_t *ke)
{
  nasd_sec_kc_q_t *bucket;
  nasd_sec_kc_ent_t *kp;
  nasd_status_t rc;

  NASD_ASSERT(capability != NULL);

  bucket = &nasd_sec_kc_bucket_ents[NASD_KC_BUCKET(capability->ni)];

  rc = NASD_FAIL;
  NASD_KC_LOCK();
  for(kp = bucket->head.cnext; kp != &bucket->head; kp = kp->cnext) {
    if(bcmp(capability, &kp->capability, sizeof(nasd_capability_t)) == 0) {
      NASD_KCQ_DEQ(kp, lnext, lprev);
      NASD_KCQ_INS(&nasd_sec_kc_lru, kp, lnext, lprev);
      bcopy(kp, ke, sizeof(nasd_sec_kc_ent_t));
      rc = NASD_SUCCESS;
      break;
    }
  }
  NASD_KC_UNLOCK();

  if(rc)
    bzero(ke, sizeof(nasd_sec_kc_ent_t));

  return rc;
}

nasd_status_t
nasd_sec_kc_insert(nasd_capability_t *capability, nasd_byte_t *op_key,
		   nasd_byte_t *integrity_key, nasd_byte_t *privacy_key)
{
  nasd_sec_kc_q_t *bucket;
  nasd_sec_kc_ent_t *ke, *kp;
  nasd_status_t rc;

  ke = NULL;

  NASD_KC_LOCK();

  /* try to get an unused buf first */
  if(NASD_KCQ_SIZE(&nasd_sec_kc_free) > 0) {
    NASD_KCQ_DEQ_TAIL(&nasd_sec_kc_free, ke, fnext, fprev);
  }

  if(!ke) {
    /* need to steal from LRU */
    NASD_ASSERT(NASD_KCQ_SIZE(&nasd_sec_kc_lru) > 0);
    NASD_KCQ_DEQ_TAIL(&nasd_sec_kc_lru, ke, lnext, lprev);
    NASD_ASSERT(ke->cnext != NULL);
    /* remove from old bucket */
    NASD_KCQ_DEQ(ke, cnext, cprev);
  }

  bcopy(capability, &ke->capability, sizeof(nasd_capability_t));
  bcopy(op_key, ke->op_key, sizeof(nasd_key_t));
  bcopy(integrity_key, ke->integrity_key, sizeof(nasd_key_t));
  bcopy(privacy_key, ke->privacy_key, sizeof(nasd_key_t));

  /* put in new bucket */
  bucket = &nasd_sec_kc_bucket_ents[NASD_KC_BUCKET(capability->ni)];
  NASD_KCQ_INS(bucket, ke, cnext, cprev);

  /* put on LRU queue */
  NASD_KCQ_INS(&nasd_sec_kc_lru, ke, lnext, lprev);

  NASD_KC_UNLOCK();
  return NASD_SUCCESS;
}

void
nasd_sec_kc_invalidate(nasd_identifier_t identifier)
{
  nasd_sec_kc_ent_t *ke;
  nasd_sec_kc_q_t *bucket;

  NASD_KC_LOCK();

  bucket = &nasd_sec_kc_bucket_ents[NASD_KC_BUCKET(identifier)];

  for(ke = bucket->head.cnext; ke != &bucket->head; ke = ke->cnext) {
    if(ke->capability.ni == identifier) {
      /* remove from bucket */
      NASD_KCQ_DEQ(ke, cnext, cprev);
      /* add to free queue */
      NASD_KCQ_INS(&nasd_sec_kc_free, ke, fnext, fprev);
    }
  }

  NASD_KC_UNLOCK();
}


nasd_status_t
nasd_sec_kc_init(nasd_shutdown_list_t *sl)
{
  nasd_status_t rc;
  int i;
  nasd_sec_kc_ent_t *ke;

  NASD_Malloc(nasd_sec_kc_bucket_ents,
	      nasd_sec_kc_buckets * sizeof(nasd_sec_kc_q_t),
	      (nasd_sec_kc_q_t *));
  if(nasd_sec_kc_bucket_ents == NULL)
    return NASD_NO_MEM;
  rc = nasd_shutdown_mem(sl, nasd_sec_kc_bucket_ents,
			 nasd_sec_kc_buckets * sizeof(nasd_sec_kc_q_t));
  if(rc) {
    NASD_Free(nasd_sec_kc_bucket_ents,
	      nasd_sec_kc_buckets * sizeof(nasd_sec_kc_q_t));
    return rc;
  }

  for(i = 0; i < nasd_sec_kc_buckets; i++) {
    nasd_sec_kc_q_init(&nasd_sec_kc_bucket_ents[i]);
  }

  nasd_sec_kc_q_init(&nasd_sec_kc_lru);
  nasd_sec_kc_q_init(&nasd_sec_kc_free);

  for(i = 0; i < nasd_sec_kc_size; i++) {
    NASD_Malloc(ke, sizeof(nasd_sec_kc_ent_t), (nasd_sec_kc_ent_t *));
    if(!ke)
      return NASD_NO_MEM;
    rc = nasd_shutdown_mem(sl, ke, sizeof(nasd_sec_kc_ent_t));
    if(rc) {
      NASD_Free(ke, sizeof(nasd_sec_kc_ent_t));
      return rc;
    }
    NASD_KCQ_INS(&nasd_sec_kc_free, ke, fnext, fprev);
  }

  rc = nasd_mutex_init(&nasd_sec_kc_lock);
  if(rc)
    return rc;
  rc = nasd_shutdown_mutex(sl, &nasd_sec_kc_lock);
  if(rc) {
    nasd_mutex_destroy(&nasd_sec_kc_lock);
    return rc;
  }

  return NASD_SUCCESS;
}

void
nasd_sec_kc_q_init(nasd_sec_kc_q_t *kq)
{
  bzero(kq, sizeof(nasd_sec_kc_q_t));
  kq->head.cnext = kq->head.cprev = &kq->head;
  kq->head.lnext = kq->head.lprev = &kq->head;
  kq->head.fnext = kq->head.fprev = &kq->head;
}
