
/*
 * ecp.c
 *
 * Written by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1998-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 */

#include "ppp.h"
#include "bund.h"
#include "ecp.h"
#include "fsm.h"

/*
 * DEFINITIONS
 */

  #define ECP_MAXFAILURE	7

  #define ECP_KNOWN_CODES	(   (1 << CODE_CONFIGREQ)	\
				  | (1 << CODE_CONFIGACK)	\
				  | (1 << CODE_CONFIGNAK)	\
				  | (1 << CODE_CONFIGREJ)	\
				  | (1 << CODE_TERMREQ)		\
				  | (1 << CODE_TERMACK)		\
				  | (1 << CODE_CODEREJ)		\
				  | (1 << CODE_RESETREQ)	\
				  | (1 << CODE_RESETACK)	)

  #define ECP_OVERHEAD		2

  #define ECP_PEER_REJECTED(p,x)	((p)->peer_reject & (1<<(x)))
  #define ECP_SELF_REJECTED(p,x)	((p)->self_reject & (1<<(x)))

  #define ECP_PEER_REJ(p,x)	do{(p)->peer_reject |= (1<<(x));}while(0)
  #define ECP_SELF_REJ(p,x)	do{(p)->self_reject |= (1<<(x));}while(0)

/* Set menu options */

  enum
  {
    SET_KEY,
    SET_ACCEPT,
    SET_DENY,
    SET_ENABLE,
    SET_DISABLE,
    SET_YES,
    SET_NO,
  };

/*
 * INTERNAL FUNCTIONS
 */

  static void		EcpConfigure(Fsm fp);
  static u_char		*EcpBuildConfigReq(Fsm fp, u_char *cp);
  static void		EcpDecodeConfig(Fsm fp, FsmOption a, int num, int mode);
  static void		EcpLayerUp(Fsm fp);
  static void		EcpLayerDown(Fsm fp);
  static void		EcpFailure(Fsm f, enum fsmfail reason);
  static void		EcpRecvResetReq(Fsm fp, int id, Mbuf bp);
  static void		EcpRecvResetAck(Fsm fp, int id, Mbuf bp);

  static int		EcpSetCommand(int ac, char *av[], void *arg);
  static EncType	EcpFindType(int type, int *indexp);
  static const char	*EcpTypeName(int type);

/*
 * GLOBAL VARIABLES
 */

  const struct cmdtab EcpSetCmds[] =
  {
    { "key string",			"Set encryption key",
	EcpSetCommand, NULL, (void *) SET_KEY },
    { "accept [opt ...]",		"Accept option",
	EcpSetCommand, NULL, (void *) SET_ACCEPT },
    { "deny [opt ...]",			"Deny option",
	EcpSetCommand, NULL, (void *) SET_DENY },
    { "enable [opt ...]",		"Enable option",
	EcpSetCommand, NULL, (void *) SET_ENABLE },
    { "disable [opt ...]",		"Disable option",
	EcpSetCommand, NULL, (void *) SET_DISABLE },
    { "yes [opt ...]",			"Enable and accept option",
	EcpSetCommand, NULL, (void *) SET_YES },
    { "no [opt ...]",			"Disable and deny option",
	EcpSetCommand, NULL, (void *) SET_NO },
    { NULL },
  };

/*
 * INTERNAL VARIABLES
 */

/* These should be listed in order of preference */

  static const EncType gEncTypes[] =
  {
#ifdef ENCRYPTION_DES
    &gDesEncType,
#endif
  };
  #define ECP_NUM_PROTOS	(sizeof(gEncTypes) / sizeof(*gEncTypes))

/* Corresponding option list */

  static const struct confinfo *gConfList;

/* Initializer for struct fsm fields */

  static const struct fsmtype gEcpFsmType =
  {
    "ECP",
    PROTO_ECP,
    ECP_KNOWN_CODES,
    LG_ECP, LG_ECP2,
    FALSE,
    NULL,
    EcpLayerUp,
    EcpLayerDown,
    NULL,
    NULL,
    EcpBuildConfigReq,
    EcpDecodeConfig,
    EcpConfigure,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    EcpFailure,
    EcpRecvResetReq,
    EcpRecvResetAck,
  };

/* Names for different types of encryption */

  static const struct ecpname
  {
    u_char	type;
    const char	*name;
  }
  gEcpTypeNames[] =
  {
    { ECP_TY_OUI,	"OUI" },
    { ECP_TY_DES,	"DES" },
    { 0,		NULL },
  };

/*
 * EcpInit()
 */

