
/*
 * ccp_mppc.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 "ccp.h"
#include "msoft.h"
#include "ngfunc.h"
#include "bund.h"
#include "radius.h"
#include <md4.h>

#include <netgraph/ng_message.h>
#include <netgraph/ng_ppp.h>
#include <netgraph.h>

/*
 * This implements both MPPC compression and MPPE encryption.
 */

/*
 * DEFINITIONS
 */

  /* #define DEBUG_KEYS */

#ifdef ENCRYPTION_MPPE
#define MPPC_SUPPORTED	(MPPC_BIT | MPPE_BITS | MPPE_STATELESS)
#else
#define MPPC_SUPPORTED	(MPPC_BIT | MPPE_STATELESS)
#endif

/*
 * INTERNAL FUNCTIONS
 */

  static int	MppcInit(int dir);
  static char	*MppcDescribe(int xmit);
  static int	MppcSubtractBloat(int size);
  static void	MppcCleanup(int dir);
  static u_char	*MppcBuildConfigReq(u_char *cp);
  static void	MppcDecodeConfigReq(Fsm fp, FsmOption opt, int mode);
  static Mbuf	MppcRecvResetReq(int id, Mbuf bp, int *noAck);
  static char	*MppcDescribeBits(u_int32_t bits);
  static int	MppcNegotiated(int xmit);

  /* Encryption stuff */
#ifdef ENCRYPTION_MPPE
  static void	MppeInitKey(MppcInfo mppc, int dir);
  static int	MppeGetKeyInfo(char **secretp, u_char **challengep);
  static void	MppeInitKeyv2(MppcInfo mppc, int dir);
  static int	MppeGetKeyInfov2(char **secretp, u_char **responsep);
  static short	MppcEnabledMppeType(short type);
  static short	MppcAcceptableMppeType(short type);  

#ifdef DEBUG_KEYS
  static void	KeyDebug(const u_char *data, int len, const char *fmt, ...);
  #define KEYDEBUG(x)	KeyDebug x
#else
  #define KEYDEBUG(x)
#endif

#endif	/* ENCRYPTION_MPPE */

/*
 * GLOBAL VARIABLES
 */

  const struct comptype	gCompMppcInfo = {
    "mppc",
    CCP_TY_MPPC,
    MppcInit,
    NULL,
    MppcDescribe,
    MppcSubtractBloat,
    MppcCleanup,
    MppcBuildConfigReq,
    MppcDecodeConfigReq,
    NULL,
    MppcRecvResetReq,
    NULL,
    MppcNegotiated,
  };

/*
 * MppcInit()
 */

static int
MppcInit(int dir)
{
  MppcInfo		const mppc = &bund->ccp.mppc;
  struct ng_mppc_config	conf;
  struct ngm_mkpeer	mp;
  char			path[NG_PATHLEN + 1];
  const char		*mppchook, *ppphook;
  int			mschap;
  int			cmd;

  /* Which type of MS-CHAP did we do? */
  if (bund->links[0]->originate == LINK_ORIGINATE_LOCAL)
    mschap = lnk->lcp.peer_chap_alg;
  else
    mschap = lnk->lcp.want_chap_alg;

  /* Initialize configuration structure */
  memset(&conf, 0, sizeof(conf));
  conf.enable = 1;
  switch (dir) {
    case COMP_DIR_XMIT:
      cmd = NGM_MPPC_CONFIG_COMP;
      ppphook = NG_PPP_HOOK_COMPRESS;
      mppchook = NG_MPPC_HOOK_COMP;
      conf.bits = mppc->xmit_bits;
#ifdef ENCRYPTION_MPPE
      if (mschap == CHAP_ALG_MSOFTv2) {
        MppeInitKeyv2(mppc, dir);
      } else {
        MppeInitKey(mppc, dir);
      }
      memcpy(conf.startkey, mppc->xmit_key0, sizeof(conf.startkey));
#endif
      break;
    case COMP_DIR_RECV:
      cmd = NGM_MPPC_CONFIG_DECOMP;
      ppphook = NG_PPP_HOOK_DECOMPRESS;
      mppchook = NG_MPPC_HOOK_DECOMP;
      conf.bits = mppc->recv_bits;
#ifdef ENCRYPTION_MPPE
      if (mschap == CHAP_ALG_MSOFTv2) {
        MppeInitKeyv2(mppc, dir);
      } else {
        MppeInitKey(mppc, dir);
      }
      memcpy(conf.startkey, mppc->recv_key0, sizeof(conf.startkey));
#endif
      break;
    default:
      assert(0);
      return(-1);
  }

  /* Attach a new MPPC node to the PPP node */
  snprintf(mp.type, sizeof(mp.type), "%s", NG_MPPC_NODE_TYPE);
  snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", ppphook);
  snprintf(mp.peerhook, sizeof(mp.peerhook), "%s", mppchook);
  if (NgSendMsg(bund->csock, MPD_HOOK_PPP,
      NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
    Log(LG_ERR, ("[%s] can't create %s node: %s",
      bund->name, mp.type, strerror(errno)));
    return(-1);
  }

  /* Configure MPPC node */
  snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, ppphook);
  if (NgSendMsg(bund->csock, path,
      NGM_MPPC_COOKIE, cmd, &conf, sizeof(conf)) < 0) {
    Log(LG_ERR, ("[%s] can't config %s node at %s: %s",
      bund->name, NG_MPPC_NODE_TYPE, path, strerror(errno)));
    NgFuncDisconnect(MPD_HOOK_PPP, ppphook);
    return(-1);
  }

  /* Done */
  return(0);
}

