// SoftEther VPN Source Code - Stable Edition Repository
// Cedar Communication Module
// 
// SoftEther VPN Server, Client and Bridge are free software under the Apache License, Version 2.0.
// 
// Copyright (c) Daiyuu Nobori.
// Copyright (c) SoftEther VPN Project, University of Tsukuba, Japan.
// Copyright (c) SoftEther Corporation.
// Copyright (c) all contributors on SoftEther VPN project in GitHub.
// 
// All Rights Reserved.
// 
// http://www.softether.org/
// 
// This stable branch is officially managed by Daiyuu Nobori, the owner of SoftEther VPN Project.
// Pull requests should be sent to the Developer Edition Master Repository on https://github.com/SoftEtherVPN/SoftEtherVPN
// 
// License: The Apache License, Version 2.0
// https://www.apache.org/licenses/LICENSE-2.0
// 
// DISCLAIMER
// ==========
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// 
// THIS SOFTWARE IS DEVELOPED IN JAPAN, AND DISTRIBUTED FROM JAPAN, UNDER
// JAPANESE LAWS. YOU MUST AGREE IN ADVANCE TO USE, COPY, MODIFY, MERGE, PUBLISH,
// DISTRIBUTE, SUBLICENSE, AND/OR SELL COPIES OF THIS SOFTWARE, THAT ANY
// JURIDICAL DISPUTES WHICH ARE CONCERNED TO THIS SOFTWARE OR ITS CONTENTS,
// AGAINST US (SOFTETHER PROJECT, SOFTETHER CORPORATION, DAIYUU NOBORI OR OTHER
// SUPPLIERS), OR ANY JURIDICAL DISPUTES AGAINST US WHICH ARE CAUSED BY ANY KIND
// OF USING, COPYING, MODIFYING, MERGING, PUBLISHING, DISTRIBUTING, SUBLICENSING,
// AND/OR SELLING COPIES OF THIS SOFTWARE SHALL BE REGARDED AS BE CONSTRUED AND
// CONTROLLED BY JAPANESE LAWS, AND YOU MUST FURTHER CONSENT TO EXCLUSIVE
// JURISDICTION AND VENUE IN THE COURTS SITTING IN TOKYO, JAPAN. YOU MUST WAIVE
// ALL DEFENSES OF LACK OF PERSONAL JURISDICTION AND FORUM NON CONVENIENS.
// PROCESS MAY BE SERVED ON EITHER PARTY IN THE MANNER AUTHORIZED BY APPLICABLE
// LAW OR COURT RULE.
// 
// USE ONLY IN JAPAN. DO NOT USE THIS SOFTWARE IN ANOTHER COUNTRY UNLESS YOU HAVE
// A CONFIRMATION THAT THIS SOFTWARE DOES NOT VIOLATE ANY CRIMINAL LAWS OR CIVIL
// RIGHTS IN THAT PARTICULAR COUNTRY. USING THIS SOFTWARE IN OTHER COUNTRIES IS
// COMPLETELY AT YOUR OWN RISK. THE SOFTETHER VPN PROJECT HAS DEVELOPED AND
// DISTRIBUTED THIS SOFTWARE TO COMPLY ONLY WITH THE JAPANESE LAWS AND EXISTING
// CIVIL RIGHTS INCLUDING PATENTS WHICH ARE SUBJECTS APPLY IN JAPAN. OTHER
// COUNTRIES' LAWS OR CIVIL RIGHTS ARE NONE OF OUR CONCERNS NOR RESPONSIBILITIES.
// WE HAVE NEVER INVESTIGATED ANY CRIMINAL REGULATIONS, CIVIL LAWS OR
// INTELLECTUAL PROPERTY RIGHTS INCLUDING PATENTS IN ANY OF OTHER 200+ COUNTRIES
// AND TERRITORIES. BY NATURE, THERE ARE 200+ REGIONS IN THE WORLD, WITH
// DIFFERENT LAWS. IT IS IMPOSSIBLE TO VERIFY EVERY COUNTRIES' LAWS, REGULATIONS
// AND CIVIL RIGHTS TO MAKE THE SOFTWARE COMPLY WITH ALL COUNTRIES' LAWS BY THE
// PROJECT. EVEN IF YOU WILL BE SUED BY A PRIVATE ENTITY OR BE DAMAGED BY A
// PUBLIC SERVANT IN YOUR COUNTRY, THE DEVELOPERS OF THIS SOFTWARE WILL NEVER BE
// LIABLE TO RECOVER OR COMPENSATE SUCH DAMAGES, CRIMINAL OR CIVIL
// RESPONSIBILITIES. NOTE THAT THIS LINE IS NOT LICENSE RESTRICTION BUT JUST A
// STATEMENT FOR WARNING AND DISCLAIMER.
// 
// READ AND UNDERSTAND THE 'WARNING.TXT' FILE BEFORE USING THIS SOFTWARE.
// SOME SOFTWARE PROGRAMS FROM THIRD PARTIES ARE INCLUDED ON THIS SOFTWARE WITH
// LICENSE CONDITIONS WHICH ARE DESCRIBED ON THE 'THIRD_PARTY.TXT' FILE.
// 
// 
// SOURCE CODE CONTRIBUTION
// ------------------------
// 
// Your contribution to SoftEther VPN Project is much appreciated.
// Please send patches to us through GitHub.
// Read the SoftEther VPN Patch Acceptance Policy in advance:
// http://www.softether.org/5-download/src/9.patch
// 
// 
// DEAR SECURITY EXPERTS
// ---------------------
// 
// If you find a bug or a security vulnerability please kindly inform us
// about the problem immediately so that we can fix the security problem
// to protect a lot of users around the world as soon as possible.
// 
// Our e-mail address for security reports is:
// softether-vpn-security [at] softether.org
// 
// Please note that the above e-mail address is not a technical support
// inquiry address. If you need technical assistance, please visit
// http://www.softether.org/ and ask your question on the users forum.
// 
// Thank you for your cooperation.
// 
// 
// NO MEMORY OR RESOURCE LEAKS
// ---------------------------
// 
// The memory-leaks and resource-leaks verification under the stress
// test has been passed before release this source code.


