C++程序  |  818行  |  20.29 KB

/*-
 * 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.28.26.1 2010/12/21 17:10:29 kensmith Exp $
 */

#include <sys/param.h>

#include <sys/socket.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/un.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#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 "throughput.h"
#include "layer.h"
#include "link.h"
#include "chap_ms.h"
#include "proto.h"
#include "mppe.h"
#include "ua.h"
#include "descriptor.h"
#ifndef NORADIUS
#include "radius.h"
#endif
#include "ncpaddr.h"
#include "iplist.h"
#include "slcompress.h"
#include "ipcp.h"
#include "ipv6cp.h"
#include "filter.h"
#include "mp.h"
#include "ncp.h"
#include "bundle.h"

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

#define	MPPE_OPT_STATELESS	0x1000000
#define	MPPE_OPT_COMPRESSED	0x01
#define	MPPE_OPT_40BIT		0x20
#define	MPPE_OPT_56BIT		0x80
#define	MPPE_OPT_128BIT		0x40
#define	MPPE_OPT_BITMASK	0xe0
#define	MPPE_OPT_MASK		(MPPE_OPT_STATELESS | MPPE_OPT_BITMASK)

#define	MPPE_FLUSHED			0x8000
#define	MPPE_ENCRYPTED			0x1000
#define	MPPE_HEADER_BITMASK		0xf000
#define	MPPE_HEADER_FLAG		0x00ff
#define	MPPE_HEADER_FLAGMASK		0x00ff
#define	MPPE_HEADER_FLAGSHIFT		8
#define	MPPE_HEADER_STATEFUL_KEYCHANGES	16

struct mppe_state {
  unsigned	stateless : 1;
  unsigned	flushnext : 1;
  unsigned	flushrequired : 1;
  int		cohnum;
  unsigned	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;
int MPPE_IsServer = 0;
char MPPE_MasterKey[MPPE_KEY_LEN];

/*
 * The peer has missed a packet.  Mark the next output frame to be FLUSHED
 */
static int
MPPEResetOutput(void *v)
{
  struct mppe_state *mop = (struct mppe_state *)v;

  if (mop->stateless)
    log_Printf(LogCCP, "MPPE: Unexpected output channel reset\n");
  else {
    log_Printf(LogCCP, "MPPE: Output channel reset\n");
    mop->flushnext = 1;
  }

  return 0;		/* Ask FSM not to ACK */
}

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:
    break;
  }
}

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 __unused, int pri __unused,
	   u_short *proto, struct mbuf *mp)
{
  struct mppe_state *mop = (struct mppe_state *)v;
  struct mbuf *mo;
  u_short nproto, prefix;
  int dictinit, ilen, len;
  char *rp;

  ilen = m_length(mp);
  dictinit = 0;

  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");
    ccp->compout += ilen;
    ccp->uncompout += ilen;
    return mp;
  }

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

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

  rp = MBUF_CTOP(mo);
  prefix = MPPE_ENCRYPTED | mop->cohnum;

  if (mop->stateless ||
      (mop->cohnum & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG) {
    /* Change our key */
    log_Printf(LogDEBUG, "MPPEOutput: Key changed [%d]\n", mop->cohnum);
    MPPEKeyChange(mop);
    dictinit = 1;
  }

  if (mop->stateless || mop->flushnext) {
    prefix |= MPPE_FLUSHED;
    dictinit = 1;
    mop->flushnext = 0;
  }

  if (dictinit) {
    /* Initialise our dictionary */
    log_Printf(LogDEBUG, "MPPEOutput: Dictionary initialised [%d]\n",
               mop->cohnum);
    RC4_set_key(&mop->rc4key, mop->keylen, mop->sesskey);
  }

  /* Set MPPE packet prefix */
  ua_htons(&prefix, rp);

  /* 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);

  mop->cohnum++;
  mop->cohnum &= ~MPPE_HEADER_BITMASK;

  /* Set the protocol number */
  *proto = ccp_Proto(ccp);
  len = m_length(mo);
  ccp->uncompout += ilen;
  ccp->compout += len;

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

  return mo;
}

