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


// Nat.c
// User-mode Router

#include "Nat.h"

#include "Admin.h"
#include "Remote.h"
#include "Sam.h"

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

static LOCK *nat_lock = NULL;
static NAT *nat = NULL;


// Disconnect the connection for the NAT administrator
void NatAdminDisconnect(RPC *r)
{
	// Validate arguments
	if (r == NULL)
	{
		return;
	}

	EndRpc(r);
}

// Connection for NAT administrator
RPC *NatAdminConnect(CEDAR *cedar, char *hostname, UINT port, void *hashed_password, UINT *err)
{
	UCHAR secure_password[SHA1_SIZE];
	UCHAR random[SHA1_SIZE];
	SOCK *sock;
	RPC *rpc;
	PACK *p;
	UINT error;
	// Validate arguments
	if (cedar == NULL || hostname == NULL || port == 0 || hashed_password == NULL || err == NULL)
	{
		if (err != NULL)
		{
			*err = ERR_INTERNAL_ERROR;
		}
		return NULL;
	}

	// Connection
	sock = Connect(hostname, port);
	if (sock == NULL)
	{
		*err = ERR_CONNECT_FAILED;
		return NULL;
	}

	if (StartSSL(sock, NULL, NULL) == false)
	{
		*err = ERR_PROTOCOL_ERROR;
		ReleaseSock(sock);
		return NULL;
	}

	SetTimeout(sock, 5000);

	p = HttpClientRecv(sock);
	if (p == NULL)
	{
		*err = ERR_DISCONNECTED;
		ReleaseSock(sock);
		return NULL;
	}

	if (PackGetData2(p, "auth_random", random, SHA1_SIZE) == false)
	{
		FreePack(p);
		*err = ERR_PROTOCOL_ERROR;
		ReleaseSock(sock);
		return NULL;
	}

	FreePack(p);

	SecurePassword(secure_password, hashed_password, random);

	p = NewPack();
	PackAddData(p, "secure_password", secure_password, SHA1_SIZE);

	if (HttpClientSend(sock, p) == false)
	{
		FreePack(p);
		*err = ERR_DISCONNECTED;
		ReleaseSock(sock);
		return NULL;
	}

	FreePack(p);

	p = HttpClientRecv(sock);
	if (p == NULL)
	{
		*err = ERR_DISCONNECTED;
		ReleaseSock(sock);
		return NULL;
	}

	error = GetErrorFromPack(p);

	FreePack(p);

	if (error != ERR_NO_ERROR)
	{
		*err = error;
		ReleaseSock(sock);
		return NULL;
	}

	SetTimeout(sock, TIMEOUT_INFINITE);

	rpc = StartRpcClient(sock, NULL);
	ReleaseSock(sock);

	return rpc;
}

// RPC functional related macro
#define	DECLARE_RPC_EX(rpc_name, data_type, function, in_rpc, out_rpc, free_rpc)		\
	else if (StrCmpi(name, rpc_name) == 0)								\
	{																	\
		data_type t;													\
		Zero(&t, sizeof(t));											\
		in_rpc(&t, p);													\
		err = function(n, &t);											\
		if (err == ERR_NO_ERROR)										\
		{																\
			out_rpc(ret, &t);											\
		}																\
		free_rpc(&t);													\
		ok = true;														\
	}
#define	DECLARE_RPC(rpc_name, data_type, function, in_rpc, out_rpc)		\
	else if (StrCmpi(name, rpc_name) == 0)								\
	{																	\
		data_type t;													\
		Zero(&t, sizeof(t));											\
		in_rpc(&t, p);													\
		err = function(n, &t);											\
		if (err == ERR_NO_ERROR)										\
		{																\
			out_rpc(ret, &t);											\
		}																\
		ok = true;														\
	}
#define	DECLARE_SC_EX(rpc_name, data_type, function, in_rpc, out_rpc, free_rpc)	\
	UINT function(RPC *r, data_type *t)									\
	{																	\
		PACK *p, *ret;													\
		UINT err;														\
		if (r == NULL || t == NULL)										\
		{																\
			return ERR_INTERNAL_ERROR;									\
		}																\
		p = NewPack();													\
		out_rpc(p, t);													\
		free_rpc(t);													\
		Zero(t, sizeof(data_type));										\
		ret = AdminCall(r, rpc_name, p);								\
		err = GetErrorFromPack(ret);									\
		if (err == ERR_NO_ERROR)										\
		{																\
			in_rpc(t, ret);												\
		}																\
		FreePack(ret);													\
		return err;														\
	}
#define	DECLARE_SC(rpc_name, data_type, function, in_rpc, out_rpc)		\
	UINT function(RPC *r, data_type *t)									\
	{																	\
		PACK *p, *ret;													\
		UINT err;														\
		if (r == NULL || t == NULL)										\
		{																\
			return ERR_INTERNAL_ERROR;									\
		}																\
		p = NewPack();													\
		out_rpc(p, t);													\
		ret = AdminCall(r, rpc_name, p);								\
		err = GetErrorFromPack(ret);									\
		if (err == ERR_NO_ERROR)										\
		{																\
			in_rpc(t, ret);												\
		}																\
		FreePack(ret);													\
		return err;														\
	}

// RPC server function
PACK *NiRpcServer(RPC *r, char *name, PACK *p)
{
	NAT *n;
	PACK *ret;
	UINT err;
	bool ok;
	// Validate arguments
	if (r == NULL || name == NULL || p == NULL)
	{
		return NULL;
	}

	n = (NAT *)r->Param;
	ret = NewPack();
	err = ERR_NO_ERROR;
	ok = false;

	if (0) {}

	// RPC function definition: From here

//	DECLARE_RPC("Online", RPC_DUMMY, NtOnline, InRpcDummy, OutRpcDummy)
//	DECLARE_RPC("Offline", RPC_DUMMY, NtOffline, InRpcDummy, OutRpcDummy)
	DECLARE_RPC("SetHostOption", VH_OPTION, NtSetHostOption, InVhOption, OutVhOption)
	DECLARE_RPC("GetHostOption", VH_OPTION, NtGetHostOption, InVhOption, OutVhOption)
//	DECLARE_RPC_EX("SetClientConfig", RPC_CREATE_LINK, NtSetClientConfig, InRpcCreateLink, OutRpcCreateLink, FreeRpcCreateLink)
//	DECLARE_RPC_EX("GetClientConfig", RPC_CREATE_LINK, NtGetClientConfig, InRpcCreateLink, OutRpcCreateLink, FreeRpcCreateLink)
	DECLARE_RPC_EX("GetStatus", RPC_NAT_STATUS, NtGetStatus, InRpcNatStatus, OutRpcNatStatus, FreeRpcNatStatus)
//	DECLARE_RPC_EX("GetInfo", RPC_NAT_INFO, NtGetInfo, InRpcNatInfo, OutRpcNatInfo, FreeRpcNatInfo)
	DECLARE_RPC_EX("EnumNatList", RPC_ENUM_NAT, NtEnumNatList, InRpcEnumNat, OutRpcEnumNat, FreeRpcEnumNat)
	DECLARE_RPC_EX("EnumDhcpList", RPC_ENUM_DHCP, NtEnumDhcpList, InRpcEnumDhcp, OutRpcEnumDhcp, FreeRpcEnumDhcp)
//	DECLARE_RPC("SetPassword", RPC_SET_PASSWORD, NtSetPassword, InRpcSetPassword, OutRpcSetPassword)

	// RPC function definition: To here

	if (ok == false)
	{
		err = ERR_NOT_SUPPORTED;
	}

	PackAddInt(ret, "error", err);

	return ret;
}




