/*
 * Copyright (c) 1995 - 2000 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "rxkad_locl.h"

RCSID("$Id: rxk_clnt.c,v 1.6 2000/01/07 17:07:36 assar Exp $");

/* This code also links into the kernel so we need to use osi_Alloc()
 * to avoid calling malloc(). Similar trick with memcpy() */

#undef osi_Alloc
#undef osi_Free
char *osi_Alloc(int32 size);
void osi_Free(void *p, int32 size);

#undef memcpy
#define memcpy(to, from, len) bcopy1((from), (to), (len))

static
void
bcopy1(const void *from_, void *to_, size_t n)
{
  char *to = to_;
  const char *from = from_;
  for (; n > 0; n--)
    {
      *to = *from;
      to++;
      from++;
    }
}

/* Security object specific client data */
typedef struct rxkad_clnt_class {
  struct rx_securityClass klass;
  rxkad_level level;
  key_stuff k;
  int32 kvno;
  int32 ticket_len;
  char *ticket;
} rxkad_clnt_class;

/* Per connection specific client data */
typedef struct clnt_con_data {
  end_stuff e;
} clnt_con_data;

static
int
client_NewConnection(struct rx_securityClass *obj_, struct rx_connection *con)
{
  rxkad_clnt_class *obj = (rxkad_clnt_class *) obj_;
  clnt_con_data *cdat;

  assert(con->securityData == 0);
  obj->klass.refCount++;
  cdat = (clnt_con_data *) osi_Alloc(sizeof(clnt_con_data));
  cdat->e.bytesReceived = cdat->e.packetsReceived = 0;
  cdat->e.bytesSent = cdat->e.packetsSent = 0;

  con->securityData = (char *) cdat;
  rx_nextCid += RX_MAXCALLS;
  con->epoch = rx_epoch;
  con->cid = rx_nextCid;
  /* We don't use trailers but the transarc implementation breaks when
   * we don't set the trailer size, packets get to large */
  switch (obj->level) {
  case rxkad_clear:
    /* nichts */
    break;
  case rxkad_auth:
    rx_SetSecurityHeaderSize(con, 4);
    rx_SetSecurityMaxTrailerSize(con, 4);
    break;
  case rxkad_crypt:
    rx_SetSecurityHeaderSize(con, 8);
    rx_SetSecurityMaxTrailerSize(con, 8);
    break;
  default:
    assert(0);
  }
  rxkad_calc_header_iv(con, obj->k.keysched,
		       (const des_cblock *)&obj->k.key, cdat->e.header_iv);
  return 0;
}

static
int
client_Close(struct rx_securityClass *obj_)
{
  rxkad_clnt_class *obj = (rxkad_clnt_class *) obj_;
  obj->klass.refCount--;
  if (obj->klass.refCount <= 0)
    {
      osi_Free(obj->ticket, obj->ticket_len);
      osi_Free(obj, sizeof(rxkad_clnt_class));
    }
  return 0;
}

static
int
client_DestroyConnection(struct rx_securityClass *obj,
			 struct rx_connection *con)
{
  clnt_con_data *cdat = (clnt_con_data *)con->securityData;
  
  if (cdat)
    osi_Free(cdat, sizeof(clnt_con_data));
  return client_Close(obj);
}

/*
 * Receive a challange and respond.
 */
static
int
client_GetResponse(const struct rx_securityClass *obj_,
		   const struct rx_connection *con,
		   struct rx_packet *pkt)
{
  rxkad_clnt_class *obj = (rxkad_clnt_class *) obj_;
  rxkad_challenge c;
  rxkad_response r;

  /* Get challenge */
  if (rx_SlowReadPacket(pkt, 0, sizeof(c), &c) != sizeof(c))
    return RXKADPACKETSHORT;

  if (ntohl(c.version) < RXKAD_VERSION)
    return RXKADINCONSISTENCY;	/* Don't know how to make vers 1 response. */
  /* Always make a vers 2 response. */
    
  if (ntohl(c.min_level) > obj->level)
    return RXKADLEVELFAIL;

  /* Make response */
  r.version = htonl(RXKAD_VERSION);
  r.unused = 0;
  r.encrypted.epoch = htonl(con->epoch);
  r.encrypted.cid = htonl(con->cid & RX_CIDMASK);
  r.encrypted.cksum = 0;
  r.encrypted.security_index = htonl(con->securityIndex);
  {
    int i;
    /* Get and fixup call number vector */
    rxi_GetCallNumberVector(con, r.encrypted.call_numbers);
    for (i = 0; i < RX_MAXCALLS; i++)
      {
	if (r.encrypted.call_numbers[i] < 0)
	  return RXKADINCONSISTENCY;
	r.encrypted.call_numbers[i] = htonl(r.encrypted.call_numbers[i]);
      }
  }
  r.encrypted.inc_nonce = htonl(ntohl(c.nonce) + 1);
  r.encrypted.level = htonl((int32)obj->level);
  r.kvno = htonl(obj->kvno);
  r.ticket_len = htonl(obj->ticket_len);
  /* Make checksum before we seal r.encrypted */
  r.encrypted.cksum = rxkad_cksum_response(&r);
  /* Seal r.encrypted */
  fc_cbc_enc2(&r.encrypted, &r.encrypted, sizeof(r.encrypted),
	      obj->k.keysched, (u_int32*)obj->k.key, ENCRYPT);

