// SoftEther VPN Source Code - Developer Edition Master Branch
// Cedar Communication Module


// Proto_PPP.c
// PPP protocol stack

#include "Proto_PPP.h"

#include "Account.h"
#include "Cedar.h"
#include "Connection.h"
#include "Hub.h"
#include "IPC.h"
#include "Logging.h"
#include "Radius.h"
#include "Server.h"

#include "Mayaqua/Memory.h"
#include "Mayaqua/Object.h"
#include "Mayaqua/Str.h"
#include "Mayaqua/Tick64.h"

// PPP main thread
void PPPThread(THREAD *thread, void *param)
{
	PPP_SESSION *p = (PPP_SESSION *)param;
	UINT i;
	USHORT next_protocol = 0;
	bool ret = false;
	char ipstr1[128], ipstr2[128];
	bool authReqSent = false;

	// Validate arguments
	if (thread == NULL || param == NULL)
	{
		return;
	}

	// Initialize

	Debug("PPP Initialize");

	PPPSetStatus(p, PPP_STATUS_CONNECTED);

	p->Eap_Protocol = PPP_UNSPECIFIED;

	p->Mru1 = p->Mru2 = PPP_MRU_DEFAULT;
	p->RecvPacketList = NewList(NULL);
	p->SentReqPacketList = NewList(NULL);
	p->DelayedPackets = NewList(PPPDelayedPacketsComparator);

	p->UseEapRadius = CedarIsThereAnyEapEnabledRadiusConfig(p->Cedar);

	Debug("UseEapRadius = 0x%x\n", p->UseEapRadius);

	//// Link establishment phase

	Debug("PPP Link establishment phase\n");

	IPToStr(ipstr1, sizeof(ipstr1), &p->ClientIP);
	IPToStr(ipstr2, sizeof(ipstr2), &p->ServerIP);
	PPPLog(p, "LP_CONNECTED", p->Postfix, ipstr1, p->ClientHostname, p->ClientPort, ipstr2, p->ServerPort,
	       p->ClientSoftwareName, p->AdjustMss);

	// We need that so we don't time out on connection immediately
	p->LastRecvTime = Tick64();

	Debug("PPP starting main dataloop\n");

	// Dataloop active if the receiving tube is still connected
	while (true)
	{
		PPP_LCP *lcp;
		bool receivedPacketProcessed = false;
		TUBE *tubes[2];
		UINT r;
		UINT64 now = Tick64();

		PPPGetNextPacket(p);

		if (p->CurrentPacket != NULL)
		{
			// First we process any possible unsupported packets
			receivedPacketProcessed = PPPRejectUnsupportedPacket(p, p->CurrentPacket);

			// Now do some basic processing
			if (receivedPacketProcessed == false && p->CurrentPacket->IsControl && p->CurrentPacket->Protocol == PPP_PROTOCOL_LCP)
			{
				if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_ECHO_REQUEST && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
				{
					// Immediately return the echo response to the echo request
					PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));

					pp2->IsControl = true;
					pp2->Protocol = PPP_PROTOCOL_LCP;
					pp2->Lcp = NewPPPLCP(PPP_LCP_CODE_ECHO_RESPONSE, p->CurrentPacket->Lcp->Id);
					pp2->Lcp->Data = Clone(p->CurrentPacket->Lcp->Data, p->CurrentPacket->Lcp->DataSize);
					pp2->Lcp->DataSize = p->CurrentPacket->Lcp->DataSize;

					if (PPPSendPacketAndFree(p, pp2) == false)
					{
						PPPSetStatus(p, PPP_STATUS_FAIL);
						WHERE;
					}

					receivedPacketProcessed = true;
				}
				else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_ECHO_RESPONSE && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
				{
					receivedPacketProcessed = true;
					// Ignore the Echo response packet
				}
				else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_DROP && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
				{
					receivedPacketProcessed = true;
					// Ignore the Drop packet
				}
				else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_IDENTIFICATION && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
				{
					receivedPacketProcessed = true;
					// Ignore the Identification packet
					WHERE;
				}
				else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_TERMINATE_REQ)
				{
					PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));;
					receivedPacketProcessed = true;
					// Return the Terminate ACK If a Terminate Request has been received

					pp2->IsControl = true;
					pp2->Protocol = PPP_PROTOCOL_LCP;
					pp2->Lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_ACK, p->CurrentPacket->Lcp->Id);
					pp2->Lcp->Data = Clone(p->CurrentPacket->Lcp->Data, p->CurrentPacket->Lcp->DataSize);
					pp2->Lcp->DataSize = p->CurrentPacket->Lcp->DataSize;

					p->IsTerminateReceived = true;

					if (PPPSendPacketAndFree(p, pp2) == false)
					{
						PPPSetStatus(p, PPP_STATUS_FAIL);
						WHERE;
					}
					else
					{
						SleepThread(100);
						PPPSetStatus(p, PPP_STATUS_CLOSED);
					}
				}
				else if (p->CurrentPacket->Lcp->Code == PPP_LCP_CODE_TERMINATE_ACK)
				{
					PPPSetStatus(p, PPP_STATUS_CLOSED);
				}
			}

			// Process responses
			if (receivedPacketProcessed == false && p->CurrentPacket != NULL && p->CurrentPacket->IsControl && PPP_CODE_IS_RESPONSE(p->CurrentPacket->Protocol, p->CurrentPacket->Lcp->Code) && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
			{
				PPP_PACKET *request = NULL;
				// Removing from resend list
				for (i = 0; i < LIST_NUM(p->SentReqPacketList); i++)
				{
					PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i);

					if (t->Id == p->CurrentPacket->Lcp->Id)
					{
						request = t->Packet;
						Delete(p->SentReqPacketList, t);
						Free(t);
						break;
					}
				}
				PPPProcessResponsePacket(p, p->CurrentPacket, request);
				FreePPPPacket(request);
				receivedPacketProcessed = true;
			}

			// Process requests
			if (receivedPacketProcessed == false && p->CurrentPacket != NULL && p->CurrentPacket->IsControl && PPP_CODE_IS_REQUEST(p->CurrentPacket->Protocol, p->CurrentPacket->Lcp->Code) && PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false)
			{
				PPPProcessRequestPacket(p, p->CurrentPacket);
				receivedPacketProcessed = true;
			}

			// Process data packets, discarded before we got any links up
			if (receivedPacketProcessed == false && p->CurrentPacket != NULL && p->CurrentPacket->IsControl == false && p->PPPStatus == PPP_STATUS_NETWORK_LAYER && p->Ipc != NULL)
			{
				UINT64 timeBeforeLoop = Tick64();
				while (true)
				{
					UINT64 nowL;
					// Here client to server
					if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IP &&
					        IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_OPENED)
					{
						receivedPacketProcessed = true;
						IPCSendIPv4(p->Ipc, p->CurrentPacket->Data, p->CurrentPacket->DataSize);
					}
					else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IP)
					{
						Debug("Got IPv4 packet before IPv4 ready!\n");
					}
					else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IPV6 &&
					         IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_OPENED)
					{
						receivedPacketProcessed = true;
						IPCIPv6Send(p->Ipc, p->CurrentPacket->Data, p->CurrentPacket->DataSize);
					}
					else if (p->CurrentPacket->Protocol == PPP_PROTOCOL_IPV6)
					{
						Debug("Got IPv6 packet before IPv6 ready!\n");
					}

					// Let's break out of the loop once in a while so we don't get stuck here endlessly
					nowL = Tick64();
					if (nowL > timeBeforeLoop + PPP_PACKET_RESEND_INTERVAL)
					{
						break;
					}

					PPPGetNextPacket(p);
					if (p->CurrentPacket == NULL)
					{
						break;
					}
					// Making sure we got a correctly parsed packet by rejecting all invalid ones
					if (PPPRejectUnsupportedPacket(p, p->CurrentPacket))
					{
						break;
					}
					if (p->CurrentPacket->IsControl || p->PPPStatus != PPP_STATUS_NETWORK_LAYER || p->Ipc == NULL)
					{
						PPPAddNextPacket(p, p->CurrentPacket, 0);
						p->CurrentPacket = NULL;
						break;
					}
				}
			}

			if (receivedPacketProcessed == false && p->CurrentPacket != NULL)
			{
				Debug("Unprocessed and unrejected packet, protocol = 0x%x\n", p->CurrentPacket->Protocol);
			}
		}
		else if (p->PPPStatus == PPP_STATUS_BEFORE_AUTH && p->AuthProtocol == PPP_PROTOCOL_EAP)
		{
			PPP_LCP *lcpEap;
			PPP_EAP *eapPacket;
			UCHAR *welcomeMessage = "Welcome to the SoftEther VPN server!";
			UCHAR flags = PPP_EAP_TLS_FLAG_NONE;
			// We got to start EAP when we got no LCP packets from the client on previous iteration
			// which means we parsed all the client requests and responses

			switch (p->Eap_Protocol)
			{
			case PPP_EAP_TYPE_TLS:
				// Sending TLS Start...
				flags |= PPP_EAP_TLS_FLAG_SSLSTARTED;
				p->Eap_PacketId = p->NextId++;
				lcpEap = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags);
				PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
				if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcpEap) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					break;
				}
				break;
			case PPP_EAP_TYPE_MSCHAPV2:
				// Sending challenge
				p->Eap_PacketId = p->NextId; // Do not increase NextId so that MSCHAPv2 could use the same id
				lcp = BuildMSCHAP2ChallengePacket(p);
				BUF *b = BuildLCPData(lcp);
				FreePPPLCP(lcp);
				lcpEap = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId, PPP_EAP_TYPE_MSCHAPV2, b->Size);
				eapPacket = lcpEap->Data;
				Copy(eapPacket->Data, b->Buf, b->Size);
				FreeBuf(b);
				PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
				if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcpEap) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					break;
				}
				break;
			case PPP_EAP_TYPE_IDENTITY:
			default: // We treat the unspecified protocol as the IDENTITY protocol
				p->Eap_Protocol = PPP_EAP_TYPE_IDENTITY;
				p->Eap_PacketId = p->NextId++;
				lcpEap = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId, PPP_EAP_TYPE_IDENTITY, StrLen(welcomeMessage) + 1);
				eapPacket = lcpEap->Data;
				Copy(eapPacket->Data, welcomeMessage, StrLen(welcomeMessage));
				PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
				PPPFreeEapClient(p);
				if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcpEap) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					break;
				}
				break;
			}
		}
		else if (p->PPPStatus == PPP_STATUS_BEFORE_AUTH && p->AuthProtocol == PPP_PROTOCOL_CHAP)
		{
			// We got to start CHAP when we got no LCP packets from the client on previous iteration
			// which means we parsed all the client requests and responses
			Debug("Starting PPP Authentication phase MS-CHAP v2\n");

			lcp = BuildMSCHAP2ChallengePacket(p);
			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_CHAP, lcp) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
			}

			PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
		}

		if (p->PPPStatus == PPP_STATUS_CONNECTED && authReqSent == false)
		{
			// EAP code
			PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
			USHORT eap_code = Endian16(PPP_LCP_AUTH_EAP);

			Debug("Request EAP\n");
			Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &eap_code, sizeof(eap_code)));
			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
			}
			authReqSent = true;
		}

		if (p->PPPStatus == PPP_STATUS_AUTHENTICATING)
		{
			//Debug("Tick waiting for auth...\n");
		}

		if (p->PPPStatus == PPP_STATUS_AUTH_FAIL)
		{
			Debug("PPP auth failed, giving up\n");
			p->DisconnectCauseCode = 15;
			p->DisconnectCauseDirection = 1;
			PPPSetStatus(p, PPP_STATUS_CLOSING);
		}

		if (p->PPPStatus == PPP_STATUS_NETWORK_LAYER)
		{
			UINT64 timeBeforeLoop;
			if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_OPENED)
			{
				if (p->DhcpAllocated)
				{
					if (now >= p->DhcpNextRenewTime)
					{
						IP ip;

						// DHCP renewal procedure
						p->DhcpNextRenewTime = now + p->DhcpRenewInterval;

						UINTToIP(&ip, p->ClientAddressOption.ServerAddress);

						IPCDhcpRenewIP(p->Ipc, &ip);
					}
				}
			}

			IPCProcessL3Events(p->Ipc);

			timeBeforeLoop = Tick64();

			while (true)
			{
				UINT64 nowL;
				bool no4packets = false;
				bool no6packets = false;
				if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_OPENED)
				{
					BLOCK *b = IPCRecvIPv4(p->Ipc);
					if (b == NULL)
					{
						no4packets = true;
					}
					else
					{
						PPP_PACKET *pp;
						PPP_PACKET tmp;

						// Since receiving the IP packet, send it to the client by PPP
						pp = &tmp;
						pp->IsControl = false;
						pp->Protocol = PPP_PROTOCOL_IP;
						pp->Lcp = NULL;
						pp->Data = b->Buf;
						pp->DataSize = b->Size;

						PPPSendPacketEx(p, pp, true);

						FreePPPPacketEx(pp, true);
						Free(b); // Not FreeBlock because freed in FreePPPPacketEx
					}
				}
				else
				{
					no4packets = true;
				}

				if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_OPENED)
				{
					BLOCK *b = IPCIPv6Recv(p->Ipc);
					if (b == NULL)
					{
						no6packets = true;
					}
					else
					{
						PPP_PACKET *pp;
						PPP_PACKET tmp;

						// Since receiving the IP packet, send it to the client by PPP
						pp = &tmp;
						pp->IsControl = false;
						pp->Protocol = PPP_PROTOCOL_IPV6;
						pp->Lcp = NULL;
						pp->Data = b->Buf;
						pp->DataSize = b->Size;

						PPPSendPacketEx(p, pp, true);

						FreePPPPacketEx(pp, true);
						Free(b); // Not FreeBlock because freed in FreePPPPacketEx
					}
				}
				else
				{
					no6packets = true;
				}

				// Let's break out of the loop once in a while so we don't get stuck here endlessly
				nowL = Tick64();
				if (nowL > timeBeforeLoop + PPP_PACKET_RESEND_INTERVAL || (no4packets && no6packets))
				{
					break;
				}
			}

			FlushTubeFlushList(p->FlushList);
		}

		if (p->PPPStatus == PPP_STATUS_AUTH_SUCCESS)
		{
			Debug("PPP auth success, ready for network layer on next tick\n");
			p->AuthOk = true;
			PPPSetStatus(p, PPP_STATUS_NETWORK_LAYER);
		}

		if ((p->PPPStatus == PPP_STATUS_CLOSING || p->PPPStatus == PPP_STATUS_FAIL) && IsTubeConnected(p->TubeRecv) && IsTubeConnected(p->TubeSend))
		{
			Debug("Trying to cleanly close the connection, status = 0x%x\n", p->PPPStatus);
			PPPSetStatus(p, PPP_STATUS_CLOSING_WAIT);
			lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_REQ, 0);
			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, lcp) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
			}
		}

		if (PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false || p->PPPStatus == PPP_STATUS_CLOSING_WAIT)
		{
			PPPProcessRetransmissions(p);
			PPPSendEchoRequest(p);
		}

		tubes[0] = p->TubeRecv;

		if (p->PPPStatus == PPP_STATUS_NETWORK_LAYER && p->Ipc != NULL && IsIPCConnected(p->Ipc))
		{
			r = GetNextIntervalForInterrupt(p->Ipc->Interrupt);
			tubes[1] = p->Ipc->Sock->RecvTube;
			WaitForTubes(tubes, 2, MIN(r, PPP_PACKET_RESEND_INTERVAL));
		}
		else
		{
			WaitForTubes(tubes, 1, 300); // Increasing timeout to make the ticks a bit slower
		}

		if (IsTubeConnected(p->TubeRecv) == false || IsTubeConnected(p->TubeSend) == false)
		{
			// Higher-level protocol is disconnected
			PPPLog(p, "LP_UPPER_PROTOCOL_DISCONNECTED", p->Postfix);
			break;
		}

		if (IsIPCConnected(p->Ipc) == false && p->PPPStatus == PPP_STATUS_NETWORK_LAYER)
		{
			// IPC VPN session is disconnected
			PPPLog(p, "LP_VPN_SESSION_TERMINATED");
			break;
		}

		// Time-out inspection
		if ((p->LastRecvTime + (UINT64)p->DataTimeout) <= now)
		{
			// Communication time-out occurs
			PPPLog(p, "LP_DATA_TIMEOUT");
			break;
		}

		// Maximum PPP session time of the user reached inspection
		if (p->UserConnectionTick != 0 && p->UserConnectionTimeout != 0 &&
		        p->UserConnectionTick + p->UserConnectionTimeout <= now)
		{
			// User connection time-out occurs
			PPPLog(p, "LP_USER_TIMEOUT");
			break;
		}

		// Terminate if the PPP disconnected
		if (p->IsTerminateReceived)
		{
			PPPLog(p, "LP_NORMAL_TERMINATE");
			break;
		}

		if (p->PPPStatus == PPP_STATUS_FAIL || p->PPPStatus == PPP_STATUS_CLOSED)
		{
			Debug("Exiting main dataloop, status = 0x%x\n", p->PPPStatus);
			break;
		}
	}

	Debug("Exited main dataloop, status = 0x%x\n", p->PPPStatus);

	if (p->PPPStatus != PPP_STATUS_FAIL)
	{
		IP ip;
		char tmp[MAX_SIZE];

		// Disconnected normally
		PPPLog(p, "LP_DISCONNECTED");

		if (p != NULL && p->DhcpAllocated && IsIPCConnected(p->Ipc) && p->ClientAddressOption.ServerAddress != 0)
		{
			// If any address is assigned from the DHCP, release it
			UINTToIP(&ip, p->ClientAddressOption.ServerAddress);

			IPToStr(tmp, sizeof(tmp), &ip);
			Debug("Releasing IP Address from DHCP Server %s...\n", tmp);

			IPCDhcpFreeIP(p->Ipc, &ip);
			IPCProcessL3Events(p->Ipc);

			SleepThread(300);
		}
	}
	else
	{
		PPPLog(p, "LP_DISCONNECTED_ABNORMAL");
	}

	FreePPPSession(p);
	Debug("PPP Session ended correctly\n");
}


