/*
* 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 <md4.h>
#include <netgraph/ng_message.h>
#include <netgraph.h>
/*
* This implements both MPPC compression and MPPE encryption.
*/
/*
* DEFINITIONS
*/
/* #define DEBUG_KEYS */
#define MPPC_SUPPORTED (MPPC_BIT | MPPE_BITS | MPPE_STATELESS)
/*
* INTERNAL FUNCTIONS
*/
static int MppcInit(Bund b, int dir);
static char *MppcDescribe(Bund b, int xmit, char *buf, size_t len);
static int MppcSubtractBloat(Bund b, int size);
static void MppcCleanup(Bund b, int dir);
static u_char *MppcBuildConfigReq(Bund b, u_char *cp, int *ok);
static void MppcDecodeConfigReq(Fsm fp, FsmOption opt, int mode);
static Mbuf MppcRecvResetReq(Bund b, int id, Mbuf bp, int *noAck);
static char *MppcDescribeBits(u_int32_t bits, char *buf, size_t len);
static int MppcNegotiated(Bund b, int xmit);
/* Encryption stuff */
static void MppeInitKey(Bund b, MppcInfo mppc, int dir);
static void MppeInitKeyv2(Bund b, MppcInfo mppc, int dir);
static short MppcEnabledMppeType(Bund b, short type);
static short MppcAcceptableMppeType(Bund b, 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
/*
* GLOBAL VARIABLES
*/
const struct comptype gCompMppcInfo = {
"mppc",
CCP_TY_MPPC,
1,
MppcInit,
NULL,
NULL,
MppcDescribe,
MppcSubtractBloat,
MppcCleanup,
MppcBuildConfigReq,
MppcDecodeConfigReq,
NULL,
MppcRecvResetReq,
NULL,
MppcNegotiated,
NULL,
NULL,
NULL,
};
/*
* MppcInit()
*/
static int
MppcInit(Bund b, int dir)
{
MppcInfo const mppc = &b->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? */
mschap = b->params.msoft.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;
if (conf.bits & MPPE_BITS) {
if (mschap == CHAP_ALG_MSOFT)
MppeInitKey(b, mppc, dir);
else
MppeInitKeyv2(b, mppc, dir);
memcpy(conf.startkey, mppc->xmit_key0, sizeof(conf.startkey));
}
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;
if (conf.bits & MPPE_BITS) {
if (mschap == CHAP_ALG_MSOFT)
MppeInitKey(b, mppc, dir);
else
MppeInitKeyv2(b, mppc, dir);
memcpy(conf.startkey, mppc->recv_key0, sizeof(conf.startkey));
}
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(b->csock, MPD_HOOK_PPP,
NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
Log(LG_ERR, ("[%s] can't create %s node: %s",
b->name, mp.type, strerror(errno)));
return(-1);
}
/* Configure MPPC node */
snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, ppphook);
if (NgSendMsg(b->csock, path,
NGM_MPPC_COOKIE, cmd, &conf, sizeof(conf)) < 0) {
Log(LG_ERR, ("[%s] can't config %s node at %s: %s",
b->name, NG_MPPC_NODE_TYPE, path, strerror(errno)));
NgFuncDisconnect(b->csock, b->name, MPD_HOOK_PPP, ppphook);
return(-1);
}
/* Done */
return(0);
}
/*
* MppcDescribe()
*/
static char *
MppcDescribe(Bund b, int dir, char *buf, size_t len)
{
MppcInfo const mppc = &b->ccp.mppc;
switch (dir) {
case COMP_DIR_XMIT:
return(MppcDescribeBits(mppc->xmit_bits, buf, len));
case COMP_DIR_RECV:
return(MppcDescribeBits(mppc->recv_bits, buf, len));
default:
assert(0);
return(NULL);
}
}
/*
* MppcSubtractBloat()
*/
static int
MppcSubtractBloat(Bund b, int size)
{
/* Account for MPPC header */
size -= 2;
/* Account for possible expansion with MPPC compression */
if ((b->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(Bund b, int dir)
{
MppcInfo const mppc = &b->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(Bund b, int dir)
{
const char *ppphook;
char path[NG_PATHLEN + 1];
/* Remove node */
switch (dir) {
case COMP_DIR_XMIT:
ppphook = NG_PPP_HOOK_COMPRESS;
break;
case COMP_DIR_RECV:
ppphook = NG_PPP_HOOK_DECOMPRESS;
break;
default:
assert(0);
return;
}
snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, ppphook);
(void)NgFuncShutdownNode(b->csock, b->name, path);
}
/*
* MppcBuildConfigReq()
*/
static u_char *
MppcBuildConfigReq(Bund b, u_char *cp, int *ok)
{
CcpState const ccp = &b->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;
/* Encryption */
if (MppcEnabledMppeType(b, 40)
&& !CCP_PEER_REJECTED(ccp, gMppe40))
bits |= MPPE_40;
#ifndef MPPE_56_UNSUPPORTED
if (MppcEnabledMppeType(b, 56)
&& !CCP_PEER_REJECTED(ccp, gMppe56))
bits |= MPPE_56;
#endif
if (MppcEnabledMppeType(b, 128)
&& !CCP_PEER_REJECTED(ccp, gMppe128))
bits |= MPPE_128;
/* Stateless mode */
if (Enabled(&ccp->options, gMppcStateless)
&& !CCP_PEER_REJECTED(ccp, gMppcStateless)
&& bits != 0)
bits |= MPPE_STATELESS;
/* Ship it */
mppc->xmit_bits = bits;
if (bits != 0) {
cp = FsmConfValue(cp, CCP_TY_MPPC, -4, &bits);
*ok = 1;
} else {
*ok = 0;
}
return(cp);
}
/*
* MppcDecodeConfigReq()
*/
static void
MppcDecodeConfigReq(Fsm fp, FsmOption opt, int mode)
{
Bund b = (Bund)fp->arg;
CcpState const ccp = &b->ccp;
MppcInfo const mppc = &ccp->mppc;
u_int32_t orig_bits;
u_int32_t bits;
char buf[64];
/* 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, buf, sizeof(buf))));
/* 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;
/* Check encryption */
if ((bits & MPPE_40) && !MppcAcceptableMppeType(b, 40))
bits &= ~MPPE_40;
#ifndef MPPE_56_UNSUPPORTED
if ((bits & MPPE_56) && !MppcAcceptableMppeType(b, 56))
#endif
bits &= ~MPPE_56;
if ((bits & MPPE_128) && !MppcAcceptableMppeType(b, 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 (MppcAcceptableMppeType(b, 40)) bits |= MPPE_40;
#ifndef MPPE_56_UNSUPPORTED
if (MppcAcceptableMppeType(b, 56)) bits |= MPPE_56;
#endif
if (MppcAcceptableMppeType(b, 128)) bits |= MPPE_128;
}
/* Stateless mode */
if ((bits & MPPE_STATELESS) &&
(!Acceptable(&ccp->options, gMppcStateless)
|| (bits & (MPPE_BITS|MPPC_BIT)) == 0))
bits &= ~MPPE_STATELESS;
/* See if what we want equals what was sent */
mppc->recv_bits = bits;
if (bits) {
if (bits != orig_bits) {
bits = htonl(bits);
memcpy(opt->data, &bits, 4);
FsmNak(fp, opt);
}
else
FsmAck(fp, opt);
}
else
FsmRej(fp, opt);
break;
case MODE_NAK:
if (!(bits & MPPC_BIT))
CCP_PEER_REJ(ccp, gMppcCompress);
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);
if (!(bits & MPPE_STATELESS))
CCP_PEER_REJ(ccp, gMppcStateless);
break;
}
}
/*
* MppcRecvResetReq()
*/
static Mbuf
MppcRecvResetReq(Bund b, 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(b->csock, path,
NGM_MPPC_COOKIE, NGM_MPPC_RESETREQ, NULL, 0) < 0) {
Log(LG_ERR, ("[%s] reset-req to %s node: %s",
b->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, char *buf, size_t len)
{
*buf = 0;
if (bits & MPPC_BIT)
snprintf(buf + strlen(buf), len - strlen(buf), "MPPC, ");
if (bits & MPPE_BITS) {
snprintf(buf + strlen(buf), len - strlen(buf), "MPPE(");
if (bits & MPPE_40) {
snprintf(buf + strlen(buf), len - strlen(buf), "40");
if (bits & (MPPE_56|MPPE_128))
snprintf(buf + strlen(buf), len - strlen(buf), ", ");
}
if (bits & MPPE_56) {
snprintf(buf + strlen(buf), len - strlen(buf), "56");
if ((bits & MPPE_128))
snprintf(buf + strlen(buf), len - strlen(buf), ", ");
}
if (bits & MPPE_128)
snprintf(buf + strlen(buf), len - strlen(buf), "128");
snprintf(buf + strlen(buf), len - strlen(buf), " bits), ");
}
if (bits & MPPE_STATELESS)
snprintf(buf + strlen(buf), len - strlen(buf), "stateless");
return(buf);
}
static short
MppcEnabledMppeType(Bund b, short type)
{
CcpState const ccp = &b->ccp;
short ret;
switch (type) {
case 40:
if (Enabled(&ccp->options, gMppePolicy)) {
ret = (b->params.msoft.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, gMppePolicy)) {
ret = (b->params.msoft.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, gMppePolicy)) {
ret = (b->params.msoft.types & MPPE_TYPE_128BIT) && !CCP_PEER_REJECTED(ccp, gMppe128);
} else {
ret = Enabled(&ccp->options, gMppe128) && !CCP_PEER_REJECTED(ccp, gMppe128);
}
}
return ret;
}
static short
MppcAcceptableMppeType(Bund b, short type)
{
CcpState const ccp = &b->ccp;
short ret;
switch (type) {
case 40:
if (Enabled(&ccp->options, gMppePolicy)) {
ret = b->params.msoft.types & MPPE_TYPE_40BIT;
} else {
ret = Acceptable(&ccp->options, gMppe40);
}
break;
#ifndef MPPE_56_UNSUPPORTED
case 56:
if (Enabled(&ccp->options, gMppePolicy)) {
ret = b->params.msoft.types & MPPE_TYPE_56BIT;
} else {
ret = Acceptable(&ccp->options, gMppe56);
}
break;
#endif
case 128:
default:
if (Enabled(&ccp->options, gMppePolicy)) {
ret = b->params.msoft.types & MPPE_TYPE_128BIT;
} else {
ret = Acceptable(&ccp->options, gMppe128);
}
}
return ret;
}
#define KEYLEN(b) (((b) & MPPE_128) ? 16 : 8)
/*
* MppeInitKey()
*/
static void
MppeInitKey(Bund b, MppcInfo mppc, int dir)
{
CcpState const ccp = &b->ccp;
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];
u_char *chal;
/* The secret comes from the originating caller's credentials */
chal = b->params.msoft.msChal;
/* Compute basis for the session key (ie, "start key" or key0) */
if (bits & MPPE_128) {
if (!b->params.msoft.has_nt_hash) {
Log(LG_ERR, ("[%s] The NT-Hash is not set, but needed for MS-CHAPv1 and MPPE 128",
b->name));
goto fail;
}
memcpy(hash, b->params.msoft.nt_hash_hash, sizeof(hash));
KEYDEBUG((hash, sizeof(hash), "NT Password Hash Hash"));
KEYDEBUG((chal, CHAP_MSOFT_CHAL_LEN, "Challenge"));
MsoftGetStartKey(chal, hash);
KEYDEBUG((hash, sizeof(hash), "NT StartKey"));
} else {
if (!b->params.msoft.has_lm_hash) {
Log(LG_ERR, ("[%s] The LM-Hash is not set, but needed for MS-CHAPv1 and MPPE 40, 56",
b->name));
goto fail;
}
memcpy(hash, b->params.msoft.lm_hash, 8);
KEYDEBUG((hash, sizeof(hash), "LM StartKey"));
}
memcpy(key0, hash, MPPE_KEY_LEN);
KEYDEBUG((key0, (bits & MPPE_128) ? 16 : 8, "InitialKey"));
return;
fail:
FsmFailure(&ccp->fsm, FAIL_CANT_ENCRYPT);
FsmFailure(&b->ipcp.fsm, FAIL_CANT_ENCRYPT);
}
/*
* MppeInitKeyv2()
*/
static void
MppeInitKeyv2(Bund b, MppcInfo mppc, int dir)
{
CcpState const ccp = &b->ccp;
u_char *const key0 = (dir == COMP_DIR_XMIT) ?
mppc->xmit_key0 : mppc->recv_key0;
u_char hash[16];
u_char *resp;
if (b->params.msoft.has_keys)
{
memcpy(mppc->xmit_key0, b->params.msoft.xmit_key, MPPE_KEY_LEN);
memcpy(mppc->recv_key0, b->params.msoft.recv_key, MPPE_KEY_LEN);
return;
}
/* The secret comes from the originating caller's credentials */
resp = b->params.msoft.ntResp;
if (!b->params.msoft.has_nt_hash) {
Log(LG_ERR, ("[%s] The NT-Hash is not set, but needed for MS-CHAPv2 and MPPE",
b->name));
goto fail;
}
/* Compute basis for the session key (ie, "start key" or key0) */
memcpy(hash, b->params.msoft.nt_hash_hash, sizeof(hash));
KEYDEBUG((hash, sizeof(hash), "NT Password Hash Hash"));
KEYDEBUG((resp, CHAP_MSOFTv2_CHAL_LEN, "Response"));
MsoftGetMasterKey(resp, hash);
KEYDEBUG((hash, sizeof(hash), "GetMasterKey"));
MsoftGetAsymetricStartKey(hash,
(dir == COMP_DIR_RECV) ^
(b->originate == LINK_ORIGINATE_LOCAL));
KEYDEBUG((hash, sizeof(hash), "GetAsymmetricKey"));
memcpy(key0, hash, MPPE_KEY_LEN);
KEYDEBUG((key0, MPPE_KEY_LEN, "InitialKey"));
return;
fail:
FsmFailure(&ccp->fsm, FAIL_CANT_ENCRYPT);
FsmFailure(&b->ipcp.fsm, FAIL_CANT_ENCRYPT);
}
#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 */
syntax highlighted by Code2HTML, v. 0.9.1