From e7db5285440631da714e3e70eea3b097bec535bd Mon Sep 17 00:00:00 2001 From: Samuel Thibault Date: Sat, 8 Mar 2025 20:09:33 -0500 Subject: [PATCH] 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. --- docs/src/html/manual.md | 12 ++ etc/startup-config.default | 4 + l2tplac.c | 6 +- l2tpns.c | 126 +++++++++++++---- l2tpns.h | 3 + ppp.c | 268 +++++++++++++++++++++++++++++++++++++ 6 files changed, 388 insertions(+), 31 deletions(-) diff --git a/docs/src/html/manual.md b/docs/src/html/manual.md index cff4b6a..6fbccf7 100644 --- a/docs/src/html/manual.md +++ b/docs/src/html/manual.md @@ -159,6 +159,18 @@ should be set by a line like: set configstring \"value\" set ipaddress : PPP counter and timer values, as described in ยง4.1 of [RFC1661](ftp://ftp.rfc-editor.org/in-notes/rfc1661.txt). +`lcp_renegotiation` (string) + +: By default (`always`), we renegotiate LCP even if the LAC already did with + the client. + + We can avoid the LCP renegotiation (proxy LCP negotiation), unless what was + already negotiated is not fine for us (`on-mismatch`). To be noted: we will + accept not using our preferred RADIUS authentication method. + This is notably useful when the LAC cannot pass LCP configuration through, + and thus we have to try to accept what was already negotiated by the LAC + with the client. + `primary_dns` (ip address); `econdary_dns` (ip address) : Whenever a PPP connection is established, DNS servers will be sent diff --git a/etc/startup-config.default b/etc/startup-config.default index 75c2cec..0075c7a 100644 --- a/etc/startup-config.default +++ b/etc/startup-config.default @@ -24,6 +24,10 @@ set l2tp_secret "secret" #set ppp_max_configure 10 #set ppp_max_failure 5 +# Can be set to "on-mismatch" to enable proxy LCP negotiation +# (e.g. if LAC cannot pass LCP through) +#set lcp_renegotiation "always" + # Only 2 DNS server entries are allowed set primary_dns 10.0.0.1 set secondary_dns 10.0.0.2 diff --git a/l2tplac.c b/l2tplac.c index 2af1570..8347d55 100644 --- a/l2tplac.c +++ b/l2tplac.c @@ -60,11 +60,11 @@ * LNS1 --------------------> LNS2 (Session Open) * ZLB * LNS1 <-------------------- LNS2 (Session Open) - * LCP Negotiation + * LCP Renegotiation (unless proxied) * Client <------------------------------------------------------------------------> LNS2 - * PAP/CHAP Authentification + * PAP (Ack only if proxied) / CHAP Authentification * Client <------------------------------------------------------------------------> LNS2 - * DATA (ppp) + * DATA (ppp) * Client <------------------------------------------------------------------------> LNS2 * */ diff --git a/l2tpns.c b/l2tpns.c index febcab0..38d2326 100644 --- a/l2tpns.c +++ b/l2tpns.c @@ -159,6 +159,7 @@ config_descriptt config_values[] = { CONFIG("ppp_max_configure", ppp_max_configure, INT), CONFIG("ppp_max_failure", ppp_max_failure, INT), CONFIG("ppp_keepalive", ppp_keepalive, BOOL), + CONFIG("lcp_renegotiation", lcp_renegotiation, STRING), CONFIG("primary_dns", default_dns1, IPv4), CONFIG("secondary_dns", default_dns2, IPv4), CONFIG("primary_radius", radiusserver[0], IPv4), @@ -4163,9 +4164,18 @@ void processudp(uint8_t *buf, int len, struct sockaddr_in *addr, uint16_t indexu uint16_t message = 0xFFFF; // message type uint8_t fatal = 0; uint8_t mandatory = 0; + uint8_t *last_sent_lcp_confreq = 0; + uint16_t last_sent_lcp_confreq_n = 0; + uint8_t *last_received_lcp_confreq = 0; + uint16_t last_received_lcp_confreq_n = 0; + uint16_t atype = 0; + uint16_t authid = 0; + char authname[MAXUSER] = ""; + char authchall[MAXPASS] = ""; + size_t authchalln = 0; + char authresp[MAXPASS] = ""; + size_t authrespn = 0; uint16_t asession = 0; // assigned session - uint32_t amagic = 0; // magic number - uint8_t aflags = 0; // flags from last LCF uint16_t version = 0x0100; // protocol version (we handle 0.0 as well and send that back just in case) char called[MAXTEL] = ""; // called number char calling[MAXTEL] = ""; // calling number @@ -4642,50 +4652,70 @@ void processudp(uint8_t *buf, int len, struct sockaddr_in *addr, uint16_t indexu } case 29: // Proxy Authentication Type { - uint16_t atype = ntohs(*(uint16_t *)b); + atype = ntohs(*(uint16_t *)b); LOG(4, s, t, " Proxy Auth Type %u (%s)\n", atype, ppp_auth_type(atype)); break; } case 30: // Proxy Authentication Name { - char authname[64]; memset(authname, 0, sizeof(authname)); memcpy(authname, b, (n < sizeof(authname)) ? n : sizeof(authname) - 1); - LOG(4, s, t, " Proxy Auth Name (%s)\n", - authname); + if (n < sizeof(authname)) + { + LOG(4, s, t, " Proxy Auth Name (%s)\n", authname); + } + else + { + LOG(2, s, t, " Proxy Auth Name too long (%s)\n", authname); + } break; } case 31: // Proxy Authentication Challenge { - LOG(4, s, t, " Proxy Auth Challenge\n"); + if (n <= sizeof(authchall)) + { + memcpy(authchall, b, n); + authchalln = n; + LOG(4, s, t, " Proxy Auth Challenge\n"); + } + else + { + LOG(2, s, t, " Proxy Auth Challenge too long\n"); + } break; } case 32: // Proxy Authentication ID { - uint16_t authid = ntohs(*(uint16_t *)(b)); + authid = ntohs(*(uint16_t *)(b)); LOG(4, s, t, " Proxy Auth ID (%u)\n", authid); break; } case 33: // Proxy Authentication Response - LOG(4, s, t, " Proxy Auth Response\n"); - break; - case 27: // last sent lcp - { // find magic number - uint8_t *p = b, *e = p + n; - while (p + 1 < e && p[1] && p + p[1] <= e) + { + if (n <= sizeof(authresp)) { - if (*p == 5 && p[1] == 6) // Magic-Number - amagic = ntohl(*(uint32_t *) (p + 2)); - else if (*p == 7) // Protocol-Field-Compression - aflags |= SESSION_PFC; - else if (*p == 8) // Address-and-Control-Field-Compression - aflags |= SESSION_ACFC; - p += p[1]; + memcpy(authresp, b, n); + authrespn = n; + LOG(4, s, t, " Proxy Auth Response\n"); } + else + { + LOG(2, s, t, " Proxy Auth Response too long\n"); + } + break; + } + case 27: // last sent lcp + { + last_sent_lcp_confreq = b; + last_sent_lcp_confreq_n = n; + break; } - break; case 28: // last recv lcp confreq - break; + { + last_received_lcp_confreq = b; + last_received_lcp_confreq_n = n; + break; + } case 26: // Initial Received LCP CONFREQ break; case 39: // seq required - we control it as an LNS anyway... @@ -4996,9 +5026,8 @@ void processudp(uint8_t *buf, int len, struct sockaddr_in *addr, uint16_t indexu break; case 12: // ICCN LOG(3, s, t, "Received ICCN\n"); - if (amagic == 0) amagic = time_now; - session[s].magic = amagic; // set magic number - session[s].flags = aflags; // set flags received + + session[s].magic = time_now; // set magic number session[s].mru = PPPoE_MRU; // default controlnull(t); // ack @@ -5013,8 +5042,39 @@ void processudp(uint8_t *buf, int len, struct sockaddr_in *addr, uint16_t indexu else sess_local[s].mp_epdis = 0; - sendlcp(s, t); - change_state(s, lcp, RequestSent); + if (last_sent_lcp_confreq_n && last_received_lcp_confreq_n && + processlcpproxy(s, t, last_sent_lcp_confreq, last_sent_lcp_confreq_n, + last_received_lcp_confreq, last_received_lcp_confreq_n) + && ( (sess_local[s].lcp_authtype == 0) + || (sess_local[s].lcp_authtype == AUTHPAP && authrespn) + || (sess_local[s].lcp_authtype == AUTHCHAP && authchalln == 16 && authrespn == 16))) + { + // Could reuse the LCP negotiation and don't have empty proxy auth response or unknown challenge size, don't renegotiate + LOG(3, s, t, "Reusing LCP negotiation\n"); + // Start with proxy auth id to avoid client caching challenge responses + sess_local[s].auth_id = authid; + + if (!sess_local[s].lcp_authtype) + { + // No auth, open immediately + lcp_open(s, t); + } + else + { + // Process auth + session[s].ppp.phase = Authenticate; + change_state(s, lcp, Opened); + processauthproxy(s, t, atype, authname, authchalln, authchall, authrespn, authresp); + } + } + else + { + // Have to renegotiate LCP + LOG(3, s, t, "Renegotiating LCP\n"); + sendlcp(s, t); + change_state(s, lcp, RequestSent); + } + break; case 14: // CDN @@ -7425,6 +7485,16 @@ static void update_config() MSS = MRU - TCP_HDRS; MSS6 = MRU - TCP6_HDRS; + if (!config->lcp_renegotiation[0]) + strcpy(config->lcp_renegotiation, "always"); + + if (strcmp(config->lcp_renegotiation, "always") && + strcmp(config->lcp_renegotiation, "on-mismatch")) + { + LOG(0, 0, 0, "Invalid LCP renegotiation type %s, assuming 'always'\n", config->lcp_renegotiation); + strcpy(config->lcp_renegotiation, "always"); + } + // Update radius config->numradiusservers = 0; for (i = 0; i < MAXRADSERVER; i++) diff --git a/l2tpns.h b/l2tpns.h index efaf836..cb2d843 100644 --- a/l2tpns.h +++ b/l2tpns.h @@ -756,6 +756,7 @@ typedef struct int ppp_max_configure; // max lcp configure requests to send int ppp_max_failure; // max lcp configure naks to send int ppp_keepalive; // send echoes regardless + char lcp_renegotiation[12]; // LCP renegotiation (always or on-mismatch) char radiussecret[64]; char radius_require_message_authenticator[5]; @@ -992,6 +993,8 @@ void processchap(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); void lcp_open(sessionidt s, tunnelidt t); void lcp_restart(sessionidt s); void processlcp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +int processlcpproxy(sessionidt s, tunnelidt t, uint8_t *sent_lcp, uint16_t sent_lcp_n, uint8_t *received_lcp, uint16_t received_lcp_n); +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]); void processipcp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); void processipv6cp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); void processipin(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); diff --git a/ppp.c b/ppp.c index b5bc5dd..94bed04 100644 --- a/ppp.c +++ b/ppp.c @@ -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) {