/*
 * MppcDescribe()
 */

static char *
MppcDescribe(int dir)
{
  MppcInfo	const mppc = &bund->ccp.mppc;

  switch (dir) {
    case COMP_DIR_XMIT:
      return(MppcDescribeBits(mppc->xmit_bits));
    case COMP_DIR_RECV:
      return(MppcDescribeBits(mppc->recv_bits));
    default:
      assert(0);
      return(NULL);
  }
}

/*
 * MppcSubtractBloat()
 */

static int
MppcSubtractBloat(int size)
{

  /* Account for MPPC header */
  size -= 2;

  /* Account for possible expansion with MPPC compression */
  if ((bund->ccp.mppc.xmit_bits & MPPC_BIT) != 0) {
    int	l, h, size0 = size;

    while (1) {
      l = MPPC_MAX_BLOWUP(size0);
      h = MPPC_MAX_BLOWUP(size0 + 1);
      if (l > size) {
	size0 -= 20;
      } else if (h > size) {
	size = size0;
	break;
      } else {
	size0++;
      }
    }
  }

  /* Done */
  return(size);
}

/*
 * MppcNegotiated()
 */

static int
MppcNegotiated(int dir)
{
  MppcInfo	const mppc = &bund->ccp.mppc;

  switch (dir) {
    case COMP_DIR_XMIT:
      return(mppc->xmit_bits != 0);
    case COMP_DIR_RECV:
      return(mppc->recv_bits != 0);
    default:
      assert(0);
      return(0);
  }
}

/*
 * MppcCleanup()
 */

static void
MppcCleanup(int dir)
{
  const char	*ppphook;
  char		path[NG_PATHLEN + 1];

  /* Remove node */
  switch (dir) {
    case COMP_DIR_XMIT:
      ppphook = NG_PPP_HOOK_DECOMPRESS;
      break;
    case COMP_DIR_RECV:
      ppphook = NG_PPP_HOOK_COMPRESS;
      break;
    default:
      assert(0);
      return;
  }
  snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, ppphook);
  (void)NgFuncShutdownNode(bund, bund->name, path);
}

/*
 * MppcBuildConfigReq()
 */

static u_char *
MppcBuildConfigReq(u_char *cp)
{
  CcpState	const ccp = &bund->ccp;
  MppcInfo	const mppc = &ccp->mppc;
  u_int32_t	bits = 0;

  /* Compression */
  if (Enabled(&ccp->options, gMppcCompress)
      && !CCP_PEER_REJECTED(ccp, gMppcCompress))
    bits |= MPPC_BIT;

#ifdef ENCRYPTION_MPPE
  /* Encryption */
  if (MppcEnabledMppeType(40)) bits |= MPPE_40;
#ifndef MPPE_56_UNSUPPORTED
  if (MppcEnabledMppeType(56)) bits |= MPPE_56;
#endif
  if (MppcEnabledMppeType(128)) bits |= MPPE_128;
#endif

  /* Stateless mode */
  if (Enabled(&ccp->options, gMppcStateless)
      && !CCP_PEER_REJECTED(ccp, gMppcStateless)
      && bits != 0)
    bits |= MPPE_STATELESS;

  /* Ship it */
  mppc->recv_bits = bits;
  if (bits != 0)
    cp = FsmConfValue(cp, CCP_TY_MPPC, -4, &bits);
  return(cp);
}