// Entry point

// Create a new PPP session
THREAD *NewPPPSession(CEDAR *cedar, IP *client_ip, UINT client_port, IP *server_ip, UINT server_port, TUBE *send_tube, TUBE *recv_tube, char *postfix, char *client_software_name, char *client_hostname, char *crypt_name, UINT adjust_mss)
{
	PPP_SESSION *p;
	THREAD *t;
	// Validate arguments
	if (cedar == NULL || client_ip == NULL || server_ip == NULL || send_tube == NULL || recv_tube == NULL)
	{
		return NULL;
	}
	if (IsEmptyStr(postfix))
	{
		postfix = "PPP";
	}
	if (IsEmptyStr(crypt_name))
	{
		crypt_name = "";
	}
	if (IsEmptyStr(client_software_name))
	{
		client_software_name = "PPP VPN Client";
	}

	// Data structure initialization
	p = ZeroMalloc(sizeof(PPP_SESSION));

	p->EnableMSCHAPv2 = true;
	p->AuthProtocol = PPP_UNSPECIFIED;
	p->MsChapV2_ErrorCode = 691;
	p->EapClient = NULL;
	Zero(&p->Eap_Identity, sizeof(p->Eap_Identity));
	p->Eap_TlsCtx.DisableTls13 = false;
	p->Eap_TlsCtx.Tls13SessionTicketsCount = 2; // Default count as per hardcoded in OpenSSL

	p->DataTimeout = PPP_DATA_TIMEOUT;
	p->PacketRecvTimeout = PPP_PACKET_RECV_TIMEOUT;
	p->UserConnectionTimeout = 0;

	p->Cedar = cedar;
	AddRef(cedar->ref);

	p->AdjustMss = adjust_mss;

	StrCpy(p->CryptName, sizeof(p->CryptName), crypt_name);

	Copy(&p->ClientIP, client_ip, sizeof(IP));
	p->ClientPort = client_port;

	Copy(&p->ServerIP, server_ip, sizeof(IP));
	p->ServerPort = server_port;

	p->TubeRecv = recv_tube;
	p->TubeSend = send_tube;

	AddRef(p->TubeRecv->Ref);
	AddRef(p->TubeSend->Ref);

	StrCpy(p->Postfix, sizeof(p->Postfix), postfix);
	StrCpy(p->ClientSoftwareName, sizeof(p->ClientSoftwareName), client_software_name);

	if (IsEmptyStr(client_hostname))
	{
		IPToStr(p->ClientHostname, sizeof(p->ClientHostname), client_ip);
	}
	else
	{
		StrCpy(p->ClientHostname, sizeof(p->ClientHostname), client_hostname);
	}

	p->FlushList = NewTubeFlushList();

	// Thread creation
	t = NewThread(PPPThread, p);

	return t;
}

// PPP processing functions

// Finds out if a packet is supported, if not - sends a notification to the peer
// result: false - supported, true - unsupported
bool PPPRejectUnsupportedPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	return PPPRejectUnsupportedPacketEx(p, pp, false);
}
bool PPPRejectUnsupportedPacketEx(PPP_SESSION *p, PPP_PACKET *pp, bool force)
{
	bool result = false;
	if (p == NULL || pp == NULL)
	{
		return false;
	}

	if (PPP_IS_SUPPORTED_PROTOCOL(pp->Protocol) == false || force == true)
	{
		// Unsupported algorithm
		PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));
		BUF *buf;
		UCHAR c;
		USHORT us;

		Debug("Rejecting PPP protocol = 0x%x\n", pp->Protocol);
		result = true;

		pp2->Protocol = PPP_PROTOCOL_LCP;
		pp2->IsControl = false;

		buf = NewBuf();

		// Code
		c = PPP_LCP_CODE_PROTOCOL_REJECT;
		WriteBuf(buf, &c, 1);

		// ID
		c = p->NextId++;
		WriteBuf(buf, &c, 1);

		// Length
		us = Endian16(pp->DataSize + 6);
		WriteBuf(buf, &us, 2);

		// Rejected Protocol
		us = Endian16(pp->Protocol);
		WriteBuf(buf, &us, 2);

		// Packet Data
		WriteBuf(buf, pp->Data, pp->DataSize);

		pp2->Data = Clone(buf->Buf, buf->Size);
		pp2->DataSize = buf->Size;

		FreeBuf(buf);

		if (PPPSendPacketAndFree(p, pp2) == false)
		{
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
		}
	}
	return result;
}

// Do the retransmissions if needed
bool PPPProcessRetransmissions(PPP_SESSION *p)
{
	INT64 i = 0;
	UINT64 now = Tick64();
	UINT64 count;
	if (p->SentReqPacketList == NULL)
	{
		Debug("Somehow SentReqPacketList is NULL!\n");
		return false;
	}
	// Making it signed but expanding to 64 bits
	count = LIST_NUM(p->SentReqPacketList);
	if (count == 0)
	{
		return true;
	}
	for (i = count - 1; i >= 0; --i)
	{
		PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i);

		if (t->TimeoutTime <= now)
		{
			Debug("Timing out on resending control packet protocol = 0x%x\n", t->Packet->Protocol);
			Delete(p->SentReqPacketList, t);
			FreePPPPacket(t->Packet);
			Free(t);
		}
		else if (t->ResendTime <= now)
		{
			Debug("Resending control packet protocol = 0x%x\n", t->Packet->Protocol);
			if (PPPSendPacketEx(p, t->Packet, false) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
			t->ResendTime = now + PPP_PACKET_RESEND_INTERVAL;
		}
	}
	return true;
}

// Send the PPP Echo Request
bool PPPSendEchoRequest(PPP_SESSION *p)
{
	// Validate arguments
	if (p == NULL)
	{
		return false;
	}

	UINT64 now = Tick64();
	if (p->NextEchoSendTime == 0 || now >= p->NextEchoSendTime)
	{
		PPP_PACKET *pp;
		char echo_data[] = "\0\0\0\0Aho Baka Manuke";

		p->NextEchoSendTime = now + (UINT64)PPP_ECHO_SEND_INTERVAL;
		if (IsIPCConnected(p->Ipc))
		{
			AddInterrupt(p->Ipc->Interrupt, p->NextEchoSendTime);
		}

		pp = ZeroMalloc(sizeof(PPP_PACKET));
		pp->Protocol = PPP_PROTOCOL_LCP;
		pp->IsControl = true;
		pp->Lcp = NewPPPLCP(PPP_LCP_CODE_ECHO_REQUEST, 0);

		pp->Lcp->Data = Clone(echo_data, sizeof(echo_data));
		pp->Lcp->DataSize = sizeof(echo_data);

		if (PPPSendPacketAndFree(p, pp) == false)
		{
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}
		return true;
	}
	return false;
}

// Processes response packets
bool PPPProcessResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
	if (req == NULL)
	{
		Debug("We received a response for... What? We never sent this request, protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code);
		// Let's just discard this, as this was probably already parsed, and we just stumbled upon a resend
		return false;
	}

	switch (pp->Protocol)
	{
	case PPP_PROTOCOL_LCP:
		return PPPProcessLCPResponsePacket(p, pp, req);
		break;
	case PPP_PROTOCOL_PAP:
		Debug("Got a response PAP, which is invalid, we should get a request instead\n");
		PPPSetStatus(p, PPP_STATUS_FAIL);
		WHERE;
		return false;
		break;
	case PPP_PROTOCOL_CHAP:
		return PPPProcessCHAPResponsePacket(p, pp, req);
		break;
	case PPP_PROTOCOL_IPCP:
		return PPPProcessIPCPResponsePacket(p, pp, req);
		break;
	case PPP_PROTOCOL_IPV6CP:
		return PPPProcessIPv6CPResponsePacket(p, pp, req);
		break;
	case PPP_PROTOCOL_EAP:
		return PPPProcessEAPResponsePacket(p, pp, req);
		break;
	default:
		Debug("We received a response for an unsupported protocol??? Should be filtered out already! protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code);
		PPPSetStatus(p, PPP_STATUS_FAIL);
		WHERE;
		return false;
	}

	return false;
}

bool PPPProcessLCPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
	UINT i;
	bool isAccepted = PPP_LCP_CODE_IS_NEGATIVE(pp->Lcp->Code) == false;
	bool result = true;
	// MSCHAPv2 code
	UCHAR ms_chap_v2_code[3];
	WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
	ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;

	// We got one of rejects here, not NACKs
	if (isAccepted == false && pp->Lcp->Code == PPP_LCP_CODE_PROTOCOL_REJECT)
	{
		// If we receive a protocol reject before we finished authenticating
		// probably means the PPP client is not compatible anyway so we fail the connection
		if (p->PPPStatus != PPP_STATUS_NETWORK_LAYER)
		{
			USHORT *protocol = pp->Lcp->Data;
			Debug("Protocol 0x%x rejected before auth, probably unsupported client, failing connection\n", *protocol);
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}
		else
		{
			USHORT *protocol = pp->Lcp->Data;
			if (*protocol == PPP_PROTOCOL_IPCP || *protocol == PPP_PROTOCOL_IP)
			{
				IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_REJECTED);
			}
			if (*protocol == PPP_PROTOCOL_IPV6CP || *protocol == PPP_PROTOCOL_IPV6)
			{
				IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_REJECTED);
			}
		}
	}

	if (isAccepted == false && pp->Lcp->Code == PPP_LCP_CODE_CODE_REJECT)
	{
		PPPSetStatus(p, PPP_STATUS_FAIL);
		WHERE;
		return false;
	}

	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);
		PPP_OPTION *opt = NULL;

		switch (t->Type)
		{
		case PPP_LCP_OPTION_MRU:
			// MRU
			if (t->DataSize == sizeof(USHORT))
			{
				USHORT value = READ_USHORT(t->Data);
				if (isAccepted == false)
				{
					if (pp->Lcp->Code != PPP_LCP_CODE_NAK)
					{
						Debug("MRU setup failed, rejected");
						p->Mru1 = p->Mru2 = PPP_MRU_DEFAULT;
					}
					if (value < PPP_MRU_MIN || value > PPP_MRU_MAX)
					{
						Debug("Couldn't agree on an MRU! Breaking link... MRU = 0x%x\n", value);
						PPPSetStatus(p, PPP_STATUS_FAIL);
						WHERE;
						return false;
					}
					else
					{
						PPP_LCP *lcp = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
						Add(lcp->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &value, sizeof(USHORT)));
						if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, lcp) == false)
						{
							PPPSetStatus(p, PPP_STATUS_FAIL);
							WHERE;
							return false;
						}
						Debug("PPP: Server got %u as MRU from NACK, re-requesting\n", p->Mru2);
					}
				}
				else if (value < PPP_MRU_MIN || value > PPP_MRU_MAX)
				{
					Debug("The client somehow ACKed an invalid MRU, breaking link... MRU = 0x%x\n", value);
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					result = false;
				}
				else
				{
					p->Mru2 = value;
					Debug("PPP: Server set %u as MRU\n", p->Mru2);
				}
			}
			break;
		case PPP_LCP_OPTION_AUTH:
			opt = PPPGetOptionValue(req->Lcp, PPP_LCP_OPTION_AUTH);
			if (opt == NULL)
			{
				Debug("We got some weird response with option absent in request, wut? Disconnecting\n");
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
			if (opt->DataSize == sizeof(USHORT) && *((USHORT *)(opt->Data)) == Endian16(PPP_LCP_AUTH_EAP))
			{
				// Try to request MS-CHAPv2 then
				if (isAccepted == false)
				{
					UINT64 offer = 0;
					PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
					UCHAR ms_chap_v2_code[3];

					WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
					ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;

					Copy(&offer, ms_chap_v2_code, sizeof(ms_chap_v2_code));
					Debug("NACK proto with code = 0x%x, cypher = 0x%x, offered cypher = 0x%x\n", pp->Lcp->Code, *((USHORT *)(opt->Data)), offer);
					Debug("Request MSCHAPv2\n");
					Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &ms_chap_v2_code, sizeof(ms_chap_v2_code)));
					if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c) == false)
					{
						PPPSetStatus(p, PPP_STATUS_FAIL);
						WHERE;
						return false;
					}
				}
				else
				{
					p->AuthProtocol = PPP_PROTOCOL_EAP;
					Debug("Setting BEFORE_AUTH from ACK on LCP response parse on EAP accept\n");
					PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
				}
			}
			else if (opt->DataSize == sizeof(ms_chap_v2_code) && Cmp(opt->Data, ms_chap_v2_code, opt->DataSize) == 0)
			{
				// Try to request PAP then
				if (isAccepted == false || p->EnableMSCHAPv2 == false)
				{
					UINT64 offer = 0;
					PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
					USHORT proto = Endian16(PPP_LCP_AUTH_PAP);
					Copy(&offer, t->Data, t->DataSize > sizeof(UINT64) ? sizeof(UINT64) : t->DataSize);
					Debug("NACK proto with code = 0x%x, cypher = 0x%x, offered cypher = 0x%x\n", pp->Lcp->Code, *((USHORT *)(opt->Data)), offer);
					Debug("Request PAP\n");
					Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &proto, sizeof(USHORT)));
					if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c) == false)
					{
						PPPSetStatus(p, PPP_STATUS_FAIL);
						WHERE;
						return false;
					}
				}
				else if (p->AuthProtocol == PPP_UNSPECIFIED)
				{
					p->AuthProtocol = PPP_PROTOCOL_CHAP;
					Debug("Setting BEFORE_AUTH from ACK on LCP response parse on CHAP accept\n");
					PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
				}

			}
			else if (opt->DataSize == sizeof(USHORT) && *((USHORT *)(opt->Data)) == Endian16(PPP_LCP_AUTH_PAP))
			{
				// We couldn't agree on auth proto, failing connection
				if (isAccepted == false)
				{
					UINT64 offer = 0;
					Copy(&offer, t->Data, t->DataSize > sizeof(UINT64) ? sizeof(UINT64) : t->DataSize);
					Debug("NACK proto with code = 0x%x, cypher = 0x%x, offered cypher = 0x%x\n", pp->Lcp->Code, *((USHORT *)(opt->Data)), offer);
					Debug("Couldn't agree on auth protocol!\n");
					PPPLog(p, "LP_PAP_MSCHAPV2_REJECTED");
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
				else if (p->AuthProtocol == PPP_UNSPECIFIED)
				{
					p->AuthProtocol = PPP_PROTOCOL_PAP;
					Debug("Setting BEFORE_AUTH from ACK on LCP response parse on PAP accept\n");
					PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
				}
			}
			break;
		}
	}

	return result;
}