// RPC call definition: From here

DECLARE_SC("Online", RPC_DUMMY, NcOnline, InRpcDummy, OutRpcDummy)
DECLARE_SC("Offline", RPC_DUMMY, NcOffline, InRpcDummy, OutRpcDummy)
DECLARE_SC("SetHostOption", VH_OPTION, NcSetHostOption, InVhOption, OutVhOption)
DECLARE_SC("GetHostOption", VH_OPTION, NcGetHostOption, InVhOption, OutVhOption)
DECLARE_SC_EX("SetClientConfig", RPC_CREATE_LINK, NcSetClientConfig, InRpcCreateLink, OutRpcCreateLink, FreeRpcCreateLink)
DECLARE_SC_EX("GetClientConfig", RPC_CREATE_LINK, NcGetClientConfig, InRpcCreateLink, OutRpcCreateLink, FreeRpcCreateLink)
DECLARE_SC_EX("GetStatus", RPC_NAT_STATUS, NcGetStatus, InRpcNatStatus, OutRpcNatStatus, FreeRpcNatStatus)
DECLARE_SC_EX("GetInfo", RPC_NAT_INFO, NcGetInfo, InRpcNatInfo, OutRpcNatInfo, FreeRpcNatInfo)
DECLARE_SC_EX("EnumNatList", RPC_ENUM_NAT, NcEnumNatList, InRpcEnumNat, OutRpcEnumNat, FreeRpcEnumNat)
DECLARE_SC_EX("EnumDhcpList", RPC_ENUM_DHCP, NcEnumDhcpList, InRpcEnumDhcp, OutRpcEnumDhcp, FreeRpcEnumDhcp)
DECLARE_SC("SetPassword", RPC_SET_PASSWORD, NcSetPassword, InRpcSetPassword, OutRpcSetPassword)

// RPC call definition: To here



// Set a password
UINT NtSetPassword(NAT *n, RPC_SET_PASSWORD *t)
{
	Copy(n->HashedPassword, t->HashedPassword, SHA1_SIZE);

	NiWriteConfig(n);

	return ERR_NO_ERROR;
}

// Online
UINT NtOnline(NAT *n, RPC_DUMMY *t)
{
	UINT ret = ERR_NO_ERROR;

	Lock(n->lock);
	{
		if (n->Online)
		{
			// It is already online
			ret = ERR_ALREADY_ONLINE;
		}
		else
		{
			if (n->ClientOption == NULL || n->ClientAuth == NULL)
			{
				// Setting is not yet done
				ret = ERR_ACCOUNT_NOT_PRESENT;
			}
			else
			{
				// OK
				n->Online = true;

				// Start connection
				n->Virtual = NewVirtualHostEx(n->Cedar, n->ClientOption, n->ClientAuth,
					&n->Option, n);
			}
		}
	}
	Unlock(n->lock);

	NiWriteConfig(n);

	return ret;
}

// Offline
UINT NtOffline(NAT *n, RPC_DUMMY *t)
{
	UINT ret = ERR_NO_ERROR;

	Lock(n->lock);
	{
		if (n->Online == false)
		{
			// It is offline
			ret = ERR_OFFLINE;
		}
		else
		{
			// Offline
			StopVirtualHost(n->Virtual);
			ReleaseVirtual(n->Virtual);
			n->Virtual = NULL;

			n->Online = false;
		}
	}
	Unlock(n->lock);

	NiWriteConfig(n);

	return ret;
}

// Set host options
UINT NtSetHostOption(NAT *n, VH_OPTION *t)
{
	UINT ret = ERR_NO_ERROR;

	Lock(n->lock);
	{
		Copy(&n->Option, t, sizeof(VH_OPTION));
	}
	Unlock(n->lock);

	SetVirtualHostOption(n->Virtual, t);

	NiWriteConfig(n);

	return ret;
}

// Get host options
UINT NtGetHostOption(NAT *n, VH_OPTION *t)
{
	UINT ret = ERR_NO_ERROR;

	Lock(n->lock);
	{
		Copy(t, &n->Option, sizeof(VH_OPTION));
	}
	Unlock(n->lock);

	return ret;
}

// Set the connection settings
UINT NtSetClientConfig(NAT *n, RPC_CREATE_LINK *t)
{
	Lock(n->lock);
	{
		if (n->ClientOption != NULL || n->ClientAuth != NULL)
		{
			Free(n->ClientOption);
			CiFreeClientAuth(n->ClientAuth);
		}

		n->ClientOption = ZeroMalloc(sizeof(CLIENT_OPTION));
		Copy(n->ClientOption, t->ClientOption, sizeof(CLIENT_OPTION));
		n->ClientAuth = CopyClientAuth(t->ClientAuth);
	}
	Unlock(n->lock);

	NiWriteConfig(n);

	if (n->Online)
	{
		NtOffline(n, NULL);
		NtOnline(n, NULL);
	}

	return ERR_NO_ERROR;
}

// Get the connection settings
UINT NtGetClientConfig(NAT *n, RPC_CREATE_LINK *t)
{
	UINT err = ERR_NO_ERROR;

	Lock(n->lock);
	{
		if (n->ClientOption == NULL || n->ClientAuth == NULL)
		{
			err = ERR_ACCOUNT_NOT_PRESENT;
		}
		else
		{
			FreeRpcCreateLink(t);

			Zero(t, sizeof(RPC_CREATE_LINK));
			t->ClientOption = ZeroMalloc(sizeof(CLIENT_OPTION));
			Copy(t->ClientOption, n->ClientOption, sizeof(CLIENT_OPTION));
			t->ClientAuth = CopyClientAuth(n->ClientAuth);
		}
	}
	Unlock(n->lock);

	return err;
}

// Get the state
UINT NtGetStatus(NAT *n, RPC_NAT_STATUS *t)
{
	Lock(n->lock);
	{
		VH *v = n->Virtual;
		FreeRpcNatStatus(t);
		Zero(t, sizeof(RPC_NAT_STATUS));

		LockVirtual(v);
		{
			UINT i;

			LockList(v->NatTable);
			{
				for (i = 0;i < LIST_NUM(v->NatTable);i++)
				{
					NAT_ENTRY *e = LIST_DATA(v->NatTable, i);

					switch (e->Protocol)
					{
					case NAT_TCP:
						t->NumTcpSessions++;
						break;

					case NAT_UDP:
						t->NumUdpSessions++;
						break;

					case NAT_ICMP:
						t->NumIcmpSessions++;
						break;

					case NAT_DNS:
						t->NumDnsSessions++;
						break;
					}
				}

				if (NnIsActive(v) && v->NativeNat != NULL)
				{
					NATIVE_NAT *nn = v->NativeNat;

					for (i = 0;i < LIST_NUM(nn->NatTableForSend->AllList);i++)
					{
						NATIVE_NAT_ENTRY *e = LIST_DATA(nn->NatTableForSend->AllList, i);

						switch (e->Protocol)
						{
						case NAT_TCP:
							t->NumTcpSessions++;
							break;

						case NAT_UDP:
							t->NumUdpSessions++;
							break;

						case NAT_ICMP:
							t->NumIcmpSessions++;
							break;

						case NAT_DNS:
							t->NumDnsSessions++;
							break;
						}
					}
				}
			}
			UnlockList(v->NatTable);

			t->NumDhcpClients = LIST_NUM(v->DhcpLeaseList);

			t->IsKernelMode = NnIsActiveEx(v, &t->IsRawIpMode);
		}
		UnlockVirtual(v);
	}
	Unlock(n->lock);

	return ERR_NO_ERROR;
}

