1
0
mirror of https://github.com/SoftEtherVPN/SoftEtherVPN.git synced 2024-09-19 10:10:40 +03:00
SoftEtherVPN/src/Cedar/Proto_PPP.c

4537 lines
116 KiB
C

// SoftEther VPN Source Code - Developer Edition Master Branch
// Cedar Communication Module
// Proto_PPP.c
// PPP protocol stack
#include "Proto_PPP.h"
#include "Account.h"
#include "Cedar.h"
#include "Connection.h"
#include "Hub.h"
#include "IPC.h"
#include "Logging.h"
#include "Radius.h"
#include "Server.h"
#include "Mayaqua/Memory.h"
#include "Mayaqua/Object.h"
#include "Mayaqua/Str.h"
#include "Mayaqua/Tick64.h"
// PPP main thread
void PPPThread(THREAD *thread, void *param)
{
PPP_SESSION *p = (PPP_SESSION *)param;
UINT i;
USHORT next_protocol = 0;
bool ret = false;
char ipstr1[128], ipstr2[128];
bool authReqSent = false;
// Validate arguments
if (thread == NULL || param == NULL)
{
return;
}
// Initialize
Debug("PPP Initialize");
PPPSetStatus(p, PPP_STATUS_CONNECTED);
p->Eap_Protocol = PPP_UNSPECIFIED;
p->Mru1 = p->Mru2 = PPP_MRU_DEFAULT;
p->RecvPacketList = NewList(NULL);
p->SentReqPacketList = NewList(NULL);
p->DelayedPackets = NewList(PPPDelayedPacketsComparator);
p->UseEapRadius = CedarIsThereAnyEapEnabledRadiusConfig(p->Cedar);
Debug("UseEapRadius = 0x%x\n", p->UseEapRadius);
//// Link establishment phase
Debug("PPP Link establishment phase\n");
IPToStr(ipstr1, sizeof(ipstr1), &p->ClientIP);
IPToStr(ipstr2, sizeof(ipstr2), &p->ServerIP);
PPPLog(p, "LP_CONNECTED", p->Postfix, ipstr1, p->ClientHostname, p->ClientPort, ipstr2, p->ServerPort,
p->ClientSoftwareName, p->AdjustMss);
// We need that so we don't time out on connection immediately
p->LastRecvTime = Tick64();
Debug("PPP starting main dataloop\n");
// Dataloop active if the receiving tube is still connected
while (true)
{
PPP_LCP *lcp;
bool receivedPacketProcessed = false;
TUBE *tubes[2];
UINT r;
UINT64 now = Tick64();
PPPGetNextPacket(p);
if (p->CurrentPacket != NULL)
{
// First we process any possible unsupported packets
receivedPacketProcessed = PPPRejectUnsupportedPacket(p, p->CurrentPacket);
// Now do some basic processing
if (receivedPacketProcessed == false && p->CurrentPacket->IsControl && p->CurrentPacket->Protocol == PPP_PROTOCOL_LCP)
{
if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_ECHO_REQUEST && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
{
// Immediately return the echo response to the echo request
PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));
pp2->IsControl = true;
pp2->Protocol = PPP_PROTOCOL_LCP;
pp2->Lcp = NewPPPLCP(PPP_LCP_CODE_ECHO_RESPONSE, p->CurrentPacket->Lcp->Id);
pp2->Lcp->Data = Clone(p->CurrentPacket->Lcp->Data, p->CurrentPacket->Lcp->DataSize);
pp2->Lcp->DataSize = p->CurrentPacket->Lcp->DataSize;
if (PPPSendPacketAndFree(p, pp2) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
}
receivedPacketProcessed = true;
}
else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_ECHO_RESPONSE && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
{
receivedPacketProcessed = true;
// Ignore the Echo response packet
}
else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_DROP && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
{
receivedPacketProcessed = true;
// Ignore the Drop packet
}
else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_IDENTIFICATION && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
{
receivedPacketProcessed = true;
// Ignore the Identification packet
WHERE;
}
else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_TERMINATE_REQ)
{
PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));;
receivedPacketProcessed = true;
// Return the Terminate ACK If a Terminate Request has been received
pp2->IsControl = true;
pp2->Protocol = PPP_PROTOCOL_LCP;
pp2->Lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_ACK, p->CurrentPacket->Lcp->Id);
pp2->Lcp->Data = Clone(p->CurrentPacket->Lcp->Data, p->CurrentPacket->Lcp->DataSize);
pp2->Lcp->DataSize = p->CurrentPacket->Lcp->DataSize;
p->IsTerminateReceived = true;
if (PPPSendPacketAndFree(p, pp2) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
}
else
{
SleepThread(100);
PPPSetStatus(p, PPP_STATUS_CLOSED);
}
}
else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_TERMINATE_ACK)
{
PPPSetStatus(p, PPP_STATUS_CLOSED);
}
}
// Process responses
if (receivedPacketProcessed == false && p->CurrentPacket != NULL && p->CurrentPacket->IsControl && PPP_CODE_IS_RESPONSE(p->CurrentPacket->Protocol, p->CurrentPacket->Lcp->Code) && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
{
PPP_PACKET *request = NULL;
// Removing from resend list
for (i = 0; i < LIST_NUM(p->SentReqPacketList); i++)
{
PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i);
if (t->Id == p->CurrentPacket->Lcp->Id)
{
request = t->Packet;
Delete(p->SentReqPacketList, t);
Free(t);
break;
}
}
PPPProcessResponsePacket(p, p->CurrentPacket, request);
FreePPPPacket(request);
receivedPacketProcessed = true;
}
// Process requests
if (receivedPacketProcessed == false && p->CurrentPacket != NULL && p->CurrentPacket->IsControl && PPP_CODE_IS_REQUEST(p->CurrentPacket->Protocol, p->CurrentPacket->Lcp->Code) && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
{
PPPProcessRequestPacket(p, p->CurrentPacket);
receivedPacketProcessed = true;
}
// Process data packets, discarded before we got any links up
if (receivedPacketProcessed == false && p->CurrentPacket != NULL && p->CurrentPacket->IsControl == false && p->PPPStatus == PPP_STATUS_NETWORK_LAYER && p->Ipc != NULL)
{
UINT64 timeBeforeLoop = Tick64();
while (true)
{
UINT64 nowL;
// Here client to server
if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IP &&
IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_OPENED)
{
receivedPacketProcessed = true;
IPCSendIPv4(p->Ipc, p->CurrentPacket->Data, p->CurrentPacket->DataSize);
}
else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IP)
{
Debug("Got IPv4 packet before IPv4 ready!\n");
}
else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IPV6 &&
IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_OPENED)
{
receivedPacketProcessed = true;
IPCIPv6Send(p->Ipc, p->CurrentPacket->Data, p->CurrentPacket->DataSize);
}
else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IPV6)
{
Debug("Got IPv6 packet before IPv6 ready!\n");
}
// Let's break out of the loop once in a while so we don't get stuck here endlessly
nowL = Tick64();
if (nowL > timeBeforeLoop + PPP_PACKET_RESEND_INTERVAL)
{
break;
}
PPPGetNextPacket(p);
if (p->CurrentPacket == NULL)
{
break;
}
// Making sure we got a correctly parsed packet by rejecting all invalid ones
if (PPPRejectUnsupportedPacket(p, p->CurrentPacket))
{
break;
}
if (p->CurrentPacket->IsControl || p->PPPStatus != PPP_STATUS_NETWORK_LAYER || p->Ipc == NULL)
{
PPPAddNextPacket(p, p->CurrentPacket, 0);
p->CurrentPacket = NULL;
break;
}
}
}
if (receivedPacketProcessed == false && p->CurrentPacket != NULL)
{
Debug("Unprocessed and unrejected packet, protocol = 0x%x\n", p->CurrentPacket->Protocol);
}
}
else if (p->PPPStatus == PPP_STATUS_BEFORE_AUTH && p->AuthProtocol == PPP_PROTOCOL_EAP)
{
PPP_LCP *lcpEap;
PPP_EAP *eapPacket;
UCHAR *welcomeMessage = "Welcome to the SoftEther VPN server!";
UCHAR flags = PPP_EAP_TLS_FLAG_NONE;
// We got to start EAP when we got no LCP packets from the client on previous iteration
// which means we parsed all the client requests and responses
switch (p->Eap_Protocol)
{
case PPP_EAP_TYPE_TLS:
// Sending TLS Start...
flags |= PPP_EAP_TLS_FLAG_SSLSTARTED;
p->Eap_PacketId = p->NextId++;
lcpEap = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags);
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcpEap) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
break;
}
break;
case PPP_EAP_TYPE_MSCHAPV2:
// Sending challenge
p->Eap_PacketId = p->NextId; // Do not increase NextId so that MSCHAPv2 could use the same id
lcp = BuildMSCHAP2ChallengePacket(p);
BUF *b = BuildLCPData(lcp);
FreePPPLCP(lcp);
lcpEap = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId, PPP_EAP_TYPE_MSCHAPV2, b->Size);
eapPacket = lcpEap->Data;
Copy(eapPacket->Data, b->Buf, b->Size);
FreeBuf(b);
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcpEap) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
break;
}
break;
case PPP_EAP_TYPE_IDENTITY:
default: // We treat the unspecified protocol as the IDENTITY protocol
p->Eap_Protocol = PPP_EAP_TYPE_IDENTITY;
p->Eap_PacketId = p->NextId++;
lcpEap = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId, PPP_EAP_TYPE_IDENTITY, StrLen(welcomeMessage) + 1);
eapPacket = lcpEap->Data;
Copy(eapPacket->Data, welcomeMessage, StrLen(welcomeMessage));
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
PPPFreeEapClient(p);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcpEap) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
break;
}
break;
}
}
else if (p->PPPStatus == PPP_STATUS_BEFORE_AUTH && p->AuthProtocol == PPP_PROTOCOL_CHAP)
{
// We got to start CHAP when we got no LCP packets from the client on previous iteration
// which means we parsed all the client requests and responses
Debug("Starting PPP Authentication phase MS-CHAP v2\n");
lcp = BuildMSCHAP2ChallengePacket(p);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_CHAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
}
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
}
if (p->PPPStatus == PPP_STATUS_CONNECTED && authReqSent == false)
{
// EAP code
PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
USHORT eap_code = Endian16(PPP_LCP_AUTH_EAP);
Debug("Request EAP\n");
Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &eap_code, sizeof(eap_code)));
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
}
authReqSent = true;
}
if (p->PPPStatus == PPP_STATUS_AUTHENTICATING)
{
//Debug("Tick waiting for auth...\n");
}
if (p->PPPStatus == PPP_STATUS_AUTH_FAIL)
{
Debug("PPP auth failed, giving up\n");
p->DisconnectCauseCode = 15;
p->DisconnectCauseDirection = 1;
PPPSetStatus(p, PPP_STATUS_CLOSING);
}
if (p->PPPStatus == PPP_STATUS_NETWORK_LAYER)
{
UINT64 timeBeforeLoop;
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_OPENED)
{
if (p->DhcpAllocated)
{
if (now >= p->DhcpNextRenewTime)
{
IP ip;
// DHCP renewal procedure
p->DhcpNextRenewTime = now + p->DhcpRenewInterval;
UINTToIP(&ip, p->ClientAddressOption.ServerAddress);
IPCDhcpRenewIP(p->Ipc, &ip);
}
}
}
IPCProcessL3Events(p->Ipc);
timeBeforeLoop = Tick64();
while (true)
{
UINT64 nowL;
bool no4packets = false;
bool no6packets = false;
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_OPENED)
{
BLOCK *b = IPCRecvIPv4(p->Ipc);
if (b == NULL)
{
no4packets = true;
}
else
{
PPP_PACKET *pp;
PPP_PACKET tmp;
// Since receiving the IP packet, send it to the client by PPP
pp = &tmp;
pp->IsControl = false;
pp->Protocol = PPP_PROTOCOL_IP;
pp->Lcp = NULL;
pp->Data = b->Buf;
pp->DataSize = b->Size;
PPPSendPacketEx(p, pp, true);
FreePPPPacketEx(pp, true);
Free(b); // Not FreeBlock because freed in FreePPPPacketEx
}
}
else
{
no4packets = true;
}
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_OPENED)
{
BLOCK *b = IPCIPv6Recv(p->Ipc);
if (b == NULL)
{
no6packets = true;
}
else
{
PPP_PACKET *pp;
PPP_PACKET tmp;
// Since receiving the IP packet, send it to the client by PPP
pp = &tmp;
pp->IsControl = false;
pp->Protocol = PPP_PROTOCOL_IPV6;
pp->Lcp = NULL;
pp->Data = b->Buf;
pp->DataSize = b->Size;
PPPSendPacketEx(p, pp, true);
FreePPPPacketEx(pp, true);
Free(b); // Not FreeBlock because freed in FreePPPPacketEx
}
}
else
{
no6packets = true;
}
// Let's break out of the loop once in a while so we don't get stuck here endlessly
nowL = Tick64();
if (nowL > timeBeforeLoop + PPP_PACKET_RESEND_INTERVAL || (no4packets && no6packets))
{
break;
}
}
FlushTubeFlushList(p->FlushList);
}
if (p->PPPStatus == PPP_STATUS_AUTH_SUCCESS)
{
Debug("PPP auth success, ready for network layer on next tick\n");
p->AuthOk = true;
PPPSetStatus(p, PPP_STATUS_NETWORK_LAYER);
}
if ((p->PPPStatus == PPP_STATUS_CLOSING || p->PPPStatus == PPP_STATUS_FAIL) && IsTubeConnected(p->TubeRecv) && IsTubeConnected(p->TubeSend))
{
Debug("Trying to cleanly close the connection, status = 0x%x\n", p->PPPStatus);
PPPSetStatus(p, PPP_STATUS_CLOSING_WAIT);
lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_REQ, 0);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
}
}
if (PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false || p->PPPStatus == PPP_STATUS_CLOSING_WAIT)
{
PPPProcessRetransmissions(p);
PPPSendEchoRequest(p);
}
tubes[0] = p->TubeRecv;
if (p->PPPStatus == PPP_STATUS_NETWORK_LAYER && p->Ipc != NULL && IsIPCConnected(p->Ipc))
{
r = GetNextIntervalForInterrupt(p->Ipc->Interrupt);
tubes[1] = p->Ipc->Sock->RecvTube;
WaitForTubes(tubes, 2, MIN(r, PPP_PACKET_RESEND_INTERVAL));
}
else
{
WaitForTubes(tubes, 1, 300); // Increasing timeout to make the ticks a bit slower
}
if (IsTubeConnected(p->TubeRecv) == false || IsTubeConnected(p->TubeSend) == false)
{
// Higher-level protocol is disconnected
PPPLog(p, "LP_UPPER_PROTOCOL_DISCONNECTED", p->Postfix);
break;
}
if (IsIPCConnected(p->Ipc) == false && p->PPPStatus == PPP_STATUS_NETWORK_LAYER)
{
// IPC VPN session is disconnected
PPPLog(p, "LP_VPN_SESSION_TERMINATED");
break;
}
// Time-out inspection
if ((p->LastRecvTime + (UINT64)p->DataTimeout) <= now)
{
// Communication time-out occurs
PPPLog(p, "LP_DATA_TIMEOUT");
break;
}
// Maximum PPP session time of the user reached inspection
if (p->UserConnectionTick != 0 && p->UserConnectionTimeout != 0 &&
p->UserConnectionTick + p->UserConnectionTimeout <= now)
{
// User connection time-out occurs
PPPLog(p, "LP_USER_TIMEOUT");
break;
}
// Terminate if the PPP disconnected
if (p->IsTerminateReceived)
{
PPPLog(p, "LP_NORMAL_TERMINATE");
break;
}
if (p->PPPStatus == PPP_STATUS_FAIL || p->PPPStatus == PPP_STATUS_CLOSED)
{
Debug("Exiting main dataloop, status = 0x%x\n", p->PPPStatus);
break;
}
}
Debug("Exited main dataloop, status = 0x%x\n", p->PPPStatus);
if (p->PPPStatus != PPP_STATUS_FAIL)
{
IP ip;
char tmp[MAX_SIZE];
// Disconnected normally
PPPLog(p, "LP_DISCONNECTED");
if (p != NULL && p->DhcpAllocated && IsIPCConnected(p->Ipc) && p->ClientAddressOption.ServerAddress != 0)
{
// If any address is assigned from the DHCP, release it
UINTToIP(&ip, p->ClientAddressOption.ServerAddress);
IPToStr(tmp, sizeof(tmp), &ip);
Debug("Releasing IP Address from DHCP Server %s...\n", tmp);
IPCDhcpFreeIP(p->Ipc, &ip);
IPCProcessL3Events(p->Ipc);
SleepThread(300);
}
}
else
{
PPPLog(p, "LP_DISCONNECTED_ABNORMAL");
}
FreePPPSession(p);
Debug("PPP Session ended correctly\n");
}
// Entry point
// Create a new PPP session
THREAD *NewPPPSession(CEDAR *cedar, IP *client_ip, UINT client_port, IP *server_ip, UINT server_port, TUBE *send_tube, TUBE *recv_tube, char *postfix, char *client_software_name, char *client_hostname, char *crypt_name, UINT adjust_mss)
{
PPP_SESSION *p;
THREAD *t;
// Validate arguments
if (cedar == NULL || client_ip == NULL || server_ip == NULL || send_tube == NULL || recv_tube == NULL)
{
return NULL;
}
if (IsEmptyStr(postfix))
{
postfix = "PPP";
}
if (IsEmptyStr(crypt_name))
{
crypt_name = "";
}
if (IsEmptyStr(client_software_name))
{
client_software_name = "PPP VPN Client";
}
// Data structure initialization
p = ZeroMalloc(sizeof(PPP_SESSION));
p->EnableMSCHAPv2 = true;
p->AuthProtocol = PPP_UNSPECIFIED;
p->MsChapV2_ErrorCode = 691;
p->EapClient = NULL;
Zero(&p->Eap_Identity, sizeof(p->Eap_Identity));
p->Eap_TlsCtx.DisableTls13 = false;
p->Eap_TlsCtx.Tls13SessionTicketsCount = 2; // Default count as per hardcoded in OpenSSL
p->DataTimeout = PPP_DATA_TIMEOUT;
p->PacketRecvTimeout = PPP_PACKET_RECV_TIMEOUT;
p->UserConnectionTimeout = 0;
p->Cedar = cedar;
AddRef(cedar->ref);
p->AdjustMss = adjust_mss;
StrCpy(p->CryptName, sizeof(p->CryptName), crypt_name);
Copy(&p->ClientIP, client_ip, sizeof(IP));
p->ClientPort = client_port;
Copy(&p->ServerIP, server_ip, sizeof(IP));
p->ServerPort = server_port;
p->TubeRecv = recv_tube;
p->TubeSend = send_tube;
AddRef(p->TubeRecv->Ref);
AddRef(p->TubeSend->Ref);
StrCpy(p->Postfix, sizeof(p->Postfix), postfix);
StrCpy(p->ClientSoftwareName, sizeof(p->ClientSoftwareName), client_software_name);
if (IsEmptyStr(client_hostname))
{
IPToStr(p->ClientHostname, sizeof(p->ClientHostname), client_ip);
}
else
{
StrCpy(p->ClientHostname, sizeof(p->ClientHostname), client_hostname);
}
p->FlushList = NewTubeFlushList();
// Thread creation
t = NewThread(PPPThread, p);
return t;
}
// PPP processing functions
// Finds out if a packet is supported, if not - sends a notification to the peer
// result: false - supported, true - unsupported
bool PPPRejectUnsupportedPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
return PPPRejectUnsupportedPacketEx(p, pp, false);
}
bool PPPRejectUnsupportedPacketEx(PPP_SESSION *p, PPP_PACKET *pp, bool force)
{
bool result = false;
if (p == NULL || pp == NULL)
{
return false;
}
if (PPP_IS_SUPPORTED_PROTOCOL(pp->Protocol) == false || force == true)
{
// Unsupported algorithm
PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));
BUF *buf;
UCHAR c;
USHORT us;
Debug("Rejecting PPP protocol = 0x%x\n", pp->Protocol);
result = true;
pp2->Protocol = PPP_PROTOCOL_LCP;
pp2->IsControl = false;
buf = NewBuf();
// Code
c = PPP_LCP_CODE_PROTOCOL_REJECT;
WriteBuf(buf, &c, 1);
// ID
c = p->NextId++;
WriteBuf(buf, &c, 1);
// Length
us = Endian16(pp->DataSize + 6);
WriteBuf(buf, &us, 2);
// Rejected Protocol
us = Endian16(pp->Protocol);
WriteBuf(buf, &us, 2);
// Packet Data
WriteBuf(buf, pp->Data, pp->DataSize);
pp2->Data = Clone(buf->Buf, buf->Size);
pp2->DataSize = buf->Size;
FreeBuf(buf);
if (PPPSendPacketAndFree(p, pp2) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
}
}
return result;
}
// Do the retransmissions if needed
bool PPPProcessRetransmissions(PPP_SESSION *p)
{
INT64 i = 0;
UINT64 now = Tick64();
UINT64 count;
if (p->SentReqPacketList == NULL)
{
Debug("Somehow SentReqPacketList is NULL!\n");
return false;
}
// Making it signed but expanding to 64 bits
count = LIST_NUM(p->SentReqPacketList);
if (count == 0)
{
return true;
}
for (i = count - 1; i >= 0; --i)
{
PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i);
if (t->TimeoutTime <= now)
{
Debug("Timing out on resending control packet protocol = 0x%x\n", t->Packet->Protocol);
Delete(p->SentReqPacketList, t);
FreePPPPacket(t->Packet);
Free(t);
}
else if (t->ResendTime <= now)
{
Debug("Resending control packet protocol = 0x%x\n", t->Packet->Protocol);
if (PPPSendPacketEx(p, t->Packet, false) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
t->ResendTime = now + PPP_PACKET_RESEND_INTERVAL;
}
}
return true;
}
// Send the PPP Echo Request
bool PPPSendEchoRequest(PPP_SESSION *p)
{
// Validate arguments
if (p == NULL)
{
return false;
}
UINT64 now = Tick64();
if (p->NextEchoSendTime == 0 || now >= p->NextEchoSendTime)
{
PPP_PACKET *pp;
char echo_data[] = "\0\0\0\0Aho Baka Manuke";
p->NextEchoSendTime = now + (UINT64)PPP_ECHO_SEND_INTERVAL;
if (IsIPCConnected(p->Ipc))
{
AddInterrupt(p->Ipc->Interrupt, p->NextEchoSendTime);
}
pp = ZeroMalloc(sizeof(PPP_PACKET));
pp->Protocol = PPP_PROTOCOL_LCP;
pp->IsControl = true;
pp->Lcp = NewPPPLCP(PPP_LCP_CODE_ECHO_REQUEST, 0);
pp->Lcp->Data = Clone(echo_data, sizeof(echo_data));
pp->Lcp->DataSize = sizeof(echo_data);
if (PPPSendPacketAndFree(p, pp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return true;
}
return false;
}
// Processes response packets
bool PPPProcessResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
if (req == NULL)
{
Debug("We received a response for... What? We never sent this request, protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code);
// Let's just discard this, as this was probably already parsed, and we just stumbled upon a resend
return false;
}
switch (pp->Protocol)
{
case PPP_PROTOCOL_LCP:
return PPPProcessLCPResponsePacket(p, pp, req);
break;
case PPP_PROTOCOL_PAP:
Debug("Got a response PAP, which is invalid, we should get a request instead\n");
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
break;
case PPP_PROTOCOL_CHAP:
return PPPProcessCHAPResponsePacket(p, pp, req);
break;
case PPP_PROTOCOL_IPCP:
return PPPProcessIPCPResponsePacket(p, pp, req);
break;
case PPP_PROTOCOL_IPV6CP:
return PPPProcessIPv6CPResponsePacket(p, pp, req);
break;
case PPP_PROTOCOL_EAP:
return PPPProcessEAPResponsePacket(p, pp, req);
break;
default:
Debug("We received a response for an unsupported protocol??? Should be filtered out already! protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code);
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return false;
}
bool PPPProcessLCPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
UINT i;
bool isAccepted = PPP_LCP_CODE_IS_NEGATIVE(pp->Lcp->Code) == false;
bool result = true;
// MSCHAPv2 code
UCHAR ms_chap_v2_code[3];
WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;
// We got one of rejects here, not NACKs
if (isAccepted == false && pp->Lcp->Code == PPP_LCP_CODE_PROTOCOL_REJECT)
{
// If we receive a protocol reject before we finished authenticating
// probably means the PPP client is not compatible anyway so we fail the connection
if (p->PPPStatus != PPP_STATUS_NETWORK_LAYER)
{
USHORT *protocol = pp->Lcp->Data;
Debug("Protocol 0x%x rejected before auth, probably unsupported client, failing connection\n", *protocol);
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
else
{
USHORT *protocol = pp->Lcp->Data;
if (*protocol == PPP_PROTOCOL_IPCP || *protocol == PPP_PROTOCOL_IP)
{
IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_REJECTED);
}
if (*protocol == PPP_PROTOCOL_IPV6CP || *protocol == PPP_PROTOCOL_IPV6)
{
IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_REJECTED);
}
}
}
if (isAccepted == false && pp->Lcp->Code == PPP_LCP_CODE_CODE_REJECT)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
PPP_OPTION *opt = NULL;
switch (t->Type)
{
case PPP_LCP_OPTION_MRU:
// MRU
if (t->DataSize == sizeof(USHORT))
{
USHORT value = READ_USHORT(t->Data);
if (isAccepted == false)
{
if (pp->Lcp->Code != PPP_LCP_CODE_NAK)
{
Debug("MRU setup failed, rejected");
p->Mru1 = p->Mru2 = PPP_MRU_DEFAULT;
}
if (value < PPP_MRU_MIN || value > PPP_MRU_MAX)
{
Debug("Couldn't agree on an MRU! Breaking link... MRU = 0x%x\n", value);
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
else
{
PPP_LCP *lcp = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
Add(lcp->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &value, sizeof(USHORT)));
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
Debug("PPP: Server got %u as MRU from NACK, re-requesting\n", p->Mru2);
}
}
else if (value < PPP_MRU_MIN || value > PPP_MRU_MAX)
{
Debug("The client somehow ACKed an invalid MRU, breaking link... MRU = 0x%x\n", value);
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
result = false;
}
else
{
p->Mru2 = value;
Debug("PPP: Server set %u as MRU\n", p->Mru2);
}
}
break;
case PPP_LCP_OPTION_AUTH:
opt = PPPGetOptionValue(req->Lcp, PPP_LCP_OPTION_AUTH);
if (opt == NULL)
{
Debug("We got some weird response with option absent in request, wut? Disconnecting\n");
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
if (opt->DataSize == sizeof(USHORT) && *((USHORT *)(opt->Data)) == Endian16(PPP_LCP_AUTH_EAP))
{
// Try to request MS-CHAPv2 then
if (isAccepted == false)
{
UINT64 offer = 0;
PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
UCHAR ms_chap_v2_code[3];
WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;
Copy(&offer, ms_chap_v2_code, sizeof(ms_chap_v2_code));
Debug("NACK proto with code = 0x%x, cypher = 0x%x, offered cypher = 0x%x\n", pp->Lcp->Code, *((USHORT *)(opt->Data)), offer);
Debug("Request MSCHAPv2\n");
Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &ms_chap_v2_code, sizeof(ms_chap_v2_code)));
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
else
{
p->AuthProtocol = PPP_PROTOCOL_EAP;
Debug("Setting BEFORE_AUTH from ACK on LCP response parse on EAP accept\n");
PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
}
}
else if (opt->DataSize == sizeof(ms_chap_v2_code) && Cmp(opt->Data, ms_chap_v2_code, opt->DataSize) == 0)
{
// Try to request PAP then
if (isAccepted == false || p->EnableMSCHAPv2 == false)
{
UINT64 offer = 0;
PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
USHORT proto = Endian16(PPP_LCP_AUTH_PAP);
Copy(&offer, t->Data, t->DataSize > sizeof(UINT64) ? sizeof(UINT64) : t->DataSize);
Debug("NACK proto with code = 0x%x, cypher = 0x%x, offered cypher = 0x%x\n", pp->Lcp->Code, *((USHORT *)(opt->Data)), offer);
Debug("Request PAP\n");
Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &proto, sizeof(USHORT)));
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
else if (p->AuthProtocol == PPP_UNSPECIFIED)
{
p->AuthProtocol = PPP_PROTOCOL_CHAP;
Debug("Setting BEFORE_AUTH from ACK on LCP response parse on CHAP accept\n");
PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
}
}
else if (opt->DataSize == sizeof(USHORT) && *((USHORT *)(opt->Data)) == Endian16(PPP_LCP_AUTH_PAP))
{
// We couldn't agree on auth proto, failing connection
if (isAccepted == false)
{
UINT64 offer = 0;
Copy(&offer, t->Data, t->DataSize > sizeof(UINT64) ? sizeof(UINT64) : t->DataSize);
Debug("NACK proto with code = 0x%x, cypher = 0x%x, offered cypher = 0x%x\n", pp->Lcp->Code, *((USHORT *)(opt->Data)), offer);
Debug("Couldn't agree on auth protocol!\n");
PPPLog(p, "LP_PAP_MSCHAPV2_REJECTED");
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
else if (p->AuthProtocol == PPP_UNSPECIFIED)
{
p->AuthProtocol = PPP_PROTOCOL_PAP;
Debug("Setting BEFORE_AUTH from ACK on LCP response parse on PAP accept\n");
PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
}
}
break;
}
}
return result;
}
// Process CHAP responses
bool PPPProcessCHAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
return PPPProcessCHAPResponsePacketEx(p, pp, req, pp->Lcp, false);
}
bool PPPProcessCHAPResponsePacketEx(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req, PPP_LCP *chap, bool use_eap)
{
PPP_LCP *lcp;
if (chap->Code == PPP_CHAP_CODE_RESPONSE)
{
bool ok = false;
if (p->PPPStatus != PPP_STATUS_AUTHENTICATING && p->AuthOk == false)
{
Debug("Receiving CHAP response packets outside of auth status, some errors probably!");
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
if (p->AuthProtocol != PPP_PROTOCOL_CHAP && use_eap == false)
{
Debug("Receiving CHAP packet when auth protocol set to 0x%x\n", p->AuthProtocol);
PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_PAP", pp->Protocol);
PPPRejectUnsupportedPacketEx(p, pp, true);
return false;
}
ok = PPPParseMSCHAP2ResponsePacketEx(p, chap, use_eap);
// If we got only first packet of double CHAP then send second challenge
if (ok && p->UseEapRadius && p->EapClient != NULL && p->Ipc == NULL)
{
lcp = BuildMSCHAP2ChallengePacket(p);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_CHAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
// We got a successful MSCHAPv2 response, so let's send a SUCCESS
else if (ok)
{
char hex[MAX_SIZE];
char ret_str[MAX_SIZE];
BUF *lcp_ret_data = NewBuf();
BinToStr(hex, sizeof(hex), p->MsChapV2_ServerResponse, 20);
Format(ret_str, sizeof(ret_str),
"S=%s", hex);
WriteBuf(lcp_ret_data, ret_str, StrLen(ret_str));
lcp = NewPPPLCP(PPP_CHAP_CODE_SUCCESS, p->MsChapV2_PacketId);
lcp->Data = Clone(lcp_ret_data->Buf, lcp_ret_data->Size);
lcp->DataSize = lcp_ret_data->Size;
if (lcp_ret_data != NULL)
{
FreeBuf(lcp_ret_data);
}
if (use_eap == false)
{
PPP_PACKET *res = ZeroMalloc(sizeof(PPP_PACKET));
res->Lcp = lcp;
res->IsControl = true;
res->Protocol = PPP_PROTOCOL_CHAP;
if (PPPSendPacketAndFree(p, res) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
}
else
{
BUF *b = BuildLCPData(lcp);
FreePPPLCP(lcp);
p->Eap_PacketId = p->NextId++;
lcp = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId, PPP_EAP_TYPE_MSCHAPV2, b->Size);
PPP_EAP *eapPacket = lcp->Data;
Copy(eapPacket->Data, b->Buf, b->Size);
FreeBuf(b);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
p->AuthOk = true;
}
// We failed MSCHAPv2 auth
else
{
char hex[MAX_SIZE];
char ret_str[MAX_SIZE];
BUF *lcp_ret_data = NewBuf();
BinToStr(hex, sizeof(hex), p->MsChapV2_ServerChallenge, 16);
Format(ret_str, sizeof(ret_str),
"E=%u R=0 C=%s V=3", p->MsChapV2_ErrorCode, hex);
WriteBuf(lcp_ret_data, ret_str, StrLen(ret_str));
lcp = NewPPPLCP(PPP_CHAP_CODE_FAILURE, p->MsChapV2_PacketId);
lcp->Data = Clone(lcp_ret_data->Buf, lcp_ret_data->Size);
lcp->DataSize = lcp_ret_data->Size;
if (lcp_ret_data != NULL)
{
FreeBuf(lcp_ret_data);
}
if (use_eap == false)
{
PPP_PACKET *res = ZeroMalloc(sizeof(PPP_PACKET));
res->Lcp = lcp;
res->IsControl = true;
res->Protocol = PPP_PROTOCOL_CHAP;
if (PPPSendPacketAndFree(p, res) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
}
else
{
BUF *b = BuildLCPData(lcp);
FreePPPLCP(lcp);
p->Eap_PacketId = p->NextId++;
lcp = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId, PPP_EAP_TYPE_MSCHAPV2, b->Size);
PPP_EAP *eapPacket = lcp->Data;
Copy(eapPacket->Data, b->Buf, b->Size);
FreeBuf(b);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
PPPLog(p, "LP_CHAP_FAILED");
}
return ok;
}
return false;
}
// Process IPCP responses
bool PPPProcessIPCPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
bool isAccepted = PPP_LCP_CODE_IS_NEGATIVE(pp->Lcp->Code) == false;
IP addrStruct;
char addrStr[MAX_SIZE];
UINT addr;
IP prevAddrStruct;
char prevAddrStr[MAX_SIZE];
UINT prevAddr;
PPP_LCP *c;
UINT ui;
if (PPPGetIPAddressValueFromLCP(pp->Lcp, PPP_IPCP_OPTION_IP, &addrStruct) == false || pp->Lcp->Code == PPP_LCP_CODE_REJECT || pp->Lcp->Code == PPP_LCP_CODE_CODE_REJECT)
{
Debug("Unsupported IPCP protocol");
IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_REJECTED);
PPPRejectUnsupportedPacketEx(p, pp, true);
return false;
}
// We're dealing either with ACK or NACK
addr = IPToUINT(&addrStruct);
IPToStr(addrStr, MAX_SIZE, &addrStruct);
if (isAccepted)
{
Debug("Accepted server IP address of %s\n", addrStr);
// We already configured client address, now server address is also confirmed, ready for IPv4 data flow
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CONFIG)
{
IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_CONFIG_WAIT);
}
return true;
}
IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_CONFIG);
PPPGetIPAddressValueFromLCP(req->Lcp, PPP_IPCP_OPTION_IP, &prevAddrStruct);
prevAddr = IPToUINT(&prevAddrStruct);
IPToStr(prevAddrStr, MAX_SIZE, &prevAddrStruct);
Debug("Denied server IP address %s, proposed %s\n", prevAddrStr, addrStr);
// Fallback mechanism - just request 192.0.0.8
if (prevAddr == Endian32(0xc0000008))
{
Debug("We already tried the fallback IP of 192.0.0.8, giving up\n");
IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_REJECTED);
PPPRejectUnsupportedPacketEx(p, pp, true);
return false;
}
c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
ui = Endian32(0xc0000008); // We always push 192.0.0.8, which is defined in RFC7600 as dummy IPv4 address.
Add(c->OptionList, NewPPPOption(PPP_IPCP_OPTION_IP, &ui, sizeof(UINT)));
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPCP, c) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return false;
}
// Process EAP responses
bool PPPProcessEAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
if (pp->Lcp->DataSize >= 1)
{
PPP_EAP *eap_packet = pp->Lcp->Data;
UINT eap_datasize = pp->Lcp->DataSize - 1;
UINT64 offer = 0;
PPP_LCP *c;
UCHAR ms_chap_v2_code[3];
HUB *hub;
bool found = false;
UINT authtype = AUTHTYPE_ANONYMOUS;
UCHAR eapidentitypkt[MAX_SIZE] = { 0 };
WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;
// Forward EAP response to Radius server
if (p->EapClient != NULL)
{
return PPPProcessEapResponseForRadius(p, eap_packet, eap_datasize);
}
switch (eap_packet->Type)
{
case PPP_EAP_TYPE_IDENTITY:
p->Eap_MatchUserByCert = false;
// Parse username
Copy(eapidentitypkt, eap_packet->Data, MIN(MAX_SIZE, eap_datasize));
Zero(&p->Eap_Identity, sizeof(p->Eap_Identity));
PPPParseUsername(p->Cedar, eapidentitypkt, &p->Eap_Identity);
Debug("EAP: username=%s, hubname=%s\n", p->Eap_Identity.UserName, p->Eap_Identity.HubName);
// Locate user
LockHubList(p->Cedar);
{
hub = GetHub(p->Cedar, p->Eap_Identity.HubName);
}
UnlockHubList(p->Cedar);
if (hub != NULL)
{
AcLock(hub);
{
USER *user = AcGetUser(hub, p->Eap_Identity.UserName);
if (user == NULL)
{
user = AcGetUser(hub, "*");
}
if (user != NULL)
{
found = true;
authtype = user->AuthType;
ReleaseUser(user);
}
else if (hub->Option->AllowEapMatchUserByCert == true)
{
authtype = AUTHTYPE_USERCERT;
Zero(p->Eap_Identity.UserName, sizeof(p->Eap_Identity.UserName));
p->Eap_MatchUserByCert = true;
}
}
AcUnlock(hub);
ReleaseHub(hub);
}
if (found == false && p->Eap_MatchUserByCert == false)
{
// User not found, fail immediately
PPP_PACKET *pack = ZeroMalloc(sizeof(PPP_PACKET));
pack->IsControl = true;
pack->Protocol = PPP_PROTOCOL_EAP;
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
pack->Lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, p->Eap_PacketId);
if (PPPSendPacketAndFree(p, pack) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
break;
}
// Select EAP method based on auth type
switch (authtype)
{
case AUTHTYPE_RADIUS:
// Create EAP client if needed
if (p->EapClient == NULL)
{
char client_ip_tmp[256];
PPP_LCP *response = NULL;
IPToStr(client_ip_tmp, sizeof(client_ip_tmp), &p->ClientIP);
Debug("Creating EAP RADIUS client\n");
p->EapClient = HubNewEapClient(p->Cedar, p->Eap_Identity.HubName, client_ip_tmp, p->Eap_Identity.UserName, "L3:PPP", true,
&response, pp->Lcp->Id);
if (p->EapClient == NULL || response == NULL)
{
PPP_PACKET *pack = ZeroMalloc(sizeof(PPP_PACKET));
pack->IsControl = true;
pack->Protocol = PPP_PROTOCOL_EAP;
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
pack->Lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, p->Eap_PacketId);
Debug("Failed to connect to a RADIUS server\n");
if (PPPSendPacketAndFree(p, pack) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
else
{
// Send first response to client
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, response) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
break;
}
case AUTHTYPE_ANONYMOUS:
case AUTHTYPE_PASSWORD:
case AUTHTYPE_NT:
// Propose EAP-MSCHAPv2 directly
p->Eap_Protocol = PPP_EAP_TYPE_MSCHAPV2;
PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
break;
default:
// Propose EAP-TLS first
p->Eap_Protocol = PPP_EAP_TYPE_TLS;
PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
break;
}
break;
case PPP_EAP_TYPE_NOTIFICATION:
// Basically this is just an acknoweldgment that the notification was accepted by the client. Nothing to do here...
break;
case PPP_EAP_TYPE_NAK:
if (p->Eap_Protocol == PPP_EAP_TYPE_TLS && p->Eap_MatchUserByCert == false)
{
// Propose EAP-MSCHAPv2
p->Eap_Protocol = PPP_EAP_TYPE_MSCHAPV2;
PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
break;
}
// Fallback to auth protocol selection to try to select MSCHAP or PAP
Debug("Got a EAP_NAK, abandoning EAP protocol\n");
PPPRejectUnsupportedPacketEx(p, pp, true);
PPPSetStatus(p, PPP_STATUS_CONNECTED);
c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
Copy(&offer, ms_chap_v2_code, sizeof(ms_chap_v2_code));
Debug("Request MSCHAPv2 from EAP NAK\n");
Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &ms_chap_v2_code, sizeof(ms_chap_v2_code)));
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
break;
case PPP_EAP_TYPE_TLS:
PPPProcessEAPTlsResponse(p, eap_packet, eap_datasize);
break;
case PPP_EAP_TYPE_MSCHAPV2:
if (p->PPPStatus != PPP_STATUS_AUTHENTICATING)
{
Debug("Received EAP-MSCHAPv2 response not during authentication\n");
break;
}
if (eap_datasize == 1)
{
// Success or failure response
PPP_PACKET *pack = ZeroMalloc(sizeof(PPP_PACKET));
pack->IsControl = true;
pack->Protocol = PPP_PROTOCOL_EAP;
if (p->AuthOk)
{
PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
pack->Lcp = NewPPPLCP(PPP_EAP_CODE_SUCCESS, p->Eap_PacketId);
}
else
{
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
pack->Lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, p->Eap_PacketId);
}
if (PPPSendPacketAndFree(p, pack) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
else
{
// CHAP response
PPP_LCP *chap = PPPParseLCP(PPP_PROTOCOL_CHAP, eap_packet->Data, eap_datasize);
if (chap == NULL)
{
Debug("Received an invalid EAP-MSCHAPv2 packet\n");
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
PPPProcessCHAPResponsePacketEx(p, pp, req, chap, true);
FreePPPLCP(chap);
}
break;
default:
Debug("We got an unexpected EAP response packet! Ignoring...\n");
break;
}
}
else
{
PPP_EAP *eap;
Debug("We got a CODE=%i ID=%i from client with zero size EAP structure, that shouldn't be happening!\n", pp->Lcp->Code, pp->Lcp->Id);
eap = req->Lcp->Data;
if (eap->Type == PPP_EAP_TYPE_TLS)
{
p->Eap_PacketId = p->NextId++;
PPP_LCP *lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, PPP_EAP_TLS_FLAG_NONE);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
}
return false;
}
// Process IPv6CP responses
bool PPPProcessIPv6CPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
bool isAccepted = PPP_LCP_CODE_IS_NEGATIVE(pp->Lcp->Code) == false;
// If we got a reject or a NACK, we just reject the whole IPv6 configuration, there is no way we can recover even from a NACK as we can't change the link-local address of an already existing router
if (isAccepted == false)
{
Debug("Unsupported IPv6CP protocol");
IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_REJECTED);
PPPRejectUnsupportedPacketEx(p, pp, true);
return false;
}
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) != IPC_PROTO_STATUS_CONFIG)
{
Debug("We got an early IPv6CP response, ignoring for now...\n");
return false;
}
Debug("Accepted server IPv6CP handshake\n");
IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_CONFIG_WAIT);
return true;
}
// Process EAP response for RADIUS (as proxy)
bool PPPProcessEapResponseForRadius(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eap_datasize)
{
PPP_LCP *lcp;
IPC *ipc;
UINT error_code;
if (p == NULL || eap_packet == NULL || p->EapClient == NULL)
{
return false;
}
lcp = EapClientSendEapRequest(p->EapClient, eap_packet, eap_datasize);
if (lcp == NULL)
{
return false;
}
switch (lcp->Code)
{
case PPP_EAP_CODE_REQUEST:
// Send back to client
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return true;
case PPP_EAP_CODE_SUCCESS:
if (p->Ipc == NULL)
{
Debug("PPP Radius creating IPC\n");
ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, p->Eap_Identity.HubName, p->Eap_Identity.UserName, "", NULL,
&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
p->ClientHostname, p->CryptName, false, p->AdjustMss, p->EapClient, NULL,
true, IPC_LAYER_3);
if (ipc != NULL)
{
p->Ipc = ipc;
// Setting user timeouts
p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout
p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000;
if (p->TubeRecv != NULL)
{
p->TubeRecv->DataTimeout = p->DataTimeout;
}
p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000;
p->UserConnectionTick = Tick64();
p->AuthOk = true;
PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
break;
}
}
case PPP_EAP_CODE_FAILURE:
default:
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
break;
}
// Send success or failure
PPP_PACKET* pack;
pack = ZeroMalloc(sizeof(PPP_PACKET));
pack->IsControl = true;
pack->Protocol = PPP_PROTOCOL_EAP;
pack->Lcp = lcp;
if (PPPSendPacketAndFree(p, pack) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return true;
}
// Processes request packets
bool PPPProcessRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
switch (pp->Protocol)
{
case PPP_PROTOCOL_LCP:
return PPPProcessLCPRequestPacket(p, pp);
break;
case PPP_PROTOCOL_PAP:
return PPPProcessPAPRequestPacket(p, pp);
break;
case PPP_PROTOCOL_CHAP:
Debug("Got a CHAP request, which is invalid, we should get CHAP response instead\n");
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
break;
case PPP_PROTOCOL_IPCP:
return PPPProcessIPCPRequestPacket(p, pp);
break;
case PPP_PROTOCOL_IPV6CP:
return PPPProcessIPv6CPRequestPacket(p, pp);
break;
case PPP_PROTOCOL_EAP:
return PPPProcessEAPRequestPacket(p, pp);
break;
default:
Debug("Unsupported protocols should be already filtered out! protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code);
return false;
break;
}
return false;
}
bool PPPProcessLCPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
bool result = true;
UINT i = 0;
USHORT NegotiatedAuthProto = PPP_UNSPECIFIED;
USHORT NegotiatedMRU = PPP_UNSPECIFIED;
// MSCHAPv2 code
UCHAR ms_chap_v2_code[3];
USHORT eap_code = PPP_LCP_AUTH_EAP;
WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;
Debug("Got LCP packet request ID=%i OptionsListSize=%i\n", pp->Lcp->Id, LIST_NUM(pp->Lcp->OptionList));
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
switch (t->Type)
{
case PPP_LCP_OPTION_AUTH:
t->IsSupported = true;
if (t->DataSize == sizeof(USHORT) && *((USHORT *)t->Data) == PPP_LCP_AUTH_EAP && p->AuthProtocol == PPP_UNSPECIFIED)
{
t->IsAccepted = true;
NegotiatedAuthProto = PPP_PROTOCOL_EAP;
}
else if (t->DataSize == sizeof(USHORT) && *((USHORT *)t->Data) == PPP_LCP_AUTH_PAP && p->AuthProtocol == PPP_UNSPECIFIED)
{
t->IsAccepted = true;
NegotiatedAuthProto = PPP_PROTOCOL_PAP;
}
else if (t->DataSize == sizeof(ms_chap_v2_code) && Cmp(t->Data, ms_chap_v2_code, t->DataSize) == 0 && p->AuthProtocol == PPP_UNSPECIFIED)
{
t->IsAccepted = true;
NegotiatedAuthProto = PPP_PROTOCOL_CHAP;
}
else
{
// We're recommending EAP by default as a more secure algo
t->IsAccepted = false;
t->AltDataSize = sizeof(eap_code);
Copy(t->AltData, &eap_code, sizeof(eap_code));
}
break;
case PPP_LCP_OPTION_MRU:
t->IsSupported = true;
if (t->DataSize == sizeof(USHORT))
{
USHORT value = READ_USHORT(t->Data);
if (value < PPP_MRU_MIN || value > PPP_MRU_MAX)
{
t->IsAccepted = false;
value = MAKESURE(value, PPP_MRU_MIN, PPP_MRU_MAX);
//Debug("MRU not accepted, sending NACK with value = 0x%x\n", value);
t->AltDataSize = sizeof(USHORT);
WRITE_USHORT(t->AltData, value);
}
else
{
t->IsAccepted = true;
NegotiatedMRU = value;
//Debug("MRU accepted, value = 0x%x\n", value);
}
}
else
{
t->IsAccepted = false;
t->AltDataSize = sizeof(USHORT);
WRITE_USHORT(t->AltData, PPP_MRU_DEFAULT);
}
break;
default:
t->IsSupported = false;
Debug("Unsupported LCP option = 0x%x\n", t->Type);
break;
}
}
if (PPPRejectLCPOptions(p, pp))
{
Debug("Rejected LCP options...\n");
return false;
}
if (PPPNackLCPOptions(p, pp))
{
Debug("NACKed LCP options...\n");
return false;
}
if (PPPAckLCPOptions(p, pp) == false)
{
return false;
}
if (NegotiatedAuthProto != PPP_UNSPECIFIED)
{
if (p->AuthProtocol == PPP_UNSPECIFIED)
{
p->AuthProtocol = NegotiatedAuthProto;
PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
Debug("Setting BEFORE_AUTH from REQ on LCP request parse\n");
}
}
if (NegotiatedMRU != PPP_UNSPECIFIED)
{
p->Mru1 = NegotiatedMRU;
}
return true;
}
bool PPPProcessPAPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
if (p->PPPStatus != PPP_STATUS_BEFORE_AUTH && p->AuthOk == false)
{
PPP_LCP *lcp = NewPPPLCP(PPP_PAP_CODE_NAK, pp->Lcp->Id);
PPP_PACKET *ret = ZeroMalloc(sizeof(PPP_PACKET));
Debug("Got a PAP request before we're ready for AUTH procedure!\n");
ret->IsControl = true;
ret->Protocol = PPP_PROTOCOL_PAP;
ret->Lcp = lcp;
if (PPPSendPacketAndFree(p, ret) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return false;
}
if (p->AuthProtocol != PPP_PROTOCOL_PAP)
{
Debug("Trying to auth with PAP when should be 0x%x\n", p->AuthProtocol);
PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_CHAP", pp->Protocol);
// Forcing rejection of PAP on configured MSCHAPv2
PPPRejectUnsupportedPacketEx(p, pp, true);
return false;
}
if (p->AuthOk == false)
{
UCHAR *data;
UINT size;
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
if (p->Ipc == NULL)
{
// PAP
// Extract the ID and the password
data = pp->Lcp->Data;
size = pp->Lcp->DataSize;
if (size >= 1)
{
UCHAR len_id = data[0];
data++;
size--;
if (size >= len_id)
{
char username[256];
char password[256];
Zero(username, sizeof(username));
Zero(password, sizeof(password));
Copy(username, data, len_id);
data += len_id;
size -= len_id;
if (size >= 1)
{
UCHAR len_pass = data[0];
data++;
size--;
if (size >= len_pass)
{
IPC *ipc;
char id[MAX_SIZE];
char hub[MAX_SIZE];
ETHERIP_ID d;
Zero(id, sizeof(id));
Zero(hub, sizeof(hub));
Copy(password, data, len_pass);
Debug("PPP: id=%s, pw=%s\n", username, password);
// The user name is divided into the ID and the virtual HUB name
Zero(&d, sizeof(d));
PPPParseUsername(p->Cedar, username, &d);
StrCpy(id, sizeof(id), d.UserName);
StrCpy(hub, sizeof(hub), d.HubName);
if (IsEmptyStr(id) == false)
{
// Attempt to connect with IPC
UINT error_code;
ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password, NULL,
&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, NULL,
false, IPC_LAYER_3);
if (ipc != NULL)
{
p->Ipc = ipc;
// Setting user timeouts
p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout
p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000;
if (p->TubeRecv != NULL)
{
p->TubeRecv->DataTimeout = p->DataTimeout;
}
p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000;
p->UserConnectionTick = Tick64();
p->AuthOk = true;
}
else
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
}
}
}
}
}
else
{
// Return success for a request from the second time when it is successfully authenticated once
p->AuthOk = true;
}
}
if (p->AuthOk)
{
PPP_LCP *lcp = NewPPPLCP(PPP_PAP_CODE_ACK, pp->Lcp->Id);
PPP_PACKET *ret = ZeroMalloc(sizeof(PPP_PACKET));
ret->IsControl = true;
ret->Protocol = PPP_PROTOCOL_PAP;
ret->Lcp = lcp;
if (PPPSendPacketAndFree(p, ret) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
if (p->PPPStatus == PPP_STATUS_AUTHENTICATING)
{
PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
}
return true;
}
if (p->AuthOk == false)
{
PPP_LCP *lcp = NewPPPLCP(PPP_PAP_CODE_NAK, pp->Lcp->Id);
PPP_PACKET *ret = ZeroMalloc(sizeof(PPP_PACKET));
ret->IsControl = true;
ret->Protocol = PPP_PROTOCOL_PAP;
ret->Lcp = lcp;
if (PPPSendPacketAndFree(p, ret) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
if (p->PPPStatus == PPP_STATUS_AUTHENTICATING)
{
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
PPPLog(p, "LP_PAP_FAILED");
}
return false;
}
return p->AuthOk;
}
bool PPPProcessIPCPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
PPP_IPOPTION o;
PPP_IPOPTION res;
PPP_OPTION *dummyIpOption;
UINT dummyIp = 0;
DHCP_OPTION_LIST cao;
IP client_ip;
IP subnet;
IP zero;
IP gw;
bool ok = true;
bool processed = false;
bool isEmptyIpAddress = false;
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_REJECTED)
{
Debug("We got an IPCP packet after we had it rejected\n");
return PPPRejectUnsupportedPacketEx(p, pp, true);
}
if (PPPGetIPOptionFromLCP(&o, pp->Lcp) == false)
{
Debug("IPCP request without client IP address received! Treating as zeroed out client IP...\n");
isEmptyIpAddress = true;
dummyIpOption = NewPPPOption(PPP_IPCP_OPTION_IP, &dummyIp, sizeof(UINT));
dummyIpOption->IsSupported = true;
dummyIpOption->IsAccepted = false;
Add(pp->Lcp->OptionList, dummyIpOption);
}
// Process if not configured yet by server
if ((IsZero(&p->ClientAddressOption, sizeof(DHCP_OPTION_LIST)) || isEmptyIpAddress) && ok)
{
// Decide if we received a static IP from client and it is allowed
if (IsZeroIP(&o.IpAddress) == false)
{
if (p->Ipc->Policy->DHCPForce == false)
{
if (p->DhcpAllocated == false)
{
if (p->UseStaticIPAddress == false)
{
DHCP_OPTION_LIST cao;
// The client specify an IP address
Zero(&cao, sizeof(cao));
cao.ClientAddress = IPToUINT(&o.IpAddress);
Copy(&p->ClientAddressOption, &cao, sizeof(cao));
p->UseStaticIPAddress = true;
}
}
}
}
else
{
p->UseStaticIPAddress = false;
}
// Get additional information for static clients
if (p->UseStaticIPAddress)
{
if (p->DhcpIpInformTried == false)
{
// Get additional information such as the subnet mask from the DHCP server
SetIP(&subnet, 255, 0, 0, 0);
Zero(&zero, sizeof(zero));
UINTToIP(&client_ip, p->ClientAddressOption.ClientAddress);
Zero(&cao, sizeof(cao));
IPCSetIPv4Parameters(p->Ipc, &client_ip, &subnet, &zero, NULL);
p->DhcpIpInformTried = true;
PPPLog(p, "LP_DHCP_INFORM_TRYING");
if (IPCDhcpRequestInformIP(p->Ipc, &cao, p->TubeRecv, &client_ip))
{
Debug("IPCDhcpRequestInformIP ok.\n");
Copy(&p->ClientAddressOption, &cao, sizeof(cao));
p->ClientAddressOption.ClientAddress = IPToUINT(&client_ip);
if (true)
{
char server_ip_str[64];
char subnet_str[64], defgw_str[64];
char dns1_str[64], dns2_str[64];
char wins1_str[64], wins2_str[64];
IPToStr32(server_ip_str, sizeof(server_ip_str), cao.ServerAddress);
IPToStr32(subnet_str, sizeof(subnet_str), cao.SubnetMask);
IPToStr32(defgw_str, sizeof(defgw_str), cao.Gateway);
IPToStr32(dns1_str, sizeof(dns1_str), cao.DnsServer);
IPToStr32(dns2_str, sizeof(dns2_str), cao.DnsServer2);
IPToStr32(wins1_str, sizeof(wins1_str), cao.WinsServer);
IPToStr32(wins2_str, sizeof(wins2_str), cao.WinsServer2);
PPPLog(p, "LP_DHCP_INFORM_OK",
subnet_str, defgw_str, cao.DomainName,
dns1_str, dns2_str, wins1_str, wins2_str,
server_ip_str);
}
}
else
{
Debug("IPCDhcpRequestInformIP failed.\n");
ok = false;
p->DhcpIpInformTried = false;
PPPLog(p, "LP_DHCP_INFORM_NG");
}
IPCSetIPv4Parameters(p->Ipc, &zero, &zero, &zero, NULL);
}
}
// Get IP address and additional information from DHCP
else
{
if (p->DhcpIpAllocTried == false)
{
DHCP_OPTION_LIST cao;
Zero(&cao, sizeof(cao));
p->DhcpIpAllocTried = true;
PPPLog(p, "LP_DHCP_REQUEST_TRYING");
if (IPCDhcpAllocateIP(p->Ipc, &cao, p->TubeRecv))
{
UINT t;
Debug("IPCDhcpAllocateIP ok.\n");
// IP address has been determined
Copy(&p->ClientAddressOption, &cao, sizeof(cao));
p->DhcpAllocated = true;
// Determine the DHCP update interval
t = cao.LeaseTime;
if (t == 0)
{
t = 600;
}
t = t / 3;
if (t == 0)
{
t = 1;
}
p->DhcpRenewInterval = (UINT64)t * (UINT64)1000;
p->DhcpNextRenewTime = Tick64() + p->DhcpRenewInterval;
if (true)
{
char client_ip_str[64], server_ip_str[64];
char subnet_str[64], defgw_str[64];
char dns1_str[64], dns2_str[64];
char wins1_str[64], wins2_str[64];
IPToStr32(client_ip_str, sizeof(client_ip_str), cao.ClientAddress);
IPToStr32(server_ip_str, sizeof(server_ip_str), cao.ServerAddress);
IPToStr32(subnet_str, sizeof(subnet_str), cao.SubnetMask);
IPToStr32(defgw_str, sizeof(defgw_str), cao.Gateway);
IPToStr32(dns1_str, sizeof(dns1_str), cao.DnsServer);
IPToStr32(dns2_str, sizeof(dns2_str), cao.DnsServer2);
IPToStr32(wins1_str, sizeof(wins1_str), cao.WinsServer);
IPToStr32(wins2_str, sizeof(wins2_str), cao.WinsServer2);
PPPLog(p, "LP_DHCP_REQUEST_OK",
client_ip_str, subnet_str, defgw_str, cao.DomainName,
dns1_str, dns2_str, wins1_str, wins2_str,
server_ip_str, cao.LeaseTime);
}
}
else
{
Debug("IPCDhcpAllocateIP failed.\n");
p->DhcpIpAllocTried = false;
ok = false;
PPPLog(p, "LP_DHCP_REQUEST_NG");
}
}
}
}
// If we already have a configured IP data - send it along
if (IsValidUnicastIPAddressUINT4(p->ClientAddressOption.ClientAddress) &&
p->ClientAddressOption.SubnetMask != 0 && ok)
{
// Success to determine the address
UINTToIP(&subnet, p->ClientAddressOption.SubnetMask);
UINTToIP(&gw, p->ClientAddressOption.Gateway);
Zero(&res, sizeof(res));
UINTToIP(&res.IpAddress, p->ClientAddressOption.ClientAddress);
UINTToIP(&res.DnsServer1, p->ClientAddressOption.DnsServer);
UINTToIP(&res.DnsServer2, p->ClientAddressOption.DnsServer2);
UINTToIP(&res.WinsServer1, p->ClientAddressOption.WinsServer);
UINTToIP(&res.WinsServer2, p->ClientAddressOption.WinsServer2);
if (IPCSetIPv4Parameters(p->Ipc, &res.IpAddress, &subnet, &gw, &p->ClientAddressOption.ClasslessRoute))
{
char client_ip_str[64];
char subnet_str[64], defgw_str[64];
char dns1_str[64], dns2_str[64];
char wins1_str[64], wins2_str[64];
// IPv4 parameters have been set for the first time
Debug("Param First Set.\n");
IPToStr(client_ip_str, sizeof(client_ip_str), &res.IpAddress);
IPToStr(subnet_str, sizeof(subnet_str), &subnet);
IPToStr(defgw_str, sizeof(defgw_str), &gw);
IPToStr(dns1_str, sizeof(dns1_str), &res.DnsServer1);
IPToStr(dns2_str, sizeof(dns2_str), &res.DnsServer2);
IPToStr(wins1_str, sizeof(wins1_str), &res.WinsServer1);
IPToStr(wins2_str, sizeof(wins2_str), &res.WinsServer2);
PPPLog(p, "LP_SET_IPV4_PARAM", client_ip_str, subnet_str,
defgw_str, dns1_str, dns2_str, wins1_str, wins2_str);
}
/*// Backporting static configuration received from client - let him use whatever he wants,
// he won't accept anything else anyway (as per testing with Windows clients)
if (!IsZeroIP(&o.DnsServer1))
{
CopyIP(&res.DnsServer1, &o.DnsServer1);
Debug("Setting DNS1 from client\n");
}
if (!IsZeroIP(&o.DnsServer2))
{
CopyIP(&res.DnsServer2, &o.DnsServer2);
Debug("Setting DNS2 from client\n");
}
if (!IsZeroIP(&o.WinsServer1))
{
CopyIP(&res.WinsServer1, &o.WinsServer1);
Debug("Setting WINS1 from client\n");
}
if (!IsZeroIP(&o.WinsServer2))
{
CopyIP(&res.WinsServer2, &o.WinsServer2);
Debug("Setting WINS2 from client\n");
}*/
/*if (!IsZeroIP(&res.DnsServer1) && IsZeroIP(&res.DnsServer2))
{
CopyIP(&res.DnsServer2, &res.DnsServer1);
}
if (!IsZeroIP(&res.WinsServer1) && IsZeroIP(&res.WinsServer2))
{
CopyIP(&res.WinsServer2, &res.WinsServer1);
}*/
PPPSetIPOptionToLCP(&res, pp->Lcp, true);
}
// We couldn't configure address for the client
else
{
// Failed to determine the address
Debug("IP Address Determination Failed.\n");
Zero(&res, sizeof(res));
// We will try to reconfigure if we receive another request by wiping all data
Zero(&p->ClientAddressOption, sizeof(DHCP_OPTION_LIST));
p->UseStaticIPAddress = false;
PPPSetIPOptionToLCP(&res, pp->Lcp, true);
}
if (PPPRejectLCPOptionsEx(p, pp, processed))
{
Debug("Rejected IPCP options ID = 0x%x\n", pp->Lcp->Id);
processed = true;
}
if (ok && PPPNackLCPOptionsEx(p, pp, processed))
{
Debug("NACKed IPCP options ID = 0x%x\n", pp->Lcp->Id);
processed = true;
}
// We will delay this packet ACK and send the server IP first, then wait for a reparse
// it is kind of dirty but fixes issues on some clients (namely VPN Client Pro on Android)
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CLOSED && p->ClientAddressOption.ServerAddress != 0 && ok)
{
PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
UINT ui = p->ClientAddressOption.ServerAddress;
Add(c->OptionList, NewPPPOption(PPP_IPCP_OPTION_IP, &ui, sizeof(UINT)));
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPCP, c) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_CONFIG);
if (processed == false)
{
PPPAddNextPacket(p, pp, 1);
}
return false;
}
// We still haven't received any answer from client about server IP, keep waiting...
if ((IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CONFIG ||
IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CLOSED) && processed == false)
{
PPPAddNextPacket(p, pp, 1);
return false;
}
//Debug("PPPAckLCPOptionsEx ok=%x, processed=%x", ok, processed);
if (ok == false || PPPAckLCPOptionsEx(p, pp, processed) == false)
{
return false;
}
Debug("ACKed IPCP options ID = 0x%x\n", pp->Lcp->Id);
if (ok && IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CONFIG_WAIT)
{
IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_OPENED);
Debug("IPv4 OPENED\n");
}
return ok;
}
// Process EAP request packets
bool PPPProcessEAPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
Debug("We got an EAP request, which is weird...\n");
return false;
}
// Process IPv6CP request packets
bool PPPProcessIPv6CPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
UINT i;
bool processed = false;
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_REJECTED)
{
Debug("We got an IPv6CP packet after we had it rejected\n");
return PPPRejectUnsupportedPacketEx(p, pp, true);
}
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
switch (t->Type)
{
case PPP_IPV6CP_OPTION_EUI:
t->IsSupported = true;
if (t->DataSize == sizeof(UINT64))
{
UINT64 newValue = 0;
UINT64 value = READ_UINT64(t->Data);
if (value != 0 && value != p->Ipc->IPv6ServerEUI && IPCIPv6CheckExistingLinkLocal(p->Ipc, value) == false)
{
t->IsAccepted = true;
p->Ipc->IPv6ClientEUI = value;
}
else
{
t->IsAccepted = false;
while (true)
{
newValue = Rand64();
if (newValue != 0 && newValue != p->Ipc->IPv6ServerEUI && IPCIPv6CheckExistingLinkLocal(p->Ipc, newValue) == false)
{
WRITE_UINT64(t->AltData, newValue);
t->AltDataSize = sizeof(UINT64);
break;
}
}
}
}
break;
default:
t->IsSupported = false;
break;
}
}
if (PPPRejectLCPOptionsEx(p, pp, processed))
{
Debug("Rejected IPv6CP options ID = 0x%x\n", pp->Lcp->Id);
processed = true;
}
if (PPPNackLCPOptionsEx(p, pp, processed))
{
Debug("NACKed IPv6CP options ID = 0x%x\n", pp->Lcp->Id);
processed = true;
}
if (p->Ipc->IPv6ClientEUI != 0 && IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_CLOSED)
{
PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
Add(c->OptionList, NewPPPOption(PPP_IPV6CP_OPTION_EUI, &p->Ipc->IPv6ServerEUI, sizeof(UINT64)));
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPV6CP, c) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_CONFIG);
}
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_CONFIG && processed == false)
{
PPPAddNextPacket(p, pp, 1);
return false;
}
if (PPPAckLCPOptionsEx(p, pp, processed) == false)
{
return false;
}
Debug("ACKed IPv6CP options ID = 0x%x\n", pp->Lcp->Id);
if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_CONFIG_WAIT)
{
IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_OPENED);
Debug("IPv6 OPENED\n");
}
return true;
}
// LCP option based packets utility
bool PPPRejectLCPOptions(PPP_SESSION *p, PPP_PACKET *pp)
{
return PPPRejectLCPOptionsEx(p, pp, false);
}
bool PPPRejectLCPOptionsEx(PPP_SESSION *p, PPP_PACKET *pp, bool simulate)
{
UINT i = 0;
bool toBeRejected = false;
PPP_PACKET *ret;
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
if (t->IsSupported == false)
{
toBeRejected = true;
break;
}
}
if (toBeRejected == false)
{
return false;
}
ret = ZeroMalloc(sizeof(PPP_PACKET));
ret->IsControl = true;
ret->Protocol = pp->Protocol;
// Return a Reject if there are unsupported parameters
ret->Lcp = NewPPPLCP(PPP_LCP_CODE_REJECT, pp->Lcp->Id);
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
if (t->IsSupported == false)
{
// Attach the original option value as is
Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize));
Debug("Rejected LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol);
}
}
if (LIST_NUM(ret->Lcp->OptionList) == 0 || simulate)
{
FreePPPPacket(ret);
return false;
}
PPPSendPacketAndFree(p, ret);
return true;
}
bool PPPNackLCPOptions(PPP_SESSION *p, PPP_PACKET *pp)
{
return PPPNackLCPOptionsEx(p, pp, false);
}
bool PPPNackLCPOptionsEx(PPP_SESSION *p, PPP_PACKET *pp, bool simulate)
{
UINT i = 0;
PPP_PACKET *ret;
bool toBeNACKed = false;
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
if (t->IsAccepted == false && t->IsSupported == true)
{
toBeNACKed = true;
break;
}
}
if (toBeNACKed == false)
{
return false;
}
ret = ZeroMalloc(sizeof(PPP_PACKET));
ret->IsControl = true;
ret->Protocol = pp->Protocol;
// Return a NAK if there are any unacceptable parameter
// even that all parameters are supported
ret->Lcp = NewPPPLCP(PPP_LCP_CODE_NAK, pp->Lcp->Id);
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
if (t->IsAccepted == false && t->IsSupported == true)
{
// Replace the original option value with an acceptable value
Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->AltData, t->AltDataSize));
Debug("NACKed LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol);
}
}
if (LIST_NUM(ret->Lcp->OptionList) == 0 || simulate)
{
FreePPPPacket(ret);
return false;
}
PPPSendPacketAndFree(p, ret);
return true;
}
bool PPPAckLCPOptions(PPP_SESSION *p, PPP_PACKET *pp)
{
return PPPAckLCPOptionsEx(p, pp, false);
}
bool PPPAckLCPOptionsEx(PPP_SESSION *p, PPP_PACKET *pp, bool simulate)
{
UINT i = 0;
PPP_PACKET *ret;
bool toBeACKed = false;
if (LIST_NUM(pp->Lcp->OptionList) == 0)
{
// We acknoweldge an empty option list
toBeACKed = true;
Debug("ACKing empty LCP options list, id=%i\n", pp->Lcp->Id);
}
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
if (t->IsAccepted == true && t->IsSupported == true)
{
toBeACKed = true;
break;
}
}
if (toBeACKed == false)
{
return false;
}
ret = ZeroMalloc(sizeof(PPP_PACKET));
ret->IsControl = true;
ret->Protocol = pp->Protocol;
// Return an ACK if all parameters are accepted
ret->Lcp = NewPPPLCP(PPP_LCP_CODE_ACK, pp->Lcp->Id);
for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
if (t->IsAccepted == true && t->IsSupported == true)
{
// Attach the original option value as is
Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize));
Debug("ACKed LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol);
}
}
if (simulate)
{
FreePPPPacket(ret);
return false;
}
PPPSendPacketAndFree(p, ret);
return true;
}
// PPP networking functions
// Send a request packet in the PPP
bool PPPSendAndRetransmitRequest(PPP_SESSION *p, USHORT protocol, PPP_LCP *c)
{
PPP_PACKET *pp;
UINT64 now = Tick64();
PPP_REQUEST_RESEND *resend;
// Validate arguments
if (p == NULL || c == NULL)
{
return false;
}
pp = ZeroMalloc(sizeof(PPP_PACKET));
pp->Protocol = protocol;
pp->IsControl = true;
pp->Lcp = c;
if (pp->Lcp->Id == 0)
{
pp->Lcp->Id = p->NextId++;
}
// Send the PPP packet
if (PPPSendPacketEx(p, pp, false) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
FreePPPPacket(pp);
WHERE;
return false;
}
resend = ZeroMalloc(sizeof(PPP_REQUEST_RESEND));
resend->Id = pp->Lcp->Id;
resend->Packet = pp;
resend->ResendTime = now + PPP_PACKET_RESEND_INTERVAL;
resend->TimeoutTime = now + p->PacketRecvTimeout;
Add(p->SentReqPacketList, resend);
return true;
}
// Send the PPP packet and frees the sent packet
bool PPPSendPacketAndFree(PPP_SESSION *p, PPP_PACKET *pp)
{
bool result = PPPSendPacketEx(p, pp, false);
FreePPPPacket(pp);
return result;
}
// Send the PPP packet
bool PPPSendPacketEx(PPP_SESSION *p, PPP_PACKET *pp, bool no_flush)
{
bool ret = false;
BUF *b;
// Validate arguments
if (p == NULL || pp == NULL)
{
return false;
}
b = BuildPPPPacketData(pp);
if (b == NULL)
{
return false;
}
ret = TubeSendEx(p->TubeSend, b->Buf, b->Size, NULL, no_flush);
if (no_flush)
{
AddTubeToFlushList(p->FlushList, p->TubeSend);
}
FreeBuf(b);
return ret;
}
// Receive a PPP packet
PPP_PACKET *PPPRecvPacket(PPP_SESSION *p, bool async)
{
TUBEDATA *d;
PPP_PACKET *pp;
// Validate arguments
if (p == NULL)
{
return NULL;
}
LABEL_LOOP:
if (async == false)
{
d = TubeRecvSync(p->TubeRecv, (UINT)p->PacketRecvTimeout);
}
else
{
d = TubeRecvAsync(p->TubeRecv);
}
if (d == NULL)
{
return NULL;
}
pp = ParsePPPPacket(d->Data, d->DataSize);
FreeTubeData(d);
if (pp == NULL)
{
// A broken packet is received
goto LABEL_LOOP;
}
p->LastRecvTime = Tick64();
return pp;
}
PPP_PACKET *PPPGetNextPacket(PPP_SESSION *p)
{
PPP_PACKET *ret = NULL;
UINT i = 0;
if (p->CurrentPacket != NULL)
{
FreePPPPacket(p->CurrentPacket);
p->CurrentPacket = NULL;
}
for (i = 0; i < LIST_NUM(p->DelayedPackets); i++)
{
PPP_DELAYED_PACKET *t = LIST_DATA(p->DelayedPackets, i);
if (t->DelayTicks > 0)
{
t->DelayTicks--;
}
else
{
ret = t->Packet;
Delete(p->DelayedPackets, t);
Free(t);
break;
}
}
if (ret != NULL)
{
p->CurrentPacket = ret;
return ret;
}
ret = PPPRecvPacket(p, true);
if (ret != NULL && ret->IsControl && ret->Lcp != NULL)
{
PPP_DELAYED_PACKET *firstRelated = NULL;
for (i = 0; i < LIST_NUM(p->DelayedPackets); i++)
{
PPP_DELAYED_PACKET *t = LIST_DATA(p->DelayedPackets, i);
char related = PPPRelatedPacketComparator(ret, t->Packet);
if (related != 0xF && related != 0xE)
{
if (related == 0)
{
// It's the same packet, just remove it and wait for it's delays
FreePPPPacket(ret);
firstRelated = NULL;
ret = NULL;
break;
}
if (related == 1)
{
// We got a packet which should come later than any of delayed ones
PPPAddNextPacket(p, ret, t->DelayTicks);
firstRelated = NULL;
ret = NULL;
break;
}
if (related == -1)
{
char prevFoundRelated = -1;
if (firstRelated != NULL)
{
prevFoundRelated = PPPRelatedPacketComparator(t->Packet, firstRelated->Packet);
}
if (prevFoundRelated == -1)
{
firstRelated = t;
}
}
}
}
if (firstRelated != NULL)
{
PPPAddNextPacket(p, ret, firstRelated->DelayTicks);
ret = NULL;
}
}
p->CurrentPacket = ret;
return ret;
}
void PPPAddNextPacket(PPP_SESSION *p, PPP_PACKET *pp, UINT delay)
{
PPP_DELAYED_PACKET *t = ZeroMalloc(sizeof(PPP_DELAYED_PACKET));
if (p->CurrentPacket == pp)
{
p->CurrentPacket = NULL;
}
t->Packet = pp;
t->DelayTicks = delay;
Add(p->DelayedPackets, t);
Sort(p->DelayedPackets);
/*Debug("after sorting delayeds\n");
for (i = 0; i < LIST_NUM(p->DelayedPackets); i++)
{
t = LIST_DATA(p->DelayedPackets, i);
if (t->Packet->Lcp != NULL)
{
Debug("> Packet proto = 0x%x, id = 0x%x, code = 0x%x, delay = 0x%x\n", t->Packet->Protocol, t->Packet->Lcp->Id, t->Packet->Lcp->Code, t->DelayTicks);
}
}
Debug("after sorting delayeds end\n");*/
}
int PPPDelayedPacketsComparator(void *a, void *b)
{
PPP_DELAYED_PACKET *first = a;
PPP_DELAYED_PACKET *second = b;
char related = PPPRelatedPacketComparator(first->Packet, second->Packet);
if (related == 0xF || related == 0xE)
{
if (first->DelayTicks < second->DelayTicks)
{
return -1;
}
if (first->DelayTicks > second->DelayTicks)
{
return 1;
}
return 0;
}
// We make all delay ticks to be accounted with the sorting
if (related <= 1 && related >= -1)
{
if (related == -1 && first->DelayTicks >= second->DelayTicks)
{
second->DelayTicks = first->DelayTicks;
second->DelayTicks++;
}
else if (related == 1 && first->DelayTicks <= second->DelayTicks)
{
first->DelayTicks = second->DelayTicks;
first->DelayTicks++;
}
return related;
}
return 0;
}
// -1 - packet a comes before packet b
// 0 - this is the same packet
// 1 - packet a comes after packet b
// 0xF - packet is not related
// 0xE - we got an error while comparing, treating as not related would be the most correct
char PPPRelatedPacketComparator(PPP_PACKET *a, PPP_PACKET *b)
{
if (a->IsControl && b->IsControl &&
a->Lcp != NULL && b->Lcp != NULL &&
a->Protocol == b->Protocol &&
PPP_CODE_IS_REQUEST(a->Protocol, a->Lcp->Code) == PPP_CODE_IS_REQUEST(b->Protocol, b->Lcp->Code) &&
PPP_CODE_IS_RESPONSE(a->Protocol, a->Lcp->Code) == PPP_CODE_IS_RESPONSE(b->Protocol, b->Lcp->Code))
{
// The packet is related!
if (a->Lcp->Id < b->Lcp->Id)
{
return -1;
}
else if (a->Lcp->Id == b->Lcp->Id)
{
if (a->Lcp->Code == b->Lcp->Code)
{
return 0;
}
else
{
return 0xE;
}
}
else if (a->Lcp->Id > b->Lcp->Id)
{
return 1;
}
else
{
return 0xE;
}
}
else
{
// The packet is not related!
return 0xF;
}
}
// PPP utility functions
// Packet structure creation utilities
// Create the LCP
PPP_LCP *NewPPPLCP(UCHAR code, UCHAR id)
{
PPP_LCP *c = ZeroMalloc(sizeof(PPP_LCP));
c->Code = code;
c->Id = id;
c->OptionList = NewListFast(NULL);
return c;
}
// Create a new PPP options
PPP_OPTION *NewPPPOption(UCHAR type, void *data, UINT size)
{
PPP_OPTION *o;
// Validate arguments
if (size != 0 && data == NULL)
{
return NULL;
}
o = ZeroMalloc(sizeof(PPP_OPTION));
o->Type = type;
Copy(o->Data, data, size);
o->DataSize = size;
return o;
}
// Packet parse utilities
// Analyse the PPP packet
PPP_PACKET *ParsePPPPacket(void *data, UINT size)
{
PPP_PACKET *pp;
UCHAR *buf;
// Validate arguments
if (data == NULL || size == 0)
{
return NULL;
}
pp = ZeroMalloc(sizeof(PPP_PACKET));
buf = (UCHAR *)data;
// Address
if (buf[0] != 0xff)
{
goto LABEL_ERROR;
}
size--;
buf++;
// Control
if (size < 1)
{
goto LABEL_ERROR;
}
if (buf[0] != 0x03)
{
goto LABEL_ERROR;
}
size--;
buf++;
// Protocol
if (size < 2)
{
goto LABEL_ERROR;
}
pp->Protocol = READ_USHORT(buf);
size -= 2;
buf += 2;
if (pp->Protocol == PPP_PROTOCOL_LCP || pp->Protocol == PPP_PROTOCOL_PAP || pp->Protocol == PPP_PROTOCOL_CHAP || pp->Protocol == PPP_PROTOCOL_IPCP || pp->Protocol == PPP_PROTOCOL_IPV6CP || pp->Protocol == PPP_PROTOCOL_EAP)
{
pp->IsControl = true;
}
pp->Data = Clone(buf, size);
pp->DataSize = size;
if (pp->IsControl)
{
pp->Lcp = PPPParseLCP(pp->Protocol, pp->Data, pp->DataSize);
if (pp->Lcp == NULL)
{
goto LABEL_ERROR;
}
}
return pp;
LABEL_ERROR:
FreePPPPacket(pp);
return NULL;
}
// Analyse the LCP data
PPP_LCP *PPPParseLCP(USHORT protocol, void *data, UINT size)
{
UCHAR *buf;
PPP_LCP *c;
USHORT len;
bool has_option_list = false;
// Validate arguments
if (data == NULL || size == 0)
{
return NULL;
}
buf = (UCHAR *)data;
c = ZeroMalloc(sizeof(PPP_LCP));
c->OptionList = NewListFast(NULL);
// Code
if (size < 1)
{
goto LABEL_ERROR;
}
c->Code = buf[0];
buf++;
size--;
// ID
if (size < 1)
{
goto LABEL_ERROR;
}
c->Id = buf[0];
buf++;
size--;
// Length
if (size < 2)
{
goto LABEL_ERROR;
}
len = READ_USHORT(buf);
// Fix bad endianness
if (len > size)
{
USHORT len1 = Swap16(len);
if (len1 <= size)
{
len = len1;
}
}
if (len < 4)
{
goto LABEL_ERROR;
}
len -= 4;
buf += 2;
size -= 2;
// Options or Data
if (size < len)
{
goto LABEL_ERROR;
}
has_option_list = PPP_CODE_IS_WITH_OPTION_LIST(protocol, c->Code);
if (has_option_list == false)
{
c->Data = Clone(buf, size);
c->DataSize = size;
}
else
{
// Option List
while (len >= 1)
{
PPP_OPTION o;
Zero(&o, sizeof(o));
// Type
if (len < 1)
{
goto LABEL_ERROR;
}
o.Type = buf[0];
buf++;
len--;
// Length
if (len < 1)
{
goto LABEL_ERROR;
}
o.DataSize = buf[0];
if (o.DataSize < 2)
{
goto LABEL_ERROR;
}
o.DataSize -= 2;
buf++;
len--;
// Data
if (len < o.DataSize)
{
goto LABEL_ERROR;
}
Copy(o.Data, buf, o.DataSize);
buf += o.DataSize;
len -= o.DataSize;
Add(c->OptionList, Clone(&o, sizeof(o)));
}
}
return c;
LABEL_ERROR:
FreePPPLCP(c);
return NULL;
}
// Analyse MS CHAP v2 Response packet
bool PPPParseMSCHAP2ResponsePacket(PPP_SESSION *p, PPP_PACKET *pp)
{
return PPPParseMSCHAP2ResponsePacketEx(p, pp->Lcp, false);
}
bool PPPParseMSCHAP2ResponsePacketEx(PPP_SESSION *p, PPP_LCP *lcp, bool use_eap)
{
bool ok = false;
char client_ip_tmp[256];
EAP_CLIENT *eap;
UCHAR client_response_buffer[49];
UCHAR *client_challenge_16;
UCHAR *client_response_24;
char username_tmp[MAX_SIZE];
IPC *ipc = NULL;
char id[MAX_SIZE];
char hub[MAX_SIZE];
char password[MAX_SIZE];
char server_challenge_hex[MAX_SIZE];
char client_challenge_hex[MAX_SIZE];
char client_response_hex[MAX_SIZE];
char eap_client_hex[64];
ETHERIP_ID d;
UINT error_code;
UINT64 eap_client_ptr = (UINT64)p->EapClient;
if (lcp != NULL && lcp->DataSize >= 51)
{
BUF *b;
if (lcp->Id != p->MsChapV2_PacketId)
{
Debug("Got incorrect LCP PacketId! Should be 0x%x, got 0x%x\n", p->MsChapV2_PacketId, lcp->Id);
p->MsChapV2_PacketId = lcp->Id;
}
b = NewBuf();
WriteBuf(b, lcp->Data, lcp->DataSize);
SeekBuf(b, 0, 0);
if (ReadBufChar(b) == 49)
{
ReadBuf(b, client_response_buffer, 49);
Zero(username_tmp, sizeof(username_tmp));
ReadBuf(b, username_tmp, sizeof(username_tmp) - 1);
Debug("MS-CHAPv2: id=%s\n", username_tmp);
client_challenge_16 = client_response_buffer + 0;
client_response_24 = client_response_buffer + 16 + 8;
Copy(p->MsChapV2_ClientChallenge, client_challenge_16, 16);
Copy(p->MsChapV2_ClientResponse, client_response_24, 24);
Zero(id, sizeof(id));
Zero(hub, sizeof(hub));
// The user name is divided into the ID and the virtual HUB name
Zero(&d, sizeof(d));
PPPParseUsername(p->Cedar, username_tmp, &d);
StrCpy(id, sizeof(id), d.UserName);
StrCpy(hub, sizeof(hub), d.HubName);
Debug("MS-CHAPv2: username=%s, hubname=%s\n", id, hub);
IPToStr(client_ip_tmp, sizeof(client_ip_tmp), &p->ClientIP);
// Convert the MS-CHAPv2 data to a password string
BinToStr(server_challenge_hex, sizeof(server_challenge_hex),
p->MsChapV2_ServerChallenge, sizeof(p->MsChapV2_ServerChallenge));
BinToStr(client_challenge_hex, sizeof(client_challenge_hex),
p->MsChapV2_ClientChallenge, sizeof(p->MsChapV2_ClientChallenge));
BinToStr(client_response_hex, sizeof(client_response_hex),
p->MsChapV2_ClientResponse, sizeof(p->MsChapV2_ClientResponse));
BinToStr(eap_client_hex, sizeof(eap_client_hex),
&eap_client_ptr, 8);
Format(password, sizeof(password), "%s%s:%s:%s:%s:%s",
IPC_PASSWORD_MSCHAPV2_TAG,
username_tmp,
server_challenge_hex,
client_challenge_hex,
client_response_hex,
eap_client_hex);
// Normal MSCHAPv2 only
// For EAP-MSCHAPv2, EAP client is created before sending the challenge
if (p->UseEapRadius && p->EapClient == NULL && use_eap == false)
{
Debug("Double MSCHAPv2 creating EAP client\n");
eap = HubNewEapClient(p->Cedar, hub, client_ip_tmp, id, "L3:PPP", false, NULL, 0);
// We do not know the user's auth type, so do not fail PPP if eap is null
if (eap)
{
ok = true;
p->EapClient = eap;
FreeBuf(b);
return ok;
}
}
if (p->Ipc == NULL)
{
Debug("MSCHAPv2 creating IPC\n");
ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password, NULL,
&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
p->ClientHostname, p->CryptName, false, p->AdjustMss, p->EapClient, NULL,
false, IPC_LAYER_3);
if (ipc != NULL)
{
p->Ipc = ipc;
// Setting user timeouts
p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout
p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000;
if (p->TubeRecv != NULL)
{
p->TubeRecv->DataTimeout = p->DataTimeout;
}
p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000;
p->UserConnectionTick = Tick64();
Copy(p->MsChapV2_ServerResponse, ipc->MsChapV2_ServerResponse, 20);
ok = true;
p->AuthOk = true;
}
}
else
{
Debug("Got weird packet when we already have an active IPC! Ipc = 0x%x, AuthOk = 0x%x, Status = 0x%x\n", p->Ipc, p->AuthOk, p->PPPStatus);
ok = p->AuthOk;
}
}
FreeBuf(b);
}
else
{
Debug("Got invalid MSCHAPv2 packet\n");
}
return ok;
}
// Packet building utilities
// Build a PPP packet data
BUF *BuildPPPPacketData(PPP_PACKET *pp)
{
BUF *ret;
UCHAR c;
USHORT us;
// Validate arguments
if (pp == NULL)
{
return NULL;
}
ret = NewBuf();
// Address
c = 0xff;
WriteBuf(ret, &c, 1);
// Control
c = 0x03;
WriteBuf(ret, &c, 1);
// Protocol
us = Endian16(pp->Protocol);
WriteBuf(ret, &us, 2);
if (pp->IsControl)
{
// LCP
BUF *b = BuildLCPData(pp->Lcp);
WriteBufBuf(ret, b);
FreeBuf(b);
}
else
{
// Data
WriteBuf(ret, pp->Data, pp->DataSize);
}
SeekBuf(ret, 0, 0);
return ret;
}
// Build the LCP packet data
BUF *BuildLCPData(PPP_LCP *c)
{
BUF *b;
UCHAR zero = 0;
UINT i;
// Validate arguments
if (c == NULL)
{
return NULL;
}
b = NewBuf();
// Code
WriteBuf(b, &c->Code, 1);
// ID
WriteBuf(b, &c->Id, 1);
// Length (to be updated later)
zero = 0;
WriteBuf(b, &zero, 1);
WriteBuf(b, &zero, 1);
if (c->Data == NULL)
{
// Option List
for (i = 0; i < LIST_NUM(c->OptionList); i++)
{
PPP_OPTION *o = LIST_DATA(c->OptionList, i);
UCHAR sz = o->DataSize + 2;
WriteBuf(b, &o->Type, 1);
WriteBuf(b, &sz, 1);
WriteBuf(b, o->Data, o->DataSize);
}
}
else
{
// Data
WriteBuf(b, c->Data, c->DataSize);
}
SeekBuf(b, 0, 0);
// Update Length
WRITE_USHORT(((UCHAR *)b->Buf) + 2, b->Size);
return b;
}
// Build the MS CHAP v2 challenge packet
PPP_LCP *BuildMSCHAP2ChallengePacket(PPP_SESSION *p)
{
PPP_LCP *lcp;
BUF *b;
char machine_name[MAX_SIZE];
UINT64 now = Tick64();
// Generate a Server Challenge packet of MS-CHAP v2
GetMachineHostName(machine_name, sizeof(machine_name));
if (p->EapClient == NULL)
{
MsChapV2Server_GenerateChallenge(p->MsChapV2_ServerChallenge);
}
else
{
Copy(p->MsChapV2_ServerChallenge, p->EapClient->MsChapV2Challenge.Chap_ChallengeValue, 16);
}
p->MsChapV2_PacketId = p->NextId++;
lcp = NewPPPLCP(PPP_CHAP_CODE_CHALLENGE, p->MsChapV2_PacketId);
b = NewBuf();
WriteBufChar(b, 16);
WriteBuf(b, p->MsChapV2_ServerChallenge, sizeof(p->MsChapV2_ServerChallenge));
WriteBuf(b, machine_name, StrLen(machine_name));
lcp->Data = Clone(b->Buf, b->Size);
lcp->DataSize = b->Size;
FreeBuf(b);
Debug("Building MS-CHAP v2 Challenge\n");
return lcp;
}
// IPCP packet utilities
// Set the IP options of PPP to LCP
bool PPPSetIPOptionToLCP(PPP_IPOPTION *o, PPP_LCP *c, bool only_modify)
{
bool ret = false;
// Validate arguments
if (c == NULL || o == NULL)
{
return false;
}
ret = PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_IP, &o->IpAddress, only_modify);
PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_DNS1, &o->DnsServer1, only_modify);
PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_DNS2, &o->DnsServer2, only_modify);
PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_WINS1, &o->WinsServer1, only_modify);
PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_WINS2, &o->WinsServer2, only_modify);
return ret;
}
// Get the IP options of PPP from LCP
bool PPPGetIPOptionFromLCP(PPP_IPOPTION *o, PPP_LCP *c)
{
bool ret;
// Validate arguments
if (c == NULL || o == NULL)
{
return false;
}
Zero(o, sizeof(PPP_IPOPTION));
ret = PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_IP, &o->IpAddress);
PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_DNS1, &o->DnsServer1);
PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_DNS2, &o->DnsServer2);
PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_WINS1, &o->WinsServer1);
PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_WINS2, &o->WinsServer2);
return ret;
}
// Set the IP address data to the option list of the LCP
bool PPPSetIPAddressValueToLCP(PPP_LCP *c, UINT type, IP *ip, bool only_modify)
{
IP ip2;
UINT ui;
// Validate arguments
if (c == NULL || ip == NULL)
{
return false;
}
ui = IPToUINT(ip);
if (PPPGetIPAddressValueFromLCP(c, type, &ip2))
{
PPP_OPTION *opt;
opt = PPPGetOptionValue(c, type);
if (opt != NULL)
{
if (IsZeroIP(ip) == false)
{
if (CmpIpAddr(&ip2, ip) == 0)
{
// No change
opt->IsAccepted = true;
opt->IsSupported = true;
}
else
{
// Changed
opt->IsAccepted = false;
opt->IsSupported = true;
opt->AltDataSize = 4;
Copy(opt->AltData, &ui, 4);
}
}
else
{
// The parameter itself is not supported
// (if the IP address is 0.0.0.0)
opt->IsSupported = false;
opt->IsAccepted = false;
}
}
return true;
}
else
{
if (IsZeroIP(ip) == false)
{
// Add as a new item
if (only_modify != false)
{
return false;
}
else
{
PPP_OPTION *opt2 = NewPPPOption(type, &ui, 4);
UCHAR ipstr[MAX_SIZE];
opt2->IsAccepted = true;
opt2->IsSupported = true;
Copy(opt2->AltData, opt2->Data, opt2->DataSize);
opt2->AltDataSize = opt2->DataSize;
Add(c->OptionList, opt2);
IPToStr(ipstr, MAX_SIZE, ip);
return true;
}
}
else
{
return false;
}
}
}
// Get the IP address data from the option list of the LCP
bool PPPGetIPAddressValueFromLCP(PPP_LCP *c, UINT type, IP *ip)
{
PPP_OPTION *opt;
UINT ui;
// Validate arguments
if (c == NULL || ip == NULL)
{
return false;
}
opt = PPPGetOptionValue(c, type);
if (opt == NULL)
{
return false;
}
if (opt->DataSize != 4)
{
return false;
}
opt->IsSupported = true;
ui = *((UINT *)opt->Data);
UINTToIP(ip, ui);
return true;
}
// EAP packet utilities
bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapSize)
{
UCHAR *dataBuffer;
UINT dataSize;
UINT tlsLength = 0;
bool isFragmented = false;
PPP_LCP *lcp;
PPP_EAP *eap;
UCHAR flags = PPP_EAP_TLS_FLAG_NONE;
UINT sizeLeft = 0;
Debug("Got EAP-TLS size=%i\n", eapSize);
if (eapSize == 0)
{
// This is a broken packet without flags, ignore it
return false;
}
if (eapSize == 1 && eap_packet->Tls.Flags == PPP_EAP_TLS_FLAG_NONE)
{
// This is an EAP-TLS message ACK
if (p->Eap_TlsCtx.CachedBufferSend != NULL)
{
// We got an ACK to transmit the next fragmented message
dataSize = p->Mru1 - 8 - 1 - 1; // Calculating the maximum payload size (without TlsLength)
sizeLeft = GetMemSize(p->Eap_TlsCtx.CachedBufferSend);
sizeLeft -= (UINT)(p->Eap_TlsCtx.CachedBufferSendPntr - p->Eap_TlsCtx.CachedBufferSend);
flags = PPP_EAP_TLS_FLAG_FRAGMENTED; // M flag
if (dataSize > sizeLeft)
{
dataSize = sizeLeft;
flags = PPP_EAP_TLS_FLAG_NONE; // Clearing the M flag because it is the last packet
}
p->Eap_PacketId = p->NextId++;
lcp = BuildEAPTlsRequest(p->Eap_PacketId, dataSize, flags);
eap = lcp->Data;
Copy(eap->Tls.TlsDataWithoutLength, p->Eap_TlsCtx.CachedBufferSendPntr, dataSize);
p->Eap_TlsCtx.CachedBufferSendPntr += (UINT64)dataSize;
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags);
if (flags == PPP_EAP_TLS_FLAG_NONE)
{
// As it is the latest message, we need to cleanup
Free(p->Eap_TlsCtx.CachedBufferSend);
p->Eap_TlsCtx.CachedBufferSend = NULL;
p->Eap_TlsCtx.CachedBufferSendPntr = NULL;
}
}
else if (p->AuthOk == true && p->Ipc != NULL && p->PPPStatus == PPP_STATUS_AUTHENTICATING)
{
// The handshake terminated and we received the final ACK, the auth is successful
// Just send an EAP-Success
PPP_PACKET* pack;
UINT identificator = p->Eap_PacketId;
PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
pack = ZeroMalloc(sizeof(PPP_PACKET));
pack->IsControl = true;
pack->Protocol = PPP_PROTOCOL_EAP;
lcp = NewPPPLCP(PPP_EAP_CODE_SUCCESS, identificator);
pack->Lcp = lcp;
Debug("Sent EAP-TLS size=%i SUCCESS\n", lcp->DataSize);
if (PPPSendPacketAndFree(p, pack) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return true;
}
else if (p->Eap_TlsCtx.ClientCert.X == NULL)
{
// Some clients needs a little help it seems - namely VPN Client Pro on Android
flags |= PPP_EAP_TLS_FLAG_SSLSTARTED;
p->Eap_PacketId = p->NextId++;
lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags);
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
Debug("Sent EAP-TLS size=%i\n", lcp->DataSize);
}
return true;
}
dataBuffer = eap_packet->Tls.TlsDataWithoutLength;
dataSize = eapSize - 1;
if (eap_packet->Tls.Flags & PPP_EAP_TLS_FLAG_TLS_LENGTH && dataSize >= 4)
{
dataBuffer = eap_packet->Tls.TlsDataWithLength.Data;
dataSize -= 4;
tlsLength = Endian32(eap_packet->Tls.TlsDataWithLength.TlsLength);
}
/*Debug("=======RECV EAP-TLS PACKET DUMP=======\n");
for (i = 0; i < dataSize; i++)
{
if (i > 0) printf(" ");
Debug("%02X", dataBuffer[i]);
}
Debug("\n=======RECV EAP-TLS PACKET DUMP END=======\n");*/
if (eap_packet->Tls.Flags & PPP_EAP_TLS_FLAG_FRAGMENTED)
{
isFragmented = true;
}
if (p->PPPStatus == PPP_STATUS_AUTHENTICATING)
{
// First we initialize the SslPipe if it is not already inited
if (p->Eap_TlsCtx.SslPipe == NULL)
{
p->Eap_TlsCtx.Dh = DhNewFromBits(DH_PARAM_BITS_DEFAULT);
p->Eap_TlsCtx.SslPipe = NewSslPipeEx3(true, p->Cedar->ServerX, p->Cedar->ServerK, p->Cedar->ServerChain, p->Eap_TlsCtx.Dh, true, &(p->Eap_TlsCtx.ClientCert), p->Eap_TlsCtx.Tls13SessionTicketsCount, p->Eap_TlsCtx.DisableTls13);
if (p->Eap_TlsCtx.SslPipe == NULL)
{
Debug("EAP-TLS: NewSslPipeEx3 failed\n");
PPPSetStatus(p, PPP_STATUS_FAIL);
return false;
}
}
// If the current frame is fragmented, or it is a possible last of a fragmented series, bufferize it
if (isFragmented || p->Eap_TlsCtx.CachedBufferRecv != NULL)
{
if (p->Eap_TlsCtx.CachedBufferRecv == NULL && tlsLength > 0)
{
p->Eap_TlsCtx.CachedBufferRecv = ZeroMalloc(MAX(dataSize, tlsLength));
p->Eap_TlsCtx.CachedBufferRecvPntr = p->Eap_TlsCtx.CachedBufferRecv;
}
else if (p->Eap_TlsCtx.CachedBufferRecv == NULL)
{
p->Eap_TlsCtx.CachedBufferRecv = ZeroMalloc(MAX(dataSize, PPP_MRU_MAX * 10)); // 10 MRUs should be enough
p->Eap_TlsCtx.CachedBufferRecvPntr = p->Eap_TlsCtx.CachedBufferRecv;
}
sizeLeft = GetMemSize(p->Eap_TlsCtx.CachedBufferRecv);
sizeLeft -= (UINT)(p->Eap_TlsCtx.CachedBufferRecvPntr - p->Eap_TlsCtx.CachedBufferRecv);
Copy(p->Eap_TlsCtx.CachedBufferRecvPntr, dataBuffer, MIN(sizeLeft, dataSize));
p->Eap_TlsCtx.CachedBufferRecvPntr += MIN(sizeLeft, dataSize);
}
// If we got a cached buffer, we should feed the FIFOs via it
if (p->Eap_TlsCtx.CachedBufferRecv != NULL)
{
dataBuffer = p->Eap_TlsCtx.CachedBufferRecv;
dataSize = GetMemSize(p->Eap_TlsCtx.CachedBufferRecv);
if (dataSize == MAX_BUFFERING_PACKET_SIZE)
{
dataSize = (UINT)(p->Eap_TlsCtx.CachedBufferRecvPntr - p->Eap_TlsCtx.CachedBufferRecv);
}
}
// Just acknoweldge that we buffered the fragmented data
if (isFragmented)
{
p->Eap_PacketId = p->NextId++;
PPP_LCP *lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, PPP_EAP_TLS_FLAG_NONE);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
Debug("Sent EAP-TLS size=%i\n", lcp->DataSize);
return true;
}
else
{
bool syncOk;
/*Debug("=======RECV EAP-TLS FIFO DUMP=======\n");
for (i = 0; i < dataSize; i++)
{
if (i > 0) printf(" ");
Debug("%02X", dataBuffer[i]);
}
Debug("\n=======RECV EAP-TLS PACKET FIFO END=======\n");*/
WriteFifo(p->Eap_TlsCtx.SslPipe->RawIn->SendFifo, dataBuffer, dataSize);
syncOk = SyncSslPipe(p->Eap_TlsCtx.SslPipe);
// Delete the cached buffer after we fed it into the pipe
if (p->Eap_TlsCtx.CachedBufferRecv != NULL)
{
Free(p->Eap_TlsCtx.CachedBufferRecv);
p->Eap_TlsCtx.CachedBufferRecv = NULL;
p->Eap_TlsCtx.CachedBufferRecvPntr = NULL;
}
// Special case - we attempt to restart downgrading TLS settings
if (!syncOk && (p->Eap_TlsCtx.DisableTls13 == false || p->Eap_TlsCtx.Tls13SessionTicketsCount == 0))
{
// If we authenticated earlier, deauthenticate back
p->DataTimeout = PPP_DATA_TIMEOUT;
p->PacketRecvTimeout = PPP_PACKET_RECV_TIMEOUT;
p->UserConnectionTimeout = 0;
p->UserConnectionTick = 0;
if (p->Ipc != NULL)
{
FreeIPC(p->Ipc);
p->Ipc = NULL;
p->AuthOk = false;
}
FreeSslPipe(p->Eap_TlsCtx.SslPipe);
DhFree(p->Eap_TlsCtx.Dh);
p->Eap_TlsCtx.SslPipe = NULL;
p->Eap_TlsCtx.Dh = NULL;
if (p->Eap_TlsCtx.Tls13SessionTicketsCount == 0)
{
p->Eap_TlsCtx.DisableTls13 = true;
}
else
{
p->Eap_TlsCtx.Tls13SessionTicketsCount = 0;
}
flags |= PPP_EAP_TLS_FLAG_SSLSTARTED;
p->Eap_PacketId = p->NextId++;
lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
Debug("EAP-TLS: Restarting the handshake! Tls13SessionTicketsCount = %d, DisableTls13 = %d\n", p->Eap_TlsCtx.Tls13SessionTicketsCount, p->Eap_TlsCtx.DisableTls13);
Debug("Sent EAP-TLS size=%i\n", lcp->DataSize);
return false;
}
// If on the server we have enough data to authenticate, let's do this before we continue with the handshake
// Check if we received the client certificate and the handshake is finished
if (p->Eap_TlsCtx.ClientCert.X != NULL && p->Ipc == NULL)
{
IPC* ipc;
UINT error_code;
if (p->Eap_MatchUserByCert)
{
HUB *hub;
bool found = false;
LockHubList(p->Cedar);
{
hub = GetHub(p->Cedar, p->Eap_Identity.HubName);
}
UnlockHubList(p->Cedar);
if (hub != NULL)
{
AcLock(hub);
{
USER* user = AcGetUserByCert(hub, p->Eap_TlsCtx.ClientCert.X);
if (user != NULL)
{
StrCpy(p->Eap_Identity.UserName, sizeof(p->Eap_Identity.UserName), user->Name);
found = true;
ReleaseUser(user);
}
}
AcUnlock(hub);
ReleaseHub(hub);
}
if (found == false)
{
PPP_PACKET* pack;
UINT identificator = p->Eap_PacketId;
ReleaseHub(hub);
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
pack = ZeroMalloc(sizeof(PPP_PACKET));
pack->IsControl = true;
pack->Protocol = PPP_PROTOCOL_EAP;
lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator);
pack->Lcp = lcp;
Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize);
if (PPPSendPacketAndFree(p, pack) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return false;
}
}
ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, p->Eap_Identity.HubName, p->Eap_Identity.UserName, "", NULL,
&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, p->Eap_TlsCtx.ClientCert.X,
false, IPC_LAYER_3);
// We use the SAM authentication here, because the handshake can still fail at this point
if (ipc != NULL)
{
// Setting user timeouts
p->Ipc = ipc;
p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout
p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000;
if (p->TubeRecv != NULL)
{
p->TubeRecv->DataTimeout = p->DataTimeout;
}
p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000;
p->UserConnectionTick = Tick64();
p->AuthOk = true;
if (p->Eap_TlsCtx.SslPipe->SslVersion == TLS1_3_VERSION)
{
// Before starting IPC and sending an EAP-Success in case of TLS 1.3 we need to send a 0x00 data packet as per RFC 9190
char zeroPacket[1] = { 0 };
WriteFifo(p->Eap_TlsCtx.SslPipe->SslInOut->SendFifo, zeroPacket, sizeof(zeroPacket));
if (!SyncSslPipe(p->Eap_TlsCtx.SslPipe))
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
}
}
else
{
PPP_PACKET* pack;
UINT identificator = p->Eap_PacketId;
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
pack = ZeroMalloc(sizeof(PPP_PACKET));
pack->IsControl = true;
pack->Protocol = PPP_PROTOCOL_EAP;
lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator);
pack->Lcp = lcp;
Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize);
if (PPPSendPacketAndFree(p, pack) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return false;
}
}
// We continue the TLS handshake
if (p->Eap_TlsCtx.SslPipe->IsDisconnected == false)
{
dataSize = FifoSize(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo);
// Do we need to send a fragmented packet?
if (dataSize > p->Mru1 - 8 - 1 - 1)
{
if (p->Eap_TlsCtx.CachedBufferSend == NULL)
{
p->Eap_TlsCtx.CachedBufferSend = ZeroMalloc(dataSize);
p->Eap_TlsCtx.CachedBufferSendPntr = p->Eap_TlsCtx.CachedBufferSend;
}
ReadFifo(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo, p->Eap_TlsCtx.CachedBufferSend, dataSize);
// Now send data from the cached buffer with set fragmentation flag and also total TLS Size
tlsLength = dataSize;
dataSize = p->Mru1 - 8 - 1 - 1 - 4; // Calculating the maximum payload size (adjusting for including TlsLength)
flags = PPP_EAP_TLS_FLAG_TLS_LENGTH; // L flag
flags |= PPP_EAP_TLS_FLAG_FRAGMENTED; // M flag
p->Eap_PacketId = p->NextId++;
lcp = BuildEAPTlsRequest(p->Eap_PacketId, dataSize, flags);
eap = lcp->Data;
eap->Tls.TlsDataWithLength.TlsLength = Endian32(tlsLength);
Copy(eap->Tls.TlsDataWithLength.Data, p->Eap_TlsCtx.CachedBufferSend, dataSize);
p->Eap_TlsCtx.CachedBufferSendPntr += dataSize;
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags);
return true;
}
else if (dataSize > 0 || p->Eap_TlsCtx.ClientCert.X == NULL)
{
p->Eap_PacketId = p->NextId++;
lcp = BuildEAPTlsRequest(p->Eap_PacketId, dataSize, 0);
eap = lcp->Data;
ReadFifo(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo, &(eap->Tls.TlsDataWithoutLength), dataSize);
if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags);
return true;
}
}
// If we end up here, we got problems, send an EAP failure
if (p->Eap_TlsCtx.SslPipe->IsDisconnected)
{
PPP_PACKET* pack;
UINT identificator = p->Eap_PacketId;
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
pack = ZeroMalloc(sizeof(PPP_PACKET));
pack->IsControl = true;
pack->Protocol = PPP_PROTOCOL_EAP;
lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator);
pack->Lcp = lcp;
Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize);
if (PPPSendPacketAndFree(p, pack) == false)
{
PPPSetStatus(p, PPP_STATUS_FAIL);
WHERE;
return false;
}
return false;
}
}
}
else
{
Debug("Got an EAP_TLS packet when not authenticating, ignoring...\n");
}
return false;
}
PPP_LCP *BuildEAPPacketEx(UCHAR code, UCHAR id, UCHAR type, UINT datasize)
{
PPP_EAP *eap_packet;
PPP_LCP *lcp_packet;
UINT lcpDatasize;
lcpDatasize = datasize + sizeof(UCHAR);
eap_packet = ZeroMalloc(lcpDatasize);
eap_packet->Type = type;
lcp_packet = NewPPPLCP(code, id);
lcp_packet->Data = eap_packet;
lcp_packet->DataSize = lcpDatasize;
return lcp_packet;
}
PPP_LCP *BuildEAPTlsPacketEx(UCHAR code, UCHAR id, UCHAR type, UINT datasize, UCHAR flags)
{
PPP_LCP *lcp_packet;
PPP_EAP *eap_packet;
UINT tls_datasize = datasize + sizeof(UCHAR);
if (flags & PPP_EAP_TLS_FLAG_TLS_LENGTH)
{
tls_datasize += sizeof(UINT);
}
lcp_packet = BuildEAPPacketEx(code, id, type, tls_datasize);
eap_packet = lcp_packet->Data;
eap_packet->Tls.Flags = flags;
return lcp_packet;
}
PPP_LCP *BuildEAPTlsRequest(UCHAR id, UINT datasize, UCHAR flags)
{
return BuildEAPTlsPacketEx(PPP_EAP_CODE_REQUEST, id, PPP_EAP_TYPE_TLS, datasize, flags);
}
// Other packet utilities
// Get the option value
PPP_OPTION *PPPGetOptionValue(PPP_LCP *c, UCHAR type)
{
UINT i;
// Validate arguments
if (c == NULL)
{
return NULL;
}
for (i = 0; i < LIST_NUM(c->OptionList); i++)
{
PPP_OPTION *t = LIST_DATA(c->OptionList, i);
if (t->Type == type)
{
return t;
}
}
return NULL;
}
// Check whether the Virtual HUB with the specified name exist?
bool IsHubExistsWithLock(CEDAR *cedar, char *hubname)
{
bool ret = false;
// Validate arguments
if (cedar == NULL || hubname == NULL)
{
return false;
}
LockList(cedar->HubList);
{
ret = IsHub(cedar, hubname);
}
UnlockList(cedar->HubList);
return ret;
}
// Sets the PPP status without overwriting the FAIL status
void PPPSetStatus(PPP_SESSION *p, UINT status)
{
if (status == PPP_STATUS_FAIL)
{
Debug("SETTING PPP_STATUS_FAIL!!!\n");
}
if (PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false || PPP_STATUS_IS_UNAVAILABLE(status))
{
p->PPPStatus = status;
}
}
// Memory freeing functions
// Release the PPP session
void FreePPPSession(PPP_SESSION *p)
{
UINT i;
// Validate arguments
if (p == NULL)
{
return;
}
// Release the memory
for (i = 0; i < LIST_NUM(p->RecvPacketList); i++)
{
PPP_PACKET *pp = LIST_DATA(p->RecvPacketList, i);
FreePPPPacket(pp);
}
ReleaseList(p->RecvPacketList);
for (i = 0; i < LIST_NUM(p->SentReqPacketList); i++)
{
PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i);
FreePPPPacket(t->Packet);
Free(t);
}
ReleaseList(p->SentReqPacketList);
for (i = 0; i < LIST_NUM(p->DelayedPackets); i++)
{
PPP_DELAYED_PACKET *t = LIST_DATA(p->DelayedPackets, i);
FreePPPPacket(t->Packet);
Free(t);
}
ReleaseList(p->DelayedPackets);
if (p->CurrentPacket != NULL)
{
FreePPPPacket(p->CurrentPacket);
}
if (p->TubeRecv != NULL)
{
// Record the PPP disconnect reason code for L2TP
p->TubeRecv->IntParam1 = p->DisconnectCauseCode;
p->TubeRecv->IntParam2 = p->DisconnectCauseDirection;
}
// Freeing EAP-TLS context
if (p->Eap_TlsCtx.CachedBufferRecv != NULL)
{
Free(p->Eap_TlsCtx.CachedBufferRecv);
}
if (p->Eap_TlsCtx.CachedBufferSend != NULL)
{
Free(p->Eap_TlsCtx.CachedBufferSend);
}
if (p->Eap_TlsCtx.SslPipe != NULL)
{
FreeSslPipe(p->Eap_TlsCtx.SslPipe);
}
if (p->Eap_TlsCtx.ClientCert.X != NULL)
{
FreeX(p->Eap_TlsCtx.ClientCert.X);
}
if (p->Eap_TlsCtx.Dh != NULL)
{
DhFree(p->Eap_TlsCtx.Dh);
}
FreeTubeFlushList(p->FlushList);
TubeDisconnect(p->TubeRecv);
TubeDisconnect(p->TubeSend);
ReleaseCedar(p->Cedar);
ReleaseTube(p->TubeRecv);
ReleaseTube(p->TubeSend);
if (p->Ipc != NULL)
{
FreeIPC(p->Ipc);
}
PPPFreeEapClient(p);
Free(p);
}
// Release the LCP
void FreePPPLCP(PPP_LCP *c)
{
// Validate arguments
if (c == NULL)
{
return;
}
FreePPPOptionList(c->OptionList);
Free(c->Data);
Free(c);
}
// Release the PPP options list
void FreePPPOptionList(LIST *o)
{
UINT i;
// Validate arguments
if (o == NULL)
{
return;
}
for (i = 0; i < LIST_NUM(o); i++)
{
PPP_OPTION *t = LIST_DATA(o, i);
Free(t);
}
ReleaseList(o);
}
// Release the PPP packet
void FreePPPPacket(PPP_PACKET *pp)
{
FreePPPPacketEx(pp, false);
}
void FreePPPPacketEx(PPP_PACKET *pp, bool no_free_struct)
{
// Validate arguments
if (pp == NULL)
{
return;
}
FreePPPLCP(pp->Lcp);
Free(pp->Data);
if (no_free_struct == false)
{
Free(pp);
}
}
// Free the associated EAP client
void PPPFreeEapClient(PPP_SESSION *p)
{
if (p == NULL)
{
return;
}
if (p->EapClient != NULL)
{
ReleaseEapClient(p->EapClient);
p->EapClient = NULL;
}
}
// Utility functions used not only in PPP stack
// Separate into the user name and the Virtual HUB name by analyzing the string
bool PPPParseUsername(CEDAR *cedar, char *src_username, ETHERIP_ID *dst)
{
UINT i, len, last_at, first_en;
char token1[MAX_SIZE]; // username
char token2[MAX_SIZE]; // hub_name
char src[MAX_SIZE];
// Validate arguments
Zero(dst, sizeof(ETHERIP_ID));
if (cedar == NULL || dst == NULL)
{
return false;
}
StrCpy(src, sizeof(src), src_username);
Trim(src);
// Search for the first "\\" in the string
len = StrLen(src);
first_en = SearchStrEx(src, "\\", 0, true);
if (first_en != INFINITE && first_en >= 1 && (first_en < (len - 1)))
{
StrCpy(token1, sizeof(token1), src + first_en + 1);
StrCpy(token2, sizeof(token2), src);
token2[first_en] = 0;
// Confirm whether the hubname exists if the virtual HUB name is
// specified like as hubname\username
if (IsHubExistsWithLock(cedar, token2) == false)
{
// If the hubname does not exist, restore to the original name
StrCpy(token1, sizeof(token1), src);
ClearStr(token2, sizeof(token2));
}
}
else
{
// Search for the separator character's last position in the string
len = StrLen(src);
last_at = INFINITE;
for (i = 0; i < len; i++)
{
char c = src[i];
if (c == cedar->UsernameHubSeparator)
{
last_at = i;
}
}
Zero(token1, sizeof(token1));
Zero(token2, sizeof(token2));
if (last_at == INFINITE)
{
// The separator character is not specifiedd
StrCpy(token1, sizeof(token1), src);
}
else
{
StrCpy(token1, sizeof(token1), src);
token1[last_at] = 0;
StrCpy(token2, sizeof(token2), src + last_at + 1);
}
// Check whether such Virtual HUB exists If the virtual HUB name is specified
if (IsEmptyStr(token2) == false)
{
if (IsHubExistsWithLock(cedar, token2) == false)
{
// Because the specified virtual HUB name doesn't exist, it's considered to be a part of the user name
StrCpy(token1, sizeof(token1), src);
ClearStr(token2, sizeof(token2));
}
}
}
if (IsEmptyStr(token2))
{
// Select the default Virtual HUB if the Virtual HUB name is not specified
StrCpy(token2, sizeof(token2), SERVER_DEFAULT_HUB_NAME);
if (cedar->Server != NULL && cedar->Server->IPsecServer != NULL)
{
Lock(cedar->Server->IPsecServer->LockSettings);
{
IPsecNormalizeServiceSetting(cedar->Server->IPsecServer);
StrCpy(token2, sizeof(token2), cedar->Server->IPsecServer->Services.L2TP_DefaultHub);
}
Unlock(cedar->Server->IPsecServer->LockSettings);
}
}
// Return the results
StrCpy(dst->HubName, sizeof(dst->HubName), token2);
StrCpy(dst->UserName, sizeof(dst->UserName), token1);
return true;
}
// Generate the NT hash of the password
void GenerateNtPasswordHash(UCHAR *dst, char *password)
{
UCHAR *tmp;
UINT tmp_size;
UINT i, len;
// Validate arguments
if (dst == NULL || password == NULL)
{
return;
}
// Generate a Unicode password
len = StrLen(password);
tmp_size = len * 2;
tmp = ZeroMalloc(tmp_size);
for (i = 0; i < len; i++)
{
tmp[i * 2] = password[i];
}
// Hashing
HashMd4(dst, tmp, tmp_size);
Free(tmp);
}
// Generate the MS-CHAPv2 server-side challenge
void MsChapV2Server_GenerateChallenge(UCHAR *dst)
{
// Validate arguments
if (dst == NULL)
{
return;
}
Rand(dst, 16);
}
// Generate the MS-CHAPv2 client-side challenge
void MsChapV2Client_GenerateChallenge(UCHAR *dst)
{
// Validate arguments
if (dst == NULL)
{
return;
}
Rand(dst, 16);
}
// Generate a 8 bytes challenge
void MsChapV2_GenerateChallenge8(UCHAR *dst, UCHAR *client_challenge, UCHAR *server_challenge, char *username)
{
BUF *b;
UCHAR hash[SHA1_SIZE];
char username2[MAX_SIZE];
char domainname2[MAX_SIZE];
// Validate arguments
if (dst == NULL || client_challenge == NULL || server_challenge == NULL)
{
return;
}
b = NewBuf();
WriteBuf(b, client_challenge, 16);
WriteBuf(b, server_challenge, 16);
ParseNtUsername(username, username2, sizeof(username2), domainname2, sizeof(domainname2), true);
if (IsEmptyStr(username2) == false)
{
WriteBuf(b, username2, StrLen(username2));
}
Sha1(hash, b->Buf, b->Size);
FreeBuf(b);
Copy(dst, hash, 8);
}
// Generate the MS-CHAPv2 client response
void MsChapV2Client_GenerateResponse(UCHAR *dst, UCHAR *challenge8, UCHAR *nt_password_hash)
{
UCHAR password_hash_2[21];
UCHAR key1[8], key2[8], key3[8];
// Validate arguments
if (dst == NULL || challenge8 == NULL || nt_password_hash == NULL)
{
return;
}
Zero(password_hash_2, sizeof(password_hash_2));
Copy(password_hash_2, nt_password_hash, 16);
Zero(key1, sizeof(key1));
Zero(key2, sizeof(key2));
Zero(key3, sizeof(key3));
Copy(key1, password_hash_2 + 0, 7);
Copy(key2, password_hash_2 + 7, 7);
Copy(key3, password_hash_2 + 14, 7);
DesEcbEncrypt(dst + 0, challenge8, key1);
DesEcbEncrypt(dst + 8, challenge8, key2);
DesEcbEncrypt(dst + 16, challenge8, key3);
}
// Generate a hash of the hash of the NT password
void GenerateNtPasswordHashHash(UCHAR *dst_hash, UCHAR *src_hash)
{
// Validate arguments
if (dst_hash == NULL || src_hash == NULL)
{
return;
}
HashMd4(dst_hash, src_hash, 16);
}
// Generate the MS-CHAPv2 server response
void MsChapV2Server_GenerateResponse(UCHAR *dst, UCHAR *nt_password_hash_hash, UCHAR *client_response, UCHAR *challenge8)
{
UCHAR digest[SHA1_SIZE];
BUF *b;
char *magic1 = "Magic server to client signing constant";
char *magic2 = "Pad to make it do more than one iteration";
// Validate arguments
if (dst == NULL || nt_password_hash_hash == NULL || client_response == NULL || challenge8 == NULL)
{
return;
}
b = NewBuf();
WriteBuf(b, nt_password_hash_hash, 16);
WriteBuf(b, client_response, 24);
WriteBuf(b, magic1, StrLen(magic1));
Sha1(digest, b->Buf, b->Size);
FreeBuf(b);
b = NewBuf();
WriteBuf(b, digest, sizeof(digest));
WriteBuf(b, challenge8, 8);
WriteBuf(b, magic2, StrLen(magic2));
Sha1(dst, b->Buf, b->Size);
FreeBuf(b);
}
// Verify whether the password matches one that is specified by the user in the MS-CHAPv2
bool MsChapV2VerityPassword(IPC_MSCHAP_V2_AUTHINFO *d, char *password)
{
UCHAR ntlm_hash[MD5_SIZE];
UCHAR challenge8[8];
UCHAR client_response[24];
// Validate arguments
if (d == NULL || password == NULL)
{
return false;
}
GenerateNtPasswordHash(ntlm_hash, password);
MsChapV2_GenerateChallenge8(challenge8, d->MsChapV2_ClientChallenge, d->MsChapV2_ServerChallenge, d->MsChapV2_PPPUsername);
MsChapV2Client_GenerateResponse(client_response, challenge8, ntlm_hash);
if (Cmp(client_response, d->MsChapV2_ClientResponse, 24) != 0)
{
return false;
}
return true;
}
// Estimate the password in the brute force for the request packet of MS-CHAPv2
char *MsChapV2DoBruteForce(IPC_MSCHAP_V2_AUTHINFO *d, LIST *password_list)
{
UINT i;
// Validate arguments
if (d == NULL || password_list == NULL)
{
return NULL;
}
for (i = 0; i < LIST_NUM(password_list); i++)
{
char *s = LIST_DATA(password_list, i);
char tmp[MAX_SIZE];
UINT j, max;
UINT len;
StrCpy(tmp, sizeof(tmp), s);
len = StrLen(tmp);
max = Power(2, MIN(len, 9));
for (j = 0; j < max; j++)
{
SetStrCaseAccordingToBits(tmp, j);
if (MsChapV2VerityPassword(d, tmp))
{
return CopyStr(tmp);
}
}
}
return NULL;
}