/*-
 * Copyright (c) 2000 Semen Ustimenko <semenu@FreeBSD.org>
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 *
 * $FreeBSD: src/usr.sbin/ppp/mppe.c,v 1.3 2000/11/05 03:25:09 brian Exp $
 */

#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#ifdef __FreeBSD__
#include <sha.h>
#else
#include <openssl/sha.h>
#endif
#include <openssl/rc4.h>

#include "defs.h"
#include "mbuf.h"
#include "log.h"
#include "timer.h"
#include "fsm.h"
#include "lqr.h"
#include "hdlc.h"
#include "lcp.h"
#include "ccp.h"
#include "chap_ms.h"
#include "mppe.h"

/*
 * Documentation:
 *
 * draft-ietf-pppext-mppe-04.txt
 * draft-ietf-pppext-mppe-keys-02.txt
 */

struct mppe_state {
	int	cohnum;
	int	keylen;			/* 8 or 16 bytes */
	int 	keybits;		/* 40, 56 or 128 bits */
	char	sesskey[MPPE_KEY_LEN];
	char	mastkey[MPPE_KEY_LEN];
	RC4_KEY	rc4key;
};

int MPPE_MasterKeyValid = 0;
char MPPE_MasterKey[MPPE_KEY_LEN];

static void
MPPEResetOutput(void *v)
{
  log_Printf(LogCCP, "MPPE: Output channel reset\n");
}

static void
MPPEReduceSessionKey(struct mppe_state *mp)
{
  switch(mp->keybits) {
  case 40:
    mp->sesskey[2] = 0x9e;
    mp->sesskey[1] = 0x26;
  case 56:
    mp->sesskey[0] = 0xd1;
  case 128:
  }
}

static void
MPPEKeyChange(struct mppe_state *mp)
{
  char InterimKey[MPPE_KEY_LEN];
  RC4_KEY RC4Key;

  GetNewKeyFromSHA(mp->mastkey, mp->sesskey, mp->keylen, InterimKey);
  RC4_set_key(&RC4Key, mp->keylen, InterimKey);
  RC4(&RC4Key, mp->keylen, InterimKey, mp->sesskey);

  MPPEReduceSessionKey(mp);
}

static struct mbuf *
MPPEOutput(void *v, struct ccp *ccp, struct link *l, int pri, u_short *proto,
           struct mbuf *mp)
{
  struct mppe_state *mop = (struct mppe_state *)v;
  struct mbuf *mo;
  u_short nproto;
  int ilen;
  char *rp;

  log_Printf(LogCCP, "MPPE: Output\n");

  ilen = m_length(mp);

  log_Printf(LogDEBUG, "MPPE: Output: Proto %02x (%d bytes)\n", *proto, ilen);
  if (*proto < 0x21 && *proto > 0xFA) {
    log_Printf(LogDEBUG, "MPPE: Output: Not encrypting\n");
    return mp;
  }

  log_DumpBp(LogDEBUG, "MPPE: Output: Encrypt packet:", mp);

  /* Get mbuf for prefixes */
  mo = m_get(4, MB_CCPOUT);
  mo->m_next = mp;

  /* Init RC4 keys */
  RC4_set_key(&mop->rc4key, mop->keylen, mop->sesskey);

  /* Set MPPE packet prefix */
  rp = MBUF_CTOP(mo);
  *(u_short *)rp = htons(0x9000 | mop->cohnum);

  /* Save encrypted protocol number */
  nproto = htons(*proto);
  RC4(&mop->rc4key, 2, (char *)&nproto, rp + 2);

  /* Encrypt main packet */
  rp = MBUF_CTOP(mp);
  RC4(&mop->rc4key, ilen, rp, rp);

  /* Rotate keys */
  MPPEKeyChange(mop);
  mop->cohnum ++; mop->cohnum &= 0xFFF;

  /* Chage protocol number */
  *proto = ccp_Proto(ccp);

  log_Printf(LogDEBUG, "MPPE: Output: Encrypted: Proto %02x (%d bytes)\n",
             *proto, m_length(mo));