// IPsec_PPP.c
// PPP protocol stack

#include "CedarPch.h"

// PPP thread
void PPPThread(THREAD *thread, void *param)
{
	PPP_SESSION *p = (PPP_SESSION *)param;
	UINT i;
	PPP_LCP *c;
	USHORT us;
	UINT ui;
	USHORT next_protocol = 0;
	bool ret = false;
	char ipstr1[128], ipstr2[128];
	bool established = false;
	// Validate arguments
	if (thread == NULL || param == NULL)
	{
		return;
	}

	// Initialize
	p->Mru1 = p->Mru2 = PPP_MRU_DEFAULT;
	p->RecvPacketList = NewList(NULL);

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

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

	// Request the use of PAP
	c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
	us = Endian16(PPP_LCP_AUTH_PAP);
	Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, &us, sizeof(USHORT)));
	ret = PPPSendRequest(p, PPP_PROTOCOL_LCP, c);
	FreePPPLCP(c);
	if (ret == false)
	{
		if (IsTubeConnected(p->TubeRecv))
		{
			// PAP protocol is denied
			p->DisconnectCauseCode = 15;
			p->DisconnectCauseDirection = 1;
			Debug("PPP: PAP Rejected.\n");

			if (p->EnableMSCHAPv2)
			{
				// Try to request the use of MS-CHAPv2
				UCHAR ms_chap_v2_code[3];
				WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP);
				ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2;

				c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
				Add(c->OptionList, NewPPPOption(PPP_LCP_OPTION_AUTH, ms_chap_v2_code, sizeof(ms_chap_v2_code)));
				ret = PPPSendRequest(p, PPP_PROTOCOL_LCP, c);
				FreePPPLCP(c);

				if (ret == false)
				{
					if (IsTubeConnected(p->TubeRecv))
					{
						// MS-CHAPv2 protocol was also rejected
						p->DisconnectCauseCode = 15;
						p->DisconnectCauseDirection = 1;
						Debug("PPP: MS-CHAPv2 Rejected.\n");
						PPPLog(p, "LP_PAP_MSCHAPV2_REJECTED");
					}
				}
				else
				{
					// It is to be used for the MS-CHAPv2
					p->AuthProtocol = PPP_PROTOCOL_CHAP;
				}
			}
			else
			{
				PPPLog(p, "LP_PAP_REJECTED");
			}
		}

		if (ret == false)
		{
			goto LABEL_CLEANUP;
		}
	}

	//// Authentication phase

	if (p->AuthProtocol == PPP_PROTOCOL_PAP)
	{
		// PAP
		next_protocol = PPPContinueCurrentProtocolRequestListening(p, PPP_PROTOCOL_LCP);
		if (next_protocol == 0)
		{
			goto LABEL_CLEANUP;
		}

		Debug("next_protocol = 0x%x\n", next_protocol);

		if (next_protocol != PPP_PROTOCOL_PAP)
		{
			Debug("next_protocol is not PAP !!\n");
			PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_PAP", next_protocol);
			goto LABEL_CLEANUP;
		}

		next_protocol = PPPContinueCurrentProtocolRequestListening(p, PPP_PROTOCOL_PAP);
		if (next_protocol == 0 || p->AuthOk == false)
		{
			if (IsTubeConnected(p->TubeRecv))
			{
				//  PAP authentication failed
				p->DisconnectCauseCode = 15;
				p->DisconnectCauseDirection = 1;
				Debug("PPP: PAP Failed.\n");
				PPPLog(p, "LP_PAP_FAILED");
			}
			goto LABEL_CLEANUP;
		}
	}
	else
	{
		// MS-CHAP v2
		PPP_PACKET *pp, *pp_ret;
		BUF *b;
		char machine_name[MAX_SIZE];
		UINT64 start_tick = Tick64();
		UINT64 timeout_tick = start_tick + (UINT64)PPP_PACKET_RECV_TIMEOUT;
		UINT64 next_send_tick = 0;
		USHORT pp_ret_protocol;

		PPPContinueUntilFinishAllLCPOptionRequestsDetermined(p);

		if (p->MsChapV2_UseDoubleMsChapV2)
		{
			// Use the double-MSCHAPv2 technieue
			GetMachineHostName(machine_name, sizeof(machine_name));
			MsChapV2Server_GenerateChallenge(p->MsChapV2_ServerChallenge);

			pp = ZeroMalloc(sizeof(PPP_PACKET));
			pp->Protocol = PPP_PROTOCOL_CHAP;
			pp->IsControl = true;
			pp->Lcp = NewPPPLCP(PPP_CHAP_CODE_CHALLENGE, 99);

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

			PPPSendPacket(p, pp);

			pp_ret = PPPRecvResponsePacket(p, pp, 0, &pp_ret_protocol, false, true);

			if (pp_ret != NULL)
			{
				// Extract the username from the first MS-CHAP v2 packet
				if (pp_ret->Lcp != NULL && pp_ret->Lcp->DataSize >= 51)
				{
					BUF *b;

					b = MemToBuf(pp_ret->Lcp->Data, pp_ret->Lcp->DataSize);

					if (ReadBufChar(b) == 49)
					{
						UCHAR client_response_buffer[49];
						char username_tmp[MAX_SIZE];
						char id[MAX_SIZE];
						char hub[MAX_SIZE];
						char client_ip_tmp[256];
						EAP_CLIENT *eap;
						ETHERIP_ID d;

						ReadBuf(b, client_response_buffer, 49);

						Zero(username_tmp, sizeof(username_tmp));
						ReadBuf(b, username_tmp, sizeof(username_tmp) - 1);

						Debug("First MS-CHAPv2: id=%s\n", username_tmp);

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

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

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

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

						eap = HubNewEapClient(p->Cedar, hub, client_ip_tmp, id, "L3:PPP");

						if (eap)
						{
							p->EapClient = eap;
						}
					}

					FreeBuf(b);
				}

				FreePPPPacket(pp_ret);
			}

			FreePPPPacket(pp);
		}

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

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

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

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

		PPPSendPacket(p, pp);

		pp_ret_protocol = 0;
		pp_ret = PPPRecvResponsePacket(p, pp, 0, &pp_ret_protocol, false, false);

		if (pp_ret != NULL)
		{
			FreePPPPacket(pp_ret);
		}

		FreePPPPacket(pp);

		if (pp_ret_protocol == 0 || p->AuthOk == false)
		{
			if (IsTubeConnected(p->TubeRecv))
			{
				// MS-CHAPv2 authentication failed
				p->DisconnectCauseCode = 15;
				p->DisconnectCauseDirection = 1;
				Debug("PPP: MS-CHAPv2 Failed.\n");
				PPPLog(p, "LP_MSCHAPV2_FAILED");
			}
			goto LABEL_CLEANUP;
		}

		next_protocol = pp_ret_protocol;
	}

	Debug("next_protocol = 0x%x\n", next_protocol);

	if (next_protocol != PPP_PROTOCOL_IPCP)
	{
		// Receive the protocol of non-IPCP
		Debug("Not IPCP Protocol.\n");
		PPPLog(p, "LP_NEXT_PROTOCOL_IS_NOT_IPCP", next_protocol);
		goto LABEL_CLEANUP;
	}

	// Notify the IP address of the PPP server
	c = NewPPPLCP(PPP_LCP_CODE_REQ, 0);
	ui = Endian32(0x01000001);	// 1.0.0.1
	Add(c->OptionList, NewPPPOption(PPP_IPCP_OPTION_IP, &ui, sizeof(UINT)));
	ret = PPPSendRequest(p, PPP_PROTOCOL_IPCP, c);
	FreePPPLCP(c);
	if (ret == false)
	{
		goto LABEL_CLEANUP;
	}

	next_protocol = PPPContinueCurrentProtocolRequestListening(p, PPP_PROTOCOL_IPCP);
	Debug("next_protocol = 0x%x\n", next_protocol);

	if (p->Ipc == NULL || IsZeroIP(&p->Ipc->ClientIPAddress))
	{
		// IP address is undetermined
		PPPLog(p, "LP_IP_ADDRESS_NOT_DETERMIND");
		goto LABEL_CLEANUP;
	}

	if (next_protocol == PPP_PROTOCOL_IP)
	{
		established = true;

		// Do the IP communication
		while (true)
		{
			TUBE *tubes[2];
			UINT64 now = Tick64();
			UINT r;

			// Flush the ARP table of the IPC
			IPCFlushArpTable(p->Ipc);

			// Packet of client to server direction
			while (true)
			{
				PPP_PACKET *pp = PPPRecvPacketForCommunication(p);
				if (pp == NULL)
				{
					break;
				}

				if (pp->Protocol == PPP_PROTOCOL_IP)
				{
					// Since I want to send the IP packet, pass it to the IPC
					IPCSendIPv4(p->Ipc, pp->Data, pp->DataSize);
				}

				FreePPPPacket(pp);
			}

			if (p->DhcpAllocated)
			{
				if (now >= p->DhcpNextRenewTime)
				{
					IP ip;

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

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

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

			// Happy procedure
			IPCProcessL3Events(p->Ipc);

			// Packet of server to client direction
			while (true)
			{
				BLOCK *b = IPCRecvIPv4(p->Ipc);
				PPP_PACKET *pp;
				PPP_PACKET tmp;
				if (b == NULL)
				{
					break;
				}

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

				PPPSendPacketEx(p, pp, true);

				FreePPPPacketEx(pp, true);
				Free(b);
			}

			FlushTubeFlushList(p->FlushList);

			// PPP Echo Request
			if (p->NextEchoSendTime == 0 || now >= p->NextEchoSendTime)
			{
				p->NextEchoSendTime = now + (UINT64)PPP_ECHO_SEND_INTERVAL;
				AddInterrupt(p->Ipc->Interrupt, p->NextEchoSendTime);

				PPPSendEchoRequest(p);
			}

			// Terminate if any tube is disconnected
			if (IsTubeConnected(p->TubeRecv) == false || IsTubeConnected(p->TubeSend) == false)
			{
				// Higher-level protocol is disconnected
				PPPLog(p, "LP_UPPER_PROTOCOL_DISCONNECTED", p->Postfix);
				break;
			}
			if (IsIPCConnected(p->Ipc) == false)
			{
				// IPC VPN session is disconnected
				PPPLog(p, "LP_VPN_SESSION_TERMINATED");
				break;
			}

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

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

			// Wait until the next packet arrives
			tubes[0] = p->TubeRecv;
			tubes[1] = p->Ipc->Sock->RecvTube;

			r = GetNextIntervalForInterrupt(p->Ipc->Interrupt);
			WaitForTubes(tubes, 2, MIN(r, 1234));
		}

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

	if (p->DhcpAllocated)
	{
		// If any address is assigned from the DHCP, release it
		IP ip;
		char tmp[MAX_SIZE];

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

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

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

		SleepThread(300);
	}

LABEL_CLEANUP:

	if (established == false)
	{
		//  Disconnected Abnormally
		PPPLog(p, "LP_DISCONNECTED_ABNORMAL");
	}

	// Disconnection process
	PPPCleanTerminate(p);

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

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

	// Release the PPP session
	FreePPPSession(p);
}

// Disconnect the PPP cleanly
void PPPCleanTerminate(PPP_SESSION *p)
{
	PPP_PACKET *pp;
	PPP_PACKET *res;
	UINT64 giveup_tick = Tick64() + (UINT64)PPP_TERMINATE_TIMEOUT;
	// Validate arguments
	if (p == NULL)
	{
		return;
	}

	// Send a Terminate Request
	pp = ZeroMalloc(sizeof(PPP_PACKET));
	pp->IsControl = true;
	pp->Protocol = PPP_PROTOCOL_LCP;
	pp->Lcp = NewPPPLCP(PPP_LCP_CODE_TERMINATE_REQ, p->NextId++);
	Debug("PPP: Terminate Request is Sent.\n");
	if (PPPSendPacket(p, pp) == false)
	{
		goto LABEL_CLEANUP;
	}

	// Wait for Terminate ACK
	while (true)
	{
		UINT64 now = Tick64();
		UINT interval;

		if (now >= giveup_tick)
		{
			break;
		}

		while (true)
		{
			if (IsTubeConnected(p->TubeRecv) == false)
			{
				break;
			}

			res = PPPRecvPacket(p, true);

			if (res == NULL)
			{
				break;
			}

			if (res->IsControl && res->Protocol == PPP_PROTOCOL_LCP && res->Lcp->Code == PPP_LCP_CODE_TERMINATE_ACK)
			{
				Debug("PPP: Terminate ACK is Received.\n");
				FreePPPPacket(res);
				goto LABEL_CLEANUP;
			}

			FreePPPPacket(res);
		}

		interval = (UINT)(giveup_tick - now);

		Wait(p->TubeRecv->Event, interval);
	}

LABEL_CLEANUP:
	FreePPPPacket(pp);
}

// Wait until all pending LCP option are determined
bool PPPContinueUntilFinishAllLCPOptionRequestsDetermined(PPP_SESSION *p)
{
	USHORT received_protocol = 0;
	// Validate arguments
	if (p == NULL)
	{
		return false;
	}

	PPPRecvResponsePacket(p, NULL, PPP_PROTOCOL_LCP, &received_protocol, true, false);

	return p->ClientLCPOptionDetermined;
}

// Continue the processing of the request packet protocol on the current PPP
USHORT PPPContinueCurrentProtocolRequestListening(PPP_SESSION *p, USHORT protocol)
{
	USHORT received_protocol = 0;
	// Validate arguments
	if (p == NULL)
	{
		return 0;
	}

	PPPRecvResponsePacket(p, NULL, protocol, &received_protocol, false, false);

	return received_protocol;
}

// Send the PPP Echo Request
void PPPSendEchoRequest(PPP_SESSION *p)
{
	PPP_PACKET *pp;
	char echo_data[]= "\0\0\0\0Aho Baka Manuke";
	// Validate arguments
	if (p == NULL)
	{
		return;
	}

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

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

	PPPSendPacket(p, pp);

	FreePPPPacket(pp);
}

// Send a request packet in the PPP
bool PPPSendRequest(PPP_SESSION *p, USHORT protocol, PPP_LCP *c)
{
	PPP_PACKET *pp;
	PPP_PACKET *pp2;
	bool ret = false;
	// Validate arguments
	if (p == NULL || c == NULL)
	{
		return false;
	}

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

	// Send the PPP packet
	if (PPPSendPacket(p, pp) == false)
	{
		goto LABEL_ERROR;
	}

	// Receive a corresponding PPP packet
	pp2 = PPPRecvResponsePacket(p, pp, 0, NULL, false, false);

	if (pp2 != NULL)
	{
		if (protocol == PPP_PROTOCOL_LCP || protocol == PPP_PROTOCOL_IPCP)
		{
			if (!PPP_LCP_CODE_IS_NEGATIVE(pp2->Lcp->Code))
			{
				// A positive response is received
				ret = true;
			}
		}
	}

	FreePPPPacket(pp2);
	Free(pp);

	return ret;

LABEL_ERROR:
	Free(pp);
	return false;
}

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

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

	return ret;
}

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

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

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

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

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

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

			if (c == '@')
			{
				last_at = i;
			}
		}

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

		if (last_at == INFINITE)
		{
			// "@" is not specified
			StrCpy(token1, sizeof(token1), src);
		}
		else
		{
			// Split with last "@"
			StrCpy(token1, sizeof(token1), src);
			token1[last_at] = 0;

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

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

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

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

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

	}

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

	return true;
}

