/*
* lcp.c
*
* Written by Toshiharu OHNO <tony-o@iij.ad.jp>
* Copyright (c) 1993, Internet Initiative Japan, Inc. All rights reserved.
* See ``COPYRIGHT.iij''
*
* Rewritten by Archie Cobbs <archie@freebsd.org>
* Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
* See ``COPYRIGHT.whistle''
*/
#include "ppp.h"
#include "lcp.h"
#include "fsm.h"
#include "mp.h"
#include "phys.h"
#include "link.h"
#include "msg.h"
#include "util.h"
/*
* DEFINITIONS
*/
#define LCP_ECHO_INTERVAL 5 /* Enable keep alive by default */
#define LCP_ECHO_TIMEOUT 40
#define LCP_KNOWN_CODES ( (1 << CODE_CONFIGREQ) \
| (1 << CODE_CONFIGACK) \
| (1 << CODE_CONFIGNAK) \
| (1 << CODE_CONFIGREJ) \
| (1 << CODE_TERMREQ) \
| (1 << CODE_TERMACK) \
| (1 << CODE_CODEREJ) \
| (1 << CODE_PROTOREJ) \
| (1 << CODE_ECHOREQ) \
| (1 << CODE_ECHOREP) \
| (1 << CODE_DISCREQ) \
| (1 << CODE_IDENT) \
| (1 << CODE_TIMEREM) )
#define LCP_PEER_REJECTED(p,x) ((p)->peer_reject & (1<<x))
#define LCP_PEER_REJ(p,x) do{(p)->peer_reject |= (1<<(x));}while(0)
#define LCP_PEER_UNREJ(p,x) do{(p)->peer_reject &= ~(1<<(x));}while(0)
/*
* INTERNAL FUNCTIONS
*/
static void LcpConfigure(Fsm fp);
static void LcpNewState(Fsm fp, enum fsm_state old, enum fsm_state new);
static void LcpNewPhase(Link l, enum lcp_phase new);
static u_char *LcpBuildConfigReq(Fsm fp, u_char *cp);
static void LcpDecodeConfig(Fsm fp, FsmOption list, int num, int mode);
static void LcpLayerDown(Fsm fp);
static void LcpLayerStart(Fsm fp);
static void LcpLayerFinish(Fsm fp);
static int LcpRecvProtoRej(Fsm fp, int proto, Mbuf bp);
static void LcpFailure(Fsm fp, enum fsmfail reason);
static const struct fsmoption *LcpAuthProtoNak(ushort proto, u_char alg);
static short LcpFindAuthProto(ushort proto, u_char alg);
static void LcpRecvIdent(Fsm fp, Mbuf bp);
static void LcpStopActivity(Link l);
/*
* INTERNAL VARIABLES
*/
static const struct fsmoptinfo gLcpConfOpts[] = {
{ "VENDOR", TY_VENDOR, 4, 255, TRUE },
{ "MRU", TY_MRU, 2, 2, TRUE },
{ "ACCMAP", TY_ACCMAP, 4, 4, TRUE },
{ "AUTHPROTO", TY_AUTHPROTO, 2, 255, TRUE },
{ "QUALPROTO", TY_QUALPROTO, 0, 0, FALSE },
{ "MAGICNUM", TY_MAGICNUM, 4, 4, TRUE },
{ "RESERVED", TY_RESERVED, 0, 0, FALSE }, /* DEPRECATED */
{ "PROTOCOMP", TY_PROTOCOMP, 0, 0, TRUE },
{ "ACFCOMP", TY_ACFCOMP, 0, 0, TRUE },
{ "FCSALT", TY_FCSALT, 0, 0, FALSE },
{ "SDP", TY_SDP, 0, 0, FALSE },
{ "NUMMODE", TY_NUMMODE, 0, 0, FALSE },
{ "MULTILINK", TY_MULTILINK, 0, 0, FALSE }, /* DEPRECATED */
{ "CALLBACK", TY_CALLBACK, 1, 255, TRUE },
{ "CONNECTTIME", TY_CONNECTTIME, 0, 0, FALSE }, /* DEPRECATED */
{ "COMPFRAME", TY_COMPFRAME, 0, 0, FALSE }, /* DEPRECATED */
{ "NDS", TY_NDS, 0, 0, FALSE }, /* DEPRECATED */
{ "MP MRRU", TY_MRRU, 2, 2, TRUE },
{ "MP SHORTSEQ", TY_SHORTSEQNUM, 0, 0, TRUE },
{ "ENDPOINTDISC", TY_ENDPOINTDISC, 1, 255, TRUE },
{ "PROPRIETARY", TY_PROPRIETARY, 0, 0, FALSE },
{ "DCEIDENTIFIER", TY_DCEIDENTIFIER, 0, 0, FALSE },
{ "MULTILINKPLUS", TY_MULTILINKPLUS, 0, 0, FALSE },
{ "BACP", TY_BACP, 0, 0, FALSE },
{ "LCPAUTHOPT", TY_LCPAUTHOPT, 0, 0, FALSE },
{ "COBS", TY_COBS, 0, 0, FALSE },
{ "PREFIXELISION", TY_PREFIXELISION, 0, 0, FALSE },
{ "MULTILINKHEADERFMT", TY_MULTILINKHEADERFMT, 0, 0, FALSE },
{ "INTERNAT", TY_INTERNAT, 0, 0, FALSE },
{ "SDATALINKSONET", TY_SDATALINKSONET, 0, 0, FALSE },
{ NULL, 0, 0, 0, 0 }
};
static struct fsmtype gLcpFsmType = {
"LCP", /* Name of protocol */
PROTO_LCP, /* Protocol Number */
LCP_KNOWN_CODES,
TRUE,
LG_LCP, LG_LCP2,
LcpNewState,
NULL,
LcpLayerDown,
LcpLayerStart,
LcpLayerFinish,
LcpBuildConfigReq,
LcpDecodeConfig,
LcpConfigure,
NULL,
NULL,
NULL,
NULL,
LcpRecvProtoRej,
LcpFailure,
NULL,
NULL,
LcpRecvIdent,
NULL, NULL, NULL
};
/* List of possible Authentication Protocols */
static struct lcpauthproto gLcpAuthProtos[] = {
{
PROTO_PAP,
0,
LINK_CONF_PAP,
},
{
PROTO_CHAP,
CHAP_ALG_MD5,
LINK_CONF_CHAPMD5
},
{
PROTO_CHAP,
CHAP_ALG_MSOFT,
LINK_CONF_CHAPMSv1
},
{
PROTO_CHAP,
CHAP_ALG_MSOFTv2,
LINK_CONF_CHAPMSv2
},
{
PROTO_EAP,
0,
LINK_CONF_EAP
}
};
static const char *PhaseNames[] = {
"DEAD",
"ESTABLISH",
"AUTHENTICATE",
"NETWORK",
"TERMINATE",
};
/*
* LcpInit()
*/
void
LcpInit(Link l)
{
LcpState const lcp = &l->lcp;
memset(lcp, 0, sizeof(*lcp));
FsmInit(&lcp->fsm, &gLcpFsmType, l);
lcp->fsm.conf.echo_int = LCP_ECHO_INTERVAL;
lcp->fsm.conf.echo_max = LCP_ECHO_TIMEOUT;
lcp->phase = PHASE_DEAD;
AuthInit(l);
}
/*
* LcpInst()
*/
void
LcpInst(Link l, Link lt)
{
LcpState const lcp = &l->lcp;
memcpy(lcp, <->lcp, sizeof(*lcp));
FsmInst(&lcp->fsm, <->lcp.fsm, l);
AuthInst(&lcp->auth, <->lcp.auth);
}
/*
* LcpShutdown()
*/
void
LcpShutdown(Link l)
{
AuthShutdown(l);
}
/*
* LcpConfigure()
*/
static void
LcpConfigure(Fsm fp)
{
Link l = (Link)fp->arg;
LcpState const lcp = &l->lcp;
short i;
/* FSM stuff */
lcp->fsm.conf.passive = Enabled(&l->conf.options, LINK_CONF_PASSIVE);
lcp->fsm.conf.check_magic =
Enabled(&l->conf.options, LINK_CONF_CHECK_MAGIC);
lcp->peer_reject = 0;
lcp->need_reset = 0;
/* Initialize normal LCP stuff */
lcp->peer_mru = PhysGetMtu(l, 1);
lcp->want_mru = PhysGetMru(l, 1);
if (l->type && (lcp->want_mru > PhysGetMru(l, 0)))
lcp->want_mru = PhysGetMru(l, 0);
lcp->peer_accmap = 0xffffffff;
lcp->want_accmap = l->conf.accmap;
lcp->peer_acfcomp = FALSE;
lcp->want_acfcomp = Enabled(&l->conf.options, LINK_CONF_ACFCOMP);
lcp->peer_protocomp = FALSE;
lcp->want_protocomp = Enabled(&l->conf.options, LINK_CONF_PROTOCOMP);
lcp->peer_magic = 0;
lcp->want_magic = Enabled(&l->conf.options,
LINK_CONF_MAGICNUM) ? GenerateMagic() : 0;
if (l->originate == LINK_ORIGINATE_LOCAL)
lcp->want_callback = Enabled(&l->conf.options, LINK_CONF_CALLBACK);
else
lcp->want_callback = FALSE;
/* Authentication stuff */
lcp->peer_auth = 0;
lcp->want_auth = 0;
lcp->peer_alg = 0;
lcp->want_alg = 0;
lcp->peer_ident[0] = 0;
memset(lcp->want_protos, 0, sizeof(lcp->want_protos));
/* fill my list of possible auth-protos, most to least secure */
/* prefer MS-CHAP to others to get encryption keys */
lcp->want_protos[0] = &gLcpAuthProtos[LINK_CONF_CHAPMSv2];
lcp->want_protos[1] = &gLcpAuthProtos[LINK_CONF_CHAPMSv1];
lcp->want_protos[2] = &gLcpAuthProtos[LINK_CONF_CHAPMD5];
lcp->want_protos[3] = &gLcpAuthProtos[LINK_CONF_PAP];
lcp->want_protos[4] = &gLcpAuthProtos[LINK_CONF_EAP];
/* Use the same list for the MODE_REQ */
memcpy(lcp->peer_protos, lcp->want_protos, sizeof(lcp->peer_protos));
for (i = 0; i < LCP_NUM_AUTH_PROTOS; i++) {
if (Enabled(&l->conf.options, lcp->want_protos[i]->conf) && lcp->want_auth == 0) {
lcp->want_auth = lcp->want_protos[i]->proto;
lcp->want_alg = lcp->want_protos[i]->alg;
/* avoid re-requesting this proto, if it was nak'd by the peer */
lcp->want_protos[i] = NULL;
} else if (!Enabled(&l->conf.options, lcp->want_protos[i]->conf)) {
/* don't request disabled Protos */
lcp->want_protos[i] = NULL;
}
/* remove all denied protos */
if (!Acceptable(&l->conf.options, lcp->peer_protos[i]->conf))
lcp->peer_protos[i] = NULL;
}
/* Multi-link stuff */
lcp->peer_mrru = 0;
lcp->peer_shortseq = FALSE;
if (Enabled(&l->conf.options, LINK_CONF_MULTILINK)) {
lcp->want_mrru = l->conf.mrru;
lcp->want_shortseq = Enabled(&l->conf.options, LINK_CONF_SHORTSEQ);
} else {
lcp->want_mrru = 0;
lcp->want_shortseq = FALSE;
}
/* Peer discriminator */
lcp->peer_discrim.class = DISCRIM_CLASS_NULL;
lcp->peer_discrim.len = 0;
}
/*
* LcpNewState()
*
* Keep track of phase shifts
*/
static void
LcpNewState(Fsm fp, enum fsm_state old, enum fsm_state new)
{
Link l = (Link)fp->arg;
switch (old) {
case ST_INITIAL: /* DEAD */
case ST_STARTING:
switch (new) {
case ST_INITIAL:
/* fall through */
case ST_STARTING:
break;
default:
LcpNewPhase(l, PHASE_ESTABLISH);
break;
}
break;
case ST_CLOSED: /* ESTABLISH */
case ST_STOPPED:
switch (new) {
case ST_INITIAL:
case ST_STARTING:
LcpNewPhase(l, PHASE_DEAD);
break;
default:
break;
}
break;
case ST_CLOSING: /* TERMINATE */
case ST_STOPPING:
switch (new) {
case ST_INITIAL:
case ST_STARTING:
LcpNewPhase(l, PHASE_DEAD);
break;
case ST_CLOSED:
case ST_STOPPED:
LcpNewPhase(l, PHASE_ESTABLISH);
break;
default:
break;
}
break;
case ST_REQSENT: /* ESTABLISH */
case ST_ACKRCVD:
case ST_ACKSENT:
switch (new) {
case ST_INITIAL:
case ST_STARTING:
LcpNewPhase(l, PHASE_DEAD);
break;
case ST_CLOSING:
case ST_STOPPING:
LcpNewPhase(l, PHASE_TERMINATE);
break;
case ST_OPENED:
LcpNewPhase(l, PHASE_AUTHENTICATE);
break;
default:
break;
}
break;
case ST_OPENED: /* AUTHENTICATE, NETWORK */
switch (new) {
case ST_STARTING:
LcpNewPhase(l, PHASE_DEAD);
break;
case ST_REQSENT:
case ST_ACKSENT:
LcpNewPhase(l, PHASE_ESTABLISH);
break;
case ST_CLOSING:
case ST_STOPPING:
LcpNewPhase(l, PHASE_TERMINATE);
break;
default:
assert(0);
}
break;
default:
assert(0);
}
LinkShutdownCheck(l, new);
}
/*
* LcpNewPhase()
*/
static void
LcpNewPhase(Link l, enum lcp_phase new)
{
LcpState const lcp = &l->lcp;
enum lcp_phase old = lcp->phase;
/* Logit */
Log(LG_LCP2, ("[%s] %s: phase shift %s --> %s",
Pref(&lcp->fsm), Fsm(&lcp->fsm), PhaseNames[old], PhaseNames[new]));
/* Sanity check transition (The picture on RFC 1661 p. 6 is incomplete) */
switch (old) {
case PHASE_DEAD:
assert(new == PHASE_ESTABLISH);
break;
case PHASE_ESTABLISH:
assert(new == PHASE_DEAD
|| new == PHASE_TERMINATE
|| new == PHASE_AUTHENTICATE);
break;
case PHASE_AUTHENTICATE:
assert(new == PHASE_TERMINATE
|| new == PHASE_ESTABLISH
|| new == PHASE_NETWORK
|| new == PHASE_DEAD);
break;
case PHASE_NETWORK:
assert(new == PHASE_TERMINATE
|| new == PHASE_ESTABLISH
|| new == PHASE_DEAD);
break;
case PHASE_TERMINATE:
assert(new == PHASE_ESTABLISH
|| new == PHASE_DEAD);
break;
default:
assert(0);
}
/* Change phase now */
lcp->phase = new;
/* Do whatever for leaving old phase */
switch (old) {
case PHASE_AUTHENTICATE:
if (new != PHASE_NETWORK)
AuthCleanup(l);
break;
case PHASE_NETWORK:
if (l->joined_bund)
BundLeave(l);
AuthCleanup(l);
break;
default:
break;
}
/* Do whatever for entering new phase */
switch (new) {
case PHASE_ESTABLISH:
break;
case PHASE_AUTHENTICATE:
if (!PhysIsSync(l))
PhysSetAccm(l, lcp->peer_accmap, lcp->want_accmap);
AuthStart(l);
break;
case PHASE_NETWORK:
/* Send ident string, if configured */
if (l->conf.ident != NULL)
FsmSendIdent(&lcp->fsm, l->conf.ident);
/* Send Time-Remaining if known */
if (Enabled(&l->conf.options, LINK_CONF_TIMEREMAIN) &&
lcp->auth.params.session_timeout != 0)
FsmSendTimeRemaining(&lcp->fsm, lcp->auth.params.session_timeout);
/* Join my bundle */
if (!BundJoin(l)) {
Log(LG_LINK|LG_BUND,
("[%s] link did not validate in bundle",
l->name));
RecordLinkUpDownReason(NULL, l,
0, STR_PROTO_ERR, "%s", STR_MULTI_FAIL);
FsmFailure(&l->lcp.fsm, FAIL_NEGOT_FAILURE);
} else {
/* If link connection complete, reset redial counter */
l->num_redial = 0;
}
break;
case PHASE_TERMINATE:
break;
case PHASE_DEAD:
break;
default:
assert(0);
}
}
/*
* LcpAuthResult()
*/
void
LcpAuthResult(Link l, int success)
{
Log(LG_AUTH|LG_LCP, ("[%s] %s: authorization %s",
Pref(&l->lcp.fsm), Fsm(&l->lcp.fsm), success ? "successful" : "failed"));
if (success) {
if (l->lcp.phase != PHASE_NETWORK)
LcpNewPhase(l, PHASE_NETWORK);
} else {
RecordLinkUpDownReason(NULL, l, 0, STR_LOGIN_FAIL,
"%s", STR_PPP_AUTH_FAILURE);
FsmFailure(&l->lcp.fsm, FAIL_NEGOT_FAILURE);
}
}
/*
* LcpStat()
*/
int
LcpStat(Context ctx, int ac, const char *const av[], const void *arg)
{
Link const l = ctx->lnk;
LcpState const lcp = &l->lcp;
char buf[64];
(void)ac;
(void)av;
(void)arg;
Printf("%s [%s]\r\n", lcp->fsm.type->name, FsmStateName(lcp->fsm.state));
Printf("Self:\r\n");
Printf( "\tMRU : %d bytes\r\n"
"\tMAGIC : 0x%08x\r\n"
"\tACCMAP : 0x%08x\r\n"
"\tACFCOMP : %s\r\n"
"\tPROTOCOMP: %s\r\n"
"\tAUTHTYPE : %s\r\n",
(int) lcp->want_mru,
(int) lcp->want_magic,
(int) lcp->want_accmap,
lcp->want_acfcomp ? "Yes" : "No",
lcp->want_protocomp ? "Yes" : "No",
(lcp->want_auth)?ProtoName(lcp->want_auth):"none");
if (lcp->want_mrru) {
Printf( "\tMRRU : %d bytes\r\n", (int) lcp->want_mrru);
Printf( "\tSHORTSEQ : %s\r\n", lcp->want_shortseq ? "Yes" : "No");
Printf( "\tENDPOINTDISC: %s\r\n", MpDiscrimText(&self_discrim, buf, sizeof(buf)));
}
if (l->conf.ident)
Printf( "\tIDENT : %s\r\n", l->conf.ident);
Printf("Peer:\r\n");
Printf( "\tMRU : %d bytes\r\n"
"\tMAGIC : 0x%08x\r\n"
"\tACCMAP : 0x%08x\r\n"
"\tACFCOMP : %s\r\n"
"\tPROTOCOMP: %s\r\n"
"\tAUTHTYPE : %s\r\n",
(int) lcp->peer_mru,
(int) lcp->peer_magic,
(int) lcp->peer_accmap,
lcp->peer_acfcomp ? "Yes" : "No",
lcp->peer_protocomp ? "Yes" : "No",
(lcp->peer_auth)?ProtoName(lcp->peer_auth):"none");
if (lcp->peer_mrru) {
Printf( "\tMRRU : %d bytes\r\n", (int) lcp->peer_mrru);
Printf( "\tSHORTSEQ : %s\r\n", lcp->peer_shortseq ? "Yes" : "No");
Printf( "\tENDPOINTDISC: %s\r\n", MpDiscrimText(&lcp->peer_discrim, buf, sizeof(buf)));
}
if (lcp->peer_ident[0])
Printf( "\tIDENT : %s\r\n", lcp->peer_ident);
return(0);
}
/*
* LcpBuildConfigReq()
*/
static u_char *
LcpBuildConfigReq(Fsm fp, u_char *cp)
{
Link l = (Link)fp->arg;
LcpState const lcp = &l->lcp;
/* Standard stuff */
if (lcp->want_acfcomp && !LCP_PEER_REJECTED(lcp, TY_ACFCOMP))
cp = FsmConfValue(cp, TY_ACFCOMP, 0, NULL);
if (lcp->want_protocomp && !LCP_PEER_REJECTED(lcp, TY_PROTOCOMP))
cp = FsmConfValue(cp, TY_PROTOCOMP, 0, NULL);
if ((!PhysIsSync(l)) && (!LCP_PEER_REJECTED(lcp, TY_ACCMAP)))
cp = FsmConfValue(cp, TY_ACCMAP, -4, &lcp->want_accmap);
if (!LCP_PEER_REJECTED(lcp, TY_MRU))
cp = FsmConfValue(cp, TY_MRU, -2, &lcp->want_mru);
if (lcp->want_magic && !LCP_PEER_REJECTED(lcp, TY_MAGICNUM))
cp = FsmConfValue(cp, TY_MAGICNUM, -4, &lcp->want_magic);
if (lcp->want_callback && !LCP_PEER_REJECTED(lcp, TY_CALLBACK)) {
struct {
u_char op;
u_char data[0];
} s_callback;
s_callback.op = 0;
cp = FsmConfValue(cp, TY_CALLBACK, 1, &s_callback);
}
/* Authorization stuff */
switch (lcp->want_auth) {
case PROTO_PAP:
case PROTO_EAP:
cp = FsmConfValue(cp, TY_AUTHPROTO, -2, &lcp->want_auth);
break;
case PROTO_CHAP: {
struct {
u_short want_auth;
u_char alg;
} s_mdx;
s_mdx.want_auth = htons(PROTO_CHAP);
s_mdx.alg = lcp->want_alg;
cp = FsmConfValue(cp, TY_AUTHPROTO, 3, &s_mdx);
}
break;
}
/* Multi-link stuff */
if (Enabled(&l->conf.options, LINK_CONF_MULTILINK)
&& !LCP_PEER_REJECTED(lcp, TY_MRRU)) {
cp = FsmConfValue(cp, TY_MRRU, -2, &lcp->want_mrru);
if (lcp->want_shortseq && !LCP_PEER_REJECTED(lcp, TY_SHORTSEQNUM))
cp = FsmConfValue(cp, TY_SHORTSEQNUM, 0, NULL);
if (!LCP_PEER_REJECTED(lcp, TY_ENDPOINTDISC))
cp = FsmConfValue(cp, TY_ENDPOINTDISC, 1 + self_discrim.len, &self_discrim.class);
}
/* Done */
return(cp);
}
static void
LcpLayerStart(Fsm fp)
{
Link l = (Link)fp->arg;
LinkNgInit(l);
if (!TimerStarted(&l->openTimer))
PhysOpen(l);
}
static void
LcpStopActivity(Link l)
{
AuthStop(l);
}
static void
LcpLayerFinish(Fsm fp)
{
Link l = (Link)fp->arg;
LcpStopActivity(l);
if (!l->rep) {
PhysClose(l);
LinkNgShutdown(l);
}
}
/*
* LcpLayerDown()
*/
static void
LcpLayerDown(Fsm fp)
{
Link l = (Link)fp->arg;
LcpState const lcp = &l->lcp;
LcpStopActivity(l);
if (lcp->phase == PHASE_AUTHENTICATE || lcp->phase == PHASE_NETWORK)
lcp->need_reset = 1;
}
void LcpOpen(Link l)
{
FsmOpen(&l->lcp.fsm);
}
void LcpClose(Link l)
{
FsmClose(&l->lcp.fsm);
}
void LcpUp(Link l)
{
FsmUp(&l->lcp.fsm);
}
void LcpDown(Link l)
{
FsmDown(&l->lcp.fsm);
}
/*
* LcpRecvProtoRej()
*/
static int
LcpRecvProtoRej(Fsm fp, int proto, Mbuf bp)
{
Link l = (Link)fp->arg;
int fatal = FALSE;
Fsm rej = NULL;
(void)bp;
/* Which protocol? */
switch (proto) {
case PROTO_CCP:
case PROTO_COMPD:
rej = l->bund ? &l->bund->ccp.fsm : NULL;
break;
case PROTO_ECP:
case PROTO_CRYPT:
rej = l->bund ? &l->bund->ecp.fsm : NULL;
break;
case PROTO_IPCP:
rej = l->bund ? &l->bund->ipcp.fsm : NULL;
break;
case PROTO_IPV6CP:
rej = l->bund ? &l->bund->ipv6cp.fsm : NULL;
break;
default:
break;
}
/* Turn off whatever protocol got rejected */
if (rej)
FsmFailure(rej, FAIL_WAS_PROTREJ);
return(fatal);
}
/*
* LcpRecvIdent()
*/
static void
LcpRecvIdent(Fsm fp, Mbuf bp)
{
Link l = (Link)fp->arg;
unsigned len, clen;
if (bp == NULL)
return;
if (l->lcp.peer_ident[0] != 0)
strlcat(l->lcp.peer_ident, " ", sizeof(l->lcp.peer_ident));
len = strlen(l->lcp.peer_ident);
clen = sizeof(l->lcp.peer_ident) - len - 1;
if (clen > MBLEN(bp))
clen = MBLEN(bp);
memcpy(l->lcp.peer_ident + len, (char *) MBDATAU(bp), clen);
l->lcp.peer_ident[len + clen] = 0;
}
/*
* LcpFailure()
*/
static void
LcpFailure(Fsm fp, enum fsmfail reason)
{
Link l = (Link)fp->arg;
char buf[100];
snprintf(buf, sizeof(buf), STR_LCP_FAILED, FsmFailureStr(reason));
RecordLinkUpDownReason(NULL, l, 0, reason == FAIL_ECHO_TIMEOUT ?
STR_ECHO_TIMEOUT : STR_PROTO_ERR, "%s", buf);
}
/*
* LcpDecodeConfig()
*/
static void
LcpDecodeConfig(Fsm fp, FsmOption list, int num, int mode)
{
Link l = (Link)fp->arg;
LcpState const lcp = &l->lcp;
int k;
/* If we have got request, forget the previous values */
if (mode == MODE_REQ) {
if (lcp->need_reset) /* perform complete reset when going back */
LcpConfigure(fp); /* from PHASE_AUTHENTICATE or PHASE_NETWORK */
else {
lcp->peer_mru = PhysGetMtu(l, 1);
lcp->peer_accmap = 0xffffffff;
lcp->peer_acfcomp = FALSE;
lcp->peer_protocomp = FALSE;
lcp->peer_magic = 0;
lcp->peer_auth = 0;
lcp->peer_alg = 0;
lcp->peer_mrru = 0;
lcp->peer_shortseq = FALSE;
}
}
/* Decode each config option */
for (k = 0; k < num; k++) {
FsmOption const opt = &list[k];
FsmOptInfo const oi = FsmFindOptInfo(gLcpConfOpts, opt->type);
/* Check option */
if (!oi) {
Log(LG_LCP, ("[%s] UNKNOWN[%d] len=%d", l->name, opt->type, opt->len));
if (mode == MODE_REQ)
FsmRej(fp, opt);
continue;
}
if (!oi->supported) {
Log(LG_LCP, ("[%s] %s", l->name, oi->name));
if (mode == MODE_REQ) {
Log(LG_LCP, ("[%s] Not supported", l->name));
FsmRej(fp, opt);
}
continue;
}
if (opt->len < oi->minLen + 2 || opt->len > oi->maxLen + 2) {
Log(LG_LCP, ("[%s] %s", l->name, oi->name));
if (mode == MODE_REQ) {
Log(LG_LCP, ("[%s] Bogus length=%d", l->name, opt->len));
FsmRej(fp, opt);
}
continue;
}
/* Do whatever */
switch (opt->type) {
case TY_MRU: /* link MRU */
{
u_int16_t mru;
memcpy(&mru, opt->data, 2);
mru = ntohs(mru);
Log(LG_LCP, ("[%s] %s %d", l->name, oi->name, mru));
switch (mode) {
case MODE_REQ:
if (mru < LCP_MIN_MRU) {
mru = htons(LCP_MIN_MRU);
memcpy(opt->data, &mru, 2);
FsmNak(fp, opt);
break;
}
if (mru < lcp->peer_mru)
lcp->peer_mru = mru;
FsmAck(fp, opt);
break;
case MODE_NAK:
/* Windows 2000 PPPoE bug workaround */
if (mru == lcp->want_mru) {
LCP_PEER_REJ(lcp, opt->type);
break;
}
if (mru >= LCP_MIN_MRU
&& (mru <= l->type->mru || mru < lcp->want_mru))
lcp->want_mru = mru;
break;
case MODE_REJ:
LCP_PEER_REJ(lcp, opt->type);
break;
}
}
break;
case TY_ACCMAP: /* async control character escape map */
{
u_int32_t accm;
memcpy(&accm, opt->data, 4);
accm = ntohl(accm);
Log(LG_LCP, ("[%s] %s 0x%08x", l->name, oi->name, accm));
switch (mode) {
case MODE_REQ:
lcp->peer_accmap = accm;
FsmAck(fp, opt);
break;
case MODE_NAK:
lcp->want_accmap = accm;
break;
case MODE_REJ:
LCP_PEER_REJ(lcp, opt->type);
break;
}
}
break;
case TY_AUTHPROTO: /* authentication protocol */
{
u_int16_t proto;
int bogus = 0, i, protoPos = -1;
LcpAuthProto authProto = NULL;
memcpy(&proto, opt->data, 2);
proto = ntohs(proto);
/* Display it */
switch (proto) {
case PROTO_CHAP:
if (opt->len >= 5) {
char buf[20];
const char *ts;
switch (opt->data[2]) {
case CHAP_ALG_MD5:
ts = "MD5";
break;
case CHAP_ALG_MSOFT:
ts = "MSOFT";
break;
case CHAP_ALG_MSOFTv2:
ts = "MSOFTv2";
break;
default:
snprintf(buf, sizeof(buf), "0x%02x", opt->data[2]);
ts = buf;
break;
}
Log(LG_LCP, ("[%s] %s %s %s", l->name, oi->name, ProtoName(proto), ts));
break;
}
break;
default:
Log(LG_LCP, ("[%s] %s %s", l->name, oi->name, ProtoName(proto)));
break;
}
/* Sanity check */
switch (proto) {
case PROTO_PAP:
if (opt->len != 4) {
Log(LG_LCP, ("[%s] Bad len=%d", l->name, opt->len));
bogus = 1;
}
break;
case PROTO_CHAP:
if (opt->len != 5) {
Log(LG_LCP, ("[%s] Bad len=%d", l->name, opt->len));
bogus = 1;
}
break;
}
if (!bogus) {
protoPos = LcpFindAuthProto(proto, proto == PROTO_CHAP ? opt->data[2] : 0);
authProto = (protoPos == -1) ? NULL : &gLcpAuthProtos[protoPos];
}
/* Deal with it */
switch (mode) {
case MODE_REQ:
/* let us check, whether the requested auth-proto is acceptable */
if ((authProto != NULL) && Acceptable(&l->conf.options, authProto->conf)) {
lcp->peer_auth = proto;
if (proto == PROTO_CHAP)
lcp->peer_alg = opt->data[2];
FsmAck(fp, opt);
break;
}
/* search an acceptable proto */
for(i = 0; i < LCP_NUM_AUTH_PROTOS; i++) {
if (lcp->peer_protos[i] != NULL) {
FsmNak(fp, LcpAuthProtoNak(lcp->peer_protos[i]->proto, lcp->peer_protos[i]->alg));
break;
}
}
/* no other acceptable auth-proto found */
if (i == LCP_NUM_AUTH_PROTOS)
FsmRej(fp, opt);
break;
case MODE_NAK:
/* this should never happen */
if (authProto == NULL)
break;
/* let us check, whether the requested auth-proto is enabled */
if (Enabled(&l->conf.options, authProto->conf)) {
lcp->want_auth = proto;
if (proto == PROTO_CHAP)
lcp->want_alg = opt->data[2];
break;
}
/* Remove the disabled proto from my list */
lcp->want_protos[protoPos] = NULL;
/* Search the next enabled proto */
for(i = 0; i < LCP_NUM_AUTH_PROTOS; i++) {
if (lcp->want_protos[i] != NULL) {
lcp->want_auth = lcp->want_protos[i]->proto;
lcp->want_alg = lcp->want_protos[i]->alg;
break;
}
}
break;
case MODE_REJ:
LCP_PEER_REJ(lcp, opt->type);
if (l->originate == LINK_ORIGINATE_LOCAL
&& Enabled(&l->conf.options, LINK_CONF_NO_ORIG_AUTH)) {
lcp->want_auth = 0;
}
break;
}
}
break;
case TY_MRRU: /* multi-link MRRU */
{
u_int16_t mrru;
memcpy(&mrru, opt->data, 2);
mrru = ntohs(mrru);
Log(LG_LCP, ("[%s] %s %d", l->name, oi->name, mrru));
switch (mode) {
case MODE_REQ:
if (!Enabled(&l->conf.options, LINK_CONF_MULTILINK)) {
FsmRej(fp, opt);
break;
}
if (mrru < MP_MIN_MRRU) {
mrru = htons(MP_MIN_MRRU);
memcpy(opt->data, &mrru, 2);
FsmNak(fp, opt);
break;
}
lcp->peer_mrru = mrru;
FsmAck(fp, opt);
break;
case MODE_NAK:
{
/* Let the peer to change it's mind. */
if (LCP_PEER_REJECTED(lcp, opt->type)) {
LCP_PEER_UNREJ(lcp, opt->type);
if (Enabled(&l->conf.options, LINK_CONF_MULTILINK))
lcp->want_mrru = l->conf.mrru;
}
/* Make sure we don't violate any rules by changing MRRU now */
if (mrru > lcp->want_mrru) /* too big */
break;
if (mrru < MP_MIN_MRRU) /* too small; clip */
mrru = MP_MIN_MRRU;
/* Update our links */
lcp->want_mrru = mrru;
}
break;
case MODE_REJ:
lcp->want_mrru = 0;
LCP_PEER_REJ(lcp, opt->type);
break;
}
}
break;
case TY_SHORTSEQNUM: /* multi-link short sequence numbers */
Log(LG_LCP, ("[%s] %s", l->name, oi->name));
switch (mode) {
case MODE_REQ:
if (!Enabled(&l->conf.options, LINK_CONF_MULTILINK) ||
!Acceptable(&l->conf.options, LINK_CONF_SHORTSEQ)) {
FsmRej(fp, opt);
break;
}
lcp->peer_shortseq = TRUE;
FsmAck(fp, opt);
break;
case MODE_NAK:
/* Let the peer to change it's mind. */
if (LCP_PEER_REJECTED(lcp, opt->type)) {
LCP_PEER_UNREJ(lcp, opt->type);
if (Enabled(&l->conf.options, LINK_CONF_MULTILINK))
lcp->want_shortseq = Enabled(&l->conf.options, LINK_CONF_SHORTSEQ);
}
break;
case MODE_REJ:
lcp->want_shortseq = FALSE;
LCP_PEER_REJ(lcp, opt->type);
break;
}
break;
case TY_ENDPOINTDISC: /* multi-link endpoint discriminator */
{
struct discrim dis;
char buf[64];
if (opt->len < 3 || opt->len > sizeof(dis.bytes)) {
Log(LG_LCP, ("[%s] %s bad len=%d", l->name, oi->name, opt->len));
if (mode == MODE_REQ)
FsmRej(fp, opt);
break;
}
memcpy(&dis.class, opt->data, opt->len - 2);
dis.len = opt->len - 3;
Log(LG_LCP, ("[%s] %s %s", l->name, oi->name, MpDiscrimText(&dis, buf, sizeof(buf))));
switch (mode) {
case MODE_REQ:
lcp->peer_discrim = dis;
FsmAck(fp, opt);
break;
case MODE_NAK:
/* Let the peer to change it's mind. */
LCP_PEER_UNREJ(lcp, opt->type);
break;
case MODE_REJ:
LCP_PEER_REJ(lcp, opt->type);
break;
}
}
break;
case TY_MAGICNUM: /* magic number */
{
u_int32_t magic;
memcpy(&magic, opt->data, 4);
magic = ntohl(magic);
Log(LG_LCP, ("[%s] %s 0x%08x", l->name, oi->name, magic));
switch (mode) {
case MODE_REQ:
if (lcp->want_magic) {
if (magic == lcp->want_magic) {
Log(LG_LCP, ("[%s] Same magic! Detected loopback condition", l->name));
magic = htonl(~magic);
memcpy(opt->data, &magic, 4);
FsmNak(fp, opt);
break;
}
lcp->peer_magic = magic;
FsmAck(fp, opt);
break;
}
FsmRej(fp, opt);
break;
case MODE_NAK:
lcp->want_magic = GenerateMagic();
break;
case MODE_REJ:
lcp->want_magic = 0;
LCP_PEER_REJ(lcp, opt->type);
break;
}
}
break;
case TY_PROTOCOMP: /* Protocol field compression */
Log(LG_LCP, ("[%s] %s", l->name, oi->name));
switch (mode) {
case MODE_REQ:
if (Acceptable(&l->conf.options, LINK_CONF_PROTOCOMP)) {
lcp->peer_protocomp = TRUE;
FsmAck(fp, opt);
break;
}
FsmRej(fp, opt);
break;
case MODE_NAK: /* a NAK here doesn't make sense */
case MODE_REJ:
lcp->want_protocomp = FALSE;
LCP_PEER_REJ(lcp, opt->type);
break;
}
break;
case TY_ACFCOMP: /* Address field compression */
Log(LG_LCP, ("[%s] %s", l->name, oi->name));
switch (mode) {
case MODE_REQ:
if (Acceptable(&l->conf.options, LINK_CONF_ACFCOMP)) {
lcp->peer_acfcomp = TRUE;
FsmAck(fp, opt);
break;
}
FsmRej(fp, opt);
break;
case MODE_NAK: /* a NAK here doesn't make sense */
case MODE_REJ:
lcp->want_acfcomp = FALSE;
LCP_PEER_REJ(lcp, opt->type);
break;
}
break;
case TY_CALLBACK: /* Callback */
Log(LG_LCP, ("[%s] %s %d", l->name, oi->name, opt->data[0]));
switch (mode) {
case MODE_REQ: /* we only support peer calling us back */
FsmRej(fp, opt);
break;
case MODE_NAK: /* we only know one way to do it */
/* fall through */
case MODE_REJ:
lcp->want_callback = FALSE;
LCP_PEER_REJ(lcp, opt->type);
break;
}
break;
case TY_VENDOR:
{
Log(LG_LCP, ("[%s] %s %02x%02x%02x:%d", l->name, oi->name,
opt->data[0], opt->data[1], opt->data[2], opt->data[3]));
switch (mode) {
case MODE_REQ:
FsmRej(fp, opt);
break;
case MODE_NAK:
/* fall through */
case MODE_REJ:
LCP_PEER_REJ(lcp, opt->type);
break;
}
break;
}
break;
default:
assert(0);
}
}
}
/*
* LcpInput()
*/
void
LcpInput(Link l, Mbuf bp)
{
FsmInput(&l->lcp.fsm, bp);
}
static const struct fsmoption *
LcpAuthProtoNak(ushort proto, u_char alg)
{
static u_char chapmd5cf[] =
{ PROTO_CHAP >> 8, PROTO_CHAP & 0xff, CHAP_ALG_MD5 };
static const struct fsmoption chapmd5Nak =
{ TY_AUTHPROTO, 2 + sizeof(chapmd5cf), (u_char *) chapmd5cf };
static u_char chapmsv1cf[] =
{ PROTO_CHAP >> 8, PROTO_CHAP & 0xff, CHAP_ALG_MSOFT };
static const struct fsmoption chapmsv1Nak =
{ TY_AUTHPROTO, 2 + sizeof(chapmsv1cf), (u_char *) chapmsv1cf };
static u_char chapmsv2cf[] =
{ PROTO_CHAP >> 8, PROTO_CHAP & 0xff, CHAP_ALG_MSOFTv2 };
static struct fsmoption chapmsv2Nak =
{ TY_AUTHPROTO, 2 + sizeof(chapmsv2cf), (u_char *) chapmsv2cf };
static u_char papcf[] =
{ PROTO_PAP >> 8, PROTO_PAP & 0xff };
static struct fsmoption papNak =
{ TY_AUTHPROTO, 2 + sizeof(papcf), (u_char *) papcf };
static u_char eapcf[] =
{ PROTO_EAP >> 8, PROTO_EAP & 0xff };
static struct fsmoption eapNak =
{ TY_AUTHPROTO, 2 + sizeof(eapcf), (u_char *) eapcf };
if (proto == PROTO_PAP) {
return &papNak;
} else if (proto == PROTO_EAP) {
return &eapNak;
} else {
switch (alg) {
case CHAP_ALG_MSOFTv2:
return &chapmsv2Nak;
case CHAP_ALG_MSOFT:
return &chapmsv1Nak;
case CHAP_ALG_MD5:
return &chapmd5Nak;
default:
return NULL;
}
}
}
/*
* LcpFindAuthProto()
*
*/
static short
LcpFindAuthProto(ushort proto, u_char alg)
{
int i;
for(i = 0; i < LCP_NUM_AUTH_PROTOS; i++) {
if (gLcpAuthProtos[i].proto == proto && gLcpAuthProtos[i].alg == alg) {
return i;
}
}
return -1;
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>