static void
MPPEResetInput(void *v __unused)
{
  log_Printf(LogCCP, "MPPE: Unexpected input channel ack\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 dictinit, flushed, ilen, len, n;

  ilen = m_length(mp);
  dictinit = 0;
  ccp->compin += ilen;

  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);
  flushed = prefix & MPPE_FLUSHED;
  prefix &= ~flushed;
  if ((prefix & MPPE_HEADER_BITMASK) != MPPE_ENCRYPTED) {
    log_Printf(LogERROR, "MPPE: Input: Invalid packet (flags = 0x%x)\n",
               (prefix & MPPE_HEADER_BITMASK) | flushed);
    m_freem(mp);
    return NULL;
  }

  prefix &= ~MPPE_HEADER_BITMASK;

  if (!flushed && mip->stateless) {
    log_Printf(LogCCP, "MPPEInput: Packet without MPPE_FLUSHED set"
               " in stateless mode\n");
    flushed = MPPE_FLUSHED;
    /* Should we really continue ? */
  }

  if (mip->stateless) {
    /* Change our key for each missed packet in stateless mode */
    while (prefix != mip->cohnum) {
      log_Printf(LogDEBUG, "MPPEInput: Key changed [%u]\n", prefix);
      MPPEKeyChange(mip);
      /*
       * mip->cohnum contains what we received last time in stateless
       * mode.
       */
      mip->cohnum++;
      mip->cohnum &= ~MPPE_HEADER_BITMASK;
    }
    dictinit = 1;
  } else {
    if (flushed) {
      /*
       * We can always process a flushed packet.
       * Catch up on any outstanding key changes.
       */
      n = (prefix >> MPPE_HEADER_FLAGSHIFT) -
          (mip->cohnum >> MPPE_HEADER_FLAGSHIFT);
      if (n < 0)
        n += MPPE_HEADER_STATEFUL_KEYCHANGES;
      while (n--) {
        log_Printf(LogDEBUG, "MPPEInput: Key changed during catchup [%u]\n",
                   prefix);
        MPPEKeyChange(mip);
      }
      mip->flushrequired = 0;
      mip->cohnum = prefix;
      dictinit = 1;
    }

    if (mip->flushrequired) {
      /*
       * Perhaps we should be lenient if
       * (prefix & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG
       * The spec says that we shouldn't be though....
       */
      log_Printf(LogDEBUG, "MPPE: Not flushed - discarded\n");
      fsm_Output(&ccp->fsm, CODE_RESETREQ, ccp->fsm.reqid++, NULL, 0,
                 MB_CCPOUT);
      m_freem(mp);
      return NULL;
    }

    if (prefix != mip->cohnum) {
      /*
       * We're in stateful mode and didn't receive the expected
       * packet.  Send a reset request, but don't tell the CCP layer
       * about it as we don't expect to receive a Reset ACK !
       * Guess what... M$ invented this !
       */
      log_Printf(LogCCP, "MPPE: Input: Got seq %u, not %u\n",
                 prefix, mip->cohnum);
      fsm_Output(&ccp->fsm, CODE_RESETREQ, ccp->fsm.reqid++, NULL, 0,
                 MB_CCPOUT);
      mip->flushrequired = 1;
      m_freem(mp);
      return NULL;
    }

    if ((prefix & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG) {
      log_Printf(LogDEBUG, "MPPEInput: Key changed [%u]\n", prefix);
      MPPEKeyChange(mip);
      dictinit = 1;
    } else if (flushed)
      dictinit = 1;

    /*
     * mip->cohnum contains what we expect to receive next time in stateful
     * mode.
     */
    mip->cohnum++;
    mip->cohnum &= ~MPPE_HEADER_BITMASK;
  }

  if (dictinit) {
    log_Printf(LogDEBUG, "MPPEInput: Dictionary initialised [%u]\n", prefix);
    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);
  len = m_length(mp);
  RC4(&mip->rc4key, len, rp, rp);

  log_Printf(LogDEBUG, "MPPEInput: Decrypted: Proto %02x (%d bytes)\n",
             *proto, len);
  log_DumpBp(LogDEBUG, "MPPEInput: Decrypted: Packet:", mp);

  ccp->uncompin += len;

  return mp;
}

static void
MPPEDictSetup(void *v __unused, struct ccp *ccp __unused,
	      u_short proto __unused, struct mbuf *mp __unused)
{
  /* Nothing to see here */
}

static const char *
MPPEDispOpts(struct fsm_opt *o)
{
  static char buf[70];
  u_int32_t val;
  char ch;
  int len, n;

  ua_ntohl(o->data, &val);
  len = 0;
  if ((n = snprintf(buf, sizeof buf, "value 0x%08x ", (unsigned)val)) > 0)
    len += n;
  if (!(val & MPPE_OPT_BITMASK)) {
    if ((n = snprintf(buf + len, sizeof buf - len, "(0")) > 0)
      len += n;
  } else {
    ch = '(';
    if (val & MPPE_OPT_128BIT) {
      if ((n = snprintf(buf + len, sizeof buf - len, "%c128", ch)) > 0)
        len += n;
      ch = '/';
    }
    if (val & MPPE_OPT_56BIT) {
      if ((n = snprintf(buf + len, sizeof buf - len, "%c56", ch)) > 0)
        len += n;
      ch = '/';
    }
    if (val & MPPE_OPT_40BIT) {
      if ((n = snprintf(buf + len, sizeof buf - len, "%c40", ch)) > 0)
        len += n;
      ch = '/';
    }
  }

  if ((n = snprintf(buf + len, sizeof buf - len, " bits, state%s",
                    (val & MPPE_OPT_STATELESS) ? "less" : "ful")) > 0)
    len += n;

  if (val & MPPE_OPT_COMPRESSED) {
    if ((n = snprintf(buf + len, sizeof buf - len, ", compressed")) > 0)
      len += n;
  }

  snprintf(buf + len, sizeof buf - len, ")");

  return buf;
}

static int
MPPEUsable(struct fsm *fp)
{
  int ok;
#ifndef NORADIUS
  struct radius *r = &fp->bundle->radius;

  /*
   * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES,
   * use that instead of our configuration value.
   */
  if (*r->cfg.file) {
    ok = r->mppe.sendkeylen && r->mppe.recvkeylen;
    if (!ok)
      log_Printf(LogCCP, "MPPE: Not permitted by RADIUS server\n");
  } else
#endif
  {
    struct lcp *lcp = &fp->link->lcp;
    ok = (lcp->want_auth == PROTO_CHAP && lcp->want_authtype == 0x81) ||
         (lcp->his_auth == PROTO_CHAP && lcp->his_authtype == 0x81);
    if (!ok)
      log_Printf(LogCCP, "MPPE: Not usable without CHAP81\n");
  }

  return ok;
}

static int
MPPERequired(struct fsm *fp)
{
#ifndef NORADIUS
  /*
   * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY,
   * use that instead of our configuration value.
   */
  if (*fp->bundle->radius.cfg.file && fp->bundle->radius.mppe.policy)
    return fp->bundle->radius.mppe.policy == MPPE_POLICY_REQUIRED ? 1 : 0;
#endif

  return fp->link->ccp.cfg.mppe.required;
}

static u_int32_t
MPPE_ConfigVal(struct bundle *bundle __unused, const struct ccp_config *cfg)
{
  u_int32_t val;

  val = cfg->mppe.state == MPPE_STATELESS ? MPPE_OPT_STATELESS : 0;
#ifndef NORADIUS
  /*
   * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES,
   * use that instead of our configuration value.
   */
  if (*bundle->radius.cfg.file && bundle->radius.mppe.types) {
    if (bundle->radius.mppe.types & MPPE_TYPE_40BIT)
      val |= MPPE_OPT_40BIT;
    if (bundle->radius.mppe.types & MPPE_TYPE_128BIT)
      val |= MPPE_OPT_128BIT;
  } else
#endif
    switch(cfg->mppe.keybits) {
    case 128:
      val |= MPPE_OPT_128BIT;
      break;
    case 56:
      val |= MPPE_OPT_56BIT;
      break;
    case 40:
      val |= MPPE_OPT_40BIT;
      break;
    case 0:
      val |= MPPE_OPT_128BIT | MPPE_OPT_56BIT | MPPE_OPT_40BIT;
      break;
    }

  return val;
}

/*
 * What options should we use for our first configure request
 */
static void
MPPEInitOptsOutput(struct bundle *bundle, struct fsm_opt *o,
                   const struct ccp_config *cfg)
{
  u_int32_t mval;

  o->hdr.len = 6;

  if (!MPPE_MasterKeyValid) {
    log_Printf(LogCCP, "MPPE: MasterKey is invalid,"
               " MPPE is available only with CHAP81 authentication\n");
    mval = 0;
    ua_htonl(&mval, o->data);
    return;
  }


  mval = MPPE_ConfigVal(bundle, cfg);
  ua_htonl(&mval, o->data);
}

/*
 * Our CCP request was NAK'd with the given options
 */
static int
MPPESetOptsOutput(struct bundle *bundle, struct fsm_opt *o,
                  const struct ccp_config *cfg)
{
  u_int32_t mval, peer;

  ua_ntohl(o->data, &peer);

  if (!MPPE_MasterKeyValid)
    /* Treat their NAK as a REJ */
    return MODE_NAK;

  mval = MPPE_ConfigVal(bundle, cfg);

  /*
   * If we haven't been configured with a specific number of keybits, allow
   * whatever the peer asks for.
   */
  if (!cfg->mppe.keybits) {
    mval &= ~MPPE_OPT_BITMASK;
    mval |= (peer & MPPE_OPT_BITMASK);
    if (!(mval & MPPE_OPT_BITMASK))
      mval |= MPPE_OPT_128BIT;
  }

  /* Adjust our statelessness */
  if (cfg->mppe.state == MPPE_ANYSTATE) {
    mval &= ~MPPE_OPT_STATELESS;
    mval |= (peer & MPPE_OPT_STATELESS);
  }

  ua_htonl(&mval, o->data);

  return MODE_ACK;
}

/*
 * The peer has requested the given options
 */
static int
MPPESetOptsInput(struct bundle *bundle, struct fsm_opt *o,
                 const struct ccp_config *cfg)
{
  u_int32_t mval, peer;
  int res = MODE_ACK;

  ua_ntohl(o->data, &peer);
  if (!MPPE_MasterKeyValid) {
    if (peer != 0) {
      peer = 0;
      ua_htonl(&peer, o->data);
      return MODE_NAK;
    } else
      return MODE_ACK;
  }

  mval = MPPE_ConfigVal(bundle, cfg);

  if (peer & ~MPPE_OPT_MASK)
    /* He's asking for bits we don't know about */
    res = MODE_NAK;

  if (peer & MPPE_OPT_STATELESS) {
    if (cfg->mppe.state == MPPE_STATEFUL)
      /* Peer can't have stateless */
      res = MODE_NAK;
    else
      /* Peer wants stateless, that's ok */
      mval |= MPPE_OPT_STATELESS;
  } else {
    if (cfg->mppe.state == MPPE_STATELESS)
      /* Peer must have stateless */
      res = MODE_NAK;
    else
      /* Peer doesn't want stateless, that's ok */
      mval &= ~MPPE_OPT_STATELESS;
  }

  /* If we've got a configured number of keybits - the peer must use that */
  if (cfg->mppe.keybits) {
    ua_htonl(&mval, o->data);
    return peer == mval ? res : MODE_NAK;
  }

  /* If a specific number of bits hasn't been requested, we'll need to NAK */
  switch (peer & MPPE_OPT_BITMASK) {
  case MPPE_OPT_128BIT:
  case MPPE_OPT_56BIT:
  case MPPE_OPT_40BIT:
    break;
  default:
    res = MODE_NAK;
  }

  /* Suggest the best number of bits */
  mval &= ~MPPE_OPT_BITMASK;
  if (peer & MPPE_OPT_128BIT)
    mval |= MPPE_OPT_128BIT;
  else if (peer & MPPE_OPT_56BIT)
    mval |= MPPE_OPT_56BIT;
  else if (peer & MPPE_OPT_40BIT)
    mval |= MPPE_OPT_40BIT;
  else
    mval |= MPPE_OPT_128BIT;
  ua_htonl(&mval, o->data);

  return res;
}

static struct mppe_state *
MPPE_InitState(struct fsm_opt *o)
{
  struct mppe_state *mp;
  u_int32_t val;

  if ((mp = calloc(1, sizeof *mp)) != NULL) {
    ua_ntohl(o->data, &val);

    switch (val & MPPE_OPT_BITMASK) {
    case MPPE_OPT_128BIT:
      mp->keylen = 16;
      mp->keybits = 128;
      break;
    case MPPE_OPT_56BIT:
      mp->keylen = 8;
      mp->keybits = 56;
      break;
    case MPPE_OPT_40BIT:
      mp->keylen = 8;
      mp->keybits = 40;
      break;
    default:
      log_Printf(LogWARN, "Unexpected MPPE options 0x%08x\n", val);
      free(mp);
      return NULL;
    }

    mp->stateless = !!(val & MPPE_OPT_STATELESS);
  }

  return mp;
}

static void *
MPPEInitInput(struct bundle *bundle __unused, struct fsm_opt *o)
{
  struct mppe_state *mip;

  if (!MPPE_MasterKeyValid) {
    log_Printf(LogWARN, "MPPE: Cannot initialise without CHAP81\n");
    return NULL;
  }

  if ((mip = MPPE_InitState(o)) == NULL) {
    log_Printf(LogWARN, "MPPEInput: Cannot initialise - unexpected options\n");
    return NULL;
  }

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

#ifndef NORADIUS
  if (*bundle->radius.cfg.file && bundle->radius.mppe.recvkey) {
    if (mip->keylen > bundle->radius.mppe.recvkeylen)
      mip->keylen = bundle->radius.mppe.recvkeylen;
    if (mip->keylen > sizeof mip->mastkey)
      mip->keylen = sizeof mip->mastkey;
    memcpy(mip->mastkey, bundle->radius.mppe.recvkey, mip->keylen);
  } else
#endif
    GetAsymetricStartKey(MPPE_MasterKey, mip->mastkey, mip->keylen, 0,
                         MPPE_IsServer);

  GetNewKeyFromSHA(mip->mastkey, mip->mastkey, mip->keylen, mip->sesskey);

  MPPEReduceSessionKey(mip);

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

  if (!mip->stateless) {
    /*
     * We need to initialise our dictionary here as the first packet we
     * receive is unlikely to have the FLUSHED bit set.
     */
    log_Printf(LogDEBUG, "MPPEInitInput: Dictionary initialised [%d]\n",
               mip->cohnum);
    RC4_set_key(&mip->rc4key, mip->keylen, mip->sesskey);
  } else {
    /*
     * We do the first key change here as the first packet is expected
     * to have a sequence number of 0 and we'll therefore not expect
     * to have to change the key at that point.
     */
    log_Printf(LogDEBUG, "MPPEInitInput: Key changed [%d]\n", mip->cohnum);
    MPPEKeyChange(mip);
  }

  return mip;
}

static void *
MPPEInitOutput(struct bundle *bundle __unused, struct fsm_opt *o)
{
  struct mppe_state *mop;

  if (!MPPE_MasterKeyValid) {
    log_Printf(LogWARN, "MPPE: Cannot initialise without CHAP81\n");
    return NULL;
  }

  if ((mop = MPPE_InitState(o)) == NULL) {
    log_Printf(LogWARN, "MPPEOutput: Cannot initialise - unexpected options\n");
    return NULL;
  }

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

#ifndef NORADIUS
  if (*bundle->radius.cfg.file && bundle->radius.mppe.sendkey) {
    if (mop->keylen > bundle->radius.mppe.sendkeylen)
      mop->keylen = bundle->radius.mppe.sendkeylen;
    if (mop->keylen > sizeof mop->mastkey)
      mop->keylen = sizeof mop->mastkey;
    memcpy(mop->mastkey, bundle->radius.mppe.sendkey, mop->keylen);
  } else
#endif
    GetAsymetricStartKey(MPPE_MasterKey, mop->mastkey, mop->keylen, 1,
                         MPPE_IsServer);

  GetNewKeyFromSHA(mop->mastkey, mop->mastkey, mop->keylen, mop->sesskey);

  MPPEReduceSessionKey(mop);

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

  if (!mop->stateless) {
    /*
     * We need to initialise our dictionary now as the first packet we
     * send won't have the FLUSHED bit set.
     */
    log_Printf(LogDEBUG, "MPPEInitOutput: Dictionary initialised [%d]\n",
               mop->cohnum);
    RC4_set_key(&mop->rc4key, mop->keylen, mop->sesskey);
  }

  return mop;
}

static void
MPPETermInput(void *v)
{
  free(v);
}

static void
MPPETermOutput(void *v)
{
  free(v);
}

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