mirror of
https://github.com/SoftEtherVPN/SoftEtherVPN.git
synced 2024-11-26 19:39:53 +03:00
4056 lines
102 KiB
C
4056 lines
102 KiB
C
// SoftEther VPN Source Code - Developer Edition Master Branch
|
|
// Cedar Communication Module
|
|
|
|
|
|
// Proto_PPP.c
|
|
// PPP protocol stack
|
|
|
|
#include "CedarPch.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;
|
|
UINT64 now = Tick64();
|
|
|
|
// 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->MsChapV2_UseDoubleMsChapV2 = CedarIsThereAnyEapEnabledRadiusConfig(p->Cedar);
|
|
|
|
Debug("MsChapV2_UseDoubleMsChapV2 = 0x%x\n", p->MsChapV2_UseDoubleMsChapV2);
|
|
|
|
//// 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;
|
|
|
|
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 && 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))
|
|
{
|
|
// 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))
|
|
{
|
|
receivedPacketProcessed = true;
|
|
// Ignore the Echo response packet
|
|
}
|
|
else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_DROP && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus))
|
|
{
|
|
receivedPacketProcessed = true;
|
|
// Ignore the Drop packet
|
|
}
|
|
else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_IDENTIFICATION && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus))
|
|
{
|
|
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 && p->CurrentPacket != NULL && p->CurrentPacket->IsControl && PPP_CODE_IS_RESPONSE(p->CurrentPacket->Protocol, p->CurrentPacket->Lcp->Code) && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus))
|
|
{
|
|
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 && p->CurrentPacket != NULL && p->CurrentPacket->IsControl && PPP_CODE_IS_REQUEST(p->CurrentPacket->Protocol, p->CurrentPacket->Lcp->Code) && !PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus))
|
|
{
|
|
PPPProcessRequestPacket(p, p->CurrentPacket);
|
|
receivedPacketProcessed = true;
|
|
}
|
|
|
|
// Process data packets, discarded before we got any links up
|
|
if (!receivedPacketProcessed && p->CurrentPacket != NULL && !p->CurrentPacket->IsControl && 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 && 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;
|
|
lcpEap = BuildEAPTlsRequest(p->Eap_PacketId++, 0, flags);
|
|
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
|
|
if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcpEap))
|
|
{
|
|
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;
|
|
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);
|
|
if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcpEap))
|
|
{
|
|
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))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
}
|
|
|
|
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
|
|
}
|
|
|
|
if (p->PPPStatus == PPP_STATUS_CONNECTED && !authReqSent)
|
|
{
|
|
// 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))
|
|
{
|
|
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))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
}
|
|
}
|
|
|
|
if (!PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) || 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
|
|
PPP_SESSION *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;
|
|
|
|
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);
|
|
|
|
p->SessionThread = t;
|
|
|
|
return p;
|
|
}
|
|
|
|
// 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))
|
|
{
|
|
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))
|
|
{
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
// Validate arguments
|
|
if (p == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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))
|
|
{
|
|
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);
|
|
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 && 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 && 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)
|
|
{
|
|
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))
|
|
{
|
|
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)
|
|
{
|
|
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))
|
|
{
|
|
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 || !p->EnableMSCHAPv2)
|
|
{
|
|
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))
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
PPP_LCP *lcp;
|
|
if (pp->Lcp->Code == PPP_CHAP_CODE_RESPONSE)
|
|
{
|
|
bool ok = false;
|
|
if (p->PPPStatus != PPP_STATUS_AUTHENTICATING && !p->AuthOk)
|
|
{
|
|
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)
|
|
{
|
|
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 = PPPParseMSCHAP2ResponsePacket(p, pp);
|
|
|
|
// If we got only first packet of double CHAP then send second challenge
|
|
if (ok && p->MsChapV2_UseDoubleMsChapV2 && p->EapClient != NULL && p->Ipc == NULL)
|
|
{
|
|
lcp = BuildMSCHAP2ChallengePacket(p);
|
|
if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_CHAP, lcp))
|
|
{
|
|
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();
|
|
PPP_PACKET *res = ZeroMalloc(sizeof(PPP_PACKET));
|
|
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);
|
|
}
|
|
|
|
res->Lcp = lcp;
|
|
res->IsControl = true;
|
|
res->Protocol = PPP_PROTOCOL_CHAP;
|
|
|
|
if (!PPPSendPacketAndFree(p, res))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
|
|
p->AuthOk = true;
|
|
PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
|
|
}
|
|
// We failed MSCHAPv2 auth
|
|
else
|
|
{
|
|
char hex[MAX_SIZE];
|
|
char ret_str[MAX_SIZE];
|
|
BUF *lcp_ret_data = NewBuf();
|
|
PPP_PACKET *res = ZeroMalloc(sizeof(PPP_PACKET));
|
|
|
|
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);
|
|
}
|
|
|
|
res->Lcp = lcp;
|
|
res->IsControl = true;
|
|
res->Protocol = PPP_PROTOCOL_CHAP;
|
|
|
|
if (!PPPSendPacketAndFree(p, res))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
|
|
PPPLog(p, "LP_CHAP_FAILED");
|
|
PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
|
|
}
|
|
|
|
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);
|
|
|
|
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) || 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))
|
|
{
|
|
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];
|
|
|
|
WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
|
|
ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;
|
|
|
|
switch (eap_packet->Type)
|
|
{
|
|
case PPP_EAP_TYPE_IDENTITY:
|
|
Copy(p->Eap_Identity, eap_packet->Data, MIN(MAX_SIZE, eap_datasize));
|
|
// As we received the identity packet, we switch back to BEFORE_AUTH and switch to the EAP_TLS proto to send the TlsStart packet on the next tick
|
|
p->Eap_Protocol = PPP_EAP_TYPE_TLS;
|
|
PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
|
|
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:
|
|
/// TODO: implement alternative EAP protocol selection based on received NAK
|
|
// For now just 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))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
break;
|
|
case PPP_EAP_TYPE_TLS:
|
|
PPPProcessEAPTlsResponse(p, eap_packet, eap_datasize);
|
|
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)
|
|
{
|
|
PPP_LCP *lcp = BuildEAPTlsRequest(p->Eap_PacketId++, 0, PPP_EAP_TLS_FLAG_NONE);
|
|
if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp))
|
|
{
|
|
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);
|
|
|
|
// 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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
// 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))
|
|
{
|
|
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)
|
|
{
|
|
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))
|
|
{
|
|
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)
|
|
{
|
|
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,
|
|
&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
|
|
p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, NULL,
|
|
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;
|
|
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))
|
|
{
|
|
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)
|
|
{
|
|
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))
|
|
{
|
|
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))
|
|
{
|
|
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))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_CONFIG);
|
|
if (!processed)
|
|
{
|
|
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)
|
|
{
|
|
PPPAddNextPacket(p, pp, 1);
|
|
return false;
|
|
}
|
|
|
|
//Debug("PPPAckLCPOptionsEx ok=%x, processed=%x", ok, processed);
|
|
if (!ok || !PPPAckLCPOptionsEx(p, pp, processed))
|
|
{
|
|
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 && !IPCIPv6CheckExistingLinkLocal(p->Ipc, value))
|
|
{
|
|
t->IsAccepted = true;
|
|
p->Ipc->IPv6ClientEUI = value;
|
|
}
|
|
else
|
|
{
|
|
t->IsAccepted = false;
|
|
GenerateEui64Address6((UCHAR *)&newValue, p->Ipc->MacAddress);
|
|
if (newValue != value && !IPCIPv6CheckExistingLinkLocal(p->Ipc, newValue))
|
|
{
|
|
WRITE_UINT64(t->AltData, newValue);
|
|
t->AltDataSize = sizeof(UINT64);
|
|
}
|
|
else
|
|
{
|
|
while (true)
|
|
{
|
|
newValue = Rand64();
|
|
if (!IPCIPv6CheckExistingLinkLocal(p->Ipc, newValue))
|
|
{
|
|
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);
|
|
UINT64 serverEui = IPCIPv6GetServerEui(p->Ipc);
|
|
if (serverEui != 0 && serverEui != p->Ipc->IPv6ClientEUI)
|
|
{
|
|
Add(c->OptionList, NewPPPOption(PPP_IPV6CP_OPTION_EUI, &serverEui, sizeof(UINT64)));
|
|
}
|
|
if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPV6CP, c))
|
|
{
|
|
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)
|
|
{
|
|
PPPAddNextPacket(p, pp, 1);
|
|
return false;
|
|
}
|
|
|
|
if (!PPPAckLCPOptionsEx(p, pp, processed))
|
|
{
|
|
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))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
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 (size < 1)
|
|
{
|
|
goto LABEL_ERROR;
|
|
}
|
|
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);
|
|
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)
|
|
{
|
|
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 (pp->Lcp != NULL && pp->Lcp->DataSize >= 51)
|
|
{
|
|
BUF *b;
|
|
if (pp->Lcp->Id != p->MsChapV2_PacketId)
|
|
{
|
|
Debug("Got incorrect LCP PacketId! Should be 0x%x, got 0x%x\n", p->MsChapV2_PacketId, pp->Lcp->Id);
|
|
p->MsChapV2_PacketId = pp->Lcp->Id;
|
|
}
|
|
|
|
b = NewBuf();
|
|
|
|
WriteBuf(b, pp->Lcp->Data, pp->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);
|
|
|
|
if (p->MsChapV2_UseDoubleMsChapV2 && p->EapClient == NULL)
|
|
{
|
|
Debug("Double MSCHAPv2 creating EAP client\n");
|
|
eap = HubNewEapClient(p->Cedar, hub, client_ip_tmp, id, "L3:PPP");
|
|
|
|
if (eap)
|
|
{
|
|
ok = true;
|
|
p->EapClient = eap;
|
|
}
|
|
else
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
}
|
|
else if (p->Ipc == NULL)
|
|
{
|
|
Debug("MSCHAPv2 creating IPC\n");
|
|
ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password,
|
|
&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
|
|
p->ClientHostname, p->CryptName, false, p->AdjustMss, p->EapClient, NULL,
|
|
+ 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;
|
|
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 eapTlsSize)
|
|
{
|
|
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", eapTlsSize);
|
|
if (eapTlsSize == 1)
|
|
{
|
|
// 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
|
|
}
|
|
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))
|
|
{
|
|
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
|
|
{
|
|
// It probably should be the final ACK on closed SSL pipe
|
|
SyncSslPipe(p->Eap_TlsCtx.SslPipe);
|
|
if (p->Eap_TlsCtx.ClientCert.X != NULL)
|
|
{
|
|
IPC *ipc;
|
|
ETHERIP_ID d;
|
|
UINT error_code;
|
|
|
|
/*if (!p->Eap_TlsCtx.SslPipe->IsDisconnected)
|
|
{
|
|
dataSize = FifoSize(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo);
|
|
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))
|
|
{
|
|
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;
|
|
}*/
|
|
|
|
PPPParseUsername(p->Cedar, p->Eap_Identity, &d);
|
|
|
|
ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, d.HubName, d.UserName, "",
|
|
&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
|
|
p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, p->Eap_TlsCtx.ClientCert.X,
|
|
IPC_LAYER_3);
|
|
|
|
if (ipc != NULL)
|
|
{
|
|
PPP_PACKET *pack;
|
|
UINT identificator = p->Eap_PacketId - 1; // THIS IS A HACK TO SUPPORT VPN Client Pro on Android!!!
|
|
|
|
p->Ipc = ipc;
|
|
PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
|
|
|
|
// Just send an EAP-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))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
PPP_PACKET *pack;
|
|
UINT identificator = p->Eap_PacketId - 1; // THIS IS A HACK TO SUPPORT VPN Client Pro on Android!!!
|
|
|
|
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))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Some clients needs a little help it seems - namely VPN Client Pro on Android
|
|
flags |= PPP_EAP_TLS_FLAG_SSLSTARTED;
|
|
lcp = BuildEAPTlsRequest(p->Eap_PacketId++, 0, flags);
|
|
PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
|
|
if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
Debug("Sent EAP-TLS size=%i\n", lcp->DataSize);
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
dataBuffer = eap_packet->Tls.TlsDataWithoutLength;
|
|
dataSize = eapTlsSize - 1;
|
|
if (eap_packet->Tls.Flags & PPP_EAP_TLS_FLAG_TLS_LENGTH)
|
|
{
|
|
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 = NewSslPipeEx(true, p->Cedar->ServerX, p->Cedar->ServerK, p->Eap_TlsCtx.Dh, true, &(p->Eap_TlsCtx.ClientCert));
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
PPP_LCP *lcp = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId++, PPP_EAP_TYPE_TLS, 0);
|
|
if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp))
|
|
{
|
|
PPPSetStatus(p, PPP_STATUS_FAIL);
|
|
WHERE;
|
|
return false;
|
|
}
|
|
Debug("Sent EAP-TLS size=%i\n", lcp->DataSize);
|
|
}
|
|
else
|
|
{
|
|
/*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);
|
|
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;
|
|
}
|
|
|
|
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
|
|
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))
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
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))
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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(UINT32);
|
|
}
|
|
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) || 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 || src == 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;
|
|
}
|
|
|
|
|