// Get the information
UINT NtGetInfo(NAT *n, RPC_NAT_INFO *t)
{
	OS_INFO *info;
	FreeRpcNatInfo(t);
	Zero(t, sizeof(RPC_NAT_INFO));

	StrCpy(t->NatProductName, sizeof(t->NatProductName), CEDAR_ROUTER_STR);
	StrCpy(t->NatVersionString, sizeof(t->NatVersionString), n->Cedar->VerString);
	StrCpy(t->NatBuildInfoString, sizeof(t->NatBuildInfoString), n->Cedar->BuildInfo);
	t->NatVerInt = n->Cedar->Build;
	t->NatBuildInt = n->Cedar->Build;

	GetMachineName(t->NatHostName, sizeof(t->NatHostName));

	info = GetOsInfo();

	CopyOsInfo(&t->OsInfo, info);

	GetMemInfo(&t->MemInfo);

	return ERR_NO_ERROR;
}

// Get the NAT list
UINT NtEnumNatList(NAT *n, RPC_ENUM_NAT *t)
{
	UINT ret = ERR_NO_ERROR;
	VH *v = NULL;

	Lock(n->lock);
	{
		v = n->Virtual;

		if (n->Online == false || v == NULL)
		{
			ret = ERR_OFFLINE;
		}
		else
		{
			LockVirtual(v);
			{
				if (v->Active == false)
				{
					ret = ERR_OFFLINE;
				}
				else
				{
					FreeRpcEnumNat(t);
					Zero(t, sizeof(RPC_ENUM_NAT));

					LockList(v->NatTable);
					{
						UINT i;
						UINT num_usermode_nat = LIST_NUM(v->NatTable);
						UINT num_kernel_mode_nat = 0;
						NATIVE_NAT *native = NULL;

						if (NnIsActive(v) && (v->NativeNat != NULL))
						{
							native = v->NativeNat;

							num_kernel_mode_nat = LIST_NUM(native->NatTableForSend->AllList);
						}

						t->NumItem = num_usermode_nat + num_kernel_mode_nat;
						t->Items = ZeroMalloc(sizeof(RPC_ENUM_NAT_ITEM) * t->NumItem);

						// Enumerate entries of the user mode NAT
						for (i = 0;i < num_usermode_nat;i++)
						{
							NAT_ENTRY *nat = LIST_DATA(v->NatTable, i);
							RPC_ENUM_NAT_ITEM *e = &t->Items[i];

							e->Id = nat->Id;
							e->Protocol = nat->Protocol;
							e->SrcIp = nat->SrcIp;
							e->DestIp = nat->DestIp;
							e->SrcPort = nat->SrcPort;
							e->DestPort = nat->DestPort;

							e->CreatedTime = TickToTime(nat->CreatedTime);
							e->LastCommTime = TickToTime(nat->LastCommTime);

							IPToStr32(e->SrcHost, sizeof(e->SrcHost), e->SrcIp);
							IPToStr32(e->DestHost, sizeof(e->DestHost), e->DestIp);

							if (nat->Sock != NULL)
							{
								e->SendSize = nat->Sock->SendSize;
								e->RecvSize = nat->Sock->RecvSize;

								if (nat->Sock->Type == SOCK_TCP)
								{
									StrCpy(e->DestHost, sizeof(e->DestHost), nat->Sock->RemoteHostname);
								}
							}

							e->TcpStatus = nat->TcpStatus;
						}

						// Enumerate the entries in the kernel-mode NAT
						if (native != NULL)
						{
							for (i = 0;i < num_kernel_mode_nat;i++)
							{
								NATIVE_NAT_ENTRY *nat = LIST_DATA(native->NatTableForSend->AllList, i);
								RPC_ENUM_NAT_ITEM *e = &t->Items[num_usermode_nat + i];

								e->Id = nat->Id;
								e->Protocol = nat->Protocol;
								e->SrcIp = nat->SrcIp;
								e->DestIp = nat->DestIp;
								e->SrcPort = nat->SrcPort;
								e->DestPort = nat->DestPort;
								e->CreatedTime = TickToTime(nat->CreatedTime);
								e->LastCommTime = TickToTime(nat->LastCommTime);

								IPToStr32(e->SrcHost, sizeof(e->SrcHost), e->SrcIp);
								IPToStr32(e->DestHost, sizeof(e->DestHost), e->DestIp);

								e->SendSize = nat->TotalSent;
								e->RecvSize = nat->TotalRecv;

								e->TcpStatus = nat->Status;
							}
						}
					}
					UnlockList(v->NatTable);
				}
			}
			UnlockVirtual(v);
		}
	}
	Unlock(n->lock);

	return ret;
}

UINT NtEnumDhcpList(NAT *n, RPC_ENUM_DHCP *t)
{
	UINT ret = ERR_NO_ERROR;
	VH *v = NULL;

	Lock(n->lock);
	{
		v = n->Virtual;

		if (n->Online == false || v == NULL)
		{
			ret = ERR_OFFLINE;
		}
		else
		{
			LockVirtual(v);
			{
				if (v->Active == false)
				{
					ret = ERR_OFFLINE;
				}
				else
				{
					FreeRpcEnumDhcp(t);
					Zero(t, sizeof(RPC_ENUM_DHCP));

					LockList(v->DhcpLeaseList);
					{
						UINT i;
						t->NumItem = LIST_NUM(v->DhcpLeaseList);
						t->Items = ZeroMalloc(sizeof(RPC_ENUM_DHCP_ITEM) * t->NumItem);

						for (i = 0;i < t->NumItem;i++)
						{
							DHCP_LEASE *dhcp = LIST_DATA(v->DhcpLeaseList, i);
							RPC_ENUM_DHCP_ITEM *e = &t->Items[i];

							e->Id = dhcp->Id;
							e->LeasedTime = TickToTime(dhcp->LeasedTime);
							e->ExpireTime = TickToTime(dhcp->ExpireTime);
							Copy(e->MacAddress, dhcp->MacAddress, 6);
							e->IpAddress = dhcp->IpAddress;
							e->Mask = dhcp->Mask;
							StrCpy(e->Hostname, sizeof(e->Hostname), dhcp->Hostname);
						}
					}
					UnlockList(v->DhcpLeaseList);
				}
			}
			UnlockVirtual(v);
		}
	}
	Unlock(n->lock);

	return ret;
}

