/* $OpenBSD: lcp.c,v 1.18 2019/02/27 04:52:19 denis Exp $ */
/*-
* Copyright (c) 2009 Internet Initiative Japan Inc.
* 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.
*/
/* $Id: lcp.c,v 1.18 2019/02/27 04:52:19 denis Exp $ */
/**@file
* This file provides LCP related functions.
*
* RFC1661: The Point-to-Point Protocol (PPP)
* RFC1570: PPP LCP Extensions
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "npppd.h"
#include "ppp.h"
#include "psm-opt.h"
#define SPACE " \t\r\n"
#include "debugutil.h"
#ifdef LCP_DEBUG
#define LCP_DBG(x) fsm_log x
#define LCP_ASSERT(x) ASSERT(x)
#else
#define LCP_DBG(x)
#define LCP_ASSERT(x)
#endif
#define PROTREJ 0x08
#define ECHOREQ 0x09
#define ECHOREP 0x0a
#define IDENTIFICATION 0x0c
static void lcp_resetci(fsm *);
static void lcp_addci(fsm *, u_char *, int *);
static int lcp_reqci(fsm *, u_char *, int *, int);
static int lcp_ackci(fsm *, u_char *, int);
static int lcp_nakci(fsm *, u_char *, int);
static int lcp_rejci(fsm *, u_char *, int);
static int lcp_cilen(fsm *);
static void lcp_open(fsm *);
static void lcp_down(fsm *);
static void lcp_finished(fsm *);
static int lcp_ext(fsm *, int, int, u_char *, int);
static void lcp_timeout(void *);
static void lcp_reset_timeout(void *);
static int lcp_proxy_recv_ci(fsm *, u_char *, int);
static int lcp_proxy_sent_ci(fsm *, u_char *, int);
static void lcp_load_authconfig(fsm *f);
static struct fsm_callbacks lcp_callbacks = {
lcp_resetci, /* Reset our Configuration Information */
lcp_cilen, /* Length of our Configuration Information */
lcp_addci, /* Add our Configuration Information */
lcp_ackci, /* ACK our Configuration Information */
lcp_nakci, /* NAK our Configuration Information */
lcp_rejci, /* Reject our Configuration Information */
lcp_reqci, /* Request peer's Configuration Information */
lcp_open, /* Called when fsm reaches OPENED state */
lcp_down, /* Called when fsm leaves OPENED state */
NULL, /* Called when we want the lower layer up */
lcp_finished, /* Called when we want the lower layer down */
NULL, /* Called when Protocol-Reject received */
NULL, /* Retransmission is necessary */
lcp_ext, /* Called to handle LCP-specific codes */
"lcp" /* String name of protocol */
};
#define NO_AUTH_AGREEABLE(lcp) \
(!psm_opt_is_enabled(lcp, pap) || psm_opt_is_rejected(lcp, pap)) && \
(!psm_opt_is_enabled(lcp, chap) || psm_opt_is_rejected(lcp, chap)) && \
(!psm_opt_is_enabled(lcp, chapms) || psm_opt_is_rejected(lcp, chapms)) &&\
(!psm_opt_is_enabled(lcp, chapms_v2) || psm_opt_is_rejected(lcp, chapms_v2)) && \
(!psm_opt_is_enabled(lcp, eap) || psm_opt_is_rejected(lcp, eap))
/** initializing context for LCP. */
void
lcp_init(lcp *_this, npppd_ppp *ppp)
{
struct tunnconf *conf;
fsm_init(&_this->fsm);
_this->fsm.ppp = ppp;
_this->fsm.callbacks = &lcp_callbacks;
_this->fsm.protocol = PPP_PROTO_LCP;
_this->fsm.flags |= OPT_SILENT;
_this->timerctx.ctx = _this;
_this->recv_ress = 0;
_this->recv_reqs = 0;
_this->magic_number = arc4random();
conf = ppp_get_tunnconf(ppp);
PPP_FSM_CONFIG(&_this->fsm, timeouttime, conf->lcp_timeout);
PPP_FSM_CONFIG(&_this->fsm, maxconfreqtransmits,
conf->lcp_max_configure);
PPP_FSM_CONFIG(&_this->fsm, maxtermtransmits,
conf->lcp_max_terminate);
PPP_FSM_CONFIG(&_this->fsm, maxnakloops,
conf->lcp_max_nak_loop);
_this->echo_failures = 0;
if (!conf->lcp_keepalive)
_this->echo_interval = 0;
else {
_this->echo_interval = conf->lcp_keepalive_interval;
_this->echo_retry_interval = conf->lcp_keepalive_retry_interval;
_this->echo_max_retries = conf->lcp_keepalive_max_retries;
}
_this->auth_order[0] = -1;
}
/**
* This function is called when HDLC as LCP's lower layer is up.
*/
void
lcp_lowerup(lcp *_this)
{
fsm_lowerup(&_this->fsm);
fsm_open(&_this->fsm);
if (_this->dialin_proxy != 0 &&
_this->dialin_proxy_lcp_renegotiation == 0) {
_this->fsm.state = OPENED;
lcp_open(&_this->fsm);
}
}
/**
* sending Protocol-Reject.
*/
void
lcp_send_protrej(lcp *_this, u_char *pktp, int lpktp)
{
LCP_ASSERT(_this != NULL);
LCP_ASSERT(pktp != NULL);
fsm_sdata(&_this->fsm, PROTREJ, _this->fsm.id++, pktp, lpktp);
}
static const char *
lcp_auth_string(int auth)
{
switch (auth) {
case PPP_AUTH_PAP: return "PAP";
case PPP_AUTH_CHAP_MD5: return "MD5-CHAP";
case PPP_AUTH_CHAP_MS: return "MS-CHAP";
case PPP_AUTH_CHAP_MS_V2: return "MS-CHAP-V2";
case PPP_AUTH_EAP: return "EAP";
case 0: return "none";
default: return "ERROR";
}
}
static void
lcp_open(fsm *f)
{
lcp *_this;
int peer_auth = 0;
LCP_ASSERT(f != NULL);
_this = &f->ppp->lcp;
if (psm_opt_is_accepted(_this, pap))
peer_auth = PPP_AUTH_PAP;
else if (psm_opt_is_accepted(_this, chap))
peer_auth = PPP_AUTH_CHAP_MD5;
else if (psm_opt_is_accepted(_this, chapms))
peer_auth = PPP_AUTH_CHAP_MS;
else if (psm_opt_is_accepted(_this, chapms_v2))
peer_auth = PPP_AUTH_CHAP_MS_V2;
else if (psm_opt_is_accepted(_this, eap))
peer_auth = PPP_AUTH_EAP;
else {
if (_this->auth_order[0] > 0) {
fsm_log(f, LOG_INFO,
"failed to negotiate a auth protocol.");
fsm_close(f, "Authentication is required");
ppp_set_disconnect_cause(f->ppp,
PPP_DISCON_AUTH_PROTOCOL_UNACCEPTABLE,
_this->auth_order[0] /* first one */,
1 /* peer refused */, NULL);
ppp_stop(f->ppp, "Authentication is required");
return;
}
}
f->ppp->peer_auth = peer_auth;
if (_this->xxxmru > 0 && f->ppp->peer_mru <= 0)
f->ppp->peer_mru = _this->xxxmru;
if (f->ppp->peer_mru <= 0)
f->ppp->peer_mru = f->ppp->mru;
/* checking the size of ppp->peer_mru. */
LCP_ASSERT(f->ppp->peer_mru > 500);
fsm_log(f, LOG_INFO, "logtype=Opened mru=%d/%d auth=%s magic=%08x/%08x"
, f->ppp->mru, f->ppp->peer_mru
, lcp_auth_string(peer_auth)
, f->ppp->lcp.magic_number, f->ppp->lcp.peer_magic_number
);
lcp_reset_timeout(_this);
ppp_lcp_up(f->ppp);
}
static void
lcp_down(fsm *f)
{
lcp *_this;
if (f->ppp->disconnect_code == PPP_DISCON_NO_INFORMATION) {
/*
* disconnect code is set when we are closing the lcp, so
* 'no info' means the lcp is going down by peer's termreq.
*/
ppp_set_disconnect_cause(f->ppp, PPP_DISCON_NORMAL, 0,
1 /* peer */, NULL);
#ifdef USE_NPPPD_RADIUS
ppp_set_radius_terminate_cause(f->ppp,
RADIUS_TERMNATE_CAUSE_USER_REQUEST);
#endif
}
_this = &f->ppp->lcp;
UNTIMEOUT(lcp_timeout, _this);
}
static void
lcp_finished(fsm *f)
{
ppp_lcp_finished(f->ppp);
}
/**
* resetting ConfReq.
*/
static void
lcp_resetci(fsm *f)
{
LCP_ASSERT(f != NULL);
/* Unless doing dialin-proxy without re-negotiation */
if (!(f->ppp->lcp.dialin_proxy != 0 &&
f->ppp->lcp.dialin_proxy_lcp_renegotiation == 0)) {
/* Reset the LCP options' state */
memset(&f->ppp->lcp.opt, 0, sizeof(f->ppp->lcp.opt));
f->ppp->lcp.auth_order[0] = -1;
}
}
/**
* The length of ConfReq.
*/
static int
lcp_cilen(fsm *f)
{
LCP_ASSERT(f != NULL);
return f->ppp->mru;
}
/**
* selecting authentication protocols which is not rejected yet in order
* of auth_order, and adding Authentication-Protocol options in ConfReq
* packet area.
*/
static int
lcp_add_auth(fsm *f, u_char **ucpp)
{
int i;
u_char *ucp;
lcp *_this;
ucp = *ucpp;
_this = &f->ppp->lcp;
for (i = 0; _this->auth_order[i] > 0 &&
i < countof(_this->auth_order); i++) {
switch (_this->auth_order[i]) {
case PPP_AUTH_PAP:
if (psm_opt_is_rejected(_this, pap))
break;
PUTCHAR(PPP_LCP_AUTH_PROTOCOL, ucp);
PUTCHAR(4, ucp);
PUTSHORT(PPP_AUTH_PAP, ucp);
psm_opt_set_requested(_this, pap, 1);
_this->lastauth = PPP_AUTH_PAP;
goto end_loop;
case PPP_AUTH_CHAP_MD5:
if (psm_opt_is_rejected(_this, chap))
break;
PUTCHAR(PPP_LCP_AUTH_PROTOCOL, ucp);
PUTCHAR(5, ucp);
PUTSHORT(PPP_AUTH_CHAP, ucp);
PUTCHAR(PPP_AUTH_CHAP_MD5, ucp);
psm_opt_set_requested(_this, chap, 1);
_this->lastauth = PPP_AUTH_CHAP_MD5;
goto end_loop;
case PPP_AUTH_CHAP_MS:
if (psm_opt_is_rejected(_this, chapms))
break;
PUTCHAR(PPP_LCP_AUTH_PROTOCOL, ucp);
PUTCHAR(5, ucp);
PUTSHORT(PPP_AUTH_CHAP, ucp);
PUTCHAR(PPP_AUTH_CHAP_MS, ucp);
psm_opt_set_requested(_this, chapms, 1);
_this->lastauth = PPP_AUTH_CHAP_MS;
goto end_loop;
case PPP_AUTH_CHAP_MS_V2:
if (psm_opt_is_rejected(_this, chapms_v2))
break;
PUTCHAR(PPP_LCP_AUTH_PROTOCOL, ucp);
PUTCHAR(5, ucp);
PUTSHORT(PPP_AUTH_CHAP, ucp);
PUTCHAR(PPP_AUTH_CHAP_MS_V2, ucp);
psm_opt_set_requested(_this, chapms_v2,1);
_this->lastauth = PPP_AUTH_CHAP_MS_V2;
goto end_loop;
case PPP_AUTH_EAP:
if (psm_opt_is_rejected(_this, eap))
break;
PUTCHAR(PPP_LCP_AUTH_PROTOCOL, ucp);
PUTCHAR(4, ucp);
PUTSHORT(PPP_AUTH_EAP, ucp);
psm_opt_set_requested(_this, eap, 1);
_this->lastauth = PPP_AUTH_EAP;
goto end_loop;
}
}
_this->lastauth = -1;
return -1;
end_loop:
*ucpp = ucp;
return 0;
}
/**
* making ConfReq.
*/
static void
lcp_addci(fsm *f, u_char *ucp, int *lenp)
{
lcp *_this;
u_char *start_ucp = ucp;
LCP_ASSERT(f != NULL);
_this = &f->ppp->lcp;
if (!psm_opt_is_rejected(_this, mru)) {
PUTCHAR(PPP_LCP_MRU, ucp);
PUTCHAR(4, ucp);
if (_this->xxxmru > 0) { /* this value is got by Nak. */
PUTSHORT(_this->xxxmru, ucp);
} else {
PUTSHORT(f->ppp->mru, ucp);
}
psm_opt_set_requested(_this, mru, 1);
}
if (f->ppp->has_acf == 1) {
if (!psm_opt_is_rejected(_this, pfc)) {
PUTCHAR(PPP_LCP_PFC, ucp);
PUTCHAR(2, ucp);
psm_opt_set_requested(_this, pfc, 1);
}
if (!psm_opt_is_rejected(_this, acfc)) {
PUTCHAR(PPP_LCP_ACFC, ucp);
PUTCHAR(2, ucp);
psm_opt_set_requested(_this, acfc, 1);
}
}
PUTCHAR(PPP_LCP_MAGICNUMBER, ucp);
PUTCHAR(6, ucp);
PUTLONG(_this->magic_number, ucp);
if (f->ppp->peer_auth != 0) {
_this->auth_order[0] = f->ppp->peer_auth;
_this->auth_order[1] = -1;
} else if (_this->auth_order[0] < 0) {
lcp_load_authconfig(f);
}
lcp_add_auth(f, &ucp);
*lenp = ucp - start_ucp;
}
static int
lcp_reqci(fsm *f, u_char *inp, int *lenp, int reject_if_disagree)
{
uint32_t magic;
int type, len, rcode, mru, lrej;
u_char *inp0, *rejbuf, *nakbuf, *nakbuf0;
lcp *_this;
_this = &f->ppp->lcp;
rejbuf = NULL;
rcode = -1;
inp0 = inp;
lrej = 0;
if ((rejbuf = malloc(*lenp)) == NULL)
return -1;
if ((nakbuf0 = malloc(*lenp)) == NULL) {
free(rejbuf);
return -1;
}
nakbuf = nakbuf0;
#define remlen() (*lenp - (inp - inp0))
#define LCP_OPT_PEER_ACCEPTED(opt) \
psm_peer_opt_set_accepted(&f->ppp->lcp, opt, 1);
f->ppp->lcp.recv_reqs++;
while (remlen() >= 2) {
GETCHAR(type, inp);
GETCHAR(len, inp);
if (len <= 0 || remlen() + 2 < len)
goto fail;
switch (type) {
case PPP_LCP_MRU:
if (len != 4)
goto fail;
GETSHORT(mru, inp);
f->ppp->peer_mru = mru;
if (mru < NPPPD_MIN_MRU) {
if (reject_if_disagree) {
inp -= 2;
goto reject;
}
if (lrej > 0) {
/* if there is a reject, will send Rej, not send Nak. */
} else {
inp -= 2;
memcpy(nakbuf, inp, len);
nakbuf += len;
inp += 2;
PUTSHORT(f->ppp->mru, nakbuf);
rcode = CONFNAK;
}
} else
LCP_OPT_PEER_ACCEPTED(mru);
break;
case PPP_LCP_MAGICNUMBER:
if (len != 6)
goto fail;
GETLONG(magic, inp);
if (magic == _this->magic_number) {
inp -= 4;
goto reject;
}
_this->peer_magic_number = magic;
break;
case PPP_LCP_PFC:
if (len != 2)
goto fail;
LCP_OPT_PEER_ACCEPTED(pfc);
break;
case PPP_LCP_ACFC:
if (len != 2)
goto fail;
LCP_OPT_PEER_ACCEPTED(acfc);
break;
case PPP_LCP_AUTH_PROTOCOL:
/* currently never authenticate. */
case PPP_LCP_QUALITY_PROTOCOL: /* not used */
default:
reject:
inp -= 2;
memcpy(rejbuf + lrej, inp, len);
lrej += len;
inp += len;
rcode = CONFREJ;
}
continue;
}
if (rcode == -1)
rcode = CONFACK;
fail:
switch (rcode) {
case CONFREJ:
memcpy(inp0, rejbuf, lrej);
*lenp = lrej;
break;
case CONFNAK:
memcpy(inp0, nakbuf0, nakbuf - nakbuf0);
*lenp = nakbuf - nakbuf0;
break;
}
if (rcode != CONFACK) {
psm_peer_opt_set_accepted(&f->ppp->lcp, mru, 0);
psm_peer_opt_set_accepted(&f->ppp->lcp, pfc, 0);
psm_peer_opt_set_accepted(&f->ppp->lcp, acfc, 0);
}
free(rejbuf);
free(nakbuf0);
return rcode;
#undef remlen
#undef LCP_OPT_PEER_ACCEPTED
}
/** receiving ConfAck. */
static int
lcp_ackci(fsm *f, u_char *inp, int inlen)
{
int chapalg, authproto, type, len, mru, magic;
u_char *inp0;
#define remlen() (inlen - (inp - inp0))
#define LCP_OPT_ACCEPTED(opt) \
if (!psm_opt_is_requested(&f->ppp->lcp, opt)) \
goto fail; \
psm_opt_set_accepted(&f->ppp->lcp, opt, 1);
f->ppp->lcp.recv_ress++;
inp0 = inp;
while (remlen() >= 2) {
GETCHAR(type, inp);
GETCHAR(len, inp);
if (len <= 0 || remlen() + 2 < len)
goto fail;
switch (type) {
case PPP_LCP_MAGICNUMBER:
if (len != 6)
goto fail;
GETLONG(magic, inp);
if (f->ppp->lcp.magic_number != magic)
goto fail;
break;
case PPP_LCP_MRU:
if (len != 4)
goto fail;
LCP_OPT_ACCEPTED(mru);
GETSHORT(mru, inp);
break;
case PPP_LCP_AUTH_PROTOCOL:
if (len < 4)
goto fail;
GETSHORT(authproto, inp);
switch (authproto) {
case PPP_AUTH_PAP:
if (len != 4)
goto fail;
LCP_OPT_ACCEPTED(pap);
break;
case PPP_AUTH_CHAP:
if (len != 5)
goto fail;
GETCHAR(chapalg, inp);
switch (chapalg) {
case PPP_AUTH_CHAP_MD5:
LCP_OPT_ACCEPTED(chap);
break;
case PPP_AUTH_CHAP_MS:
LCP_OPT_ACCEPTED(chapms);
break;
case PPP_AUTH_CHAP_MS_V2:
LCP_OPT_ACCEPTED(chapms_v2);
break;
}
break;
case PPP_AUTH_EAP:
if (len != 4)
goto fail;
LCP_OPT_ACCEPTED(eap);
break;
}
break;
/*
* As RFC1661, ConfRej must be used for boolean options, but
* at least RouterTester uses ConfNak for them.
*/
case PPP_LCP_PFC:
if (len != 2)
goto fail;
LCP_OPT_ACCEPTED(pfc);
break;
case PPP_LCP_ACFC:
if (len != 2)
goto fail;
LCP_OPT_ACCEPTED(acfc);
break;
default:
goto fail;
}
}
return 1;
fail:
fsm_log(f, LOG_ERR, "Received unexpected ConfAck.");
if (debug_get_debugfp() != NULL)
show_hd(debug_get_debugfp(), inp, remlen());
return 0;
#undef LCP_OPT_ACCEPTED
}
/** receiving ConfNak. */
static int
lcp_nakci(fsm *f, u_char *inp, int inlen)
{
int chapalg, authproto, type, len, mru;
u_char *inp0;
lcp *_this;
const char *peer_auth = "unknown";
#define remlen() (inlen - (inp - inp0))
#define LCP_OPT_REJECTED(opt) \
if (!psm_opt_is_requested(&f->ppp->lcp, opt)) \
goto fail; \
psm_opt_set_rejected(&f->ppp->lcp, opt, 1);
f->ppp->lcp.recv_ress++;
inp0 = inp;
_this = &f->ppp->lcp;
while (remlen() >= 2) {
GETCHAR(type, inp);
GETCHAR(len, inp);
if (len <= 0 || remlen() + 2 < len)
goto fail;
switch (type) {
case PPP_LCP_MRU:
if (len < 4)
goto fail;
GETSHORT(mru, inp);
fsm_log(f, LOG_NOTICE,
"ignored ConfNak from the peer: mru=%d", mru);
_this->xxxmru = mru;
break;
case PPP_LCP_AUTH_PROTOCOL:
if (len < 4)
goto fail;
switch (_this->lastauth) {
case PPP_AUTH_PAP:
psm_opt_set_rejected(_this, pap, 1);
break;
case PPP_AUTH_CHAP_MD5:
psm_opt_set_rejected(_this, chap, 1);
break;
case PPP_AUTH_CHAP_MS:
psm_opt_set_rejected(_this, chapms, 1);
break;
case PPP_AUTH_CHAP_MS_V2:
psm_opt_set_rejected(_this, chapms_v2, 1);
break;
case PPP_AUTH_EAP:
psm_opt_set_rejected(_this, eap, 1);
break;
}
GETSHORT(authproto, inp);
switch (authproto) {
case PPP_AUTH_PAP:
if (psm_opt_is_requested(_this, pap))
psm_opt_set_accepted(_this, pap, 1);
peer_auth = "pap";
break;
case PPP_AUTH_CHAP:
chapalg = 0;
if (len == 5)
GETCHAR(chapalg, inp);
switch (chapalg) {
case PPP_AUTH_CHAP_MD5:
if (psm_opt_is_requested(_this, chap))
psm_opt_set_accepted(_this,
chap, 1);
peer_auth = "chap";
break;
case PPP_AUTH_CHAP_MS:
if (psm_opt_is_requested(_this, chapms))
psm_opt_set_accepted(_this,
chapms, 1);
peer_auth = "mschap";
break;
case PPP_AUTH_CHAP_MS_V2:
if (psm_opt_is_requested(_this,
chapms_v2))
psm_opt_set_accepted(_this,
chapms_v2, 1);
peer_auth = "mschap_v2";
break;
default:
fsm_log(f, LOG_INFO,
"Nacked chap algorithm is "
"unknown(%d).", chapalg);
peer_auth = "unknown";
break;
}
break;
case PPP_AUTH_EAP:
if (len != 4)
goto fail;
peer_auth = "eap";
if (psm_opt_is_requested(_this, eap))
psm_opt_set_accepted(_this, eap, 1);
break;
}
if (NO_AUTH_AGREEABLE(_this)) {
fsm_log(f, LOG_INFO, "No authentication "
"protocols are agreeable. peer's "
"auth proto=%s",
peer_auth);
ppp_set_disconnect_cause(f->ppp,
PPP_DISCON_AUTH_PROTOCOL_UNACCEPTABLE,
authproto, 2 /* couldn't accept peer's */,
NULL);
ppp_stop(f->ppp, "Authentication is required");
return 1;
}
break;
case PPP_LCP_PFC:
if (len != 2)
goto fail;
LCP_OPT_REJECTED(pfc);
break;
case PPP_LCP_ACFC:
if (len != 2)
goto fail;
LCP_OPT_REJECTED(acfc);
break;
default:
goto fail;
}
}
return 1;
fail:
log_printf(LOG_ERR, "Received unexpected ConfNak.");
if (debug_get_debugfp() != NULL)
show_hd(debug_get_debugfp(), inp, inlen);
return 0;
#undef remlen
#undef LCP_OPT_REJECTED
}
/**
* receiving ConfRej.
*/
static int
lcp_rejci(fsm *f, u_char *inp, int inlen)
{
int chapalg, authproto, type, len, mru;
u_char *inp0;
lcp *_this;
#define remlen() (inlen - (inp - inp0))
#define LCP_OPT_REJECTED(opt) \
if (!psm_opt_is_requested(&f->ppp->lcp, opt)) \
goto fail; \
psm_opt_set_rejected(&f->ppp->lcp, opt, 1);
f->ppp->lcp.recv_ress++;
inp0 = inp;
_this = &f->ppp->lcp;
while (remlen() >= 2) {
GETCHAR(type, inp);
GETCHAR(len, inp);
if (len <= 0 || remlen() + 2 < len)
goto fail;
switch (type) {
case PPP_LCP_MAGICNUMBER:
if (f->ppp->lcp.echo_interval > 0)
goto fail;
inp += 4;
break;
case PPP_LCP_MRU:
LCP_OPT_REJECTED(mru);
GETSHORT(mru, inp);
break;
case PPP_LCP_AUTH_PROTOCOL:
if (len < 4)
goto fail;
GETSHORT(authproto, inp);
switch (authproto) {
case PPP_AUTH_PAP:
if (len != 4)
goto fail;
LCP_OPT_REJECTED(pap);
break;
case PPP_AUTH_CHAP:
chapalg = 0;
if (len == 5)
GETCHAR(chapalg, inp);
switch (chapalg) {
case PPP_AUTH_CHAP_MD5:
LCP_OPT_REJECTED(chap);
break;
case PPP_AUTH_CHAP_MS:
LCP_OPT_REJECTED(chapms);
break;
case PPP_AUTH_CHAP_MS_V2:
LCP_OPT_REJECTED(chapms_v2);
break;
default:
fsm_log(f, LOG_INFO,
"Rejected chap algorithm is "
"unknown(%d).", chapalg);
break;
}
break;
case PPP_AUTH_EAP:
if (len != 4)
goto fail;
LCP_OPT_REJECTED(eap);
break;
}
if (NO_AUTH_AGREEABLE(_this)) {
fsm_log(f, LOG_INFO, "No authentication "
"protocols are agreeable.");
ppp_set_disconnect_cause(f->ppp,
PPP_DISCON_AUTH_PROTOCOL_UNACCEPTABLE,
authproto, 1 /* rejected by peer */, NULL);
ppp_stop(f->ppp, "Authentication is required");
return 1;
}
break;
case PPP_LCP_PFC:
if (len != 2)
goto fail;
LCP_OPT_REJECTED(pfc);
break;
case PPP_LCP_ACFC:
if (len != 2)
goto fail;
LCP_OPT_REJECTED(acfc);
break;
default:
goto fail;
}
}
return 1;
fail:
log_printf(LOG_ERR, "Received unexpected ConfRej.");
if (debug_get_debugfp() != NULL)
show_hd(debug_get_debugfp(), inp, inlen);
return 0;
#undef remlen
}
static void
lcp_rcoderej(fsm *f, u_char *inp, int inlen)
{
uint16_t proto;
fsm *rejfsm;
if (inlen < 2) {
fsm_log(f, LOG_WARNING, "Received short ProtRej packet.");
return;
}
GETSHORT(proto, inp);
rejfsm = NULL;
switch (proto) {
case PPP_PROTO_LCP:
rejfsm = &f->ppp->lcp.fsm;
break;
case PPP_PROTO_PAP:
fsm_log(f, LOG_WARNING, "our PAP packet is rejected");
return;
case PPP_PROTO_CHAP:
fsm_log(f, LOG_WARNING, "our CHAP packet is rejected");
return;
case PPP_PROTO_EAP:
fsm_log(f, LOG_ERR, "our EAP packet is rejected");
ppp_stop(f->ppp, "Authentication Required");
break;
case PPP_PROTO_NCP | NCP_IPCP:
rejfsm = &f->ppp->ipcp.fsm;
break;
case PPP_PROTO_NCP | NCP_CCP:
rejfsm = &f->ppp->ccp.fsm;
break;
}
if (rejfsm == NULL) {
fsm_log(f, LOG_WARNING,
"Received ProtRej packet for unknown protocol=(%d/%04x)",
proto, proto);
return;
}
fsm_protreject(rejfsm);
return;
}
static void
lcp_reset_timeout(void *ctx)
{
lcp *_this;
_this = ctx;
if (_this->echo_interval > 0) {
if (_this->echo_failures == 0) {
TIMEOUT(lcp_timeout, _this, _this->echo_interval);
} else {
TIMEOUT(lcp_timeout, _this, _this->echo_retry_interval);
}
} else {
UNTIMEOUT(lcp_timeout, _this);
}
}
static void
lcp_timeout(void *ctx)
{
lcp *_this;
u_char *cp, buf[32];
_this = ctx;
if (_this->echo_failures >= _this->echo_max_retries) {
fsm_log(&_this->fsm, LOG_NOTICE, "keepalive failure.");
if (_this->fsm.ppp != NULL) {
#ifdef USE_NPPPD_RADIUS
ppp_set_radius_terminate_cause(_this->fsm.ppp,
RADIUS_TERMNATE_CAUSE_IDLE_TIMEOUT);
#endif
ppp_stop(_this->fsm.ppp, NULL);
}
return;
}
cp = buf;
PUTLONG(_this->magic_number, cp);
fsm_sdata(&_this->fsm, ECHOREQ, _this->fsm.id++, buf, 4);
_this->echo_failures++;
lcp_reset_timeout(_this);
}
static int
lcp_rechoreq(fsm *f, int id, u_char *inp, int inlen)
{
u_char *inp0;
lcp *_this;
int len;
if (inlen < 4)
return 0;
_this = &f->ppp->lcp;
inp0 = inp;
PUTLONG(_this->magic_number, inp)
len = MINIMUM(inlen, f->ppp->peer_mru - 8);
fsm_sdata(f, ECHOREP, id, inp0, len);
return 1;
}
static int
lcp_ext(fsm *f, int code, int id, u_char *inp, int inlen)
{
lcp *_this;
uint32_t magic;
char buf[256];
int i, len;
_this = &f->ppp->lcp;
switch (code) {
case IDENTIFICATION:
/* RFC 1570 */
if (inlen > 4) {
GETLONG(magic, inp);
inlen -= 4;
memset(buf, 0, sizeof(buf));
len = MINIMUM(inlen, sizeof(buf) - 1);
memcpy(buf, inp, len);
buf[len] = '\0';
for (i = 0; i < len; i++) {
if (!isprint((unsigned char)buf[i]))
buf[i] = '.';
}
fsm_log(f, LOG_INFO,
"RecvId magic=%08x text=%s", magic, buf);
}
return 1;
case PROTREJ:
lcp_rcoderej(f, inp, inlen);
return 1;
case ECHOREP:
if (f->state == OPENED) {
if (inlen >= 4) {
GETLONG(magic, inp);
if (_this->peer_magic_number == magic) {
_this->echo_failures = 0;
lcp_reset_timeout(_this);
}
}
}
return 1;
case ECHOREQ:
if (f->state == OPENED)
return lcp_rechoreq(f, id, inp, inlen);
return 1;
}
return 0;
}
/*
* reading some authentication settings and storing ppp_order in
* order of settings.
*/
static void
lcp_load_authconfig(fsm *f)
{
int i;
lcp *_this;
struct tunnconf *conf;
i = 0;
_this = &f->ppp->lcp;
conf = ppp_get_tunnconf(f->ppp);
if ((conf->auth_methods & NPPPD_AUTH_METHODS_MSCHAPV2) != 0) {
_this->auth_order[i++] = PPP_AUTH_CHAP_MS_V2;
psm_opt_set_enabled(_this,chapms_v2, 1);
}
if ((conf->auth_methods & NPPPD_AUTH_METHODS_CHAP) != 0) {
_this->auth_order[i++] = PPP_AUTH_CHAP_MD5;
psm_opt_set_enabled(_this, chap, 1);
}
if ((conf->auth_methods & NPPPD_AUTH_METHODS_PAP) != 0) {
_this->auth_order[i++] = PPP_AUTH_PAP;
psm_opt_set_enabled(_this, pap, 1);
}
_this->auth_order[i] = -1;
}
/***********************************************************************
* related functions of Dialin Proxy
**********************************************************************/
/**
* This function set LCP status following dialin proxy information.
* This returns non-zero value when LCP status is unacceptable.
*
*/
int
lcp_dialin_proxy(lcp *_this, dialin_proxy_info *dpi, int renegotiation,
int force_renegotiation)
{
int i, authok;
_this->dialin_proxy = 1;
lcp_load_authconfig(&_this->fsm);
/* whether authentication type is permitted by configuration or not. */
authok = 0;
if (dpi->auth_type != 0) {
for (i = 0; _this->auth_order[i] > 0; i++) {
if (_this->auth_order[i] != dpi->auth_type)
continue;
authok = 1;
break;
}
}
if (!authok) {
if (!renegotiation) {
fsm_log(&_this->fsm, LOG_NOTICE,
"dialin-proxy failed. auth-method=%s is "
"not enabled. Try 'l2tp.dialin.lcp_renegotion'",
lcp_auth_string(dpi->auth_type));
return 1;
}
_this->dialin_proxy_lcp_renegotiation = 1;
}
if (force_renegotiation)
_this->dialin_proxy_lcp_renegotiation = 1;
if (_this->dialin_proxy_lcp_renegotiation == 0) {
_this->fsm.ppp->peer_auth = dpi->auth_type;
/*
* Set the rejected flag to all options here for the moment,
* the agreeed options will be handled in lcp_proxy_sent_ci().
*/
psm_opt_set_rejected(_this, mru, 1);
psm_opt_set_rejected(_this, pfc, 1);
psm_opt_set_rejected(_this, acfc, 1);
psm_opt_set_rejected(_this, pap, 1);
psm_opt_set_rejected(_this, chap, 1);
psm_opt_set_rejected(_this, chapms, 1);
psm_opt_set_rejected(_this, chapms_v2, 1);
psm_opt_set_rejected(_this, eap, 1);
}
switch (dpi->auth_type) {
case PPP_AUTH_PAP:
pap_proxy_authen_prepare(&_this->fsm.ppp->pap, dpi);
break;
case PPP_AUTH_CHAP_MD5:
chap_proxy_authen_prepare(&_this->fsm.ppp->chap, dpi);
break;
}
if (lcp_proxy_sent_ci(&_this->fsm, dpi->last_sent_lcp.data,
dpi->last_sent_lcp.ldata) != 0) {
fsm_log(&_this->fsm, LOG_NOTICE,
"dialin-proxy failed. couldn't use proxied lcp.");
return 1;
}
if (lcp_proxy_recv_ci(&_this->fsm, dpi->last_recv_lcp.data,
dpi->last_recv_lcp.ldata) != 0) {
fsm_log(&_this->fsm, LOG_NOTICE,
"dialin-proxy failed. couldn't use proxied lcp.");
return 1;
}
fsm_log(&_this->fsm, LOG_INFO,
"dialin-proxy user=%s auth-type=%s renegotiate=%s",
dpi->username,
(dpi->auth_type == 0)? "none" : lcp_auth_string(dpi->auth_type),
(_this->dialin_proxy_lcp_renegotiation != 0)? "yes" : "no");
if (_this->dialin_proxy_lcp_renegotiation == 0)
_this->fsm.flags |= OPT_SILENT; /* It's ready to be "Opened" */
else
_this->fsm.flags &= ~OPT_SILENT;
return 0;
}
/*
* This function copies from lcp_reqci. It only differs as follows:
* - changes LCP_OPT_ACCEPTED.
* - Magic Number and MRU.
*/
static int
lcp_proxy_recv_ci(fsm *f, u_char *inp, int inlen)
{
int type, mru, len;
uint32_t magic;
u_char *inp0;
lcp *_this;
#define remlen() (inlen - (inp - inp0))
#define LCP_OPT_PEER_ACCEPTED(opt) \
psm_peer_opt_set_rejected(&f->ppp->lcp, opt, 0); \
psm_peer_opt_set_requested(&f->ppp->lcp, opt, 1); \
psm_peer_opt_set_accepted(&f->ppp->lcp, opt, 1);
_this = &f->ppp->lcp;
inp0 = inp;
while (remlen() >= 2) {
GETCHAR(type, inp);
GETCHAR(len, inp);
if (len <= 0 || remlen() + 2 < len)
goto fail;
switch (type) {
case PPP_LCP_MRU:
if (len != 4)
goto fail;
GETSHORT(mru, inp);
f->ppp->peer_mru = mru;
if (mru < NPPPD_MIN_MRU)
goto fail;
else
LCP_OPT_PEER_ACCEPTED(mru);
break;
case PPP_LCP_MAGICNUMBER:
if (len != 6)
goto fail;
GETLONG(magic, inp);
if (magic == _this->magic_number)
goto fail;
_this->peer_magic_number = magic;
break;
case PPP_LCP_PFC:
if (len != 2)
goto fail;
LCP_OPT_PEER_ACCEPTED(pfc);
break;
case PPP_LCP_ACFC:
if (len != 2)
goto fail;
LCP_OPT_PEER_ACCEPTED(acfc);
break;
case PPP_LCP_ACCM:
if (len != 6)
goto fail;
/* we don't use async framing. ignore this */
inp += (len - 2);
break;
default:
goto fail;
}
}
#undef remlen
#undef LCP_OPT_PEER_ACCEPTED
return 0;
fail:
return 1;
}
/*
* This function copies from lcp_ackci. It only differs as follows:
* - Do not recv_reass++.
* - changes LCP_OPT_ACCEPTED.
* - Magic Number and MRU.
*/
static int
lcp_proxy_sent_ci(fsm *f, u_char *inp, int inlen)
{
int chapalg, authproto, type, len, mru, magic;
u_char *inp0;
#define remlen() (inlen - (inp - inp0))
#define LCP_OPT_ACCEPTED(opt) \
if (f->ppp->lcp.dialin_proxy_lcp_renegotiation == 0) { \
psm_opt_set_rejected(&f->ppp->lcp, opt, 0); \
psm_opt_set_requested(&f->ppp->lcp, opt, 1); \
psm_opt_set_accepted(&f->ppp->lcp, opt, 1); \
}
inp0 = inp;
while (remlen() >= 2) {
GETCHAR(type, inp);
GETCHAR(len, inp);
if (len <= 0 || remlen() + 2 < len)
goto fail;
switch (type) {
case PPP_LCP_MAGICNUMBER:
if (len != 6)
goto fail;
GETLONG(magic, inp);
f->ppp->lcp.magic_number = magic;
break;
case PPP_LCP_MRU:
if (len != 4)
goto fail;
LCP_OPT_ACCEPTED(mru);
GETSHORT(mru, inp);
f->ppp->lcp.xxxmru = mru;
break;
case PPP_LCP_AUTH_PROTOCOL:
if (len < 4)
goto fail;
GETSHORT(authproto, inp);
switch (authproto) {
case PPP_AUTH_PAP:
if (len != 4)
goto fail;
LCP_OPT_ACCEPTED(pap);
break;
case PPP_AUTH_CHAP:
if (len != 5)
goto fail;
GETCHAR(chapalg, inp);
switch (chapalg) {
case PPP_AUTH_CHAP_MD5:
LCP_OPT_ACCEPTED(chap);
break;
case PPP_AUTH_CHAP_MS:
LCP_OPT_ACCEPTED(chapms);
break;
case PPP_AUTH_CHAP_MS_V2:
LCP_OPT_ACCEPTED(chapms_v2);
break;
}
break;
case PPP_AUTH_EAP:
if (len != 4)
goto fail;
LCP_OPT_ACCEPTED(eap);
break;
}
break;
case PPP_LCP_PFC:
if (len != 2)
goto fail;
LCP_OPT_ACCEPTED(pfc);
break;
case PPP_LCP_ACFC:
if (len != 2)
goto fail;
LCP_OPT_ACCEPTED(acfc);
break;
case PPP_LCP_ACCM:
if (len != 6)
goto fail;
/* we don't use async framing. ignore this */
inp += (len - 2);
break;
default:
goto fail;
}
}
return 0;
fail:
return 1;
#undef LCP_OPT_ACCEPTED
}