  return mo;
}

static void
MPPEResetInput(void *v)
{
  log_Printf(LogCCP, "MPPE: Input channel reset\n");
}

static struct mbuf *
MPPEInput(void *v, struct ccp *ccp, u_short *proto, struct mbuf *mp)
{
  struct mppe_state *mip = (struct mppe_state *)v;
  u_short prefix;
  char *rp;
  int ilen;

  log_Printf(LogCCP, "MPPE: Input\n");

  ilen = m_length(mp);

  log_Printf(LogDEBUG, "MPPE: Input: Proto %02x (%d bytes)\n", *proto, ilen);

  log_DumpBp(LogDEBUG, "MPPE: Input: Packet:", mp);

  mp = mbuf_Read(mp, &prefix, 2);
  prefix = ntohs(prefix);
  if ((prefix & 0xF000) != 0x9000) {
    log_Printf(LogERROR, "MPPE: Input: Invalid packet\n");
    m_freem(mp);
    return NULL;
  }

  prefix &= 0xFFF;
  while (prefix != mip->cohnum) {
    MPPEKeyChange(mip);
    mip->cohnum ++; mip->cohnum &= 0xFFF;
  }

  RC4_set_key(&mip->rc4key, mip->keylen, mip->sesskey);

  mp = mbuf_Read(mp, proto, 2);
  RC4(&mip->rc4key, 2, (char *)proto, (char *)proto);
  *proto = ntohs(*proto);

  rp = MBUF_CTOP(mp);
  RC4(&mip->rc4key, m_length(mp), rp, rp);

  log_Printf(LogDEBUG, "MPPE: Input: Decrypted: Proto %02x (%d bytes)\n",
             *proto, m_length(mp));

  log_DumpBp(LogDEBUG, "MPPE: Input: Decrypted: Packet:", mp);

  return mp;
}

static void
MPPEDictSetup(void *v, struct ccp *ccp, u_short proto, struct mbuf *mi)
{
  log_Printf(LogCCP, "MPPE: DictSetup\n");
}

static const char *
MPPEDispOpts(struct lcp_opt *o)
{
  static char buf[32];
  sprintf(buf, "value 0x%08x", (int)ntohl(*(u_int32_t *)(o->data)));
  return buf;
}

static void
MPPEInitOptsOutput(struct lcp_opt *o, const struct ccp_config *cfg)
{
  u_long val;

  o->len = 6;

  log_Printf(LogCCP, "MPPE: InitOptsOutput\n");

  if (!MPPE_MasterKeyValid) {
    log_Printf(LogWARN, "MPPE: MasterKey is invalid,"
               " MPPE is capable only with CHAP81 authentication\n");
    *(u_int32_t *)o->data = htonl(0x0);
    return;
  }

  val = 0x1000000;
  switch(cfg->mppe.keybits) {
  case 128:
    val |= 0x40; break;
  case 56:
    val |= 0x80; break;
  case 40:
    val |= 0x20; break;
  }
  *(u_int32_t *)o->data = htonl(val);
}

static int
MPPESetOptsOutput(struct lcp_opt *o)
{
  u_long *p = (u_long *)(o->data);
  u_long val = ntohl(*p);

  log_Printf(LogCCP, "MPPE: SetOptsOutput\n");

  if (!MPPE_MasterKeyValid) {
    if (*p != 0x0) {
      *p = 0x0;
      return MODE_NAK;
    } else {
      return MODE_ACK;
    }
  }

  if (val == 0x01000020 ||
      val == 0x01000040 ||
      val == 0x01000080)
    return MODE_ACK;

  return MODE_NAK;
}