// VH_OPTION
void InVhOption(VH_OPTION *t, PACK *p)
{
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	Zero(t, sizeof(VH_OPTION));
	PackGetData2(p, "MacAddress", t->MacAddress, 6);
	PackGetIp(p, "Ip", &t->Ip);
	PackGetIp(p, "Mask", &t->Mask);
	t->UseNat = PackGetBool(p, "UseNat");
	t->Mtu = PackGetInt(p, "Mtu");
	t->NatTcpTimeout = PackGetInt(p, "NatTcpTimeout");
	t->NatUdpTimeout = PackGetInt(p, "NatUdpTimeout");
	t->UseDhcp = PackGetBool(p, "UseDhcp");
	PackGetIp(p, "DhcpLeaseIPStart", &t->DhcpLeaseIPStart);
	PackGetIp(p, "DhcpLeaseIPEnd", &t->DhcpLeaseIPEnd);
	PackGetIp(p, "DhcpSubnetMask", &t->DhcpSubnetMask);
	t->DhcpExpireTimeSpan = PackGetInt(p, "DhcpExpireTimeSpan");
	PackGetIp(p, "DhcpGatewayAddress", &t->DhcpGatewayAddress);
	PackGetIp(p, "DhcpDnsServerAddress", &t->DhcpDnsServerAddress);
	PackGetIp(p, "DhcpDnsServerAddress2", &t->DhcpDnsServerAddress2);
	PackGetStr(p, "DhcpDomainName", t->DhcpDomainName, sizeof(t->DhcpDomainName));
	t->SaveLog = PackGetBool(p, "SaveLog");
	PackGetStr(p, "RpcHubName", t->HubName, sizeof(t->HubName));
	t->ApplyDhcpPushRoutes = PackGetBool(p, "ApplyDhcpPushRoutes");
	PackGetStr(p, "DhcpPushRoutes", t->DhcpPushRoutes, sizeof(t->DhcpPushRoutes));
}
void OutVhOption(PACK *p, VH_OPTION *t)
{
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	PackAddData(p, "MacAddress", t->MacAddress, 6);
	PackAddIp(p, "Ip", &t->Ip);
	PackAddIp(p, "Mask", &t->Mask);
	PackAddBool(p, "UseNat", t->UseNat);
	PackAddInt(p, "Mtu", t->Mtu);
	PackAddInt(p, "NatTcpTimeout", t->NatTcpTimeout);
	PackAddInt(p, "NatUdpTimeout", t->NatUdpTimeout);
	PackAddBool(p, "UseDhcp", t->UseDhcp);
	PackAddIp(p, "DhcpLeaseIPStart", &t->DhcpLeaseIPStart);
	PackAddIp(p, "DhcpLeaseIPEnd", &t->DhcpLeaseIPEnd);
	PackAddIp(p, "DhcpSubnetMask", &t->DhcpSubnetMask);
	PackAddInt(p, "DhcpExpireTimeSpan", t->DhcpExpireTimeSpan);
	PackAddIp(p, "DhcpGatewayAddress", &t->DhcpGatewayAddress);
	PackAddIp(p, "DhcpDnsServerAddress", &t->DhcpDnsServerAddress);
	PackAddIp(p, "DhcpDnsServerAddress2", &t->DhcpDnsServerAddress2);
	PackAddStr(p, "DhcpDomainName", t->DhcpDomainName);
	PackAddBool(p, "SaveLog", t->SaveLog);
	PackAddStr(p, "RpcHubName", t->HubName);
	PackAddBool(p, "ApplyDhcpPushRoutes", true);
	PackAddStr(p, "DhcpPushRoutes", t->DhcpPushRoutes);
}

// RPC_ENUM_DHCP
void InRpcEnumDhcp(RPC_ENUM_DHCP *t, PACK *p)
{
	UINT i;
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	Zero(t, sizeof(RPC_ENUM_DHCP));
	t->NumItem = PackGetInt(p, "NumItem");
	t->Items = ZeroMalloc(sizeof(RPC_ENUM_DHCP_ITEM) * t->NumItem);
	PackGetStr(p, "HubName", t->HubName, sizeof(t->HubName));

	for (i = 0;i < t->NumItem;i++)
	{
		RPC_ENUM_DHCP_ITEM *e = &t->Items[i];

		e->Id = PackGetIntEx(p, "Id", i);
		e->LeasedTime = PackGetInt64Ex(p, "LeasedTime", i);
		e->ExpireTime = PackGetInt64Ex(p, "ExpireTime", i);
		PackGetDataEx2(p, "MacAddress", e->MacAddress, 6, i);
		e->IpAddress = PackGetIp32Ex(p, "IpAddress", i);
		e->Mask = PackGetIntEx(p, "Mask", i);
		PackGetStrEx(p, "Hostname", e->Hostname, sizeof(e->Hostname), i);
	}
}
void OutRpcEnumDhcp(PACK *p, RPC_ENUM_DHCP *t)
{
	UINT i;
	// Validate arguments
	if (p == NULL || t == NULL)
	{
		return;
	}

	PackAddInt(p, "NumItem", t->NumItem);
	PackAddStr(p, "HubName", t->HubName);

	PackSetCurrentJsonGroupName(p, "DhcpTable");
	for (i = 0;i < t->NumItem;i++)
	{
		RPC_ENUM_DHCP_ITEM *e = &t->Items[i];

		PackAddIntEx(p, "Id", e->Id, i, t->NumItem);
		PackAddTime64Ex(p, "LeasedTime", e->LeasedTime, i, t->NumItem);
		PackAddTime64Ex(p, "ExpireTime", e->ExpireTime, i, t->NumItem);
		PackAddDataEx(p, "MacAddress", e->MacAddress, 6, i, t->NumItem);
		PackAddIp32Ex(p, "IpAddress", e->IpAddress, i, t->NumItem);
		PackAddIntEx(p, "Mask", e->Mask, i, t->NumItem);
		PackAddStrEx(p, "Hostname", e->Hostname, i, t->NumItem);
	}
	PackSetCurrentJsonGroupName(p, NULL);
}
void FreeRpcEnumDhcp(RPC_ENUM_DHCP *t)
{
	// Validate arguments
	if (t == NULL)
	{
		return;
	}

	Free(t->Items);
}

// RPC_ENUM_NAT
void InRpcEnumNat(RPC_ENUM_NAT *t, PACK *p)
{
	UINT i;
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	Zero(t, sizeof(RPC_ENUM_NAT));
	t->NumItem = PackGetInt(p, "NumItem");
	PackGetStr(p, "HubName", t->HubName, sizeof(t->HubName));
	t->Items = ZeroMalloc(sizeof(RPC_ENUM_NAT_ITEM) * t->NumItem);
	for (i = 0;i < t->NumItem;i++)
	{
		RPC_ENUM_NAT_ITEM *e = &t->Items[i];

		e->Id = PackGetIntEx(p, "Id", i);
		e->Protocol = PackGetIntEx(p, "Protocol", i);
		e->SrcIp = PackGetIntEx(p, "SrcIp", i);
		PackGetStrEx(p, "SrcHost", e->SrcHost, sizeof(e->SrcHost), i);
		e->SrcPort = PackGetIntEx(p, "SrcPort", i);
		e->DestIp = PackGetIntEx(p, "DestIp", i);
		PackGetStrEx(p, "DestHost", e->DestHost, sizeof(e->DestHost), i);
		e->DestPort = PackGetIntEx(p, "DestPort", i);
		e->CreatedTime = PackGetInt64Ex(p, "CreatedTime", i);
		e->LastCommTime = PackGetInt64Ex(p, "LastCommTime", i);
		e->SendSize = PackGetInt64Ex(p, "SendSize", i);
		e->RecvSize = PackGetInt64Ex(p, "RecvSize", i);
		e->TcpStatus = PackGetIntEx(p, "TcpStatus", i);
	}
}
void OutRpcEnumNat(PACK *p, RPC_ENUM_NAT *t)
{
	UINT i;
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	PackAddInt(p, "NumItem", t->NumItem);
	PackAddStr(p, "HubName", t->HubName);

	PackSetCurrentJsonGroupName(p, "NatTable");
	for (i = 0;i < t->NumItem;i++)
	{
		RPC_ENUM_NAT_ITEM *e = &t->Items[i];

		PackAddIntEx(p, "Id", e->Id, i, t->NumItem);
		PackAddIntEx(p, "Protocol", e->Protocol, i, t->NumItem);
		PackAddIp32Ex(p, "SrcIp", e->SrcIp, i, t->NumItem);
		PackAddStrEx(p, "SrcHost", e->SrcHost, i, t->NumItem);
		PackAddIntEx(p, "SrcPort", e->SrcPort, i, t->NumItem);
		PackAddIp32Ex(p, "DestIp", e->DestIp, i, t->NumItem);
		PackAddStrEx(p, "DestHost", e->DestHost, i, t->NumItem);
		PackAddIntEx(p, "DestPort", e->DestPort, i, t->NumItem);
		PackAddTime64Ex(p, "CreatedTime", e->CreatedTime, i, t->NumItem);
		PackAddTime64Ex(p, "LastCommTime", e->LastCommTime, i, t->NumItem);
		PackAddInt64Ex(p, "SendSize", e->SendSize, i, t->NumItem);
		PackAddInt64Ex(p, "RecvSize", e->RecvSize, i, t->NumItem);
		PackAddIntEx(p, "TcpStatus", e->TcpStatus, i, t->NumItem);
	}
	PackSetCurrentJsonGroupName(p, NULL);
}
void FreeRpcEnumNat(RPC_ENUM_NAT *t)
{
	// Validate arguments
	if (t == NULL)
	{
		return;
	}

	Free(t->Items);
}