// Process CHAP responses
bool PPPProcessCHAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
	return PPPProcessCHAPResponsePacketEx(p, pp, req, pp->Lcp, false);
}
bool PPPProcessCHAPResponsePacketEx(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req, PPP_LCP *chap, bool use_eap)
{
	PPP_LCP *lcp;
	if (chap->Code == PPP_CHAP_CODE_RESPONSE)
	{
		bool ok = false;
		if (p->PPPStatus != PPP_STATUS_AUTHENTICATING && p->AuthOk == false)
		{
			Debug("Receiving CHAP response packets outside of auth status, some errors probably!");
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}
		if (p->AuthProtocol != PPP_PROTOCOL_CHAP && use_eap == false)
		{
			Debug("Receiving CHAP packet when auth protocol set to 0x%x\n", p->AuthProtocol);
			PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_PAP", pp->Protocol);
			PPPRejectUnsupportedPacketEx(p, pp, true);
			return false;
		}

		ok = PPPParseMSCHAP2ResponsePacketEx(p, chap, use_eap);

		// If we got only first packet of double CHAP then send second challenge
		if (ok && p->UseEapRadius && p->EapClient != NULL && p->Ipc == NULL)
		{
			lcp = BuildMSCHAP2ChallengePacket(p);
			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_CHAP, lcp) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
		}
		// We got a successful MSCHAPv2 response, so let's send a SUCCESS
		else if (ok)
		{
			char hex[MAX_SIZE];
			char ret_str[MAX_SIZE];
			BUF *lcp_ret_data = NewBuf();
			BinToStr(hex, sizeof(hex), p->MsChapV2_ServerResponse, 20);

			Format(ret_str, sizeof(ret_str),
			       "S=%s", hex);

			WriteBuf(lcp_ret_data, ret_str, StrLen(ret_str));

			lcp = NewPPPLCP(PPP_CHAP_CODE_SUCCESS, p->MsChapV2_PacketId);
			lcp->Data = Clone(lcp_ret_data->Buf, lcp_ret_data->Size);
			lcp->DataSize = lcp_ret_data->Size;

			if (lcp_ret_data != NULL)
			{
				FreeBuf(lcp_ret_data);
			}

			if (use_eap == false)
			{
				PPP_PACKET *res = ZeroMalloc(sizeof(PPP_PACKET));
				res->Lcp = lcp;
				res->IsControl = true;
				res->Protocol = PPP_PROTOCOL_CHAP;

				if (PPPSendPacketAndFree(p, res) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
				PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
			}
			else
			{
				BUF *b = BuildLCPData(lcp);
				FreePPPLCP(lcp);
				p->Eap_PacketId = p->NextId++;
				lcp = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId, PPP_EAP_TYPE_MSCHAPV2, b->Size);
				PPP_EAP *eapPacket = lcp->Data;
				Copy(eapPacket->Data, b->Buf, b->Size);
				FreeBuf(b);

				if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
			}

			p->AuthOk = true;
		}
		// We failed MSCHAPv2 auth
		else
		{
			char hex[MAX_SIZE];
			char ret_str[MAX_SIZE];
			BUF *lcp_ret_data = NewBuf();

			BinToStr(hex, sizeof(hex), p->MsChapV2_ServerChallenge, 16);

			Format(ret_str, sizeof(ret_str),
			       "E=%u R=0 C=%s V=3", p->MsChapV2_ErrorCode, hex);

			WriteBuf(lcp_ret_data, ret_str, StrLen(ret_str));

			lcp = NewPPPLCP(PPP_CHAP_CODE_FAILURE, p->MsChapV2_PacketId);
			lcp->Data = Clone(lcp_ret_data->Buf, lcp_ret_data->Size);
			lcp->DataSize = lcp_ret_data->Size;

			if (lcp_ret_data != NULL)
			{
				FreeBuf(lcp_ret_data);
			}

			if (use_eap == false)
			{
				PPP_PACKET *res = ZeroMalloc(sizeof(PPP_PACKET));
				res->Lcp = lcp;
				res->IsControl = true;
				res->Protocol = PPP_PROTOCOL_CHAP;

				if (PPPSendPacketAndFree(p, res) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
				PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
			}
			else
			{
				BUF *b = BuildLCPData(lcp);
				FreePPPLCP(lcp);
				p->Eap_PacketId = p->NextId++;
				lcp = BuildEAPPacketEx(PPP_EAP_CODE_REQUEST, p->Eap_PacketId, PPP_EAP_TYPE_MSCHAPV2, b->Size);
				PPP_EAP *eapPacket = lcp->Data;
				Copy(eapPacket->Data, b->Buf, b->Size);
				FreeBuf(b);

				if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
			}

			PPPLog(p, "LP_CHAP_FAILED");
		}

		return ok;
	}
	return false;
}

// Process IPCP responses
bool PPPProcessIPCPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
	bool isAccepted = PPP_LCP_CODE_IS_NEGATIVE(pp->Lcp->Code) == false;

	IP addrStruct;
	char addrStr[MAX_SIZE];
	UINT addr;
	IP prevAddrStruct;
	char prevAddrStr[MAX_SIZE];
	UINT prevAddr;
	PPP_LCP *c;
	UINT ui;

	if (PPPGetIPAddressValueFromLCP(pp->Lcp, PPP_IPCP_OPTION_IP, &addrStruct) == false || pp->Lcp->Code == PPP_LCP_CODE_REJECT || pp->Lcp->Code == PPP_LCP_CODE_CODE_REJECT)
	{
		Debug("Unsupported IPCP protocol");
		IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_REJECTED);
		PPPRejectUnsupportedPacketEx(p, pp, true);
		return false;
	}

	// We're dealing either with ACK or NACK
	addr = IPToUINT(&addrStruct);
	IPToStr(addrStr, MAX_SIZE, &addrStruct);

	if (isAccepted)
	{
		Debug("Accepted server IP address of %s\n", addrStr);

		// We already configured client address, now server address is also confirmed, ready for IPv4 data flow
		if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CONFIG)
		{
			IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_CONFIG_WAIT);
		}
		return true;
	}

	IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_CONFIG);

	PPPGetIPAddressValueFromLCP(req->Lcp, PPP_IPCP_OPTION_IP, &prevAddrStruct);
	prevAddr = IPToUINT(&prevAddrStruct);
	IPToStr(prevAddrStr, MAX_SIZE, &prevAddrStruct);

	Debug("Denied server IP address %s, proposed %s\n", prevAddrStr, addrStr);

	// Fallback mechanism - just request 192.0.0.8
	if (prevAddr == Endian32(0xc0000008))
	{
		Debug("We already tried the fallback IP of 192.0.0.8, giving up\n");
		IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_REJECTED);
		PPPRejectUnsupportedPacketEx(p, pp, true);
		return false;
	}

	c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
	ui = Endian32(0xc0000008);	// We always push 192.0.0.8, which is defined in RFC7600 as dummy IPv4 address.
	Add(c->OptionList, NewPPPOption(PPP_IPCP_OPTION_IP, &ui, sizeof(UINT)));
	if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPCP, c) == false)
	{
		PPPSetStatus(p, PPP_STATUS_FAIL);
		WHERE;
		return false;
	}

	return false;
}

// Process EAP responses
bool PPPProcessEAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
	if (pp->Lcp->DataSize >= 1)
	{
		PPP_EAP *eap_packet = pp->Lcp->Data;
		UINT eap_datasize = pp->Lcp->DataSize - 1;
		UINT64 offer = 0;
		PPP_LCP *c;
		UCHAR ms_chap_v2_code[3];
		HUB *hub;
		bool found = false;
		UINT authtype = AUTHTYPE_ANONYMOUS;
		UCHAR eapidentitypkt[MAX_SIZE] = { 0 };

		WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
		ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;

		// Forward EAP response to Radius server
		if (p->EapClient != NULL)
		{
			return PPPProcessEapResponseForRadius(p, eap_packet, eap_datasize);
		}

		switch (eap_packet->Type)
		{
		case PPP_EAP_TYPE_IDENTITY:
			p->Eap_MatchUserByCert = false;
			// Parse username
			Copy(eapidentitypkt, eap_packet->Data, MIN(MAX_SIZE, eap_datasize));
			
			Zero(&p->Eap_Identity, sizeof(p->Eap_Identity));
			PPPParseUsername(p->Cedar, eapidentitypkt, &p->Eap_Identity);
			Debug("EAP: username=%s, hubname=%s\n", p->Eap_Identity.UserName, p->Eap_Identity.HubName);

			// Locate user
			LockHubList(p->Cedar);
			{
				hub = GetHub(p->Cedar, p->Eap_Identity.HubName);
			}
			UnlockHubList(p->Cedar);
			if (hub != NULL)
			{
				AcLock(hub);
				{
					USER *user = AcGetUser(hub, p->Eap_Identity.UserName);
					if (user == NULL)
					{
						user = AcGetUser(hub, "*");
					}
					if (user != NULL)
					{
						found = true;
						authtype = user->AuthType;
						ReleaseUser(user);
					}
					else if (hub->Option->AllowEapMatchUserByCert == true)
					{
						authtype = AUTHTYPE_USERCERT;
						Zero(p->Eap_Identity.UserName, sizeof(p->Eap_Identity.UserName));
						p->Eap_MatchUserByCert = true;
					}
				}
				AcUnlock(hub);
				ReleaseHub(hub);
			}

			if (found == false && p->Eap_MatchUserByCert == false)
			{
				// User not found, fail immediately
				PPP_PACKET *pack = ZeroMalloc(sizeof(PPP_PACKET));
				pack->IsControl = true;
				pack->Protocol = PPP_PROTOCOL_EAP;
				PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
				pack->Lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, p->Eap_PacketId);

				if (PPPSendPacketAndFree(p, pack) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
				break;
			}

			// Select EAP method based on auth type
			switch (authtype)
			{
			case AUTHTYPE_RADIUS:
				// Create EAP client if needed
				if (p->EapClient == NULL)
				{
					char client_ip_tmp[256];
					PPP_LCP *response = NULL;
					IPToStr(client_ip_tmp, sizeof(client_ip_tmp), &p->ClientIP);
					Debug("Creating EAP RADIUS client\n");
					p->EapClient = HubNewEapClient(p->Cedar, p->Eap_Identity.HubName, client_ip_tmp, p->Eap_Identity.UserName, "L3:PPP", true, 
													&response, pp->Lcp->Id);

					if (p->EapClient == NULL || response == NULL)
					{
						PPP_PACKET *pack = ZeroMalloc(sizeof(PPP_PACKET));
						pack->IsControl = true;
						pack->Protocol = PPP_PROTOCOL_EAP;
						PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
						pack->Lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, p->Eap_PacketId);
						Debug("Failed to connect to a RADIUS server\n");

						if (PPPSendPacketAndFree(p, pack) == false)
						{
							PPPSetStatus(p, PPP_STATUS_FAIL);
							WHERE;
							return false;
						}
					}
					else
					{
						// Send first response to client
						if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, response) == false)
						{
							PPPSetStatus(p, PPP_STATUS_FAIL);
							WHERE;
							return false;
						}
					}	

					break;
				}
			case AUTHTYPE_ANONYMOUS:
			case AUTHTYPE_PASSWORD:
			case AUTHTYPE_NT:
				// Propose EAP-MSCHAPv2 directly
				p->Eap_Protocol = PPP_EAP_TYPE_MSCHAPV2;
				PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
				break;
			default:
				// Propose EAP-TLS first
				p->Eap_Protocol = PPP_EAP_TYPE_TLS;
				PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
				break;
			}
			break;
		case PPP_EAP_TYPE_NOTIFICATION:
			// Basically this is just an acknoweldgment that the notification was accepted by the client. Nothing to do here...
			break;
		case PPP_EAP_TYPE_NAK:
			if (p->Eap_Protocol == PPP_EAP_TYPE_TLS && p->Eap_MatchUserByCert == false)
			{
				// Propose EAP-MSCHAPv2
				p->Eap_Protocol = PPP_EAP_TYPE_MSCHAPV2;
				PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
				break;
			}
			// Fallback to auth protocol selection to try to select MSCHAP or PAP
			Debug("Got a EAP_NAK, abandoning EAP protocol\n");
			PPPRejectUnsupportedPacketEx(p, pp, true);
			PPPSetStatus(p, PPP_STATUS_CONNECTED);

			c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
			Copy(&offer, ms_chap_v2_code, sizeof(ms_chap_v2_code));
			Debug("Request MSCHAPv2 from EAP NAK\n");
			Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &ms_chap_v2_code, sizeof(ms_chap_v2_code)));
			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_LCP, c) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
			break;
		case PPP_EAP_TYPE_TLS:
			PPPProcessEAPTlsResponse(p, eap_packet, eap_datasize);
			break;
		case PPP_EAP_TYPE_MSCHAPV2:
			if (p->PPPStatus != PPP_STATUS_AUTHENTICATING)
			{
				Debug("Received EAP-MSCHAPv2 response not during authentication\n");
				break;
			}
			if (eap_datasize == 1)
			{
				// Success or failure response
				PPP_PACKET *pack = ZeroMalloc(sizeof(PPP_PACKET));
				pack->IsControl = true;
				pack->Protocol = PPP_PROTOCOL_EAP;

				if (p->AuthOk)
				{
					PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
					pack->Lcp = NewPPPLCP(PPP_EAP_CODE_SUCCESS, p->Eap_PacketId);
				}
				else
				{
					PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
					pack->Lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, p->Eap_PacketId);
				}

				if (PPPSendPacketAndFree(p, pack) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
			}
			else
			{
				// CHAP response
				PPP_LCP *chap = PPPParseLCP(PPP_PROTOCOL_CHAP, eap_packet->Data, eap_datasize);
				if (chap == NULL)
				{
					Debug("Received an invalid EAP-MSCHAPv2 packet\n");
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
				PPPProcessCHAPResponsePacketEx(p, pp, req, chap, true);
				FreePPPLCP(chap);
			}
			break;
		default:
			Debug("We got an unexpected EAP response packet! Ignoring...\n");
			break;
		}
	}
	else
	{
		PPP_EAP *eap;

		Debug("We got a CODE=%i ID=%i from client with zero size EAP structure, that shouldn't be happening!\n", pp->Lcp->Code, pp->Lcp->Id);

		eap = req->Lcp->Data;
		if (eap->Type == PPP_EAP_TYPE_TLS)
		{
			p->Eap_PacketId = p->NextId++;
			PPP_LCP *lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, PPP_EAP_TLS_FLAG_NONE);
			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
		}
	}
	return false;
}

// Process IPv6CP responses
bool PPPProcessIPv6CPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req)
{
	bool isAccepted = PPP_LCP_CODE_IS_NEGATIVE(pp->Lcp->Code) == false;

	// If we got a reject or a NACK, we just reject the whole IPv6 configuration, there is no way we can recover even from a NACK as we can't change the link-local address of an already existing router
	if (isAccepted == false)
	{
		Debug("Unsupported IPv6CP protocol");
		IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_REJECTED);
		PPPRejectUnsupportedPacketEx(p, pp, true);
		return false;
	}

	if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) != IPC_PROTO_STATUS_CONFIG)
	{
		Debug("We got an early IPv6CP response, ignoring for now...\n");
		return false;
	}

	Debug("Accepted server IPv6CP handshake\n");
	IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_CONFIG_WAIT);
	return true;
}

// Process EAP response for RADIUS (as proxy)
bool PPPProcessEapResponseForRadius(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eap_datasize)
{
	PPP_LCP *lcp;
	IPC *ipc;
	UINT error_code;

	if (p == NULL || eap_packet == NULL || p->EapClient == NULL)
	{
		return false;
	}

	lcp = EapClientSendEapRequest(p->EapClient, eap_packet, eap_datasize);
	if (lcp == NULL)
	{
		return false;
	}

	switch (lcp->Code)
	{
	case PPP_EAP_CODE_REQUEST:
		// Send back to client
		if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
		{
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}

		return true;
	case PPP_EAP_CODE_SUCCESS:
		if (p->Ipc == NULL)
		{
			Debug("PPP Radius creating IPC\n");
			ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, p->Eap_Identity.HubName, p->Eap_Identity.UserName, "", NULL,
							&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
							p->ClientHostname, p->CryptName, false, p->AdjustMss, p->EapClient, NULL,
							true, IPC_LAYER_3);

			if (ipc != NULL)
			{
				p->Ipc = ipc;

				// Setting user timeouts
				p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout
				p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000;
				if (p->TubeRecv != NULL)
				{
					p->TubeRecv->DataTimeout = p->DataTimeout;
				}
				p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000;
				p->UserConnectionTick = Tick64();
				p->AuthOk = true;
				PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
				break;
			}
		}
	case PPP_EAP_CODE_FAILURE:
	default:
		PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
		break;
	}

	// Send success or failure
	PPP_PACKET* pack;
	pack = ZeroMalloc(sizeof(PPP_PACKET));
	pack->IsControl = true;
	pack->Protocol = PPP_PROTOCOL_EAP;
	pack->Lcp = lcp;
	if (PPPSendPacketAndFree(p, pack) == false)
	{
		PPPSetStatus(p, PPP_STATUS_FAIL);
		WHERE;
		return false;
	}

	return true;
}

// Processes request packets
bool PPPProcessRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	switch (pp->Protocol)
	{
	case PPP_PROTOCOL_LCP:
		return PPPProcessLCPRequestPacket(p, pp);
		break;
	case PPP_PROTOCOL_PAP:
		return PPPProcessPAPRequestPacket(p, pp);
		break;
	case PPP_PROTOCOL_CHAP:
		Debug("Got a CHAP request, which is invalid, we should get CHAP response instead\n");
		PPPSetStatus(p, PPP_STATUS_FAIL);
		WHERE;
		return false;
		break;
	case PPP_PROTOCOL_IPCP:
		return PPPProcessIPCPRequestPacket(p, pp);
		break;
	case PPP_PROTOCOL_IPV6CP:
		return PPPProcessIPv6CPRequestPacket(p, pp);
		break;
	case PPP_PROTOCOL_EAP:
		return PPPProcessEAPRequestPacket(p, pp);
		break;
	default:
		Debug("Unsupported protocols should be already filtered out! protocol = 0x%x, code = 0x%x\n", pp->Protocol, pp->Lcp->Code);
		return false;
		break;
	}
	return false;
}