  /* Stuff response and kerberos ticket into packet */
  if (rx_SlowWritePacket(pkt, 0, sizeof(r), &r) != sizeof(r))
    return RXKADPACKETSHORT;
  if (rx_SlowWritePacket(pkt, sizeof(r), obj->ticket_len, obj->ticket) != obj->ticket_len)
    return RXKADPACKETSHORT;
  rx_SetDataSize(pkt, sizeof(r) + obj->ticket_len);
  return 0;
}

/*
 * Checksum and/or encrypt packet.
 */
static
int
client_PreparePacket(struct rx_securityClass *obj_,
		     struct rx_call *call,
		     struct rx_packet *pkt)
{
  rxkad_clnt_class *obj = (rxkad_clnt_class *) obj_;
  key_stuff *k = &obj->k;
  struct rx_connection *con = rx_ConnectionOf(call);
  end_stuff *e = &((clnt_con_data *) con->securityData)->e;

  return rxkad_prepare_packet(pkt, con, obj->level, k, e);
}

/*
 * Verify checksums and/or decrypt packet.
 */
static
int
client_CheckPacket(struct rx_securityClass *obj_,
		   struct rx_call *call,
		   struct rx_packet *pkt)
{
  rxkad_clnt_class *obj = (rxkad_clnt_class *) obj_;
  key_stuff *k = &obj->k;
  struct rx_connection *con = rx_ConnectionOf(call);
  end_stuff *e = &((clnt_con_data *) con->securityData)->e;

  return rxkad_check_packet(pkt, con, obj->level, k, e);
}

static
int
client_GetStats(const struct rx_securityClass *obj,
		const struct rx_connection *con,
		struct rx_securityObjectStats *st)
{
  clnt_con_data *cdat = (clnt_con_data *) con->securityData;

  st->type = rxkad_disipline;
  st->level = ((rxkad_clnt_class *)obj)->level;
  st->flags = rxkad_checksummed;
  if (cdat == 0)
    st->flags |= rxkad_unallocated;
  {
    st->bytesReceived = cdat->e.bytesReceived;
    st->packetsReceived = cdat->e.packetsReceived;
    st->bytesSent = cdat->e.bytesSent;
    st->packetsSent = cdat->e.packetsSent;
  }
  return 0;
}

static
struct rx_securityOps client_ops = {
  client_Close,
  client_NewConnection,
  client_PreparePacket,
  0,
  0,
  0,
  0,
  client_GetResponse,
  0,
  client_CheckPacket,
  client_DestroyConnection,
  client_GetStats,
  0,
  0,
  0,
};

int rxkad_EpochWasSet = 0;

int rxkad_min_level = rxkad_clear; /* rxkad_{clear, auth, crypt} */

struct rx_securityClass *
rxkad_NewClientSecurityObject(/*rxkad_level*/ int level,
			      void *sessionkey,
			      int32 kvno,
			      int ticket_len,
			      char *ticket)
{
  rxkad_clnt_class *obj;
  static int inited = 0;

  if (level < rxkad_min_level)
    level = rxkad_min_level;	/* Boost security level */

  if (!inited)
    {
      /* Any good random numbers will do, no real need to use
       * cryptographic techniques here */
      union {
	u_int32 rnd[2];
	des_cblock k;
      } u;
      int32 sched[ROUNDS];
      u_long next_epoch;

      u.rnd[0] = rx_nextCid;
      u.rnd[1] = rx_epoch;
      fc_keysched(sessionkey, sched);
      fc_ecb_encrypt(&u.k, &u.k, sched, ENCRYPT);

      /* Some paranoia so we won't reveal the key */
      /*des_set_odd_parity(&u.k);*/
      fc_keysched(&u.k, sched);
      fc_ecb_encrypt(&u.k, &u.k, sched, ENCRYPT);

      /* Some paranoia so we won't reveal the key */
      /*des_set_odd_parity(&u.k);*/
      fc_keysched(&u.k, sched);
      fc_ecb_encrypt(&u.k, &u.k, sched, ENCRYPT);

      /* Set new cid and epoch generator */
      rx_nextCid = u.rnd[0] << RX_CIDSHIFT;
      next_epoch = u.rnd[0] ^ u.rnd[1];
      next_epoch &= 0x7FFFFFFF;
      rx_SetEpoch(next_epoch);
      rxkad_EpochWasSet = 1;
      inited = 1;
    }

#if 0
  /* If we are passed a to large kerberos 5 ticket hope for the best */
  if (ticket_len > MAXKRB5TICKETLEN)
    ticket_len = MAXKRB5TICKETLEN;
#endif

  obj = (rxkad_clnt_class *) osi_Alloc(sizeof(rxkad_clnt_class));
  obj->klass.refCount = 1;
  obj->klass.ops = &client_ops;

  obj->klass.privateData = (char *) obj;

  obj->level = level;
  fc_keysched(sessionkey, obj->k.keysched);
  memcpy(obj->k.key, sessionkey, sizeof(des_cblock));
  obj->kvno = kvno;

  obj->ticket_len = ticket_len;
  obj->ticket = osi_Alloc(ticket_len);
  memcpy(obj->ticket, ticket, ticket_len);

  return &obj->klass;
}