// RPC_NAT_INFO
void InRpcNatInfo(RPC_NAT_INFO *t, PACK *p)
{
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	Zero(t, sizeof(RPC_NAT_INFO));
	PackGetStr(p, "NatProductName", t->NatProductName, sizeof(t->NatProductName));
	PackGetStr(p, "NatVersionString", t->NatVersionString, sizeof(t->NatVersionString));
	PackGetStr(p, "NatBuildInfoString", t->NatBuildInfoString, sizeof(t->NatBuildInfoString));
	t->NatVerInt = PackGetInt(p, "NatVerInt");
	t->NatBuildInt = PackGetInt(p, "NatBuildInt");
	PackGetStr(p, "NatHostName", t->NatHostName, sizeof(t->NatHostName));
	InRpcOsInfo(&t->OsInfo, p);
	InRpcMemInfo(&t->MemInfo, p);
}
void OutRpcNatInfo(PACK *p, RPC_NAT_INFO *t)
{
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	PackAddStr(p, "NatProductName", t->NatProductName);
	PackAddStr(p, "NatVersionString", t->NatVersionString);
	PackAddStr(p, "NatBuildInfoString", t->NatBuildInfoString);
	PackAddInt(p, "NatVerInt", t->NatVerInt);
	PackAddInt(p, "NatBuildInt", t->NatBuildInt);
	PackAddStr(p, "NatHostName", t->NatHostName);
	OutRpcOsInfo(p, &t->OsInfo);
	OutRpcMemInfo(p, &t->MemInfo);
}
void FreeRpcNatInfo(RPC_NAT_INFO *t)
{
	// Validate arguments
	if (t == NULL)
	{
		return;
	}

	FreeRpcOsInfo(&t->OsInfo);
}

// RPC_NAT_STATUS
void InRpcNatStatus(RPC_NAT_STATUS *t, PACK *p)
{
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	Zero(t, sizeof(RPC_NAT_STATUS));
	t->NumTcpSessions = PackGetInt(p, "NumTcpSessions");
	t->NumUdpSessions = PackGetInt(p, "NumUdpSessions");
	t->NumIcmpSessions = PackGetInt(p, "NumIcmpSessions");
	t->NumDnsSessions = PackGetInt(p, "NumDnsSessions");
	t->NumDhcpClients = PackGetInt(p, "NumDhcpClients");
	t->IsKernelMode = PackGetBool(p, "IsKernelMode");
	t->IsRawIpMode = PackGetBool(p, "IsRawIpMode");
	PackGetStr(p, "HubName", t->HubName, sizeof(t->HubName));
}
void OutRpcNatStatus(PACK *p, RPC_NAT_STATUS *t)
{
	// Validate arguments
	if (p == NULL || t == NULL)
	{
		return;
	}

	PackAddStr(p, "HubName", t->HubName);
	PackAddInt(p, "NumTcpSessions", t->NumTcpSessions);
	PackAddInt(p, "NumUdpSessions", t->NumUdpSessions);
	PackAddInt(p, "NumIcmpSessions", t->NumIcmpSessions);
	PackAddInt(p, "NumDnsSessions", t->NumDnsSessions);
	PackAddInt(p, "NumDhcpClients", t->NumDhcpClients);
	PackAddBool(p, "IsKernelMode", t->IsKernelMode);
	PackAddBool(p, "IsRawIpMode", t->IsRawIpMode);
}
void FreeRpcNatStatus(RPC_NAT_STATUS *t)
{
}

// RPC_DUMMY
void InRpcDummy(RPC_DUMMY *t, PACK *p)
{
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	Zero(t, sizeof(RPC_DUMMY));
	t->DummyValue = PackGetInt(p, "DummyValue");
}
void OutRpcDummy(PACK *p, RPC_DUMMY *t)
{
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	PackAddInt(p, "DummyValue", t->DummyValue);
}

// Main procedure for management
void NiAdminMain(NAT *n, SOCK *s)
{
	RPC *r;
	PACK *p;
	// Validate arguments
	if (n == NULL || s == NULL)
	{
		return;
	}

	p = NewPack();
	HttpServerSend(s, p);
	FreePack(p);

	r = StartRpcServer(s, NiRpcServer, n);

	RpcServer(r);

	RpcFree(r);
}

// Management thread
void NiAdminThread(THREAD *thread, void *param)
{
	NAT_ADMIN *a = (NAT_ADMIN *)param;
	NAT *n;
	SOCK *s;
	UCHAR random[SHA1_SIZE];
	UINT err;
	// Validate arguments
	if (thread == NULL || param == NULL)
	{
		return;
	}

	// Random number generation
	Rand(random, sizeof(random));

	a->Thread = thread;
	AddRef(a->Thread->ref);
	s = a->Sock;
	AddRef(s->ref);

	n = a->Nat;

	LockList(n->AdminList);
	{
		Add(n->AdminList, a);
	}
	UnlockList(n->AdminList);

	NoticeThreadInit(thread);

	err = ERR_AUTH_FAILED;

	if (StartSSL(s, n->AdminX, n->AdminK))
	{
		PACK *p;

		// Send the random number
		p = NewPack();
		PackAddData(p, "auth_random", random, sizeof(random));

		if (HttpServerSend(s, p))
		{
			PACK *p;
			// Receive a password
			p = HttpServerRecv(s);
			if (p != NULL)
			{
				UCHAR secure_password[SHA1_SIZE];
				UCHAR secure_check[SHA1_SIZE];

				if (PackGetData2(p, "secure_password", secure_password, sizeof(secure_password)))
				{
					SecurePassword(secure_check, n->HashedPassword, random);

					if (Cmp(secure_check, secure_password, SHA1_SIZE) == 0)
					{
						UCHAR test[SHA1_SIZE];
						// Password match
						Sha0(test, "", 0);
						SecurePassword(test, test, random);

#if	0
						if (Cmp(test, secure_check, SHA1_SIZE) == 0 && s->RemoteIP.addr[0] != 127)
						{
							// A client can not connect from the outside with blank password
							err = ERR_NULL_PASSWORD_LOCAL_ONLY;
						}
						else
#endif

						{
							// Successful connection
							err = ERR_NO_ERROR;
							NiAdminMain(n, s);
						}
					}
				}

				FreePack(p);
			}
		}

		FreePack(p);

		if (err != ERR_NO_ERROR)
		{
			p = PackError(err);
			HttpServerSend(s, p);
			FreePack(p);
		}
	}

	Disconnect(s);
	ReleaseSock(s);
}