/*
 * MppcDecodeConfigReq()
 */

static void
MppcDecodeConfigReq(Fsm fp, FsmOption opt, int mode)
{
  CcpState	const ccp = &bund->ccp;
  MppcInfo	const mppc = &ccp->mppc;
  u_int32_t	orig_bits;
  u_int32_t	bits;

  /* Get bits */
  memcpy(&orig_bits, opt->data, 4);
  orig_bits = ntohl(orig_bits);
  bits = orig_bits;

  /* Sanity check */
  if (opt->len != 6) {
    Log(LG_CCP, ("   bogus length %d", opt->len));
    if (mode == MODE_REQ)
      FsmRej(fp, opt);
    return;
  }

  /* Display it */
  Log(LG_CCP, ("   0x%08x:%s", bits, MppcDescribeBits(bits)));

  /* Deal with it */
  switch (mode) {
    case MODE_REQ:

      /* Check for supported bits */
      if (bits & ~MPPC_SUPPORTED) {
	Log(LG_CCP, ("   Bits 0x%08x not supported", bits & ~MPPC_SUPPORTED));
	bits &= MPPC_SUPPORTED;
      }

      /* Check compression */
      if ((bits & MPPC_BIT) && !Acceptable(&ccp->options, gMppcCompress))
	bits &= ~MPPC_BIT;

#ifdef ENCRYPTION_MPPE

      /* Check encryption */
      if ((bits & MPPE_40) && !MppcAcceptableMppeType(40))
	bits &= ~MPPE_40;
#ifndef MPPE_56_UNSUPPORTED
      if ((bits & MPPE_56) && !MppcAcceptableMppeType(56))
#endif
	bits &= ~MPPE_56;
      if ((bits & MPPE_128) && !MppcAcceptableMppeType(128))
	bits &= ~MPPE_128;

      /* Choose the strongest encryption available */
      if (bits & MPPE_128)
	bits &= ~(MPPE_40|MPPE_56);
      else if (bits & MPPE_56)
	bits &= ~(MPPE_40|MPPE_128);
      else if (bits & MPPE_40)
	bits &= ~(MPPE_56|MPPE_128);

      /* It doesn't really make sense to encrypt in only one direction.
	 Also, Win95/98 PPTP can't handle uni-directional encryption. So
	 if the remote side doesn't request encryption, try to prompt it.
	 This is broken wrt. normal PPP negotiation: typical Microsoft. */
      if ((bits & MPPE_BITS) == 0) {
	if (MppcEnabledMppeType(40)) bits |= MPPE_40;
#ifndef MPPE_56_UNSUPPORTED
	if (MppcEnabledMppeType(56)) bits |= MPPE_56;
#endif
	if (MppcEnabledMppeType(128)) bits |= MPPE_128;
      }
#endif

      /* Stateless mode */
      if ((bits & MPPE_STATELESS) && !Acceptable(&ccp->options, gMppcStateless))
	bits &= ~MPPE_STATELESS;

      /* See if what we want equals what was sent */
      mppc->xmit_bits = bits;
      if (bits != orig_bits) {
	bits = htonl(bits);
	memcpy(opt->data, &bits, 4);
	FsmNak(fp, opt);
      }
      else
	FsmAck(fp, opt);
      break;

    case MODE_NAK:
      if (!(bits & MPPC_BIT))
	CCP_PEER_REJ(ccp, gMppcCompress);
#ifdef ENCRYPTION_MPPE
      if (!(bits & MPPE_40))
	CCP_PEER_REJ(ccp, gMppe40);
      if (!(bits & MPPE_56))
	CCP_PEER_REJ(ccp, gMppe56);
      if (!(bits & MPPE_128))
	CCP_PEER_REJ(ccp, gMppe128);
#endif
      if (!(bits & MPPE_STATELESS))
	CCP_PEER_REJ(ccp, gMppcStateless);
      break;
  }
}