bool PPPProcessLCPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	bool result = true;
	UINT i = 0;

	USHORT NegotiatedAuthProto = PPP_UNSPECIFIED;
	USHORT NegotiatedMRU = PPP_UNSPECIFIED;
	// MSCHAPv2 code
	UCHAR ms_chap_v2_code[3];
	USHORT eap_code = PPP_LCP_AUTH_EAP;

	WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
	ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;

	Debug("Got LCP packet request ID=%i OptionsListSize=%i\n", pp->Lcp->Id, LIST_NUM(pp->Lcp->OptionList));

	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);

		switch (t->Type)
		{
		case PPP_LCP_OPTION_AUTH:
			t->IsSupported = true;
			if (t->DataSize == sizeof(USHORT) && *((USHORT *)t->Data) == PPP_LCP_AUTH_EAP && p->AuthProtocol == PPP_UNSPECIFIED)
			{
				t->IsAccepted = true;
				NegotiatedAuthProto = PPP_PROTOCOL_EAP;
			}
			else if (t->DataSize == sizeof(USHORT) && *((USHORT *)t->Data) == PPP_LCP_AUTH_PAP && p->AuthProtocol == PPP_UNSPECIFIED)
			{
				t->IsAccepted = true;
				NegotiatedAuthProto = PPP_PROTOCOL_PAP;
			}
			else if (t->DataSize == sizeof(ms_chap_v2_code) && Cmp(t->Data, ms_chap_v2_code, t->DataSize) == 0 && p->AuthProtocol == PPP_UNSPECIFIED)
			{
				t->IsAccepted = true;
				NegotiatedAuthProto = PPP_PROTOCOL_CHAP;
			}
			else
			{
				// We're recommending EAP by default as a more secure algo
				t->IsAccepted = false;
				t->AltDataSize = sizeof(eap_code);
				Copy(t->AltData, &eap_code, sizeof(eap_code));
			}
			break;
		case PPP_LCP_OPTION_MRU:
			t->IsSupported = true;
			if (t->DataSize == sizeof(USHORT))
			{
				USHORT value = READ_USHORT(t->Data);
				if (value < PPP_MRU_MIN || value > PPP_MRU_MAX)
				{
					t->IsAccepted = false;
					value = MAKESURE(value, PPP_MRU_MIN, PPP_MRU_MAX);
					//Debug("MRU not accepted, sending NACK with value = 0x%x\n", value);
					t->AltDataSize = sizeof(USHORT);
					WRITE_USHORT(t->AltData, value);
				}
				else
				{
					t->IsAccepted = true;
					NegotiatedMRU = value;
					//Debug("MRU accepted, value = 0x%x\n", value);
				}
			}
			else
			{
				t->IsAccepted = false;
				t->AltDataSize = sizeof(USHORT);
				WRITE_USHORT(t->AltData, PPP_MRU_DEFAULT);
			}
			break;
		default:
			t->IsSupported = false;
			Debug("Unsupported LCP option = 0x%x\n", t->Type);
			break;
		}
	}

	if (PPPRejectLCPOptions(p, pp))
	{
		Debug("Rejected LCP options...\n");
		return false;
	}

	if (PPPNackLCPOptions(p, pp))
	{
		Debug("NACKed LCP options...\n");
		return false;
	}

	if (PPPAckLCPOptions(p, pp) == false)
	{
		return false;
	}

	if (NegotiatedAuthProto != PPP_UNSPECIFIED)
	{
		if (p->AuthProtocol == PPP_UNSPECIFIED)
		{
			p->AuthProtocol = NegotiatedAuthProto;
			PPPSetStatus(p, PPP_STATUS_BEFORE_AUTH);
			Debug("Setting BEFORE_AUTH from REQ on LCP request parse\n");
		}
	}
	if (NegotiatedMRU != PPP_UNSPECIFIED)
	{
		p->Mru1 = NegotiatedMRU;
	}

	return true;
}

bool PPPProcessPAPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	if (p->PPPStatus != PPP_STATUS_BEFORE_AUTH && p->AuthOk == false)
	{
		PPP_LCP *lcp = NewPPPLCP(PPP_PAP_CODE_NAK, pp->Lcp->Id);
		PPP_PACKET *ret = ZeroMalloc(sizeof(PPP_PACKET));

		Debug("Got a PAP request before we're ready for AUTH procedure!\n");

		ret->IsControl = true;
		ret->Protocol = PPP_PROTOCOL_PAP;
		ret->Lcp = lcp;
		if (PPPSendPacketAndFree(p, ret) == false)
		{
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}

		return false;
	}
	if (p->AuthProtocol != PPP_PROTOCOL_PAP)
	{
		Debug("Trying to auth with PAP when should be 0x%x\n", p->AuthProtocol);
		PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_CHAP", pp->Protocol);

		// Forcing rejection of PAP on configured MSCHAPv2
		PPPRejectUnsupportedPacketEx(p, pp, true);

		return false;
	}
	if (p->AuthOk == false)
	{
		UCHAR *data;
		UINT size;

		PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);

		if (p->Ipc == NULL)
		{
			// PAP

			// Extract the ID and the password
			data = pp->Lcp->Data;
			size = pp->Lcp->DataSize;

			if (size >= 1)
			{
				UCHAR len_id = data[0];
				data++;
				size--;

				if (size >= len_id)
				{
					char username[256];
					char password[256];

					Zero(username, sizeof(username));
					Zero(password, sizeof(password));

					Copy(username, data, len_id);
					data += len_id;
					size -= len_id;

					if (size >= 1)
					{
						UCHAR len_pass = data[0];
						data++;
						size--;

						if (size >= len_pass)
						{
							IPC *ipc;
							char id[MAX_SIZE];
							char hub[MAX_SIZE];
							ETHERIP_ID d;

							Zero(id, sizeof(id));
							Zero(hub, sizeof(hub));

							Copy(password, data, len_pass);

							Debug("PPP: id=%s, pw=%s\n", username, password);

							// The user name is divided into the ID and the virtual HUB name
							Zero(&d, sizeof(d));
							PPPParseUsername(p->Cedar, username, &d);

							StrCpy(id, sizeof(id), d.UserName);
							StrCpy(hub, sizeof(hub), d.HubName);

							if (IsEmptyStr(id) == false)
							{
								// Attempt to connect with IPC
								UINT error_code;

								ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password, NULL,
								             &error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
								             p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, NULL,
								             false, IPC_LAYER_3);

								if (ipc != NULL)
								{
									p->Ipc = ipc;

									// Setting user timeouts
									p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout
									p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000;
									if (p->TubeRecv != NULL)
									{
										p->TubeRecv->DataTimeout = p->DataTimeout;
									}
									p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000;
									p->UserConnectionTick = Tick64();

									p->AuthOk = true;
								}
								else
								{
									PPPSetStatus(p, PPP_STATUS_FAIL);
									WHERE;
									return false;
								}
							}
						}
					}
				}
			}
		}
		else
		{
			// Return success for a request from the second time when it is successfully authenticated once
			p->AuthOk = true;
		}
	}
	if (p->AuthOk)
	{
		PPP_LCP *lcp = NewPPPLCP(PPP_PAP_CODE_ACK, pp->Lcp->Id);
		PPP_PACKET *ret = ZeroMalloc(sizeof(PPP_PACKET));
		ret->IsControl = true;
		ret->Protocol = PPP_PROTOCOL_PAP;
		ret->Lcp = lcp;
		if (PPPSendPacketAndFree(p, ret) == false)
		{
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}

		if (p->PPPStatus == PPP_STATUS_AUTHENTICATING)
		{
			PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
		}
		return true;
	}
	if (p->AuthOk == false)
	{
		PPP_LCP *lcp = NewPPPLCP(PPP_PAP_CODE_NAK, pp->Lcp->Id);
		PPP_PACKET *ret = ZeroMalloc(sizeof(PPP_PACKET));
		ret->IsControl = true;
		ret->Protocol = PPP_PROTOCOL_PAP;
		ret->Lcp = lcp;
		if (PPPSendPacketAndFree(p, ret) == false)
		{
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}

		if (p->PPPStatus == PPP_STATUS_AUTHENTICATING)
		{
			PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);
			PPPLog(p, "LP_PAP_FAILED");
		}

		return false;
	}
	return p->AuthOk;
}


bool PPPProcessIPCPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	PPP_IPOPTION o;
	PPP_IPOPTION res;
	PPP_OPTION *dummyIpOption;
	UINT dummyIp = 0;
	DHCP_OPTION_LIST cao;
	IP client_ip;
	IP subnet;
	IP zero;
	IP gw;
	bool ok = true;
	bool processed = false;
	bool isEmptyIpAddress = false;

	if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_REJECTED)
	{
		Debug("We got an IPCP packet after we had it rejected\n");
		return PPPRejectUnsupportedPacketEx(p, pp, true);
	}

	if (PPPGetIPOptionFromLCP(&o, pp->Lcp) == false)
	{
		Debug("IPCP request without client IP address received! Treating as zeroed out client IP...\n");
		isEmptyIpAddress = true;
		dummyIpOption = NewPPPOption(PPP_IPCP_OPTION_IP, &dummyIp, sizeof(UINT));
		dummyIpOption->IsSupported = true;
		dummyIpOption->IsAccepted = false;
		Add(pp->Lcp->OptionList, dummyIpOption);
	}

	// Process if not configured yet by server
	if ((IsZero(&p->ClientAddressOption, sizeof(DHCP_OPTION_LIST)) || isEmptyIpAddress) && ok)
	{
		// Decide if we received a static IP from client and it is allowed
		if (IsZeroIP(&o.IpAddress) == false)
		{
			if (p->Ipc->Policy->DHCPForce == false)
			{
				if (p->DhcpAllocated == false)
				{
					if (p->UseStaticIPAddress == false)
					{
						DHCP_OPTION_LIST cao;

						// The client specify an IP address
						Zero(&cao, sizeof(cao));

						cao.ClientAddress = IPToUINT(&o.IpAddress);

						Copy(&p->ClientAddressOption, &cao, sizeof(cao));

						p->UseStaticIPAddress = true;
					}
				}
			}
		}
		else
		{
			p->UseStaticIPAddress = false;
		}

		// Get additional information for static clients
		if (p->UseStaticIPAddress)
		{
			if (p->DhcpIpInformTried == false)
			{
				// Get additional information such as the subnet mask from the DHCP server
				SetIP(&subnet, 255, 0, 0, 0);
				Zero(&zero, sizeof(zero));

				UINTToIP(&client_ip, p->ClientAddressOption.ClientAddress);

				Zero(&cao, sizeof(cao));

				IPCSetIPv4Parameters(p->Ipc, &client_ip, &subnet, &zero, NULL);

				p->DhcpIpInformTried = true;

				PPPLog(p, "LP_DHCP_INFORM_TRYING");

				if (IPCDhcpRequestInformIP(p->Ipc, &cao, p->TubeRecv, &client_ip))
				{
					Debug("IPCDhcpRequestInformIP ok.\n");
					Copy(&p->ClientAddressOption, &cao, sizeof(cao));
					p->ClientAddressOption.ClientAddress = IPToUINT(&client_ip);

					if (true)
					{
						char server_ip_str[64];
						char subnet_str[64], defgw_str[64];
						char dns1_str[64], dns2_str[64];
						char wins1_str[64], wins2_str[64];

						IPToStr32(server_ip_str, sizeof(server_ip_str), cao.ServerAddress);
						IPToStr32(subnet_str, sizeof(subnet_str), cao.SubnetMask);
						IPToStr32(defgw_str, sizeof(defgw_str), cao.Gateway);
						IPToStr32(dns1_str, sizeof(dns1_str), cao.DnsServer);
						IPToStr32(dns2_str, sizeof(dns2_str), cao.DnsServer2);
						IPToStr32(wins1_str, sizeof(wins1_str), cao.WinsServer);
						IPToStr32(wins2_str, sizeof(wins2_str), cao.WinsServer2);

						PPPLog(p, "LP_DHCP_INFORM_OK",
						       subnet_str, defgw_str, cao.DomainName,
						       dns1_str, dns2_str, wins1_str, wins2_str,
						       server_ip_str);
					}
				}
				else
				{
					Debug("IPCDhcpRequestInformIP failed.\n");
					ok = false;
					p->DhcpIpInformTried = false;
					PPPLog(p, "LP_DHCP_INFORM_NG");
				}

				IPCSetIPv4Parameters(p->Ipc, &zero, &zero, &zero, NULL);
			}
		}
		// Get IP address and additional information from DHCP
		else
		{
			if (p->DhcpIpAllocTried == false)
			{
				DHCP_OPTION_LIST cao;

				Zero(&cao, sizeof(cao));
				p->DhcpIpAllocTried = true;

				PPPLog(p, "LP_DHCP_REQUEST_TRYING");

				if (IPCDhcpAllocateIP(p->Ipc, &cao, p->TubeRecv))
				{
					UINT t;

					Debug("IPCDhcpAllocateIP ok.\n");

					// IP address has been determined
					Copy(&p->ClientAddressOption, &cao, sizeof(cao));

					p->DhcpAllocated = true;

					// Determine the DHCP update interval
					t = cao.LeaseTime;
					if (t == 0)
					{
						t = 600;
					}

					t = t / 3;

					if (t == 0)
					{
						t = 1;
					}

					p->DhcpRenewInterval = (UINT64)t * (UINT64)1000;
					p->DhcpNextRenewTime = Tick64() + p->DhcpRenewInterval;

					if (true)
					{
						char client_ip_str[64], server_ip_str[64];
						char subnet_str[64], defgw_str[64];
						char dns1_str[64], dns2_str[64];
						char wins1_str[64], wins2_str[64];

						IPToStr32(client_ip_str, sizeof(client_ip_str), cao.ClientAddress);
						IPToStr32(server_ip_str, sizeof(server_ip_str), cao.ServerAddress);
						IPToStr32(subnet_str, sizeof(subnet_str), cao.SubnetMask);
						IPToStr32(defgw_str, sizeof(defgw_str), cao.Gateway);
						IPToStr32(dns1_str, sizeof(dns1_str), cao.DnsServer);
						IPToStr32(dns2_str, sizeof(dns2_str), cao.DnsServer2);
						IPToStr32(wins1_str, sizeof(wins1_str), cao.WinsServer);
						IPToStr32(wins2_str, sizeof(wins2_str), cao.WinsServer2);

						PPPLog(p, "LP_DHCP_REQUEST_OK",
						       client_ip_str, subnet_str, defgw_str, cao.DomainName,
						       dns1_str, dns2_str, wins1_str, wins2_str,
						       server_ip_str, cao.LeaseTime);
					}
				}
				else
				{
					Debug("IPCDhcpAllocateIP failed.\n");
					p->DhcpIpAllocTried = false;
					ok = false;
					PPPLog(p, "LP_DHCP_REQUEST_NG");
				}
			}
		}
	}

	// If we already have a configured IP data - send it along
	if (IsValidUnicastIPAddressUINT4(p->ClientAddressOption.ClientAddress) &&
	        p->ClientAddressOption.SubnetMask != 0 && ok)
	{
		// Success to determine the address
		UINTToIP(&subnet, p->ClientAddressOption.SubnetMask);
		UINTToIP(&gw, p->ClientAddressOption.Gateway);

		Zero(&res, sizeof(res));
		UINTToIP(&res.IpAddress, p->ClientAddressOption.ClientAddress);
		UINTToIP(&res.DnsServer1, p->ClientAddressOption.DnsServer);
		UINTToIP(&res.DnsServer2, p->ClientAddressOption.DnsServer2);
		UINTToIP(&res.WinsServer1, p->ClientAddressOption.WinsServer);
		UINTToIP(&res.WinsServer2, p->ClientAddressOption.WinsServer2);

		if (IPCSetIPv4Parameters(p->Ipc, &res.IpAddress, &subnet, &gw, &p->ClientAddressOption.ClasslessRoute))
		{
			char client_ip_str[64];
			char subnet_str[64], defgw_str[64];
			char dns1_str[64], dns2_str[64];
			char wins1_str[64], wins2_str[64];

			// IPv4 parameters have been set for the first time
			Debug("Param First Set.\n");

			IPToStr(client_ip_str, sizeof(client_ip_str), &res.IpAddress);
			IPToStr(subnet_str, sizeof(subnet_str), &subnet);
			IPToStr(defgw_str, sizeof(defgw_str), &gw);
			IPToStr(dns1_str, sizeof(dns1_str), &res.DnsServer1);
			IPToStr(dns2_str, sizeof(dns2_str), &res.DnsServer2);
			IPToStr(wins1_str, sizeof(wins1_str), &res.WinsServer1);
			IPToStr(wins2_str, sizeof(wins2_str), &res.WinsServer2);

			PPPLog(p, "LP_SET_IPV4_PARAM", client_ip_str, subnet_str,
			       defgw_str, dns1_str, dns2_str, wins1_str, wins2_str);
		}

		/*// Backporting static configuration received from client - let him use whatever he wants,
		// he won't accept anything else anyway (as per testing with Windows clients)
		if (!IsZeroIP(&o.DnsServer1))
		{
			CopyIP(&res.DnsServer1, &o.DnsServer1);
			Debug("Setting DNS1 from client\n");
		}
		if (!IsZeroIP(&o.DnsServer2))
		{
			CopyIP(&res.DnsServer2, &o.DnsServer2);
			Debug("Setting DNS2 from client\n");
		}
		if (!IsZeroIP(&o.WinsServer1))
		{
			CopyIP(&res.WinsServer1, &o.WinsServer1);
			Debug("Setting WINS1 from client\n");
		}
		if (!IsZeroIP(&o.WinsServer2))
		{
			CopyIP(&res.WinsServer2, &o.WinsServer2);
			Debug("Setting WINS2 from client\n");
		}*/
		/*if (!IsZeroIP(&res.DnsServer1) && IsZeroIP(&res.DnsServer2))
		{
			CopyIP(&res.DnsServer2, &res.DnsServer1);
		}
		if (!IsZeroIP(&res.WinsServer1) && IsZeroIP(&res.WinsServer2))
		{
			CopyIP(&res.WinsServer2, &res.WinsServer1);
		}*/
		PPPSetIPOptionToLCP(&res, pp->Lcp, true);
	}
	// We couldn't configure address for the client
	else
	{
		// Failed to determine the address
		Debug("IP Address Determination Failed.\n");

		Zero(&res, sizeof(res));
		// We will try to reconfigure if we receive another request by wiping all data
		Zero(&p->ClientAddressOption, sizeof(DHCP_OPTION_LIST));
		p->UseStaticIPAddress = false;

		PPPSetIPOptionToLCP(&res, pp->Lcp, true);
	}

	if (PPPRejectLCPOptionsEx(p, pp, processed))
	{
		Debug("Rejected IPCP options ID = 0x%x\n", pp->Lcp->Id);
		processed = true;
	}

	if (ok && PPPNackLCPOptionsEx(p, pp, processed))
	{
		Debug("NACKed IPCP options ID = 0x%x\n", pp->Lcp->Id);
		processed = true;
	}

	// We will delay this packet ACK and send the server IP first, then wait for a reparse
	// it is kind of dirty but fixes issues on some clients (namely VPN Client Pro on Android)
	if (IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CLOSED && p->ClientAddressOption.ServerAddress != 0 && ok)
	{
		PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
		UINT ui = p->ClientAddressOption.ServerAddress;
		Add(c->OptionList, NewPPPOption(PPP_IPCP_OPTION_IP, &ui, sizeof(UINT)));
		if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPCP, c) == false)
		{
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}
		IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_CONFIG);
		if (processed == false)
		{
			PPPAddNextPacket(p, pp, 1);
		}
		return false;
	}

	// We still haven't received any answer from client about server IP, keep waiting...
	if ((IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CONFIG ||
	        IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CLOSED) && processed == false)
	{
		PPPAddNextPacket(p, pp, 1);
		return false;
	}

	//Debug("PPPAckLCPOptionsEx ok=%x, processed=%x", ok, processed);
	if (ok == false || PPPAckLCPOptionsEx(p, pp, processed) == false)
	{
		return false;
	}
	Debug("ACKed IPCP options ID = 0x%x\n", pp->Lcp->Id);

	if (ok && IPC_PROTO_GET_STATUS(p->Ipc, IPv4State) == IPC_PROTO_STATUS_CONFIG_WAIT)
	{
		IPC_PROTO_SET_STATUS(p->Ipc, IPv4State, IPC_PROTO_STATUS_OPENED);
		Debug("IPv4 OPENED\n");
	}
	return ok;
}