void
EcpInit(void)
{
  EcpState	ecp = &bund->ecp;

/* Init ECP state for this bundle */

  memset(ecp, 0, sizeof(*ecp));
  FsmInit(&ecp->fsm, &gEcpFsmType);
  ecp->fsm.conf.maxfailure = ECP_MAXFAILURE;

/* Construct options list if we haven't done so already */

  if (gConfList == NULL)
  {
    struct confinfo	*ci;
    int			k;

    ci = Malloc(MB_CRYPT, (ECP_NUM_PROTOS + 1) * sizeof(*ci));
    for (k = 0; k < ECP_NUM_PROTOS; k++)
    {
      ci[k].option = k;
      ci[k].peered = TRUE;
      ci[k].name = gEncTypes[k]->name;
    }
    ci[k].name = NULL;
    gConfList = (const struct confinfo *) ci;
  }
}

/*
 * EcpConfigure()
 */

static void
EcpConfigure(Fsm fp)
{
  EcpState	const ecp = &bund->ecp;
  int		k;

/* Reset state */

  memset(&ecp->stat, 0, sizeof(ecp->stat));
  for (k = 0; k < ECP_NUM_PROTOS; k++)
  {
    EncType	const et = gEncTypes[k];

    if (et->Configure)
      (*et->Configure)();
  }
  ecp->xmit = NULL;
  ecp->recv = NULL;
  ecp->self_reject = 0;
  ecp->peer_reject = 0;
}

/*
 * EcpDataOutput()
 *
 * Encrypt a frame. Consumes the original packet.
 */

Mbuf
EcpDataOutput(Mbuf plain, int *protop)
{
  EcpState	const ecp = &bund->ecp;
  Mbuf		cypher, header;

/* Prepend protocol field */

  assert(ecp->fsm.state == ST_OPENED);
  header = mballoc(MB_CRYPT, 2);
  header->cnt = 0;
  if (!PROT_COMPRESSIBLE(*protop))
    MBDATA(header)[header->cnt++] = *protop >> 8;
  MBDATA(header)[header->cnt++] = *protop & 0xff;
  header->next = plain;
  plain = header;

/* Encrypt packet */

  LogDumpBp(LG_ECP2, plain, "[%s] xmit plain proto %s",
    bund->name, ProtoName(*protop));
  if (!ecp->xmit)
  {
    Log(LG_ERR, ("%s: no encryption for xmit", Pref(&ecp->fsm)));
    PFREE(plain);
    return(NULL);
  }
  cypher = (*ecp->xmit->Encrypt)(plain);
  LogDumpBp(LG_ECP2, cypher, "[%s] xmit cypher", bund->name);

/* Return result, with new protocol number */

  ecp->stat.outPackets++;
  *protop = PROTO_CRYPT;
  return(cypher);
}

/*
 * EcpDataInput()
 *
 * Decrypt incoming packet. If packet got garbled, return NULL.
 * In any case, we consume the packet passed to us.
 */

Mbuf
EcpDataInput(Mbuf cypher, int *protop)
{
  EcpState	const ecp = &bund->ecp;
  u_int16_t	proto;
  Mbuf		plain;

  assert(ecp->fsm.state == ST_OPENED);
  LogDumpBp(LG_ECP2, cypher, "[%s] recv cypher", bund->name);

/* Decrypt packet */

  if (!ecp->recv)
  {
    Log(LG_ERR, ("%s: no encryption for recv", Pref(&ecp->fsm)));
    PFREE(cypher);
    return(NULL);
  }

  LogDumpBp(LG_ECP2, cypher, "[%s] recv cypher", bund->name);
  plain = (*ecp->recv->Decrypt)(cypher);

/* Encrypted ok? */

  if (plain == NULL)
  {
    Log(LG_ECP, ("%s: decryption failed", Pref(&ecp->fsm)));
    ecp->stat.inPacketDrops++;
    return(NULL);
  }
  LogDumpBp(LG_ECP2, plain, "[%s] recv plain", bund->name);
  ecp->stat.inPackets++;

/* Extract protocol number */

  for (proto = 0; !(proto & 1) && plain->cnt > 0; plain->offset++, plain->cnt--)
    proto = (proto << 8) + *MBDATA(plain);
  *protop = proto;

/* Done */

  return(plain);
}

/*
 * EcpUp()
 */

void
EcpUp(void)
{
  FsmUp(&bund->ecp.fsm);
}

/*
 * EcpDown()
 */

void
EcpDown(void)
{
  FsmDown(&bund->ecp.fsm);
}

/*
 * EcpOpen()
 */