/*
 * MppcRecvResetReq()
 */

static Mbuf
MppcRecvResetReq(int id, Mbuf bp, int *noAck)
{
  char	path[NG_PATHLEN + 1];

  /* Forward ResetReq to the MPPC compression node */
  snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, NG_PPP_HOOK_COMPRESS);
  if (NgSendMsg(bund->csock, path,
      NGM_MPPC_COOKIE, NGM_MPPC_RESETREQ, NULL, 0) < 0) {
    Log(LG_ERR, ("[%s] reset-req to %s node: %s",
      bund->name, NG_MPPC_NODE_TYPE, strerror(errno)));
  }

  /* No ResetAck required for MPPC */
  if (noAck)
    *noAck = 1;
  return(NULL);
}

/*
 * MppcDescribeBits()
 */

static char *
MppcDescribeBits(u_int32_t bits)
{
  static char	buf[100];

  *buf = 0;
  if (bits & MPPC_BIT)
    snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " MPPC");
  if (bits & MPPE_BITS) {
    snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " MPPE");
    if (bits & MPPE_40)
      snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ", 40 bit");
    if (bits & MPPE_56)
      snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ", 56 bit");
    if (bits & MPPE_128)
      snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ", 128 bit");
    if (bits & MPPE_STATELESS)
      snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ", stateless");
  }
  return(buf);
}

static short
MppcEnabledMppeType(short type)
{
  CcpState	const ccp = &bund->ccp;
  struct radius	*rad = &bund->radius;
  short		ret, radius = FALSE;
 
  switch (type) {
  case 40:
    if (Enabled(&ccp->options, gCcpRadius) && rad->valid) {
      radius = TRUE;
      ret = (rad->mppe.types & MPPE_TYPE_40BIT) && !CCP_PEER_REJECTED(ccp, gMppe40);
    } else {
      ret = Enabled(&ccp->options, gMppe40) && !CCP_PEER_REJECTED(ccp, gMppe40);
    }
    break;

#ifndef MPPE_56_UNSUPPORTED
  case 56:
    if (Enabled(&ccp->options, gCcpRadius) && rad->valid) {
      radius = TRUE;    
      ret = (rad->mppe.types & MPPE_TYPE_56BIT) && !CCP_PEER_REJECTED(ccp, gMppe56);
    } else {
      ret = Enabled(&ccp->options, gMppe56) && !CCP_PEER_REJECTED(ccp, gMppe56);
    }

    break;
#endif
      
  case 128:
  default:
    if (Enabled(&ccp->options, gCcpRadius) && rad->valid) {
      radius = TRUE;    
      ret = (rad->mppe.types & MPPE_TYPE_128BIT) && !CCP_PEER_REJECTED(ccp, gMppe128);
    } else {
      ret = Enabled(&ccp->options, gMppe128) && !CCP_PEER_REJECTED(ccp, gMppe128);
    }
  }
  Log(LG_CCP, ("[%s] CCP: Checking whether %d bits are enabled -> %s%s", 
    lnk->name, type, ret ? "yes" : "no", radius ? " (RADIUS)" : "" ));
  return ret;
}

static short
MppcAcceptableMppeType(short type)
{
  CcpState	const ccp = &bund->ccp;
  struct radius	*rad = &bund->radius;
  short		ret, radius = FALSE;
  
  switch (type) {
  case 40:
    if (Enabled(&ccp->options, gCcpRadius) && rad->valid) {
      radius = TRUE;
      ret = rad->mppe.types & MPPE_TYPE_40BIT;
    } else {
      ret = Acceptable(&ccp->options, gMppe40);
    }
    break;

#ifndef MPPE_56_UNSUPPORTED
  case 56:
    if (Enabled(&ccp->options, gCcpRadius) && rad->valid) {
      radius = TRUE;
      ret = rad->mppe.types & MPPE_TYPE_56BIT;
    } else {
      ret = Acceptable(&ccp->options, gMppe56);
    }

    break;
#endif
      
  case 128:
  default:
    if (Enabled(&ccp->options, gCcpRadius) && rad->valid) {
      radius = TRUE;    
      ret = rad->mppe.types & MPPE_TYPE_128BIT;
    } else {
      ret = Acceptable(&ccp->options, gMppe128);
    }
  }

  Log(LG_CCP, ("[%s] CCP: Checking whether %d bits are acceptable -> %s%s", 
    lnk->name, type, ret ? "yes" : "no", radius ? " (RADIUS)" : "" ));
  return ret;

}
#ifdef ENCRYPTION_MPPE