// Process EAP request packets
bool PPPProcessEAPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	Debug("We got an EAP request, which is weird...\n");
	return false;
}

// Process IPv6CP request packets
bool PPPProcessIPv6CPRequestPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	UINT i;
	bool processed = false;
	if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_REJECTED)
	{
		Debug("We got an IPv6CP packet after we had it rejected\n");
		return PPPRejectUnsupportedPacketEx(p, pp, true);
	}

	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);

		switch (t->Type)
		{
		case PPP_IPV6CP_OPTION_EUI:
			t->IsSupported = true;
			if (t->DataSize == sizeof(UINT64))
			{
				UINT64 newValue = 0;
				UINT64 value = READ_UINT64(t->Data);
				if (value != 0 && value != p->Ipc->IPv6ServerEUI && IPCIPv6CheckExistingLinkLocal(p->Ipc, value) == false)
				{
					t->IsAccepted = true;
					p->Ipc->IPv6ClientEUI = value;
				}
				else
				{
					t->IsAccepted = false;
					while (true)
					{
						newValue = Rand64();
						if (newValue != 0 && newValue != p->Ipc->IPv6ServerEUI && IPCIPv6CheckExistingLinkLocal(p->Ipc, newValue) == false)
						{
							WRITE_UINT64(t->AltData, newValue);
							t->AltDataSize = sizeof(UINT64);
							break;
						}
					}
				}
			}
			break;
		default:
			t->IsSupported = false;
			break;
		}
	}

	if (PPPRejectLCPOptionsEx(p, pp, processed))
	{
		Debug("Rejected IPv6CP options ID = 0x%x\n", pp->Lcp->Id);
		processed = true;
	}

	if (PPPNackLCPOptionsEx(p, pp, processed))
	{
		Debug("NACKed IPv6CP options ID = 0x%x\n", pp->Lcp->Id);
		processed = true;
	}

	if (p->Ipc->IPv6ClientEUI != 0 && IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_CLOSED)
	{
		PPP_LCP *c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
		Add(c->OptionList, NewPPPOption(PPP_IPV6CP_OPTION_EUI, &p->Ipc->IPv6ServerEUI, sizeof(UINT64)));
		if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_IPV6CP, c) == false)
		{
			PPPSetStatus(p, PPP_STATUS_FAIL);
			WHERE;
			return false;
		}

		IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_CONFIG);
	}

	if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_CONFIG && processed == false)
	{
		PPPAddNextPacket(p, pp, 1);
		return false;
	}

	if (PPPAckLCPOptionsEx(p, pp, processed) == false)
	{
		return false;
	}
	Debug("ACKed IPv6CP options ID = 0x%x\n", pp->Lcp->Id);

	if (IPC_PROTO_GET_STATUS(p->Ipc, IPv6State) == IPC_PROTO_STATUS_CONFIG_WAIT)
	{
		IPC_PROTO_SET_STATUS(p->Ipc, IPv6State, IPC_PROTO_STATUS_OPENED);
		Debug("IPv6 OPENED\n");
	}

	return true;
}

// LCP option based packets utility
bool PPPRejectLCPOptions(PPP_SESSION *p, PPP_PACKET *pp)
{
	return PPPRejectLCPOptionsEx(p, pp, false);
}
bool PPPRejectLCPOptionsEx(PPP_SESSION *p, PPP_PACKET *pp, bool simulate)
{
	UINT i = 0;
	bool toBeRejected = false;
	PPP_PACKET *ret;
	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);

		if (t->IsSupported == false)
		{
			toBeRejected = true;
			break;
		}
	}

	if (toBeRejected == false)
	{
		return false;
	}

	ret = ZeroMalloc(sizeof(PPP_PACKET));
	ret->IsControl = true;
	ret->Protocol = pp->Protocol;
	// Return a Reject if there are unsupported parameters
	ret->Lcp = NewPPPLCP(PPP_LCP_CODE_REJECT, pp->Lcp->Id);

	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);

		if (t->IsSupported == false)
		{
			// Attach the original option value as is
			Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize));
			Debug("Rejected LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol);
		}
	}

	if (LIST_NUM(ret->Lcp->OptionList) == 0 || simulate)
	{
		FreePPPPacket(ret);
		return false;
	}

	PPPSendPacketAndFree(p, ret);
	return true;
}
bool PPPNackLCPOptions(PPP_SESSION *p, PPP_PACKET *pp)
{
	return PPPNackLCPOptionsEx(p, pp, false);
}
bool PPPNackLCPOptionsEx(PPP_SESSION *p, PPP_PACKET *pp, bool simulate)
{
	UINT i = 0;
	PPP_PACKET *ret;
	bool toBeNACKed = false;
	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);

		if (t->IsAccepted == false && t->IsSupported == true)
		{
			toBeNACKed = true;
			break;
		}
	}

	if (toBeNACKed == false)
	{
		return false;
	}

	ret = ZeroMalloc(sizeof(PPP_PACKET));
	ret->IsControl = true;
	ret->Protocol = pp->Protocol;
	// Return a NAK if there are any unacceptable parameter
	// even that all parameters are supported
	ret->Lcp = NewPPPLCP(PPP_LCP_CODE_NAK, pp->Lcp->Id);

	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);

		if (t->IsAccepted == false && t->IsSupported == true)
		{
			// Replace the original option value with an acceptable value
			Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->AltData, t->AltDataSize));
			Debug("NACKed LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol);
		}
	}

	if (LIST_NUM(ret->Lcp->OptionList) == 0 || simulate)
	{
		FreePPPPacket(ret);
		return false;
	}

	PPPSendPacketAndFree(p, ret);
	return true;
}
bool PPPAckLCPOptions(PPP_SESSION *p, PPP_PACKET *pp)
{
	return PPPAckLCPOptionsEx(p, pp, false);
}
bool PPPAckLCPOptionsEx(PPP_SESSION *p, PPP_PACKET *pp, bool simulate)
{
	UINT i = 0;
	PPP_PACKET *ret;
	bool toBeACKed = false;
	if (LIST_NUM(pp->Lcp->OptionList) == 0)
	{
		// We acknoweldge an empty option list
		toBeACKed = true;
		Debug("ACKing empty LCP options list, id=%i\n", pp->Lcp->Id);
	}
	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);

		if (t->IsAccepted == true && t->IsSupported == true)
		{
			toBeACKed = true;
			break;
		}
	}

	if (toBeACKed == false)
	{
		return false;
	}

	ret = ZeroMalloc(sizeof(PPP_PACKET));
	ret->IsControl = true;
	ret->Protocol = pp->Protocol;
	// Return an ACK if all parameters are accepted
	ret->Lcp = NewPPPLCP(PPP_LCP_CODE_ACK, pp->Lcp->Id);

	for (i = 0; i < LIST_NUM(pp->Lcp->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(pp->Lcp->OptionList, i);

		if (t->IsAccepted == true && t->IsSupported == true)
		{
			// Attach the original option value as is
			Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize));
			Debug("ACKed LCP option = 0x%x, proto = 0x%x\n", t->Type, pp->Protocol);
		}
	}

	if (simulate)
	{
		FreePPPPacket(ret);
		return false;
	}

	PPPSendPacketAndFree(p, ret);
	return true;
}

// PPP networking functions
// Send a request packet in the PPP
bool PPPSendAndRetransmitRequest(PPP_SESSION *p, USHORT protocol, PPP_LCP *c)
{
	PPP_PACKET *pp;
	UINT64 now = Tick64();
	PPP_REQUEST_RESEND *resend;

	// Validate arguments
	if (p == NULL || c == NULL)
	{
		return false;
	}

	pp = ZeroMalloc(sizeof(PPP_PACKET));
	pp->Protocol = protocol;
	pp->IsControl = true;
	pp->Lcp = c;
	if (pp->Lcp->Id == 0)
	{
		pp->Lcp->Id = p->NextId++;
	}

	// Send the PPP packet
	if (PPPSendPacketEx(p, pp, false) == false)
	{
		PPPSetStatus(p, PPP_STATUS_FAIL);
		FreePPPPacket(pp);
		WHERE;
		return false;
	}

	resend = ZeroMalloc(sizeof(PPP_REQUEST_RESEND));
	resend->Id = pp->Lcp->Id;
	resend->Packet = pp;
	resend->ResendTime = now + PPP_PACKET_RESEND_INTERVAL;
	resend->TimeoutTime = now + p->PacketRecvTimeout;

	Add(p->SentReqPacketList, resend);

	return true;
}
// Send the PPP packet and frees the sent packet
bool PPPSendPacketAndFree(PPP_SESSION *p, PPP_PACKET *pp)
{
	bool result = PPPSendPacketEx(p, pp, false);
	FreePPPPacket(pp);
	return result;
}
// Send the PPP packet
bool PPPSendPacketEx(PPP_SESSION *p, PPP_PACKET *pp, bool no_flush)
{
	bool ret = false;
	BUF *b;
	// Validate arguments
	if (p == NULL || pp == NULL)
	{
		return false;
	}

	b = BuildPPPPacketData(pp);
	if (b == NULL)
	{
		return false;
	}

	ret = TubeSendEx(p->TubeSend, b->Buf, b->Size, NULL, no_flush);

	if (no_flush)
	{
		AddTubeToFlushList(p->FlushList, p->TubeSend);
	}

	FreeBuf(b);

	return ret;
}

// Receive a PPP packet
PPP_PACKET *PPPRecvPacket(PPP_SESSION *p, bool async)
{
	TUBEDATA *d;
	PPP_PACKET *pp;
	// Validate arguments
	if (p == NULL)
	{
		return NULL;
	}

LABEL_LOOP:

	if (async == false)
	{
		d = TubeRecvSync(p->TubeRecv, (UINT)p->PacketRecvTimeout);
	}
	else
	{
		d = TubeRecvAsync(p->TubeRecv);
	}

	if (d == NULL)
	{
		return NULL;
	}

	pp = ParsePPPPacket(d->Data, d->DataSize);
	FreeTubeData(d);

	if (pp == NULL)
	{
		// A broken packet is received
		goto LABEL_LOOP;
	}

	p->LastRecvTime = Tick64();

	return pp;
}

PPP_PACKET *PPPGetNextPacket(PPP_SESSION *p)
{
	PPP_PACKET *ret = NULL;
	UINT i = 0;
	if (p->CurrentPacket != NULL)
	{
		FreePPPPacket(p->CurrentPacket);
		p->CurrentPacket = NULL;
	}
	for (i = 0; i < LIST_NUM(p->DelayedPackets); i++)
	{
		PPP_DELAYED_PACKET *t = LIST_DATA(p->DelayedPackets, i);
		if (t->DelayTicks > 0)
		{
			t->DelayTicks--;
		}
		else
		{
			ret = t->Packet;
			Delete(p->DelayedPackets, t);
			Free(t);
			break;
		}
	}

	if (ret != NULL)
	{
		p->CurrentPacket = ret;
		return ret;
	}

	ret = PPPRecvPacket(p, true);

	if (ret != NULL && ret->IsControl && ret->Lcp != NULL)
	{
		PPP_DELAYED_PACKET *firstRelated = NULL;
		for (i = 0; i < LIST_NUM(p->DelayedPackets); i++)
		{
			PPP_DELAYED_PACKET *t = LIST_DATA(p->DelayedPackets, i);
			char related = PPPRelatedPacketComparator(ret, t->Packet);
			if (related != 0xF && related != 0xE)
			{
				if (related == 0)
				{
					// It's the same packet, just remove it and wait for it's delays
					FreePPPPacket(ret);
					firstRelated = NULL;
					ret = NULL;
					break;
				}
				if (related == 1)
				{
					// We got a packet which should come later than any of delayed ones
					PPPAddNextPacket(p, ret, t->DelayTicks);
					firstRelated = NULL;
					ret = NULL;
					break;
				}
				if (related == -1)
				{
					char prevFoundRelated = -1;
					if (firstRelated != NULL)
					{
						prevFoundRelated = PPPRelatedPacketComparator(t->Packet, firstRelated->Packet);
					}
					if (prevFoundRelated == -1)
					{
						firstRelated = t;
					}
				}
			}
		}

		if (firstRelated != NULL)
		{
			PPPAddNextPacket(p, ret, firstRelated->DelayTicks);
			ret = NULL;
		}
	}

	p->CurrentPacket = ret;
	return ret;
}

void PPPAddNextPacket(PPP_SESSION *p, PPP_PACKET *pp, UINT delay)
{
	PPP_DELAYED_PACKET *t = ZeroMalloc(sizeof(PPP_DELAYED_PACKET));
	if (p->CurrentPacket == pp)
	{
		p->CurrentPacket = NULL;
	}
	t->Packet = pp;
	t->DelayTicks = delay;
	Add(p->DelayedPackets, t);
	Sort(p->DelayedPackets);
	/*Debug("after sorting delayeds\n");
	for (i = 0; i < LIST_NUM(p->DelayedPackets); i++)
	{
		t = LIST_DATA(p->DelayedPackets, i);
		if (t->Packet->Lcp != NULL)
		{
			Debug("> Packet proto = 0x%x, id = 0x%x, code = 0x%x, delay = 0x%x\n", t->Packet->Protocol, t->Packet->Lcp->Id, t->Packet->Lcp->Code, t->DelayTicks);
		}
	}
	Debug("after sorting delayeds end\n");*/
}

int PPPDelayedPacketsComparator(void *a, void *b)
{
	PPP_DELAYED_PACKET *first = a;
	PPP_DELAYED_PACKET *second = b;

	char related = PPPRelatedPacketComparator(first->Packet, second->Packet);

	if (related == 0xF || related == 0xE)
	{
		if (first->DelayTicks < second->DelayTicks)
		{
			return -1;
		}
		if (first->DelayTicks > second->DelayTicks)
		{
			return 1;
		}
		return 0;
	}

	// We make all delay ticks to be accounted with the sorting
	if (related <= 1 && related >= -1)
	{
		if (related == -1 && first->DelayTicks >= second->DelayTicks)
		{
			second->DelayTicks = first->DelayTicks;
			second->DelayTicks++;
		}
		else if (related == 1 && first->DelayTicks <= second->DelayTicks)
		{
			first->DelayTicks = second->DelayTicks;
			first->DelayTicks++;
		}
		return related;
	}
	return 0;
}