void
EcpOpen(void)
{
  FsmOpen(&bund->ecp.fsm);
}

/*
 * EcpClose()
 */

void
EcpClose(void)
{
  FsmClose(&bund->ecp.fsm);
}

/*
 * EcpFailure()
 *
 * This is fatal to the entire link if encryption is required.
 */

static void
EcpFailure(Fsm f, enum fsmfail reason)
{
  if (Enabled(&bund->conf.options, BUND_CONF_CRYPT_REQD))
    FsmFailure(&bund->ipcp.fsm, FAIL_CANT_ENCRYPT);
}

/*
 * EcpStat()
 */

int
EcpStat(int ac, char *av[], void *arg)
{
  EcpState	const ecp = &bund->ecp;

  printf("%s [%s]\n", Pref(&ecp->fsm), FsmStateName(ecp->fsm.state));
  printf("Enabled protocols:\n");
  OptStat(&ecp->options, gConfList);
  printf("Incoming encryption:\n");
  printf("\tProtocol  : %5s\n", ecp->xmit ? ecp->xmit->name : "none");
  printf("\tRecv pkts : %5d\n", ecp->stat.inPackets);
  printf("\tRecv drops: %5d\n", ecp->stat.inPacketDrops);
  printf("Outgoing encryption:\n");
  printf("\tProtocol  : %5s\n", ecp->recv ? ecp->recv->name : "none");
  printf("\tXmit pkts : %5d\n", ecp->stat.outPackets);
  return(0);
}

/*
 * EcpSendResetReq()
 */

void
EcpSendResetReq(Fsm fp)
{
  EcpState	const ecp = &bund->ecp;
  EncType	const et = ecp->recv;
  Mbuf		bp = NULL;

  assert(et);
  if (et->SendResetReq)
    bp = (*et->SendResetReq)();
  Log(LG_ECP, ("%s: SendResetReq", Pref(fp)));
  FsmOutputMbuf(fp, CODE_RESETREQ, fp->reqid, bp);
}

/*
 * EcpRecvResetReq()
 */

void
EcpRecvResetReq(Fsm fp, int id, Mbuf bp)
{
  EcpState	const ecp = &bund->ecp;
  EncType	const et = ecp->xmit;

  bp = (et && et->RecvResetReq) ? (*et->RecvResetReq)(id, bp) : NULL;
  Log(fp->log, ("%s: SendResetAck", Pref(fp)));
  FsmOutputMbuf(fp, CODE_RESETACK, fp->reqid, bp);
}

/*
 * EcpRecvResetAck()
 */

static void
EcpRecvResetAck(Fsm fp, int id, Mbuf bp)
{
  EcpState	const ecp = &bund->ecp;
  EncType	const et = ecp->recv;

  if (et && et->RecvResetAck)
    (*et->RecvResetAck)(id, bp);
}

/*
 * EcpInput()
 */

void
EcpInput(Mbuf bp, int linkNum)
{
  FsmInput(&bund->ecp.fsm, bp, linkNum);
}

/*
 * EcpBuildConfigReq()
 */

static u_char *
EcpBuildConfigReq(Fsm fp, u_char *cp)
{
  EcpState	const ecp = &bund->ecp;
  int		type;

/* Put in all options that peer hasn't rejected */

  for (ecp->recv = NULL, type = 0; type < ECP_NUM_PROTOS; type++)
  {
    EncType	const et = gEncTypes[type];

    if (Enabled(&ecp->options, type) && !ECP_PEER_REJECTED(ecp, type))
    {
      cp = (*et->BuildConfigReq)(cp);
      if (!ecp->recv)
	ecp->recv = et;
    }
  }
  return(cp);
}

/*
 * EcpLayerUp()
 *
 * Called when ECP has reached the OPENED state
 */

static void
EcpLayerUp(Fsm fp)
{
  EcpState	const ecp = &bund->ecp;

  Log(LG_ECP, (" Encrypt = %s, Decrypt = %s",
    ecp->xmit ? ecp->xmit->name : "none",
    ecp->recv ? ecp->recv->name : "none"));

  /* Initialize */
  if (ecp->xmit && ecp->xmit->Init)
    (*ecp->xmit->Init)(TRUE);
  if (ecp->recv && ecp->recv->Init)
    (*ecp->recv->Init)(FALSE);

  /* Update interface MTU */
  BundUpdateParams();
}

/*
 * EcpLayerDown()
 *
 * Called when ECP leaves the OPENED state
 */