#define KEYLEN(b)	(((b) & MPPE_128) ? 16 : 8)

/*
 * MppeInitKey()
 */

static void
MppeInitKey(MppcInfo mppc, int dir)
{
  u_int32_t	const bits = (dir == COMP_DIR_XMIT) ?
			mppc->xmit_bits : mppc->recv_bits;
  u_char	*const key0 = (dir == COMP_DIR_XMIT) ?
			mppc->xmit_key0 : mppc->recv_key0;
  u_char	hash[16];
  char		*pass;
  u_char	*chal;

  /* Get credential info */
  if (MppeGetKeyInfo(&pass, &chal) < 0)
    return;

  /* Compute basis for the session key (ie, "start key" or key0) */
  if (bits & MPPE_128) {
    MD4_CTX	c;

    if (bund->radius.valid)
      memcpy(hash, bund->radius.mppe.nt_hash, sizeof(hash));
    else {
      NTPasswordHash(pass, hash);
      KEYDEBUG((hash, sizeof(hash), "NTPasswordHash"));
      MD4Init(&c);
      MD4Update(&c, hash, 16);
      MD4Final(hash, &c);
      KEYDEBUG((hash, sizeof(hash), "MD4 of that"));
      KEYDEBUG((chal, CHAP_MSOFT_CHAL_LEN, "Challenge"));
    }
    MsoftGetStartKey(chal, hash);
    KEYDEBUG((hash, sizeof(hash), "NT StartKey"));
  } else {
    if (bund->radius.valid)
      memcpy(hash, bund->radius.mppe.lm_key, 8);
    else
      LMPasswordHash(pass, hash);
    KEYDEBUG((hash, sizeof(hash), "LM StartKey"));
  }
  memcpy(key0, hash, MPPE_KEY_LEN);
  KEYDEBUG((key0, (bits & MPPE_128) ? 16 : 8, "InitialKey"));
}

/*
 * MppeGetKeyInfo()
 *
 * This is described in:
 *   draft-ietf-pppext-mschapv1-keys-00.txt
 */

static int
MppeGetKeyInfo(char **secretp, u_char **challengep)
{
  CcpState		const ccp = &bund->ccp;
  u_char		*challenge;

  /* The secret comes from the originating caller's credentials */
  switch (lnk->originate) {
    case LINK_ORIGINATE_LOCAL:
      if (lnk->lcp.peer_auth != PROTO_CHAP
	  || (lnk->lcp.peer_chap_alg != CHAP_ALG_MSOFT
	    && lnk->lcp.peer_chap_alg != CHAP_ALG_MSOFTv2)) {
	Log(LG_ERR,
	  ("[%s] \"%s chap\" required for MPPE", lnk->name, "accept"));
	goto fail;
      }
      challenge = bund->peer_msChal;
      break;
    case LINK_ORIGINATE_REMOTE:
      if (lnk->lcp.want_auth != PROTO_CHAP
	  || (lnk->lcp.want_chap_alg != CHAP_ALG_MSOFT
	    && lnk->lcp.want_chap_alg != CHAP_ALG_MSOFTv2)) {
	Log(LG_ERR,
	  ("[%s] \"%s chap\" required for MPPE", lnk->name, "enable"));
	goto fail;
      }
      challenge = bund->self_msChal;
      break;
    case LINK_ORIGINATE_UNKNOWN:
    default:
      Log(LG_ERR, ("[%s] can't determine link direction for MPPE", lnk->name));
      goto fail;
  }

  /* Return info */
  *secretp = bund->msPassword;
  *challengep = challenge;  
  return(0);

fail:
  Log(LG_ERR, ("[%s] can't determine credentials for MPPE", bund->name));
  FsmFailure(&ccp->fsm, FAIL_CANT_ENCRYPT);
  return(-1);
}

/*
 * MppeInitKeyv2()
 */