// Management port Listen thread
void NiListenThread(THREAD *thread, void *param)
{
	NAT *n = (NAT *)param;
	SOCK *a;
	UINT i;
	bool b = false;
	// Validate arguments
	if (thread == NULL || param == NULL)
	{
		return;
	}

	// Initialize the management list
	n->AdminList = NewList(NULL);

	while (true)
	{
		a = Listen(DEFAULT_NAT_ADMIN_PORT);
		if (b == false)
		{
			b = true;
			NoticeThreadInit(thread);
		}
		if (a != NULL)
		{
			break;
		}

		Wait(n->HaltEvent, NAT_ADMIN_PORT_LISTEN_INTERVAL);
		if (n->Halt)
		{
			return;
		}
	}

	n->AdminListenSock = a;
	AddRef(a->ref);

	// Waiting
	while (true)
	{
		SOCK *s = Accept(a);
		THREAD *t;
		NAT_ADMIN *admin;
		if (s == NULL)
		{
			break;
		}
		if (n->Halt)
		{
			ReleaseSock(s);
			break;
		}

		admin = ZeroMalloc(sizeof(NAT_ADMIN));
		admin->Nat = n;
		admin->Sock = s;
		t = NewThread(NiAdminThread, admin);
		WaitThreadInit(t);
		ReleaseThread(t);
	}

	// Disconnect all management connections
	LockList(n->AdminList);
	{
		for (i = 0;i < LIST_NUM(n->AdminList);i++)
		{
			NAT_ADMIN *a = LIST_DATA(n->AdminList, i);
			Disconnect(a->Sock);
			WaitThread(a->Thread, INFINITE);
			ReleaseThread(a->Thread);
			ReleaseSock(a->Sock);
			Free(a);
		}
	}
	UnlockList(n->AdminList);

	ReleaseList(n->AdminList);

	ReleaseSock(a);
}

// Initialize receiving management command
void NiInitAdminAccept(NAT *n)
{
	THREAD *t;
	// Validate arguments
	if (n == NULL)
	{
		return;
	}

	t = NewThread(NiListenThread, n);
	WaitThreadInit(t);
	n->AdminAcceptThread = t;
}

// Complete receiving management command
void NiFreeAdminAccept(NAT *n)
{
	// Validate arguments
	if (n == NULL)
	{
		return;
	}

	n->Halt = true;
	Disconnect(n->AdminListenSock);
	Set(n->HaltEvent);

	while (true)
	{
		if (WaitThread(n->AdminAcceptThread, 1000) == false)
		{
			Disconnect(n->AdminListenSock);
		}
		else
		{
			break;
		}
	}
	ReleaseThread(n->AdminAcceptThread);

	ReleaseSock(n->AdminListenSock);
}

// Clear the DHCP options that are not supported by the dynamic Virtual HUB
void NiClearUnsupportedVhOptionForDynamicHub(VH_OPTION *o, bool initial)
{
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	o->UseNat = false;

	if (initial)
	{
		Zero(&o->DhcpGatewayAddress, sizeof(IP));
		Zero(&o->DhcpDnsServerAddress, sizeof(IP));
		Zero(&o->DhcpDnsServerAddress2, sizeof(IP));
		StrCpy(o->DhcpDomainName, sizeof(o->DhcpDomainName), "");
	}
}

// Initialize the options for the virtual host
void NiSetDefaultVhOption(NAT *n, VH_OPTION *o)
{
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	Zero(o, sizeof(VH_OPTION));
	GenMacAddress(o->MacAddress);

	// Set the virtual IP to 192.168.30.1/24
	SetIP(&o->Ip, 192, 168, 30, 1);
	SetIP(&o->Mask, 255, 255, 255, 0);
	o->UseNat = true;
	o->Mtu = 1500;
	o->NatTcpTimeout = 1800;
	o->NatUdpTimeout = 60;
	o->UseDhcp = true;
	SetIP(&o->DhcpLeaseIPStart, 192, 168, 30, 10);
	SetIP(&o->DhcpLeaseIPEnd, 192, 168, 30, 200);
	SetIP(&o->DhcpSubnetMask, 255, 255, 255, 0);
	o->DhcpExpireTimeSpan = 7200;
	o->SaveLog = true;

	SetIP(&o->DhcpGatewayAddress, 192, 168, 30, 1);
	SetIP(&o->DhcpDnsServerAddress, 192, 168, 30, 1);

	GetDomainName(o->DhcpDomainName, sizeof(o->DhcpDomainName));
}

// Reset the setting of NAT to the default
void NiInitDefaultConfig(NAT *n)
{
	// Validate arguments
	if (n == NULL)
	{
		return;
	}

	// Initialize the virtual host option
	NiSetDefaultVhOption(n, &n->Option);

	// Initialize management port
	n->AdminPort = DEFAULT_NAT_ADMIN_PORT;

	// Offline
	n->Online = false;

	// Save the log
	n->Option.SaveLog = true;
}

// Initialize the NAT configuration
void NiInitConfig(NAT *n)
{
	// Validate arguments
	if (n == NULL)
	{
		return;
	}

	// Initial state
	NiInitDefaultConfig(n);
}