// -1 - packet a comes before packet b
// 0 - this is the same packet
// 1 - packet a comes after packet b
// 0xF - packet is not related
// 0xE - we got an error while comparing, treating as not related would be the most correct
char PPPRelatedPacketComparator(PPP_PACKET *a, PPP_PACKET *b)
{
	if (a->IsControl && b->IsControl &&
	        a->Lcp != NULL && b->Lcp != NULL &&
	        a->Protocol == b->Protocol &&
	        PPP_CODE_IS_REQUEST(a->Protocol, a->Lcp->Code) == PPP_CODE_IS_REQUEST(b->Protocol, b->Lcp->Code) &&
	        PPP_CODE_IS_RESPONSE(a->Protocol, a->Lcp->Code) == PPP_CODE_IS_RESPONSE(b->Protocol, b->Lcp->Code))
	{
		// The packet is related!
		if (a->Lcp->Id < b->Lcp->Id)
		{
			return -1;
		}
		else if (a->Lcp->Id == b->Lcp->Id)
		{
			if (a->Lcp->Code == b->Lcp->Code)
			{
				return 0;
			}
			else
			{
				return 0xE;
			}
		}
		else if (a->Lcp->Id > b->Lcp->Id)
		{
			return 1;
		}
		else
		{
			return 0xE;
		}
	}
	else
	{
		// The packet is not related!
		return 0xF;
	}
}

// PPP utility functions
// Packet structure creation utilities

// Create the LCP
PPP_LCP *NewPPPLCP(UCHAR code, UCHAR id)
{
	PPP_LCP *c = ZeroMalloc(sizeof(PPP_LCP));

	c->Code = code;
	c->Id = id;
	c->OptionList = NewListFast(NULL);

	return c;
}

// Create a new PPP options
PPP_OPTION *NewPPPOption(UCHAR type, void *data, UINT size)
{
	PPP_OPTION *o;
	// Validate arguments
	if (size != 0 && data == NULL)
	{
		return NULL;
	}

	o = ZeroMalloc(sizeof(PPP_OPTION));

	o->Type = type;
	Copy(o->Data, data, size);
	o->DataSize = size;

	return o;
}

// Packet parse utilities

// Analyse the PPP packet
PPP_PACKET *ParsePPPPacket(void *data, UINT size)
{
	PPP_PACKET *pp;
	UCHAR *buf;
	// Validate arguments
	if (data == NULL || size == 0)
	{
		return NULL;
	}

	pp = ZeroMalloc(sizeof(PPP_PACKET));

	buf = (UCHAR *)data;

	// Address
	if (buf[0] != 0xff)
	{
		goto LABEL_ERROR;
	}
	size--;
	buf++;

	// Control
	if (size < 1)
	{
		goto LABEL_ERROR;
	}
	if (buf[0] != 0x03)
	{
		goto LABEL_ERROR;
	}
	size--;
	buf++;

	// Protocol
	if (size < 2)
	{
		goto LABEL_ERROR;
	}
	pp->Protocol = READ_USHORT(buf);

	size -= 2;
	buf += 2;

	if (pp->Protocol == PPP_PROTOCOL_LCP || pp->Protocol == PPP_PROTOCOL_PAP || pp->Protocol == PPP_PROTOCOL_CHAP || pp->Protocol == PPP_PROTOCOL_IPCP || pp->Protocol == PPP_PROTOCOL_IPV6CP || pp->Protocol == PPP_PROTOCOL_EAP)
	{
		pp->IsControl = true;
	}

	pp->Data = Clone(buf, size);
	pp->DataSize = size;

	if (pp->IsControl)
	{
		pp->Lcp = PPPParseLCP(pp->Protocol, pp->Data, pp->DataSize);
		if (pp->Lcp == NULL)
		{
			goto LABEL_ERROR;
		}
	}

	return pp;

LABEL_ERROR:
	FreePPPPacket(pp);
	return NULL;
}

// Analyse the LCP data
PPP_LCP *PPPParseLCP(USHORT protocol, void *data, UINT size)
{
	UCHAR *buf;
	PPP_LCP *c;
	USHORT len;
	bool has_option_list = false;
	// Validate arguments
	if (data == NULL || size == 0)
	{
		return NULL;
	}

	buf = (UCHAR *)data;
	c = ZeroMalloc(sizeof(PPP_LCP));

	c->OptionList = NewListFast(NULL);

	// Code
	if (size < 1)
	{
		goto LABEL_ERROR;
	}
	c->Code = buf[0];
	buf++;
	size--;

	// ID
	if (size < 1)
	{
		goto LABEL_ERROR;
	}
	c->Id = buf[0];
	buf++;
	size--;

	// Length
	if (size < 2)
	{
		goto LABEL_ERROR;
	}
	len = READ_USHORT(buf);
	// Fix bad endianness
	if (len > size)
	{
		USHORT len1 = Swap16(len);
		if (len1 <= size)
		{
			len = len1;
		}
	}
	if (len < 4)
	{
		goto LABEL_ERROR;
	}
	len -= 4;
	buf += 2;
	size -= 2;

	// Options or Data
	if (size < len)
	{
		goto LABEL_ERROR;
	}

	has_option_list = PPP_CODE_IS_WITH_OPTION_LIST(protocol, c->Code);

	if (has_option_list == false)
	{
		c->Data = Clone(buf, size);
		c->DataSize = size;
	}
	else
	{
		// Option List
		while (len >= 1)
		{
			PPP_OPTION o;

			Zero(&o, sizeof(o));

			// Type
			if (len < 1)
			{
				goto LABEL_ERROR;
			}
			o.Type = buf[0];
			buf++;
			len--;

			// Length
			if (len < 1)
			{
				goto LABEL_ERROR;
			}
			o.DataSize = buf[0];
			if (o.DataSize < 2)
			{
				goto LABEL_ERROR;
			}
			o.DataSize -= 2;
			buf++;
			len--;

			// Data
			if (len < o.DataSize)
			{
				goto LABEL_ERROR;
			}
			Copy(o.Data, buf, o.DataSize);
			buf += o.DataSize;
			len -= o.DataSize;

			Add(c->OptionList, Clone(&o, sizeof(o)));
		}
	}

	return c;

LABEL_ERROR:
	FreePPPLCP(c);
	return NULL;
}

// Analyse MS CHAP v2 Response packet
bool PPPParseMSCHAP2ResponsePacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	return PPPParseMSCHAP2ResponsePacketEx(p, pp->Lcp, false);
}
bool PPPParseMSCHAP2ResponsePacketEx(PPP_SESSION *p, PPP_LCP *lcp, bool use_eap)
{
	bool ok = false;

	char client_ip_tmp[256];
	EAP_CLIENT *eap;

	UCHAR client_response_buffer[49];
	UCHAR *client_challenge_16;
	UCHAR *client_response_24;
	char username_tmp[MAX_SIZE];
	IPC *ipc = NULL;
	char id[MAX_SIZE];
	char hub[MAX_SIZE];
	char password[MAX_SIZE];
	char server_challenge_hex[MAX_SIZE];
	char client_challenge_hex[MAX_SIZE];
	char client_response_hex[MAX_SIZE];
	char eap_client_hex[64];
	ETHERIP_ID d;
	UINT error_code;
	UINT64 eap_client_ptr = (UINT64)p->EapClient;

	if (lcp != NULL && lcp->DataSize >= 51)
	{
		BUF *b;
		if (lcp->Id != p->MsChapV2_PacketId)
		{
			Debug("Got incorrect LCP PacketId! Should be 0x%x, got 0x%x\n", p->MsChapV2_PacketId, lcp->Id);
			p->MsChapV2_PacketId = lcp->Id;
		}

		b = NewBuf();

		WriteBuf(b, lcp->Data, lcp->DataSize);
		SeekBuf(b, 0, 0);

		if (ReadBufChar(b) == 49)
		{
			ReadBuf(b, client_response_buffer, 49);

			Zero(username_tmp, sizeof(username_tmp));
			ReadBuf(b, username_tmp, sizeof(username_tmp) - 1);
			Debug("MS-CHAPv2: id=%s\n", username_tmp);

			client_challenge_16 = client_response_buffer + 0;
			client_response_24 = client_response_buffer + 16 + 8;

			Copy(p->MsChapV2_ClientChallenge, client_challenge_16, 16);
			Copy(p->MsChapV2_ClientResponse, client_response_24, 24);

			Zero(id, sizeof(id));
			Zero(hub, sizeof(hub));

			// The user name is divided into the ID and the virtual HUB name
			Zero(&d, sizeof(d));
			PPPParseUsername(p->Cedar, username_tmp, &d);

			StrCpy(id, sizeof(id), d.UserName);
			StrCpy(hub, sizeof(hub), d.HubName);
			Debug("MS-CHAPv2: username=%s, hubname=%s\n", id, hub);

			IPToStr(client_ip_tmp, sizeof(client_ip_tmp), &p->ClientIP);

			// Convert the MS-CHAPv2 data to a password string
			BinToStr(server_challenge_hex, sizeof(server_challenge_hex),
			         p->MsChapV2_ServerChallenge, sizeof(p->MsChapV2_ServerChallenge));
			BinToStr(client_challenge_hex, sizeof(client_challenge_hex),
			         p->MsChapV2_ClientChallenge, sizeof(p->MsChapV2_ClientChallenge));
			BinToStr(client_response_hex, sizeof(client_response_hex),
			         p->MsChapV2_ClientResponse, sizeof(p->MsChapV2_ClientResponse));
			BinToStr(eap_client_hex, sizeof(eap_client_hex),
			         &eap_client_ptr, 8);

			Format(password, sizeof(password), "%s%s:%s:%s:%s:%s",
			       IPC_PASSWORD_MSCHAPV2_TAG,
			       username_tmp,
			       server_challenge_hex,
			       client_challenge_hex,
			       client_response_hex,
			       eap_client_hex);

			// Normal MSCHAPv2 only
			// For EAP-MSCHAPv2, EAP client is created before sending the challenge
			if (p->UseEapRadius && p->EapClient == NULL && use_eap == false)
			{
				Debug("Double MSCHAPv2 creating EAP client\n");
				eap = HubNewEapClient(p->Cedar, hub, client_ip_tmp, id, "L3:PPP", false, NULL, 0);

				// We do not know the user's auth type, so do not fail PPP if eap is null
				if (eap)
				{
					ok = true;
					p->EapClient = eap;
					FreeBuf(b);
					return ok;
				}
			}
			if (p->Ipc == NULL)
			{
				Debug("MSCHAPv2 creating IPC\n");
				ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password, NULL,
				             &error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
				             p->ClientHostname, p->CryptName, false, p->AdjustMss, p->EapClient, NULL,
				             false, IPC_LAYER_3);

				if (ipc != NULL)
				{
					p->Ipc = ipc;

					// Setting user timeouts
					p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout
					p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000;
					if (p->TubeRecv != NULL)
					{
						p->TubeRecv->DataTimeout = p->DataTimeout;
					}
					p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000;
					p->UserConnectionTick = Tick64();

					Copy(p->MsChapV2_ServerResponse, ipc->MsChapV2_ServerResponse, 20);

					ok = true;

					p->AuthOk = true;
				}
			}
			else
			{
				Debug("Got weird packet when we already have an active IPC! Ipc = 0x%x, AuthOk = 0x%x, Status = 0x%x\n", p->Ipc, p->AuthOk, p->PPPStatus);
				ok = p->AuthOk;
			}
		}

		FreeBuf(b);
	}
	else
	{
		Debug("Got invalid MSCHAPv2 packet\n");
	}

	return ok;
}

// Packet building utilities

// Build a PPP packet data
BUF *BuildPPPPacketData(PPP_PACKET *pp)
{
	BUF *ret;
	UCHAR c;
	USHORT us;
	// Validate arguments
	if (pp == NULL)
	{
		return NULL;
	}

	ret = NewBuf();

	// Address
	c = 0xff;
	WriteBuf(ret, &c, 1);

	// Control
	c = 0x03;
	WriteBuf(ret, &c, 1);

	// Protocol
	us = Endian16(pp->Protocol);
	WriteBuf(ret, &us, 2);

	if (pp->IsControl)
	{
		// LCP
		BUF *b = BuildLCPData(pp->Lcp);

		WriteBufBuf(ret, b);

		FreeBuf(b);
	}
	else
	{
		// Data
		WriteBuf(ret, pp->Data, pp->DataSize);
	}

	SeekBuf(ret, 0, 0);

	return ret;
}

// Build the LCP packet data
BUF *BuildLCPData(PPP_LCP *c)
{
	BUF *b;
	UCHAR zero = 0;
	UINT i;
	// Validate arguments
	if (c == NULL)
	{
		return NULL;
	}

	b = NewBuf();

	// Code
	WriteBuf(b, &c->Code, 1);

	// ID
	WriteBuf(b, &c->Id, 1);

	// Length (to be updated later)
	zero = 0;
	WriteBuf(b, &zero, 1);
	WriteBuf(b, &zero, 1);

	if (c->Data == NULL)
	{
		// Option List
		for (i = 0; i < LIST_NUM(c->OptionList); i++)
		{
			PPP_OPTION *o = LIST_DATA(c->OptionList, i);
			UCHAR sz = o->DataSize + 2;

			WriteBuf(b, &o->Type, 1);
			WriteBuf(b, &sz, 1);

			WriteBuf(b, o->Data, o->DataSize);
		}
	}
	else
	{
		// Data
		WriteBuf(b, c->Data, c->DataSize);
	}

	SeekBuf(b, 0, 0);

	// Update Length
	WRITE_USHORT(((UCHAR *)b->Buf) + 2, b->Size);

	return b;
}

// Build the MS CHAP v2 challenge packet
PPP_LCP *BuildMSCHAP2ChallengePacket(PPP_SESSION *p)
{
	PPP_LCP *lcp;
	BUF *b;
	char machine_name[MAX_SIZE];
	UINT64 now = Tick64();

	// Generate a Server Challenge packet of MS-CHAP v2
	GetMachineHostName(machine_name, sizeof(machine_name));

	if (p->EapClient == NULL)
	{
		MsChapV2Server_GenerateChallenge(p->MsChapV2_ServerChallenge);
	}
	else
	{
		Copy(p->MsChapV2_ServerChallenge, p->EapClient->MsChapV2Challenge.Chap_ChallengeValue, 16);
	}

	p->MsChapV2_PacketId = p->NextId++;
	lcp = NewPPPLCP(PPP_CHAP_CODE_CHALLENGE, p->MsChapV2_PacketId);

	b = NewBuf();
	WriteBufChar(b, 16);
	WriteBuf(b, p->MsChapV2_ServerChallenge, sizeof(p->MsChapV2_ServerChallenge));
	WriteBuf(b, machine_name, StrLen(machine_name));
	lcp->Data = Clone(b->Buf, b->Size);
	lcp->DataSize = b->Size;
	FreeBuf(b);

	Debug("Building MS-CHAP v2 Challenge\n");

	return lcp;
}

// IPCP packet utilities

// Set the IP options of PPP to LCP
bool PPPSetIPOptionToLCP(PPP_IPOPTION *o, PPP_LCP *c, bool only_modify)
{
	bool ret = false;
	// Validate arguments
	if (c == NULL || o == NULL)
	{
		return false;
	}

	ret = PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_IP, &o->IpAddress, only_modify);

	PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_DNS1, &o->DnsServer1, only_modify);
	PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_DNS2, &o->DnsServer2, only_modify);
	PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_WINS1, &o->WinsServer1, only_modify);
	PPPSetIPAddressValueToLCP(c, PPP_IPCP_OPTION_WINS2, &o->WinsServer2, only_modify);

	return ret;
}

// Get the IP options of PPP from LCP
bool PPPGetIPOptionFromLCP(PPP_IPOPTION *o, PPP_LCP *c)
{
	bool ret;
	// Validate arguments
	if (c == NULL || o == NULL)
	{
		return false;
	}

	Zero(o, sizeof(PPP_IPOPTION));

	ret = PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_IP, &o->IpAddress);

	PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_DNS1, &o->DnsServer1);
	PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_DNS2, &o->DnsServer2);
	PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_WINS1, &o->WinsServer1);
	PPPGetIPAddressValueFromLCP(c, PPP_IPCP_OPTION_WINS2, &o->WinsServer2);

	return ret;
}

// Set the IP address data to the option list of the LCP
bool PPPSetIPAddressValueToLCP(PPP_LCP *c, UINT type, IP *ip, bool only_modify)
{
	IP ip2;
	UINT ui;
	// Validate arguments
	if (c == NULL || ip == NULL)
	{
		return false;
	}

	ui = IPToUINT(ip);

	if (PPPGetIPAddressValueFromLCP(c, type, &ip2))
	{
		PPP_OPTION *opt;
		opt = PPPGetOptionValue(c, type);

		if (opt != NULL)
		{
			if (IsZeroIP(ip) == false)
			{
				if (CmpIpAddr(&ip2, ip) == 0)
				{
					// No change
					opt->IsAccepted = true;
					opt->IsSupported = true;
				}
				else
				{
					// Changed
					opt->IsAccepted = false;
					opt->IsSupported = true;
					opt->AltDataSize = 4;
					Copy(opt->AltData, &ui, 4);
				}
			}
			else
			{
				// The parameter itself is not supported
				// (if the IP address is 0.0.0.0)
				opt->IsSupported = false;
				opt->IsAccepted = false;
			}
		}

		return true;
	}
	else
	{
		if (IsZeroIP(ip) == false)
		{
			// Add as a new item
			if (only_modify != false)
			{
				return false;
			}
			else
			{
				PPP_OPTION *opt2 = NewPPPOption(type, &ui, 4);
				UCHAR ipstr[MAX_SIZE];

				opt2->IsAccepted = true;
				opt2->IsSupported = true;
				Copy(opt2->AltData, opt2->Data, opt2->DataSize);
				opt2->AltDataSize = opt2->DataSize;

				Add(c->OptionList, opt2);
				IPToStr(ipstr, MAX_SIZE, ip);

				return true;
			}
		}
		else
		{
			return false;
		}
	}
}