static void
EcpLayerDown(Fsm fp)
{
  EcpState	const ecp = &bund->ecp;

  if (ecp->xmit && ecp->xmit->Cleanup)
    (ecp->xmit->Cleanup)(TRUE);
  if (ecp->recv && ecp->recv->Cleanup)
    (ecp->recv->Cleanup)(FALSE);
}

/*
 * EcpDecodeConfig()
 */

static void
EcpDecodeConfig(Fsm fp, FsmOption list, int num, int mode)
{
  EcpState	const ecp = &bund->ecp;
  u_int		ackSizeSave, rejSizeSave;
  int		k, rej;

/* Decode each config option */

  for (k = 0; k < num; k++)
  {
    FsmOption	const opt = &list[k];
    int		index;
    EncType	et;

    Log(LG_ECP, (" %s", EcpTypeName(opt->type)));
    if ((et = EcpFindType(opt->type, &index)) == NULL)
    {
      if (mode == MODE_REQ)
      {
	Log(LG_ECP, ("   Not supported"));
	FsmRej(fp, opt);
      }
      continue;
    }
    switch (mode)
    {
      case MODE_REQ:
	ackSizeSave = gAckSize;
	rejSizeSave = gRejSize;
	rej = (!Acceptable(&ecp->options, index)
	  || ECP_SELF_REJECTED(ecp, index)
	  || (ecp->xmit && ecp->xmit != et));
	if (rej)
	{
	  (*et->DecodeConfig)(fp, opt, MODE_NOP);
	  FsmRej(fp, opt);
	  break;
	}
	(*et->DecodeConfig)(fp, opt, mode);
	if (gRejSize != rejSizeSave)		/* we rejected it */
	{
	  ECP_SELF_REJ(ecp, index);
	  break;
	}
	if (gAckSize != ackSizeSave)		/* we accepted it */
	  ecp->xmit = et;
	break;

      case MODE_NAK:
	(*et->DecodeConfig)(fp, opt, mode);
	break;

      case MODE_REJ:
	ECP_PEER_REJ(ecp, index);
	break;

      case MODE_NOP:
	(*et->DecodeConfig)(fp, opt, mode);
	break;
    }
  }
}

/*
 * EcpSubtractBloat()
 *
 * Given that "size" is our MTU, return the maximum length frame
 * we can encrypt without the result being longer than "size".
 */

int
EcpSubtractBloat(int size)
{
  EcpState	const ecp = &bund->ecp;

  /* Account for ECP's protocol number overhead */
  if (OPEN_STATE(ecp->fsm.state))
    size -= ECP_OVERHEAD;

  /* Check transmit encryption */
  if (OPEN_STATE(ecp->fsm.state) && ecp->xmit && ecp->xmit->SubtractBloat)
    size = (*ecp->xmit->SubtractBloat)(size);

  /* Done */
  return(size);
}

/*
 * EcpSetCommand()
 */

static int
EcpSetCommand(int ac, char *av[], void *arg)
{
  EcpState	const ecp = &bund->ecp;

  if (ac == 0)
    return(-1);
  switch ((intptr_t)arg)
  {
    case SET_KEY:
      if (ac != 1)
	return(-1);
      snprintf(ecp->key, sizeof(ecp->key), "%s", av[0]);
      break;

    case SET_ACCEPT:
      AcceptCommand(ac, av, &ecp->options, gConfList);
      break;

    case SET_DENY:
      DenyCommand(ac, av, &ecp->options, gConfList);
      break;

    case SET_ENABLE:
      EnableCommand(ac, av, &ecp->options, gConfList);
      break;

    case SET_DISABLE:
      DisableCommand(ac, av, &ecp->options, gConfList);
      break;

    case SET_YES:
      YesCommand(ac, av, &ecp->options, gConfList);
      break;

    case SET_NO:
      NoCommand(ac, av, &ecp->options, gConfList);
      break;

    default:
      assert(0);
  }
  return(0);
}

/*
 * EcpFindType()
 */

static EncType
EcpFindType(int type, int *indexp)
{
  int	k;

  for (k = 0; k < ECP_NUM_PROTOS; k++)
    if (gEncTypes[k]->type == type)
    {
      if (indexp)
	*indexp = k;
      return(gEncTypes[k]);
    }
  return(NULL);
}

/*
 * EcpTypeName()
 */

static const char *
EcpTypeName(int type)
{
  const struct ecpname	*p;
  static char		buf[20];

  for (p = gEcpTypeNames; p->name; p++)
    if (p->type == type)
      return(p->name);
  snprintf(buf, sizeof(buf), "UNKNOWN[%d]", type);
  return(buf);
}