// Read the virtual host option (extended)
void NiLoadVhOptionEx(VH_OPTION *o, FOLDER *root)
{
	FOLDER *host, *nat, *dhcp;
	char mac_address[MAX_SIZE];
	// Validate arguments
	if (o == NULL || root == NULL)
	{
		return;
	}

	host = CfgGetFolder(root, "VirtualHost");
	nat = CfgGetFolder(root, "VirtualRouter");
	dhcp = CfgGetFolder(root, "VirtualDhcpServer");

	Zero(o, sizeof(VH_OPTION));

	GenMacAddress(o->MacAddress);
	if (CfgGetStr(host, "VirtualHostMacAddress", mac_address, sizeof(mac_address)))
	{
		BUF *b = StrToBin(mac_address);
		if (b != NULL)
		{
			if (b->Size == 6)
			{
				Copy(o->MacAddress, b->Buf, 6);
			}
		}
		FreeBuf(b);
	}
	CfgGetIp(host, "VirtualHostIp", &o->Ip);
	CfgGetIp(host, "VirtualHostIpSubnetMask", &o->Mask);

	o->UseNat = CfgGetBool(nat, "NatEnabled");
	o->Mtu = CfgGetInt(nat, "NatMtu");
	o->NatTcpTimeout = CfgGetInt(nat, "NatTcpTimeout");
	o->NatUdpTimeout = CfgGetInt(nat, "NatUdpTimeout");

	o->UseDhcp = CfgGetBool(dhcp, "DhcpEnabled");
	CfgGetIp(dhcp, "DhcpLeaseIPStart", &o->DhcpLeaseIPStart);
	CfgGetIp(dhcp, "DhcpLeaseIPEnd", &o->DhcpLeaseIPEnd);
	CfgGetIp(dhcp, "DhcpSubnetMask", &o->DhcpSubnetMask);
	o->DhcpExpireTimeSpan = CfgGetInt(dhcp, "DhcpExpireTimeSpan");
	CfgGetIp(dhcp, "DhcpGatewayAddress", &o->DhcpGatewayAddress);
	CfgGetIp(dhcp, "DhcpDnsServerAddress", &o->DhcpDnsServerAddress);
	CfgGetIp(dhcp, "DhcpDnsServerAddress2", &o->DhcpDnsServerAddress2);
	CfgGetStr(dhcp, "DhcpDomainName", o->DhcpDomainName, sizeof(o->DhcpDomainName));

	CfgGetStr(dhcp, "DhcpPushRoutes", o->DhcpPushRoutes, sizeof(o->DhcpPushRoutes));

// Test code
//	StrCpy(o->DhcpPushRoutes, sizeof(o->DhcpPushRoutes),
//		"130.158.6.0/24/192.168.9.2 130.158.80.244/255.255.255.255/192.168.9.2");

	NormalizeClasslessRouteTableStr(o->DhcpPushRoutes, sizeof(o->DhcpPushRoutes), o->DhcpPushRoutes);
	o->ApplyDhcpPushRoutes = true;

	Trim(o->DhcpDomainName);
	if (StrLen(o->DhcpDomainName) == 0)
	{
		//GetDomainName(o->DhcpDomainName, sizeof(o->DhcpDomainName));
	}

	o->SaveLog = CfgGetBool(root, "SaveLog");
}

// Read the virtual host option
void NiLoadVhOption(NAT *n, FOLDER *root)
{
	VH_OPTION *o;
	FOLDER *host, *nat, *dhcp;
	char mac_address[MAX_SIZE];
	// Validate arguments
	if (n == NULL || root == NULL)
	{
		return;
	}

	host = CfgGetFolder(root, "VirtualHost");
	nat = CfgGetFolder(root, "VirtualRouter");
	dhcp = CfgGetFolder(root, "VirtualDhcpServer");

	o = &n->Option;
	Zero(o, sizeof(VH_OPTION));

	GenMacAddress(o->MacAddress);
	if (CfgGetStr(host, "VirtualHostMacAddress", mac_address, sizeof(mac_address)))
	{
		BUF *b = StrToBin(mac_address);
		if (b != NULL)
		{
			if (b->Size == 6)
			{
				Copy(o->MacAddress, b->Buf, 6);
			}
		}
		FreeBuf(b);
	}
	CfgGetIp(host, "VirtualHostIp", &o->Ip);
	CfgGetIp(host, "VirtualHostIpSubnetMask", &o->Mask);

	o->UseNat = CfgGetBool(nat, "NatEnabled");
	o->Mtu = CfgGetInt(nat, "NatMtu");
	o->NatTcpTimeout = CfgGetInt(nat, "NatTcpTimeout");
	o->NatUdpTimeout = CfgGetInt(nat, "NatUdpTimeout");

	o->UseDhcp = CfgGetBool(dhcp, "DhcpEnabled");
	CfgGetIp(dhcp, "DhcpLeaseIPStart", &o->DhcpLeaseIPStart);
	CfgGetIp(dhcp, "DhcpLeaseIPEnd", &o->DhcpLeaseIPEnd);
	CfgGetIp(dhcp, "DhcpSubnetMask", &o->DhcpSubnetMask);
	o->DhcpExpireTimeSpan = CfgGetInt(dhcp, "DhcpExpireTimeSpan");
	CfgGetIp(dhcp, "DhcpGatewayAddress", &o->DhcpGatewayAddress);
	CfgGetIp(dhcp, "DhcpDnsServerAddress", &o->DhcpDnsServerAddress);
	CfgGetIp(dhcp, "DhcpDnsServerAddress2", &o->DhcpDnsServerAddress2);
	CfgGetStr(dhcp, "DhcpDomainName", o->DhcpDomainName, sizeof(o->DhcpDomainName));

	o->SaveLog = CfgGetBool(root, "SaveLog");
}

// Read connection options from the VPN server
void NiLoadClientData(NAT *n, FOLDER *root)
{
	FOLDER *co, *ca;
	// Validate arguments
	if (n == NULL || root == NULL)
	{
		return;
	}

	co = CfgGetFolder(root, "VpnClientOption");
	ca = CfgGetFolder(root, "VpnClientAuth");
	if (co == NULL || ca == NULL)
	{
		return;
	}

	n->ClientOption = CiLoadClientOption(co);
	n->ClientAuth = CiLoadClientAuth(ca);
}

// Write connection options to the VPN server
void NiWriteClientData(NAT *n, FOLDER *root)
{
	// Validate arguments
	if (n == NULL || root == NULL || n->ClientOption == NULL || n->ClientAuth == NULL)
	{
		return;
	}

	CiWriteClientOption(CfgCreateFolder(root, "VpnClientOption"), n->ClientOption);
	CiWriteClientAuth(CfgCreateFolder(root, "VpnClientAuth"), n->ClientAuth);
}

// Write the virtual host option (extended)
void NiWriteVhOptionEx(VH_OPTION *o, FOLDER *root)
{
	FOLDER *host, *nat, *dhcp;
	char mac_address[MAX_SIZE];
	// Validate arguments
	if (o == NULL || root == NULL)
	{
		return;
	}

	host = CfgCreateFolder(root, "VirtualHost");
	nat = CfgCreateFolder(root, "VirtualRouter");
	dhcp = CfgCreateFolder(root, "VirtualDhcpServer");

	MacToStr(mac_address, sizeof(mac_address), o->MacAddress);
	CfgAddStr(host, "VirtualHostMacAddress", mac_address);
	CfgAddIp(host, "VirtualHostIp", &o->Ip);
	CfgAddIp(host, "VirtualHostIpSubnetMask", &o->Mask);

	CfgAddBool(nat, "NatEnabled", o->UseNat);
	CfgAddInt(nat, "NatMtu", o->Mtu);
	CfgAddInt(nat, "NatTcpTimeout", o->NatTcpTimeout);
	CfgAddInt(nat, "NatUdpTimeout", o->NatUdpTimeout);

	CfgAddBool(dhcp, "DhcpEnabled", o->UseDhcp);
	CfgAddIp(dhcp, "DhcpLeaseIPStart", &o->DhcpLeaseIPStart);
	CfgAddIp(dhcp, "DhcpLeaseIPEnd", &o->DhcpLeaseIPEnd);
	CfgAddIp(dhcp, "DhcpSubnetMask", &o->DhcpSubnetMask);
	CfgAddInt(dhcp, "DhcpExpireTimeSpan", o->DhcpExpireTimeSpan);
	CfgAddIp(dhcp, "DhcpGatewayAddress", &o->DhcpGatewayAddress);
	CfgAddIp(dhcp, "DhcpDnsServerAddress", &o->DhcpDnsServerAddress);
	CfgAddIp(dhcp, "DhcpDnsServerAddress2", &o->DhcpDnsServerAddress2);
	CfgAddStr(dhcp, "DhcpDomainName", o->DhcpDomainName);
	CfgAddStr(dhcp, "DhcpPushRoutes", o->DhcpPushRoutes);

	CfgAddBool(root, "SaveLog", o->SaveLog);
}