// Process the PPP request packet
PPP_PACKET *PPPProcessRequestPacket(PPP_SESSION *p, PPP_PACKET *req)
{
	UINT i;
	PPP_PACKET *ret = NULL;
	UINT num_not_supported = 0;
	UINT num_not_accepted = 0;
	bool no_return_option_list = false;
	UINT return_code = 0;
	BUF *lcp_ret_data = NULL;
	// Validate arguments
	if (p == NULL || req == NULL || req->Lcp == NULL)
	{
		return NULL;
	}

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

		t->IsAccepted = false;
		t->IsSupported = false;
		t->AltDataSize = 0;
		Zero(t->AltData, sizeof(t->AltData));
	}

	// Process by scanning the specified option value
	if (req->Protocol == PPP_PROTOCOL_LCP)
	{
		// LCP
		if (req->Lcp == NULL)
		{
			return NULL;
		}
		for (i = 0;i < LIST_NUM(req->Lcp->OptionList);i++)
		{
			PPP_OPTION *t = LIST_DATA(req->Lcp->OptionList, i);

			switch (t->Type)
			{
			case PPP_LCP_OPTION_MRU:
				// MRU
				t->IsSupported = true;
				if (t->DataSize == sizeof(USHORT))
				{
					UINT value = READ_USHORT(t->Data);
					if (value < PPP_MRU_MIN || value > PPP_MRU_MAX)
					{
						t->IsAccepted = false;
						value = MAKESURE(value, PPP_MRU_MIN, PPP_MRU_MAX);
						WRITE_USHORT(t->AltData, value);
						t->AltDataSize = sizeof(USHORT);
					}
					else
					{
						p->Mru1 = value;
						Debug("PPP: Client set %u as MRU\n", p->Mru1);
						t->IsAccepted = true;
					}
				}
				break;
			}
		}
	}
	else if (req->Protocol == PPP_PROTOCOL_CHAP)
	{
		bool ok = false;
		char ret_str[MAX_SIZE];

		no_return_option_list = true;

		if (p->Ipc == NULL)
		{
			// MS-CHAPv2
			if (req->Lcp->DataSize >= 51)
			{
				BUF *b;

				b = NewBuf();

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

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

					ReadBuf(b, client_response_buffer, 49);

					Zero(username_tmp, sizeof(username_tmp));
					ReadBuf(b, username_tmp, sizeof(username_tmp) - 1);

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

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

					Debug("MS-CHAPv2: id=%s\n", username_tmp);

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

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

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

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

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

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

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

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

						ok = true;
					}
					else
					{
						switch (error_code)
						{
						default:
							// Normal authentication error
							p->MsChapV2_ErrorCode = 691;
							break;

						case ERR_MSCHAP2_PASSWORD_NEED_RESET:
							// Authentication errors due to compatibility issues of the password
							p->MsChapV2_ErrorCode = 942;
							break;
						}
					}
				}

				FreeBuf(b);
			}
		}
		else
		{
			// Return success for a request from the second time when it is successfully authenticated once
			ok = true;
		}

		// Generate a response options string
		if (ok == false)
		{
			// In the case of failure
			char hex[MAX_SIZE];
			BinToStr(hex, sizeof(hex), p->MsChapV2_ServerChallenge, 16);

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

			return_code = PPP_CHAP_CODE_FAILURE;
		}
		else
		{
			// In the case of success
			char hex[MAX_SIZE];
			BinToStr(hex, sizeof(hex), p->MsChapV2_ServerResponse, 20);

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

			return_code = PPP_CHAP_CODE_SUCCESS;

			p->AuthOk = true;
		}

		lcp_ret_data = NewBuf();
		WriteBuf(lcp_ret_data, ret_str, StrLen(ret_str));
	}
	else if (req->Protocol == PPP_PROTOCOL_PAP)
	{
		UCHAR *data;
		UINT size;
		bool ok = false;

		no_return_option_list = true;

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

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

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

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

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

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

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

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

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

							Copy(password, data, len_pass);

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

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

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

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

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

								if (ipc != NULL)
								{
									p->Ipc = ipc;
									ok = true;
								}
							}
						}
					}
				}
			}
		}
		else
		{
			// Return success for a request from the second time when it is successfully authenticated once
			ok = true;
		}

		if (ok == false)
		{
			// Authentication failure
			return_code = PPP_PAP_CODE_NAK;
		}
		else
		{
			// Authentication success
			return_code = PPP_PAP_CODE_ACK;

			p->AuthOk = true;
		}
	}
	else if (req->Protocol == PPP_PROTOCOL_IPCP)
	{
		PPP_IPOPTION o;
		// Get the IP options data from the request data
		if (PPPGetIPOptionFromLCP(&o, req->Lcp))
		{
			PPP_IPOPTION res;
			IP subnet;
			IP gw;

			if (IsZeroIP(&o.IpAddress) == false)
			{
				if (p->Ipc->Policy->DHCPForce == false)
				{
					if (p->DhcpAllocated == false)
					{
						if (p->UseStaticIPAddress == false)
						{
							DHCP_OPTION_LIST cao;

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

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

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

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

			if (p->UseStaticIPAddress)
			{
				if (p->DhcpIpInformTried == false)
				{
					// Get additional information such as the subnet mask from the DHCP server
					DHCP_OPTION_LIST cao;
					IP client_ip;

					IP subnet;
					IP zero;

					SetIP(&subnet, 255, 0, 0, 0);
					Zero(&zero, sizeof(zero));

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

					Zero(&cao, sizeof(cao));

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

					p->DhcpIpInformTried = true;

					PPPLog(p, "LP_DHCP_INFORM_TRYING");

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

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

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

							PPPLog(p, "LP_DHCP_INFORM_OK",
								subnet_str, defgw_str, cao.DomainName,
								dns1_str, dns2_str, wins1_str, wins2_str,
								server_ip_str);
						}
					}
					else
					{
						Debug("IPCDhcpRequestInformIP failed.\n");

						PPPLog(p, "LP_DHCP_INFORM_NG");
					}

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

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

					PPPLog(p, "LP_DHCP_REQUEST_TRYING");

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

						Debug("IPCDhcpAllocateIP ok.\n");

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

						p->DhcpAllocated = true;

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

						t = t / 3;

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

						p->DhcpRenewInterval = (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");

						PPPLog(p, "LP_DHCP_REQUEST_NG");
					}
				}
			}

			if (IsValidUnicastIPAddressUINT4(p->ClientAddressOption.ClientAddress) &&
				p->ClientAddressOption.SubnetMask != 0)
			{
				// Success to determine the address
				UINTToIP(&subnet, p->ClientAddressOption.SubnetMask);
				UINTToIP(&gw, p->ClientAddressOption.Gateway);

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

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

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

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

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

				PPPSetIPOptionToLCP(&res, req->Lcp, true);
			}
			else
			{
				// Failed to determine the address
				Debug("IP Address Determination Failed.\n");

				Zero(&res, sizeof(res));

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

	// Assemble the LCP response packet based on the results
	for (i = 0;i < LIST_NUM(req->Lcp->OptionList);i++)
	{
		PPP_OPTION *t = LIST_DATA(req->Lcp->OptionList, i);

		if (t->IsSupported == false)
		{
			num_not_supported++;
		}

		if (t->IsAccepted == false)
		{
			num_not_accepted++;
		}
	}

	// Create a PPP response packet
	ret = ZeroMalloc(sizeof(PPP_PACKET));
	ret->IsControl = true;
	ret->Protocol = req->Protocol;

	if (no_return_option_list == false)
	{
		// Response by attaching an optional list
		if (num_not_supported >= 1)
		{
			// Return a Reject if there are unsupported parameters
			ret->Lcp = NewPPPLCP(PPP_LCP_CODE_REJECT, req->Lcp->Id);

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

				if (t->IsSupported == false)
				{
					// Attach the original option value as is
					Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize));
				}
			}
		}
		else if (num_not_accepted >= 1)
		{
			// Return a NAK if there are any unacceptable parameter
			// even that all parameters are supported
			ret->Lcp = NewPPPLCP(PPP_LCP_CODE_NAK, req->Lcp->Id);

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

				if (t->IsAccepted == false)
				{
					// Replace the original option value with an acceptable value
					Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->AltData, t->AltDataSize));
				}
			}
		}
		else
		{
			// Return an ACK if all parameters are accepted
			ret->Lcp = NewPPPLCP(PPP_LCP_CODE_ACK, req->Lcp->Id);

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

				// Attach the original option value as is
				Add(ret->Lcp->OptionList, NewPPPOption(t->Type, t->Data, t->DataSize));
			}

			if (req->Protocol == PPP_PROTOCOL_LCP)
			{
				p->ClientLCPOptionDetermined = true;
			}
		}
	}
	else
	{
		// Response without attaching a list of options
		ret->Lcp = NewPPPLCP(return_code, req->Lcp->Id);

		if (lcp_ret_data != NULL && lcp_ret_data->Size >= 1)
		{
			ret->Lcp->Data = Clone(lcp_ret_data->Buf, lcp_ret_data->Size);
			ret->Lcp->DataSize = lcp_ret_data->Size;
		}
	}

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

	return ret;
}

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

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

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

	return ret;
}

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

	Zero(o, sizeof(PPP_IPOPTION));

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

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

	return ret;
}

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

	ui = IPToUINT(ip);

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

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

		return true;
	}
	else
	{
		if (IsZeroIP(ip) == false)
		{
			// Add as a new item
			if (only_modify != false)
			{
				return false;
			}
			else
			{
				PPP_OPTION *opt2 = NewPPPOption(type, &ui, 4);
				opt2->IsAccepted = opt2->IsSupported = true;

				Add(c->OptionList, opt2);

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

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

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

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

	opt->IsSupported = true;

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

	UINTToIP(ip, ui);

	return true;
}

// Process corresponding to the incoming request while receiving a PPP packet corresponding to the transmitted request.
// (If req == NULL, process on that protocol while the protocol specified in expected_protocol have received.
//If other protocols has arrived, without further processing, and then store that packet in the session context once,
// return NULL by setting the received_protocol.)
PPP_PACKET *PPPRecvResponsePacket(PPP_SESSION *p, PPP_PACKET *req, USHORT expected_protocol, USHORT *received_protocol, bool finish_when_all_lcp_acked,
								  bool return_mschapv2_response_with_no_processing)
{
	UINT64 giveup_tick = Tick64() + (UINT64)PPP_PACKET_RECV_TIMEOUT;
	UINT64 next_resend = Tick64() + (UINT64)PPP_PACKET_RESEND_INTERVAL;
	PPP_PACKET *ret = NULL;
	USHORT tmp_us = 0;
	// Validate arguments
	if (p == NULL || req != NULL && req->Lcp == NULL)
	{
		return NULL;
	}

	if (received_protocol == NULL)
	{
		received_protocol = &tmp_us;
	}

	if (req != NULL)
	{
		expected_protocol = req->Protocol;
	}

	*received_protocol = 0;

	// Receive the next packet (Retransmission repeatedly the last packet until the reception is completed)
	while (true)
	{
		UINT64 now = Tick64();
		UINT interval;

		if (IsTubeConnected(p->TubeRecv) == false)
		{
			return NULL;
		}

		while (true)
		{
			PPP_PACKET *pp;
			PPP_PACKET *response;

			if (p->LastStoredPacket != NULL)
			{
				pp = p->LastStoredPacket;
				p->LastStoredPacket = NULL;
			}
			else
			{
				pp = PPPRecvPacketWithLowLayerProcessing(p, true);
			}

			if (pp == NULL)
			{
				break;
			}

			if (req != NULL)
			{
				// Determine whether the packet is corresponding to the request that was sent at the last
				if (pp->IsControl && pp->Protocol == req->Protocol && pp->Lcp->Id == req->Lcp->Id &&
					PPP_CODE_IS_RESPONSE(pp->Protocol, pp->Lcp->Code))
				{
					return pp;
				}

				if (return_mschapv2_response_with_no_processing)
				{
					// For the double-MSCHAPv2 technique
					if (pp->IsControl && pp->Protocol == req->Protocol && pp->Lcp->Id == req->Lcp->Id &&
						pp->Protocol == PPP_PROTOCOL_CHAP && PPP_PAP_CODE_IS_RESPONSE(pp->Lcp->Code))
					{
						return pp;
					}
				}
			}

			// Return a response immediately without processing if a protocol other than the expected received
			if ((pp->IsControl && pp->Protocol != expected_protocol) || pp->IsControl == false)
			{
				if (PPP_IS_SUPPORTED_PROTOCOL(pp->Protocol))
				{
					// This is another supported protocol
					// Store this packet
					PPPStoreLastPacket(p, pp);

					*received_protocol = pp->Protocol;
					return NULL;
				}
				else
				{
					// Unsupported protocol
					Debug("Unsupported Protocol: 0x%x\n", pp->Protocol);
					FreePPPPacket(pp);

					return NULL;
				}
			}

			if (pp->IsControl && PPP_CODE_IS_REQUEST(pp->Protocol, pp->Lcp->Code))
			{
				// Record current resend because next steps may take a while
				UINT64 currentresend = next_resend - now;

				// Process when the received packet is a request packet
				response = PPPProcessRequestPacket(p, pp);

				// Increase next resend because this may have taken a while
				next_resend = Tick64() + currentresend;

				FreePPPPacket(pp);

				if (response == NULL)
				{
					return NULL;
				}
				else
				{
					bool is_pap_and_disconnect_now = false;
					bool is_chap_and_disconnect_now = false;

					if (PPPSendPacket(p, response) == false)
					{
						FreePPPPacket(response);
						return NULL;
					}

					if (response->Protocol == PPP_PROTOCOL_PAP && response->IsControl &&
						response->Lcp->Code != PPP_PAP_CODE_ACK)
					{
						is_pap_and_disconnect_now = true;
					}

					if (response->Protocol == PPP_PROTOCOL_CHAP && response->IsControl &&
						response->Lcp->Code == PPP_CHAP_CODE_FAILURE)
					{
						is_chap_and_disconnect_now = true;
					}

					FreePPPPacket(response);

					if (is_pap_and_disconnect_now)
					{
						// Disconnect immediately if user authentication fails at least once in the PAP authentication protocol
						Debug("Disconnecting because PAP failed.\n");
						SleepThread(300);
						return NULL;
					}

					if (is_chap_and_disconnect_now)
					{
						// Disconnect immediately if it fails to user authentication at least once in the CHAP authentication protocol
						Debug("Disconnecting because CHAP failed.\n");
						SleepThread(300);
						return NULL;
					}
				}
			}
			else
			{
				// Ignore in the case of the other packets
				FreePPPPacket(pp);
			}
		}

		// Packet retransmission
		if (req != NULL)
		{
			if (now >= next_resend)
			{
				next_resend = now + PPP_PACKET_RESEND_INTERVAL;

				if (PPPSendPacket(p, req) == false)
				{
					return NULL;
				}
			}
		}

		if (req == NULL)
		{
			giveup_tick = now + (UINT64)PPP_PACKET_RECV_TIMEOUT;
		}

		// Time-out decision
		if (now >= giveup_tick)
		{
			PPPLog(p, "LP_CONTROL_TIMEOUT");
			return NULL;
		}

		// Wait
		if (req != NULL)
		{
			interval = MIN((UINT)(giveup_tick - now), (UINT)(next_resend - now));
		}
		else
		{
			interval = (UINT)(giveup_tick - now);
		}

		if (finish_when_all_lcp_acked && p->ClientLCPOptionDetermined)
		{
			return NULL;
		}

		Wait(p->TubeRecv->Event, interval);
	}
}

// Store the last packet in the session (to be read the next time)
void PPPStoreLastPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	// Validate arguments
	if (p == NULL)
	{
		return;
	}

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

	p->LastStoredPacket = pp;
}

// Receive a PPP communication packet
PPP_PACKET *PPPRecvPacketForCommunication(PPP_SESSION *p)
{
	// Validate arguments
	if (p == NULL)
	{
		return NULL;
	}

	if (p->LastStoredPacket != NULL)
	{
		PPP_PACKET *pp = p->LastStoredPacket;
		p->LastStoredPacket = NULL;
		return pp;
	}

	return PPPRecvPacketWithLowLayerProcessing(p, true);
}

// Receive a PPP packet (Also performs low layer processing)
PPP_PACKET *PPPRecvPacketWithLowLayerProcessing(PPP_SESSION *p, bool async)
{
	PPP_PACKET *pp = NULL;
	// Validate arguments
	if (p == NULL)
	{
		return NULL;
	}

LABEL_LOOP:
	pp = PPPRecvPacket(p, async);
	if (pp == NULL)
	{
		return NULL;
	}

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

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

		buf = NewBuf();

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

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

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

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

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

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

		FreePPPPacket(pp);

		FreeBuf(buf);

		if (PPPSendPacket(p, pp2) == false)
		{
			FreePPPPacket(pp2);
			return NULL;
		}

		FreePPPPacket(pp2);
		goto LABEL_LOOP;
	}

	if (pp->IsControl && pp->Protocol == PPP_PROTOCOL_LCP)
	{
		if (pp->Lcp->Code == PPP_LCP_CODE_ECHO_REQUEST)
		{
			// Immediately return the echo response to the echo request
			PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));

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

			FreePPPPacket(pp);

			if (PPPSendPacket(p, pp2) == false)
			{
				FreePPPPacket(pp2);
				return NULL;
			}

			FreePPPPacket(pp2);
			goto LABEL_LOOP;
		}
		else if (pp->Lcp->Code == PPP_LCP_CODE_ECHO_RESPONSE)
		{
			// Ignore the Echo response packet
			FreePPPPacket(pp);
			goto LABEL_LOOP;
		}
		else if (pp->Lcp->Code == PPP_LCP_CODE_DROP)
		{
			// Ignore the Drop packet
			FreePPPPacket(pp);
			goto LABEL_LOOP;
		}
		else if (pp->Lcp->Code == PPP_LCP_CODE_IDENTIFICATION)
		{
			// Ignore the Identification packet
			FreePPPPacket(pp);
			WHERE;
			goto LABEL_LOOP;
		}
		else if (pp->Lcp->Code == PPP_LCP_CODE_TERMINATE_REQ)
		{
			// Return the Terminate ACK If a Terminate Request has been received
			PPP_PACKET *pp2 = ZeroMalloc(sizeof(PPP_PACKET));

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

			p->IsTerminateReceived = true;

			FreePPPPacket(pp);

			if (PPPSendPacket(p, pp2) == false)
			{
				FreePPPPacket(pp2);
				return NULL;
			}

			SleepThread(100);

			FreePPPPacket(pp2);
			goto LABEL_LOOP;
		}
	}

	return pp;
}

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

