1
0
mirror of https://github.com/SoftEtherVPN/SoftEtherVPN.git synced 2024-11-26 19:39:53 +03:00
SoftEtherVPN/src/Cedar/Proto_PPP.c

2709 lines
59 KiB
C

// SoftEther VPN Source Code - Developer Edition Master Branch
// Cedar Communication Module
// Proto_PPP.c
// PPP protocol stack
#include "CedarPch.h"
// PPP thread
void PPPThread(THREAD *thread, void *param)
{
PPP_SESSION *p = (PPP_SESSION *)param;
UINT i;
PPP_LCP *c;
USHORT us;
UINT ui;
USHORT next_protocol = 0;
bool ret = false;
char ipstr1[128], ipstr2[128];
bool established = false;
// Validate arguments
if (thread == NULL || param == NULL)
{
return;
}
// Initialize
p->Mru1 = p->Mru2 = PPP_MRU_DEFAULT;
p->RecvPacketList = NewList(NULL);
p->MsChapV2_UseDoubleMsChapV2 = CedarIsThereAnyEapEnabledRadiusConfig(p->Cedar);
//// Link establishment phase
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);
// Request the use of PAP
c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
us = Endian16(PPP_LCP_AUTH_PAP);
Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &us, sizeof(USHORT)));
ret = PPPSendRequest(p, PPP_PROTOCOL_LCP, c);
FreePPPLCP(c);
if (ret == false)
{
if (IsTubeConnected(p->TubeRecv))
{
// PAP protocol is denied
p->DisconnectCauseCode = 15;
p->DisconnectCauseDirection = 1;
Debug("PPP: PAP Rejected.\n");
if (p->EnableMSCHAPv2)
{
// Try to request the use of MS-CHAPv2
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;
c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, ms_chap_v2_code, sizeof(ms_chap_v2_code)));
ret = PPPSendRequest(p, PPP_PROTOCOL_LCP, c);
FreePPPLCP(c);
if (ret == false)
{
if (IsTubeConnected(p->TubeRecv))
{
// MS-CHAPv2 protocol was also rejected
p->DisconnectCauseCode = 15;
p->DisconnectCauseDirection = 1;
Debug("PPP: MS-CHAPv2 Rejected.\n");
PPPLog(p, "LP_PAP_MSCHAPV2_REJECTED");
}
}
else
{
// It is to be used for the MS-CHAPv2
p->AuthProtocol = PPP_PROTOCOL_CHAP;
}
}
else
{
PPPLog(p, "LP_PAP_REJECTED");
}
}
if (ret == false)
{
goto LABEL_CLEANUP;
}
}
//// Authentication phase
if (p->AuthProtocol == PPP_PROTOCOL_PAP)
{
// PAP
next_protocol = PPPContinueCurrentProtocolRequestListening(p, PPP_PROTOCOL_LCP);
if (next_protocol == 0)
{
goto LABEL_CLEANUP;
}
Debug("next_protocol = 0x%x\n", next_protocol);
if (next_protocol != PPP_PROTOCOL_PAP)
{
Debug("next_protocol is not PAP !!\n");
PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_PAP", next_protocol);
goto LABEL_CLEANUP;
}
next_protocol = PPPContinueCurrentProtocolRequestListening(p, PPP_PROTOCOL_PAP);
if (next_protocol == 0 || p->AuthOk == false)
{
if (IsTubeConnected(p->TubeRecv))
{
// PAP authentication failed
p->DisconnectCauseCode = 15;
p->DisconnectCauseDirection = 1;
Debug("PPP: PAP Failed.\n");
PPPLog(p, "LP_PAP_FAILED");
}
goto LABEL_CLEANUP;
}
}
else
{
// MS-CHAP v2
PPP_PACKET *pp, *pp_ret;
BUF *b;
char machine_name[MAX_SIZE];
UINT64 start_tick = Tick64();
UINT64 timeout_tick = start_tick + (UINT64)PPP_PACKET_RECV_TIMEOUT;
UINT64 next_send_tick = 0;
USHORT pp_ret_protocol;
PPPContinueUntilFinishAllLCPOptionRequestsDetermined(p);
if (p->MsChapV2_UseDoubleMsChapV2)
{
// Use the double-MSCHAPv2 technieue
GetMachineHostName(machine_name, sizeof(machine_name));
MsChapV2Server_GenerateChallenge(p->MsChapV2_ServerChallenge);
pp = ZeroMalloc(sizeof(PPP_PACKET));
pp->Protocol = PPP_PROTOCOL_CHAP;
pp->IsControl = true;
pp->Lcp = NewPPPLCP(PPP_CHAP_CODE_CHALLENGE, 99);
b = NewBuf();
WriteBufChar(b, 16);
WriteBuf(b, p->MsChapV2_ServerChallenge, sizeof(p->MsChapV2_ServerChallenge));
WriteBuf(b, machine_name, StrLen(machine_name));
pp->Lcp->Data = Clone(b->Buf, b->Size);
pp->Lcp->DataSize = b->Size;
FreeBuf(b);
PPPSendPacket(p, pp);
pp_ret = PPPRecvResponsePacket(p, pp, 0, &pp_ret_protocol, false, true);
if (pp_ret != NULL)
{
// Extract the username from the first MS-CHAP v2 packet
if (pp_ret->Lcp != NULL && pp_ret->Lcp->DataSize >= 51)
{
BUF *b;
b = MemToBuf(pp_ret->Lcp->Data, pp_ret->Lcp->DataSize);
if (ReadBufChar(b) == 49)
{
UCHAR client_response_buffer[49];
char username_tmp[MAX_SIZE];
char id[MAX_SIZE];
char hub[MAX_SIZE];
char client_ip_tmp[256];
EAP_CLIENT *eap;
ETHERIP_ID d;
ReadBuf(b, client_response_buffer, 49);
Zero(username_tmp, sizeof(username_tmp));
ReadBuf(b, username_tmp, sizeof(username_tmp) - 1);
Debug("First MS-CHAPv2: id=%s\n", username_tmp);
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("First MS-CHAPv2: username=%s, hubname=%s\n", id, hub);
IPToStr(client_ip_tmp, sizeof(client_ip_tmp), &p->ClientIP);
eap = HubNewEapClient(p->Cedar, hub, client_ip_tmp, id);
if (eap)
{
p->EapClient = eap;
}
}
FreeBuf(b);
}
FreePPPPacket(pp_ret);
}
FreePPPPacket(pp);
}
// 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);
}
pp = ZeroMalloc(sizeof(PPP_PACKET));
pp->Protocol = PPP_PROTOCOL_CHAP;
pp->IsControl = true;
pp->Lcp = NewPPPLCP(PPP_CHAP_CODE_CHALLENGE, 0);
b = NewBuf();
WriteBufChar(b, 16);
WriteBuf(b, p->MsChapV2_ServerChallenge, sizeof(p->MsChapV2_ServerChallenge));
WriteBuf(b, machine_name, StrLen(machine_name));
pp->Lcp->Data = Clone(b->Buf, b->Size);
pp->Lcp->DataSize = b->Size;
FreeBuf(b);
PPPSendPacket(p, pp);
pp_ret_protocol = 0;
pp_ret = PPPRecvResponsePacket(p, pp, 0, &pp_ret_protocol, false, false);
if (pp_ret != NULL)
{
FreePPPPacket(pp_ret);
}
FreePPPPacket(pp);
if (pp_ret_protocol == 0 || p->AuthOk == false)
{
if (IsTubeConnected(p->TubeRecv))
{
// MS-CHAPv2 authentication failed
p->DisconnectCauseCode = 15;
p->DisconnectCauseDirection = 1;
Debug("PPP: MS-CHAPv2 Failed.\n");
PPPLog(p, "LP_MSCHAPV2_FAILED");
}
goto LABEL_CLEANUP;
}
next_protocol = pp_ret_protocol;
}
Debug("next_protocol = 0x%x\n", next_protocol);
if (next_protocol != PPP_PROTOCOL_IPCP)
{
// Receive the protocol of non-IPCP
Debug("Not IPCP Protocol.\n");
PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_IPCP", next_protocol);
goto LABEL_CLEANUP;
}
// Notify the IP address of the PPP server
c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
// SoftEther VPN is L2-based VPN, so there is no concept of gateway IP address.
// We always push 192.0.0.8, which is defined in RFC7600 as dummy IPv4 address.
ui = Endian32(0xc0000008);
Add(c->OptionList, NewPPPOption(PPP_IPCP_OPTION_IP, &ui, sizeof(UINT)));
ret = PPPSendRequest(p, PPP_PROTOCOL_IPCP, c);
FreePPPLCP(c);
if (ret == false)
{
goto LABEL_CLEANUP;
}
next_protocol = PPPContinueCurrentProtocolRequestListening(p, PPP_PROTOCOL_IPCP);
Debug("next_protocol = 0x%x\n", next_protocol);
if (p->Ipc == NULL || IsZeroIP(&p->Ipc->ClientIPAddress))
{
// IP address is undetermined
PPPLog(p, "LP_IP_ADDRESS_NOT_DETERMIND");
goto LABEL_CLEANUP;
}
if (next_protocol == PPP_PROTOCOL_IP)
{
established = true;
// Do the IP communication
while (true)
{
TUBE *tubes[2];
UINT64 now = Tick64();
UINT r;
// Flush the ARP table of the IPC
IPCFlushArpTable(p->Ipc);
// Packet of client to server direction
while (true)
{
PPP_PACKET *pp = PPPRecvPacketForCommunication(p);
if (pp == NULL)
{
break;
}
if (pp->Protocol == PPP_PROTOCOL_IP)
{
// Since I want to send the IP packet, pass it to the IPC
IPCSendIPv4(p->Ipc, pp->Data, pp->DataSize);
}
FreePPPPacket(pp);
}
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);
}
}
// Happy procedure
IPCProcessL3Events(p->Ipc);
// Packet of server to client direction
while (true)
{
BLOCK *b = IPCRecvIPv4(p->Ipc);
PPP_PACKET *pp;
PPP_PACKET tmp;
if (b == NULL)
{
break;
}
// 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);
}
FlushTubeFlushList(p->FlushList);
// PPP Echo Request
if (p->NextEchoSendTime == 0 || now >= p->NextEchoSendTime)
{
p->NextEchoSendTime = now + (UINT64)PPP_ECHO_SEND_INTERVAL;
AddInterrupt(p->Ipc->Interrupt, p->NextEchoSendTime);
PPPSendEchoRequest(p);
}
// Terminate if any tube is disconnected
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)
{
// IPC VPN session is disconnected
PPPLog(p, "LP_VPN_SESSION_TERMINATED");
break;
}
// Time-out inspection
if ((p->LastRecvTime + (UINT64)PPP_DATA_TIMEOUT) <= now)
{
// Communication time-out occurs
PPPLog(p, "LP_DATA_TIMEOUT");
break;
}
// Terminate if the PPP disconnected
if (p->IsTerminateReceived)
{
PPPLog(p, "LP_NORMAL_TERMINATE");
break;
}
// Wait until the next packet arrives
tubes[0] = p->TubeRecv;
tubes[1] = p->Ipc->Sock->RecvTube;
r = GetNextIntervalForInterrupt(p->Ipc->Interrupt);
WaitForTubes(tubes, 2, MIN(r, 1234));
}
// Disconnected normally
PPPLog(p, "LP_DISCONNECTED");
}
if (p->DhcpAllocated)
{
// If any address is assigned from the DHCP, release it
IP ip;
char tmp[MAX_SIZE];
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);
}
LABEL_CLEANUP:
if (established == false)
{
// Disconnected Abnormally
PPPLog(p, "LP_DISCONNECTED_ABNORMAL");
}
// Disconnection process
PPPCleanTerminate(p);
// 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);
// Release the PPP session
FreePPPSession(p);
}
// Disconnect the PPP cleanly
void PPPCleanTerminate(PPP_SESSION *p)
{
PPP_PACKET *pp;
PPP_PACKET *res;
UINT64 giveup_tick = Tick64() + (UINT64)PPP_TERMINATE_TIMEOUT;
// Validate arguments
if (p == NULL)
{
return;
}
// Send a Terminate Request
pp = ZeroMalloc(sizeof(PPP_PACKET));
pp->IsControl = true;
pp->Protocol = PPP_PROTOCOL_LCP;
pp->Lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_REQ, p->NextId++);
Debug("PPP: Terminate Request is Sent.\n");
if (PPPSendPacket(p, pp) == false)
{
goto LABEL_CLEANUP;
}
// Wait for Terminate ACK
while (true)
{
UINT64 now = Tick64();
UINT interval;
if (now >= giveup_tick)
{
break;
}
while (true)
{
if (IsTubeConnected(p->TubeRecv) == false)
{
break;
}
res = PPPRecvPacket(p, true);
if (res == NULL)
{
break;
}
if (res->IsControl && res->Protocol == PPP_PROTOCOL_LCP && res->Lcp->Code == PPP_LCP_CODE_TERMINATE_ACK)
{
Debug("PPP: Terminate ACK is Received.\n");
FreePPPPacket(res);
goto LABEL_CLEANUP;
}
FreePPPPacket(res);
}
interval = (UINT)(giveup_tick - now);
Wait(p->TubeRecv->Event, interval);
}
LABEL_CLEANUP:
FreePPPPacket(pp);
}
// Wait until all pending LCP option are determined
bool PPPContinueUntilFinishAllLCPOptionRequestsDetermined(PPP_SESSION *p)
{
USHORT received_protocol = 0;
// Validate arguments
if (p == NULL)
{
return false;
}
PPPRecvResponsePacket(p, NULL, PPP_PROTOCOL_LCP, &received_protocol, true, false);
return p->ClientLCPOptionDetermined;
}
// Continue the processing of the request packet protocol on the current PPP
USHORT PPPContinueCurrentProtocolRequestListening(PPP_SESSION *p, USHORT protocol)
{
USHORT received_protocol = 0;
// Validate arguments
if (p == NULL)
{
return 0;
}
PPPRecvResponsePacket(p, NULL, protocol, &received_protocol, false, false);
return received_protocol;
}
// Send the PPP Echo Request
void PPPSendEchoRequest(PPP_SESSION *p)
{
PPP_PACKET *pp;
char echo_data[]= "\0\0\0\0Aho Baka Manuke";
// Validate arguments
if (p == NULL)
{
return;
}
pp = ZeroMalloc(sizeof(PPP_PACKET));
pp->Protocol = PPP_PROTOCOL_LCP;
pp->IsControl = true;
pp->Lcp = NewPPPLCP(PPP_LCP_CODE_ECHO_REQUEST, p->NextId++);
pp->Lcp->Data = Clone(echo_data, sizeof(echo_data));
pp->Lcp->DataSize = sizeof(echo_data);
PPPSendPacket(p, pp);
FreePPPPacket(pp);
}
// Send a request packet in the PPP
bool PPPSendRequest(PPP_SESSION *p, USHORT protocol, PPP_LCP *c)
{
PPP_PACKET *pp;
PPP_PACKET *pp2;
bool ret = false;
// Validate arguments
if (p == NULL || c == NULL)
{
return false;
}
pp = ZeroMalloc(sizeof(PPP_PACKET));
pp->Protocol = protocol;
pp->IsControl = true;
pp->Lcp = c;
pp->Lcp->Id = p->NextId++;
// Send the PPP packet
if (PPPSendPacket(p, pp) == false)
{
goto LABEL_ERROR;
}
// Receive a corresponding PPP packet
pp2 = PPPRecvResponsePacket(p, pp, 0, NULL, false, false);
if (pp2 != NULL)
{
if (protocol == PPP_PROTOCOL_LCP || protocol == PPP_PROTOCOL_IPCP)
{
if (!PPP_LCP_CODE_IS_NEGATIVE(pp2->Lcp->Code))
{
// A positive response is received
ret = true;
}
}
}
FreePPPPacket(pp2);
Free(pp);
return ret;
LABEL_ERROR:
Free(pp);
return false;
}
// 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;
}
// Separate into the user name and the Virtual HUB name by analyzing the string
bool PPPParseUsername(CEDAR *cedar, char *src_username, ETHERIP_ID *dst)
{
UINT i, len, last_at, first_en;
char token1[MAX_SIZE]; // username
char token2[MAX_SIZE]; // hub_name
char src[MAX_SIZE];
// Validate arguments
Zero(dst, sizeof(ETHERIP_ID));
if (cedar == NULL || dst == NULL)
{
return false;
}
StrCpy(src, sizeof(src), src_username);
Trim(src);
// Search for the first "\\" in the string
len = StrLen(src);
first_en = SearchStrEx(src, "\\", 0, true);
if (first_en != INFINITE && first_en >= 1 && (first_en < (len - 1)))
{
StrCpy(token1, sizeof(token1), src + first_en + 1);
StrCpy(token2, sizeof(token2), src);
token2[first_en] = 0;
// Confirm whether the hubname exists if the virtual HUB name is
// specified like as hubname\username
if (IsHubExistsWithLock(cedar, token2) == false)
{
// If the hubname does not exist, restore to the original name
StrCpy(token1, sizeof(token1), src);
ClearStr(token2, sizeof(token2));
}
}
else
{
// Search for the separator character's last position in the string
len = StrLen(src);
last_at = INFINITE;
for (i = 0;i < len;i++)
{
char c = src[i];
if (c == cedar->UsernameHubSeparator)
{
last_at = i;
}
}
Zero(token1, sizeof(token1));
Zero(token2, sizeof(token2));
if (last_at == INFINITE)
{
// The separator character is not specified
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;
}
// Process the PPP request packet
PPP_PACKET *PPPProcessRequestPacket(PPP_SESSION *p, PPP_PACKET *req)
{
UINT i;
PPP_PACKET *ret = NULL;
UINT num_not_supported = 0;
UINT num_not_accepted = 0;
bool no_return_option_list = false;
UINT return_code = 0;
BUF *lcp_ret_data = NULL;
// Validate arguments
if (p == NULL || req == NULL || req->Lcp == NULL)
{
return NULL;
}
// Initialize
for (i = 0;i < LIST_NUM(req->Lcp->OptionList);i++)
{
PPP_OPTION *t = LIST_DATA(req->Lcp->OptionList, i);
t->IsAccepted = false;
t->IsSupported = false;
t->AltDataSize = 0;
Zero(t->AltData, sizeof(t->AltData));
}
// Process by scanning the specified option value
if (req->Protocol == PPP_PROTOCOL_LCP)
{
// LCP
if (req->Lcp == NULL)
{
return NULL;
}
for (i = 0;i < LIST_NUM(req->Lcp->OptionList);i++)
{
PPP_OPTION *t = LIST_DATA(req->Lcp->OptionList, i);
switch (t->Type)
{
case PPP_LCP_OPTION_MRU:
// MRU
t->IsSupported = true;
if (t->DataSize == sizeof(USHORT))
{
UINT 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);
WRITE_USHORT(t->AltData, value);
t->AltDataSize = sizeof(USHORT);
}
else
{
p->Mru1 = value;
Debug("PPP: Client set %u as MRU\n", p->Mru1);
t->IsAccepted = true;
}
}
break;
}
}
}
else if (req->Protocol == PPP_PROTOCOL_CHAP)
{
bool ok = false;
char ret_str[MAX_SIZE];
no_return_option_list = true;
if (p->Ipc == NULL)
{
// MS-CHAPv2
if (req->Lcp->DataSize >= 51)
{
BUF *b;
b = NewBuf();
WriteBuf(b, req->Lcp->Data, req->Lcp->DataSize);
SeekBuf(b, 0, 0);
if (ReadBufChar(b) == 49)
{
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;
ReadBuf(b, client_response_buffer, 49);
Zero(username_tmp, sizeof(username_tmp));
ReadBuf(b, username_tmp, sizeof(username_tmp) - 1);
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);
Debug("MS-CHAPv2: id=%s\n", username_tmp);
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);
// 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);
// Attempt to connect with IPC
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);
if (ipc != NULL)
{
p->Ipc = ipc;
Copy(p->MsChapV2_ServerResponse, ipc->MsChapV2_ServerResponse, 20);
ok = true;
}
else
{
switch (error_code)
{
default:
// Normal authentication error
p->MsChapV2_ErrorCode = 691;
break;
case ERR_MSCHAP2_PASSWORD_NEED_RESET:
// Authentication errors due to compatibility issues of the password
p->MsChapV2_ErrorCode = 942;
break;
}
}
}
FreeBuf(b);
}
}
else
{
// Return success for a request from the second time when it is successfully authenticated once
ok = true;
}
// Generate a response options string
if (ok == false)
{
// In the case of failure
char hex[MAX_SIZE];
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);
return_code = PPP_CHAP_CODE_FAILURE;
}
else
{
// In the case of success
char hex[MAX_SIZE];
BinToStr(hex, sizeof(hex), p->MsChapV2_ServerResponse, 20);
Format(ret_str, sizeof(ret_str),
"S=%s", hex);
return_code = PPP_CHAP_CODE_SUCCESS;
p->AuthOk = true;
}
lcp_ret_data = NewBuf();
WriteBuf(lcp_ret_data, ret_str, StrLen(ret_str));
}
else if (req->Protocol == PPP_PROTOCOL_PAP)
{
UCHAR *data;
UINT size;
bool ok = false;
no_return_option_list = true;
if (p->Ipc == NULL)
{
// PAP
// Extract the ID and the password
data = req->Lcp->Data;
size = req->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);
if (ipc != NULL)
{
p->Ipc = ipc;
ok = true;
}
}
}
}
}
}
}
else
{
// Return success for a request from the second time when it is successfully authenticated once
ok = true;
}
if (ok == false)
{
// Authentication failure
return_code = PPP_PAP_CODE_NAK;
}
else
{
// Authentication success
return_code = PPP_PAP_CODE_ACK;
p->AuthOk = true;
}
}
else if (req->Protocol == PPP_PROTOCOL_IPCP)
{
PPP_IPOPTION o;
// Get the IP options data from the request data
if (PPPGetIPOptionFromLCP(&o, req->Lcp))
{
PPP_IPOPTION res;
IP subnet;
IP gw;
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;
}
if (p->UseStaticIPAddress)
{
if (p->DhcpIpInformTried == false)
{
// Get additional information such as the subnet mask from the DHCP server
DHCP_OPTION_LIST cao;
IP client_ip;
IP subnet;
IP zero;
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");
PPPLog(p, "LP_DHCP_INFORM_NG");
}
IPCSetIPv4Parameters(p->Ipc, &zero, &zero, &zero, NULL);
}
}
else
{
// Get an IP address from a DHCP server
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 = 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");
PPPLog(p, "LP_DHCP_REQUEST_NG");
}
}
}
if (IsValidUnicastIPAddressUINT4(p->ClientAddressOption.ClientAddress) &&
p->ClientAddressOption.SubnetMask != 0)
{
// 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);
}
PPPSetIPOptionToLCP(&res, req->Lcp, true);
}
else
{
// Failed to determine the address
Debug("IP Address Determination Failed.\n");
Zero(&res, sizeof(res));
PPPSetIPOptionToLCP(&res, req->Lcp, true);
}
}
}
// Assemble the LCP response packet based on the results
for (i = 0;i < LIST_NUM(req->Lcp->OptionList);i++)
{
PPP_OPTION *t = LIST_DATA(req->Lcp->OptionList, i);
if (t->IsSupported == false)
{
num_not_supported++;
}
if (t->IsAccepted == false)
{
num_not_accepted++;
}
}
// Create a PPP response packet
ret = ZeroMalloc(sizeof(PPP_PACKET));
ret->IsControl = true;
ret->Protocol = req->Protocol;
if (no_return_option_list == false)
{
// Response by attaching an optional list
if (num_not_supported >= 1)
{
// Return a Reject if there are unsupported parameters
ret->Lcp = NewPPPLCP(PPP_LCP_CODE_REJECT, req->Lcp->Id);
for (i = 0;i < LIST_NUM(req->Lcp->OptionList);i++)
{
PPP_OPTION *t = LIST_DATA(req->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));
}
}
}
else if (num_not_accepted >= 1)
{
// Return a NAK if there are any unacceptable parameter
// even that all parameters are supported
ret->Lcp = NewPPPLCP(PPP_LCP_CODE_NAK, req->Lcp->Id);
for (i = 0;i < LIST_NUM(req->Lcp->OptionList);i++)
{
PPP_OPTION *t = LIST_DATA(req->Lcp->OptionList, i);
if (t->IsAccepted == false)
{
// Replace the original option value with an acceptable value
Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->AltData, t->AltDataSize));
}
}
}
else
{
// Return an ACK if all parameters are accepted
ret->Lcp = NewPPPLCP(PPP_LCP_CODE_ACK, req->Lcp->Id);
for (i = 0;i < LIST_NUM(req->Lcp->OptionList);i++)
{
PPP_OPTION *t = LIST_DATA(req->Lcp->OptionList, i);
// Attach the original option value as is
Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize));
}
if (req->Protocol == PPP_PROTOCOL_LCP)
{
p->ClientLCPOptionDetermined = true;
}
}
}
else
{
// Response without attaching a list of options
ret->Lcp = NewPPPLCP(return_code, req->Lcp->Id);
if (lcp_ret_data != NULL && lcp_ret_data->Size >= 1)
{
ret->Lcp->Data = Clone(lcp_ret_data->Buf, lcp_ret_data->Size);
ret->Lcp->DataSize = lcp_ret_data->Size;
}
}
if (lcp_ret_data != NULL)
{
FreeBuf(lcp_ret_data);
}
return ret;
}
// 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 = GetOptionValue(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);
opt2->IsAccepted = opt2->IsSupported = true;
Add(c->OptionList, opt2);
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 = GetOptionValue(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;
}
// Process corresponding to the incoming request while receiving a PPP packet corresponding to the transmitted request.
// (If req == NULL, process on that protocol while the protocol specified in expected_protocol have received.
//If other protocols has arrived, without further processing, and then store that packet in the session context once,
// return NULL by setting the received_protocol.)
PPP_PACKET *PPPRecvResponsePacket(PPP_SESSION *p, PPP_PACKET *req, USHORT expected_protocol, USHORT *received_protocol, bool finish_when_all_lcp_acked,
bool return_mschapv2_response_with_no_processing)
{
UINT64 giveup_tick = Tick64() + (UINT64)PPP_PACKET_RECV_TIMEOUT;
UINT64 next_resend = Tick64() + (UINT64)PPP_PACKET_RESEND_INTERVAL;
PPP_PACKET *ret = NULL;
USHORT tmp_us = 0;
// Validate arguments
if (p == NULL || req != NULL && req->Lcp == NULL)
{
return NULL;
}
if (received_protocol == NULL)
{
received_protocol = &tmp_us;
}
if (req != NULL)
{
expected_protocol = req->Protocol;
}
*received_protocol = 0;
// Receive the next packet (Retransmission repeatedly the last packet until the reception is completed)
while (true)
{
UINT64 now = Tick64();
UINT interval;
if (IsTubeConnected(p->TubeRecv) == false)
{
return NULL;
}
while (true)
{
PPP_PACKET *pp;
PPP_PACKET *response;
if (p->LastStoredPacket != NULL)
{
pp = p->LastStoredPacket;
p->LastStoredPacket = NULL;
}
else
{
pp = PPPRecvPacketWithLowLayerProcessing(p, true);
}
if (pp == NULL)
{
break;
}
if (req != NULL)
{
// Determine whether the packet is corresponding to the request that was sent at the last
if (pp->IsControl && pp->Protocol == req->Protocol && pp->Lcp->Id == req->Lcp->Id &&
PPP_CODE_IS_RESPONSE(pp->Protocol, pp->Lcp->Code))
{
return pp;
}
if (return_mschapv2_response_with_no_processing)
{
// For the double-MSCHAPv2 technique
if (pp->IsControl && pp->Protocol == req->Protocol && pp->Lcp->Id == req->Lcp->Id &&
pp->Protocol == PPP_PROTOCOL_CHAP && PPP_PAP_CODE_IS_RESPONSE(pp->Lcp->Code))
{
return pp;
}
}
}
// Return a response immediately without processing if a protocol other than the expected received
if ((pp->IsControl && pp->Protocol != expected_protocol) || pp->IsControl == false)
{
if (PPP_IS_SUPPORTED_PROTOCOL(pp->Protocol))
{
// This is another supported protocol
// Store this packet
PPPStoreLastPacket(p, pp);
*received_protocol = pp->Protocol;
return NULL;
}
else
{
// Unsupported protocol
Debug("Unsupported Protocol: 0x%x\n", pp->Protocol);
FreePPPPacket(pp);
return NULL;
}
}
if (pp->IsControl && PPP_CODE_IS_REQUEST(pp->Protocol, pp->Lcp->Code))
{
// Record current resend because next steps may take a while
UINT64 currentresend = next_resend - now;
// Process when the received packet is a request packet
response = PPPProcessRequestPacket(p, pp);
// Increase next resend because this may have taken a while
next_resend = Tick64() + currentresend;
FreePPPPacket(pp);
if (response == NULL)
{
return NULL;
}
else
{
bool is_pap_and_disconnect_now = false;
bool is_chap_and_disconnect_now = false;
if (PPPSendPacket(p, response) == false)
{
FreePPPPacket(response);
return NULL;
}
if (response->Protocol == PPP_PROTOCOL_PAP && response->IsControl &&
response->Lcp->Code != PPP_PAP_CODE_ACK)
{
is_pap_and_disconnect_now = true;
}
if (response->Protocol == PPP_PROTOCOL_CHAP && response->IsControl &&
response->Lcp->Code == PPP_CHAP_CODE_FAILURE)
{
is_chap_and_disconnect_now = true;
}
FreePPPPacket(response);
if (is_pap_and_disconnect_now)
{
// Disconnect immediately if user authentication fails at least once in the PAP authentication protocol
Debug("Disconnecting because PAP failed.\n");
SleepThread(300);
return NULL;
}
if (is_chap_and_disconnect_now)
{
// Disconnect immediately if it fails to user authentication at least once in the CHAP authentication protocol
Debug("Disconnecting because CHAP failed.\n");
SleepThread(300);
return NULL;
}
}
}
else
{
// Ignore in the case of the other packets
FreePPPPacket(pp);
}
}
// Packet retransmission
if (req != NULL)
{
if (now >= next_resend)
{
next_resend = now + PPP_PACKET_RESEND_INTERVAL;
if (PPPSendPacket(p, req) == false)
{
return NULL;
}
}
}
if (req == NULL)
{
giveup_tick = now + (UINT64)PPP_PACKET_RECV_TIMEOUT;
}
// Time-out decision
if (now >= giveup_tick)
{
PPPLog(p, "LP_CONTROL_TIMEOUT");
return NULL;
}
// Wait
if (req != NULL)
{
interval = MIN((UINT)(giveup_tick - now), (UINT)(next_resend - now));
}
else
{
interval = (UINT)(giveup_tick - now);
}
if (finish_when_all_lcp_acked && p->ClientLCPOptionDetermined)
{
return NULL;
}
Wait(p->TubeRecv->Event, interval);
}
}
// Store the last packet in the session (to be read the next time)
void PPPStoreLastPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
// Validate arguments
if (p == NULL)
{
return;
}
if (p->LastStoredPacket != NULL)
{
FreePPPPacket(p->LastStoredPacket);
}
p->LastStoredPacket = pp;
}
// Receive a PPP communication packet
PPP_PACKET *PPPRecvPacketForCommunication(PPP_SESSION *p)
{
// Validate arguments
if (p == NULL)
{
return NULL;
}
if (p->LastStoredPacket != NULL)
{
PPP_PACKET *pp = p->LastStoredPacket;
p->LastStoredPacket = NULL;
return pp;
}
return PPPRecvPacketWithLowLayerProcessing(p, true);
}
// Receive a PPP packet (Also performs low layer processing)
PPP_PACKET *PPPRecvPacketWithLowLayerProcessing(PPP_SESSION *p, bool async)
{
PPP_PACKET *pp = NULL;
// Validate arguments
if (p == NULL)
{
return NULL;
}
LABEL_LOOP:
pp = PPPRecvPacket(p, async);
if (pp == NULL)
{
return NULL;
}
if (PPP_IS_SUPPORTED_PROTOCOL(pp->Protocol) == false)
{
// Unsupported algorithm
PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));
BUF *buf;
UCHAR c;
USHORT us;
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;
FreePPPPacket(pp);
FreeBuf(buf);
if (PPPSendPacket(p, pp2) == false)
{
FreePPPPacket(pp2);
return NULL;
}
FreePPPPacket(pp2);
goto LABEL_LOOP;
}
if (pp->IsControl && pp->Protocol == PPP_PROTOCOL_LCP)
{
if (pp->Lcp->Code == PPP_LCP_CODE_ECHO_REQUEST)
{
// 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, pp->Lcp->Id);
pp2->Lcp->Data = Clone(pp->Lcp->Data, pp->Lcp->DataSize);
pp2->Lcp->DataSize = pp->Lcp->DataSize;
FreePPPPacket(pp);
if (PPPSendPacket(p, pp2) == false)
{
FreePPPPacket(pp2);
return NULL;
}
FreePPPPacket(pp2);
goto LABEL_LOOP;
}
else if (pp->Lcp->Code == PPP_LCP_CODE_ECHO_RESPONSE)
{
// Ignore the Echo response packet
FreePPPPacket(pp);
goto LABEL_LOOP;
}
else if (pp->Lcp->Code == PPP_LCP_CODE_DROP)
{
// Ignore the Drop packet
FreePPPPacket(pp);
goto LABEL_LOOP;
}
else if (pp->Lcp->Code == PPP_LCP_CODE_IDENTIFICATION)
{
// Ignore the Identification packet
FreePPPPacket(pp);
WHERE;
goto LABEL_LOOP;
}
else if (pp->Lcp->Code == PPP_LCP_CODE_TERMINATE_REQ)
{
// Return the Terminate ACK If a Terminate Request has been received
PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));
pp2->IsControl = true;
pp2->Protocol = PPP_PROTOCOL_LCP;
pp2->Lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_ACK, pp->Lcp->Id);
pp2->Lcp->Data = Clone(pp->Lcp->Data, pp->Lcp->DataSize);
pp2->Lcp->DataSize = pp->Lcp->DataSize;
p->IsTerminateReceived = true;
FreePPPPacket(pp);
if (PPPSendPacket(p, pp2) == false)
{
FreePPPPacket(pp2);
return NULL;
}
SleepThread(100);
FreePPPPacket(pp2);
goto LABEL_LOOP;
}
}
return pp;
}
// 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, PPP_PACKET_RECV_TIMEOUT);
}
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;
}
// Send the PPP packet
bool PPPSendPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
return PPPSendPacketEx(p, pp, false);
}
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;
}
// 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;
}
// 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;
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->IsControl = true;
}
pp->Data = Clone(buf, size);
pp->DataSize = size;
if (pp->IsControl)
{
pp->Lcp = ParseLCP(pp->Protocol, pp->Data, pp->DataSize);
if (pp->Lcp == NULL)
{
goto LABEL_ERROR;
}
}
return pp;
LABEL_ERROR:
FreePPPPacket(pp);
return NULL;
}
// 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;
}
// Analyse the LCP data
PPP_LCP *ParseLCP(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
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));
o.Type = buf[0];
buf++;
len--;
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;
}
// 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);
}
}
// Release the PPP session
void FreePPPSession(PPP_SESSION *p)
{
// Validate arguments
if (p == NULL)
{
return;
}
if (p->TubeRecv != NULL)
{
// Record the PPP disconnect reason code for L2TP
p->TubeRecv->IntParam1 = p->DisconnectCauseCode;
p->TubeRecv->IntParam2 = p->DisconnectCauseDirection;
}
FreeTubeFlushList(p->FlushList);
TubeDisconnect(p->TubeRecv);
TubeDisconnect(p->TubeSend);
ReleaseCedar(p->Cedar);
ReleaseTube(p->TubeRecv);
ReleaseTube(p->TubeSend);
PPPStoreLastPacket(p, NULL);
if (p->Ipc != NULL)
{
FreeIPC(p->Ipc);
}
PPPFreeEapClient(p);
Free(p);
}
// Free the associated EAP client
void PPPFreeEapClient(PPP_SESSION *p)
{
if (p == NULL)
{
return;
}
if (p->EapClient != NULL)
{
ReleaseEapClient(p->EapClient);
p->EapClient = NULL;
}
}
// Get the option value
PPP_OPTION *GetOptionValue(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;
}
// 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;
}
// 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);
}
// Create a new PPP session
THREAD *NewPPPSession(CEDAR *cedar, IP *client_ip, UINT client_port, IP *server_ip, UINT server_port, TUBE *send_tube, TUBE *recv_tube, char *postfix, char *client_software_name, char *client_hostname, char *crypt_name, UINT adjust_mss)
{
PPP_SESSION *p;
THREAD *t;
// Validate arguments
if (cedar == NULL || client_ip == NULL || server_ip == NULL || send_tube == NULL || recv_tube == NULL)
{
return NULL;
}
if (IsEmptyStr(postfix))
{
postfix = "PPP";
}
if (IsEmptyStr(crypt_name))
{
crypt_name = "";
}
if (IsEmptyStr(client_software_name))
{
client_software_name = "PPP VPN Client";
}
// Data structure initialization
p = ZeroMalloc(sizeof(PPP_SESSION));
p->EnableMSCHAPv2 = true;
p->AuthProtocol = PPP_PROTOCOL_PAP;
p->MsChapV2_ErrorCode = 691;
p->Cedar = cedar;
AddRef(cedar->ref);
p->AdjustMss = adjust_mss;
StrCpy(p->CryptName, sizeof(p->CryptName), crypt_name);
Copy(&p->ClientIP, client_ip, sizeof(IP));
p->ClientPort = client_port;
Copy(&p->ServerIP, server_ip, sizeof(IP));
p->ServerPort = server_port;
p->TubeRecv = recv_tube;
p->TubeSend = send_tube;
AddRef(p->TubeRecv->Ref);
AddRef(p->TubeSend->Ref);
StrCpy(p->Postfix, sizeof(p->Postfix), postfix);
StrCpy(p->ClientSoftwareName, sizeof(p->ClientSoftwareName), client_software_name);
if (IsEmptyStr(client_hostname))
{
IPToStr(p->ClientHostname, sizeof(p->ClientHostname), client_ip);
}
else
{
StrCpy(p->ClientHostname, sizeof(p->ClientHostname), client_hostname);
}
p->FlushList = NewTubeFlushList();
// Thread creation
t = NewThread(PPPThread, p);
return t;
}
// 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 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;
}