// Get the IP address data from the option list of the LCP
bool PPPGetIPAddressValueFromLCP(PPP_LCP *c, UINT type, IP *ip)
{
	PPP_OPTION *opt;
	UINT ui;
	// Validate arguments
	if (c == NULL || ip == NULL)
	{
		return false;
	}

	opt = PPPGetOptionValue(c, type);
	if (opt == NULL)
	{
		return false;
	}

	if (opt->DataSize != 4)
	{
		return false;
	}

	opt->IsSupported = true;

	ui = *((UINT *)opt->Data);

	UINTToIP(ip, ui);

	return true;
}

// EAP packet utilities
bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapSize)
{
	UCHAR *dataBuffer;
	UINT dataSize;
	UINT tlsLength = 0;
	bool isFragmented = false;
	PPP_LCP *lcp;
	PPP_EAP *eap;
	UCHAR flags = PPP_EAP_TLS_FLAG_NONE;
	UINT sizeLeft = 0;
	Debug("Got EAP-TLS size=%i\n", eapSize);
	if (eapSize == 0)
	{
		// This is a broken packet without flags, ignore it
		return false;
	}
	if (eapSize == 1 && eap_packet->Tls.Flags == PPP_EAP_TLS_FLAG_NONE)
	{
		// This is an EAP-TLS message ACK
		if (p->Eap_TlsCtx.CachedBufferSend != NULL)
		{
			// We got an ACK to transmit the next fragmented message
			dataSize = p->Mru1 - 8 - 1 - 1; // Calculating the maximum payload size (without TlsLength)
			sizeLeft = GetMemSize(p->Eap_TlsCtx.CachedBufferSend);
			sizeLeft -= (UINT)(p->Eap_TlsCtx.CachedBufferSendPntr - p->Eap_TlsCtx.CachedBufferSend);

			flags = PPP_EAP_TLS_FLAG_FRAGMENTED; // M flag
			if (dataSize > sizeLeft)
			{
				dataSize = sizeLeft;
				flags = PPP_EAP_TLS_FLAG_NONE; // Clearing the M flag because it is the last packet
			}
			p->Eap_PacketId = p->NextId++;
			lcp = BuildEAPTlsRequest(p->Eap_PacketId, dataSize, flags);
			eap = lcp->Data;
			Copy(eap->Tls.TlsDataWithoutLength, p->Eap_TlsCtx.CachedBufferSendPntr, dataSize);
			p->Eap_TlsCtx.CachedBufferSendPntr += (UINT64)dataSize;

			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
			Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags);

			if (flags == PPP_EAP_TLS_FLAG_NONE)
			{
				// As it is the latest message, we need to cleanup
				Free(p->Eap_TlsCtx.CachedBufferSend);
				p->Eap_TlsCtx.CachedBufferSend = NULL;
				p->Eap_TlsCtx.CachedBufferSendPntr = NULL;
			}
		}
		else if (p->AuthOk == true && p->Ipc != NULL && p->PPPStatus == PPP_STATUS_AUTHENTICATING)
		{
			// The handshake terminated and we received the final ACK, the auth is successful
			// Just send an EAP-Success
			PPP_PACKET* pack;
			UINT identificator = p->Eap_PacketId;

			PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS);
			pack = ZeroMalloc(sizeof(PPP_PACKET));
			pack->IsControl = true;
			pack->Protocol = PPP_PROTOCOL_EAP;
			lcp = NewPPPLCP(PPP_EAP_CODE_SUCCESS, identificator);
			pack->Lcp = lcp;
			Debug("Sent EAP-TLS size=%i SUCCESS\n", lcp->DataSize);
			if (PPPSendPacketAndFree(p, pack) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
			return true;
		}
		else if (p->Eap_TlsCtx.ClientCert.X == NULL)
		{
			// Some clients needs a little help it seems - namely VPN Client Pro on Android
			flags |= PPP_EAP_TLS_FLAG_SSLSTARTED;
			p->Eap_PacketId = p->NextId++;
			lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags);
			PPPSetStatus(p, PPP_STATUS_AUTHENTICATING);
			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
			Debug("Sent EAP-TLS size=%i\n", lcp->DataSize);
		}
		return true;
	}
	dataBuffer = eap_packet->Tls.TlsDataWithoutLength;
	dataSize = eapSize - 1;
	if (eap_packet->Tls.Flags & PPP_EAP_TLS_FLAG_TLS_LENGTH && dataSize >= 4)
	{
		dataBuffer = eap_packet->Tls.TlsDataWithLength.Data;
		dataSize -= 4;
		tlsLength = Endian32(eap_packet->Tls.TlsDataWithLength.TlsLength);
		// Let's just clamp it to a safe size to avoid DoS (GHSA-q5g3-qhc6-pr3h)
		tlsLength = MIN(tlsLength, PPP_MRU_MAX * 10);
	}
	/*Debug("=======RECV EAP-TLS PACKET DUMP=======\n");
	for (i = 0; i < dataSize; i++)
	{
		if (i > 0) printf(" ");
		Debug("%02X", dataBuffer[i]);
	}
	Debug("\n=======RECV EAP-TLS PACKET DUMP END=======\n");*/
	if (eap_packet->Tls.Flags & PPP_EAP_TLS_FLAG_FRAGMENTED)
	{
		isFragmented = true;
	}

	if (p->PPPStatus == PPP_STATUS_AUTHENTICATING)
	{
		// First we initialize the SslPipe if it is not already inited
		if (p->Eap_TlsCtx.SslPipe == NULL)
		{
			p->Eap_TlsCtx.Dh = DhNewFromBits(DH_PARAM_BITS_DEFAULT);
			p->Eap_TlsCtx.SslPipe = NewSslPipeEx3(true, p->Cedar->ServerX, p->Cedar->ServerK, p->Cedar->ServerChain, p->Eap_TlsCtx.Dh, true, &(p->Eap_TlsCtx.ClientCert), p->Eap_TlsCtx.Tls13SessionTicketsCount, p->Eap_TlsCtx.DisableTls13);
			if (p->Eap_TlsCtx.SslPipe == NULL)
			{
				Debug("EAP-TLS: NewSslPipeEx3 failed\n");
				PPPSetStatus(p, PPP_STATUS_FAIL);
				return false;
			}
		}

		// If the current frame is fragmented, or it is a possible last of a fragmented series, bufferize it
		if (isFragmented || p->Eap_TlsCtx.CachedBufferRecv != NULL)
		{
			if (p->Eap_TlsCtx.CachedBufferRecv == NULL && tlsLength > 0)
			{
				p->Eap_TlsCtx.CachedBufferRecv = ZeroMalloc(MAX(dataSize, tlsLength));
				p->Eap_TlsCtx.CachedBufferRecvPntr = p->Eap_TlsCtx.CachedBufferRecv;
			}
			else if (p->Eap_TlsCtx.CachedBufferRecv == NULL)
			{
				p->Eap_TlsCtx.CachedBufferRecv = ZeroMalloc(MAX(dataSize, PPP_MRU_MAX * 10)); // 10 MRUs should be enough
				p->Eap_TlsCtx.CachedBufferRecvPntr = p->Eap_TlsCtx.CachedBufferRecv;
			}
			sizeLeft = GetMemSize(p->Eap_TlsCtx.CachedBufferRecv);
			sizeLeft -= (UINT)(p->Eap_TlsCtx.CachedBufferRecvPntr - p->Eap_TlsCtx.CachedBufferRecv);

			if (sizeLeft > 0)
			{
				Copy(p->Eap_TlsCtx.CachedBufferRecvPntr, dataBuffer, MIN(sizeLeft, dataSize));

				p->Eap_TlsCtx.CachedBufferRecvPntr += MIN(sizeLeft, dataSize);
			}
		}

		// If we got a cached buffer, we should feed the FIFOs via it
		if (p->Eap_TlsCtx.CachedBufferRecv != NULL)
		{
			dataBuffer = p->Eap_TlsCtx.CachedBufferRecv;
			dataSize = GetMemSize(p->Eap_TlsCtx.CachedBufferRecv);
			if (dataSize == MAX_BUFFERING_PACKET_SIZE)
			{
				dataSize = (UINT)(p->Eap_TlsCtx.CachedBufferRecvPntr - p->Eap_TlsCtx.CachedBufferRecv);
			}
		}

		// Just acknoweldge that we buffered the fragmented data
		if (isFragmented)
		{
			p->Eap_PacketId = p->NextId++;
			PPP_LCP *lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, PPP_EAP_TLS_FLAG_NONE);
			if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
			{
				PPPSetStatus(p, PPP_STATUS_FAIL);
				WHERE;
				return false;
			}
			Debug("Sent EAP-TLS size=%i\n", lcp->DataSize);
			return true;
		}
		else
		{
			bool syncOk;
			/*Debug("=======RECV EAP-TLS FIFO DUMP=======\n");
			for (i = 0; i < dataSize; i++)
			{
				if (i > 0) printf(" ");
				Debug("%02X", dataBuffer[i]);
			}
			Debug("\n=======RECV EAP-TLS PACKET FIFO END=======\n");*/
			WriteFifo(p->Eap_TlsCtx.SslPipe->RawIn->SendFifo, dataBuffer, dataSize);
			syncOk = SyncSslPipe(p->Eap_TlsCtx.SslPipe);

			// Delete the cached buffer after we fed it into the pipe
			if (p->Eap_TlsCtx.CachedBufferRecv != NULL)
			{
				Free(p->Eap_TlsCtx.CachedBufferRecv);
				p->Eap_TlsCtx.CachedBufferRecv = NULL;
				p->Eap_TlsCtx.CachedBufferRecvPntr = NULL;
			}

			// Special case - we attempt to restart downgrading TLS settings
			if (!syncOk && (p->Eap_TlsCtx.DisableTls13 == false || p->Eap_TlsCtx.Tls13SessionTicketsCount == 0))
			{
				// If we authenticated earlier, deauthenticate back
				p->DataTimeout = PPP_DATA_TIMEOUT;
				p->PacketRecvTimeout = PPP_PACKET_RECV_TIMEOUT;
				p->UserConnectionTimeout = 0;
				p->UserConnectionTick = 0;
				if (p->Ipc != NULL)
				{
					FreeIPC(p->Ipc);
					p->Ipc = NULL;
					p->AuthOk = false;
				}

				FreeSslPipe(p->Eap_TlsCtx.SslPipe);
				DhFree(p->Eap_TlsCtx.Dh);
				p->Eap_TlsCtx.SslPipe = NULL;
				p->Eap_TlsCtx.Dh = NULL;
				if (p->Eap_TlsCtx.Tls13SessionTicketsCount == 0)
				{
					p->Eap_TlsCtx.DisableTls13 = true;
				}
				else
				{
					p->Eap_TlsCtx.Tls13SessionTicketsCount = 0;
				}
				flags |= PPP_EAP_TLS_FLAG_SSLSTARTED;
				p->Eap_PacketId = p->NextId++;
				lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags);
				if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
				Debug("EAP-TLS: Restarting the handshake! Tls13SessionTicketsCount = %d, DisableTls13 = %d\n", p->Eap_TlsCtx.Tls13SessionTicketsCount, p->Eap_TlsCtx.DisableTls13);
				Debug("Sent EAP-TLS size=%i\n", lcp->DataSize);
				return false;
			}

			// If on the server we have enough data to authenticate, let's do this before we continue with the handshake
			// Check if we received the client certificate and the handshake is finished
			if (p->Eap_TlsCtx.ClientCert.X != NULL && p->Ipc == NULL)
			{
				IPC* ipc;
				UINT error_code;

				if (p->Eap_MatchUserByCert)
				{
					HUB *hub;
					bool found = false;

					LockHubList(p->Cedar);
					{
						hub = GetHub(p->Cedar, p->Eap_Identity.HubName);
					}
					UnlockHubList(p->Cedar);

					if (hub != NULL)
					{
						AcLock(hub);
						{
							USER* user = AcGetUserByCert(hub, p->Eap_TlsCtx.ClientCert.X);
							if (user != NULL)
							{
								StrCpy(p->Eap_Identity.UserName, sizeof(p->Eap_Identity.UserName), user->Name);
								found = true;
								ReleaseUser(user);
							}
						}
						AcUnlock(hub);
						ReleaseHub(hub);
						// Making sure the stale pntr is cleared and can't be reused (GHSA-7437-282p-7465)
						hub = NULL;
					}

					if (found == false)
					{
						PPP_PACKET* pack;
						UINT identificator = p->Eap_PacketId;

						PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);

						pack = ZeroMalloc(sizeof(PPP_PACKET));
						pack->IsControl = true;
						pack->Protocol = PPP_PROTOCOL_EAP;
						lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator);
						pack->Lcp = lcp;
						Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize);
						if (PPPSendPacketAndFree(p, pack) == false)
						{
							PPPSetStatus(p, PPP_STATUS_FAIL);
							WHERE;
							return false;
						}
						return false;
					}
				}

				ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, p->Eap_Identity.HubName, p->Eap_Identity.UserName, "", NULL,
					&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
					p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, p->Eap_TlsCtx.ClientCert.X,
					false, IPC_LAYER_3);

				// We use the SAM authentication here, because the handshake can still fail at this point
				if (ipc != NULL)
				{
					// Setting user timeouts
					p->Ipc = ipc;
					p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout
					p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000;
					if (p->TubeRecv != NULL)
					{
						p->TubeRecv->DataTimeout = p->DataTimeout;
					}
					p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000;
					p->UserConnectionTick = Tick64();

					p->AuthOk = true;

					if (p->Eap_TlsCtx.SslPipe->SslVersion == TLS1_3_VERSION)
					{
						// Before starting IPC and sending an EAP-Success in case of TLS 1.3 we need to send a 0x00 data packet as per RFC 9190
						char zeroPacket[1] = { 0 };
						WriteFifo(p->Eap_TlsCtx.SslPipe->SslInOut->SendFifo, zeroPacket, sizeof(zeroPacket));
						if (!SyncSslPipe(p->Eap_TlsCtx.SslPipe))
						{
							PPPSetStatus(p, PPP_STATUS_FAIL);
							WHERE;
							return false;
						}
					}
				}
				else
				{
					PPP_PACKET* pack;
					UINT identificator = p->Eap_PacketId;

					PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);

					pack = ZeroMalloc(sizeof(PPP_PACKET));
					pack->IsControl = true;
					pack->Protocol = PPP_PROTOCOL_EAP;
					lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator);
					pack->Lcp = lcp;
					Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize);
					if (PPPSendPacketAndFree(p, pack) == false)
					{
						PPPSetStatus(p, PPP_STATUS_FAIL);
						WHERE;
						return false;
					}
					return false;
				}
			}

			// We continue the TLS handshake
			if (p->Eap_TlsCtx.SslPipe->IsDisconnected == false)
			{
				dataSize = FifoSize(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo);
				// Do we need to send a fragmented packet?
				if (dataSize > p->Mru1 - 8 - 1 - 1)
				{
					if (p->Eap_TlsCtx.CachedBufferSend == NULL)
					{
						p->Eap_TlsCtx.CachedBufferSend = ZeroMalloc(dataSize);
						p->Eap_TlsCtx.CachedBufferSendPntr = p->Eap_TlsCtx.CachedBufferSend;
					}
					ReadFifo(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo, p->Eap_TlsCtx.CachedBufferSend, dataSize);

					// Now send data from the cached buffer with set fragmentation flag and also total TLS Size
					tlsLength = dataSize;
					dataSize = p->Mru1 - 8 - 1 - 1 - 4; // Calculating the maximum payload size (adjusting for including TlsLength)
					flags = PPP_EAP_TLS_FLAG_TLS_LENGTH; // L flag
					flags |= PPP_EAP_TLS_FLAG_FRAGMENTED; // M flag
					p->Eap_PacketId = p->NextId++;
					lcp = BuildEAPTlsRequest(p->Eap_PacketId, dataSize, flags);
					eap = lcp->Data;
					eap->Tls.TlsDataWithLength.TlsLength = Endian32(tlsLength);
					Copy(eap->Tls.TlsDataWithLength.Data, p->Eap_TlsCtx.CachedBufferSend, dataSize);
					p->Eap_TlsCtx.CachedBufferSendPntr += dataSize;
					if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
					{
						PPPSetStatus(p, PPP_STATUS_FAIL);
						WHERE;
						return false;
					}
					Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags);
					return true;
				}
				else if (dataSize > 0 || p->Eap_TlsCtx.ClientCert.X == NULL)
				{
					p->Eap_PacketId = p->NextId++;
					lcp = BuildEAPTlsRequest(p->Eap_PacketId, dataSize, 0);
					eap = lcp->Data;
					ReadFifo(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo, &(eap->Tls.TlsDataWithoutLength), dataSize);
					if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false)
					{
						PPPSetStatus(p, PPP_STATUS_FAIL);
						WHERE;
						return false;
					}
					Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags);
					return true;
				}
			}

			

			// If we end up here, we got problems, send an EAP failure
			if (p->Eap_TlsCtx.SslPipe->IsDisconnected)
			{
				PPP_PACKET* pack;
				UINT identificator = p->Eap_PacketId;

				PPPSetStatus(p, PPP_STATUS_AUTH_FAIL);

				pack = ZeroMalloc(sizeof(PPP_PACKET));
				pack->IsControl = true;
				pack->Protocol = PPP_PROTOCOL_EAP;
				lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator);
				pack->Lcp = lcp;
				Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize);
				if (PPPSendPacketAndFree(p, pack) == false)
				{
					PPPSetStatus(p, PPP_STATUS_FAIL);
					WHERE;
					return false;
				}
				return false;
			}
		}
	}
	else
	{
		Debug("Got an EAP_TLS packet when not authenticating, ignoring...\n");
	}
	return false;
}