LABEL_LOOP:

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

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

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

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

	p->LastRecvTime = Tick64();

	return pp;
}

// Send the PPP packet
bool PPPSendPacket(PPP_SESSION *p, PPP_PACKET *pp)
{
	return PPPSendPacketEx(p, pp, false);
}
bool PPPSendPacketEx(PPP_SESSION *p, PPP_PACKET *pp, bool no_flush)
{
	bool ret = false;
	BUF *b;
	// Validate arguments
	if (p == NULL || pp == NULL)
	{
		return false;
	}

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

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

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

	FreeBuf(b);

	return ret;
}

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

	o = ZeroMalloc(sizeof(PPP_OPTION));

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

	return o;
}

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

	pp = ZeroMalloc(sizeof(PPP_PACKET));

	buf = (UCHAR *)data;

	// 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->IsControl = true;
	}

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

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

	return pp;

LABEL_ERROR:
	FreePPPPacket(pp);
	return NULL;
}

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

	ret = NewBuf();

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

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

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

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

		WriteBufBuf(ret, b);

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

	SeekBuf(ret, 0, 0);

	return ret;
}

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

	b = NewBuf();

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

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

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

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

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

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

	SeekBuf(b, 0, 0);

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

	return b;
}

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

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

	c->OptionList = NewListFast(NULL);

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

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

	// Length
	if (size < 2)
	{
		goto LABEL_ERROR;
	}
	len = READ_USHORT(buf);
	if (len < 4)
	{
		goto LABEL_ERROR;
	}
	len -= 4;
	buf += 2;
	size -= 2;

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

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

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

			Zero(&o, sizeof(o));

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

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

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

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

	return c;