static void
MppeInitKeyv2(MppcInfo mppc, int dir)
{
  u_char	*const key0 = (dir == COMP_DIR_XMIT) ?
			mppc->xmit_key0 : mppc->recv_key0;
  u_char	hash[16];
  char		*pass;
  u_char	*resp;
  MD4_CTX	c;

  /* If using RADIUS, key info comes from the server */
  if (bund->radius.valid) {
    if (dir == COMP_DIR_XMIT) {
      memcpy(mppc->xmit_key0, bund->radius.mppe.sendkey, MPPE_KEY_LEN);
    } else {
      memcpy(mppc->recv_key0, bund->radius.mppe.recvkey, MPPE_KEY_LEN);
    }
    return;
  }

  /* Get credential info */
  if (MppeGetKeyInfov2(&pass, &resp) < 0)
    return;

  /* Compute basis for the session key (ie, "start key" or key0) */
  NTPasswordHash(pass, hash);
  KEYDEBUG((hash, sizeof(hash), "NTPasswordHash"));
  MD4Init(&c);
  MD4Update(&c, hash, 16);
  MD4Final(hash, &c);
  KEYDEBUG((hash, sizeof(hash), "MD4 of that"));
  KEYDEBUG((resp, CHAP_MSOFTv2_CHAL_LEN, "Response"));
  MsoftGetMasterKey(resp, hash);
  KEYDEBUG((hash, sizeof(hash), "GetMasterKey"));
  MsoftGetAsymetricStartKey(hash,
    (dir == COMP_DIR_RECV) ^
      (bund->links[0]->originate == LINK_ORIGINATE_LOCAL));
  KEYDEBUG((hash, sizeof(hash), "GetAsymmetricKey"));
  memcpy(key0, hash, MPPE_KEY_LEN);
  KEYDEBUG((key0, MPPE_KEY_LEN, "InitialKey"));
}

/*
 * MppeGetKeyInfov2()
 */

static int
MppeGetKeyInfov2(char **secretp, u_char **responsep)
{
  CcpState		const ccp = &bund->ccp;
  u_char		*response;

  /* The secret comes from the originating caller's credentials */
  switch (lnk->originate) {
    case LINK_ORIGINATE_LOCAL:
      if (lnk->lcp.peer_auth != PROTO_CHAP
	  || (lnk->lcp.peer_chap_alg != CHAP_ALG_MSOFT
	    && lnk->lcp.peer_chap_alg != CHAP_ALG_MSOFTv2)) {
	Log(LG_ERR,
	  ("[%s] \"%s chap\" required for MPPE", lnk->name, "accept"));
	goto fail;
      }
      response = bund->self_ntResp;
      break;
    case LINK_ORIGINATE_REMOTE:
      if (lnk->lcp.want_auth != PROTO_CHAP
	  || (lnk->lcp.want_chap_alg != CHAP_ALG_MSOFT
	    && lnk->lcp.want_chap_alg != CHAP_ALG_MSOFTv2)) {
	Log(LG_ERR,
	  ("[%s] \"%s chap\" required for MPPE", lnk->name, "enable"));
	goto fail;
      }
      response = bund->peer_ntResp;
      break;
    case LINK_ORIGINATE_UNKNOWN:
    default:
      Log(LG_ERR, ("[%s] can't determine link direction for MPPE", lnk->name));
      goto fail;
  }

  /* Return info */
  *secretp = bund->msPassword;
  *responsep = response;
  return(0);

fail:
  Log(LG_ERR, ("[%s] can't determine credentials for MPPE", bund->name));
  FsmFailure(&ccp->fsm, FAIL_CANT_ENCRYPT);
  return(-1);
}

#endif	/* ENCRYPTION_MPPE */

#ifdef DEBUG_KEYS

/*
 * KeyDebug()
 */

static void
KeyDebug(const u_char *data, int len, const char *fmt, ...)
{
  char		buf[100];
  int		k;
  va_list	args;

  va_start(args, fmt);
  vsnprintf(buf, sizeof(buf), fmt, args);
  va_end(args);
  snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ":");
  for (k = 0; k < len; k++) {
    snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
      " %02x", (u_char) data[k]);
  }
  Log(LG_ERR, ("%s", buf));
}

#endif	/* DEBUG_KEYS */