static int
MPPESetOptsInput(struct lcp_opt *o, const struct ccp_config *cfg)
{
  u_long *p = (u_long *)(o->data);
  u_long val = ntohl(*p);
  u_long mval;

  log_Printf(LogCCP, "MPPE: SetOptsInput\n");

  if (!MPPE_MasterKeyValid) {
    if (*p != 0x0) {
      *p = 0x0;
      return MODE_NAK;
    } else {
      return MODE_ACK;
    }
  }

  mval = 0x01000000;
  switch(cfg->mppe.keybits) {
  case 128:
    mval |= 0x40; break;
  case 56:
    mval |= 0x80; break;
  case 40:
    mval |= 0x20; break;
  }

  if (val == mval)
    return MODE_ACK;

  *p = htonl(mval);

  return MODE_NAK;
}

static void *
MPPEInitInput(struct lcp_opt *o)
{
  struct mppe_state *mip;
  u_int32_t val = ntohl(*(unsigned long *)o->data);

  log_Printf(LogCCP, "MPPE: InitInput\n");

  if (!MPPE_MasterKeyValid) {
    log_Printf(LogERROR, "MPPE: InitInput: MasterKey is invalid!!!!\n");
    return NULL;
  }

  mip = malloc(sizeof(*mip));
  memset(mip, 0, sizeof(*mip));

  if (val & 0x20) {		/* 40-bits */
    mip->keylen = 8;
    mip->keybits = 40;
  } else if (val & 0x80) {	/* 56-bits */
    mip->keylen = 8;
    mip->keybits = 56;
  } else {			/* 128-bits */
    mip->keylen = 16;
    mip->keybits = 128;
  }

  log_Printf(LogDEBUG, "MPPE: InitInput: %d-bits\n", mip->keybits);

  GetAsymetricStartKey(MPPE_MasterKey, mip->mastkey, mip->keylen, 0, 0);
  GetNewKeyFromSHA(mip->mastkey, mip->mastkey, mip->keylen, mip->sesskey);

  MPPEReduceSessionKey(mip);

  MPPEKeyChange(mip);

  mip->cohnum = 0;

  return mip;
}

static void *
MPPEInitOutput(struct lcp_opt *o)
{
  struct mppe_state *mop;
  u_int32_t val = ntohl(*(unsigned long *)o->data);

  log_Printf(LogCCP, "MPPE: InitOutput\n");

  if (!MPPE_MasterKeyValid) {
    log_Printf(LogERROR, "MPPE: InitOutput: MasterKey is invalid!!!!\n");
    return NULL;
  }

  mop = malloc(sizeof(*mop));
  memset(mop, 0, sizeof(*mop));

  if (val & 0x20) {		/* 40-bits */
    mop->keylen = 8;
    mop->keybits = 40;
  } else if (val & 0x80) {	/* 56-bits */
    mop->keylen = 8;
    mop->keybits = 56;
  } else {			/* 128-bits */
    mop->keylen = 16;
    mop->keybits = 128;
  }

  log_Printf(LogDEBUG, "MPPE: InitOutput: %d-bits\n", mop->keybits);

  GetAsymetricStartKey(MPPE_MasterKey, mop->mastkey, mop->keylen, 1, 0);
  GetNewKeyFromSHA(mop->mastkey, mop->mastkey, mop->keylen, mop->sesskey);

  MPPEReduceSessionKey(mop);

  MPPEKeyChange(mop);

  mop->cohnum = 0;

  return mop;
}

static void
MPPETermInput(void *v)
{
  log_Printf(LogCCP, "MPPE: TermInput\n");
  free(v);
}

static void
MPPETermOutput(void *v)
{
  log_Printf(LogCCP, "MPPE: TermOutput\n");
  free(v);
}

const struct ccp_algorithm MPPEAlgorithm = {
  TY_MPPE,
  CCP_NEG_MPPE,
  MPPEDispOpts,
  {
    MPPESetOptsInput,
    MPPEInitInput,
    MPPETermInput,
    MPPEResetInput,
    MPPEInput,
    MPPEDictSetup
  },
  {
    MPPEInitOptsOutput,
    MPPESetOptsOutput,
    MPPEInitOutput,
    MPPETermOutput,
    MPPEResetOutput,
    MPPEOutput
  },
};
