The SPDX folks have obsoleted the BSD-2-Clause-FreeBSD identifier. Catch up to that fact and revert to their recommended match of BSD-2-Clause. Discussed with: pfg MFC After: 3 days Sponsored by: Netflix
820 lines
20 KiB
C
820 lines
20 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* 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$
|
|
*/
|
|
|
|
#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
|
|
},
|
|
};
|