// Write the virtual host option
void NiWriteVhOption(NAT *n, FOLDER *root)
{
	VH_OPTION *o;
	FOLDER *host, *nat, *dhcp;
	char mac_address[MAX_SIZE];
	// Validate arguments
	if (n == NULL || root == NULL)
	{
		return;
	}

	host = CfgCreateFolder(root, "VirtualHost");
	nat = CfgCreateFolder(root, "VirtualRouter");
	dhcp = CfgCreateFolder(root, "VirtualDhcpServer");

	o = &n->Option;

	MacToStr(mac_address, sizeof(mac_address), o->MacAddress);
	CfgAddStr(host, "VirtualHostMacAddress", mac_address);
	CfgAddIp(host, "VirtualHostIp", &o->Ip);
	CfgAddIp(host, "VirtualHostIpSubnetMask", &o->Mask);

	CfgAddBool(nat, "NatEnabled", o->UseNat);
	CfgAddInt(nat, "NatMtu", o->Mtu);
	CfgAddInt(nat, "NatTcpTimeout", o->NatTcpTimeout);
	CfgAddInt(nat, "NatUdpTimeout", o->NatUdpTimeout);

	CfgAddBool(dhcp, "DhcpEnabled", o->UseDhcp);
	CfgAddIp(dhcp, "DhcpLeaseIPStart", &o->DhcpLeaseIPStart);
	CfgAddIp(dhcp, "DhcpLeaseIPEnd", &o->DhcpLeaseIPEnd);
	CfgAddIp(dhcp, "DhcpSubnetMask", &o->DhcpSubnetMask);
	CfgAddInt(dhcp, "DhcpExpireTimeSpan", o->DhcpExpireTimeSpan);
	CfgAddIp(dhcp, "DhcpGatewayAddress", &o->DhcpGatewayAddress);
	CfgAddIp(dhcp, "DhcpDnsServerAddress", &o->DhcpDnsServerAddress);
	CfgAddIp(dhcp, "DhcpDnsServerAddress2", &o->DhcpDnsServerAddress2);
	CfgAddStr(dhcp, "DhcpDomainName", o->DhcpDomainName);

	CfgAddBool(root, "SaveLog", o->SaveLog);
}

// Read the configuration file
bool NiLoadConfig(NAT *n, FOLDER *root)
{
	FOLDER *host;
	BUF *b;
	// Validate arguments
	if (n == NULL || root == NULL)
	{
		return false;
	}

	host = CfgGetFolder(root, "VirtualHost");
	if (host == NULL)
	{
		return false;
	}

	CfgGetByte(root, "HashedPassword", n->HashedPassword, sizeof(n->HashedPassword));
	n->AdminPort = CfgGetInt(root, "AdminPort");
	n->Online = CfgGetBool(root, "Online");

	b = CfgGetBuf(root, "AdminCert");
	if (b != NULL)
	{
		n->AdminX = BufToX(b, false);
		FreeBuf(b);
	}

	b = CfgGetBuf(root, "AdminKey");
	if (b != NULL)
	{
		n->AdminK = BufToK(b, true, false, NULL);
		FreeBuf(b);
	}

	NiLoadVhOption(n, root);

	NiLoadClientData(n, root);

	return true;
}

// Write the configuration to a file
void NiWriteConfig(NAT *n)
{
	// Validate arguments
	if (n == NULL)
	{
		return;
	}

	Lock(n->lock);
	{
		FOLDER *root = CfgCreateFolder(NULL, TAG_ROOT);
		BUF *b;

		// Certificate
		b = XToBuf(n->AdminX, false);
		CfgAddBuf(root, "AdminCert", b);
		FreeBuf(b);

		// Secret key
		b = KToBuf(n->AdminK, false, NULL);
		CfgAddBuf(root, "AdminKey", b);
		FreeBuf(b);

		// Password
		CfgAddByte(root, "HashedPassword", n->HashedPassword, sizeof(n->HashedPassword));
		CfgAddInt(root, "AdminPort", n->AdminPort);
		CfgAddBool(root, "Online", n->Online);

		// Virtual host option
		NiWriteVhOption(n, root);

		// Connection options
		if (n->ClientOption != NULL && n->ClientAuth != NULL)
		{
			NiWriteClientData(n, root);
		}

		SaveCfgRw(n->CfgRw, root);
		CfgDeleteFolder(root);
	}
	Unlock(n->lock);
}

// Release the NAT configuration
void NiFreeConfig(NAT *n)
{
	// Validate arguments
	if (n == NULL)
	{
		return;
	}

	// Write the latest configuration
	NiWriteConfig(n);

	// Release the configuration R/W
	FreeCfgRw(n->CfgRw);
	n->CfgRw = NULL;

	Free(n->ClientOption);
	CiFreeClientAuth(n->ClientAuth);

	FreeX(n->AdminX);
	FreeK(n->AdminK);
}

// Create a NAT
NAT *NiNewNatEx(SNAT *snat, VH_OPTION *o)
{
	NAT *n = ZeroMalloc(sizeof(NAT));

	n->lock = NewLock();
	Sha0(n->HashedPassword, "", 0);
	n->HaltEvent = NewEvent();

	//n->Cedar = NewCedar(NULL, NULL);

	n->SecureNAT = snat;

	// Raise the priority
	//OSSetHighPriority();

	// Initialize the settings
	NiInitConfig(n);

#if	0
	// Start the operation of the virtual host
	if (n->Online && n->ClientOption != NULL)
	{
		n->Virtual = NewVirtualHostEx(n->Cedar, n->ClientOption, n->ClientAuth, &n->Option, n);
	}
	else
	{
		n->Online = false;
		n->Virtual = NULL;
	}
#else
	n->Virtual = NewVirtualHostEx(n->Cedar, NULL, NULL, o, n);
	n->Online = true;
#endif

	// Start management command
	//NiInitAdminAccept(n);

	return n;
}
NAT *NiNewNat()
{
	return NiNewNatEx(NULL, NULL);
}

// Release the NAT
void NiFreeNat(NAT *n)
{
	// Validate arguments
	if (n == NULL)
	{
		return;
	}

	// Complete management command
	//NiFreeAdminAccept(n);

	// Stop if the virtual host is running
	Lock(n->lock);
	{
		if (n->Virtual != NULL)
		{
			StopVirtualHost(n->Virtual);
			ReleaseVirtual(n->Virtual);
			n->Virtual = NULL;
		}
	}
	Unlock(n->lock);

	// Release the settings
	NiFreeConfig(n);

	// Delete the object
	ReleaseCedar(n->Cedar);
	ReleaseEvent(n->HaltEvent);
	DeleteLock(n->lock);

	Free(n);
}

// Stop the NAT
void NtStopNat()
{
	Lock(nat_lock);
	{
		if (nat != NULL)
		{
			NiFreeNat(nat);
			nat = NULL;
		}
	}
	Unlock(nat_lock);
}

// Start the NAT
void NtStartNat()
{
	Lock(nat_lock);
	{
		if (nat == NULL)
		{
			nat = NiNewNat();
		}
	}
	Unlock(nat_lock);
}

// Initialize the NtXxx function
void NtInit()
{
	if (nat_lock != NULL)
	{
		return;
	}

	nat_lock = NewLock();
}

// Release the NtXxx function
void NtFree()
{
	if (nat_lock == NULL)
	{
		return;
	}

	DeleteLock(nat_lock);
	nat_lock = NULL;
}