LABEL_ERROR:
	FreePPPLCP(c);
	return NULL;
}

// Release the PPP packet
void FreePPPPacket(PPP_PACKET *pp)
{
	FreePPPPacketEx(pp, false);
}
void FreePPPPacketEx(PPP_PACKET *pp, bool no_free_struct)
{
	// Validate arguments
	if (pp == NULL)
	{
		return;
	}

	FreePPPLCP(pp->Lcp);

	Free(pp->Data);

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

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

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

	FreeTubeFlushList(p->FlushList);

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

	ReleaseCedar(p->Cedar);

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

	PPPStoreLastPacket(p, NULL);

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

	PPPFreeEapClient(p);

	Free(p);
}

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

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

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

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

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

	return NULL;
}

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

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

	return c;
}

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

	FreePPPOptionList(c->OptionList);

	Free(c->Data);

	Free(c);
}

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

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

		Free(t);
	}

	ReleaseList(o);
}

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

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

	p->EnableMSCHAPv2 = true;
	p->AuthProtocol = PPP_PROTOCOL_PAP;
	p->MsChapV2_ErrorCode = 691;

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

	p->AdjustMss = adjust_mss;

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

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

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

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

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

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

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

	p->FlushList = NewTubeFlushList();

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

	return t;
}

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

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

	tmp = ZeroMalloc(tmp_size);

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

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

	Free(tmp);
}

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

	Rand(dst, 16);
}

// Generate 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));
	}

	HashSha1(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));
	HashSha1(digest, b->Buf, b->Size);
	FreeBuf(b);

	b = NewBuf();
	WriteBuf(b, digest, sizeof(digest));
	WriteBuf(b, challenge8, 8);
	WriteBuf(b, magic2, StrLen(magic2));
	HashSha1(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;
}



