Add lcp_renegotiation option

To support proxy LCP negotiation.

Note: we *have* to take the auth id from the proxy answer, otherwise we would
replay previous ids, for which the client might cache the answer and thus
ignore our new challenge and just repeat their outdated answer.
This commit is contained in:
Samuel Thibault 2025-03-08 20:09:33 -05:00
parent 817ce35748
commit e7db528544
6 changed files with 388 additions and 31 deletions

268
ppp.c
View file

@ -719,6 +719,274 @@ static void processreceivedlcpconfreq(sessionidt s, tunnelidt t, uint8_t *p, uin
}
}
// Process sent LCP options from o
// Set changed to 1 if we changed configuration that should be sent to cluster slaves
static int processsentlcpconfreq(sessionidt s, tunnelidt t, uint8_t *p, uint8_t *o, uint16_t x, int *changed)
{
int auth = 0;
int ok = 1;
while (x > 2)
{
int type = o[0];
int length = o[1];
if (length == 0 || length == 1 || type == 0 || x < length) break;
switch (type)
{
case 1: // Maximum-Receive-Unit
{
uint16_t mru = ntohs(*(uint16_t *)(o + 2));
if (mru >= MINMTU)
{
sess_local[s].ppp_mru = mru;
break;
}
ok = 0;
break;
}
case 2: // Async-Control-Character-Map
if (ntohl(*(uint32_t *)(o + 2)))
ok = 0;
break;
case 3: // Authentication-Protocol
{
int proto = ntohs(*(uint16_t *)(o + 2));
if (proto == PPPPAP)
{
if (config->radius_authtypes & AUTHPAP)
{
auth = sess_local[s].lcp_authtype = AUTHPAP;
break;
}
ok = 0;
}
else if (proto == PPPCHAP)
{
if (config->radius_authtypes & AUTHCHAP
&& *(o + 4) == 5) // MD5
{
auth = sess_local[s].lcp_authtype = AUTHCHAP;
break;
}
ok = 0;
}
else
ok = 0;
}
break;
case 4: // Quality-Protocol
break;
case 5: // Magic-Number
if (length < 6) break;
session[s].magic = ntohl(*(uint32_t *)(o + 2));
*changed = 1;
break;
case 7: // Protocol-Field-Compression
session[s].flags |= SESSION_PFC;
*changed = 1;
break;
case 8: // Address-And-Control-Field-Compression
session[s].flags |= SESSION_ACFC;
*changed = 1;
break;
case 17: // Multilink Max-Receive-Reconstructed-Unit
sess_local[s].mp_mrru = ntohs(*(uint16_t *)(o + 2));
break;
case 18: // Multilink Short Sequence Number Header Format
sess_local[s].mp_mssf = 1;
break;
case 19: // Multilink Endpoint Discriminator
ok = 0;
break;
default: // Reject any unknown options
ok = 0;
break;
}
x -= length;
o += length;
}
if (!ok)
// Return after taking note of magic and flags
return 0;
if (sess_local[s].lcp_authtype && !auth)
// We do want authentication, we have to check with client
return 0;
if (sess_local[s].mp_epdis)
// We do want Endpoint Discriminator
return 0;
return 1;
}
// Try to use LCP proxy to avoid renegotiating it (in case the LAC doesn't support passing LCP through)
int processlcpproxy(sessionidt s, tunnelidt t, uint8_t *sent_lcp, uint16_t sent_lcp_n, uint8_t *received_lcp, uint16_t received_lcp_n)
{
uint8_t *response = 0;
int changed = 0;
if (!strcmp(config->lcp_renegotiation, "always"))
/* We always renegotiate */
return 0;
LOG(3, s, t, "Trying to reuse the LAC sent and received LCP configuration:\n");
if (config->debug >= 3)
{
dumplcp(sent_lcp, sent_lcp_n);
dumplcp(received_lcp, received_lcp_n);
}
/* Check what configuration the LAC sent */
if (!processsentlcpconfreq(s, t, NULL, sent_lcp, sent_lcp_n, &changed))
{
LOG(3, s, t, "Sent LCP conf does not match\n");
return 0;
}
/* Check what configuration the LAC received */
processreceivedlcpconfreq(s, t, NULL, received_lcp, received_lcp_n, &changed, &response);
if (response)
{
LOG(3, s, t, "Received LCP conf does not match\n");
/* Mismatch, renegotiate */
return 0;
}
LOG(3, s, t, "LCP conf match\n");
if (changed)
cluster_send_session(s);
// Ok!
return 1;
}
// Try to use auth proxy to avoid renegotiating it (in case the LAC forces PAP)
int processauthproxy(sessionidt s, tunnelidt t,
uint16_t authtype, const char *authname,
size_t authchalln, const char authchall[authchalln],
size_t authrespn, const char authresp[authrespn])
{
uint16_t r;
if ((sess_local[s].lcp_authtype == 0 && authtype != 0)
|| (sess_local[s].lcp_authtype == AUTHPAP && authtype != 3) // PAP
|| (sess_local[s].lcp_authtype == AUTHCHAP && authtype != 2)) // CHAP
{
LOG(3, s, t, "Authentication proxy mismatch: got %u (%s) while expecting %u\n",
authtype, ppp_auth_type(authtype), sess_local[s].lcp_authtype);
return 0;
}
if (session[s].ppp.phase != Authenticate)
{
LOG(3, s, t, "Already authenticated\n");
return 1;
}
if (sess_local[s].lcp_authtype == 0)
{
LOG(4, s, t, "No auth needed\n");
return 1;
}
if ((!config->disable_lac_func) && lac_conf_forwardtoremotelns(s, authname))
{
// Creating a tunnel/session has been started
return 1;
}
if (!(r = radiusnew(s)))
{
LOG(1, s, t, "No RADIUS session available to authenticate session...\n");
sessionshutdown(s, "No free RADIUS sessions.", CDN_UNAVAILABLE, TERM_SERVICE_UNAVAILABLE);
return 1;
}
// Run PRE_AUTH plugins
struct param_pre_auth packet = { &tunnel[t], &session[s], strdup(authname), NULL, 0, 1 };
if (sess_local[s].lcp_authtype == AUTHPAP)
{
packet.password = strndup(authresp, authrespn);
packet.protocol = PPPPAP;
}
else if (sess_local[s].lcp_authtype == AUTHCHAP)
{
if (authchalln != 16)
{
LOG(1, s, t, "CHAP challenge size different from 16: %zu\n", authchalln);
return 0;
}
if (authrespn != 16)
{
LOG(1, s, t, "CHAP response size different from 16: %zu\n", authrespn);
return 0;
}
packet.password = calloc(17, 1);
memcpy(packet.password, authresp, 16);
packet.protocol = PPPCHAP;
}
else
// Not PAP or CHAP??
return 0;
run_plugins(PLUGIN_PRE_AUTH, &packet);
if (!packet.continue_auth)
{
LOG(3, s, t, "A plugin rejected PRE_AUTH\n");
if (packet.username) free(packet.username);
if (packet.password) free(packet.password);
return 1;
}
// Take all authentication information from proxy, and record for possibly proxying ourself
strncpy(session[s].user, packet.username, sizeof(session[s].user) - 1);
strncpy((char *) sess_local[s].auth_name, packet.username, sizeof(sess_local[s].auth_name) - 1);
if (sess_local[s].lcp_authtype == AUTHPAP)
{
strncpy(radius[r].pass, packet.password, sizeof(radius[r].pass) - 1);
strncpy((char *) sess_local[s].auth_resp, packet.password, sizeof(sess_local[s].auth_resp) - 1);
}
else
{
memcpy(radius[r].auth, authchall, 16);
memcpy(sess_local[s].auth_chall, authchall, 16);
memcpy(radius[r].pass, packet.password, 16);
memcpy(sess_local[s].auth_resp, packet.password, 16);
radius[r].chap = 1;
}
radius[r].id = sess_local[s].auth_id;
LOG(3, s, t, "Sending login for %s to RADIUS\n", packet.username);
free(packet.username);
free(packet.password);
if ((session[s].mrru) && (!first_session_in_bundle(s)))
radiussend(r, RADIUSJUSTAUTH);
else
radiussend(r, RADIUSAUTH);
return 1;
}
// Process LCP messages
void processlcp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l)
{