PPP_LCP *BuildEAPPacketEx(UCHAR code, UCHAR id, UCHAR type, UINT datasize)
{
	PPP_EAP *eap_packet;
	PPP_LCP *lcp_packet;
	UINT lcpDatasize;
	lcpDatasize = datasize + sizeof(UCHAR);
	eap_packet = ZeroMalloc(lcpDatasize);
	eap_packet->Type = type;
	lcp_packet = NewPPPLCP(code, id);
	lcp_packet->Data = eap_packet;
	lcp_packet->DataSize = lcpDatasize;
	return lcp_packet;
}

PPP_LCP *BuildEAPTlsPacketEx(UCHAR code, UCHAR id, UCHAR type, UINT datasize, UCHAR flags)
{
	PPP_LCP *lcp_packet;
	PPP_EAP *eap_packet;
	UINT tls_datasize = datasize + sizeof(UCHAR);
	if (flags & PPP_EAP_TLS_FLAG_TLS_LENGTH)
	{
		tls_datasize += sizeof(UINT);
	}
	lcp_packet = BuildEAPPacketEx(code, id, type, tls_datasize);
	eap_packet = lcp_packet->Data;
	eap_packet->Tls.Flags = flags;
	return lcp_packet;
}

PPP_LCP *BuildEAPTlsRequest(UCHAR id, UINT datasize, UCHAR flags)
{
	return BuildEAPTlsPacketEx(PPP_EAP_CODE_REQUEST, id, PPP_EAP_TYPE_TLS, datasize, flags);
}

// Other packet utilities

// Get the option value
PPP_OPTION *PPPGetOptionValue(PPP_LCP *c, UCHAR type)
{
	UINT i;
	// Validate arguments
	if (c == NULL)
	{
		return NULL;
	}

	for (i = 0; i < LIST_NUM(c->OptionList); i++)
	{
		PPP_OPTION *t = LIST_DATA(c->OptionList, i);

		if (t->Type == type)
		{
			return t;
		}
	}

	return NULL;
}

// Check whether the Virtual HUB with the specified name exist?
bool IsHubExistsWithLock(CEDAR *cedar, char *hubname)
{
	bool ret = false;
	// Validate arguments
	if (cedar == NULL || hubname == NULL)
	{
		return false;
	}

	LockList(cedar->HubList);
	{
		ret = IsHub(cedar, hubname);
	}
	UnlockList(cedar->HubList);

	return ret;
}

// Sets the PPP status without overwriting the FAIL status
void PPPSetStatus(PPP_SESSION *p, UINT status)
{
	if (status == PPP_STATUS_FAIL)
	{
		Debug("SETTING PPP_STATUS_FAIL!!!\n");
	}
	if (PPP_STATUS_IS_UNAVAILABLE(p->PPPStatus) == false || PPP_STATUS_IS_UNAVAILABLE(status))
	{
		p->PPPStatus = status;
	}
}


// Memory freeing functions

// Release the PPP session
void FreePPPSession(PPP_SESSION *p)
{
	UINT i;
	// Validate arguments
	if (p == NULL)
	{
		return;
	}

	// Release the memory
	for (i = 0; i < LIST_NUM(p->RecvPacketList); i++)
	{
		PPP_PACKET *pp = LIST_DATA(p->RecvPacketList, i);

		FreePPPPacket(pp);
	}
	ReleaseList(p->RecvPacketList);

	for (i = 0; i < LIST_NUM(p->SentReqPacketList); i++)
	{
		PPP_REQUEST_RESEND *t = LIST_DATA(p->SentReqPacketList, i);
		FreePPPPacket(t->Packet);

		Free(t);
	}

	ReleaseList(p->SentReqPacketList);

	for (i = 0; i < LIST_NUM(p->DelayedPackets); i++)
	{
		PPP_DELAYED_PACKET *t = LIST_DATA(p->DelayedPackets, i);
		FreePPPPacket(t->Packet);

		Free(t);
	}

	ReleaseList(p->DelayedPackets);

	if (p->CurrentPacket != NULL)
	{
		FreePPPPacket(p->CurrentPacket);
	}

	if (p->TubeRecv != NULL)
	{
		// Record the PPP disconnect reason code for L2TP
		p->TubeRecv->IntParam1 = p->DisconnectCauseCode;
		p->TubeRecv->IntParam2 = p->DisconnectCauseDirection;
	}

	// Freeing EAP-TLS context
	if (p->Eap_TlsCtx.CachedBufferRecv != NULL)
	{
		Free(p->Eap_TlsCtx.CachedBufferRecv);
	}
	if (p->Eap_TlsCtx.CachedBufferSend != NULL)
	{
		Free(p->Eap_TlsCtx.CachedBufferSend);
	}
	if (p->Eap_TlsCtx.SslPipe != NULL)
	{
		FreeSslPipe(p->Eap_TlsCtx.SslPipe);
	}
	if (p->Eap_TlsCtx.ClientCert.X != NULL)
	{
		FreeX(p->Eap_TlsCtx.ClientCert.X);
	}
	if (p->Eap_TlsCtx.Dh != NULL)
	{
		DhFree(p->Eap_TlsCtx.Dh);
	}


	FreeTubeFlushList(p->FlushList);

	TubeDisconnect(p->TubeRecv);
	TubeDisconnect(p->TubeSend);

	ReleaseCedar(p->Cedar);

	ReleaseTube(p->TubeRecv);
	ReleaseTube(p->TubeSend);

	if (p->Ipc != NULL)
	{
		FreeIPC(p->Ipc);
	}

	PPPFreeEapClient(p);

	Free(p);
}

// Release the LCP
void FreePPPLCP(PPP_LCP *c)
{
	// Validate arguments
	if (c == NULL)
	{
		return;
	}

	FreePPPOptionList(c->OptionList);

	Free(c->Data);

	Free(c);
}

// Release the PPP options list
void FreePPPOptionList(LIST *o)
{
	UINT i;
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	for (i = 0; i < LIST_NUM(o); i++)
	{
		PPP_OPTION *t = LIST_DATA(o, i);

		Free(t);
	}

	ReleaseList(o);
}

// Release the PPP packet
void FreePPPPacket(PPP_PACKET *pp)
{
	FreePPPPacketEx(pp, false);
}

void FreePPPPacketEx(PPP_PACKET *pp, bool no_free_struct)
{
	// Validate arguments
	if (pp == NULL)
	{
		return;
	}

	FreePPPLCP(pp->Lcp);

	Free(pp->Data);

	if (no_free_struct == false)
	{
		Free(pp);
	}
}

// Free the associated EAP client
void PPPFreeEapClient(PPP_SESSION *p)
{
	if (p == NULL)
	{
		return;
	}

	if (p->EapClient != NULL)
	{
		ReleaseEapClient(p->EapClient);
		p->EapClient = NULL;
	}
}


// Utility functions used not only in PPP stack

// Separate into the user name and the Virtual HUB name by analyzing the string
bool PPPParseUsername(CEDAR *cedar, char *src_username, ETHERIP_ID *dst)
{
	UINT i, len, last_at, first_en;
	char token1[MAX_SIZE];	// username
	char token2[MAX_SIZE];	// hub_name
	char src[MAX_SIZE];
	// Validate arguments
	Zero(dst, sizeof(ETHERIP_ID));
	if (cedar == NULL || dst == NULL)
	{
		return false;
	}

	StrCpy(src, sizeof(src), src_username);
	Trim(src);

	// Search for the first "\\" in the string
	len = StrLen(src);

	first_en = SearchStrEx(src, "\\", 0, true);

	if (first_en != INFINITE && first_en >= 1 && (first_en < (len - 1)))
	{
		StrCpy(token1, sizeof(token1), src + first_en + 1);
		StrCpy(token2, sizeof(token2), src);
		token2[first_en] = 0;

		// Confirm whether the hubname exists if the virtual HUB name is
		// specified like as hubname\username
		if (IsHubExistsWithLock(cedar, token2) == false)
		{
			// If the hubname does not exist, restore to the original name
			StrCpy(token1, sizeof(token1), src);
			ClearStr(token2, sizeof(token2));
		}
	}
	else
	{
		// Search for the separator character's last position in the string
		len = StrLen(src);
		last_at = INFINITE;
		for (i = 0; i < len; i++)
		{
			char c = src[i];

			if (c == cedar->UsernameHubSeparator)
			{
				last_at = i;
			}
		}

		Zero(token1, sizeof(token1));
		Zero(token2, sizeof(token2));

		if (last_at == INFINITE)
		{
			// The separator character is not specifiedd
			StrCpy(token1, sizeof(token1), src);
		}
		else
		{
			StrCpy(token1, sizeof(token1), src);
			token1[last_at] = 0;

			StrCpy(token2, sizeof(token2), src + last_at + 1);
		}

		// Check whether such Virtual HUB exists If the virtual HUB name is specified
		if (IsEmptyStr(token2) == false)
		{
			if (IsHubExistsWithLock(cedar, token2) == false)
			{
				// Because the specified virtual HUB name doesn't exist, it's considered to be a part of the user name
				StrCpy(token1, sizeof(token1), src);

				ClearStr(token2, sizeof(token2));
			}
		}
	}

	if (IsEmptyStr(token2))
	{
		// Select the default Virtual HUB if the Virtual HUB name is not specified
		StrCpy(token2, sizeof(token2), SERVER_DEFAULT_HUB_NAME);
		if (cedar->Server != NULL && cedar->Server->IPsecServer != NULL)
		{
			Lock(cedar->Server->IPsecServer->LockSettings);
			{
				IPsecNormalizeServiceSetting(cedar->Server->IPsecServer);

				StrCpy(token2, sizeof(token2), cedar->Server->IPsecServer->Services.L2TP_DefaultHub);
			}
			Unlock(cedar->Server->IPsecServer->LockSettings);
		}

	}

	// Return the results
	StrCpy(dst->HubName, sizeof(dst->HubName), token2);
	StrCpy(dst->UserName, sizeof(dst->UserName), token1);

	return true;
}

// Generate the NT hash of the password
void GenerateNtPasswordHash(UCHAR *dst, char *password)
{
	UCHAR *tmp;
	UINT tmp_size;
	UINT i, len;
	// Validate arguments
	if (dst == NULL || password == NULL)
	{
		return;
	}

	// Generate a Unicode password
	len = StrLen(password);
	tmp_size = len * 2;

	tmp = ZeroMalloc(tmp_size);

	for (i = 0; i < len; i++)
	{
		tmp[i * 2] = password[i];
	}

	// Hashing
	HashMd4(dst, tmp, tmp_size);

	Free(tmp);
}

// Generate the MS-CHAPv2 server-side challenge
void MsChapV2Server_GenerateChallenge(UCHAR *dst)
{
	// Validate arguments
	if (dst == NULL)
	{
		return;
	}

	Rand(dst, 16);
}

// Generate the MS-CHAPv2 client-side challenge
void MsChapV2Client_GenerateChallenge(UCHAR *dst)
{
	// Validate arguments
	if (dst == NULL)
	{
		return;
	}

	Rand(dst, 16);
}

// Generate a 8 bytes challenge
void MsChapV2_GenerateChallenge8(UCHAR *dst, UCHAR *client_challenge, UCHAR *server_challenge, char *username)
{
	BUF *b;
	UCHAR hash[SHA1_SIZE];
	char username2[MAX_SIZE];
	char domainname2[MAX_SIZE];
	// Validate arguments
	if (dst == NULL || client_challenge == NULL || server_challenge == NULL)
	{
		return;
	}

	b = NewBuf();

	WriteBuf(b, client_challenge, 16);
	WriteBuf(b, server_challenge, 16);

	ParseNtUsername(username, username2, sizeof(username2), domainname2, sizeof(domainname2), true);

	if (IsEmptyStr(username2) == false)
	{
		WriteBuf(b, username2, StrLen(username2));
	}

	Sha1(hash, b->Buf, b->Size);

	FreeBuf(b);

	Copy(dst, hash, 8);
}

// Generate the MS-CHAPv2 client response
void MsChapV2Client_GenerateResponse(UCHAR *dst, UCHAR *challenge8, UCHAR *nt_password_hash)
{
	UCHAR password_hash_2[21];
	UCHAR key1[8], key2[8], key3[8];
	// Validate arguments
	if (dst == NULL || challenge8 == NULL || nt_password_hash == NULL)
	{
		return;
	}

	Zero(password_hash_2, sizeof(password_hash_2));
	Copy(password_hash_2, nt_password_hash, 16);

	Zero(key1, sizeof(key1));
	Zero(key2, sizeof(key2));
	Zero(key3, sizeof(key3));

	Copy(key1, password_hash_2 + 0, 7);
	Copy(key2, password_hash_2 + 7, 7);
	Copy(key3, password_hash_2 + 14, 7);

	DesEcbEncrypt(dst + 0, challenge8, key1);
	DesEcbEncrypt(dst + 8, challenge8, key2);
	DesEcbEncrypt(dst + 16, challenge8, key3);
}

// Generate a hash of the hash of the NT password
void GenerateNtPasswordHashHash(UCHAR *dst_hash, UCHAR *src_hash)
{
	// Validate arguments
	if (dst_hash == NULL || src_hash == NULL)
	{
		return;
	}

	HashMd4(dst_hash, src_hash, 16);
}

// Generate the MS-CHAPv2 server response
void MsChapV2Server_GenerateResponse(UCHAR *dst, UCHAR *nt_password_hash_hash, UCHAR *client_response, UCHAR *challenge8)
{
	UCHAR digest[SHA1_SIZE];
	BUF *b;
	char *magic1 = "Magic server to client signing constant";
	char *magic2 = "Pad to make it do more than one iteration";
	// Validate arguments
	if (dst == NULL || nt_password_hash_hash == NULL || client_response == NULL || challenge8 == NULL)
	{
		return;
	}

	b = NewBuf();
	WriteBuf(b, nt_password_hash_hash, 16);
	WriteBuf(b, client_response, 24);
	WriteBuf(b, magic1, StrLen(magic1));
	Sha1(digest, b->Buf, b->Size);
	FreeBuf(b);

	b = NewBuf();
	WriteBuf(b, digest, sizeof(digest));
	WriteBuf(b, challenge8, 8);
	WriteBuf(b, magic2, StrLen(magic2));
	Sha1(dst, b->Buf, b->Size);
	FreeBuf(b);
}

// Verify whether the password matches one that is specified by the user in the MS-CHAPv2
bool MsChapV2VerityPassword(IPC_MSCHAP_V2_AUTHINFO *d, char *password)
{
	UCHAR ntlm_hash[MD5_SIZE];
	UCHAR challenge8[8];
	UCHAR client_response[24];
	// Validate arguments
	if (d == NULL || password == NULL)
	{
		return false;
	}

	GenerateNtPasswordHash(ntlm_hash, password);
	MsChapV2_GenerateChallenge8(challenge8, d->MsChapV2_ClientChallenge, d->MsChapV2_ServerChallenge, d->MsChapV2_PPPUsername);
	MsChapV2Client_GenerateResponse(client_response, challenge8, ntlm_hash);

	if (Cmp(client_response, d->MsChapV2_ClientResponse, 24) != 0)
	{
		return false;
	}

	return true;
}

// Estimate the password in the brute force for the request packet of MS-CHAPv2
char *MsChapV2DoBruteForce(IPC_MSCHAP_V2_AUTHINFO *d, LIST *password_list)
{
	UINT i;
	// Validate arguments
	if (d == NULL || password_list == NULL)
	{
		return NULL;
	}

	for (i = 0; i < LIST_NUM(password_list); i++)
	{
		char *s = LIST_DATA(password_list, i);
		char tmp[MAX_SIZE];
		UINT j, max;
		UINT len;

		StrCpy(tmp, sizeof(tmp), s);

		len = StrLen(tmp);
		max = Power(2, MIN(len, 9));

		for (j = 0; j < max; j++)
		{
			SetStrCaseAccordingToBits(tmp, j);
			if (MsChapV2VerityPassword(d, tmp))
			{
				return CopyStr(tmp);
			}
		}
	}

	return NULL;
}


