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


// Server.c
// VPN Server module

#include "Server.h"

#include "Admin.h"
#include "AzureClient.h"
#include "BridgeUnix.h"
#include "BridgeWin32.h"
#include "Connection.h"
#include "DDNS.h"
#include "Layer3.h"
#include "Link.h"
#include "Listener.h"
#include "Nat.h"
#include "Proto_IPsec.h"
#include "Protocol.h"
#include "Radius.h"
#include "Sam.h"
#include "SecureNAT.h"
#include "WinUi.h"

#include "Mayaqua/Cfg.h"
#include "Mayaqua/DNS.h"
#include "Mayaqua/FileIO.h"
#include "Mayaqua/Internat.h"
#include "Mayaqua/Memory.h"
#include "Mayaqua/Microsoft.h"
#include "Mayaqua/Object.h"
#include "Mayaqua/OS.h"
#include "Mayaqua/Pack.h"
#include "Mayaqua/Str.h"
#include "Mayaqua/Table.h"
#include "Mayaqua/TcpIp.h"
#include "Mayaqua/Tick64.h"
#include "Mayaqua/Win32.h"

static SERVER *server = NULL;
static LOCK *server_lock = NULL;
char *SERVER_CONFIG_FILE_NAME = "$vpn_server.config";
char *SERVER_CONFIG_FILE_NAME_IN_CLIENT = "$vpn_gate_svc.config";
char *SERVER_CONFIG_FILE_NAME_IN_CLIENT_RELAY = "$vpn_gate_relay.config";
char *BRIDGE_CONFIG_FILE_NAME = "$vpn_bridge.config";
char *SERVER_CONFIG_TEMPLATE_NAME = "$vpn_server_template.config";
char *BRIDGE_CONFIG_TEMPLATE_NAME = "$vpn_server_template.config";

static bool server_reset_setting = false;

static volatile UINT global_server_flags[NUM_GLOBAL_SERVER_FLAGS] = {0};

UINT vpn_global_parameters[NUM_GLOBAL_PARAMS] = {0};

// Get whether the number of user objects that are registered in the VPN Server is too many
bool SiTooManyUserObjectsInServer(SERVER *s, bool oneMore)
{
	return false;
}

typedef struct SI_DEBUG_PROC_LIST
{
	UINT Id;
	char *Description;
	char *Args;
	SI_DEBUG_PROC *Proc;
} SI_DEBUG_PROC_LIST;

// Debugging function
UINT SiDebug(SERVER *s, RPC_TEST *ret, UINT i, char *str)
{
	SI_DEBUG_PROC_LIST proc_list[] =
	{
		{1, "Hello World", "<test string>", SiDebugProcHelloWorld},
		{2, "Terminate process now", "", SiDebugProcExit},
		{3, "Write memory dumpfile", "", SiDebugProcDump},
		{4, "Restore process priority", "", SiDebugProcRestorePriority},
		{5, "Set the process priority high", "", SiDebugProcSetHighPriority},
		{6, "Get the .exe filename of the process", "", SiDebugProcGetExeFileName},
		{7, "Crash the process", "", SiDebugProcCrash},
		{8, "Get IPsecMessageDisplayed Flag", "", SiDebugProcGetIPsecMessageDisplayedValue},
		{9, "Set IPsecMessageDisplayed Flag", "", SiDebugProcSetIPsecMessageDisplayedValue},
		{10, "Get VgsMessageDisplayed Flag", "", SiDebugProcGetVgsMessageDisplayedValue},
		{11, "Set VgsMessageDisplayed Flag", "", SiDebugProcSetVgsMessageDisplayedValue},
		{12, "Get the current TCP send queue length", "", SiDebugProcGetCurrentTcpSendQueueLength},
		{13, "Get the current GetIP thread count", "", SiDebugProcGetCurrentGetIPThreadCount},
	};
	UINT num_proc_list = sizeof(proc_list) / sizeof(proc_list[0]);
	UINT j;
	UINT ret_value = ERR_NO_ERROR;
	// Validate arguments
	if (s == NULL || ret == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	if (i == 0)
	{
		char tmp[MAX_SIZE];
		Zero(ret, sizeof(RPC_TEST));

		StrCat(ret->StrValue, sizeof(ret->StrValue),
			"\n--- Debug Functions List --\n");

		for (j = 0;j < num_proc_list;j++)
		{
			SI_DEBUG_PROC_LIST *p = &proc_list[j];

			if (IsEmptyStr(p->Args) == false)
			{
				Format(tmp, sizeof(tmp),
					" %u: %s - Usage: %u /ARG:\"%s\"\n",
					p->Id, p->Description, p->Id, p->Args);
			}
			else
			{
				Format(tmp, sizeof(tmp),
					" %u: %s - Usage: %u\n",
					p->Id, p->Description, p->Id);
			}

			StrCat(ret->StrValue, sizeof(ret->StrValue), tmp);
		}
	}
	else
	{
		ret_value = ERR_NOT_SUPPORTED;

		for (j = 0;j < num_proc_list;j++)
		{
			SI_DEBUG_PROC_LIST *p = &proc_list[j];

			if (p->Id == i)
			{
				ret_value = p->Proc(s, str, ret->StrValue, sizeof(ret->StrValue));

				if (ret_value == ERR_NO_ERROR && IsEmptyStr(ret->StrValue))
				{
					StrCpy(ret->StrValue, sizeof(ret->StrValue), "Ok.");
				}
				break;
			}
		}
	}

	return ret_value;
}
UINT SiDebugProcHelloWorld(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	Format(ret_str, ret_str_size, "Hello World %s\n", in_str);

	return ERR_NO_ERROR;
}
UINT SiDebugProcExit(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	_exit(1);

	return ERR_NO_ERROR;
}
UINT SiDebugProcDump(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

#ifdef	OS_WIN32
	MsWriteMinidump(NULL, NULL);
#else	// OS_WIN32
	return ERR_NOT_SUPPORTED;
#endif	// OS_WIN32

	return ERR_NO_ERROR;
}
UINT SiDebugProcRestorePriority(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	OSRestorePriority();

	return ERR_NO_ERROR;
}
UINT SiDebugProcSetHighPriority(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	OSSetHighPriority();

	return ERR_NO_ERROR;
}
UINT SiDebugProcGetExeFileName(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	GetExeName(ret_str, ret_str_size);

	return ERR_NO_ERROR;
}
UINT SiDebugProcCrash(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	CrashNow();

	return ERR_NO_ERROR;
}
UINT SiDebugProcGetIPsecMessageDisplayedValue(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	ToStr(ret_str, s->IPsecMessageDisplayed);

	return ERR_NO_ERROR;
}
UINT SiDebugProcSetIPsecMessageDisplayedValue(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	s->IPsecMessageDisplayed = ToInt(in_str);

	return ERR_NO_ERROR;
}
UINT SiDebugProcGetVgsMessageDisplayedValue(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

#if	0
	if (VgDoNotPopupMessage() == false)
	{
		ToStr(ret_str, s->VgsMessageDisplayed);
	}
	else
	{
		ToStr(ret_str, 1);
	}
#else
	// Do not show the VGS message in VPN Server of the current version
	ToStr(ret_str, 1);
#endif	

	return ERR_NO_ERROR;
}
UINT SiDebugProcGetCurrentTcpSendQueueLength(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	char tmp1[64], tmp2[64], tmp3[64];
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	ToStr3(tmp1, 0, CedarGetCurrentTcpQueueSize(s->Cedar));
	ToStr3(tmp2, 0, CedarGetQueueBudgetConsuming(s->Cedar));
	ToStr3(tmp3, 0, CedarGetFifoBudgetConsuming(s->Cedar));

	Format(ret_str, 0, 
		"CurrentTcpQueueSize  = %s\n"
		"QueueBudgetConsuming = %s\n"
		"FifoBudgetConsuming  = %s\n",
		tmp1, tmp2, tmp3);

	return ERR_NO_ERROR;
}
UINT SiDebugProcGetCurrentGetIPThreadCount(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	char tmp1[64], tmp2[64];
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}

	ToStr3(tmp1, 0, DnsThreadNum());
	ToStr3(tmp2, 0, DnsThreadNumMax());

	Format(ret_str, 0, 
		"Current threads = %s\n"
		"Quota           = %s\n",
		tmp1, tmp2);

	return ERR_NO_ERROR;
}
UINT SiDebugProcSetVgsMessageDisplayedValue(SERVER *s, char *in_str, char *ret_str, UINT ret_str_size)
{
	// Validate arguments
	if (s == NULL || in_str == NULL || ret_str == NULL)
	{
		return ERR_INVALID_PARAMETER;
	}


	return ERR_NO_ERROR;
}

// Write the debug log
void SiDebugLog(SERVER *s, char *msg)
{
	// Validate arguments
	if (s == NULL || msg == NULL)
	{
		return;
	}

	if (s->DebugLog != NULL)
	{
		WriteTinyLog(s->DebugLog, msg);
	}
}

// Deadlock inspection main
void SiCheckDeadLockMain(SERVER *s, UINT timeout)
{
	CEDAR *cedar;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	//Debug("SiCheckDeadLockMain Start.\n");


	cedar = s->Cedar;

	if (s->ServerListenerList != NULL)
	{
		CheckDeadLock(s->ServerListenerList->lock, timeout, "s->ServerListenerList->lock");
	}

	CheckDeadLock(s->lock, timeout, "s->lock");

	if (s->FarmMemberList != NULL)
	{
		CheckDeadLock(s->FarmMemberList->lock, timeout, "s->FarmMemberList->lock");
	}

	if (s->HubCreateHistoryList != NULL)
	{
		CheckDeadLock(s->HubCreateHistoryList->lock, timeout, "s->HubCreateHistoryList->lock");
	}

	CheckDeadLock(s->CapsCacheLock, timeout, "s->CapsCacheLock");

	CheckDeadLock(s->TasksFromFarmControllerLock, timeout, "s->TasksFromFarmControllerLock");

	if (cedar != NULL)
	{
		if (cedar->HubList != NULL)
		{
			CheckDeadLock(cedar->HubList->lock, timeout, "cedar->HubList->lock");
		}

		if (cedar->ListenerList != NULL)
		{
			UINT i;
			LIST *o = NewListFast(NULL);

			CheckDeadLock(cedar->ListenerList->lock, timeout, "cedar->ListenerList->lock");

			LockList(cedar->ListenerList);
			{
				for (i = 0;i < LIST_NUM(cedar->ListenerList);i++)
				{
					LISTENER *r = LIST_DATA(cedar->ListenerList, i);

					AddRef(r->ref);

					Add(o, r);
				}
			}
			UnlockList(cedar->ListenerList);

			for (i = 0;i < LIST_NUM(o);i++)
			{
				LISTENER *r = LIST_DATA(o, i);


				ReleaseListener(r);
			}

			ReleaseList(o);
		}

		if (cedar->ConnectionList != NULL)
		{
			CheckDeadLock(cedar->ConnectionList->lock, timeout, "cedar->ConnectionList->lock");
		}

		if (cedar->CaList != NULL)
		{
			CheckDeadLock(cedar->CaList->lock, timeout, "cedar->CaList->lock");
		}

		if (cedar->WgkList != NULL)
		{
			CheckDeadLock(cedar->WgkList->lock, timeout, "cedar->WgkList->lock");
		}

		if (cedar->TrafficLock != NULL)
		{
			CheckDeadLock(cedar->TrafficLock, timeout, "cedar->TrafficLock");
		}

		if (cedar->TrafficDiffList != NULL)
		{
			CheckDeadLock(cedar->TrafficDiffList->lock, timeout, "cedar->TrafficDiffList->lock");
		}

		if (cedar->LocalBridgeList != NULL)
		{
			CheckDeadLock(cedar->LocalBridgeList->lock, timeout, "cedar->LocalBridgeList->lock");
		}

		if (cedar->L3SwList != NULL)
		{
			CheckDeadLock(cedar->L3SwList->lock, timeout, "cedar->L3SwList->lock");
		}
	}

	//Debug("SiCheckDeadLockMain Finish.\n");
}

// Deadlock check thread
void SiDeadLockCheckThread(THREAD *t, void *param)
{
	SERVER *s = (SERVER *)param;
	// Validate arguments
	if (s == NULL || t == NULL)
	{
		return;
	}

	while (true)
	{
		Wait(s->DeadLockWaitEvent, SERVER_DEADLOCK_CHECK_SPAN);

		if (s->HaltDeadLockThread)
		{
			break;
		}

		SiCheckDeadLockMain(s, SERVER_DEADLOCK_CHECK_TIMEOUT);
	}
}

// Initialize the deadlock check
void SiInitDeadLockCheck(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}
	if (s->DisableDeadLockCheck)
	{
		return;
	}

	s->HaltDeadLockThread = false;
	s->DeadLockWaitEvent = NewEvent();
	s->DeadLockCheckThread = NewThread(SiDeadLockCheckThread, s);
}

// Release the deadlock check
void SiFreeDeadLockCheck(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	if (s->DeadLockCheckThread == NULL)
	{
		return;
	}

	s->HaltDeadLockThread = true;
	Set(s->DeadLockWaitEvent);

	WaitThread(s->DeadLockCheckThread, INFINITE);

	ReleaseThread(s->DeadLockCheckThread);
	s->DeadLockCheckThread = NULL;

	ReleaseEvent(s->DeadLockWaitEvent);
	s->DeadLockWaitEvent = NULL;

	s->HaltDeadLockThread = false;
}

// Check whether the specified virtual HUB has been registered to creation history
bool SiIsHubRegistedOnCreateHistory(SERVER *s, char *name)
{
	UINT i;
	bool ret = false;
	// Validate arguments
	if (s == NULL || name == NULL)
	{
		return false;
	}

	SiDeleteOldHubCreateHistory(s);

	LockList(s->HubCreateHistoryList);
	{
		for (i = 0;i < LIST_NUM(s->HubCreateHistoryList);i++)
		{
			SERVER_HUB_CREATE_HISTORY *h = LIST_DATA(s->HubCreateHistoryList, i);

			if (StrCmpi(h->HubName, name) == 0)
			{
				ret = true;
				break;
			}
		}
	}
	UnlockList(s->HubCreateHistoryList);

	return ret;
}

// Delete the Virtual HUB creation history
void SiDelHubCreateHistory(SERVER *s, char *name)
{
	UINT i;
	// Validate arguments
	if (s == NULL || name == NULL)
	{
		return;
	}

	LockList(s->HubCreateHistoryList);
	{
		SERVER_HUB_CREATE_HISTORY *hh = NULL;
		for (i = 0;i < LIST_NUM(s->HubCreateHistoryList);i++)
		{
			SERVER_HUB_CREATE_HISTORY *h = LIST_DATA(s->HubCreateHistoryList, i);

			if (StrCmpi(h->HubName, name) == 0)
			{
				Delete(s->HubCreateHistoryList, h);
				Free(h);
				break;
			}
		}
	}
	UnlockList(s->HubCreateHistoryList);

	SiDeleteOldHubCreateHistory(s);
}

// Register to the Virtual HUB creation history
void SiAddHubCreateHistory(SERVER *s, char *name)
{
	UINT i;
	// Validate arguments
	if (s == NULL || name == NULL)
	{
		return;
	}

	LockList(s->HubCreateHistoryList);
	{
		SERVER_HUB_CREATE_HISTORY *hh = NULL;
		for (i = 0;i < LIST_NUM(s->HubCreateHistoryList);i++)
		{
			SERVER_HUB_CREATE_HISTORY *h = LIST_DATA(s->HubCreateHistoryList, i);

			if (StrCmpi(h->HubName, name) == 0)
			{
				hh = h;
				break;
			}
		}

		if (hh == NULL)
		{
			hh = ZeroMalloc(sizeof(SERVER_HUB_CREATE_HISTORY));
			StrCpy(hh->HubName, sizeof(hh->HubName), name);

			Add(s->HubCreateHistoryList, hh);
		}

		hh->CreatedTime = Tick64();
	}
	UnlockList(s->HubCreateHistoryList);

	SiDeleteOldHubCreateHistory(s);
}

// Delete outdated Virtual HUB creation histories
void SiDeleteOldHubCreateHistory(SERVER *s)
{
	UINT i;
	LIST *o;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	LockList(s->HubCreateHistoryList);
	{
		o = NewListFast(NULL);

		for (i = 0;i < LIST_NUM(s->HubCreateHistoryList);i++)
		{
			SERVER_HUB_CREATE_HISTORY *h = LIST_DATA(s->HubCreateHistoryList, i);

			if ((h->CreatedTime + ((UINT64)TICKET_EXPIRES)) <= Tick64())
			{
				// Expired
				Add(o, h);
			}
		}

		for (i = 0;i < LIST_NUM(o);i++)
		{
			SERVER_HUB_CREATE_HISTORY *h = LIST_DATA(o, i);

			Delete(s->HubCreateHistoryList, h);

			Free(h);
		}

		ReleaseList(o);
	}
	UnlockList(s->HubCreateHistoryList);
}

// Initialize the Virtual HUB creation history
void SiInitHubCreateHistory(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	s->HubCreateHistoryList = NewList(NULL);
}

// Release the Virtual HUB creation history
void SiFreeHubCreateHistory(SERVER *s)
{
	UINT i;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	for (i = 0;i < LIST_NUM(s->HubCreateHistoryList);i++)
	{
		SERVER_HUB_CREATE_HISTORY *h = LIST_DATA(s->HubCreateHistoryList, i);

		Free(h);
	}

	ReleaseList(s->HubCreateHistoryList);

	s->HubCreateHistoryList = NULL;
}

// Identify whether the server can be connected from the VPN Client that is
// created by the installer creating kit of Admin Pack
bool IsAdminPackSupportedServerProduct(char *name)
{
	return true;
}


// Get the saving status of syslog
UINT SiGetSysLogSaveStatus(SERVER *s)
{
	SYSLOG_SETTING set;
	// Validate arguments
	if (s == NULL)
	{
		return SYSLOG_NONE;
	}

	SiGetSysLogSetting(s, &set);

	return set.SaveType;
}

// Send a syslog
void SiWriteSysLog(SERVER *s, char *typestr, char *hubname, wchar_t *message)
{
	wchar_t tmp[1024];
	char machinename[MAX_HOST_NAME_LEN + 1];
	char datetime[MAX_PATH];
	SYSTEMTIME st;
	// Validate arguments
	if (s == NULL || typestr == NULL || message == NULL)
	{
		return;
	}

	if (GetGlobalServerFlag(GSF_DISABLE_SYSLOG) != 0)
	{
		return;
	}

	// Host name
	GetMachineName(machinename, sizeof(machinename));

	// Date and time
	LocalTime(&st);
	if(s->StrictSyslogDatetimeFormat){
		GetDateTimeStrRFC3339(datetime, sizeof(datetime), &st, GetCurrentTimezone());
	}else{
		GetDateTimeStrMilli(datetime, sizeof(datetime), &st);
	}

	if (IsEmptyStr(hubname) == false)
	{
		UniFormat(tmp, sizeof(tmp), L"[%S/VPN/%S] (%S) <%S>: %s",
			machinename, hubname, datetime, typestr, message);
	}
	else
	{
		UniFormat(tmp, sizeof(tmp), L"[%S/VPN] (%S) <%S>: %s",
			machinename, datetime, typestr, message);
	}

	Debug("Syslog send: %S\n",tmp);

	SendSysLog(s->Syslog, tmp);
}

// Write the syslog configuration
void SiSetSysLogSetting(SERVER *s, SYSLOG_SETTING *setting)
{
	SYSLOG_SETTING set;
	// Validate arguments
	if (s == NULL || setting == NULL)
	{
		return;
	}

	Zero(&set, sizeof(set));
	Copy(&set, setting, sizeof(SYSLOG_SETTING));

	if (IsEmptyStr(set.Hostname) || set.Port == 0)
	{
		set.SaveType = SYSLOG_NONE;
	}

	Lock(s->SyslogLock);
	{
		Copy(&s->SyslogSetting, &set, sizeof(SYSLOG_SETTING));

		SetSysLog(s->Syslog, set.Hostname, set.Port);
	}
	Unlock(s->SyslogLock);
}

// Read the syslog configuration
void SiGetSysLogSetting(SERVER *s, SYSLOG_SETTING *setting)
{
	// Validate arguments
	if (s == NULL || setting == NULL)
	{
		return;
	}

	//Lock(s->SyslogLock);
	{
		Copy(setting, &s->SyslogSetting, sizeof(SYSLOG_SETTING));
	}
	//Unlock(s->SyslogLock);
}


// Get the server product name
void GetServerProductName(SERVER *s, char *name, UINT size)
{
	char *cpu;
	// Validate arguments
	if (s == NULL || name == NULL)
	{
		return;
	}

	GetServerProductNameInternal(s, name, size);

#ifdef	CPU_64
	cpu = " (64 bit)";
#else	// CPU_64
	cpu = " (32 bit)";
#endif	// CPU_64

	StrCat(name, size, cpu);

	StrCat(name, size, " (Open Source)");
}
void GetServerProductNameInternal(SERVER *s, char *name, UINT size)
{
	// Validate arguments
	if (s == NULL || name == NULL)
	{
		return;
	}

#ifdef	BETA_NUMBER
	if (s->Cedar->Bridge)
	{
		StrCpy(name, size, CEDAR_BRIDGE_STR);
	}
	else
	{
		StrCpy(name, size, CEDAR_BETA_SERVER);
	}
	return;
#else	// BETA_NUMBER
	if (s->Cedar->Bridge)
	{
		StrCpy(name, size, CEDAR_BRIDGE_STR);
	}
	else
	{
		StrCpy(name, size, CEDAR_SERVER_STR);
	}
#endif	// BETA_NUMBER
}

// Check whether the log file with the specified name is contained in the enumerated list
bool CheckLogFileNameFromEnumList(LIST *o, char *name, char *server_name)
{
	LOG_FILE t;
	// Validate arguments
	if (o == NULL || name == NULL || server_name == NULL)
	{
		return false;
	}

	Zero(&t, sizeof(t));
	StrCpy(t.Path, sizeof(t.Path), name);
	StrCpy(t.ServerName, sizeof(t.ServerName), server_name);

	if (Search(o, &t) == NULL)
	{
		return false;
	}

	return true;
}

// Release the log file enumeration
void FreeEnumLogFile(LIST *o)
{
	UINT i;
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	for (i = 0;i < LIST_NUM(o);i++)
	{
		LOG_FILE *f = LIST_DATA(o, i);

		Free(f);
	}

	ReleaseList(o);
}

// Enumerate the log files associated with the virtual HUB (All logs are listed in the case of server administrator)
LIST *EnumLogFile(char *hubname)
{
	char exe_dir[MAX_PATH];
	char tmp[MAX_PATH];
	LIST *o = NewListFast(CmpLogFile);
	DIRLIST *dir;

	if (StrLen(hubname) == 0)
	{
		hubname = NULL;
	}

	GetLogDir(exe_dir, sizeof(exe_dir));

	// Enumerate in the server_log
	if (hubname == NULL)
	{
		EnumLogFileDir(o, SERVER_LOG_DIR);
	}

	// Enumerate in the packet_log
	Format(tmp, sizeof(tmp), "%s/"HUB_PACKET_LOG_DIR, exe_dir);

	if (hubname == NULL)
	{
		dir = EnumDir(tmp);
		if (dir != NULL)
		{
			UINT i;
			for (i = 0;i < dir->NumFiles;i++)
			{
				DIRENT *e = dir->File[i];

				if (e->Folder)
				{
					char dir_name[MAX_PATH];
					Format(dir_name, sizeof(dir_name), HUB_PACKET_LOG_DIR"/%s", e->FileName);
					EnumLogFileDir(o, dir_name);
				}
			}

			FreeDir(dir);
		}
	}
	else
	{
		char dir_name[MAX_PATH];

		Format(dir_name, sizeof(dir_name), HUB_PACKET_LOG_DIR"/%s", hubname);

		EnumLogFileDir(o, dir_name);
	}

	// Enumerate in the security_log
	Format(tmp, sizeof(tmp), "%s/"HUB_SECURITY_LOG_DIR, exe_dir);

	if (hubname == NULL)
	{
		dir = EnumDir(tmp);
		if (dir != NULL)
		{
			UINT i;
			for (i = 0;i < dir->NumFiles;i++)
			{
				DIRENT *e = dir->File[i];

				if (e->Folder)
				{
					char dir_name[MAX_PATH];

					Format(dir_name, sizeof(dir_name), HUB_SECURITY_LOG_DIR"/%s", e->FileName);

					EnumLogFileDir(o, dir_name);
				}
			}

			FreeDir(dir);
		}
	}
	else
	{
		char dir_name[MAX_PATH];

		Format(dir_name, sizeof(dir_name), HUB_SECURITY_LOG_DIR"/%s", hubname);

		EnumLogFileDir(o, dir_name);
	}

	return o;
}

// Enumerate log files in the specified directory
void EnumLogFileDir(LIST *o, char *dirname)
{
	UINT i;
	char exe_dir[MAX_PATH];
	char dir_full_path[MAX_PATH];
	DIRLIST *dir;
	// Validate arguments
	if (o == NULL || dirname == NULL)
	{
		return;
	}

	GetLogDir(exe_dir, sizeof(exe_dir));
	Format(dir_full_path, sizeof(dir_full_path), "%s/%s", exe_dir, dirname);

	dir = EnumDir(dir_full_path);
	if (dir == NULL)
	{
		return;
	}

	for (i = 0;i < dir->NumFiles;i++)
	{
		DIRENT *e = dir->File[i];

		if (e->Folder == false && e->FileSize > 0)
		{
			char full_path[MAX_PATH];
			char file_path[MAX_PATH];

			Format(file_path, sizeof(file_path), "%s/%s", dirname, e->FileName);
			Format(full_path, sizeof(full_path), "%s/%s", exe_dir, file_path);

			if (EndWith(file_path, ".log"))
			{
				LOG_FILE *f = ZeroMalloc(sizeof(LOG_FILE));

				StrCpy(f->Path, sizeof(f->Path), file_path);
				f->FileSize = (UINT)(MIN(e->FileSize, 0xffffffffUL));
				f->UpdatedTime = e->UpdateDate;

				GetMachineName(f->ServerName, sizeof(f->ServerName));

				Insert(o, f);
			}
		}
	}

	FreeDir(dir);
}

// Log file list entry comparison
int CmpLogFile(void *p1, void *p2)
{
	LOG_FILE *f1, *f2;
	UINT i;
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	f1 = *(LOG_FILE **)p1;
	f2 = *(LOG_FILE **)p2;
	if (f1 == NULL || f2 == NULL)
	{
		return 0;
	}

	i = StrCmpi(f1->Path, f2->Path);
	if (i != 0)
	{
		return i;
	}

	return StrCmpi(f1->ServerName, f2->ServerName);
}

// Get the Caps of the server
UINT GetServerCapsInt(SERVER *s, char *name)
{
	CAPSLIST t;
	UINT ret;
	// Validate arguments
	if (s == NULL || name == NULL)
	{
		return 0;
	}


	Lock(s->CapsCacheLock);
	{
		Zero(&t, sizeof(t));
		GetServerCaps(s, &t);

		ret = GetCapsInt(&t, name);
	}
	Unlock(s->CapsCacheLock);

	return ret;
}
bool GetServerCapsBool(SERVER *s, char *name)
{
	return (GetServerCapsInt(s, name) == 0) ? false : true;
}

// Initialize the Caps cache of the server
void InitServerCapsCache(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	s->CapsCacheLock = NewLock();
	s->CapsListCache = NULL;
}

// Release the Caps cache of the server
void FreeServerCapsCache(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	if (s->CapsListCache != NULL)
	{
		FreeCapsList(s->CapsListCache);
		s->CapsListCache = NULL;
	}
	DeleteLock(s->CapsCacheLock);
}

// Dispose the Caps cache of the server
void DestroyServerCapsCache(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	Lock(s->CapsCacheLock);
	{
		if (s->CapsListCache != NULL)
		{
			FreeCapsList(s->CapsListCache);
			s->CapsListCache = NULL;
		}
	}
	Unlock(s->CapsCacheLock);
}

// Flush the Caps list for this server
void FlushServerCaps(SERVER *s)
{
	CAPSLIST t;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	Lock(s->CapsCacheLock);
	{
		DestroyServerCapsCache(s);

		Zero(&t, sizeof(t));
		GetServerCaps(s, &t);
	}
	Unlock(s->CapsCacheLock);
}

// Get the Caps list for this server
void GetServerCaps(SERVER *s, CAPSLIST *t)
{
	// Validate arguments
	if (s == NULL || t == NULL)
	{
		return;
	}

	Lock(s->CapsCacheLock);
	{

		if (s->CapsListCache == NULL)
		{
			s->CapsListCache = ZeroMalloc(sizeof(CAPSLIST));
			GetServerCapsMain(s, s->CapsListCache);
		}

		Copy(t, s->CapsListCache, sizeof(CAPSLIST));
	}
	Unlock(s->CapsCacheLock);
}

// Update the global server flags
void UpdateGlobalServerFlags(SERVER *s, CAPSLIST *t)
{
	bool is_restricted = false;
	// Validate arguments
	if (s == NULL || t == NULL)
	{
		return;
	}

	is_restricted = SiIsEnterpriseFunctionsRestrictedOnOpenSource(s->Cedar);

	SetGlobalServerFlag(GSF_DISABLE_PUSH_ROUTE, is_restricted);
	SetGlobalServerFlag(GSF_DISABLE_RADIUS_AUTH, is_restricted);
	SetGlobalServerFlag(GSF_DISABLE_CERT_AUTH, is_restricted);
	SetGlobalServerFlag(GSF_DISABLE_DEEP_LOGGING, is_restricted);
	SetGlobalServerFlag(GSF_DISABLE_AC, is_restricted);
	SetGlobalServerFlag(GSF_DISABLE_SYSLOG, is_restricted);
}

// Set a global server flag
void SetGlobalServerFlag(UINT index, UINT value)
{
	// Validate arguments
	if (index >= NUM_GLOBAL_SERVER_FLAGS)
	{
		return;
	}

	global_server_flags[index] = value;
}

// Get a global server flag
UINT GetGlobalServerFlag(UINT index)
{
	// Validate arguments
	if (index >= NUM_GLOBAL_SERVER_FLAGS)
	{
		return 0;
	}

	return global_server_flags[index];
}

// Main of the acquisition of Caps of the server
void GetServerCapsMain(SERVER *s, CAPSLIST *t)
{
	bool is_restricted = false;

	// Validate arguments
	if (s == NULL || t == NULL)
	{
		return;
	}

	is_restricted = SiIsEnterpriseFunctionsRestrictedOnOpenSource(s->Cedar);

	// Initialize
	InitCapsList(t);

	// Maximum Ethernet packet size
	AddCapsInt(t, "i_max_packet_size", MAX_PACKET_SIZE);

	if (s->Cedar->Bridge == false)
	{
		UINT max_sessions, max_clients, max_bridges, max_user_creations;

		max_clients = INFINITE;
		max_bridges = INFINITE;
		max_sessions = SERVER_MAX_SESSIONS_FOR_CARRIER_EDITION;
		max_user_creations = INFINITE;

		// Maximum number of virtual HUBs
		AddCapsInt(t, "i_max_hubs", SERVER_MAX_SESSIONS_FOR_CARRIER_EDITION);

		// The maximum number of concurrent sessions
		AddCapsInt(t, "i_max_sessions", max_sessions);

		// Maximum number of creatable users
		AddCapsInt(t, "i_max_user_creation", max_user_creations);

		// Maximum number of clients
		AddCapsInt(t, "i_max_clients", max_clients);

		// Maximum number of bridges
		AddCapsInt(t, "i_max_bridges", max_bridges);

		if (s->ServerType != SERVER_TYPE_FARM_MEMBER)
		{
			// Maximum number of registrable users / Virtual HUB
			AddCapsInt(t, "i_max_users_per_hub", MAX_USERS);

			// Maximum number of registrable groups / Virtual HUB
			AddCapsInt(t, "i_max_groups_per_hub", MAX_GROUPS);

			// Maximum number of registrable access list entries / Virtual HUB
			AddCapsInt(t, "i_max_access_lists", MAX_ACCESSLISTS);
		}
		else
		{
			// Maximum number of registrable users / Virtual HUB
			AddCapsInt(t, "i_max_users_per_hub", 0);

			// Maximum number of registrable groups / Virtual HUB
			AddCapsInt(t, "i_max_groups_per_hub", 0);

			// Maximum number of registrable access list entries / Virtual HUB
			AddCapsInt(t, "i_max_access_lists", 0);
		}

		// The policy related to multiple logins
		AddCapsBool(t, "b_support_limit_multilogin", true);

		// QoS / VoIP
		AddCapsBool(t, "b_support_qos", true);

		// syslog
		AddCapsBool(t, "b_support_syslog", true);

		// IPsec
		// (Only works in stand-alone mode currently)
		AddCapsBool(t, "b_support_ipsec", (s->ServerType == SERVER_TYPE_STANDALONE));

		// SSTP
		// (Only works in stand-alone mode currently)
		AddCapsBool(t, "b_support_sstp", (s->ServerType == SERVER_TYPE_STANDALONE));

		// OpenVPN
		// (Only works in stand-alone mode currently)
		AddCapsBool(t, "b_support_openvpn", (s->ServerType == SERVER_TYPE_STANDALONE));

		// DDNS
		AddCapsBool(t, "b_support_ddns", (s->DDnsClient != NULL));

		if (s->DDnsClient != NULL)
		{
			// DDNS via Proxy
			AddCapsBool(t, "b_support_ddns_proxy", true);
		}

		// VPN over ICMP, VPN over DNS
		AddCapsBool(t, "b_support_special_listener", true);
	}
	else
	{
		// Maximum number of virtual HUBs
		AddCapsInt(t, "i_max_hubs", 0);

		// The maximum number of concurrent sessions
		AddCapsInt(t, "i_max_sessions", 0);

		// Maximum number of clients
		AddCapsInt(t, "i_max_clients", 0);

		// Maximum number of bridges
		AddCapsInt(t, "i_max_bridges", 0);

		// Maximum number of registrable users / Virtual HUB
		AddCapsInt(t, "i_max_users_per_hub", 0);

		// Maximum number of registrable groups / Virtual HUB
		AddCapsInt(t, "i_max_groups_per_hub", 0);

		// Maximum number of registrable access list entries / Virtual HUB
		AddCapsInt(t, "i_max_access_lists", 0);

		// QoS / VoIP
		AddCapsBool(t, "b_support_qos", true);

		// syslog
		AddCapsBool(t, "b_support_syslog", true);

		// IPsec
		AddCapsBool(t, "b_support_ipsec", false);

		// SSTP
		AddCapsBool(t, "b_support_sstp", false);

		// OpenVPN
		AddCapsBool(t, "b_support_openvpn", false);

		// DDNS
		AddCapsBool(t, "b_support_ddns", false);

		// VPN over ICMP, VPN over DNS
		AddCapsBool(t, "b_support_special_listener", false);
	}

	// Changing the type of Virtual HUB in cluster is prohibited
	AddCapsBool(t, "b_cluster_hub_type_fixed", true);

	// Maximum MAC address table  size / Virtual HUB
	AddCapsInt(t, "i_max_mac_tables", MAX_MAC_TABLES);

	// Maximum IP address table  size / Virtual HUB
	AddCapsInt(t, "i_max_ip_tables", MAX_IP_TABLES);

	// SecureNAT function is available
	AddCapsBool(t, "b_support_securenat", true);

	// Pushing routing table function of SecureNAT Virtual DHCP Server is available
	AddCapsBool(t, "b_suppport_push_route", !is_restricted);
	AddCapsBool(t, "b_suppport_push_route_config", true);

	if (s->ServerType != SERVER_TYPE_STANDALONE)
	{
		AddCapsBool(t, "b_virtual_nat_disabled", true);
	}

	// Maximum NAT table size / Virtual HUB
	AddCapsInt(t, "i_max_secnat_tables", NAT_MAX_SESSIONS);

	// Cascade connection
	if (s->ServerType == SERVER_TYPE_STANDALONE)
	{
		AddCapsBool(t, "b_support_cascade", true);
	}
	else
	{
		AddCapsBool(t, "b_support_cascade", false);
	}

	if (s->Cedar->Bridge)
	{
		// Bridge mode
		AddCapsBool(t, "b_bridge", true);
	}
	else if (s->ServerType == SERVER_TYPE_STANDALONE)
	{
		// Stand-alone mode
		AddCapsBool(t, "b_standalone", true);
	}
	else if (s->ServerType == SERVER_TYPE_FARM_CONTROLLER)
	{
		// Cluster controller mode
		AddCapsBool(t, "b_cluster_controller", true);
	}
	else
	{
		// Cluster member mode
		AddCapsBool(t, "b_cluster_member", true);
	}

	// Virtual HUB is modifiable
	AddCapsBool(t, "b_support_config_hub", s->ServerType != SERVER_TYPE_FARM_MEMBER &&
		s->Cedar->Bridge == false);

	// VPN client can be connected
	AddCapsBool(t, "b_vpn_client_connect", s->Cedar->Bridge == false ? true : false);

	// External authentication server is available
	AddCapsBool(t, "b_support_radius", s->ServerType != SERVER_TYPE_FARM_MEMBER &&
		s->Cedar->Bridge == false);

	// Local-bridge function is available
	AddCapsBool(t, "b_local_bridge", IsBridgeSupported());

	if (OS_IS_WINDOWS(GetOsInfo()->OsType))
	{
		// Packet capture driver is not installed
		AddCapsBool(t, "b_must_install_pcap", IsEthSupported() == false ? true : false);
	}
	else
	{
		// Regard that the driver is installed in the Linux version
		AddCapsBool(t, "b_must_install_pcap", false);
	}

	if (IsBridgeSupported())
	{
		// TUN / TAP device availability (Linux and BSD)
		const UINT OsType = GetOsInfo()->OsType;
		AddCapsBool(t, "b_tap_supported", OsType == OSTYPE_LINUX || OsType == OSTYPE_BSD);
	}

	// Cascade connection
	if (s->ServerType == SERVER_TYPE_STANDALONE)
	{
		AddCapsBool(t, "b_support_cascade", true);
	}
	else
	{
		AddCapsBool(t, "b_support_cascade", false);
	}

	// Server authentication can be used in cascade connection
	AddCapsBool(t, "b_support_cascade_cert", true);

	//  the log file settings is modifiable
	AddCapsBool(t, "b_support_config_log", s->ServerType != SERVER_TYPE_FARM_MEMBER);

	// Automatic deletion of log file is available
	AddCapsBool(t, "b_support_autodelete", true);

	// Config file operation is available
	AddCapsBool(t, "b_support_config_rw", true);

	// Attribute of each Virtual HUB can be set
	AddCapsBool(t, "b_support_hub_admin_option", true);

	// Client certificate can be set in a cascade connection
	AddCapsBool(t, "b_support_cascade_client_cert", true);

	// Virtual HUB can be hidden
	AddCapsBool(t, "b_support_hide_hub", true);

	// Integrated management
	AddCapsBool(t, "b_support_cluster_admin", true);

	// Flag of open-source version
	AddCapsBool(t, "b_is_softether", true);

	if (s->Cedar->Bridge == false)
	{

		// The virtual layer 3 switch function is available
		AddCapsBool(t, "b_support_layer3", true);

		AddCapsInt(t, "i_max_l3_sw", MAX_NUM_L3_SWITCH);
		AddCapsInt(t, "i_max_l3_if", MAX_NUM_L3_IF);
		AddCapsInt(t, "i_max_l3_table", MAX_NUM_L3_TABLE);

		// Can act as a part of a cluster
		AddCapsBool(t, "b_support_cluster", true);
	}
	else
	{
		AddCapsBool(t, "b_support_layer3", false);

		AddCapsInt(t, "i_max_l3_sw", 0);
		AddCapsInt(t, "i_max_l3_if", 0);
		AddCapsInt(t, "i_max_l3_table", 0);

		AddCapsBool(t, "b_support_cluster", false);
	}

	if (s->ServerType != SERVER_TYPE_FARM_MEMBER && s->Cedar->Bridge == false)
	{
		// Support for CRL
		AddCapsBool(t, "b_support_crl", true);

		// Supports AC
		AddCapsBool(t, "b_support_ac", true);
	}

	// Supports downloading a log file
	AddCapsBool(t, "b_support_read_log", true);

	// Cascade connection can be renamed
	AddCapsBool(t, "b_support_rename_cascade", true);


	if (s->Cedar->Beta)
	{
		// Beta version
		AddCapsBool(t, "b_beta_version", true);
	}

	// VM discrimination
	AddCapsBool(t, "b_is_in_vm", s->IsInVm);

	// Support for display name of the network connection for the local bridge
#ifdef	OS_WIN32
	if (IsBridgeSupported() && IsNt() && GetOsInfo()->OsType >= OSTYPE_WINDOWS_2000_PROFESSIONAL)
	{
		AddCapsBool(t, "b_support_network_connection_name", true);
	}
#else	// OS_WIN32
	if (IsBridgeSupported() && EthIsInterfaceDescriptionSupportedUnix())
	{
		AddCapsBool(t, "b_support_network_connection_name", true);
	}
#endif	// OS_WIN32

	// Support for MAC address filtering
	AddCapsBool(t, "b_support_check_mac", true);

	// Support for status check of the TCP connection
	AddCapsBool(t, "b_support_check_tcp_state", true);

	// Can specify multiple server and retry intervals in Radius authentication
	AddCapsBool(t, "b_support_radius_retry_interval_and_several_servers", s->ServerType != SERVER_TYPE_FARM_MEMBER &&
		s->Cedar->Bridge == false);

	// Can manage the ID of the tagged VLAN in the MAC address table
	AddCapsBool(t, "b_support_vlan", true);

	// Support for Virtual HUB extended options
	if ((s->Cedar->Bridge == false) &&
		(s->ServerType == SERVER_TYPE_STANDALONE || s->ServerType == SERVER_TYPE_FARM_CONTROLLER))
	{
		AddCapsBool(t, "b_support_hub_ext_options", true);
	}
	else
	{
		AddCapsBool(t, "b_support_hub_ext_options", false);
	}

	// Support for Security Policy version 3.0
	AddCapsBool(t, "b_support_policy_ver_3", true);

	// Support for IPv6 access list
	AddCapsBool(t, "b_support_ipv6_acl", true);

	// Support for setting of delay, jitter and packet loss in the access list
	AddCapsBool(t, "b_support_ex_acl", true);

	// Support for URL redirection in the access list
	AddCapsBool(t, "b_support_redirect_url_acl", true);

	// Supports the specification by the group name in the access list
	AddCapsBool(t, "b_support_acl_group", true);

	// Support for IPv6 in connection source IP restriction list
	AddCapsBool(t, "b_support_ipv6_ac", true);

	// Support for VLAN tagged packet transmission configuration tool
	AddCapsBool(t, "b_support_eth_vlan", true);

	// Support for the message display function when the VPN connect to the Virtual HUB
	AddCapsBool(t, "b_support_msg", true);

	// UDP acceleration feature
	AddCapsBool(t, "b_support_udp_acceleration", true);

	// AES acceleration function
	AddCapsBool(t, "b_support_aes_ni", IsAesNiSupported());

#ifdef	OS_WIN32
	// SeLow driver
	AddCapsBool(t, "b_using_selow_driver", Win32IsUsingSeLow());
#endif	// OS_WIN32

	// VPN Azure function
	AddCapsBool(t, "b_support_azure", SiIsAzureSupported(s));

	// VPN3
	AddCapsBool(t, "b_vpn3", true);

	// VPN4
	AddCapsBool(t, "b_vpn4", true);


	UpdateGlobalServerFlags(s, t);
}

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

	Zero(t, sizeof(SYSLOG_SETTING));
	t->SaveType = PackGetInt(p, "SaveType");
	t->Port = PackGetInt(p, "Port");
	PackGetStr(p, "Hostname", t->Hostname, sizeof(t->Hostname));
}
void OutRpcSysLogSetting(PACK *p, SYSLOG_SETTING *t)
{
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	PackAddInt(p, "SaveType", t->SaveType);
	PackAddInt(p, "Port", t->Port);
	PackAddStr(p, "Hostname", t->Hostname);
}

// CAPSLIST
void InitCapsList(CAPSLIST *t)
{
	// Validate arguments
	if (t == NULL)
	{
		return;
	}

	Zero(t, sizeof(CAPSLIST));
	t->CapsList = NewListFast(NULL);
}
void InRpcCapsList(CAPSLIST *t, PACK *p)
{
	UINT i;
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	Zero(t, sizeof(CAPSLIST));
	t->CapsList = NewListFast(CompareCaps);

	for (i = 0;i < LIST_NUM(p->elements);i++)
	{
		ELEMENT *e = LIST_DATA(p->elements, i);

		if (StartWith(e->name, "caps_") && e->type == VALUE_INT && e->num_value == 1)
		{
			CAPS *c = NewCaps(e->name + 5, e->values[0]->IntValue);
			Insert(t->CapsList, c);
		}
	}
}
void OutRpcCapsList(PACK *p, CAPSLIST *t)
{
	UINT i;
	// Validate arguments
	if (t == NULL || p == NULL)
	{
		return;
	}

	PackSetCurrentJsonGroupName(p, "CapsList");
	for (i = 0;i < LIST_NUM(t->CapsList);i++)
	{
		char tmp[MAX_SIZE];
		char ct_key[MAX_PATH];
		wchar_t ct_description[MAX_PATH];
		wchar_t *w;
		CAPS *c = LIST_DATA(t->CapsList, i);

		Format(tmp, sizeof(tmp), "caps_%s", c->Name);

		Format(ct_key, sizeof(ct_key), "CT_%s", c->Name);

		Zero(ct_description, sizeof(ct_description));
		w = _UU(ct_key);
		if (UniIsEmptyStr(w) == false)
		{
			UniStrCpy(ct_description, sizeof(ct_description), w);
		}
		else
		{
			StrToUni(ct_description, sizeof(ct_description), c->Name);
		}

		PackAddInt(p, tmp, c->Value);

		PackAddStrEx(p, "CapsName", c->Name, i, LIST_NUM(t->CapsList));
		PackAddIntEx(p, "CapsValue", c->Value, i, LIST_NUM(t->CapsList));
		PackAddUniStrEx(p, "CapsDescrption", ct_description, i, LIST_NUM(t->CapsList));
	}
	PackSetCurrentJsonGroupName(p, NULL);
}
void FreeRpcCapsList(CAPSLIST *t)
{
	UINT i;
	// Validate arguments
	if (t == NULL)
	{
		return;
	}

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

		FreeCaps(c);
	}

	ReleaseList(t->CapsList);
}

// Add a bool type to Caps list
void AddCapsBool(CAPSLIST *caps, char *name, bool b)
{
	CAPS *c;
	// Validate arguments
	if (caps == NULL || name == NULL)
	{
		return;
	}

	c = NewCaps(name, b == false ? 0 : 1);
	AddCaps(caps, c);
}

// Add the int type to Caps list
void AddCapsInt(CAPSLIST *caps, char *name, UINT i)
{
	CAPS *c;
	// Validate arguments
	if (caps == NULL || name == NULL)
	{
		return;
	}

	c = NewCaps(name, i);
	AddCaps(caps, c);
}

// Get the int type from the Caps list
UINT GetCapsInt(CAPSLIST *caps, char *name)
{
	CAPS *c;
	// Validate arguments
	if (caps == NULL || name == NULL)
	{
		return 0;
	}

	c = GetCaps(caps, name);
	if (c == NULL)
	{
		return 0;
	}

	return c->Value;
}

// Get bool type from the Caps list
bool GetCapsBool(CAPSLIST *caps, char *name)
{
	CAPS *c;
	// Validate arguments
	if (caps == NULL || name == NULL)
	{
		return false;
	}

	c = GetCaps(caps, name);
	if (c == NULL)
	{
		return false;
	}

	return c->Value == 0 ? false : true;
}

// Release the Caps list
void FreeCapsList(CAPSLIST *caps)
{
	UINT i;
	// Validate arguments
	if (caps == NULL)
	{
		return;
	}

	for (i = 0;i < LIST_NUM(caps->CapsList);i++)
	{
		CAPS *c = LIST_DATA(caps->CapsList, i);

		FreeCaps(c);
	}

	ReleaseList(caps->CapsList);
	Free(caps);
}

// Get the Caps
CAPS *GetCaps(CAPSLIST *caps, char *name)
{
	UINT i;
	// Validate arguments
	if (caps == NULL || name == NULL)
	{
		return NULL;
	}

	for (i = 0;i < LIST_NUM(caps->CapsList);i++)
	{
		CAPS *c = LIST_DATA(caps->CapsList, i);

		if (StrCmpi(c->Name, name) == 0)
		{
			return c;
		}
	}

	return NULL;
}

// Add to the Caps
void AddCaps(CAPSLIST *caps, CAPS *c)
{
	// Validate arguments
	if (caps == NULL || c == NULL)
	{
		return;
	}

	Insert(caps->CapsList, c);
}

// Comparison of Caps
int CompareCaps(void *p1, void *p2)
{
	CAPS *c1, *c2;
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	c1 = *(CAPS **)p1;
	c2 = *(CAPS **)p2;
	if (c1 == NULL || c2 == NULL)
	{
		return 0;
	}

	return StrCmpi(c1->Name, c2->Name);
}

// Create a Caps list
CAPSLIST *NewCapsList()
{
	CAPSLIST *caps = ZeroMalloc(sizeof(CAPSLIST));

	caps->CapsList = NewListFast(CompareCaps);

	return caps;
}

// Release the Caps
void FreeCaps(CAPS *c)
{
	// Validate arguments
	if (c == NULL)
	{
		return;
	}

	Free(c->Name);
	Free(c);
}

// Create a Caps
CAPS *NewCaps(char *name, UINT value)
{
	CAPS *c;
	// Validate arguments
	if (name == NULL)
	{
		return NULL;
	}

	c = ZeroMalloc(sizeof(CAPS));
	c->Name = CopyStr(name);
	c->Value = value;

	return c;
}

// Calculate the score from the current number of connections and weight
UINT SiCalcPoint(SERVER *s, UINT num, UINT weight)
{
	UINT server_max_sessions = SERVER_MAX_SESSIONS;
	if (s == NULL)
	{
		return 0;
	}
	if (weight == 0)
	{
		weight = 100;
	}

	server_max_sessions = GetServerCapsInt(s, "i_max_sessions");

	if (server_max_sessions == 0)
	{
		// Avoid divide by zero
		server_max_sessions = 1;
	}

	return (UINT)(((double)server_max_sessions -
		MIN((double)num * 100.0 / (double)weight, (double)server_max_sessions))
		* (double)FARM_BASE_POINT / (double)server_max_sessions);
}

// Get the server score
UINT SiGetPoint(SERVER *s)
{
	UINT num_session;
	// Validate arguments
	if (s == NULL)
	{
		return 0;
	}

	num_session = Count(s->Cedar->CurrentSessions);

	return SiCalcPoint(s, num_session, s->Weight);
}

// Generate the default certificate
void SiGenerateDefaultCert(X **server_x, K **server_k)
{
	SiGenerateDefaultCertEx(server_x, server_k, NULL);
}
void SiGenerateDefaultCertEx(X **server_x, K **server_k, char *common_name)
{
	X *x;
	K *private_key, *public_key;
	NAME *name;
	char tmp[MAX_SIZE];
	wchar_t cn[MAX_SIZE];
	// Validate arguments
	if (server_x == NULL || server_k == NULL)
	{
		return;
	}

	// Create a key pair
	RsaGen(&private_key, &public_key, 2048);

	if (IsEmptyStr(common_name))
	{
		// Get the host name
		StrCpy(tmp, sizeof(tmp), "server.softether.vpn");
		GetMachineName(tmp, sizeof(tmp));
		StrToUni(cn, sizeof(cn), tmp);
	}
	else
	{
		StrToUni(cn, sizeof(cn), common_name);
	}

	name = NewName(cn, cn, cn,
		L"US", NULL, NULL);
	x = NewRootX(public_key, private_key, name, GetDaysUntil2038Ex(), NULL);

	*server_x = x;
	*server_k = private_key;

	FreeName(name);

	FreeK(public_key);
}

// Set the server certificate to default
void SiInitDefaultServerCert(SERVER *s)
{
	X *x = NULL;
	K *k = NULL;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	// Generate a server certificate and private key
	SiGenerateDefaultCert(&x, &k);

	// Configure
	SetCedarCert(s->Cedar, x, k);

	FreeX(x);
	FreeK(k);
}

// Set the encryption algorithm name to default
void SiInitCipherName(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	SetCedarCipherList(s->Cedar, SERVER_DEFAULT_CIPHER_NAME);
}

// Initialize the listener list
void SiInitListenerList(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	SiLockListenerList(s);
	{
		{
			// Register the 4 ports (443, 992, 1194, 8888) as the default port
			SiAddListener(s, SERVER_DEF_PORTS_1, true);
			SiAddListener(s, SERVER_DEF_PORTS_2, true);
			SiAddListener(s, SERVER_DEF_PORTS_3, true);
			SiAddListener(s, SERVER_DEF_PORTS_4, true);
		}
	}
	SiUnlockListenerList(s);
}

// Remove the listener
bool SiDeleteListener(SERVER *s, UINT port)
{
	SERVER_LISTENER *e;
	// Validate arguments
	if (s == NULL || port == 0)
	{
		return false;
	}

	e = SiGetListener(s, port);
	if (e == NULL)
	{
		return false;
	}

	// Stop if still alive
	SiDisableListener(s, port);

	if (e->Listener != NULL)
	{
		ReleaseListener(e->Listener);
	}

	Delete(s->ServerListenerList, e);
	Free(e);

	return true;
}

// Compare the SERVER_LISTENER
int CompareServerListener(void *p1, void *p2)
{
	SERVER_LISTENER *s1, *s2;
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	s1 = *(SERVER_LISTENER **)p1;
	s2 = *(SERVER_LISTENER **)p2;
	if (s1 == NULL || s2 == NULL)
	{
		return 0;
	}

	if (s1->Port > s2->Port)
	{
		return 1;
	}
	else if (s1->Port < s2->Port)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

// Stop the listener
bool SiDisableListener(SERVER *s, UINT port)
{
	SERVER_LISTENER *e;
	// Validate arguments
	if (s == NULL || port == 0)
	{
		return false;
	}

	// Get the listener
	e = SiGetListener(s, port);
	if (e == NULL)
	{
		return false;
	}

	if (e->Enabled == false || e->Listener == NULL)
	{
		// Already stopped
		return true;
	}

	// Stop the listener
	StopListener(e->Listener);

	// Release the listener
	ReleaseListener(e->Listener);
	e->Listener = NULL;

	e->Enabled = false;

	return true;
}

// Start the listener
bool SiEnableListener(SERVER *s, UINT port)
{
	SERVER_LISTENER *e;
	// Validate arguments
	if (s == NULL || port == 0)
	{
		return false;
	}

	// Get the listener
	e = SiGetListener(s, port);
	if (e == NULL)
	{
		return false;
	}

	if (e->Enabled)
	{
		// It has already started
		return true;
	}

	// Create a listener
	e->Listener = NewListener(s->Cedar, LISTENER_TCP, e->Port);
	if (e->Listener == NULL)
	{
		// Failure
		return false;
	}

	e->Listener->DisableDos = e->DisableDos;

	e->Enabled = true;

	return true;
}

// Get the listener
SERVER_LISTENER *SiGetListener(SERVER *s, UINT port)
{
	UINT i;
	// Validate arguments
	if (s == NULL || port == 0)
	{
		return NULL;
	}

	for (i = 0;i < LIST_NUM(s->ServerListenerList);i++)
	{
		SERVER_LISTENER *e = LIST_DATA(s->ServerListenerList, i);
		if (e->Port == port)
		{
			return e;
		}
	}

	return NULL;
}

// Add a listener
bool SiAddListener(SERVER *s, UINT port, bool enabled)
{
	return SiAddListenerEx(s, port, enabled, false);
}
bool SiAddListenerEx(SERVER *s, UINT port, bool enabled, bool disable_dos)
{
	SERVER_LISTENER *e;
	UINT i;
	// Validate arguments
	if (s == NULL || port == 0)
	{
		return false;
	}

	// Check whether the listener exists already
	for (i = 0;i < LIST_NUM(s->ServerListenerList);i++)
	{
		e = LIST_DATA(s->ServerListenerList, i);
		if (e->Port == port)
		{
			// Already exist
			return false;
		}
	}

	// Register by initializing a new listener
	e = ZeroMalloc(sizeof(SERVER_LISTENER));
	e->Enabled = enabled;
	e->Port = port;
	e->DisableDos = disable_dos;

	if (e->Enabled)
	{
		// Create a listener
		e->Listener = NewListener(s->Cedar, LISTENER_TCP, e->Port);
		if (e->Listener != NULL)
		{
			e->Listener->DisableDos = e->DisableDos;
		}
	}

	Insert(s->ServerListenerList, e);

	return true;
}

// Lock the listener list
void SiLockListenerList(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	LockList(s->ServerListenerList);
}

// Unlock the listener list
void SiUnlockListenerList(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	UnlockList(s->ServerListenerList);
}

// Set the default value of the Virtual HUB options
void SiSetDefaultHubOption(HUB_OPTION *o)
{
	// Validate arguments
	if (o == NULL)
	{
		return;
	}

	o->DefaultGateway = SetIP32(192, 168, 30, 1);
	o->DefaultSubnet = SetIP32(255, 255, 255, 0);
	o->MaxSession = 0;
	o->VlanTypeId = MAC_PROTO_TAGVLAN;
	o->NoIPv6DefaultRouterInRAWhenIPv6 = false;
	o->ManageOnlyPrivateIP = true;
	o->ManageOnlyLocalUnicastIPv6 = true;
	o->NoMacAddressLog = true;
	o->NoDhcpPacketLogOutsideHub = true;
	o->AccessListIncludeFileCacheLifetime = ACCESS_LIST_INCLUDE_FILE_CACHE_LIFETIME;
	o->RemoveDefGwOnDhcpForLocalhost = true;
	o->FloodingSendQueueBufferQuota = DEFAULT_FLOODING_QUEUE_LENGTH;
}

// Create a default virtual HUB
void SiInitDefaultHubList(SERVER *s)
{
	HUB *h;
	HUB_OPTION o;
	HUB_LOG g;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	Zero(&o, sizeof(o));

	// Configure a default Virtual HUB management options
	SiSetDefaultHubOption(&o);

	h = NewHub(s->Cedar, s->Cedar->Bridge == false ? SERVER_DEFAULT_HUB_NAME : SERVER_DEFAULT_BRIDGE_NAME, &o);
	h->CreatedTime = SystemTime64();
	AddHub(s->Cedar, h);

	if (s->Cedar->Bridge)
	{
		// Randomize the password
		Rand(h->HashedPassword, sizeof(h->HashedPassword));
		Rand(h->SecurePassword, sizeof(h->SecurePassword));
	}

	h->Offline = true;
	SetHubOnline(h);

	// Log settings
	SiSetDefaultLogSetting(&g);
	SetHubLogSetting(h, &g);

	ReleaseHub(h);
}

// Set the log settings to default
void SiSetDefaultLogSetting(HUB_LOG *g)
{
	// Validate arguments
	if (g == NULL)
	{
		return;
	}

	Zero(g, sizeof(HUB_LOG));
	g->SaveSecurityLog = true;
	g->SecurityLogSwitchType = LOG_SWITCH_DAY;
	g->SavePacketLog = true;
	g->PacketLogSwitchType = LOG_SWITCH_DAY;
	g->PacketLogConfig[PACKET_LOG_TCP_CONN] =
		g->PacketLogConfig[PACKET_LOG_DHCP] = PACKET_LOG_HEADER;
}

// Set the initial configuration
void SiLoadInitialConfiguration(SERVER *s)
{
	RPC_KEEP k;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	// Auto saving interval related
	s->AutoSaveConfigSpan = SERVER_FILE_SAVE_INTERVAL_DEFAULT;
	s->BackupConfigOnlyWhenModified = true;

	s->Weight = FARM_DEFAULT_WEIGHT;

	SiLoadGlobalParamsCfg(NULL);

	// KEEP related
	Zero(&k, sizeof(k));

	{
		k.UseKeepConnect = true;
	}
	k.KeepConnectPort = 80;
	StrCpy(k.KeepConnectHost, sizeof(k.KeepConnectHost), CLIENT_DEFAULT_KEEPALIVE_HOST);
	k.KeepConnectInterval = KEEP_INTERVAL_DEFAULT * 1000;
	k.KeepConnectProtocol = CONNECTION_UDP;

	Lock(s->Keep->lock);
	{
		KEEP *keep = s->Keep;
		keep->Enable = k.UseKeepConnect;
		keep->Server = true;
		StrCpy(keep->ServerName, sizeof(keep->ServerName), k.KeepConnectHost);
		keep->ServerPort = k.KeepConnectPort;
		keep->UdpMode = k.KeepConnectProtocol;
		keep->Interval = k.KeepConnectInterval;
	}
	Unlock(s->Keep->lock);

	// Initialize the password
	{
		Sha0(s->HashedPassword, "", 0);
	}

	// Set the encryption algorithm name to default
	SiInitCipherName(s);

	// Set the server certificate to default
	SiInitDefaultServerCert(s);

	// Set the character which separates the username from the hub name
	s->Cedar->UsernameHubSeparator = DEFAULT_USERNAME_HUB_SEPARATOR;

	// Create a default HUB
	{
		SiInitDefaultHubList(s);
	}

	if (s->Cedar->Bridge == false)
	{
		// Create a DDNS client
		s->DDnsClient = NewDDNSClient(s->Cedar, NULL, NULL);
	}


	// Set the listener list to default setting
	SiInitListenerList(s);

	if (s->Cedar->Bridge)
	{
		// NAT traversal can not be used in the bridge environment
		s->DisableNatTraversal = true;
	}
	else
	{
		// Disable VPN-over-ICMP and VPN-over-DNS by default
		s->EnableVpnOverIcmp = false;
		s->EnableVpnOverDns = false;

		{
			LIST *ports = s->PortsUDP;

			AddInt(ports, SERVER_DEF_PORTS_1);
			AddInt(ports, SERVER_DEF_PORTS_2);
			AddInt(ports, SERVER_DEF_PORTS_3);
			AddInt(ports, SERVER_DEF_PORTS_4);

			ProtoSetUdpPorts(s->Proto, ports);
		}
	}

	s->Eraser = NewEraser(s->Logger, 0);
}

// Check whether the ports required for VPN-over-ICMP can be opened
bool SiCanOpenVpnOverIcmpPort()
{
	// Whether the ICMP can be opened
	SOCK *s = NewUDP(MAKE_SPECIAL_PORT(IP_PROTO_ICMPV4));

	if (s == NULL)
	{
		// Failure
		return false;
	}

	Disconnect(s);
	ReleaseSock(s);

	return true;
}

// Check whether the ports required for VPN-over-DNS can be opened
bool SiCanOpenVpnOverDnsPort()
{
	// Whether UDP Port 53 can be listen on
	SOCK *s = NewUDP(53);

	if (s == NULL)
	{
		// Listening failure
		return false;
	}

	Disconnect(s);
	ReleaseSock(s);

	return true;
}

// Read the configuration file (main)
bool SiLoadConfigurationFileMain(SERVER *s, FOLDER *root)
{
	// Validate arguments
	if (s == NULL || root == NULL)
	{
		return false;
	}

	return SiLoadConfigurationCfg(s, root);
}

// Read the configuration file
bool SiLoadConfigurationFile(SERVER *s)
{
	// Validate arguments
	bool ret = false;
	FOLDER *root;
	char *server_config_filename = SERVER_CONFIG_FILE_NAME;
	if (s == NULL)
	{
		return false;
	}


	s->CfgRw = NewCfgRwEx2A(&root,
		s->Cedar->Bridge == false ? server_config_filename : BRIDGE_CONFIG_FILE_NAME, false,
		s->Cedar->Bridge == false ? SERVER_CONFIG_TEMPLATE_NAME : BRIDGE_CONFIG_TEMPLATE_NAME);

	if (server_reset_setting)
	{
		CfgDeleteFolder(root);
		root = NULL;
		server_reset_setting = false;
	}

	if (root == NULL)
	{
		return false;
	}

	ret = SiLoadConfigurationFileMain(s, root);

	CfgDeleteFolder(root);

	return ret;
}

// Initialize the configuration
void SiInitConfiguration(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	s->AutoSaveConfigSpan = SERVER_FILE_SAVE_INTERVAL_DEFAULT;
	s->BackupConfigOnlyWhenModified = true;

	if (s->Cedar->Bridge == false)
	{
		// Protocols handler
		s->Proto = ProtoNew(s->Cedar);
		// IPsec server
		s->IPsecServer = NewIPsecServer(s->Cedar);
	}

	SLog(s->Cedar, "LS_LOAD_CONFIG_1");
	if (SiLoadConfigurationFile(s) == false)
	{
		// Ethernet initialization
		InitEth();

		SLog(s->Cedar, "LS_LOAD_CONFIG_3");
		SiLoadInitialConfiguration(s);

		SetFifoCurrentReallocMemSize(MEM_FIFO_REALLOC_MEM_SIZE);

		server_reset_setting = false;
	}
	else
	{
		SLog(s->Cedar, "LS_LOAD_CONFIG_2");
	}

	s->CfgRw->DontBackup = s->DontBackupConfig;

	// The arp_filter in Linux
	if (GetOsInfo()->OsType == OSTYPE_LINUX)
	{
		if (s->NoLinuxArpFilter == false)
		{
			SetLinuxArpFilter();
		}
	}

	if (s->DisableDosProtection)
	{
		DisableDosProtect();
	}
	else
	{
		EnableDosProtect();
	}

	s->AutoSaveConfigSpanSaved = s->AutoSaveConfigSpan;

	// Create a VPN Azure client
	if (s->DDnsClient != NULL && s->Cedar->Bridge == false && s->ServerType == SERVER_TYPE_STANDALONE)
	{
		s->AzureClient = NewAzureClient(s->Cedar, s);

		AcSetEnable(s->AzureClient, s->EnableVpnAzure);
	}

	// Reduce the storage interval in the case of user mode
#ifdef	OS_WIN32
	if (MsIsUserMode())
	{
		s->AutoSaveConfigSpan = MIN(s->AutoSaveConfigSpan, SERVER_FILE_SAVE_INTERVAL_USERMODE);
	}
#endif	//OS_WIN32

	// Create a saving thread
	SLog(s->Cedar, "LS_INIT_SAVE_THREAD", s->AutoSaveConfigSpan / 1000);
	s->SaveHaltEvent = NewEvent();
	s->SaveThread = NewThread(SiSaverThread, s);
}

// Set the state of Enabled / Disabled of Azure Client
void SiSetAzureEnable(SERVER *s, bool enabled)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	if (s->AzureClient != NULL)
	{
		AcSetEnable(s->AzureClient, enabled);
	}

	s->EnableVpnAzure = enabled;
}

// Apply the Config to the Azure Client
void SiApplyAzureConfig(SERVER *s, DDNS_CLIENT_STATUS *ddns_status)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	AcApplyCurrentConfig(s->AzureClient, ddns_status);
}

// Get whether the Azure Client is enabled
bool SiIsAzureEnabled(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return false;
	}

	if (s->AzureClient == NULL)
	{
		return false;
	}

	return s->EnableVpnAzure;
}

// Get whether the Azure Client is supported
bool SiIsAzureSupported(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return false;
	}

	if (s->AzureClient == NULL)
	{
		return false;
	}

	return true;
}

// Read the server settings from the CFG
bool SiLoadConfigurationCfg(SERVER *s, FOLDER *root)
{
	FOLDER *f1, *f2, *f3, *f4, *f5, *f6, *f7, *f8, *f9;
	// Validate arguments
	if (s == NULL || root == NULL)
	{
		return false;
	}

	f1 = CfgGetFolder(root, "ServerConfiguration");
	f2 = CfgGetFolder(root, "VirtualHUB");
	f3 = CfgGetFolder(root, "ListenerList");
	f4 = CfgGetFolder(root, "LocalBridgeList");
	f5 = CfgGetFolder(root, "VirtualLayer3SwitchList");
	f6 = CfgGetFolder(root, "LicenseManager");
	f7 = CfgGetFolder(root, "IPsec");
	f8 = CfgGetFolder(root, "DDnsClient");
	f9 = CfgGetFolder(root, "WireGuardKeyList");

	if (f1 == NULL)
	{
		SLog(s->Cedar, "LS_BAD_CONFIG");
		return false;
	}

#ifdef	OS_WIN32
	if (f4 != NULL)
	{
		// Read the flag of using the SeLow driver
		bool b = true;

		if (CfgIsItem(f4, "EnableSoftEtherKernelModeDriver"))
		{
			b = CfgGetBool(f4, "EnableSoftEtherKernelModeDriver");
		}

		Win32SetEnableSeLow(b);
	}
#endif	// OS_WIN32

	// Ethernet initialization
	InitEth();

	s->ConfigRevision = CfgGetInt(root, "ConfigRevision");

	if (s->Cedar->Bridge == false && f6 != NULL)
	{
		if (GetServerCapsBool(s, "b_support_license"))
		{
			SiLoadLicenseManager(s, f6);
		}
	}

	DestroyServerCapsCache(s);

	SiLoadServerCfg(s, f1);

	if (s->ServerType != SERVER_TYPE_FARM_MEMBER)
	{
		TOKEN_LIST *t = CfgEnumFolderToTokenList(f9);
		if (t != NULL)
		{
			LockList(s->Cedar->WgkList);
			{
				UINT i;
				for (i = 0; i < t->NumTokens; ++i)
				{
					const char *name = t->Token[i];
					FOLDER *f = CfgGetFolder(f9, name);
					if (f != NULL)
					{
						WGK *wgk = Malloc(sizeof(WGK));
						StrCpy(wgk->Key, sizeof(wgk->Key), name);
						CfgGetStr(f, "Hub", wgk->Hub, sizeof(wgk->Hub));
						CfgGetStr(f, "User", wgk->User, sizeof(wgk->User));
						Add(s->Cedar->WgkList, wgk);
					}
				}
			}
			UnlockList(s->Cedar->WgkList);
			FreeToken(t);
		}

		SiLoadHubs(s, f2);
	}

	SiLoadListeners(s, f3);

	if (f4 != NULL)
	{
		SiLoadLocalBridges(s, f4);
	}

	if (s->Cedar->Bridge == false && f5 != NULL)
	{
		SiLoadL3Switchs(s, f5);
	}

	if (f7 != NULL && GetServerCapsBool(s, "b_support_ipsec"))
	{
		SiLoadIPsec(s, f7);
	}

	if (s->Cedar->Bridge == false)
	{
		if (f8 == NULL)
		{
			// Create a DDNS client with a new key
			s->DDnsClient = NewDDNSClient(s->Cedar, NULL, NULL);
		}
		else
		{
			// Create by reading the setting of the DDNS client
			UCHAR key[SHA1_SIZE];
			if (CfgGetBool(f8, "Disabled"))
			{
				// Disabled
			}
			else
			{
				char machine_name[MAX_SIZE];
				char machine_name2[MAX_SIZE];
				INTERNET_SETTING t;
				BUF *pw;

				// Proxy Setting
				Zero(&t, sizeof(t));
				t.ProxyType = CfgGetInt(f8, "ProxyType");
				CfgGetStr(f8, "ProxyHostName", t.ProxyHostName, sizeof(t.ProxyHostName));
				t.ProxyPort = CfgGetInt(f8, "ProxyPort");
				CfgGetStr(f8, "ProxyUsername", t.ProxyUsername, sizeof(t.ProxyUsername));
				pw = CfgGetBuf(f8, "ProxyPassword");
				if (pw != NULL)
				{
					char *pw_str = DecryptPassword(pw);
					StrCpy(t.ProxyPassword, sizeof(t.ProxyPassword), pw_str);

					Free(pw_str);
					FreeBuf(pw);
				}

				CfgGetStr(f8, "CustomHttpHeader", t.CustomHttpHeader, sizeof(t.CustomHttpHeader));

				GetMachineHostName(machine_name, sizeof(machine_name));

				CfgGetStr(f8, "LocalHostname", machine_name2, sizeof(machine_name2));

				if (CfgGetByte(f8, "Key", key, sizeof(key)) != sizeof(key) || StrCmpi(machine_name, machine_name2) != 0)
				{
					// Create a DDNS client with a new key
					s->DDnsClient = NewDDNSClient(s->Cedar, NULL, &t);
				}
				else
				{
					// Create the DDNS client with stored key
					s->DDnsClient = NewDDNSClient(s->Cedar, key, &t);
				}
			}
		}
	}

	s->IPsecMessageDisplayed = CfgGetBool(root, "IPsecMessageDisplayed");

	return true;
}

// Write the listener configuration
void SiWriteListenerCfg(FOLDER *f, SERVER_LISTENER *r)
{
	// Validate arguments
	if (f == NULL || r == NULL)
	{
		return;
	}

	CfgAddBool(f, "Enabled", r->Enabled);
	CfgAddInt(f, "Port", r->Port);
	CfgAddBool(f, "DisableDos", r->DisableDos);
}

// Read the listener configuration
void SiLoadListenerCfg(SERVER *s, FOLDER *f)
{
	bool enable;
	UINT port;
	bool disable_dos;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	enable = CfgGetBool(f, "Enabled");
	port = CfgGetInt(f, "Port");
	disable_dos = CfgGetBool(f, "DisableDos");

	if (port == 0)
	{
		return;
	}

	SiAddListenerEx(s, port, enable, disable_dos);
}

// Read the listener list
void SiLoadListeners(SERVER *s, FOLDER *f)
{
	TOKEN_LIST *t;
	UINT i;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	t = CfgEnumFolderToTokenList(f);
	for (i = 0;i < t->NumTokens;i++)
	{
		FOLDER *ff = CfgGetFolder(f, t->Token[i]);
		if (ff != NULL)
		{
			SiLoadListenerCfg(s, ff);
		}
	}
	FreeToken(t);
}

// Write the listener list
void SiWriteListeners(FOLDER *f, SERVER *s)
{
	// Validate arguments
	if (f == NULL || s == NULL)
	{
		return;
	}

	LockList(s->ServerListenerList);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(s->ServerListenerList);i++)
		{
			SERVER_LISTENER *r = LIST_DATA(s->ServerListenerList, i);
			char name[MAX_SIZE];
			Format(name, sizeof(name), "Listener%u", i);
			SiWriteListenerCfg(CfgCreateFolder(f, name), r);
		}
	}
	UnlockList(s->ServerListenerList);
}

// Write the bridge
void SiWriteLocalBridgeCfg(FOLDER *f, LOCALBRIDGE *br)
{
	// Validate arguments
	if (f == NULL || br == NULL)
	{
		return;
	}

	CfgAddStr(f, "DeviceName", br->DeviceName);
	CfgAddStr(f, "HubName", br->HubName);
	CfgAddBool(f, "NoPromiscuousMode", br->Local);
	CfgAddBool(f, "MonitorMode", br->Monitor);
	CfgAddBool(f, "LimitBroadcast", br->LimitBroadcast);

	if (OS_IS_UNIX(GetOsInfo()->OsType))
	{
		CfgAddBool(f, "TapMode", br->TapMode);

		if (br->TapMode)
		{
			char tmp[MAX_SIZE];
			MacToStr(tmp, sizeof(tmp), br->TapMacAddress);
			CfgAddStr(f, "TapMacAddress", tmp);
		}
	}
}

// Write the bridge list
void SiWriteLocalBridges(FOLDER *f, SERVER *s)
{
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

#ifdef	OS_WIN32
	CfgAddBool(f, "ShowAllInterfaces", Win32EthGetShowAllIf());

	CfgAddBool(f, "EnableSoftEtherKernelModeDriver", Win32GetEnableSeLow());
#endif	// OS_WIN32

#ifdef	UNIX_LINUX
	CfgAddBool(f, "DoNotDisableOffloading", GetGlobalServerFlag(GSF_LOCALBRIDGE_NO_DISABLE_OFFLOAD));
#endif	// UNIX_LINUX

	LockList(s->Cedar->LocalBridgeList);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(s->Cedar->LocalBridgeList);i++)
		{
			LOCALBRIDGE *br = LIST_DATA(s->Cedar->LocalBridgeList, i);
			char name[MAX_SIZE];

			Format(name, sizeof(name), "LocalBridge%u", i);
			SiWriteLocalBridgeCfg(CfgCreateFolder(f, name), br);
		}
	}
	UnlockList(s->Cedar->LocalBridgeList);
}

// Read the bridge
void SiLoadLocalBridgeCfg(SERVER *s, FOLDER *f)
{
	char hub[MAX_SIZE];
	char nic[MAX_SIZE];
	bool tapmode = false;
	UCHAR tapaddr[6];
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	Zero(hub, sizeof(hub));
	Zero(nic, sizeof(nic));

	CfgGetStr(f, "HubName", hub, sizeof(hub));
	CfgGetStr(f, "DeviceName", nic, sizeof(nic));

	if (IsEmptyStr(hub) || IsEmptyStr(nic)
		)
	{
		return;
	}

	if (OS_IS_UNIX(GetOsInfo()->OsType))
	{
		if (CfgGetBool(f, "TapMode"))
		{
			char tmp[MAX_SIZE];
			tapmode = true;
			Zero(tapaddr, sizeof(tapaddr));
			if (CfgGetStr(f, "TapMacAddress", tmp, sizeof(tmp)))
			{
				BUF *b;
				b = StrToBin(tmp);
				if (b != NULL && b->Size == 6)
				{
					Copy(tapaddr, b->Buf, sizeof(tapaddr));
				}
				FreeBuf(b);
			}
		}
	}

	AddLocalBridge(s->Cedar, hub, nic, CfgGetBool(f, "NoPromiscuousMode"), CfgGetBool(f, "MonitorMode"),
		tapmode, tapaddr, CfgGetBool(f, "LimitBroadcast"));
}

// Read the bridge list
void SiLoadLocalBridges(SERVER *s, FOLDER *f)
{
	TOKEN_LIST *t;
	UINT i;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

#ifdef	OS_WIN32
	Win32EthSetShowAllIf(CfgGetBool(f, "ShowAllInterfaces"));
#endif	// OS_WIN32

#ifdef	UNIX_LINUX
	SetGlobalServerFlag(GSF_LOCALBRIDGE_NO_DISABLE_OFFLOAD, CfgGetBool(f, "DoNotDisableOffloading"));
#endif	// UNIX_LINUX

	t = CfgEnumFolderToTokenList(f);

	for (i = 0;i < t->NumTokens;i++)
	{
		char *name = t->Token[i];

		SiLoadLocalBridgeCfg(s, CfgGetFolder(f, name));
	}

	FreeToken(t);
}

// Increment the configuration revision of the server
void IncrementServerConfigRevision(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	s->ConfigRevision++;
}

// Write the server settings to CFG
FOLDER *SiWriteConfigurationToCfg(SERVER *s)
{
	FOLDER *root;
	char region[128];
	// Validate arguments
	if (s == NULL)
	{
		return NULL;
	}

	root = CfgCreateFolder(NULL, TAG_ROOT);

	SiGetCurrentRegion(s->Cedar, region, sizeof(region));

	CfgAddStr(root, "Region", region);

	CfgAddInt(root, "ConfigRevision", s->ConfigRevision);

	SiWriteListeners(CfgCreateFolder(root, "ListenerList"), s);

	SiWriteLocalBridges(CfgCreateFolder(root, "LocalBridgeList"), s);

	SiWriteServerCfg(CfgCreateFolder(root, "ServerConfiguration"), s);

	if (s->UpdatedServerType != SERVER_TYPE_FARM_MEMBER)
	{
		FOLDER *f = CfgCreateFolder(root, "WireGuardKeyList");
		if (f != NULL)
		{
			LockList(s->Cedar->WgkList);
			{
				UINT i;
				for (i = 0; i < LIST_NUM(s->Cedar->WgkList); ++i)
				{
					WGK *wgk = LIST_DATA(s->Cedar->WgkList, i);
					FOLDER *ff = CfgCreateFolder(f, wgk->Key);
					if (ff != NULL)
					{
						CfgAddStr(ff, "Hub", wgk->Hub);
						CfgAddStr(ff, "User", wgk->User);
					}
				}
			}
			UnlockList(s->Cedar->WgkList);
		}

		SiWriteHubs(CfgCreateFolder(root, "VirtualHUB"), s);
	}

	if (s->Cedar->Bridge == false)
	{
		SiWriteL3Switchs(CfgCreateFolder(root, "VirtualLayer3SwitchList"), s);

		if (GetServerCapsBool(s, "b_support_license"))
		{
			SiWriteLicenseManager(CfgCreateFolder(root, "LicenseManager"), s);
		}
	}

	if (s->Led)
	{
		CfgAddBool(root, "Led", true);
		CfgAddBool(root, "LedSpecial", s->LedSpecial);
	}

	if (GetServerCapsBool(s, "b_support_ipsec"))
	{
		SiWriteIPsec(CfgCreateFolder(root, "IPsec"), s);
	}

	if (s->Cedar->Bridge == false)
	{
		FOLDER *ddns_folder = CfgCreateFolder(root, "DDnsClient");

		if (s->DDnsClient == NULL)
		{
			// Disabled
			CfgAddBool(ddns_folder, "Disabled", true);
		}
		else
		{
			char machine_name[MAX_SIZE];
			BUF *pw;
			INTERNET_SETTING *t;
			// Enabled
			CfgAddBool(ddns_folder, "Disabled", false);
			CfgAddByte(ddns_folder, "Key", s->DDnsClient->Key, SHA1_SIZE);

			GetMachineHostName(machine_name, sizeof(machine_name));
			CfgAddStr(ddns_folder, "LocalHostname", machine_name);

			t = &s->DDnsClient->InternetSetting;

			CfgAddInt(ddns_folder, "ProxyType", t->ProxyType);
			CfgAddStr(ddns_folder, "ProxyHostName", t->ProxyHostName);
			CfgAddInt(ddns_folder, "ProxyPort", t->ProxyPort);
			CfgAddStr(ddns_folder, "ProxyUsername", t->ProxyUsername);

			if (IsEmptyStr(t->ProxyPassword) == false)
			{
				pw = EncryptPassword(t->ProxyPassword);

				CfgAddBuf(ddns_folder, "ProxyPassword", pw);

				FreeBuf(pw);
			}

			CfgAddStr(ddns_folder, "CustomHttpHeader", t->CustomHttpHeader);
		}
	}

	CfgAddBool(root, "IPsecMessageDisplayed", s->IPsecMessageDisplayed);


	return root;
}

// Read the policy
void SiLoadPolicyCfg(POLICY *p, FOLDER *f)
{
	// Validate arguments
	if (f == NULL || p == NULL)
	{
		return;
	}

	Zero(p, sizeof(POLICY));

	// Ver 2
	p->Access = CfgGetBool(f, "Access");
	p->DHCPFilter = CfgGetBool(f, "DHCPFilter");
	p->DHCPNoServer = CfgGetBool(f, "DHCPNoServer");
	p->DHCPForce = CfgGetBool(f, "DHCPForce");
	p->NoBridge = CfgGetBool(f, "NoBridge");
	p->NoRouting = CfgGetBool(f, "NoRouting");
	p->CheckMac = CfgGetBool(f, "CheckMac");
	p->CheckIP = CfgGetBool(f, "CheckIP");
	p->ArpDhcpOnly = CfgGetBool(f, "ArpDhcpOnly");
	p->PrivacyFilter = CfgGetBool(f, "PrivacyFilter");
	p->NoServer = CfgGetBool(f, "NoServer");
	p->NoBroadcastLimiter = CfgGetBool(f, "NoBroadcastLimiter");
	p->MonitorPort = CfgGetBool(f, "MonitorPort");
	p->MaxConnection = CfgGetInt(f, "MaxConnection");
	p->TimeOut = CfgGetInt(f, "TimeOut");
	p->MaxMac = CfgGetInt(f, "MaxMac");
	p->MaxIP = CfgGetInt(f, "MaxIP");
	p->MaxUpload = CfgGetInt(f, "MaxUpload");
	p->MaxDownload = CfgGetInt(f, "MaxDownload");
	p->FixPassword = CfgGetBool(f, "FixPassword");
	p->MultiLogins = CfgGetInt(f, "MultiLogins");
	p->NoQoS = CfgGetBool(f, "NoQoS");

	// Ver 3
	p->RSandRAFilter = CfgGetBool(f, "RSandRAFilter");
	p->RAFilter = CfgGetBool(f, "RAFilter");
	p->DHCPv6Filter = CfgGetBool(f, "DHCPv6Filter");
	p->DHCPv6NoServer = CfgGetBool(f, "DHCPv6NoServer");
	p->NoRoutingV6 = CfgGetBool(f, "NoRoutingV6");
	p->CheckIPv6 = CfgGetBool(f, "CheckIPv6");
	p->NoServerV6 = CfgGetBool(f, "NoServerV6");
	p->MaxIPv6 = CfgGetInt(f, "MaxIPv6");
	p->NoSavePassword = CfgGetBool(f, "NoSavePassword");
	p->AutoDisconnect = CfgGetInt(f, "AutoDisconnect");
	p->FilterIPv4 = CfgGetBool(f, "FilterIPv4");
	p->FilterIPv6 = CfgGetBool(f, "FilterIPv6");
	p->FilterNonIP = CfgGetBool(f, "FilterNonIP");
	p->NoIPv6DefaultRouterInRA = CfgGetBool(f, "NoIPv6DefaultRouterInRA");
	p->NoIPv6DefaultRouterInRAWhenIPv6 = CfgGetBool(f, "NoIPv6DefaultRouterInRAWhenIPv6");
	p->VLanId = CfgGetInt(f, "VLanId");
}

// Write the policy
void SiWritePolicyCfg(FOLDER *f, POLICY *p, bool cascade_mode)
{
	// Validate arguments
	if (f == NULL || p == NULL)
	{
		return;
	}

	// Ver 2.0
	if (cascade_mode == false)
	{
		CfgAddBool(f, "Access", p->Access);
	}

	CfgAddBool(f, "DHCPFilter", p->DHCPFilter);
	CfgAddBool(f, "DHCPNoServer", p->DHCPNoServer);
	CfgAddBool(f, "DHCPForce", p->DHCPForce);

	if (cascade_mode == false)
	{
		CfgAddBool(f, "NoBridge", p->NoBridge);
		CfgAddBool(f, "NoRouting", p->NoRouting);
	}

	CfgAddBool(f, "CheckMac", p->CheckMac);
	CfgAddBool(f, "CheckIP", p->CheckIP);
	CfgAddBool(f, "ArpDhcpOnly", p->ArpDhcpOnly);

	if (cascade_mode == false)
	{
		CfgAddBool(f, "PrivacyFilter", p->PrivacyFilter);
	}

	CfgAddBool(f, "NoServer", p->NoServer);
	CfgAddBool(f, "NoBroadcastLimiter", p->NoBroadcastLimiter);

	if (cascade_mode == false)
	{
		CfgAddBool(f, "MonitorPort", p->MonitorPort);
		CfgAddInt(f, "MaxConnection", p->MaxConnection);
		CfgAddInt(f, "TimeOut", p->TimeOut);
	}

	CfgAddInt(f, "MaxMac", p->MaxMac);
	CfgAddInt(f, "MaxIP", p->MaxIP);
	CfgAddInt(f, "MaxUpload", p->MaxUpload);
	CfgAddInt(f, "MaxDownload", p->MaxDownload);

	if (cascade_mode == false)
	{
		CfgAddBool(f, "FixPassword", p->FixPassword);
		CfgAddInt(f, "MultiLogins", p->MultiLogins);
		CfgAddBool(f, "NoQoS", p->NoQoS);
	}

	// Ver 3.0
	CfgAddBool(f, "RSandRAFilter", p->RSandRAFilter);
	CfgAddBool(f, "RAFilter", p->RAFilter);
	CfgAddBool(f, "DHCPv6Filter", p->DHCPv6Filter);
	CfgAddBool(f, "DHCPv6NoServer", p->DHCPv6NoServer);

	if (cascade_mode == false)
	{
		CfgAddBool(f, "NoRoutingV6", p->NoRoutingV6);
	}

	CfgAddBool(f, "CheckIPv6", p->CheckIPv6);
	CfgAddBool(f, "NoServerV6", p->NoServerV6);
	CfgAddInt(f, "MaxIPv6", p->MaxIPv6);

	if (cascade_mode == false)
	{
		CfgAddBool(f, "NoSavePassword", p->NoSavePassword);
		CfgAddInt(f, "AutoDisconnect", p->AutoDisconnect);
	}

	CfgAddBool(f, "FilterIPv4", p->FilterIPv4);
	CfgAddBool(f, "FilterIPv6", p->FilterIPv6);
	CfgAddBool(f, "FilterNonIP", p->FilterNonIP);
	CfgAddBool(f, "NoIPv6DefaultRouterInRA", p->NoIPv6DefaultRouterInRA);
	CfgAddBool(f, "NoIPv6DefaultRouterInRAWhenIPv6", p->NoIPv6DefaultRouterInRAWhenIPv6);
	CfgAddInt(f, "VLanId", p->VLanId);
}

// Write the link information of the Virtual HUB
void SiWriteHubLinkCfg(FOLDER *f, LINK *k)
{
	// Validate arguments
	if (f == NULL || k == NULL)
	{
		return;
	}

	Lock(k->lock);
	{
		// Online
		CfgAddBool(f, "Online", k->Offline ? false : true);

		// Client options
		CiWriteClientOption(CfgCreateFolder(f, "ClientOption"), k->Option);

		// Client authentication data
		CiWriteClientAuth(CfgCreateFolder(f, "ClientAuth"), k->Auth);

		// Policy
		if (k->Policy != NULL)
		{
			SiWritePolicyCfg(CfgCreateFolder(f, "Policy"), k->Policy, true);
		}

		CfgAddBool(f, "CheckServerCert", k->CheckServerCert);
		CfgAddBool(f, "AddDefaultCA", k->AddDefaultCA);

		if (k->ServerCert != NULL)
		{
			BUF *b = XToBuf(k->ServerCert, false);
			CfgAddBuf(f, "ServerCert", b);
			FreeBuf(b);
		}
	}
	Unlock(k->lock);
}

// Read the link information
void SiLoadHubLinkCfg(FOLDER *f, HUB *h)
{
	bool online;
	CLIENT_OPTION *o;
	CLIENT_AUTH *a;
	FOLDER *pf;
	POLICY p;
	LINK *k;
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	pf = CfgGetFolder(f, "Policy");
	if (pf == NULL)
	{
		return;
	}

	SiLoadPolicyCfg(&p, pf);

	online = CfgGetBool(f, "Online");

	o = CiLoadClientOption(CfgGetFolder(f, "ClientOption"));
	a = CiLoadClientAuth(CfgGetFolder(f, "ClientAuth"));
	if (o == NULL || a == NULL)
	{
		Free(o);
		CiFreeClientAuth(a);
		return;
	}

	k = NewLink(h->Cedar, h, o, a, &p);
	if (k != NULL)
	{
		BUF *b;
		k->CheckServerCert = CfgGetBool(f, "CheckServerCert");
		k->AddDefaultCA = CfgGetBool(f, "AddDefaultCA");
		b = CfgGetBuf(f, "ServerCert");
		if (b != NULL)
		{
			k->ServerCert = BufToX(b, false);
			FreeBuf(b);
		}

		k->Offline = !online;

		ReleaseLink(k);
	}

	Free(o);
	CiFreeClientAuth(a);
}

// Write the SecureNAT of the Virtual HUB
void SiWriteSecureNAT(HUB *h, FOLDER *f)
{
	// Validate arguments
	if (h == NULL || f == NULL)
	{
		return;
	}

	CfgAddBool(f, "Disabled", h->EnableSecureNAT ? false : true);

	NiWriteVhOptionEx(h->SecureNATOption, f);
}

// Read the administration options for the virtual HUB
void SiLoadHubAdminOptions(HUB *h, FOLDER *f)
{
	TOKEN_LIST *t;
	// Validate arguments
	if (h == NULL || f == NULL)
	{
		return;
	}

	t = CfgEnumItemToTokenList(f);
	if (t != NULL)
	{
		UINT i;

		LockList(h->AdminOptionList);
		{
			DeleteAllHubAdminOption(h, false);

			for (i = 0;i < t->NumTokens;i++)
			{
				char *name = t->Token[i];
				ADMIN_OPTION *a;
				UINT value = CfgGetInt(f, name);;

				Trim(name);

				a = ZeroMalloc(sizeof(ADMIN_OPTION));
				StrCpy(a->Name, sizeof(a->Name), name);
				a->Value = value;

				Insert(h->AdminOptionList, a);
			}

			AddHubAdminOptionsDefaults(h, false);
		}
		UnlockList(h->AdminOptionList);

		FreeToken(t);
	}
}

// Write the administration options for the virtual HUB
void SiWriteHubAdminOptions(FOLDER *f, HUB *h)
{
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	LockList(h->AdminOptionList);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(h->AdminOptionList);i++)
		{
			ADMIN_OPTION *a = LIST_DATA(h->AdminOptionList, i);

			CfgAddInt(f, a->Name, a->Value);
		}
	}
	UnlockList(h->AdminOptionList);
}

// Write the link list of the Virtual HUB
void SiWriteHubLinks(FOLDER *f, HUB *h)
{
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	LockList(h->LinkList);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(h->LinkList);i++)
		{
			LINK *k = LIST_DATA(h->LinkList, i);
			char name[MAX_SIZE];
			Format(name, sizeof(name), "Cascade%u", i);
			SiWriteHubLinkCfg(CfgCreateFolder(f, name), k);
		}
	}
	UnlockList(h->LinkList);
}

// Read the link list
void SiLoadHubLinks(HUB *h, FOLDER *f)
{
	TOKEN_LIST *t;
	UINT i;
	// Validate arguments
	if (h == NULL || f == NULL)
	{
		return;
	}

	t = CfgEnumFolderToTokenList(f);

	for (i = 0;i < t->NumTokens;i++)
	{
		char *name = t->Token[i];
		SiLoadHubLinkCfg(CfgGetFolder(f, name), h);
	}

	FreeToken(t);
}

// Write an item of the access list
void SiWriteHubAccessCfg(FOLDER *f, ACCESS *a)
{
	// Validate arguments
	if (f == NULL || a == NULL)
	{
		return;
	}

	CfgAddUniStr(f, "Note", a->Note);
	CfgAddBool(f, "Active", a->Active);
	CfgAddInt(f, "Priority", a->Priority);
	CfgAddBool(f, "Discard", a->Discard);
	CfgAddBool(f, "IsIPv6", a->IsIPv6);

	if (a->IsIPv6 == false)
	{
		CfgAddIp32(f, "SrcIpAddress", a->SrcIpAddress);
		CfgAddIp32(f, "SrcSubnetMask", a->SrcSubnetMask);
		CfgAddIp32(f, "DestIpAddress", a->DestIpAddress);
		CfgAddIp32(f, "DestSubnetMask", a->DestSubnetMask);
	}
	else
	{
		CfgAddIp6Addr(f, "SrcIpAddress6", &a->SrcIpAddress6);
		CfgAddIp6Addr(f, "SrcSubnetMask6", &a->SrcSubnetMask6);
		CfgAddIp6Addr(f, "DestIpAddress6", &a->DestIpAddress6);
		CfgAddIp6Addr(f, "DestSubnetMask6", &a->DestSubnetMask6);
	}

	CfgAddInt(f, "Protocol", a->Protocol);
	CfgAddInt(f, "SrcPortStart", a->SrcPortStart);
	CfgAddInt(f, "SrcPortEnd", a->SrcPortEnd);
	CfgAddInt(f, "DestPortStart", a->DestPortStart);
	CfgAddInt(f, "DestPortEnd", a->DestPortEnd);
	CfgAddStr(f, "SrcUsername", a->SrcUsername);
	CfgAddStr(f, "DestUsername", a->DestUsername);
	CfgAddBool(f, "CheckSrcMac", a->CheckSrcMac);

	if (a->CheckSrcMac)
	{
		char tmp[MAX_PATH];

		MacToStr(tmp, sizeof(tmp), a->SrcMacAddress);
		CfgAddStr(f, "SrcMacAddress", tmp);

		MacToStr(tmp, sizeof(tmp), a->SrcMacMask);
		CfgAddStr(f, "SrcMacMask", tmp);
	}

	CfgAddBool(f, "CheckDstMac", a->CheckDstMac);

	if (a->CheckDstMac)
	{
		char tmp[MAX_PATH];

		MacToStr(tmp, sizeof(tmp), a->DstMacAddress);
		CfgAddStr(f, "DstMacAddress", tmp);

		MacToStr(tmp, sizeof(tmp), a->DstMacMask);
		CfgAddStr(f, "DstMacMask", tmp);
	}

	CfgAddBool(f, "CheckTcpState", a->CheckTcpState);
	CfgAddBool(f, "Established", a->Established);

	CfgAddStr(f, "RedirectUrl", a->RedirectUrl);

	CfgAddInt(f, "Delay", a->Delay);
	CfgAddInt(f, "Jitter", a->Jitter);
	CfgAddInt(f, "Loss", a->Loss);
}

// Read an item of the access list
void SiLoadHubAccessCfg(HUB *h, FOLDER *f)
{
	ACCESS a;
	char tmp[MAX_PATH];
	// Validate arguments
	if (h == NULL || f == NULL)
	{
		return;
	}

	Zero(&a, sizeof(a));

	CfgGetUniStr(f, "Note", a.Note, sizeof(a.Note));
	a.Active = CfgGetBool(f, "Active");
	a.Priority = CfgGetInt(f, "Priority");
	a.Discard = CfgGetBool(f, "Discard");
	a.IsIPv6 = CfgGetBool(f, "IsIPv6");

	if (a.IsIPv6 == false)
	{
		a.SrcIpAddress = CfgGetIp32(f, "SrcIpAddress");
		a.SrcSubnetMask = CfgGetIp32(f, "SrcSubnetMask");
		a.DestIpAddress = CfgGetIp32(f, "DestIpAddress");
		a.DestSubnetMask = CfgGetIp32(f, "DestSubnetMask");
	}
	else
	{
		CfgGetIp6Addr(f, "SrcIpAddress6", &a.SrcIpAddress6);
		CfgGetIp6Addr(f, "SrcSubnetMask6", &a.SrcSubnetMask6);
		CfgGetIp6Addr(f, "DestIpAddress6", &a.DestIpAddress6);
		CfgGetIp6Addr(f, "DestSubnetMask6", &a.DestSubnetMask6);
	}

	a.Protocol = CfgGetInt(f, "Protocol");
	a.SrcPortStart = CfgGetInt(f, "SrcPortStart");
	a.SrcPortEnd = CfgGetInt(f, "SrcPortEnd");
	a.DestPortStart = CfgGetInt(f, "DestPortStart");
	a.DestPortEnd = CfgGetInt(f, "DestPortEnd");
	CfgGetStr(f, "SrcUsername", a.SrcUsername, sizeof(a.SrcUsername));
	CfgGetStr(f, "DestUsername", a.DestUsername, sizeof(a.DestUsername));
	a.CheckSrcMac = CfgGetBool(f, "CheckSrcMac");

	if (CfgGetByte(f, "SrcMacAddress", a.SrcMacAddress, sizeof(a.SrcMacAddress)) == 0)
	{
		CfgGetStr(f, "SrcMacAddress", tmp, sizeof(tmp));
		if (StrToMac(a.SrcMacAddress, tmp) == false)
		{
			a.CheckSrcMac = false;
		}
	}

	if (CfgGetByte(f, "SrcMacMask", a.SrcMacMask, sizeof(a.SrcMacMask)) == 0)
	{
		CfgGetStr(f, "SrcMacMask", tmp, sizeof(tmp));
		if (StrToMac(a.SrcMacMask, tmp) == false)
		{
			a.CheckSrcMac = false;
		}
	}

	a.CheckDstMac = CfgGetBool(f, "CheckDstMac");

	if (CfgGetByte(f, "DstMacAddress", a.DstMacAddress, sizeof(a.DstMacAddress)) == 0)
	{
		CfgGetStr(f, "DstMacAddress", tmp, sizeof(tmp));
		if (StrToMac(a.DstMacAddress, tmp) == false)
		{
			a.CheckDstMac = false;
		}
	}

	if (CfgGetByte(f, "DstMacMask", a.DstMacMask, sizeof(a.DstMacMask)) == 0)
	{
		CfgGetStr(f, "DstMacMask", tmp, sizeof(tmp));
		if (StrToMac(a.DstMacMask, tmp) == false)
		{
			a.CheckDstMac = false;
		}
	}

	a.CheckTcpState = CfgGetBool(f, "CheckTcpState");
	a.Established = CfgGetBool(f, "Established");
	a.Delay = MAKESURE(CfgGetInt(f, "Delay"), 0, HUB_ACCESSLIST_DELAY_MAX);
	a.Jitter = MAKESURE(CfgGetInt(f, "Jitter"), 0, HUB_ACCESSLIST_JITTER_MAX);
	a.Loss = MAKESURE(CfgGetInt(f, "Loss"), 0, HUB_ACCESSLIST_LOSS_MAX);

	CfgGetStr(f, "RedirectUrl", a.RedirectUrl, sizeof(a.RedirectUrl));

	AddAccessList(h, &a);
}

// Write the access list
void SiWriteHubAccessLists(FOLDER *f, HUB *h)
{
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	LockList(h->AccessList);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(h->AccessList);i++)
		{
			ACCESS *a = LIST_DATA(h->AccessList, i);
			char name[MAX_SIZE];
			ToStr(name, a->Id);
			SiWriteHubAccessCfg(CfgCreateFolder(f, name), a);
		}
	}
	UnlockList(h->AccessList);
}

// Read the access list
void SiLoadHubAccessLists(HUB *h, FOLDER *f)
{
	TOKEN_LIST *t;
	UINT i;
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	t = CfgEnumFolderToTokenList(f);

	for (i = 0;i < t->NumTokens;i++)
	{
		char *name = t->Token[i];
		SiLoadHubAccessCfg(h, CfgGetFolder(f, name));
	}

	FreeToken(t);
}

// Read the HUB_OPTION
void SiLoadHubOptionCfg(FOLDER *f, HUB_OPTION *o)
{
	char tmp[MAX_SIZE];
	// Validate arguments
	if (f == NULL || o == NULL)
	{
		return;
	}

	o->DefaultGateway = CfgGetIp32(f, "DefaultGateway");
	o->DefaultSubnet = CfgGetIp32(f, "DefaultSubnet");
	o->MaxSession = CfgGetInt(f, "MaxSession");
	o->NoArpPolling = CfgGetBool(f, "NoArpPolling");
	o->NoIPv6AddrPolling = CfgGetBool(f, "NoIPv6AddrPolling");
	o->NoIpTable = CfgGetBool(f, "NoIpTable");
	o->NoEnum = CfgGetBool(f, "NoEnum");
	o->FilterPPPoE = CfgGetBool(f, "FilterPPPoE");
	o->FilterOSPF = CfgGetBool(f, "FilterOSPF");
	o->FilterIPv4 = CfgGetBool(f, "FilterIPv4");
	o->FilterIPv6 = CfgGetBool(f, "FilterIPv6");
	o->FilterNonIP = CfgGetBool(f, "FilterNonIP");
	o->FilterBPDU = CfgGetBool(f, "FilterBPDU");
	o->NoIPv4PacketLog = CfgGetBool(f, "NoIPv4PacketLog");
	o->NoIPv6PacketLog = CfgGetBool(f, "NoIPv6PacketLog");
	o->NoIPv6DefaultRouterInRAWhenIPv6 = CfgGetBool(f, "NoIPv6DefaultRouterInRAWhenIPv6");
	o->DisableIPParsing = CfgGetBool(f, "DisableIPParsing");
	o->YieldAfterStorePacket = CfgGetBool(f, "YieldAfterStorePacket");
	o->NoSpinLockForPacketDelay = CfgGetBool(f, "NoSpinLockForPacketDelay");
	o->BroadcastStormDetectionThreshold = CfgGetInt(f, "BroadcastStormDetectionThreshold");
	o->ClientMinimumRequiredBuild = CfgGetInt(f, "ClientMinimumRequiredBuild");
	o->RequiredClientId = CfgGetInt(f, "RequiredClientId");
	o->NoManageVlanId = CfgGetBool(f, "NoManageVlanId");
	o->VlanTypeId = 0;
	if (CfgGetStr(f, "VlanTypeId", tmp, sizeof(tmp)))
	{
		o->VlanTypeId = HexToInt(tmp);
	}
	if (o->VlanTypeId == 0)
	{
		o->VlanTypeId = MAC_PROTO_TAGVLAN;
	}
	o->FixForDLinkBPDU = CfgGetBool(f, "FixForDLinkBPDU");
	o->BroadcastLimiterStrictMode = CfgGetBool(f, "BroadcastLimiterStrictMode");
	o->MaxLoggedPacketsPerMinute = CfgGetInt(f, "MaxLoggedPacketsPerMinute");
	if (CfgIsItem(f, "FloodingSendQueueBufferQuota"))
	{
		o->FloodingSendQueueBufferQuota = CfgGetInt(f, "FloodingSendQueueBufferQuota");
	}
	else
	{
		o->FloodingSendQueueBufferQuota = DEFAULT_FLOODING_QUEUE_LENGTH;
	}
	o->DoNotSaveHeavySecurityLogs = CfgGetBool(f, "DoNotSaveHeavySecurityLogs");

	if (CfgIsItem(f, "DropBroadcastsInPrivacyFilterMode"))
	{
		o->DropBroadcastsInPrivacyFilterMode = CfgGetBool(f, "DropBroadcastsInPrivacyFilterMode");
	}
	else
	{
		o->DropBroadcastsInPrivacyFilterMode = true;
	}

	if (CfgIsItem(f, "DropArpInPrivacyFilterMode"))
	{
		o->DropArpInPrivacyFilterMode = CfgGetBool(f, "DropArpInPrivacyFilterMode");
	}
	else
	{
		o->DropArpInPrivacyFilterMode = true;
	}

	if (CfgIsItem(f, "AllowSameUserInPrivacyFilterMode"))
        {
                o->AllowSameUserInPrivacyFilterMode = CfgGetBool(f, "AllowSameUserInPrivacyFilterMode");
        }
        else
        {
                o->AllowSameUserInPrivacyFilterMode = false;
        }


	o->NoLookBPDUBridgeId = CfgGetBool(f, "NoLookBPDUBridgeId");
	o->AdjustTcpMssValue = CfgGetInt(f, "AdjustTcpMssValue");
	o->DisableAdjustTcpMss = CfgGetBool(f, "DisableAdjustTcpMss");
	if (CfgIsItem(f, "NoDhcpPacketLogOutsideHub"))
	{
		o->NoDhcpPacketLogOutsideHub = CfgGetBool(f, "NoDhcpPacketLogOutsideHub");
	}
	else
	{
		o->NoDhcpPacketLogOutsideHub = true;
	}
	o->DisableHttpParsing = CfgGetBool(f, "DisableHttpParsing");
	o->DisableUdpAcceleration = CfgGetBool(f, "DisableUdpAcceleration");
	o->DisableUdpFilterForLocalBridgeNic = CfgGetBool(f, "DisableUdpFilterForLocalBridgeNic");
	o->ApplyIPv4AccessListOnArpPacket = CfgGetBool(f, "ApplyIPv4AccessListOnArpPacket");
	if (CfgIsItem(f, "RemoveDefGwOnDhcpForLocalhost"))
	{
		o->RemoveDefGwOnDhcpForLocalhost = CfgGetBool(f, "RemoveDefGwOnDhcpForLocalhost");
	}
	else
	{
		o->RemoveDefGwOnDhcpForLocalhost = true;
	}
	o->SecureNAT_MaxTcpSessionsPerIp = CfgGetInt(f, "SecureNAT_MaxTcpSessionsPerIp");
	o->SecureNAT_MaxTcpSynSentPerIp = CfgGetInt(f, "SecureNAT_MaxTcpSynSentPerIp");
	o->SecureNAT_MaxUdpSessionsPerIp = CfgGetInt(f, "SecureNAT_MaxUdpSessionsPerIp");
	o->SecureNAT_MaxDnsSessionsPerIp = CfgGetInt(f, "SecureNAT_MaxDnsSessionsPerIp");
	o->SecureNAT_MaxIcmpSessionsPerIp = CfgGetInt(f, "SecureNAT_MaxIcmpSessionsPerIp");
	o->AccessListIncludeFileCacheLifetime = CfgGetInt(f, "AccessListIncludeFileCacheLifetime");

	if (o->AccessListIncludeFileCacheLifetime == 0)
	{
		o->AccessListIncludeFileCacheLifetime = ACCESS_LIST_INCLUDE_FILE_CACHE_LIFETIME;
	}

	o->DisableKernelModeSecureNAT = CfgGetBool(f, "DisableKernelModeSecureNAT");
	o->DisableIpRawModeSecureNAT = CfgGetBool(f, "DisableIpRawModeSecureNAT");
	o->DisableUserModeSecureNAT = CfgGetBool(f, "DisableUserModeSecureNAT");
	o->DisableCheckMacOnLocalBridge = CfgGetBool(f, "DisableCheckMacOnLocalBridge");
	o->DisableCorrectIpOffloadChecksum = CfgGetBool(f, "DisableCorrectIpOffloadChecksum");
	o->SuppressClientUpdateNotification = CfgGetBool(f, "SuppressClientUpdateNotification");
	o->AssignVLanIdByRadiusAttribute = CfgGetBool(f, "AssignVLanIdByRadiusAttribute");
	o->DenyAllRadiusLoginWithNoVlanAssign = CfgGetBool(f, "DenyAllRadiusLoginWithNoVlanAssign");
	o->SecureNAT_RandomizeAssignIp = CfgGetBool(f, "SecureNAT_RandomizeAssignIp");
	o->DetectDormantSessionInterval = CfgGetInt(f, "DetectDormantSessionInterval");
	o->NoPhysicalIPOnPacketLog = CfgGetBool(f, "NoPhysicalIPOnPacketLog");
	o->UseHubNameAsDhcpUserClassOption = CfgGetBool(f, "UseHubNameAsDhcpUserClassOption");
	o->UseHubNameAsRadiusNasId = CfgGetBool(f, "UseHubNameAsRadiusNasId");
	o->AllowEapMatchUserByCert = CfgGetBool(f, "AllowEapMatchUserByCert");

	// Enabled by default
	if (CfgIsItem(f, "ManageOnlyPrivateIP"))
	{
		o->ManageOnlyPrivateIP = CfgGetBool(f, "ManageOnlyPrivateIP");
	}
	else
	{
		o->ManageOnlyPrivateIP = true;
	}
	if (CfgIsItem(f, "ManageOnlyLocalUnicastIPv6"))
	{
		o->ManageOnlyLocalUnicastIPv6 = CfgGetBool(f, "ManageOnlyLocalUnicastIPv6");
	}
	else
	{
		o->ManageOnlyLocalUnicastIPv6 = true;
	}
	if (CfgIsItem(f, "NoMacAddressLog"))
	{
		o->NoMacAddressLog = CfgGetBool(f, "NoMacAddressLog");
	}
	else
	{
		o->NoMacAddressLog = true;
	}
}

// Write the HUB_OPTION
void SiWriteHubOptionCfg(FOLDER *f, HUB_OPTION *o)
{
	char tmp[MAX_SIZE];
	// Validate arguments
	if (f == NULL || o == NULL)
	{
		return;
	}

	CfgAddIp32(f, "DefaultGateway", o->DefaultGateway);
	CfgAddIp32(f, "DefaultSubnet", o->DefaultSubnet);
	CfgAddInt(f, "MaxSession", o->MaxSession);
	CfgAddBool(f, "NoArpPolling", o->NoArpPolling);
	CfgAddBool(f, "NoIPv6AddrPolling", o->NoIPv6AddrPolling);
	CfgAddBool(f, "NoIpTable", o->NoIpTable);
	CfgAddBool(f, "NoEnum", o->NoEnum);
	CfgAddBool(f, "FilterPPPoE", o->FilterPPPoE);
	CfgAddBool(f, "FilterOSPF", o->FilterOSPF);
	CfgAddBool(f, "FilterIPv4", o->FilterIPv4);
	CfgAddBool(f, "FilterIPv6", o->FilterIPv6);
	CfgAddBool(f, "FilterNonIP", o->FilterNonIP);
	CfgAddBool(f, "NoIPv4PacketLog", o->NoIPv4PacketLog);
	CfgAddBool(f, "NoIPv6PacketLog", o->NoIPv6PacketLog);
	CfgAddBool(f, "FilterBPDU", o->FilterBPDU);
	CfgAddBool(f, "NoIPv6DefaultRouterInRAWhenIPv6", o->NoIPv6DefaultRouterInRAWhenIPv6);
	CfgAddBool(f, "NoMacAddressLog", o->NoMacAddressLog);
	CfgAddBool(f, "ManageOnlyPrivateIP", o->ManageOnlyPrivateIP);
	CfgAddBool(f, "ManageOnlyLocalUnicastIPv6", o->ManageOnlyLocalUnicastIPv6);
	CfgAddBool(f, "DisableIPParsing", o->DisableIPParsing);
	CfgAddBool(f, "YieldAfterStorePacket", o->YieldAfterStorePacket);
	CfgAddBool(f, "NoSpinLockForPacketDelay", o->NoSpinLockForPacketDelay);
	CfgAddInt(f, "BroadcastStormDetectionThreshold", o->BroadcastStormDetectionThreshold);
	CfgAddInt(f, "ClientMinimumRequiredBuild", o->ClientMinimumRequiredBuild);
	CfgAddInt(f, "RequiredClientId", o->RequiredClientId);
	CfgAddBool(f, "NoManageVlanId", o->NoManageVlanId);
	Format(tmp, sizeof(tmp), "0x%x", o->VlanTypeId);
	CfgAddStr(f, "VlanTypeId", tmp);
	if (o->FixForDLinkBPDU)
	{
		CfgAddBool(f, "FixForDLinkBPDU", o->FixForDLinkBPDU);
	}
	CfgAddBool(f, "BroadcastLimiterStrictMode", o->BroadcastLimiterStrictMode);
	CfgAddInt(f, "MaxLoggedPacketsPerMinute", o->MaxLoggedPacketsPerMinute);
	CfgAddInt(f, "FloodingSendQueueBufferQuota", o->FloodingSendQueueBufferQuota);
	CfgAddBool(f, "DoNotSaveHeavySecurityLogs", o->DoNotSaveHeavySecurityLogs);
	CfgAddBool(f, "DropBroadcastsInPrivacyFilterMode", o->DropBroadcastsInPrivacyFilterMode);
	CfgAddBool(f, "DropArpInPrivacyFilterMode", o->DropArpInPrivacyFilterMode);
	CfgAddBool(f, "AllowSameUserInPrivacyFilterMode", o->AllowSameUserInPrivacyFilterMode);
	CfgAddBool(f, "SuppressClientUpdateNotification", o->SuppressClientUpdateNotification);
	CfgAddBool(f, "AssignVLanIdByRadiusAttribute", o->AssignVLanIdByRadiusAttribute);
	CfgAddBool(f, "DenyAllRadiusLoginWithNoVlanAssign", o->DenyAllRadiusLoginWithNoVlanAssign);
	CfgAddBool(f, "SecureNAT_RandomizeAssignIp", o->SecureNAT_RandomizeAssignIp);
	CfgAddBool(f, "NoPhysicalIPOnPacketLog", o->NoPhysicalIPOnPacketLog);
	CfgAddInt(f, "DetectDormantSessionInterval", o->DetectDormantSessionInterval);
	CfgAddBool(f, "NoLookBPDUBridgeId", o->NoLookBPDUBridgeId);
	CfgAddInt(f, "AdjustTcpMssValue", o->AdjustTcpMssValue);
	CfgAddBool(f, "DisableAdjustTcpMss", o->DisableAdjustTcpMss);
	CfgAddBool(f, "NoDhcpPacketLogOutsideHub", o->NoDhcpPacketLogOutsideHub);
	CfgAddBool(f, "DisableHttpParsing", o->DisableHttpParsing);
	CfgAddBool(f, "DisableUdpAcceleration", o->DisableUdpAcceleration);
	CfgAddBool(f, "DisableUdpFilterForLocalBridgeNic", o->DisableUdpFilterForLocalBridgeNic);
	CfgAddBool(f, "ApplyIPv4AccessListOnArpPacket", o->ApplyIPv4AccessListOnArpPacket);
	CfgAddBool(f, "RemoveDefGwOnDhcpForLocalhost", o->RemoveDefGwOnDhcpForLocalhost);
	CfgAddInt(f, "SecureNAT_MaxTcpSessionsPerIp", o->SecureNAT_MaxTcpSessionsPerIp);
	CfgAddInt(f, "SecureNAT_MaxTcpSynSentPerIp", o->SecureNAT_MaxTcpSynSentPerIp);
	CfgAddInt(f, "SecureNAT_MaxUdpSessionsPerIp", o->SecureNAT_MaxUdpSessionsPerIp);
	CfgAddInt(f, "SecureNAT_MaxDnsSessionsPerIp", o->SecureNAT_MaxDnsSessionsPerIp);
	CfgAddInt(f, "SecureNAT_MaxIcmpSessionsPerIp", o->SecureNAT_MaxIcmpSessionsPerIp);
	CfgAddInt(f, "AccessListIncludeFileCacheLifetime", o->AccessListIncludeFileCacheLifetime);
	CfgAddBool(f, "DisableKernelModeSecureNAT", o->DisableKernelModeSecureNAT);
	CfgAddBool(f, "DisableIpRawModeSecureNAT", o->DisableIpRawModeSecureNAT);
	CfgAddBool(f, "DisableUserModeSecureNAT", o->DisableUserModeSecureNAT);
	CfgAddBool(f, "DisableCheckMacOnLocalBridge", o->DisableCheckMacOnLocalBridge);
	CfgAddBool(f, "DisableCorrectIpOffloadChecksum", o->DisableCorrectIpOffloadChecksum);
	CfgAddBool(f, "UseHubNameAsDhcpUserClassOption", o->UseHubNameAsDhcpUserClassOption);
	CfgAddBool(f, "UseHubNameAsRadiusNasId", o->UseHubNameAsRadiusNasId);
	CfgAddBool(f, "AllowEapMatchUserByCert", o->AllowEapMatchUserByCert);
}

// Write the user
void SiWriteUserCfg(FOLDER *f, USER *u)
{
	BUF *b;
	AUTHPASSWORD *password;
	AUTHRADIUS *radius;
	AUTHNT *nt;
	AUTHUSERCERT *usercert;
	AUTHROOTCERT *rootcert;
	// Validate arguments
	if (f == NULL || u == NULL)
	{
		return;
	}

	Lock(u->lock);
	{
		CfgAddUniStr(f, "RealName", u->RealName);
		CfgAddUniStr(f, "Note", u->Note);
		if (u->Group != NULL)
		{
			CfgAddStr(f, "GroupName", u->GroupName);
		}
		CfgAddInt64(f, "CreatedTime", u->CreatedTime);
		CfgAddInt64(f, "UpdatedTime", u->UpdatedTime);
		CfgAddInt64(f, "ExpireTime", u->ExpireTime);
		CfgAddInt64(f, "LastLoginTime", u->LastLoginTime);
		CfgAddInt(f, "NumLogin", u->NumLogin);
		if (u->Policy != NULL)
		{
			SiWritePolicyCfg(CfgCreateFolder(f, "Policy"), u->Policy, false);
		}
		SiWriteTraffic(f, "Traffic", u->Traffic);

		CfgAddInt(f, "AuthType", u->AuthType);
		if (u->AuthData != NULL)
		{
			switch (u->AuthType)
			{
			case AUTHTYPE_ANONYMOUS:
				break;

			case AUTHTYPE_PASSWORD:
				password = (AUTHPASSWORD *)u->AuthData;
				CfgAddByte(f, "AuthPassword", password->HashedKey, sizeof(password->HashedKey));

				if (IsZero(password->NtLmSecureHash, sizeof(password->NtLmSecureHash)) == false)
				{
					CfgAddByte(f, "AuthNtLmSecureHash", password->NtLmSecureHash, sizeof(password->NtLmSecureHash));
				}
				break;

			case AUTHTYPE_NT:
				nt = (AUTHNT *)u->AuthData;
				CfgAddUniStr(f, "AuthNtUserName", nt->NtUsername);
				break;

			case AUTHTYPE_RADIUS:
				radius = (AUTHRADIUS *)u->AuthData;
				CfgAddUniStr(f, "AuthRadiusUsername", radius->RadiusUsername);
				break;

			case AUTHTYPE_USERCERT:
				usercert = (AUTHUSERCERT *)u->AuthData;
				b = XToBuf(usercert->UserX, false);
				if (b != NULL)
				{
					CfgAddBuf(f, "AuthUserCert", b);
					FreeBuf(b);
				}
				break;

			case AUTHTYPE_ROOTCERT:
				rootcert = (AUTHROOTCERT *)u->AuthData;
				if (rootcert->Serial != NULL && rootcert->Serial->size >= 1)
				{
					CfgAddByte(f, "AuthSerial", rootcert->Serial->data, rootcert->Serial->size);
				}
				if (rootcert->CommonName != NULL && UniIsEmptyStr(rootcert->CommonName) == false)
				{
					CfgAddUniStr(f, "AuthCommonName", rootcert->CommonName);
				}
				break;
			}
		}
	}
	Unlock(u->lock);
}

// Read an user
void SiLoadUserCfg(HUB *h, FOLDER *f)
{
	char *username;
	wchar_t realname[MAX_SIZE];
	wchar_t note[MAX_SIZE];
	char groupname[MAX_SIZE];
	FOLDER *pf;
	UINT64 created_time;
	UINT64 updated_time;
	UINT64 expire_time;
	UINT64 last_login_time;
	UINT num_login;
	POLICY p;
	TRAFFIC t;
	BUF *b;
	UINT authtype;
	void *authdata;
	X_SERIAL *serial = NULL;
	wchar_t common_name[MAX_SIZE];
	UCHAR hashed_password[SHA1_SIZE];
	UCHAR md4_password[MD5_SIZE];
	wchar_t tmp[MAX_SIZE];
	USER *u;
	USERGROUP *g;
	// Validate arguments
	if (h == NULL || f == NULL)
	{
		return;
	}

	username = f->Name;
	CfgGetUniStr(f, "RealName", realname, sizeof(realname));
	CfgGetUniStr(f, "Note", note, sizeof(note));
	CfgGetStr(f, "GroupName", groupname, sizeof(groupname));

	created_time = CfgGetInt64(f, "CreatedTime");
	updated_time = CfgGetInt64(f, "UpdatedTime");
	expire_time = CfgGetInt64(f, "ExpireTime");
	last_login_time = CfgGetInt64(f, "LastLoginTime");
	num_login = CfgGetInt(f, "NumLogin");
	pf = CfgGetFolder(f, "Policy");
	if (pf != NULL)
	{
		SiLoadPolicyCfg(&p, pf);
	}
	SiLoadTraffic(f, "Traffic", &t);

	authtype = CfgGetInt(f, "AuthType");
	authdata = NULL;

	switch (authtype)
	{
	case AUTHTYPE_PASSWORD:
		Zero(hashed_password, sizeof(hashed_password));
		Zero(md4_password, sizeof(md4_password));
		CfgGetByte(f, "AuthPassword", hashed_password, sizeof(hashed_password));
		CfgGetByte(f, "AuthNtLmSecureHash", md4_password, sizeof(md4_password));
		authdata = NewPasswordAuthDataRaw(hashed_password, md4_password);
		break;

	case AUTHTYPE_NT:
		if (CfgGetUniStr(f, "AuthNtUserName", tmp, sizeof(tmp)))
		{
			authdata = NewNTAuthData(tmp);
		}
		else
		{
			authdata = NewNTAuthData(NULL);
		}
		break;

	case AUTHTYPE_RADIUS:
		if (CfgGetUniStr(f, "AuthRadiusUsername", tmp, sizeof(tmp)))
		{
			authdata = NewRadiusAuthData(tmp);
		}
		else
		{
			authdata = NewRadiusAuthData(NULL);
		}
		break;

	case AUTHTYPE_USERCERT:
		b = CfgGetBuf(f, "AuthUserCert");
		if (b != NULL)
		{
			X *x = BufToX(b, false);
			if (x != NULL)
			{
				authdata = NewUserCertAuthData(x);
				FreeX(x);
			}
			FreeBuf(b);
		}
		break;

	case AUTHTYPE_ROOTCERT:
		b = CfgGetBuf(f, "AuthSerial");
		if (b != NULL)
		{
			serial = NewXSerial(b->Buf, b->Size);
			FreeBuf(b);
		}
		CfgGetUniStr(f, "AuthCommonName", common_name, sizeof(common_name));
		authdata = NewRootCertAuthData(serial, common_name);
		break;
	}

	// Add an user
	AcLock(h);
	{
		if (StrLen(groupname) > 0)
		{
			g = AcGetGroup(h, groupname);
		}
		else
		{
			g = NULL;
		}

		u = NewUser(username, realname, note, authtype, authdata);
		if (u != NULL)
		{
			if (g != NULL)
			{
				JoinUserToGroup(u, g);
			}

			SetUserTraffic(u, &t);

			if (pf != NULL)
			{
				SetUserPolicy(u, &p);
			}

			Lock(u->lock);
			{
				u->CreatedTime = created_time;
				u->UpdatedTime = updated_time;
				u->ExpireTime = expire_time;
				u->LastLoginTime = last_login_time;
				u->NumLogin = num_login;
			}
			Unlock(u->lock);

			AcAddUser(h, u);

			ReleaseUser(u);
		}

		if (g != NULL)
		{
			ReleaseGroup(g);
		}
	}
	AcUnlock(h);

	if (serial != NULL)
	{
		FreeXSerial(serial);
	}
}

// Write the user list
void SiWriteUserList(FOLDER *f, LIST *o)
{
	// Validate arguments
	if (f == NULL || o == NULL)
	{
		return;
	}

	LockList(o);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(o);i++)
		{
			USER *u = LIST_DATA(o, i);
			SiWriteUserCfg(CfgCreateFolder(f, u->Name), u);
		}
	}
	UnlockList(o);
}

// Read the user list
void SiLoadUserList(HUB *h, FOLDER *f)
{
	TOKEN_LIST *t;
	UINT i;
	char *name;
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	t = CfgEnumFolderToTokenList(f);

	for (i = 0;i < t->NumTokens;i++)
	{
		FOLDER *ff;
		name = t->Token[i];
		ff = CfgGetFolder(f, name);
		SiLoadUserCfg(h, ff);
	}

	FreeToken(t);
}

// Write the group information
void SiWriteGroupCfg(FOLDER *f, USERGROUP *g)
{
	// Validate arguments
	if (f == NULL || g == NULL)
	{
		return;
	}

	Lock(g->lock);
	{
		CfgAddUniStr(f, "RealName", g->RealName);
		CfgAddUniStr(f, "Note", g->Note);
		if (g->Policy != NULL)
		{
			SiWritePolicyCfg(CfgCreateFolder(f, "Policy"), g->Policy, false);
		}
		SiWriteTraffic(f, "Traffic", g->Traffic);
	}
	Unlock(g->lock);
}

// Read the group information
void SiLoadGroupCfg(HUB *h, FOLDER *f)
{
	wchar_t realname[MAX_SIZE];
	wchar_t note[MAX_SIZE];
	char *name;
	FOLDER *pf;
	POLICY p;
	TRAFFIC t;
	USERGROUP *g;
	// Validate arguments
	if (h == NULL || f == NULL)
	{
		return;
	}

	name = f->Name;

	CfgGetUniStr(f, "RealName", realname, sizeof(realname));
	CfgGetUniStr(f, "Note", note, sizeof(note));

	pf = CfgGetFolder(f, "Policy");
	if (pf != NULL)
	{
		SiLoadPolicyCfg(&p, pf);
	}

	SiLoadTraffic(f, "Traffic", &t);

	g = NewGroup(name, realname, note);
	if (g == NULL)
	{
		return;
	}

	if (pf != NULL)
	{
		SetGroupPolicy(g, &p);
	}

	SetGroupTraffic(g, &t);

	AcLock(h);
	{
		AcAddGroup(h, g);
	}
	AcUnlock(h);

	ReleaseGroup(g);
}

// Write the group list
void SiWriteGroupList(FOLDER *f, LIST *o)
{
	// Validate arguments
	if (f == NULL || o == NULL)
	{
		return;
	}

	LockList(o);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(o);i++)
		{
			USERGROUP *g = LIST_DATA(o, i);
			SiWriteGroupCfg(CfgCreateFolder(f, g->Name), g);
		}
	}
	UnlockList(o);
}

// Read the group List
void SiLoadGroupList(HUB *h, FOLDER *f)
{
	TOKEN_LIST *t;
	UINT i;
	char *name;
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	t = CfgEnumFolderToTokenList(f);

	for (i = 0;i < t->NumTokens;i++)
	{
		name = t->Token[i];
		SiLoadGroupCfg(h, CfgGetFolder(f, name));
	}

	FreeToken(t);
}

// Write the AC list
void SiWriteAcList(FOLDER *f, LIST *o)
{
	// Validate arguments
	if (f == NULL || o == NULL)
	{
		return;
	}

	LockList(o);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(o);i++)
		{
			char name[MAX_SIZE];
			AC *ac = LIST_DATA(o, i);
			FOLDER *ff;

			Format(name, sizeof(name), "Acl%u", i + 1);

			ff = CfgCreateFolder(f, name);

			CfgAddBool(ff, "Deny", ac->Deny);
			CfgAddInt(ff, "Priority", ac->Priority);
			CfgAddIp(ff, "IpAddress", &ac->IpAddress);

			if (ac->Masked)
			{
				CfgAddIp(ff, "NetMask", &ac->SubnetMask);
			}
		}
	}
	UnlockList(o);
}

// Read the AC list
void SiLoadAcList(LIST *o, FOLDER *f)
{
	// Validate arguments
	if (o == NULL || f == NULL)
	{
		return;
	}

	LockList(o);
	{
		TOKEN_LIST *t = CfgEnumFolderToTokenList(f);

		if (t != NULL)
		{
			UINT i;

			for (i = 0;i < t->NumTokens;i++)
			{
				FOLDER *ff = CfgGetFolder(f, t->Token[i]);

				if (ff != NULL)
				{
					AC ac;

					Zero(&ac, sizeof(ac));
					ac.Deny = CfgGetBool(ff, "Deny");
					ac.Priority = CfgGetInt(ff, "Priority");
					CfgGetIp(ff, "IpAddress", &ac.IpAddress);

					if (CfgGetIp(ff, "NetMask", &ac.SubnetMask))
					{
						ac.Masked = true;
					}

					AddAc(o, &ac);
				}
			}

			FreeToken(t);
		}
	}
	UnlockList(o);
}

// Write the certificate revocation list
void SiWriteCrlList(FOLDER *f, LIST *o)
{
	// Validate arguments
	if (f == NULL || o == NULL)
	{
		return;
	}

	LockList(o);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(o);i++)
		{
			char name[MAX_SIZE];
			CRL *crl = LIST_DATA(o, i);
			FOLDER *ff;
			NAME *n;

			Format(name, sizeof(name), "Crl%u", i);

			ff = CfgCreateFolder(f, name);
			n = crl->Name;

			if (UniIsEmptyStr(n->CommonName) == false)
			{
				CfgAddUniStr(ff, "CommonName", n->CommonName);
			}

			if (UniIsEmptyStr(n->Organization) == false)
			{
				CfgAddUniStr(ff, "Organization", n->Organization);
			}

			if (UniIsEmptyStr(n->Unit) == false)
			{
				CfgAddUniStr(ff, "Unit", n->Unit);
			}

			if (UniIsEmptyStr(n->Country) == false)
			{
				CfgAddUniStr(ff, "Country", n->Country);
			}

			if (UniIsEmptyStr(n->State) == false)
			{
				CfgAddUniStr(ff, "State", n->State);
			}

			if (UniIsEmptyStr(n->Local) == false)
			{
				CfgAddUniStr(ff, "Local", n->Local);
			}

			if (IsZero(crl->DigestMD5, MD5_SIZE) == false)
			{
				char tmp[MAX_SIZE];

				BinToStr(tmp, sizeof(tmp), crl->DigestMD5, MD5_SIZE);
				CfgAddStr(ff, "DigestMD5", tmp);
			}

			if (IsZero(crl->DigestSHA1, SHA1_SIZE) == false)
			{
				char tmp[MAX_SIZE];

				BinToStr(tmp, sizeof(tmp), crl->DigestSHA1, SHA1_SIZE);
				CfgAddStr(ff, "DigestSHA1", tmp);
			}

			if (crl->Serial != NULL)
			{
				char tmp[MAX_SIZE];

				BinToStr(tmp, sizeof(tmp), crl->Serial->data, crl->Serial->size);
				CfgAddStr(ff, "Serial", tmp);
			}
		}
	}
	UnlockList(o);
}

// Read the certificate revocation list
void SiLoadCrlList(LIST *o, FOLDER *f)
{
	// Validate arguments
	if (o == NULL || f == NULL)
	{
		return;
	}

	LockList(o);
	{
		UINT i;
		TOKEN_LIST *t;

		t = CfgEnumFolderToTokenList(f);

		for (i = 0;i < t->NumTokens;i++)
		{
			CRL *crl;
			FOLDER *ff = CfgGetFolder(f, t->Token[i]);
			wchar_t cn[MAX_SIZE], org[MAX_SIZE], u[MAX_SIZE], c[MAX_SIZE],
				st[MAX_SIZE], l[MAX_SIZE];
			char tmp[MAX_SIZE];

			if (ff != NULL)
			{
				BUF *b;

				crl = ZeroMalloc(sizeof(CRL));

				CfgGetUniStr(ff, "CommonName", cn, sizeof(cn));
				CfgGetUniStr(ff, "Organization", org, sizeof(org));
				CfgGetUniStr(ff, "Unit", u, sizeof(u));
				CfgGetUniStr(ff, "Country", c, sizeof(c));
				CfgGetUniStr(ff, "State", st, sizeof(st));
				CfgGetUniStr(ff, "Local", l, sizeof(l));

				crl->Name = NewName(cn, org, u, c, st, l);

				if (CfgGetStr(ff, "Serial", tmp, sizeof(tmp)))
				{
					b = StrToBin(tmp);

					if (b != NULL)
					{
						if (b->Size >= 1)
						{
							crl->Serial = NewXSerial(b->Buf, b->Size);
						}

						FreeBuf(b);
					}
				}

				if (CfgGetStr(ff, "DigestMD5", tmp, sizeof(tmp)))
				{
					b = StrToBin(tmp);

					if (b != NULL)
					{
						if (b->Size == MD5_SIZE)
						{
							Copy(crl->DigestMD5, b->Buf, MD5_SIZE);
						}

						FreeBuf(b);
					}
				}

				if (CfgGetStr(ff, "DigestSHA1", tmp, sizeof(tmp)))
				{
					b = StrToBin(tmp);

					if (b != NULL)
					{
						if (b->Size == SHA1_SIZE)
						{
							Copy(crl->DigestSHA1, b->Buf, SHA1_SIZE);
						}

						FreeBuf(b);
					}
				}

				Insert(o, crl);
			}
		}

		FreeToken(t);
	}
	UnlockList(o);
}

// Write the certificates list
void SiWriteCertList(FOLDER *f, LIST *o)
{
	// Validate arguments
	if (f == NULL || o == NULL)
	{
		return;
	}

	LockList(o);
	{
		UINT i;
		X *x;
		for (i = 0;i < LIST_NUM(o);i++)
		{
			char name[MAX_SIZE];
			BUF *b;
			x = LIST_DATA(o, i);
			Format(name, sizeof(name), "Cert%u", i);
			b = XToBuf(x, false);
			if (b != NULL)
			{
				CfgAddBuf(CfgCreateFolder(f, name), "X509", b);
				FreeBuf(b);
			}
		}
	}
	UnlockList(o);
}

// Read the certificates list
void SiLoadCertList(LIST *o, FOLDER *f)
{
	// Validate arguments
	if (o == NULL || f == NULL)
	{
		return;
	}

	LockList(o);
	{
		UINT i;
		TOKEN_LIST *t;

		t = CfgEnumFolderToTokenList(f);

		for (i = 0;i < t->NumTokens;i++)
		{
			FOLDER *ff = CfgGetFolder(f, t->Token[i]);
			BUF *b;

			b = CfgGetBuf(ff, "X509");
			if (b != NULL)
			{
				X *x = BufToX(b, false);
				if (x != NULL)
				{
					Insert(o, x);
				}
				FreeBuf(b);
			}
		}

		FreeToken(t);
	}
	UnlockList(o);
}

// Write the database
void SiWriteHubDb(FOLDER *f, HUBDB *db, bool no_save_ac_list)
{
	// Validate arguments
	if (f == NULL || db == NULL)
	{
		return;
	}

	SiWriteUserList(CfgCreateFolder(f, "UserList"), db->UserList);
	SiWriteGroupList(CfgCreateFolder(f, "GroupList"), db->GroupList);
	SiWriteCertList(CfgCreateFolder(f, "CertList"), db->RootCertList);
	SiWriteCrlList(CfgCreateFolder(f, "CrlList"), db->CrlList);

	if (no_save_ac_list == false)
	{
		SiWriteAcList(CfgCreateFolder(f, "IPAccessControlList"), db->AcList);
	}
}

// Read the database
void SiLoadHubDb(HUB *h, FOLDER *f)
{
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	SiLoadGroupList(h, CfgGetFolder(f, "GroupList"));
	SiLoadUserList(h, CfgGetFolder(f, "UserList"));

	if (h->HubDb != NULL)
	{
		SiLoadCertList(h->HubDb->RootCertList, CfgGetFolder(f, "CertList"));
		SiLoadCrlList(h->HubDb->CrlList, CfgGetFolder(f, "CrlList"));
		SiLoadAcList(h->HubDb->AcList, CfgGetFolder(f, "IPAccessControlList"));
	}
}

// Write the Virtual HUB setting
void SiWriteHubCfg(FOLDER *f, HUB *h)
{
	// Validate arguments
	if (f == NULL || h == NULL)
	{
		return;
	}

	// Radius server name
	Lock(h->RadiusOptionLock);
	{
		if (h->RadiusServerName != NULL)
		{
			CfgAddStr(f, "RadiusServerName", h->RadiusServerName);
			CfgAddBuf(f, "RadiusSecret", h->RadiusSecret);
		}
		CfgAddInt(f, "RadiusServerPort", h->RadiusServerPort);
		CfgAddInt(f, "RadiusRetryInterval", h->RadiusRetryInterval);
		CfgAddStr(f, "RadiusSuffixFilter", h->RadiusSuffixFilter);
		CfgAddStr(f, "RadiusRealm", h->RadiusRealm);

		CfgAddBool(f, "RadiusConvertAllMsChapv2AuthRequestToEap", h->RadiusConvertAllMsChapv2AuthRequestToEap);
		CfgAddBool(f, "RadiusUsePeapInsteadOfEap", h->RadiusUsePeapInsteadOfEap);
	}
	Unlock(h->RadiusOptionLock);

	// Password
	CfgAddByte(f, "HashedPassword", h->HashedPassword, sizeof(h->HashedPassword));
	CfgAddByte(f, "SecurePassword", h->SecurePassword, sizeof(h->SecurePassword));

	// Online / Offline flag
	if (h->Cedar->Bridge == false)
	{
		CfgAddBool(f, "Online", (h->Offline && (h->HubIsOnlineButHalting == false)) ? false : true);
	}

	// Traffic information
	SiWriteTraffic(f, "Traffic", h->Traffic);

	// HUB options
	SiWriteHubOptionCfg(CfgCreateFolder(f, "Option"), h->Option);

	// Message
	{
		FOLDER *folder = CfgCreateFolder(f, "Message");

		if (IsEmptyUniStr(h->Msg) == false)
		{
			CfgAddUniStr(folder, "MessageText", h->Msg);
		}
	}

	// HUB_LOG
	SiWriteHubLogCfg(CfgCreateFolder(f, "LogSetting"), &h->LogSetting);

	if (h->Type == HUB_TYPE_STANDALONE)
	{
		// Link list
		SiWriteHubLinks(CfgCreateFolder(f, "CascadeList"), h);
	}

	if (h->Type != HUB_TYPE_FARM_STATIC)
	{
		if (GetServerCapsBool(h->Cedar->Server, "b_support_securenat"))
		{
			// SecureNAT
			SiWriteSecureNAT(h, CfgCreateFolder(f, "SecureNAT"));
		}
	}

	// Access list
	SiWriteHubAccessLists(CfgCreateFolder(f, "AccessList"), h);

	// Administration options
	SiWriteHubAdminOptions(CfgCreateFolder(f, "AdminOption"), h);

	// Type of HUB
	CfgAddInt(f, "Type", h->Type);

	// Database
	if (h->Cedar->Bridge == false)
	{
		SiWriteHubDb(CfgCreateFolder(f, "SecurityAccountDatabase"), h->HubDb,
			false
			);
	}

	// Usage status
	CfgAddInt64(f, "LastCommTime", h->LastCommTime);
	CfgAddInt64(f, "LastLoginTime", h->LastLoginTime);
	CfgAddInt64(f, "CreatedTime", h->CreatedTime);
	CfgAddInt(f, "NumLogin", h->NumLogin);
}

// Read the logging options
void SiLoadHubLogCfg(HUB_LOG *g, FOLDER *f)
{
	// Validate arguments
	if (f == NULL || g == NULL)
	{
		return;
	}

	Zero(g, sizeof(HUB_LOG));
	g->SaveSecurityLog = CfgGetBool(f, "SaveSecurityLog");
	g->SecurityLogSwitchType = CfgGetInt(f, "SecurityLogSwitchType");
	g->SavePacketLog = CfgGetBool(f, "SavePacketLog");
	g->PacketLogSwitchType = CfgGetInt(f, "PacketLogSwitchType");

	g->PacketLogConfig[PACKET_LOG_TCP_CONN] = CfgGetInt(f, "PACKET_LOG_TCP_CONN");
	g->PacketLogConfig[PACKET_LOG_TCP] = CfgGetInt(f, "PACKET_LOG_TCP");
	g->PacketLogConfig[PACKET_LOG_DHCP] = CfgGetInt(f, "PACKET_LOG_DHCP");
	g->PacketLogConfig[PACKET_LOG_UDP] = CfgGetInt(f, "PACKET_LOG_UDP");
	g->PacketLogConfig[PACKET_LOG_ICMP] = CfgGetInt(f, "PACKET_LOG_ICMP");
	g->PacketLogConfig[PACKET_LOG_IP] = CfgGetInt(f, "PACKET_LOG_IP");
	g->PacketLogConfig[PACKET_LOG_ARP] = CfgGetInt(f, "PACKET_LOG_ARP");
	g->PacketLogConfig[PACKET_LOG_ETHERNET] = CfgGetInt(f, "PACKET_LOG_ETHERNET");
}

// Write the logging options
void SiWriteHubLogCfg(FOLDER *f, HUB_LOG *g)
{
	SiWriteHubLogCfgEx(f, g, false);
}
void SiWriteHubLogCfgEx(FOLDER *f, HUB_LOG *g, bool el_mode)
{
	// Validate arguments
	if (f == NULL || g == NULL)
	{
		return;
	}

	if (el_mode == false)
	{
		CfgAddBool(f, "SaveSecurityLog", g->SaveSecurityLog);
		CfgAddInt(f, "SecurityLogSwitchType", g->SecurityLogSwitchType);
		CfgAddBool(f, "SavePacketLog", g->SavePacketLog);
	}

	CfgAddInt(f, "PacketLogSwitchType", g->PacketLogSwitchType);

	CfgAddInt(f, "PACKET_LOG_TCP_CONN", g->PacketLogConfig[PACKET_LOG_TCP_CONN]);
	CfgAddInt(f, "PACKET_LOG_TCP", g->PacketLogConfig[PACKET_LOG_TCP]);
	CfgAddInt(f, "PACKET_LOG_DHCP", g->PacketLogConfig[PACKET_LOG_DHCP]);
	CfgAddInt(f, "PACKET_LOG_UDP", g->PacketLogConfig[PACKET_LOG_UDP]);
	CfgAddInt(f, "PACKET_LOG_ICMP", g->PacketLogConfig[PACKET_LOG_ICMP]);
	CfgAddInt(f, "PACKET_LOG_IP", g->PacketLogConfig[PACKET_LOG_IP]);
	CfgAddInt(f, "PACKET_LOG_ARP", g->PacketLogConfig[PACKET_LOG_ARP]);
	CfgAddInt(f, "PACKET_LOG_ETHERNET", g->PacketLogConfig[PACKET_LOG_ETHERNET]);
}

// Read the Virtual HUB settings
void SiLoadHubCfg(SERVER *s, FOLDER *f, char *name)
{
	HUB *h;
	CEDAR *c;
	HUB_OPTION o;
	bool online;
	UINT hub_old_type = 0;
	// Validate arguments
	if (s == NULL || f == NULL || name == NULL)
	{
		return;
	}

	c = s->Cedar;

	// Get the option
	Zero(&o, sizeof(o));
	SiLoadHubOptionCfg(CfgGetFolder(f, "Option"), &o);

	// Create a HUB
	h = NewHub(c, name, &o);
	if (h != NULL)
	{
		HUB_LOG g;
		// Radius server settings
		Lock(h->RadiusOptionLock);
		{
			char name[MAX_SIZE];
			BUF *secret;
			UINT port;
			UINT interval;

			port = CfgGetInt(f, "RadiusServerPort");
			interval = CfgGetInt(f, "RadiusRetryInterval");

			CfgGetStr(f, "RadiusSuffixFilter", h->RadiusSuffixFilter, sizeof(h->RadiusSuffixFilter));
			CfgGetStr(f, "RadiusRealm", h->RadiusRealm, sizeof(h->RadiusRealm));

			h->RadiusConvertAllMsChapv2AuthRequestToEap = CfgGetBool(f, "RadiusConvertAllMsChapv2AuthRequestToEap");
			h->RadiusUsePeapInsteadOfEap = CfgGetBool(f, "RadiusUsePeapInsteadOfEap");

			if (interval == 0)
			{
				interval = RADIUS_RETRY_INTERVAL;
			}

			if (port != 0 && CfgGetStr(f, "RadiusServerName", name, sizeof(name)))
			{
				secret = CfgGetBuf(f, "RadiusSecret");
				if (secret != NULL)
				{
					char secret_str[MAX_SIZE];
					Zero(secret_str, sizeof(secret_str));
					if (secret->Size < sizeof(secret_str))
					{
						Copy(secret_str, secret->Buf, secret->Size);
					}
					secret_str[sizeof(secret_str) - 1] = 0;
					//SetRadiusServer(h, name, port, secret_str);
					SetRadiusServerEx(h, name, port, secret_str, interval);
					FreeBuf(secret);
				}
			}
		}
		Unlock(h->RadiusOptionLock);

		// Password
		if (CfgGetByte(f, "HashedPassword", h->HashedPassword, sizeof(h->HashedPassword)) != sizeof(h->HashedPassword))
		{
			Sha0(h->HashedPassword, "", 0);
		}
		if (CfgGetByte(f, "SecurePassword", h->SecurePassword, sizeof(h->SecurePassword)) != sizeof(h->SecurePassword))
		{
			HashPassword(h->SecurePassword, ADMINISTRATOR_USERNAME, "");
		}

		// Log Settings
		Zero(&g, sizeof(g));
		SiLoadHubLogCfg(&g, CfgGetFolder(f, "LogSetting"));
		SetHubLogSetting(h, &g);

		// Online / Offline flag
		if (h->Cedar->Bridge == false)
		{
			online = CfgGetBool(f, "Online");
		}
		else
		{
			online = true;
		}

		// Traffic information
		SiLoadTraffic(f, "Traffic", h->Traffic);

		// Access list
		SiLoadHubAccessLists(h, CfgGetFolder(f, "AccessList"));

		// Type of HUB
		hub_old_type = h->Type = CfgGetInt(f, "Type");

		if (s->ServerType == SERVER_TYPE_STANDALONE)
		{
			if (h->Type != HUB_TYPE_STANDALONE)
			{
				// Change the type of all HUB to a stand-alone if the server is a stand-alone
				h->Type = HUB_TYPE_STANDALONE;
			}
		}
		else
		{
			if (h->Type == HUB_TYPE_STANDALONE)
			{
				// If the server is a farm controller, change the type of HUB to the farm supported types
				h->Type = HUB_TYPE_FARM_DYNAMIC;
			}
		}

		if (h->Type == HUB_TYPE_FARM_DYNAMIC)
		{
			h->CurrentVersion = h->LastVersion = 1;
		}

		// Message
		{
			FOLDER *folder = CfgGetFolder(f, "Message");
			if (folder != NULL)
			{
				wchar_t *tmp = Malloc(sizeof(wchar_t) * (HUB_MAXMSG_LEN + 1));
				if (CfgGetUniStr(folder, "MessageText", tmp, sizeof(wchar_t) * (HUB_MAXMSG_LEN + 1)))
				{
					SetHubMsg(h, tmp);
				}
				Free(tmp);
			}
		}

		// Link list
		if (h->Type == HUB_TYPE_STANDALONE)
		{
			// The link list is used only on stand-alone HUB
			// In VPN Gate hubs, don't load this
			{
				SiLoadHubLinks(h, CfgGetFolder(f, "CascadeList"));
			}
		}

		// SecureNAT
		if (GetServerCapsBool(h->Cedar->Server, "b_support_securenat"))
		{
			if (h->Type == HUB_TYPE_STANDALONE || h->Type == HUB_TYPE_FARM_DYNAMIC)
			{
				// SecureNAT is used only in the case of dynamic HUB or standalone HUB
				SiLoadSecureNAT(h, CfgGetFolder(f, "SecureNAT"));

				if (h->Type != HUB_TYPE_STANDALONE && h->Cedar != NULL && h->Cedar->Server != NULL &&
					h->Cedar->Server->ServerType == SERVER_TYPE_FARM_CONTROLLER)
				{
					NiClearUnsupportedVhOptionForDynamicHub(h->SecureNATOption,
						hub_old_type == HUB_TYPE_STANDALONE);
				}

			}
		}

		// Administration options
		SiLoadHubAdminOptions(h, CfgGetFolder(f, "AdminOption"));

		// Database
		if (h->Cedar->Bridge == false)
		{
			SiLoadHubDb(h, CfgGetFolder(f, "SecurityAccountDatabase"));
		}

		// Usage status
		h->LastCommTime = CfgGetInt64(f, "LastCommTime");
		if (h->LastCommTime == 0)
		{
			h->LastCommTime = SystemTime64();
		}
		h->LastLoginTime = CfgGetInt64(f, "LastLoginTime");
		if (h->LastLoginTime == 0)
		{
			h->LastLoginTime = SystemTime64();
		}
		h->CreatedTime = CfgGetInt64(f, "CreatedTime");
		h->NumLogin = CfgGetInt(f, "NumLogin");

		// Start the operation of the HUB
		AddHub(c, h);

		if (online)
		{
			h->Offline = true;
			SetHubOnline(h);
		}
		else
		{
			h->Offline = false;
			SetHubOffline(h);
		}

		WaitLogFlush(h->SecurityLogger);
		WaitLogFlush(h->PacketLogger);

		ReleaseHub(h);
	}
}

// Read the SecureNAT configuration
void SiLoadSecureNAT(HUB *h, FOLDER *f)
{
	VH_OPTION o;
	// Validate arguments
	if (h == NULL || f == NULL)
	{
		return;
	}

	// Read the VH_OPTION
	NiLoadVhOptionEx(&o, f);

	// Set the VH_OPTION
	Copy(h->SecureNATOption, &o, sizeof(VH_OPTION));

	EnableSecureNAT(h, CfgGetBool(f, "Disabled") ? false : true);
}

// Read the virtual layer 3 switch settings
void SiLoadL3SwitchCfg(L3SW *sw, FOLDER *f)
{
	UINT i;
	FOLDER *if_folder, *table_folder;
	TOKEN_LIST *t;
	bool active = false;
	// Validate arguments
	if (sw == NULL || f == NULL)
	{
		return;
	}

	active = CfgGetBool(f, "Active");

	// Interface list
	if_folder = CfgGetFolder(f, "InterfaceList");
	if (if_folder != NULL)
	{
		t = CfgEnumFolderToTokenList(if_folder);
		if (t != NULL)
		{
			for (i = 0;i < t->NumTokens;i++)
			{
				FOLDER *ff = CfgGetFolder(if_folder, t->Token[i]);
				char name[MAX_HUBNAME_LEN + 1];
				UINT ip, subnet;

				CfgGetStr(ff, "HubName", name, sizeof(name));
				ip = CfgGetIp32(ff, "IpAddress");
				subnet = CfgGetIp32(ff, "SubnetMask");

				{
					L3AddIf(sw, name, ip, subnet);
				}
			}
			FreeToken(t);
		}
	}

	// Routing table
	table_folder = CfgGetFolder(f, "RoutingTable");
	if (table_folder != NULL)
	{
		t = CfgEnumFolderToTokenList(table_folder);
		if (t != NULL)
		{
			for (i = 0;i < t->NumTokens;i++)
			{
				FOLDER *ff = CfgGetFolder(table_folder, t->Token[i]);
				L3TABLE tbl;

				Zero(&tbl, sizeof(tbl));
				tbl.NetworkAddress = CfgGetIp32(ff, "NetworkAddress");
				tbl.SubnetMask = CfgGetIp32(ff, "SubnetMask");
				tbl.GatewayAddress = CfgGetIp32(ff, "GatewayAddress");
				tbl.Metric = CfgGetInt(ff, "Metric");

				L3AddTable(sw, &tbl);
			}
			FreeToken(t);
		}
	}

	if (active)
	{
		L3SwStart(sw);
	}
}

// Write the virtual layer 3 switch settings
void SiWriteL3SwitchCfg(FOLDER *f, L3SW *sw)
{
	UINT i;
	FOLDER *if_folder, *table_folder;
	char tmp[MAX_SIZE];
	// Validate arguments
	if (f == NULL || sw == NULL)
	{
		return;
	}

	// Active flag
	CfgAddBool(f, "Active", sw->Active);

	// Interface list
	if_folder = CfgCreateFolder(f, "InterfaceList");
	for (i = 0;i < LIST_NUM(sw->IfList);i++)
	{
		L3IF *e = LIST_DATA(sw->IfList, i);
		FOLDER *ff;

		Format(tmp, sizeof(tmp), "Interface%u", i);
		ff = CfgCreateFolder(if_folder, tmp);

		CfgAddStr(ff, "HubName", e->HubName);
		CfgAddIp32(ff, "IpAddress", e->IpAddress);
		CfgAddIp32(ff, "SubnetMask", e->SubnetMask);
	}

	// Routing table
	table_folder = CfgCreateFolder(f, "RoutingTable");
	for (i = 0;i < LIST_NUM(sw->TableList);i++)
	{
		L3TABLE *e = LIST_DATA(sw->TableList, i);
		FOLDER *ff;

		Format(tmp, sizeof(tmp), "Entry%u", i);
		ff = CfgCreateFolder(table_folder, tmp);

		CfgAddIp32(ff, "NetworkAddress", e->NetworkAddress);
		CfgAddIp32(ff, "SubnetMask", e->SubnetMask);
		CfgAddIp32(ff, "GatewayAddress", e->GatewayAddress);
		CfgAddInt(ff, "Metric", e->Metric);
	}
}

// Read the Virtual Layer 3 switch list
void SiLoadL3Switchs(SERVER *s, FOLDER *f)
{
	UINT i;
	TOKEN_LIST *t;
	CEDAR *c;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}
	c = s->Cedar;

	t = CfgEnumFolderToTokenList(f);
	if (t != NULL)
	{
		for (i = 0;i < t->NumTokens;i++)
		{
			char *name = t->Token[i];
			L3SW *sw = L3AddSw(c, name);

			SiLoadL3SwitchCfg(sw, CfgGetFolder(f, name));

			ReleaseL3Sw(sw);
		}
	}
	FreeToken(t);
}

// Write the Virtual Layer 3 switch list
void SiWriteL3Switchs(FOLDER *f, SERVER *s)
{
	UINT i;
	FOLDER *folder;
	CEDAR *c;
	// Validate arguments
	if (f == NULL || s == NULL)
	{
		return;
	}
	c = s->Cedar;

	LockList(c->L3SwList);
	{
		for (i = 0;i < LIST_NUM(c->L3SwList);i++)
		{
			L3SW *sw = LIST_DATA(c->L3SwList, i);

			Lock(sw->lock);
			{
				folder = CfgCreateFolder(f, sw->Name);

				SiWriteL3SwitchCfg(folder, sw);
			}
			Unlock(sw->lock);
		}
	}
	UnlockList(c->L3SwList);
}

// Read the IPsec server configuration
void SiLoadIPsec(SERVER *s, FOLDER *f)
{
	IPSEC_SERVICES sl;
	FOLDER *list_folder;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	Zero(&sl, sizeof(sl));

	CfgGetStr(f, "IPsec_Secret", sl.IPsec_Secret, sizeof(sl.IPsec_Secret));
	CfgGetStr(f, "L2TP_DefaultHub", sl.L2TP_DefaultHub, sizeof(sl.L2TP_DefaultHub));

	if (s->ServerType == SERVER_TYPE_STANDALONE)
	{
		// IPsec feature only be enabled on a standalone server
		sl.L2TP_Raw = CfgGetBool(f, "L2TP_Raw");
		sl.L2TP_IPsec = CfgGetBool(f, "L2TP_IPsec");
		sl.EtherIP_IPsec = CfgGetBool(f, "EtherIP_IPsec");
	}

	IPsecServerSetServices(s->IPsecServer, &sl);

	list_folder = CfgGetFolder(f, "EtherIP_IDSettingsList");

	if (list_folder != NULL)
	{
		TOKEN_LIST *t = CfgEnumFolderToTokenList(list_folder);
		if (t != NULL)
		{
			UINT i;

			for (i = 0;i < t->NumTokens;i++)
			{
				char *name = t->Token[i];
				FOLDER *f = CfgGetFolder(list_folder, name);

				if (f != NULL)
				{
					ETHERIP_ID d;
					BUF *b;

					Zero(&d, sizeof(d));

					StrCpy(d.Id, sizeof(d.Id), name);
					CfgGetStr(f, "HubName", d.HubName, sizeof(d.HubName));
					CfgGetStr(f, "UserName", d.UserName, sizeof(d.UserName));

					b = CfgGetBuf(f, "EncryptedPassword");
					if (b != NULL)
					{
						char *pass = DecryptPassword2(b);

						StrCpy(d.Password, sizeof(d.Password), pass);

						Free(pass);

						AddEtherIPId(s->IPsecServer, &d);

						FreeBuf(b);
					}
				}
			}

			FreeToken(t);
		}
	}
}

// Write the IPsec server configuration
void SiWriteIPsec(FOLDER *f, SERVER *s)
{
	IPSEC_SERVICES sl;
	FOLDER *list_folder;
	UINT i;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	if (s->IPsecServer == NULL)
	{
		return;
	}

	Zero(&sl, sizeof(sl));
	IPsecServerGetServices(s->IPsecServer, &sl);

	CfgAddStr(f, "IPsec_Secret", sl.IPsec_Secret);
	CfgAddStr(f, "L2TP_DefaultHub", sl.L2TP_DefaultHub);

	CfgAddBool(f, "L2TP_Raw", sl.L2TP_Raw);
	CfgAddBool(f, "L2TP_IPsec", sl.L2TP_IPsec);
	CfgAddBool(f, "EtherIP_IPsec", sl.EtherIP_IPsec);

	list_folder = CfgCreateFolder(f, "EtherIP_IDSettingsList");

	Lock(s->IPsecServer->LockSettings);
	{
		for (i = 0;i < LIST_NUM(s->IPsecServer->EtherIPIdList);i++)
		{
			ETHERIP_ID *d = LIST_DATA(s->IPsecServer->EtherIPIdList, i);
			FOLDER *f;
			BUF *b;

			f = CfgCreateFolder(list_folder, d->Id);

			CfgAddStr(f, "HubName", d->HubName);
			CfgAddStr(f, "UserName", d->UserName);

			b = EncryptPassword2(d->Password);

			CfgAddBuf(f, "EncryptedPassword", b);

			FreeBuf(b);
		}
	}
	Unlock(s->IPsecServer->LockSettings);
}

// Write the license list
void SiWriteLicenseManager(FOLDER *f, SERVER *s)
{
}

// Read the license list
void SiLoadLicenseManager(SERVER *s, FOLDER *f)
{
}

// Write the Virtual HUB list
void SiWriteHubs(FOLDER *f, SERVER *s)
{
	UINT i;
	FOLDER *hub_folder;
	CEDAR *c;
	UINT num;
	HUB **hubs;
	// Validate arguments
	if (f == NULL || s == NULL)
	{
		return;
	}
	c = s->Cedar;

	LockList(c->HubList);
	{
		hubs = ToArray(c->HubList);
		num = LIST_NUM(c->HubList);

		for (i = 0;i < num;i++)
		{
			AddRef(hubs[i]->ref);
		}
	}
	UnlockList(c->HubList);

	for (i = 0;i < num;i++)
	{
		HUB *h = hubs[i];

		Lock(h->lock);
		{
			hub_folder = CfgCreateFolder(f, h->Name);
			SiWriteHubCfg(hub_folder, h);
		}
		Unlock(h->lock);

		ReleaseHub(h);

		if ((i % 30) == 1)
		{
			YieldCpu();
		}
	}

	Free(hubs);
}

// Read the Virtual HUB list
void SiLoadHubs(SERVER *s, FOLDER *f)
{
	UINT i;
	FOLDER *hub_folder;
	TOKEN_LIST *t;
	bool b = false;
	// Validate arguments
	if (f == NULL || s == NULL)
	{
		return;
	}

	t = CfgEnumFolderToTokenList(f);
	for (i = 0;i < t->NumTokens;i++)
	{
		char *name = t->Token[i];


		if (s->Cedar->Bridge)
		{
			if (StrCmpi(name, SERVER_DEFAULT_BRIDGE_NAME) == 0)
			{
				// Read only the setting of Virtual HUB named "BRIDGE"
				// in the case of the Bridge
				b = true;
			}
			else
			{
				continue;
			}
		}
		hub_folder = CfgGetFolder(f, name);
		if (hub_folder != NULL)
		{
			SiLoadHubCfg(s, hub_folder, name);
		}
	}
	FreeToken(t);

	if (s->Cedar->Bridge && b == false)
	{
		// If there isn't "BRIDGE" virtual HUB setting, create it newly
		SiInitDefaultHubList(s);
	}
}

// Read the server-specific settings
void SiLoadServerCfg(SERVER *s, FOLDER *f)
{
	BUF *b;
	CEDAR *c;
	char tmp[MAX_SIZE];
	X *x = NULL;
	K *k = NULL;
	LIST *chain = NewList(NULL);
	FOLDER *params_folder;
	UINT i;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	// Save interval related
	s->AutoSaveConfigSpan = CfgGetInt(f, "AutoSaveConfigSpan") * 1000;
	if (s->AutoSaveConfigSpan == 0)
	{
		s->AutoSaveConfigSpan = SERVER_FILE_SAVE_INTERVAL_DEFAULT;
	}
	else
	{
		s->AutoSaveConfigSpan = MAKESURE(s->AutoSaveConfigSpan, SERVER_FILE_SAVE_INTERVAL_MIN, SERVER_FILE_SAVE_INTERVAL_MAX);
	}

	i = CfgGetInt(f, "MaxConcurrentDnsClientThreads");
	if (i != 0)
	{
		DnsThreadNumMaxSet(i);
	}
	else
	{
		DnsThreadNumMaxSet(DNS_THREAD_DEFAULT_NUM_MAX);
	}

	s->DontBackupConfig = CfgGetBool(f, "DontBackupConfig");

	CfgGetIp(f, "ListenIP", &s->ListenIP);
	ProtoSetListenIP(s->Proto, &s->ListenIP);

	if (CfgIsItem(f, "BackupConfigOnlyWhenModified"))
	{
		s->BackupConfigOnlyWhenModified = CfgGetBool(f, "BackupConfigOnlyWhenModified");
	}
	else
	{
		s->BackupConfigOnlyWhenModified = true;
	}

	// Server log switch type
	if (CfgIsItem(f, "ServerLogSwitchType"))
	{
		UINT st = CfgGetInt(f, "ServerLogSwitchType");

		SetLogSwitchType(s->Logger, st);
	}

	SetMaxLogSize(CfgGetInt64(f, "LoggerMaxLogSize"));

	params_folder = CfgGetFolder(f, "GlobalParams");
	SiLoadGlobalParamsCfg(params_folder);

	c = s->Cedar;
	Lock(c->lock);
	{
		FOLDER *ff;
		{
			UINT i;
			LIST *ports;

			// Load and set UDP ports
			CfgGetStr(f, "PortsUDP", tmp, sizeof(tmp));
			NormalizeIntListStr(tmp, sizeof(tmp), tmp, true, ", ");

			ports = StrToIntList(tmp, true);
			for (i = 0; i < LIST_NUM(ports); ++i)
			{
				AddInt(s->PortsUDP, *(UINT *)LIST_DATA(ports, i));
			}
			ReleaseIntList(ports);

			ProtoSetUdpPorts(s->Proto, s->PortsUDP);
		}
		{
			RPC_KEEP k;

			// Keep-alive related
			Zero(&k, sizeof(k));
			k.UseKeepConnect = CfgGetBool(f, "UseKeepConnect");
			CfgGetStr(f, "KeepConnectHost", k.KeepConnectHost, sizeof(k.KeepConnectHost));
			k.KeepConnectPort = CfgGetInt(f, "KeepConnectPort");
			k.KeepConnectProtocol = CfgGetInt(f, "KeepConnectProtocol");
			k.KeepConnectInterval = CfgGetInt(f, "KeepConnectInterval") * 1000;
			if (k.KeepConnectPort == 0)
			{
				k.KeepConnectPort = 80;
			}
			if (StrLen(k.KeepConnectHost) == 0)
			{
				StrCpy(k.KeepConnectHost, sizeof(k.KeepConnectHost), CLIENT_DEFAULT_KEEPALIVE_HOST);
			}
			if (k.KeepConnectInterval == 0)
			{
				k.KeepConnectInterval = KEEP_INTERVAL_DEFAULT * 1000;
			}
			if (k.KeepConnectInterval < 5000)
			{
				k.KeepConnectInterval = 5000;
			}
			if (k.KeepConnectInterval > 600000)
			{
				k.KeepConnectInterval = 600000;
			}

			Lock(s->Keep->lock);
			{
				KEEP *keep = s->Keep;
				keep->Enable = k.UseKeepConnect;
				keep->Server = true;
				StrCpy(keep->ServerName, sizeof(keep->ServerName), k.KeepConnectHost);
				keep->ServerPort = k.KeepConnectPort;
				keep->UdpMode = k.KeepConnectProtocol;
				keep->Interval = k.KeepConnectInterval;
			}
			Unlock(s->Keep->lock);
		}

		// syslog
		ff = CfgGetFolder(f, "SyslogSettings");
		if (ff != NULL && GetServerCapsBool(s, "b_support_syslog"))
		{
			SYSLOG_SETTING set;

			Zero(&set, sizeof(set));

			set.SaveType = CfgGetInt(ff, "SaveType");
			CfgGetStr(ff, "HostName", set.Hostname, sizeof(set.Hostname));
			set.Port = CfgGetInt(ff, "Port");

			SiSetSysLogSetting(s, &set);
		}

		// Proto
		ff = CfgGetFolder(f, "Proto");
		if (ff != NULL)
		{
			SiLoadProtoCfg(s->Proto, ff);
		}

		// Whether to disable the IPv6 listener
		s->Cedar->DisableIPv6Listener = CfgGetBool(f, "DisableIPv6Listener");

		// DoS
		s->DisableDosProtection = CfgGetBool(f, "DisableDosProtection");

		// Num Connections Per IP
		SetMaxConnectionsPerIp(CfgGetInt(f, "MaxConnectionsPerIP"));

		// MaxUnestablishedConnections
		SetMaxUnestablishedConnections(CfgGetInt(f, "MaxUnestablishedConnections"));

		// DeadLock
		s->DisableDeadLockCheck = CfgGetBool(f, "DisableDeadLockCheck");

		// Eraser
		SetEraserCheckInterval(CfgGetInt(f, "AutoDeleteCheckIntervalSecs"));
		s->Eraser = NewEraser(s->Logger, CfgGetInt64(f, "AutoDeleteCheckDiskFreeSpaceMin"));

		// WebUI
		s->UseWebUI = CfgGetBool(f, "UseWebUI");


		// WebTimePage
		s->UseWebTimePage = CfgGetBool(f, "UseWebTimePage");

		// NoLinuxArpFilter
		s->NoLinuxArpFilter = CfgGetBool(f, "NoLinuxArpFilter");

		// NoHighPriorityProcess
		s->NoHighPriorityProcess = CfgGetBool(f, "NoHighPriorityProcess");

		// NoDebugDump
		s->NoDebugDump = CfgGetBool(f, "NoDebugDump");
		if (s->NoDebugDump)
		{
#ifdef	OS_WIN32
			MsSetEnableMinidump(false);
#endif	// OS_WIN32
		}

		// Disable the NAT-traversal feature
		s->DisableNatTraversal = CfgGetBool(f, "DisableNatTraversal");

		// Disable IPsec's aggressive mode
		s->DisableIPsecAggressiveMode = CfgGetBool(f, "DisableIPsecAggressiveMode");

		if (s->Cedar->Bridge == false)
		{
			// Enable the VPN-over-ICMP
			if (CfgIsItem(f, "EnableVpnOverIcmp"))
			{
				s->EnableVpnOverIcmp = CfgGetBool(f, "EnableVpnOverIcmp");
			}
			else
			{
				s->EnableVpnOverIcmp = false;
			}

			// Enable the VPN-over-DNS
			if (CfgIsItem(f, "EnableVpnOverDns"))
			{
				s->EnableVpnOverDns = CfgGetBool(f, "EnableVpnOverDns");
			}
			else
			{
				s->EnableVpnOverDns = false;
			}
		}

		// Debug log
		s->SaveDebugLog = CfgGetBool(f, "SaveDebugLog");
		if (s->SaveDebugLog)
		{
			s->DebugLog = NewTinyLog();
		}

		// Let the client not to send a signature
		s->NoSendSignature = CfgGetBool(f, "NoSendSignature");

		// Server certificate
		b = CfgGetBuf(f, "ServerCert");
		if (b != NULL)
		{
			x = BufToX(b, false);
			FreeBuf(b);
		}

		// Server private key
		b = CfgGetBuf(f, "ServerKey");
		if (b != NULL)
		{
			k = BufToK(b, true, false, NULL);
			FreeBuf(b);
		}

		// Server trust chain
		SiLoadCertList(chain, CfgGetFolder(f, "ServerChain"));

		if (x == NULL || k == NULL || CheckXandK(x, k) == false)
		{
			FreeX(x);
			FreeK(k);
			FreeXList(chain);
			SiGenerateDefaultCert(&x, &k);

			SetCedarCert(c, x, k);

			FreeX(x);
			FreeK(k);
		}
		else
		{
			if (LIST_NUM(chain) == 0)
			{
				SetCedarCert(c, x, k);
			}
			else
			{
				SetCedarCertAndChain(c, x, k, chain);
			}

			FreeX(x);
			FreeK(k);
			FreeXList(chain);
		}

		// Character which separates the username from the hub name
		if (CfgGetStr(f, "UsernameHubSeparator", tmp, sizeof(tmp)))
		{
			c->UsernameHubSeparator = IsPrintableAsciiChar(tmp[0]) ? tmp[0] : DEFAULT_USERNAME_HUB_SEPARATOR;
		}

		// Cipher Name
		if (CfgGetStr(f, "CipherName", tmp, sizeof(tmp)))
		{
			StrUpper(tmp);
			SetCedarCipherList(c, tmp);
		}

		// Traffic information
		Lock(c->TrafficLock);
		{
			SiLoadTraffic(f, "ServerTraffic", c->Traffic);
		}
		Unlock(c->TrafficLock);

		// Type of server
		s->UpdatedServerType = s->ServerType = CfgGetInt(f, "ServerType");

		// Password
		if (CfgGetByte(f, "HashedPassword", s->HashedPassword, sizeof(s->HashedPassword)) != sizeof(s->HashedPassword))
		{
			Sha0(s->HashedPassword, "", 0);
		}

		if (s->ServerType != SERVER_TYPE_STANDALONE)
		{
			// Performance ratio of the server
			s->Weight = CfgGetInt(f, "ClusterMemberWeight");
			if (s->Weight == 0)
			{
				s->Weight = FARM_DEFAULT_WEIGHT;
			}
		}
		else
		{
			s->Weight = FARM_DEFAULT_WEIGHT;
		}

		if (s->ServerType == SERVER_TYPE_FARM_CONTROLLER)
		{
			s->ControllerOnly = CfgGetBool(f, "ControllerOnly");
		}

		if (s->ServerType != SERVER_TYPE_STANDALONE)
		{
			// NAT traversal can not be used in a cluster environment
			s->DisableNatTraversal = true;
		}

		if (s->Cedar->Bridge)
		{
			// NAT traversal function can not be used in the bridge environment
			s->DisableNatTraversal = true;
		}

		if (CfgGetStr(f, "PortsUDP", tmp, sizeof(tmp)))
		{
			UINT i;
			TOKEN_LIST *tokens;
			LIST *ports = s->PortsUDP;

			for (i = 0; i < LIST_NUM(ports); ++i)
			{
				Free(LIST_DATA(ports, i));
			}
			DeleteAll(ports);

			NormalizeIntListStr(tmp, sizeof(tmp), tmp, true, ", ");

			tokens = ParseTokenWithoutNullStr(tmp, ", ");
			for (i = 0; i < tokens->NumTokens; ++i)
			{
				char *str = tokens->Token[i];
				if (IsNum(str))
				{
					InsertIntDistinct(ports, ToInt(str));
				}
			}
			FreeToken(tokens);
		}

		if (s->ServerType == SERVER_TYPE_FARM_MEMBER)
		{
			char tmp[6 * MAX_PUBLIC_PORT_NUM + 1];
			// Load the settings item in the case of farm members
			CfgGetStr(f, "ControllerName", s->ControllerName, sizeof(s->ControllerName));
			s->ControllerPort = CfgGetInt(f, "ControllerPort");
			CfgGetByte(f, "MemberPassword", s->MemberPassword, SHA1_SIZE);
			s->PublicIp = CfgGetIp32(f, "PublicIp");
			if (CfgGetStr(f, "PublicPorts", tmp, sizeof(tmp)))
			{
				TOKEN_LIST *t = ParseToken(tmp, ", ");
				UINT i;
				s->NumPublicPort = t->NumTokens;
				s->PublicPorts = ZeroMalloc(s->NumPublicPort * sizeof(UINT));
				for (i = 0;i < s->NumPublicPort;i++)
				{
					s->PublicPorts[i] = ToInt(t->Token[i]);
				}
				FreeToken(t);
			}
		}

		// Configuration of VPN Azure Client
		s->EnableVpnAzure = CfgGetBool(f, "EnableVpnAzure");

		// Disable GetHostName when accepting TCP
		s->DisableGetHostNameWhenAcceptTcp = CfgGetBool(f, "DisableGetHostNameWhenAcceptTcp");

		if (s->DisableGetHostNameWhenAcceptTcp)
		{
			DisableGetHostNameWhenAcceptInit();
		}

		// Disable core dump on UNIX
		s->DisableCoreDumpOnUnix = CfgGetBool(f, "DisableCoreDumpOnUnix");

		// Disable session reconnect
		SetGlobalServerFlag(GSF_DISABLE_SESSION_RECONNECT, CfgGetBool(f, "DisableSessionReconnect"));

		c->SslAcceptSettings.Tls_Disable1_0 = CfgGetBool(f, "Tls_Disable1_0");
		c->SslAcceptSettings.Tls_Disable1_1 = CfgGetBool(f, "Tls_Disable1_1");
		c->SslAcceptSettings.Tls_Disable1_2 = CfgGetBool(f, "Tls_Disable1_2");
		c->SslAcceptSettings.Tls_Disable1_3 = CfgGetBool(f, "Tls_Disable1_3");
		c->SslAcceptSettings.Override_Security_Level = CfgGetBool(f, "Override_Security_Level");
		c->SslAcceptSettings.Override_Security_Level_Value = CfgGetInt(f, "Override_Security_Level_Value");

		s->StrictSyslogDatetimeFormat = CfgGetBool(f, "StrictSyslogDatetimeFormat");

		// Disable JSON-RPC Web API
		s->DisableJsonRpcWebApi = CfgGetBool(f, "DisableJsonRpcWebApi");

		// Bits of Diffie-Hellman parameters
		c->DhParamBits = CfgGetInt(f, "DhParamBits");
		if (c->DhParamBits == 0)
		{
			c->DhParamBits = DH_PARAM_BITS_DEFAULT;
		}

		SetDhParam(DhNewFromBits(c->DhParamBits));
	}
	Unlock(c->lock);

#ifdef	OS_UNIX
	if (s->DisableCoreDumpOnUnix)
	{
		UnixDisableCoreDump();
	}
#endif	// OS_UNIX
}

// Load global params
void SiLoadGlobalParamsCfg(FOLDER *f)
{
	SiLoadGlobalParamItem(GP_MAX_SEND_SOCKET_QUEUE_SIZE, CfgGetInt(f, "MAX_SEND_SOCKET_QUEUE_SIZE"));
	SiLoadGlobalParamItem(GP_MIN_SEND_SOCKET_QUEUE_SIZE, CfgGetInt(f, "MIN_SEND_SOCKET_QUEUE_SIZE"));
	SiLoadGlobalParamItem(GP_MAX_SEND_SOCKET_QUEUE_NUM, CfgGetInt(f, "MAX_SEND_SOCKET_QUEUE_NUM"));
	SiLoadGlobalParamItem(GP_SELECT_TIME, CfgGetInt(f, "SELECT_TIME"));
	SiLoadGlobalParamItem(GP_SELECT_TIME_FOR_NAT, CfgGetInt(f, "SELECT_TIME_FOR_NAT"));
	SiLoadGlobalParamItem(GP_MAX_STORED_QUEUE_NUM, CfgGetInt(f, "MAX_STORED_QUEUE_NUM"));
	SiLoadGlobalParamItem(GP_MAX_BUFFERING_PACKET_SIZE, CfgGetInt(f, "MAX_BUFFERING_PACKET_SIZE"));
	SiLoadGlobalParamItem(GP_HUB_ARP_SEND_INTERVAL, CfgGetInt(f, "HUB_ARP_SEND_INTERVAL"));
	SiLoadGlobalParamItem(GP_MAC_TABLE_EXPIRE_TIME, CfgGetInt(f, "MAC_TABLE_EXPIRE_TIME"));
	SiLoadGlobalParamItem(GP_IP_TABLE_EXPIRE_TIME, CfgGetInt(f, "IP_TABLE_EXPIRE_TIME"));
	SiLoadGlobalParamItem(GP_IP_TABLE_EXPIRE_TIME_DHCP, CfgGetInt(f, "IP_TABLE_EXPIRE_TIME_DHCP"));
	SiLoadGlobalParamItem(GP_STORM_CHECK_SPAN, CfgGetInt(f, "STORM_CHECK_SPAN"));
	SiLoadGlobalParamItem(GP_STORM_DISCARD_VALUE_START, CfgGetInt(f, "STORM_DISCARD_VALUE_START"));
	SiLoadGlobalParamItem(GP_STORM_DISCARD_VALUE_END, CfgGetInt(f, "STORM_DISCARD_VALUE_END"));
	SiLoadGlobalParamItem(GP_MAX_MAC_TABLES, CfgGetInt(f, "MAX_MAC_TABLES"));
	SiLoadGlobalParamItem(GP_MAX_IP_TABLES, CfgGetInt(f, "MAX_IP_TABLES"));
	SiLoadGlobalParamItem(GP_MAX_HUB_LINKS, CfgGetInt(f, "MAX_HUB_LINKS"));
	SiLoadGlobalParamItem(GP_MEM_FIFO_REALLOC_MEM_SIZE, CfgGetInt(f, "MEM_FIFO_REALLOC_MEM_SIZE"));
	SiLoadGlobalParamItem(GP_QUEUE_BUDGET, CfgGetInt(f, "QUEUE_BUDGET"));
	SiLoadGlobalParamItem(GP_FIFO_BUDGET, CfgGetInt(f, "FIFO_BUDGET"));

	SetFifoCurrentReallocMemSize(MEM_FIFO_REALLOC_MEM_SIZE);
}

// Load global param itesm
void SiLoadGlobalParamItem(UINT id, UINT value)
{
	// Validate arguments
	if (id == 0)
	{
		return;
	}

	vpn_global_parameters[id] = value;
}

// Write global params
void SiWriteGlobalParamsCfg(FOLDER *f)
{
	// Validate arguments
	if (f == NULL)
	{
		return;
	}

	CfgAddInt(f, "MAX_SEND_SOCKET_QUEUE_SIZE", MAX_SEND_SOCKET_QUEUE_SIZE);
	CfgAddInt(f, "MIN_SEND_SOCKET_QUEUE_SIZE", MIN_SEND_SOCKET_QUEUE_SIZE);
	CfgAddInt(f, "MAX_SEND_SOCKET_QUEUE_NUM", MAX_SEND_SOCKET_QUEUE_NUM);
	CfgAddInt(f, "SELECT_TIME", SELECT_TIME);
	CfgAddInt(f, "SELECT_TIME_FOR_NAT", SELECT_TIME_FOR_NAT);
	CfgAddInt(f, "MAX_STORED_QUEUE_NUM", MAX_STORED_QUEUE_NUM);
	CfgAddInt(f, "MAX_BUFFERING_PACKET_SIZE", MAX_BUFFERING_PACKET_SIZE);
	CfgAddInt(f, "HUB_ARP_SEND_INTERVAL", HUB_ARP_SEND_INTERVAL);
	CfgAddInt(f, "MAC_TABLE_EXPIRE_TIME", MAC_TABLE_EXPIRE_TIME);
	CfgAddInt(f, "IP_TABLE_EXPIRE_TIME", IP_TABLE_EXPIRE_TIME);
	CfgAddInt(f, "IP_TABLE_EXPIRE_TIME_DHCP", IP_TABLE_EXPIRE_TIME_DHCP);
	CfgAddInt(f, "STORM_CHECK_SPAN", STORM_CHECK_SPAN);
	CfgAddInt(f, "STORM_DISCARD_VALUE_START", STORM_DISCARD_VALUE_START);
	CfgAddInt(f, "STORM_DISCARD_VALUE_END", STORM_DISCARD_VALUE_END);
	CfgAddInt(f, "MAX_MAC_TABLES", MAX_MAC_TABLES);
	CfgAddInt(f, "MAX_IP_TABLES", MAX_IP_TABLES);
	CfgAddInt(f, "MAX_HUB_LINKS", MAX_HUB_LINKS);
	CfgAddInt(f, "MEM_FIFO_REALLOC_MEM_SIZE", MEM_FIFO_REALLOC_MEM_SIZE);
	CfgAddInt(f, "QUEUE_BUDGET", QUEUE_BUDGET);
	CfgAddInt(f, "FIFO_BUDGET", FIFO_BUDGET);
}

// Write the server-specific settings
void SiWriteServerCfg(FOLDER *f, SERVER *s)
{
	BUF *b;
	CEDAR *c;
	FOLDER *params_folder;
	// Validate arguments
	if (f == NULL || s == NULL)
	{
		return;
	}

	CfgAddInt(f, "MaxConcurrentDnsClientThreads", DnsThreadNumMax());

	CfgAddInt(f, "CurrentBuild", s->Cedar->Build);

	CfgAddInt(f, "AutoSaveConfigSpan", s->AutoSaveConfigSpanSaved / 1000);

	CfgAddBool(f, "DontBackupConfig", s->DontBackupConfig);
	CfgAddBool(f, "BackupConfigOnlyWhenModified", s->BackupConfigOnlyWhenModified);

	CfgAddIp(f, "ListenIP", &s->ListenIP);

	{
		char str[MAX_SIZE];
		IntListToStr(str, sizeof(str), s->PortsUDP, ", ");
		CfgAddStr(f, "PortsUDP", str);
	}

	if (s->Logger != NULL)
	{
		CfgAddInt(f, "ServerLogSwitchType", s->Logger->SwitchType);
	}

	CfgAddInt64(f, "LoggerMaxLogSize", GetMaxLogSize());

	params_folder = CfgCreateFolder(f, "GlobalParams");

	if (params_folder != NULL)
	{
		SiWriteGlobalParamsCfg(params_folder);
	}

	c = s->Cedar;

	Lock(c->lock);
	{
		FOLDER *ff;
		Lock(s->Keep->lock);
		{
			KEEP *k = s->Keep;
			CfgAddBool(f, "UseKeepConnect", k->Enable);
			CfgAddStr(f, "KeepConnectHost", k->ServerName);
			CfgAddInt(f, "KeepConnectPort", k->ServerPort);
			CfgAddInt(f, "KeepConnectProtocol", k->UdpMode);
			CfgAddInt(f, "KeepConnectInterval", k->Interval / 1000);
		}
		Unlock(s->Keep->lock);

		// syslog
		ff = CfgCreateFolder(f, "SyslogSettings");
		if (ff != NULL)
		{
			SYSLOG_SETTING set;

			SiGetSysLogSetting(s, &set);

			CfgAddInt(ff, "SaveType", set.SaveType);
			CfgAddStr(ff, "HostName", set.Hostname);
			CfgAddInt(ff, "Port", set.Port);
		}

		// Proto
		ff = CfgCreateFolder(f, "Proto");
		if (ff != NULL)
		{
			SiWriteProtoCfg(ff, s->Proto);
		}

		// IPv6 listener disable setting
		CfgAddBool(f, "DisableIPv6Listener", s->Cedar->DisableIPv6Listener);

		// DoS
		CfgAddBool(f, "DisableDosProtection", s->DisableDosProtection);

		// MaxConnectionsPerIP
		CfgAddInt(f, "MaxConnectionsPerIP", GetMaxConnectionsPerIp());

		// MaxUnestablishedConnections
		CfgAddInt(f, "MaxUnestablishedConnections", GetMaxUnestablishedConnections());

		// DeadLock
		CfgAddBool(f, "DisableDeadLockCheck", s->DisableDeadLockCheck);

		// Eraser related
		CfgAddInt64(f, "AutoDeleteCheckDiskFreeSpaceMin", s->Eraser->MinFreeSpace);
		CfgAddInt(f, "AutoDeleteCheckIntervalSecs", GetEraserCheckInterval());

		// WebUI
		CfgAddBool(f, "UseWebUI", s->UseWebUI);


		// NoLinuxArpFilter
		if (GetOsInfo()->OsType == OSTYPE_LINUX)
		{
			CfgAddBool(f, "NoLinuxArpFilter", s->NoLinuxArpFilter);
		}

		// NoHighPriorityProcess
		CfgAddBool(f, "NoHighPriorityProcess", s->NoHighPriorityProcess);

#ifdef	OS_WIN32
		CfgAddBool(f, "NoDebugDump", s->NoDebugDump);
#endif	// OS_WIN32

		if (s->ServerType == SERVER_TYPE_STANDALONE)
		{
			if (c->Bridge == false)
			{
				// Disable the NAT-traversal feature
				CfgAddBool(f, "DisableNatTraversal", s->DisableNatTraversal);
			}
		}

		CfgAddBool(f, "DisableIPsecAggressiveMode", s->DisableIPsecAggressiveMode);

		if (c->Bridge == false)
		{
			// VPN over ICMP
			CfgAddBool(f, "EnableVpnOverIcmp", s->EnableVpnOverIcmp);

			// VPN over DNS
			CfgAddBool(f, "EnableVpnOverDns", s->EnableVpnOverDns);
		}

		// WebTimePage
		CfgAddBool(f, "UseWebTimePage", s->UseWebTimePage);

		// Debug log
		CfgAddBool(f, "SaveDebugLog", s->SaveDebugLog);

		// Let the client not to send a signature
		CfgAddBool(f, "NoSendSignature", s->NoSendSignature);

		// Server certificate
		b = XToBuf(c->ServerX, false);
		CfgAddBuf(f, "ServerCert", b);
		FreeBuf(b);

		// Server private key
		b = KToBuf(c->ServerK, false, NULL);
		CfgAddBuf(f, "ServerKey", b);
		FreeBuf(b);

		// Server trust chain
		SiWriteCertList(CfgCreateFolder(f, "ServerChain"), c->ServerChain);

		{
			// Character which separates the username from the hub name
			char str[2];
			StrCpy(str, sizeof(str), &c->UsernameHubSeparator);
			CfgAddStr(f, "UsernameHubSeparator", str);
		}

		// Traffic information
		Lock(c->TrafficLock);
		{
			SiWriteTraffic(f, "ServerTraffic", c->Traffic);
		}
		Unlock(c->TrafficLock);

		// Type of server
		if (s->Cedar->Bridge == false)
		{
			CfgAddInt(f, "ServerType", s->UpdatedServerType);
		}

		// Cipher Name
		CfgAddStr(f, "CipherName", s->Cedar->CipherList);

		// Password
		CfgAddByte(f, "HashedPassword", s->HashedPassword, sizeof(s->HashedPassword));

		if (s->UpdatedServerType == SERVER_TYPE_FARM_MEMBER)
		{
			char tmp[6 * MAX_PUBLIC_PORT_NUM + 1];
			UINT i;
			// Setting items in the case of farm members
			CfgAddStr(f, "ControllerName", s->ControllerName);
			CfgAddInt(f, "ControllerPort", s->ControllerPort);
			CfgAddByte(f, "MemberPassword", s->MemberPassword, SHA1_SIZE);
			CfgAddIp32(f, "PublicIp", s->PublicIp);
			tmp[0] = 0;
			for (i = 0;i < s->NumPublicPort;i++)
			{
				char tmp2[MAX_SIZE];
				ToStr(tmp2, s->PublicPorts[i]);
				StrCat(tmp, sizeof(tmp), tmp2);
				StrCat(tmp, sizeof(tmp), ",");
			}
			if (StrLen(tmp) >= 1)
			{
				if (tmp[StrLen(tmp) - 1] == ',')
				{
					tmp[StrLen(tmp) - 1] = 0;
				}
			}
			CfgAddStr(f, "PublicPorts", tmp);
		}

		if (s->UpdatedServerType != SERVER_TYPE_STANDALONE)
		{
			CfgAddInt(f, "ClusterMemberWeight", s->Weight);
		}

		if (s->UpdatedServerType == SERVER_TYPE_FARM_CONTROLLER)
		{
			CfgAddBool(f, "ControllerOnly", s->ControllerOnly);
		}

		// VPN Azure Client
		if (s->AzureClient != NULL)
		{
			CfgAddBool(f, "EnableVpnAzure", s->EnableVpnAzure);
		}

		CfgAddBool(f, "DisableGetHostNameWhenAcceptTcp", s->DisableGetHostNameWhenAcceptTcp);
		CfgAddBool(f, "DisableCoreDumpOnUnix", s->DisableCoreDumpOnUnix);

		CfgAddBool(f, "Tls_Disable1_0", c->SslAcceptSettings.Tls_Disable1_0);
		CfgAddBool(f, "Tls_Disable1_1", c->SslAcceptSettings.Tls_Disable1_1);
		CfgAddBool(f, "Tls_Disable1_2", c->SslAcceptSettings.Tls_Disable1_2);
		CfgAddBool(f, "Tls_Disable1_3", c->SslAcceptSettings.Tls_Disable1_3);
		CfgAddBool(f, "Override_Security_Level", c->SslAcceptSettings.Override_Security_Level);
		CfgAddInt(f, "Override_Security_Level_Value", c->SslAcceptSettings.Override_Security_Level_Value);
		CfgAddInt(f, "DhParamBits", c->DhParamBits);

		// Disable session reconnect
		CfgAddBool(f, "DisableSessionReconnect", GetGlobalServerFlag(GSF_DISABLE_SESSION_RECONNECT));

		CfgAddBool(f, "StrictSyslogDatetimeFormat", s->StrictSyslogDatetimeFormat);

		// Disable JSON-RPC Web API
		CfgAddBool(f, "DisableJsonRpcWebApi", s->DisableJsonRpcWebApi);
	}
	Unlock(c->lock);
}

void SiLoadProtoCfg(PROTO *p, FOLDER *f)
{
	UINT i;

	if (p == NULL || f == NULL)
	{
		return;
	}

	for (i = 0; i < LIST_NUM(p->Containers); ++i)
	{
		UINT j;
		const PROTO_CONTAINER *container = LIST_DATA(p->Containers, i);
		LIST *options = container->Options;
		FOLDER *ff = CfgGetFolder(f, container->Name);
		if (ff == NULL)
		{
			continue;
		}

		LockList(options);

		for (j = 0; j < LIST_NUM(options); ++j)
		{
			PROTO_OPTION *option = LIST_DATA(options, j);
			if (CfgIsItem(ff, option->Name) == false)
			{
				continue;
			}

			switch (option->Type)
			{
			case PROTO_OPTION_BOOL:
				option->Bool = CfgGetBool(ff, option->Name);
				break;
			case PROTO_OPTION_UINT32:
				option->UInt32 = CfgGetInt(ff, option->Name);
				break;
			case PROTO_OPTION_STRING:
			{
				UINT size;
				char buf[MAX_SIZE];
				if (CfgGetStr(ff, option->Name, buf, sizeof(buf)) == false)
				{
					continue;
				}

				size = StrLen(buf) + 1;
				option->String = ReAlloc(option->String, size);
				StrCpy(option->String, size, buf);

				break;
			}
			default:
				Debug("SiLoadProtoCfg(): unhandled option type %u!\n", option->Type);
			}
		}

		UnlockList(options);
	}
}

void SiWriteProtoCfg(FOLDER *f, PROTO *p)
{
	UINT i;

	if (f == NULL || p == NULL)
	{
		return;
	}

	for (i = 0; i < LIST_NUM(p->Containers); ++i)
	{
		UINT j;
		const PROTO_CONTAINER *container = LIST_DATA(p->Containers, i);
		LIST *options = container->Options;
		FOLDER *ff = CfgCreateFolder(f, container->Name);

		LockList(options);

		for (j = 0; j < LIST_NUM(options); ++j)
		{
			const PROTO_OPTION *option = LIST_DATA(options, j);
			switch (option->Type)
			{
				case PROTO_OPTION_STRING:
					CfgAddStr(ff, option->Name, option->String);
					break;
				case PROTO_OPTION_BOOL:
					CfgAddBool(ff, option->Name, option->Bool);
					break;
				case PROTO_OPTION_UINT32:
					CfgAddInt(ff, option->Name, option->UInt32);
					break;
				default:
					Debug("SiWriteProtoCfg(): unhandled option type %u!\n", option->Type);
			}
		}

		UnlockList(options);
	}
}

// Read the traffic information
void SiLoadTraffic(FOLDER *parent, char *name, TRAFFIC *t)
{
	FOLDER *f;
	// Validate arguments
	if (t != NULL)
	{
		Zero(t, sizeof(TRAFFIC));
	}
	if (parent == NULL || name == NULL || t == NULL)
	{
		return;
	}

	f = CfgGetFolder(parent, name);

	if (f == NULL)
	{
		return;
	}

	SiLoadTrafficInner(f, "SendTraffic", &t->Send);
	SiLoadTrafficInner(f, "RecvTraffic", &t->Recv);
}
void SiLoadTrafficInner(FOLDER *parent, char *name, TRAFFIC_ENTRY *e)
{
	FOLDER *f;
	// Validate arguments
	if (e != NULL)
	{
		Zero(e, sizeof(TRAFFIC_ENTRY));
	}
	if (parent == NULL || name == NULL || e == NULL)
	{
		return;
	}

	f = CfgGetFolder(parent, name);
	if (f == NULL)
	{
		return;
	}

	e->BroadcastCount = CfgGetInt64(f, "BroadcastCount");
	e->BroadcastBytes = CfgGetInt64(f, "BroadcastBytes");
	e->UnicastCount = CfgGetInt64(f, "UnicastCount");
	e->UnicastBytes = CfgGetInt64(f, "UnicastBytes");
}

// Write the traffic information
void SiWriteTraffic(FOLDER *parent, char *name, TRAFFIC *t)
{
	FOLDER *f;
	// Validate arguments
	if (parent == NULL || name == NULL || t == NULL)
	{
		return;
	}

	f = CfgCreateFolder(parent, name);

	SiWriteTrafficInner(f, "SendTraffic", &t->Send);
	SiWriteTrafficInner(f, "RecvTraffic", &t->Recv);
}
void SiWriteTrafficInner(FOLDER *parent, char *name, TRAFFIC_ENTRY *e)
{
	FOLDER *f;
	// Validate arguments
	if (parent == NULL || name == NULL || e == NULL)
	{
		return;
	}

	f = CfgCreateFolder(parent, name);
	CfgAddInt64(f, "BroadcastCount", e->BroadcastCount);
	CfgAddInt64(f, "BroadcastBytes", e->BroadcastBytes);
	CfgAddInt64(f, "UnicastCount", e->UnicastCount);
	CfgAddInt64(f, "UnicastBytes", e->UnicastBytes);
}

// Thread for writing configuration file
void SiSaverThread(THREAD *thread, void *param)
{
	SERVER *s = (SERVER *)param;
	// Validate arguments
	if (thread == NULL || param == NULL)
	{
		return;
	}

	while (s->Halt == false)
	{
		// Save to the configuration file
		if (s->NoMoreSave == false)
		{
			SiWriteConfigurationFile(s);
		}

		Wait(s->SaveHaltEvent, s->AutoSaveConfigSpan);
	}
}

// Write to the configuration file
UINT SiWriteConfigurationFile(SERVER *s)
{
	UINT ret;
	// Validate arguments
	if (s == NULL)
	{
		return 0;
	}

	if (s->CfgRw == NULL)
	{
		return 0;
	}

	if (s->NoMoreSave)
	{
		return 0;
	}

	Lock(s->SaveCfgLock);
	{
		FOLDER *f;

		Debug("save: SiWriteConfigurationToCfg() start.\n");
		f = SiWriteConfigurationToCfg(s);
		Debug("save: SiWriteConfigurationToCfg() finished.\n");

		Debug("save: SaveCfgRw() start.\n");
		ret = SaveCfgRwEx(s->CfgRw, f, s->BackupConfigOnlyWhenModified ? s->ConfigRevision : INFINITE);
		Debug("save: SaveCfgRw() finished.\n");

		Debug("save: CfgDeleteFolder() start.\n");
		CfgDeleteFolder(f);
		Debug("save: CfgDeleteFolder() finished.\n");
	}
	Unlock(s->SaveCfgLock);

	return ret;
}

// Release the configuration
void SiFreeConfiguration(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	// Write to the configuration file
	SiWriteConfigurationFile(s);

	// Terminate the configuration file saving thread
	s->NoMoreSave = true;
	s->Halt = true;
	Set(s->SaveHaltEvent);
	WaitThread(s->SaveThread, INFINITE);

	ReleaseEvent(s->SaveHaltEvent);
	ReleaseThread(s->SaveThread);

	s->SaveHaltEvent = NULL;
	s->SaveThread = NULL;

	// Stop the protocols handler
	if (s->Proto != NULL)
	{
		ProtoDelete(s->Proto);
	}

	// Stop the IPsec server
	if (s->IPsecServer != NULL)
	{
		FreeIPsecServer(s->IPsecServer);
		s->IPsecServer = NULL;
	}

	// Terminate the DDNS client
	if (s->DDnsClient != NULL)
	{
		FreeDDNSClient(s->DDnsClient);
		s->DDnsClient = NULL;
	}

	// Terminate the VPN Azure client
	if (s->AzureClient != NULL)
	{
		FreeAzureClient(s->AzureClient);
		s->AzureClient = NULL;
	}

	FreeCfgRw(s->CfgRw);
	s->CfgRw = NULL;

	// Release the Ethernet 
	FreeEth();
}

// Initialize the StXxx related function
void StInit()
{
	if (server_lock != NULL)
	{
		return;
	}

	server_lock = NewLock();
}

// Release the StXxx related function
void StFree()
{
	DeleteLock(server_lock);
	server_lock = NULL;
}

// Start the server
void StStartServer(bool bridge)
{
	Lock(server_lock);
	{
		if (server != NULL)
		{
			// It has already started
			Unlock(server_lock);
			return;
		}

		// Create a server
		server = SiNewServer(bridge);
	}
	Unlock(server_lock);

//	StartCedarLog();
}

// Stop the server
void StStopServer()
{
	Lock(server_lock);
	{
		if (server == NULL)
		{
			// Not started
			Unlock(server_lock);
			return;
		}

		// Release the server
		SiReleaseServer(server);
		server = NULL;
	}
	Unlock(server_lock);

	StopCedarLog();
}

// Set the type of server
void SiSetServerType(SERVER *s, UINT type,
					 UINT ip, UINT num_port, UINT *ports,
					 char *controller_name, UINT controller_port, UCHAR *password, UINT weight, bool controller_only)
{
	bool bridge;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}
	if (type == SERVER_TYPE_FARM_MEMBER &&
		(num_port == 0 || ports == NULL || controller_name == NULL ||
		controller_port == 0 || password == NULL || num_port > MAX_PUBLIC_PORT_NUM))
	{
		return;
	}
	if (weight == 0)
	{
		weight = FARM_DEFAULT_WEIGHT;
	}

	bridge = s->Cedar->Bridge;

	Lock(s->lock);
	{
		// Update types
		s->UpdatedServerType = type;

		s->Weight = weight;

		// Set the value
		if (type == SERVER_TYPE_FARM_MEMBER)
		{
			StrCpy(s->ControllerName, sizeof(s->ControllerName), controller_name);
			s->ControllerPort = controller_port;
			if (IsZero(password, SHA1_SIZE) == false)
			{
				Copy(s->MemberPassword, password, SHA1_SIZE);
			}
			s->PublicIp = ip;
			s->NumPublicPort = num_port;
			if (s->PublicPorts != NULL)
			{
				Free(s->PublicPorts);
			}
			s->PublicPorts = ZeroMalloc(num_port * sizeof(UINT));
			Copy(s->PublicPorts, ports, num_port * sizeof(UINT));
		}

		if (type == SERVER_TYPE_FARM_CONTROLLER)
		{
			s->ControllerOnly = controller_only;
		}
	}
	Unlock(s->lock);

	// Restart the server
	SiRebootServer(bridge);
}

// Thread to restart the server
void SiRebootServerThread(THREAD *thread, void *param)
{
	// Validate arguments
	if (thread == NULL)
	{
		return;
	}

	if (server == NULL)
	{
		return;
	}

	// Stop the server
	StStopServer();

	// Start the server
	StStartServer((bool)param);
}

// Restart the server
void SiRebootServer(bool bridge)
{
	SiRebootServerEx(bridge, false);
}
void SiRebootServerEx(bool bridge, bool reset_setting)
{
	THREAD *t;

	server_reset_setting = reset_setting;

	t = NewThread(SiRebootServerThread, (void *)bridge);
	ReleaseThread(t);
}

// Set the state of the special listener
void SiApplySpecialListenerStatus(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	if (s->DynListenerDns != NULL)
	{
		*s->DynListenerDns->EnablePtr = s->EnableVpnOverDns;
		ApplyDynamicListener(s->DynListenerDns);
	}

	if (s->DynListenerIcmp != NULL)
	{
		*s->DynListenerIcmp->EnablePtr = s->EnableVpnOverIcmp;
		ApplyDynamicListener(s->DynListenerIcmp);
	}
}

// Stop all listeners
void SiStopAllListener(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	SiLockListenerList(s);
	{
		UINT i;
		LIST *o = NewListFast(NULL);
		for (i = 0;i < LIST_NUM(s->ServerListenerList);i++)
		{
			SERVER_LISTENER *e = LIST_DATA(s->ServerListenerList, i);
			Add(o, e);
		}

		for (i = 0;i < LIST_NUM(o);i++)
		{
			SERVER_LISTENER *e = LIST_DATA(o, i);
			SiDeleteListener(s, e->Port);
		}

		ReleaseList(o);
	}
	SiUnlockListenerList(s);

	ReleaseList(s->ServerListenerList);

	// Stop the VPN over ICMP listener
	FreeDynamicListener(s->DynListenerIcmp);
	s->DynListenerIcmp = NULL;

	// Stop the VPN over DNS listener
	FreeDynamicListener(s->DynListenerDns);
	s->DynListenerDns = NULL;
}

// Clean-up the server
void SiCleanupServer(SERVER *s)
{
	UINT i;
	CEDAR *c;
	LISTENER **listener_list;
	UINT num_listener;
	HUB **hub_list;
	UINT num_hub;
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	SiFreeDeadLockCheck(s);


	c = s->Cedar;

	if (s->ServerType == SERVER_TYPE_FARM_MEMBER)
	{
		// In the case of farm members, stop the connection to the farm controller
		SLog(c, "LS_STOP_FARM_MEMBER");
		SiStopConnectToController(s->FarmController);
		s->FarmController = NULL;
		SLog(c, "LS_STOP_FARM_MEMBER_2");
	}

	IncrementServerConfigRevision(s);

	SLog(c, "LS_END_2");

	SLog(c, "LS_STOP_ALL_LISTENER");
	// Stop all listeners
	LockList(c->ListenerList);
	{
		listener_list = ToArray(c->ListenerList);
		num_listener = LIST_NUM(c->ListenerList);
		for (i = 0;i < num_listener;i++)
		{
			AddRef(listener_list[i]->ref);
		}
	}
	UnlockList(c->ListenerList);

	for (i = 0;i < num_listener;i++)
	{
		StopListener(listener_list[i]);
		ReleaseListener(listener_list[i]);
	}
	Free(listener_list);
	SLog(c, "LS_STOP_ALL_LISTENER_2");

	SLog(c, "LS_STOP_ALL_HUB");
	// Stop all HUBs
	LockList(c->HubList);
	{
		hub_list = ToArray(c->HubList);
		num_hub = LIST_NUM(c->HubList);
		for (i = 0;i < num_hub;i++)
		{
			AddRef(hub_list[i]->ref);
		}
	}
	UnlockList(c->HubList);

	for (i = 0;i < num_hub;i++)
	{
		StopHub(hub_list[i]);
		ReleaseHub(hub_list[i]);
	}
	Free(hub_list);
	SLog(c, "LS_STOP_ALL_HUB_2");

	// Release the configuration
	SiFreeConfiguration(s);

	// Stop the Cedar
	SLog(c, "LS_STOP_CEDAR");
	StopCedar(s->Cedar);
	SLog(c, "LS_STOP_CEDAR_2");

	// Stop all listeners
	SiStopAllListener(s);

	ReleaseIntList(s->PortsUDP);

	if (s->ServerType == SERVER_TYPE_FARM_CONTROLLER)
	{
		// In the case of farm controller
		UINT i;

		SLog(c, "LS_STOP_FARM_CONTROL");

		// Stop the farm controling
		SiStopFarmControl(s);

		// Release the farm member information
		ReleaseList(s->FarmMemberList);
		s->FarmMemberList = NULL;

		for (i = 0;i < LIST_NUM(s->Me->HubList);i++)
		{
			Free(LIST_DATA(s->Me->HubList, i));
		}
		ReleaseList(s->Me->HubList);

		Free(s->Me);

		SLog(c, "LS_STOP_FARM_CONTROL_2");
	}

	if (s->PublicPorts != NULL)
	{
		Free(s->PublicPorts);
	}

	SLog(s->Cedar, "LS_END_1");
	SLog(s->Cedar, "L_LINE");

#ifdef	ENABLE_AZURE_SERVER
	if (s->AzureServer != NULL)
	{
		FreeAzureServer(s->AzureServer);
	}
#endif	// ENABLE_AZURE_SERVER

	ReleaseCedar(s->Cedar);
	DeleteLock(s->lock);
	DeleteLock(s->SaveCfgLock);

	StopKeep(s->Keep);

	FreeEraser(s->Eraser);


	FreeLog(s->Logger);

	FreeSysLog(s->Syslog);
	DeleteLock(s->SyslogLock);

	FreeServerCapsCache(s);

	SiFreeHubCreateHistory(s);

	// Stop the debug log
	FreeTinyLog(s->DebugLog);

	DeleteLock(s->TasksFromFarmControllerLock);
	DeleteLock(s->OpenVpnSstpConfigLock);


	Free(s);
}

// Release the server
void SiReleaseServer(SERVER *s)
{
	// Validate arguments
	if (s == NULL)
	{
		return;
	}

	if (Release(s->ref) == 0)
	{
		SiCleanupServer(s);
	}
}

// Get the URL of the member selector
bool SiGetMemberSelectorUrl(char *url, UINT url_size)
{
	BUF *b;
	bool ret = false;
	// Validate arguments
	if (url == NULL)
	{
		return false;
	}

	b = ReadDump(MEMBER_SELECTOR_TXT_FILENAME);
	if (b == NULL)
	{
		return false;
	}

	while (true)
	{
		char *line = CfgReadNextLine(b);
		if (line == NULL)
		{
			break;
		}

		Trim(line);

		if (IsEmptyStr(line) == false && ret == false)
		{
			StrCpy(url, url_size, line);
			ret = true;
		}

		Free(line);
	}

	FreeBuf(b);

	return ret;
}

// Specify the farm member for the next processing
FARM_MEMBER *SiGetNextFarmMember(SERVER *s, CONNECTION *c, HUB *h)
{
	UINT i, num;
	UINT min_point = 0;
	FARM_MEMBER *ret = NULL;
	PACK *p;
	char url[MAX_SIZE];
	// Validate arguments
	if (s == NULL || s->ServerType != SERVER_TYPE_FARM_CONTROLLER || c == NULL || h == NULL)
	{
		return NULL;
	}

	num = LIST_NUM(s->FarmMemberList);
	if (num == 0)
	{
		return NULL;
	}

	if (SiGetMemberSelectorUrl(url, sizeof(url)))
	{
		UINT64 ret_key = 0;
		// Generate the data for the member selector
		p = NewPack();
		for (i = 0;i < num;i++)
		{
			UINT num_sessions;
			UINT max_sessions;
			FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);
			bool do_not_select = false;
			if (s->ControllerOnly)
			{
				if (f->Me)
				{
					// No to select myself in the case of ControllerOnly
					do_not_select = true;
				}
			}

			if (f->Me == false)
			{
				num_sessions = f->NumSessions;
				max_sessions = f->MaxSessions;
			}
			else
			{
				num_sessions = Count(s->Cedar->CurrentSessions);
				max_sessions = GetServerCapsInt(s, "i_max_sessions");
			}

			if (max_sessions == 0)
			{
				max_sessions = GetServerCapsInt(s, "i_max_sessions");
			}

			if (num_sessions >= max_sessions)
			{
				do_not_select = true;
			}

			if (true)
			{
				UINT point = f->Point;
				char public_ip_str[MAX_SIZE];

				IPToStr32(public_ip_str, sizeof(public_ip_str), f->Ip);

				PackAddIntEx(p, "Point", point, i, num);
				PackAddInt64Ex(p, "Key", (UINT64)f, i, num);
				PackAddStrEx(p, "Hostname", f->hostname, i, num);
				PackAddStrEx(p, "PublicIp", public_ip_str, i, num);
				PackAddIntEx(p, "NumSessions", num_sessions, i, num);
				PackAddIntEx(p, "MaxSessions", max_sessions, i, num);
				PackAddIntEx(p, "AssignedClientLicense", f->AssignedClientLicense, i, num);
				PackAddIntEx(p, "AssignedBridgeLicense", f->AssignedBridgeLicense, i, num);
				PackAddIntEx(p, "Weight", f->Weight, i, num);
				PackAddDataEx(p, "RandomKey", f->RandomKey, SHA1_SIZE, i, num);
				PackAddIntEx(p, "NumTcpConnections", f->NumTcpConnections, i, num);
				PackAddIntEx(p, "NumHubs", LIST_NUM(f->HubList), i, num);
				PackAddBoolEx(p, "Me", f->Me, i, num);
				PackAddTime64Ex(p, "ConnectedTime", f->ConnectedTime, i, num);
				PackAddInt64Ex(p, "SystemId", f->SystemId, i, num);
				PackAddBoolEx(p, "DoNotSelect", do_not_select, i, num);
			}
		}

		if (true)
		{
			char client_ip_str[MAX_SIZE];
			UINT client_port = 0;
			UINT server_port = 0;
			SOCK *s = c->FirstSock;

			Zero(client_ip_str, sizeof(client_ip_str));
			if (s != NULL)
			{
				IPToStr(client_ip_str, sizeof(client_ip_str), &s->RemoteIP);
				client_port = s->RemotePort;
				server_port = s->LocalPort;
			}

			PackAddStr(p, "ClientIp", client_ip_str);
			PackAddInt(p, "ClientPort", client_port);
			PackAddInt(p, "ServerPort", server_port);

			PackAddInt(p, "ClientBuild", c->ClientBuild);
			PackAddStr(p, "CipherName", c->CipherName);
			PackAddStr(p, "ClientStr", c->ClientStr);
			PackAddInt(p, "ClientVer", c->ClientVer);
			PackAddTime64(p, "ConnectedTime", Tick64ToTime64(c->ConnectedTick));

			PackAddStr(p, "HubName", h->Name);
			PackAddBool(p, "StaticHub", h->Type == HUB_TYPE_FARM_STATIC);
		}

		PackAddInt(p, "NumMembers", num);

		// Make the member selector choose a member
		UnlockList(s->FarmMemberList);
		Unlock(s->Cedar->CedarSuperLock);
		{
			PACK *ret;

			Debug("Calling %s ...\n", url);

			ret = WpcCall(url, NULL, MEMBER_SELECTOR_CONNECT_TIMEOUT, MEMBER_SELECTOR_DATA_TIMEOUT,
				"Select", p, NULL, NULL, NULL);

			if (GetErrorFromPack(ret) == ERR_NO_ERROR)
			{
				ret_key = PackGetInt64(ret, "Key");
				Debug("Ret Key = %I64u\n", ret_key);
			}
			else
			{
				Debug("Error: %u\n", GetErrorFromPack(ret));
			}

			FreePack(ret);
		}
		Lock(s->Cedar->CedarSuperLock);
		LockList(s->FarmMemberList);

		FreePack(p);

		if (ret_key != 0)
		{
			FARM_MEMBER *f = (FARM_MEMBER *)ret_key;
			if (IsInList(s->FarmMemberList, f))
			{
				Debug("Farm Member Selected by Selector: %s\n", f->hostname);

				return f;
			}
			else
			{
				Debug("Farm Member Key = %I64u Not Found.\n", ret_key);
			}
		}
		else
		{
			// The member selector failed to select a member
			return NULL;
		}
	}

	num = LIST_NUM(s->FarmMemberList);
	if (num == 0)
	{
		return NULL;
	}

	for (i = 0;i < num;i++)
	{
		UINT num_sessions;
		UINT max_sessions;
		FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);
		if (s->ControllerOnly)
		{
			if (f->Me)
			{
				// No to select myself in the case of ControllerOnly
				continue;
			}
		}

		if (f->Me == false)
		{
			num_sessions = f->NumSessions;
			max_sessions = f->MaxSessions;
		}
		else
		{
			num_sessions = Count(s->Cedar->CurrentSessions);
			max_sessions = GetServerCapsInt(s, "i_max_sessions");
		}

		if (max_sessions == 0)
		{
			max_sessions = GetServerCapsInt(s, "i_max_sessions");
		}

		if (num_sessions < max_sessions)
		{
			if (f->Point >= min_point)
			{
				min_point = f->Point;
				ret = f;
			}
		}
	}

	return ret;
}

// Receive a HUB enumeration directive
void SiCalledEnumHub(SERVER *s, PACK *p, PACK *req)
{
	UINT i;
	CEDAR *c;
	// Validate arguments
	if (s == NULL || p == NULL || req == NULL)
	{
		return;
	}


	c = s->Cedar;

	LockList(c->HubList);
	{
		UINT num = LIST_NUM(c->HubList);
		for (i = 0;i < num;i++)
		{
			HUB *h = LIST_DATA(c->HubList, i);
			Lock(h->lock);
			{
				PackAddStrEx(p, "HubName", h->Name, i, num);
				PackAddIntEx(p, "HubType", h->Type, i, num);
				PackAddIntEx(p, "NumSession", Count(h->NumSessions), i, num);

				PackAddIntEx(p, "NumSessions", LIST_NUM(h->SessionList), i, num);
				PackAddIntEx(p, "NumSessionsClient", Count(h->NumSessionsClient), i, num);
				PackAddIntEx(p, "NumSessionsBridge", Count(h->NumSessionsBridge), i, num);

				PackAddIntEx(p, "NumMacTables", HASH_LIST_NUM(h->MacHashTable), i, num);

				PackAddIntEx(p, "NumIpTables", LIST_NUM(h->IpTable), i, num);

				PackAddTime64Ex(p, "LastCommTime", h->LastCommTime, i, num);
				PackAddTime64Ex(p, "CreatedTime", h->CreatedTime, i, num);
			}
			Unlock(h->lock);
		}
	}
	UnlockList(c->HubList);

	PackAddInt(p, "Point", SiGetPoint(s));
	PackAddInt(p, "NumTcpConnections", Count(s->Cedar->CurrentTcpConnections));
	PackAddInt(p, "NumTotalSessions", Count(s->Cedar->CurrentSessions));
	PackAddInt(p, "MaxSessions", GetServerCapsInt(s, "i_max_sessions"));

	PackAddInt(p, "AssignedClientLicense", Count(s->Cedar->AssignedClientLicense));
	PackAddInt(p, "AssignedBridgeLicense", Count(s->Cedar->AssignedBridgeLicense));

	PackAddData(p, "RandomKey", s->MyRandomKey, SHA1_SIZE);


	Lock(c->TrafficLock);
	{
		OutRpcTraffic(p, c->Traffic);
	}
	Unlock(c->TrafficLock);

	LockList(c->TrafficDiffList);
	{
		UINT num = LIST_NUM(c->TrafficDiffList);
		UINT i;

		for (i = 0;i < num;i++)
		{
			TRAFFIC_DIFF *d = LIST_DATA(c->TrafficDiffList, i);

			PackAddIntEx(p, "TdType", d->Type, i, num);
			PackAddStrEx(p, "TdHubName", d->HubName, i, num);
			PackAddStrEx(p, "TdName", d->Name, i, num);

			OutRpcTrafficEx(&d->Traffic, p, i, num);

			Free(d->HubName);
			Free(d->Name);
			Free(d);
		}

		DeleteAll(c->TrafficDiffList);
	}
	UnlockList(c->TrafficDiffList);
}

// Receive a HUB delete directive
void SiCalledDeleteHub(SERVER *s, PACK *p)
{
	char name[MAX_SIZE];
	HUB *h;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return;
	}

	if (PackGetStr(p, "HubName", name, sizeof(name)) == false)
	{
		return;
	}

	LockHubList(s->Cedar);

	h = GetHub(s->Cedar, name);
	if (h == NULL)
	{
		UnlockHubList(s->Cedar);
		return;
	}
	UnlockHubList(s->Cedar);

	SetHubOffline(h);

	LockHubList(s->Cedar);

	DelHubEx(s->Cedar, h, true);

	UnlockHubList(s->Cedar);

	ReleaseHub(h);
}

// Receive a HUB update directive
void SiCalledUpdateHub(SERVER *s, PACK *p)
{
	char name[MAX_SIZE];
	UINT type;
	HUB_OPTION o;
	HUB_LOG log;
	bool save_packet_log;
	UINT packet_log_switch_type;
	UINT packet_log_config[NUM_PACKET_LOG];
	bool save_security_log;
	bool type_changed = false;
	UINT security_log_switch_type;
	UINT i;
	HUB *h;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return;
	}

	PackGetStr(p, "HubName", name, sizeof(name));
	type = PackGetInt(p, "HubType");
	Zero(&o, sizeof(o));
	o.MaxSession = PackGetInt(p, "MaxSession");
	o.NoArpPolling = PackGetBool(p, "NoArpPolling");
	o.NoIPv6AddrPolling = PackGetBool(p, "NoIPv6AddrPolling");
	o.FilterPPPoE = PackGetBool(p, "FilterPPPoE");
	o.YieldAfterStorePacket = PackGetBool(p, "YieldAfterStorePacket");
	o.NoSpinLockForPacketDelay = PackGetBool(p, "NoSpinLockForPacketDelay");
	o.BroadcastStormDetectionThreshold = PackGetInt(p, "BroadcastStormDetectionThreshold");
	o.ClientMinimumRequiredBuild = PackGetInt(p, "ClientMinimumRequiredBuild");
	o.FixForDLinkBPDU = PackGetBool(p, "FixForDLinkBPDU");
	o.BroadcastLimiterStrictMode = PackGetBool(p, "BroadcastLimiterStrictMode");
	o.NoLookBPDUBridgeId = PackGetBool(p, "NoLookBPDUBridgeId");
	o.NoManageVlanId = PackGetBool(p, "NoManageVlanId");
	o.MaxLoggedPacketsPerMinute = PackGetInt(p, "MaxLoggedPacketsPerMinute");
	o.FloodingSendQueueBufferQuota = PackGetInt(p, "FloodingSendQueueBufferQuota");
	o.DoNotSaveHeavySecurityLogs = PackGetBool(p, "DoNotSaveHeavySecurityLogs");
	o.DropBroadcastsInPrivacyFilterMode = PackGetBool(p, "DropBroadcastsInPrivacyFilterMode");
	o.DropArpInPrivacyFilterMode = PackGetBool(p, "DropArpInPrivacyFilterMode");
	o.AllowSameUserInPrivacyFilterMode= PackGetBool(p, "AllowSameUserInPrivacyFilterMode");
	o.SuppressClientUpdateNotification = PackGetBool(p, "SuppressClientUpdateNotification");
	o.AssignVLanIdByRadiusAttribute = PackGetBool(p, "AssignVLanIdByRadiusAttribute");
	o.DenyAllRadiusLoginWithNoVlanAssign = PackGetBool(p, "DenyAllRadiusLoginWithNoVlanAssign");
	o.SecureNAT_RandomizeAssignIp = PackGetBool(p, "SecureNAT_RandomizeAssignIp");
	o.DetectDormantSessionInterval = PackGetInt(p, "DetectDormantSessionInterval");
	o.VlanTypeId = PackGetInt(p, "VlanTypeId");
	o.NoPhysicalIPOnPacketLog = PackGetBool(p, "NoPhysicalIPOnPacketLog");
	if (o.VlanTypeId == 0)
	{
		o.VlanTypeId = MAC_PROTO_TAGVLAN;
	}
	o.FilterOSPF = PackGetBool(p, "FilterOSPF");
	o.FilterIPv4 = PackGetBool(p, "FilterIPv4");
	o.FilterIPv6 = PackGetBool(p, "FilterIPv6");
	o.FilterNonIP = PackGetBool(p, "FilterNonIP");
	o.NoIPv4PacketLog = PackGetBool(p, "NoIPv4PacketLog");
	o.NoIPv6PacketLog = PackGetBool(p, "NoIPv6PacketLog");
	o.FilterBPDU = PackGetBool(p, "FilterBPDU");
	o.NoIPv6DefaultRouterInRAWhenIPv6 = PackGetBool(p, "NoIPv6DefaultRouterInRAWhenIPv6");
	o.NoMacAddressLog = PackGetBool(p, "NoMacAddressLog");
	o.ManageOnlyPrivateIP = PackGetBool(p, "ManageOnlyPrivateIP");
	o.ManageOnlyLocalUnicastIPv6 = PackGetBool(p, "ManageOnlyLocalUnicastIPv6");
	o.DisableIPParsing = PackGetBool(p, "DisableIPParsing");
	o.NoIpTable = PackGetBool(p, "NoIpTable");
	o.NoEnum = PackGetBool(p, "NoEnum");
	o.AdjustTcpMssValue = PackGetInt(p, "AdjustTcpMssValue");
	o.DisableAdjustTcpMss = PackGetBool(p, "DisableAdjustTcpMss");
	o.NoDhcpPacketLogOutsideHub = PackGetBool(p, "NoDhcpPacketLogOutsideHub");
	o.DisableHttpParsing = PackGetBool(p, "DisableHttpParsing");
	o.DisableUdpAcceleration = PackGetBool(p, "DisableUdpAcceleration");
	o.DisableUdpFilterForLocalBridgeNic = PackGetBool(p, "DisableUdpFilterForLocalBridgeNic");
	o.ApplyIPv4AccessListOnArpPacket = PackGetBool(p, "ApplyIPv4AccessListOnArpPacket");
	o.RemoveDefGwOnDhcpForLocalhost = PackGetBool(p, "RemoveDefGwOnDhcpForLocalhost");
	o.SecureNAT_MaxTcpSessionsPerIp = PackGetInt(p, "SecureNAT_MaxTcpSessionsPerIp");
	o.SecureNAT_MaxTcpSynSentPerIp = PackGetInt(p, "SecureNAT_MaxTcpSynSentPerIp");
	o.SecureNAT_MaxUdpSessionsPerIp = PackGetInt(p, "SecureNAT_MaxUdpSessionsPerIp");
	o.SecureNAT_MaxDnsSessionsPerIp = PackGetInt(p, "SecureNAT_MaxDnsSessionsPerIp");
	o.SecureNAT_MaxIcmpSessionsPerIp = PackGetInt(p, "SecureNAT_MaxIcmpSessionsPerIp");
	o.AccessListIncludeFileCacheLifetime = PackGetInt(p, "AccessListIncludeFileCacheLifetime");
	if (o.AccessListIncludeFileCacheLifetime == 0)
	{
		o.AccessListIncludeFileCacheLifetime = ACCESS_LIST_INCLUDE_FILE_CACHE_LIFETIME;
	}
	o.DisableKernelModeSecureNAT = PackGetBool(p, "DisableKernelModeSecureNAT");
	o.DisableIpRawModeSecureNAT = PackGetBool(p, "DisableIpRawModeSecureNAT");
	o.DisableUserModeSecureNAT = PackGetBool(p, "DisableUserModeSecureNAT");
	o.DisableCheckMacOnLocalBridge = PackGetBool(p, "DisableCheckMacOnLocalBridge");
	o.DisableCorrectIpOffloadChecksum = PackGetBool(p, "DisableCorrectIpOffloadChecksum");
	o.UseHubNameAsDhcpUserClassOption = PackGetBool(p, "UseHubNameAsDhcpUserClassOption");
	o.UseHubNameAsRadiusNasId = PackGetBool(p, "UseHubNameAsRadiusNasId");
	o.AllowEapMatchUserByCert = PackGetBool(p, "AllowEapMatchUserByCert");

	save_packet_log = PackGetInt(p, "SavePacketLog");
	packet_log_switch_type = PackGetInt(p, "PacketLogSwitchType");
	for (i = 0;i < NUM_PACKET_LOG;i++)
	{
		packet_log_config[i] = PackGetIntEx(p, "PacketLogConfig", i);
	}
	save_security_log = PackGetInt(p, "SaveSecurityLog");
	security_log_switch_type = PackGetInt(p, "SecurityLogSwitchType");

	Zero(&log, sizeof(log));
	log.SavePacketLog = save_packet_log;
	log.PacketLogSwitchType = packet_log_switch_type;
	Copy(log.PacketLogConfig, packet_log_config, sizeof(log.PacketLogConfig));
	log.SaveSecurityLog = save_security_log;
	log.SecurityLogSwitchType = security_log_switch_type;

	h = GetHub(s->Cedar, name);
	if (h == NULL)
	{
		return;
	}

	h->FarmMember_MaxSessionClient = PackGetInt(p, "MaxSessionClient");
	h->FarmMember_MaxSessionBridge = PackGetInt(p, "MaxSessionBridge");
	h->FarmMember_MaxSessionClientBridgeApply = PackGetBool(p, "MaxSessionClientBridgeApply");

	if (h->FarmMember_MaxSessionClientBridgeApply == false)
	{
		h->FarmMember_MaxSessionClient = INFINITE;
		h->FarmMember_MaxSessionBridge = INFINITE;
	}

	Lock(h->lock);
	{
		Copy(h->Option, &o, sizeof(HUB_OPTION));
		PackGetData2(p, "SecurePassword", h->SecurePassword, SHA1_SIZE);
		PackGetData2(p, "HashedPassword", h->HashedPassword, SHA1_SIZE);
	}
	Unlock(h->lock);

	SetHubLogSetting(h, &log);

	if (h->Type != type)
	{
		h->Type = type;
		type_changed = true;
	}

	LockList(h->AccessList);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(h->AccessList);i++)
		{
			ACCESS *a = LIST_DATA(h->AccessList, i);
			Free(a);
		}
		DeleteAll(h->AccessList);
	}
	UnlockList(h->AccessList);

	for (i = 0;i < SiNumAccessFromPack(p);i++)
	{
		ACCESS *a = SiPackToAccess(p, i);
		AddAccessList(h, a);
		Free(a);
	}

	if (PackGetBool(p, "EnableSecureNAT"))
	{
		VH_OPTION t;
		bool changed;

		InVhOption(&t, p);

		changed = Cmp(h->SecureNATOption, &t, sizeof(VH_OPTION)) == 0 ? false : true;
		Copy(h->SecureNATOption, &t, sizeof(VH_OPTION));

		EnableSecureNAT(h, true);

		if (changed)
		{
			Lock(h->lock_online);
			{
				if (h->SecureNAT != NULL)
				{
					SetVirtualHostOption(h->SecureNAT->Nat->Virtual, &t);
					Debug("SiCalledUpdateHub: SecureNAT Updated.\n");
				}
			}
			Unlock(h->lock_online);
		}
	}
	else
	{
		EnableSecureNAT(h, false);
		Debug("SiCalledUpdateHub: SecureNAT Disabled.\n");
	}

	if (type_changed)
	{
		// Remove all sessions since the type of HUB has been changed
		if (h->Offline == false)
		{
			SetHubOffline(h);
			SetHubOnline(h);
		}
	}

	ReleaseHub(h);
}

// Inspect the ticket
bool SiCheckTicket(HUB *h, UCHAR *ticket, char *username, UINT username_size, char *usernamereal, UINT usernamereal_size, POLICY *policy, char *sessionname, UINT sessionname_size, char *groupname, UINT groupname_size)
{
	bool ret = false;
	// Validate arguments
	if (h == NULL || ticket == NULL || username == NULL || usernamereal == NULL || policy == NULL || sessionname == NULL)
	{
		return false;
	}

	LockList(h->TicketList);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(h->TicketList);i++)
		{
			TICKET *t = LIST_DATA(h->TicketList, i);
			if (Cmp(t->Ticket, ticket, SHA1_SIZE) == 0)
			{
				ret = true;
				StrCpy(username, username_size, t->Username);
				StrCpy(usernamereal, usernamereal_size, t->UsernameReal);
				StrCpy(sessionname, sessionname_size, t->SessionName);
				StrCpy(groupname, groupname_size, t->GroupName);
				Copy(policy, &t->Policy, sizeof(POLICY));
				Delete(h->TicketList, t);
				Free(t);
				break;
			}
		}
	}
	UnlockList(h->TicketList);

	return ret;
}

// Receive a MAC address deletion directive
void SiCalledDeleteMacTable(SERVER *s, PACK *p)
{
	UINT key;
	char hubname[MAX_HUBNAME_LEN + 1];
	HUB *h;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return;
	}

	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return;
	}
	key = PackGetInt(p, "Key");

	LockHubList(s->Cedar);
	{
		h = GetHub(s->Cedar, hubname);
	}
	UnlockHubList(s->Cedar);

	if (h == NULL)
	{
		return;
	}

	LockHashList(h->MacHashTable);
	{
		MAC_TABLE_ENTRY *e = HashListKeyToPointer(h->MacHashTable, key);
		DeleteHash(h->MacHashTable, e);
		Free(e);
	}
	UnlockHashList(h->MacHashTable);

	ReleaseHub(h);
}

// Receive an IP address delete directive
void SiCalledDeleteIpTable(SERVER *s, PACK *p)
{
	UINT key;
	char hubname[MAX_HUBNAME_LEN + 1];
	HUB *h;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return;
	}

	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return;
	}
	key = PackGetInt(p, "Key");

	LockHubList(s->Cedar);
	{
		h = GetHub(s->Cedar, hubname);
	}
	UnlockHubList(s->Cedar);

	if (h == NULL)
	{
		return;
	}

	LockList(h->IpTable);
	{
		if (IsInList(h->IpTable, (void *)key))
		{
			IP_TABLE_ENTRY *e = (IP_TABLE_ENTRY *)key;
			Delete(h->IpTable, e);
			Free(e);
		}
	}
	UnlockList(h->IpTable);

	ReleaseHub(h);
}

// Receive a session deletion directive
void SiCalledDeleteSession(SERVER *s, PACK *p)
{
	char name[MAX_SESSION_NAME_LEN + 1];
	char hubname[MAX_HUBNAME_LEN + 1];
	HUB *h;
	SESSION *sess;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return;
	}

	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return;
	}
	if (PackGetStr(p, "SessionName", name, sizeof(name)) == false)
	{
		return;
	}

	LockHubList(s->Cedar);
	{
		h = GetHub(s->Cedar, hubname);
	}
	UnlockHubList(s->Cedar);

	if (h == NULL)
	{
		return;
	}

	sess = GetSessionByName(h, name);

	if (sess != NULL)
	{
		if (sess->BridgeMode == false && sess->LinkModeServer == false && sess->SecureNATMode == false)
		{
			StopSession(sess);
		}
		ReleaseSession(sess);
	}

	ReleaseHub(h);
}

// Receive a log file reading directive
PACK *SiCalledReadLogFile(SERVER *s, PACK *p)
{
	RPC_READ_LOG_FILE t;
	PACK *ret;
	char filepath[MAX_PATH];
	UINT offset;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NULL;
	}

	PackGetStr(p, "FilePath", filepath, sizeof(filepath));
	offset = PackGetInt(p, "Offset");

	Zero(&t, sizeof(t));

	SiReadLocalLogFile(s, filepath, offset, &t);

	ret = NewPack();

	OutRpcReadLogFile(ret, &t);
	FreeRpcReadLogFile(&t);

	return ret;
}

// Receive a log file enumeration directive
PACK *SiCalledEnumLogFileList(SERVER *s, PACK *p)
{
	RPC_ENUM_LOG_FILE t;
	PACK *ret;
	char hubname[MAX_HUBNAME_LEN + 1];
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NULL;
	}

	PackGetStr(p, "HubName", hubname, sizeof(hubname));

	Zero(&t, sizeof(t));

	SiEnumLocalLogFileList(s, hubname, &t);

	ret = NewPack();

	OutRpcEnumLogFile(ret, &t);
	FreeRpcEnumLogFile(&t);

	return ret;
}

// Receive a session information directive
PACK *SiCalledGetSessionStatus(SERVER *s, PACK *p)
{
	RPC_SESSION_STATUS t;
	ADMIN a;
	PACK *ret;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NULL;
	}

	Zero(&t, sizeof(t));
	InRpcSessionStatus(&t, p);

	Zero(&a, sizeof(a));
	a.Server = s;
	a.ServerAdmin = true;

	if (StGetSessionStatus(&a, &t) != ERR_NO_ERROR)
	{
		FreeRpcSessionStatus(&t);
		return NULL;
	}

	ret = NewPack();

	OutRpcSessionStatus(ret, &t);

	FreeRpcSessionStatus(&t);

	return ret;
}

// IP table enumeration directive
PACK *SiCalledEnumIpTable(SERVER *s, PACK *p)
{
	char hubname[MAX_HUBNAME_LEN + 1];
	RPC_ENUM_IP_TABLE t;
	PACK *ret;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NewPack();
	}
	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return NewPack();
	}
	Zero(&t, sizeof(t));

	SiEnumIpTable(s, hubname, &t);

	ret = NewPack();
	OutRpcEnumIpTable(ret, &t);
	FreeRpcEnumIpTable(&t);

	return ret;
}

// MAC table enumeration directive
PACK *SiCalledEnumMacTable(SERVER *s, PACK *p)
{
	char hubname[MAX_HUBNAME_LEN + 1];
	RPC_ENUM_MAC_TABLE t;
	PACK *ret;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NewPack();
	}
	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return NewPack();
	}
	Zero(&t, sizeof(t));

	SiEnumMacTable(s, hubname, &t);

	ret = NewPack();
	OutRpcEnumMacTable(ret, &t);
	FreeRpcEnumMacTable(&t);

	return ret;
}

// NAT status acquisition directive
PACK *SiCalledGetNatStatus(SERVER *s, PACK *p)
{
	char hubname[MAX_HUBNAME_LEN + 1];
	RPC_NAT_STATUS t;
	PACK *ret;
	HUB *h;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NewPack();
	}
	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return NewPack();
	}
	Zero(&t, sizeof(t));

	LockHubList(s->Cedar);
	{
		h = GetHub(s->Cedar, hubname);
	}
	UnlockHubList(s->Cedar);

	if (h != NULL)
	{
		Lock(h->lock_online);
		{
			if (h->SecureNAT != NULL)
			{
				NtGetStatus(h->SecureNAT->Nat, &t);
			}
		}
		Unlock(h->lock_online);
	}

	ReleaseHub(h);

	ret = NewPack();
	OutRpcNatStatus(ret, &t);
	FreeRpcNatStatus(&t);

	return ret;
}

// DHCP table enumeration directive
PACK *SiCalledEnumDhcp(SERVER *s, PACK *p)
{
	char hubname[MAX_HUBNAME_LEN + 1];
	RPC_ENUM_DHCP t;
	PACK *ret;
	HUB *h;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NewPack();
	}
	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return NewPack();
	}
	Zero(&t, sizeof(t));

	LockHubList(s->Cedar);
	{
		h = GetHub(s->Cedar, hubname);
	}
	UnlockHubList(s->Cedar);

	if (h != NULL)
	{
		Lock(h->lock_online);
		{
			if (h->SecureNAT != NULL)
			{
				NtEnumDhcpList(h->SecureNAT->Nat, &t);
			}
		}
		Unlock(h->lock_online);
	}

	ReleaseHub(h);

	ret = NewPack();
	OutRpcEnumDhcp(ret, &t);
	FreeRpcEnumDhcp(&t);

	return ret;
}

// NAT table enumeration directive
PACK *SiCalledEnumNat(SERVER *s, PACK *p)
{
	char hubname[MAX_HUBNAME_LEN + 1];
	RPC_ENUM_NAT t;
	PACK *ret;
	HUB *h;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NewPack();
	}
	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return NewPack();
	}
	Zero(&t, sizeof(t));

	LockHubList(s->Cedar);
	{
		h = GetHub(s->Cedar, hubname);
	}
	UnlockHubList(s->Cedar);

	if (h != NULL)
	{
		Lock(h->lock_online);
		{
			if (h->SecureNAT != NULL)
			{
				NtEnumNatList(h->SecureNAT->Nat, &t);
			}
		}
		Unlock(h->lock_online);
	}

	ReleaseHub(h);

	ret = NewPack();
	OutRpcEnumNat(ret, &t);
	FreeRpcEnumNat(&t);

	return ret;
}

// Receive a session enumeration directive
PACK *SiCalledEnumSession(SERVER *s, PACK *p)
{
	char hubname[MAX_HUBNAME_LEN + 1];
	RPC_ENUM_SESSION t;
	PACK *ret;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NewPack();
	}
	if (PackGetStr(p, "HubName", hubname, sizeof(hubname)) == false)
	{
		return NewPack();
	}
	Zero(&t, sizeof(t));

	SiEnumLocalSession(s, hubname, &t);

	ret = NewPack();
	OutRpcEnumSession(ret, &t);
	FreeRpcEnumSession(&t);

	return ret;
}

// Receive a ticket creation directive
PACK *SiCalledCreateTicket(SERVER *s, PACK *p)
{
	char username[MAX_SIZE];
	char hubname[MAX_SIZE];
	char groupname[MAX_SIZE];
	char realusername[MAX_SIZE];
	char sessionname[MAX_SESSION_NAME_LEN + 1];
	POLICY policy;
	UCHAR ticket[SHA1_SIZE];
	char ticket_str[MAX_SIZE];
	HUB *h;
	UINT i;
	PACK *ret;
	TICKET *t;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return NewPack();
	}

	PackGetStr(p, "UserName", username, sizeof(username));
	PackGetStr(p, "GroupName", groupname, sizeof(groupname));
	PackGetStr(p, "HubName", hubname, sizeof(hubname));
	PackGetStr(p, "RealUserName", realusername, sizeof(realusername));
	PackGetStr(p, "SessionName", sessionname, sizeof(sessionname));

	InRpcPolicy(&policy, p);
	if (PackGetDataSize(p, "Ticket") == SHA1_SIZE)
	{
		PackGetData(p, "Ticket", ticket);
	}

	BinToStr(ticket_str, sizeof(ticket_str), ticket, SHA1_SIZE);

	SLog(s->Cedar, "LS_TICKET_2", hubname, username, realusername, sessionname,
		ticket_str, TICKET_EXPIRES / 1000);

	// Get the HUB
	h = GetHub(s->Cedar, hubname);
	if (h == NULL)
	{
		return NewPack();
	}

	LockList(h->TicketList);
	{
		LIST *o = NewListFast(NULL);
		// Discard old tickets
		for (i = 0;i < LIST_NUM(h->TicketList);i++)
		{
			TICKET *t = LIST_DATA(h->TicketList, i);
			if ((t->CreatedTick + TICKET_EXPIRES) < Tick64())
			{
				Add(o, t);
			}
		}
		for (i = 0;i < LIST_NUM(o);i++)
		{
			TICKET *t = LIST_DATA(o, i);
			Delete(h->TicketList, t);
			Free(t);
		}
		ReleaseList(o);

		// Create a ticket
		t = ZeroMalloc(sizeof(TICKET));
		t->CreatedTick = Tick64();
		Copy(&t->Policy, &policy, sizeof(POLICY));
		Copy(t->Ticket, ticket, SHA1_SIZE);
		StrCpy(t->Username, sizeof(t->Username), username);
		StrCpy(t->UsernameReal, sizeof(t->UsernameReal), realusername);
		StrCpy(t->GroupName, sizeof(t->GroupName), groupname);
		StrCpy(t->SessionName, sizeof(t->SessionName), sessionname);

		Add(h->TicketList, t);
	}
	UnlockList(h->TicketList);

	ReleaseHub(h);

	ret = NewPack();

	PackAddInt(ret, "Point", SiGetPoint(s));

	return ret;
}

// Receive a HUB creation directive
void SiCalledCreateHub(SERVER *s, PACK *p)
{
	char name[MAX_SIZE];
	UINT type;
	HUB_OPTION o;
	HUB_LOG log;
	bool save_packet_log;
	UINT packet_log_switch_type;
	UINT packet_log_config[NUM_PACKET_LOG];
	bool save_security_log;
	UINT security_log_switch_type;
	UINT i;
	HUB *h;
	// Validate arguments
	if (s == NULL || p == NULL)
	{
		return;
	}

	PackGetStr(p, "HubName", name, sizeof(name));
	type = PackGetInt(p, "HubType");
	Zero(&o, sizeof(o));
	o.MaxSession = PackGetInt(p, "MaxSession");
	save_packet_log = PackGetInt(p, "SavePacketLog");
	packet_log_switch_type = PackGetInt(p, "PacketLogSwitchType");
	for (i = 0;i < NUM_PACKET_LOG;i++)
	{
		packet_log_config[i] = PackGetIntEx(p, "PacketLogConfig", i);
	}
	save_security_log = PackGetInt(p, "SaveSecurityLog");
	security_log_switch_type = PackGetInt(p, "SecurityLogSwitchType");

	Zero(&log, sizeof(log));
	log.SavePacketLog = save_packet_log;
	log.PacketLogSwitchType = packet_log_switch_type;
	Copy(log.PacketLogConfig, packet_log_config, sizeof(log.PacketLogConfig));
	log.SaveSecurityLog = save_security_log;
	log.SecurityLogSwitchType = security_log_switch_type;

	h = NewHub(s->Cedar, name, &o);
	h->LastCommTime = h->LastLoginTime = h->CreatedTime = 0;
	SetHubLogSetting(h, &log);
	h->Type = type;
	h->FarmMember_MaxSessionClient = PackGetInt(p, "MaxSessionClient");
	h->FarmMember_MaxSessionBridge = PackGetInt(p, "MaxSessionBridge");
	h->FarmMember_MaxSessionClientBridgeApply = PackGetBool(p, "MaxSessionClientBridgeApply");

	if (h->FarmMember_MaxSessionClientBridgeApply == false)
	{
		h->FarmMember_MaxSessionClient = INFINITE;
		h->FarmMember_MaxSessionBridge = INFINITE;
	}

	PackGetData2(p, "SecurePassword", h->SecurePassword, SHA1_SIZE);
	PackGetData2(p, "HashedPassword", h->HashedPassword, SHA1_SIZE);

	for (i = 0;i < SiNumAccessFromPack(p);i++)
	{
		ACCESS *a = SiPackToAccess(p, i);
		AddAccessList(h, a);
		Free(a);
	}

	if (PackGetBool(p, "EnableSecureNAT"))
	{
		VH_OPTION t;

		InVhOption(&t, p);

		Copy(h->SecureNATOption, &t, sizeof(VH_OPTION));
		EnableSecureNAT(h, true);

		Debug("SiCalledCreateHub: SecureNAT Created.\n");
	}

	AddHub(s->Cedar, h);
	h->Offline = true;
	SetHubOnline(h);

	ReleaseHub(h);
}

// Farm control thread
void SiFarmControlThread(THREAD *thread, void *param)
{
	SERVER *s;
	CEDAR *c;
	EVENT *e;
	LIST *o;
	UINT i;
	char tmp[MAX_PATH];
	// Validate arguments
	if (thread == NULL || param == NULL)
	{
		return;
	}

	s = (SERVER *)param;
	c = s->Cedar;
	e = s->FarmControlThreadHaltEvent;

	while (true)
	{
		Lock(c->CedarSuperLock);

		// Enumerate HUB list which is hosted by each farm member
		Format(tmp, sizeof(tmp), "CONTROLLER: %s %u", __FILE__, __LINE__);
		SiDebugLog(s, tmp);

		LockList(s->FarmMemberList);
		{
			UINT i;
			UINT num;
			UINT assigned_client_license = 0;
			UINT assigned_bridge_license = 0;
			LIST *fm_list = NewListFast(NULL);

			Format(tmp, sizeof(tmp), "CONTROLLER: %s %u", __FILE__, __LINE__);
			SiDebugLog(s, tmp);

			num = 0;

			while (true)
			{
				bool escape = true;
				for (i = 0;i < LIST_NUM(s->FarmMemberList);i++)
				{
					FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);

					if (IsInList(fm_list, f) == false)
					{
						SiCallEnumHub(s, f);
						// Get the total number of sessions across the server farm
						num += f->NumSessions;

						assigned_client_license += f->AssignedClientLicense;
						assigned_bridge_license += f->AssignedBridgeLicense;

						escape = false;

						Add(fm_list, f);
						break;
					}
				}

				if (escape)
				{
					break;
				}

				UnlockList(s->FarmMemberList);
				LockList(s->FarmMemberList);
			}

			ReleaseList(fm_list);

			s->CurrentTotalNumSessionsOnFarm = num;

			// Update the number of assigned licenses
			s->CurrentAssignedBridgeLicense = assigned_bridge_license;
			s->CurrentAssignedClientLicense = assigned_client_license;

			Format(tmp, sizeof(tmp), "CONTROLLER: %s %u", __FILE__, __LINE__);
			SiDebugLog(s, tmp);
		}
		UnlockList(s->FarmMemberList);

		Format(tmp, sizeof(tmp), "CONTROLLER: %s %u", __FILE__, __LINE__);
		SiDebugLog(s, tmp);

		o = NewListFast(NULL);

		Format(tmp, sizeof(tmp), "CONTROLLER: %s %u", __FILE__, __LINE__);
		SiDebugLog(s, tmp);

		// Emit an update notification for each HUB
		LockList(c->HubList);
		{
			UINT i;
			for (i = 0;i < LIST_NUM(c->HubList);i++)
			{
				HUB *h = LIST_DATA(c->HubList, i);
				AddRef(h->ref);
				Add(o, h);
			}
		}
		UnlockList(c->HubList);

		Format(tmp, sizeof(tmp), "CONTROLLER: %s %u", __FILE__, __LINE__);
		SiDebugLog(s, tmp);

		for (i = 0;i < LIST_NUM(o);i++)
		{
			HUB *h = LIST_DATA(o, i);
			SiHubUpdateProc(h);
			ReleaseHub(h);
		}

		Format(tmp, sizeof(tmp), "CONTROLLER: %s %u", __FILE__, __LINE__);
		SiDebugLog(s, tmp);

		ReleaseList(o);

		Unlock(c->CedarSuperLock);

		Wait(e, SERVER_FARM_CONTROL_INTERVAL);
		if (s->Halt)
		{
			break;
		}
	}
}

// Start the farm controling
void SiStartFarmControl(SERVER *s)
{
	// Validate arguments
	if (s == NULL || s->ServerType != SERVER_TYPE_FARM_CONTROLLER)
	{
		return;
	}

	s->FarmControlThreadHaltEvent = NewEvent();
	s->FarmControlThread = NewThread(SiFarmControlThread, s);
}

// Stop the farm controling
void SiStopFarmControl(SERVER *s)
{
	// Validate arguments
	if (s == NULL || s->ServerType != SERVER_TYPE_FARM_CONTROLLER)
	{
		return;
	}

	Set(s->FarmControlThreadHaltEvent);
	WaitThread(s->FarmControlThread, INFINITE);
	ReleaseEvent(s->FarmControlThreadHaltEvent);
	ReleaseThread(s->FarmControlThread);
}

// HUB enumeration directive
void SiCallEnumHub(SERVER *s, FARM_MEMBER *f)
{
	CEDAR *c;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	c = s->Cedar;

	if (f->Me)
	{

		// Enumerate local HUBs
		LockList(f->HubList);
		{
			// For a local HUB, re-enumerate by erasing all STATIC HUB list once first
			UINT i;
			LIST *o = NewListFast(NULL);
			for (i = 0;i < LIST_NUM(f->HubList);i++)
			{
				HUB_LIST *h = LIST_DATA(f->HubList, i);
				if (h->DynamicHub == false)
				{
					Add(o, h);
				}
			}

			// Clear all the STATIC HUB
			for (i = 0;i < LIST_NUM(o);i++)
			{
				HUB_LIST *h = LIST_DATA(o, i);
				Free(h);
				Delete(f->HubList, h);
			}
			ReleaseList(o);

			// Second, stop DYNAMIC HUBs without user
			o = NewListFast(NULL);
			for (i = 0;i < LIST_NUM(f->HubList);i++)
			{
				HUB_LIST *h = LIST_DATA(f->HubList, i);
				if (h->DynamicHub == true)
				{
					LockList(c->HubList);
					{
						HUB *hub = GetHub(s->Cedar, h->Name);
						if (hub != NULL)
						{
							if (Count(hub->NumSessions) == 0 || hub->Type != HUB_TYPE_FARM_DYNAMIC)
							{
								Add(o, h);
							}
							ReleaseHub(hub);
						}
					}
					UnlockList(c->HubList);
				}
			}

			for (i = 0;i < LIST_NUM(o);i++)
			{
				HUB_LIST *h = LIST_DATA(o, i);
				Debug("Delete HUB: %s\n", h->Name);
				Free(h);
				Delete(f->HubList, h);
			}

			ReleaseList(o);

			// Set the enumeration results
			LockList(c->HubList);
			{
				for (i = 0;i < LIST_NUM(c->HubList);i++)
				{
					HUB *h = LIST_DATA(c->HubList, i);
					if (h->Offline == false)
					{
						if (h->Type == HUB_TYPE_FARM_STATIC)
						{
							HUB_LIST *hh = ZeroMalloc(sizeof(HUB_LIST));
							hh->FarmMember = f;
							hh->DynamicHub = false;
							StrCpy(hh->Name, sizeof(hh->Name), h->Name);
							Add(f->HubList, hh);

							LockList(h->SessionList);
							{
								hh->NumSessions = LIST_NUM(h->SessionList);
								hh->NumSessionsBridge = Count(h->NumSessionsBridge);
								hh->NumSessionsClient = Count(h->NumSessionsClient);
							}
							UnlockList(h->SessionList);

							LockHashList(h->MacHashTable);
							{
								hh->NumMacTables = HASH_LIST_NUM(h->MacHashTable);
							}
							UnlockHashList(h->MacHashTable);

							LockList(h->IpTable);
							{
								hh->NumIpTables = LIST_NUM(h->IpTable);
							}
							UnlockList(h->IpTable);
						}
					}
				}
			}
			UnlockList(c->HubList);
		}
		UnlockList(f->HubList);

		// Point
		f->Point = SiGetPoint(s);
		f->NumSessions = Count(s->Cedar->CurrentSessions);
		f->MaxSessions = GetServerCapsInt(s, "i_max_sessions");
		f->NumTcpConnections = Count(s->Cedar->CurrentTcpConnections);

		Lock(s->Cedar->TrafficLock);
		{
			Copy(&f->Traffic, s->Cedar->Traffic, sizeof(TRAFFIC));
		}
		Unlock(s->Cedar->TrafficLock);

		f->AssignedBridgeLicense = Count(s->Cedar->AssignedBridgeLicense);
		f->AssignedClientLicense = Count(s->Cedar->AssignedClientLicense);

		Copy(f->RandomKey, s->MyRandomKey, SHA1_SIZE);


		Debug("Server %s: Point %u\n", f->hostname, f->Point);
	}
	else
	{
		// Enumerate HUBs which are remote member
		PACK *p = NewPack();
		UINT i, num, j;
		LIST *o = NewListFast(NULL);

		num = 0;

		for (i = 0;i < LIST_NUM(s->FarmMemberList);i++)
		{
			FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);

			if (IsZero(f->RandomKey, SHA1_SIZE) == false && f->SystemId != 0)
			{
				num++;
			}
		}

		j = 0;

		for (i = 0;i < LIST_NUM(s->FarmMemberList);i++)
		{
			FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);

			if (IsZero(f->RandomKey, SHA1_SIZE) == false && f->SystemId != 0)
			{
				PackAddDataEx(p, "MemberRandomKey", f->RandomKey, SHA1_SIZE, j, num);
				PackAddInt64Ex(p, "MemberSystemId", f->SystemId, j, num);
				j++;
			}
		}
		PackAddInt(p, "MemberSystemIdNum", num);

		p = SiCallTask(f, p, "enumhub");
		if (p != NULL)
		{
			LockList(f->HubList);
			{
				UINT i;
				// Erase the list
				for (i = 0;i < LIST_NUM(f->HubList);i++)
				{
					HUB_LIST *hh = LIST_DATA(f->HubList, i);
					Free(hh);
				}
				DeleteAll(f->HubList);

				for (i = 0;i < PackGetIndexCount(p, "HubName");i++)
				{
					HUB_LIST *hh = ZeroMalloc(sizeof(HUB_LIST));
					UINT num;
					UINT64 LastCommTime;

					PackGetStrEx(p, "HubName", hh->Name, sizeof(hh->Name), i);
					num = PackGetIntEx(p, "NumSession", i);
					hh->DynamicHub = ((PackGetIntEx(p, "HubType", i) == HUB_TYPE_FARM_DYNAMIC) ? true : false);
					hh->FarmMember = f;
					hh->NumSessions = PackGetIntEx(p, "NumSessions", i);
					hh->NumSessionsClient = PackGetIntEx(p, "NumSessionsClient", i);
					hh->NumSessionsBridge = PackGetIntEx(p, "NumSessionsBridge", i);
					hh->NumIpTables = PackGetIntEx(p, "NumIpTables", i);
					hh->NumMacTables = PackGetIntEx(p, "NumMacTables", i);
					LastCommTime = PackGetInt64Ex(p, "LastCommTime", i);
					Add(f->HubList, hh);
					//Debug("%s\n", hh->Name);

					LockList(c->HubList);
					{
						HUB *h = GetHub(c, hh->Name);

						if (h != NULL)
						{
							// Update the LastCommTime of the Virtual HUB
							Lock(h->lock);
							{
								if (h->LastCommTime < LastCommTime)
								{
									h->LastCommTime = LastCommTime;
								}
							}
							Unlock(h->lock);

							ReleaseHub(h);
						}
					}
					UnlockList(c->HubList);

					if (hh->DynamicHub && num >= 1)
					{
						// It is not necessary to be registered in the virtual HUB creation
						// history list because user session is already connected.
						// Remove from the Virtual HUB creation history list
						SiDelHubCreateHistory(s, hh->Name);
					}

					if (hh->DynamicHub && num == 0)
					{
						// Check the Virtual HUB creation history list.
						// If it is created within 60 seconds of the most recent
						// in the case of Virtual HUB which the first user is not
						// connected yet, not to remove because there is no user
						if (SiIsHubRegistedOnCreateHistory(s, hh->Name) == false)
						{
							// Stop because all uses have gone in the dynamic HUB
							HUB *h;
							LockList(c->HubList);
							{
								h = GetHub(c, hh->Name);
							}
							UnlockList(c->HubList);

							if (h != NULL)
							{
								Add(o, h);
							}
						}
					}
				}
			}
			UnlockList(f->HubList);
			f->Point = PackGetInt(p, "Point");
			Debug("Server %s: Point %u\n", f->hostname, f->Point);
			f->NumSessions = PackGetInt(p, "NumTotalSessions");
			if (f->NumSessions == 0)
			{
				f->NumSessions = PackGetInt(p, "NumSessions");
			}
			f->MaxSessions = PackGetInt(p, "MaxSessions");
			f->NumTcpConnections = PackGetInt(p, "NumTcpConnections");
			InRpcTraffic(&f->Traffic, p);

			f->AssignedBridgeLicense = PackGetInt(p, "AssignedBridgeLicense");
			f->AssignedClientLicense = PackGetInt(p, "AssignedClientLicense");

			if (PackGetDataSize(p, "RandomKey") == SHA1_SIZE)
			{
				PackGetData(p, "RandomKey", f->RandomKey);
			}

			f->SystemId = PackGetInt64(p, "SystemId");

			// Apply the traffic difference information
			num = PackGetIndexCount(p, "TdType");
			for (i = 0;i < num;i++)
			{
				TRAFFIC traffic;
				UINT type;
				HUB *h;
				char name[MAX_SIZE];
				char hubname[MAX_SIZE];

				type = PackGetIntEx(p, "TdType", i);
				PackGetStrEx(p, "TdName", name, sizeof(name), i);
				PackGetStrEx(p, "TdHubName", hubname, sizeof(hubname), i);
				InRpcTrafficEx(&traffic, p, i);

				LockList(c->HubList);
				{
					h = GetHub(c, hubname);
					if (h != NULL)
					{
						if (type == TRAFFIC_DIFF_HUB)
						{
							Lock(h->TrafficLock);
							{
								AddTraffic(h->Traffic, &traffic);
							}
							Unlock(h->TrafficLock);
						}
						else
						{
							AcLock(h);
							{
								USER *u = AcGetUser(h, name);
								if (u != NULL)
								{
									Lock(u->lock);
									{
										AddTraffic(u->Traffic, &traffic);
									}
									Unlock(u->lock);
									if (u->Group != NULL)
									{
										Lock(u->Group->lock);
										{
											AddTraffic(u->Group->Traffic, &traffic);
										}
										Unlock(u->Group->lock);
									}
									ReleaseUser(u);
								}
							}
							AcUnlock(h);
						}
						ReleaseHub(h);
					}
					UnlockList(c->HubList);
				}
			}

			FreePack(p);
		}

		for (i = 0;i < LIST_NUM(o);i++)
		{
			HUB *h = LIST_DATA(o, i);
			SiCallDeleteHub(s, f, h);
			Debug("Delete HUB: %s\n", h->Name);
			ReleaseHub(h);
		}

		ReleaseList(o);
	}
}

// Send a session information directive
bool SiCallGetSessionStatus(SERVER *s, FARM_MEMBER *f, RPC_SESSION_STATUS *t)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return false;
	}

	p = NewPack();
	OutRpcSessionStatus(p, t);
	FreeRpcSessionStatus(t);
	Zero(t, sizeof(RPC_SESSION_STATUS));

	p = SiCallTask(f, p, "getsessionstatus");

	if (p == NULL)
	{
		return false;
	}

	InRpcSessionStatus(t, p);
	FreePack(p);

	return true;
}

// Log file reading directive
bool SiCallReadLogFile(SERVER *s, FARM_MEMBER *f, RPC_READ_LOG_FILE *t)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return false;
	}

	p = NewPack();
	OutRpcReadLogFile(p, t);
	FreeRpcReadLogFile(t);
	Zero(t, sizeof(RPC_READ_LOG_FILE));

	p = SiCallTask(f, p, "readlogfile");

	if (p == NULL)
	{
		return false;
	}

	InRpcReadLogFile(t, p);
	FreePack(p);

	return true;
}

// Log file enumeration directive
bool SiCallEnumLogFileList(SERVER *s, FARM_MEMBER *f, RPC_ENUM_LOG_FILE *t, char *hubname)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return false;
	}

	p = NewPack();
	OutRpcEnumLogFile(p, t);
	FreeRpcEnumLogFile(t);
	Zero(t, sizeof(RPC_ENUM_LOG_FILE));

	PackAddStr(p, "HubName", hubname);

	p = SiCallTask(f, p, "enumlogfilelist");

	if (p == NULL)
	{
		return false;
	}

	InRpcEnumLogFile(t, p);
	FreePack(p);

	return true;
}

// HUB delete directive
void SiCallDeleteHub(SERVER *s, FARM_MEMBER *f, HUB *h)
{
	PACK *p;
	UINT i;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	if (f->Me == false)
	{
		p = NewPack();

		PackAddStr(p, "HubName", h->Name);

		p = SiCallTask(f, p, "deletehub");
		FreePack(p);
	}

	LockList(f->HubList);
	{
		for (i = 0;i < LIST_NUM(f->HubList);i++)
		{
			HUB_LIST *hh = LIST_DATA(f->HubList, i);
			if (StrCmpi(hh->Name, h->Name) == 0)
			{
				Free(hh);
				Delete(f->HubList, hh);
			}
		}
	}
	UnlockList(f->HubList);
}

// Submit a HUB update directive
void SiCallUpdateHub(SERVER *s, FARM_MEMBER *f, HUB *h)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	if (f->Me == false)
	{
		p = NewPack();

		SiPackAddCreateHub(p, h);

		p = SiCallTask(f, p, "updatehub");
		FreePack(p);
	}
}

// Send a ticket creation directive
void SiCallCreateTicket(SERVER *s, FARM_MEMBER *f, char *hubname, char *username, char *realusername, POLICY *policy, UCHAR *ticket, UINT counter, char *groupname)
{
	PACK *p;
	char name[MAX_SESSION_NAME_LEN + 1];
	char hub_name_upper[MAX_SIZE];
	char user_name_upper[MAX_USERNAME_LEN + 1];
	char ticket_str[MAX_SIZE];
	UINT point;
	// Validate arguments
	if (s == NULL || f == NULL || realusername == NULL || hubname == NULL || username == NULL || policy == NULL || ticket == NULL)
	{
		return;
	}
	if (groupname == NULL)
	{
		groupname = "";
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);
	PackAddStr(p, "UserName", username);
	PackAddStr(p, "groupname", groupname);
	PackAddStr(p, "RealUserName", realusername);
	OutRpcPolicy(p, policy);
	PackAddData(p, "Ticket", ticket, SHA1_SIZE);

	BinToStr(ticket_str, sizeof(ticket_str), ticket, SHA1_SIZE);

	StrCpy(hub_name_upper, sizeof(hub_name_upper), hubname);
	StrUpper(hub_name_upper);
	StrCpy(user_name_upper, sizeof(user_name_upper), username);
	StrUpper(user_name_upper);
	Format(name, sizeof(name), "SID-%s-%u", user_name_upper,
		counter);
	PackAddStr(p, "SessionName", name);

	p = SiCallTask(f, p, "createticket");

	SLog(s->Cedar, "LS_TICKET_1", f->hostname, hubname, username, realusername, name, ticket_str);

	point = PackGetInt(p, "Point");
	if (point != 0)
	{
		f->Point = point;
		f->NumSessions++;
	}

	FreePack(p);
}

// Send a MAC address deletion directive
void SiCallDeleteMacTable(SERVER *s, FARM_MEMBER *f, char *hubname, UINT key)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);
	PackAddInt(p, "Key", key);

	p = SiCallTask(f, p, "deletemactable");

	FreePack(p);
}

// Send an IP address delete directive
void SiCallDeleteIpTable(SERVER *s, FARM_MEMBER *f, char *hubname, UINT key)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);
	PackAddInt(p, "Key", key);

	p = SiCallTask(f, p, "deleteiptable");

	FreePack(p);
}

// Send a session deletion directive
void SiCallDeleteSession(SERVER *s, FARM_MEMBER *f, char *hubname, char *session_name)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL || session_name == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);
	PackAddStr(p, "SessionName", session_name);

	p = SiCallTask(f, p, "deletesession");

	FreePack(p);
}

// Send an IP table enumeration directive
void SiCallEnumIpTable(SERVER *s, FARM_MEMBER *f, char *hubname, RPC_ENUM_IP_TABLE *t)
{
	PACK *p;
	UINT i;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL || t == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);

	p = SiCallTask(f, p, "enumiptable");

	Zero(t, sizeof(RPC_ENUM_IP_TABLE));
	InRpcEnumIpTable(t, p);

	for (i = 0;i < t->NumIpTable;i++)
	{
		t->IpTables[i].RemoteItem = true;
		StrCpy(t->IpTables[i].RemoteHostname, sizeof(t->IpTables[i].RemoteHostname),
			f->hostname);
	}

	FreePack(p);
}

// Submit a MAC table enumeration directive
void SiCallEnumMacTable(SERVER *s, FARM_MEMBER *f, char *hubname, RPC_ENUM_MAC_TABLE *t)
{
	PACK *p;
	UINT i;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL || t == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);

	p = SiCallTask(f, p, "enummactable");

	Zero(t, sizeof(RPC_ENUM_MAC_TABLE));
	InRpcEnumMacTable(t, p);

	for (i = 0;i < t->NumMacTable;i++)
	{
		t->MacTables[i].RemoteItem = true;
		StrCpy(t->MacTables[i].RemoteHostname, sizeof(t->MacTables[i].RemoteHostname),
			f->hostname);
	}

	FreePack(p);
}

// Send a SecureNAT status acquisition directive
void SiCallGetNatStatus(SERVER *s, FARM_MEMBER *f, char *hubname, RPC_NAT_STATUS *t)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL || t == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);

	p = SiCallTask(f, p, "getnatstatus");

	Zero(t, sizeof(RPC_NAT_STATUS));
	InRpcNatStatus(t, p);

	FreePack(p);
}

// Submit a DHCP entry enumeration directive
void SiCallEnumDhcp(SERVER *s, FARM_MEMBER *f, char *hubname, RPC_ENUM_DHCP *t)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL || t == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);

	p = SiCallTask(f, p, "enumdhcp");

	Zero(t, sizeof(RPC_ENUM_DHCP));
	InRpcEnumDhcp(t, p);

	FreePack(p);
}

// Submit a NAT entry enumeration directive 
void SiCallEnumNat(SERVER *s, FARM_MEMBER *f, char *hubname, RPC_ENUM_NAT *t)
{
	PACK *p;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL || t == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);

	p = SiCallTask(f, p, "enumnat");

	Zero(t, sizeof(RPC_ENUM_NAT));
	InRpcEnumNat(t, p);

	FreePack(p);
}

// Send a session enumeration directive
void SiCallEnumSession(SERVER *s, FARM_MEMBER *f, char *hubname, RPC_ENUM_SESSION *t)
{
	PACK *p;
	UINT i;
	// Validate arguments
	if (s == NULL || f == NULL || hubname == NULL || t == NULL)
	{
		return;
	}

	p = NewPack();
	PackAddStr(p, "HubName", hubname);

	p = SiCallTask(f, p, "enumsession");

	Zero(t, sizeof(RPC_ENUM_SESSION));
	InRpcEnumSession(t, p);

	for (i = 0;i < t->NumSession;i++)
	{
		t->Sessions[i].RemoteSession = true;
		StrCpy(t->Sessions[i].RemoteHostname, sizeof(t->Sessions[i].RemoteHostname),
			f->hostname);
	}

	FreePack(p);
}

// Send a HUB creation directive
void SiCallCreateHub(SERVER *s, FARM_MEMBER *f, HUB *h)
{
	PACK *p;
	HUB_LIST *hh;
	// Validate arguments
	if (s == NULL || f == NULL)
	{
		return;
	}

	if (f->Me == false)
	{
		p = NewPack();

		SiPackAddCreateHub(p, h);

		p = SiCallTask(f, p, "createhub");
		FreePack(p);
	}

	hh = ZeroMalloc(sizeof(HUB_LIST));
	hh->DynamicHub = (h->Type == HUB_TYPE_FARM_DYNAMIC ? true : false);
	StrCpy(hh->Name, sizeof(hh->Name), h->Name);
	hh->FarmMember = f;

	LockList(f->HubList);
	{
		bool exists = false;
		UINT i;
		for (i = 0;i < LIST_NUM(f->HubList);i++)
		{
			HUB_LIST *t = LIST_DATA(f->HubList, i);
			if (StrCmpi(t->Name, hh->Name) == 0)
			{
				exists = true;
			}
		}
		if (exists == false)
		{
			Add(f->HubList, hh);
		}
		else
		{
			Free(hh);
		}
	}
	UnlockList(f->HubList);
}

// Write the PACK for creating HUB
void SiPackAddCreateHub(PACK *p, HUB *h)
{
	UINT i;
	UINT max_session;
	SERVER *s;


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


	s = h->Cedar->Server;
	if (s != NULL)
	{
	}

	PackAddStr(p, "HubName", h->Name);
	PackAddInt(p, "HubType", h->Type);

	max_session = h->Option->MaxSession;

	if (GetHubAdminOption(h, "max_sessions") != 0)
	{
		if (max_session == 0)
		{
			max_session = GetHubAdminOption(h, "max_sessions");
		}
		else
		{
			UINT r = GetHubAdminOption(h, "max_sessions");
			max_session = MIN(max_session, r);
		}
	}

	PackAddInt(p, "MaxSession", max_session);

	if (GetHubAdminOption(h, "max_sessions_client_bridge_apply") != 0
		)
	{
		PackAddInt(p, "MaxSessionClient", GetHubAdminOption(h, "max_sessions_client"));
		PackAddInt(p, "MaxSessionBridge", GetHubAdminOption(h, "max_sessions_bridge"));
		PackAddBool(p, "MaxSessionClientBridgeApply", true);
	}
	else
	{
		PackAddInt(p, "MaxSessionClient", INFINITE);
		PackAddInt(p, "MaxSessionBridge", INFINITE);
	}

	PackAddBool(p, "NoArpPolling", h->Option->NoArpPolling);
	PackAddBool(p, "NoIPv6AddrPolling", h->Option->NoIPv6AddrPolling);
	PackAddBool(p, "NoIpTable", h->Option->NoIpTable);
	PackAddBool(p, "NoEnum", h->Option->NoEnum);
	PackAddBool(p, "FilterPPPoE", h->Option->FilterPPPoE);
	PackAddBool(p, "YieldAfterStorePacket", h->Option->YieldAfterStorePacket);
	PackAddBool(p, "NoSpinLockForPacketDelay", h->Option->NoSpinLockForPacketDelay);
	PackAddInt(p, "BroadcastStormDetectionThreshold", h->Option->BroadcastStormDetectionThreshold);
	PackAddInt(p, "MaxLoggedPacketsPerMinute", h->Option->MaxLoggedPacketsPerMinute);
	PackAddInt(p, "FloodingSendQueueBufferQuota", h->Option->FloodingSendQueueBufferQuota);
	PackAddBool(p, "DoNotSaveHeavySecurityLogs", h->Option->DoNotSaveHeavySecurityLogs);
	PackAddBool(p, "DropBroadcastsInPrivacyFilterMode", h->Option->DropBroadcastsInPrivacyFilterMode);
	PackAddBool(p, "DropArpInPrivacyFilterMode", h->Option->DropArpInPrivacyFilterMode);
	PackAddBool(p, "AllowSameUserInPrivacyFilterMode", h->Option->AllowSameUserInPrivacyFilterMode);
	PackAddBool(p, "SuppressClientUpdateNotification", h->Option->SuppressClientUpdateNotification);
	PackAddBool(p, "AssignVLanIdByRadiusAttribute", h->Option->AssignVLanIdByRadiusAttribute);
	PackAddBool(p, "DenyAllRadiusLoginWithNoVlanAssign", h->Option->DenyAllRadiusLoginWithNoVlanAssign);
	PackAddInt(p, "ClientMinimumRequiredBuild", h->Option->ClientMinimumRequiredBuild);
	PackAddBool(p, "SecureNAT_RandomizeAssignIp", h->Option->SecureNAT_RandomizeAssignIp);
	PackAddBool(p, "NoPhysicalIPOnPacketLog", h->Option->NoPhysicalIPOnPacketLog);
	PackAddInt(p, "DetectDormantSessionInterval", h->Option->DetectDormantSessionInterval);
	PackAddBool(p, "FixForDLinkBPDU", h->Option->FixForDLinkBPDU);
	PackAddBool(p, "BroadcastLimiterStrictMode", h->Option->BroadcastLimiterStrictMode);
	PackAddBool(p, "NoLookBPDUBridgeId", h->Option->NoLookBPDUBridgeId);
	PackAddBool(p, "NoManageVlanId", h->Option->NoManageVlanId);
	PackAddInt(p, "VlanTypeId", h->Option->VlanTypeId);
	PackAddBool(p, "FilterOSPF", h->Option->FilterOSPF);
	PackAddBool(p, "FilterIPv4", h->Option->FilterIPv4);
	PackAddBool(p, "FilterIPv6", h->Option->FilterIPv6);
	PackAddBool(p, "FilterNonIP", h->Option->FilterNonIP);
	PackAddBool(p, "NoIPv4PacketLog", h->Option->NoIPv4PacketLog);
	PackAddBool(p, "NoIPv6PacketLog", h->Option->NoIPv6PacketLog);
	PackAddBool(p, "FilterBPDU", h->Option->FilterBPDU);
	PackAddBool(p, "NoIPv6DefaultRouterInRAWhenIPv6", h->Option->NoIPv6DefaultRouterInRAWhenIPv6);
	PackAddBool(p, "NoMacAddressLog", h->Option->NoMacAddressLog);
	PackAddBool(p, "ManageOnlyPrivateIP", h->Option->ManageOnlyPrivateIP);
	PackAddBool(p, "ManageOnlyLocalUnicastIPv6", h->Option->ManageOnlyLocalUnicastIPv6);
	PackAddBool(p, "DisableIPParsing", h->Option->DisableIPParsing);
	PackAddInt(p, "AdjustTcpMssValue", h->Option->AdjustTcpMssValue);
	PackAddBool(p, "DisableAdjustTcpMss", h->Option->DisableAdjustTcpMss);
	PackAddBool(p, "NoDhcpPacketLogOutsideHub", h->Option->NoDhcpPacketLogOutsideHub);
	PackAddBool(p, "DisableHttpParsing", h->Option->DisableHttpParsing);
	PackAddBool(p, "DisableUdpAcceleration", h->Option->DisableUdpAcceleration);
	PackAddBool(p, "DisableUdpFilterForLocalBridgeNic", h->Option->DisableUdpFilterForLocalBridgeNic);
	PackAddBool(p, "ApplyIPv4AccessListOnArpPacket", h->Option->ApplyIPv4AccessListOnArpPacket);
	PackAddBool(p, "RemoveDefGwOnDhcpForLocalhost", h->Option->RemoveDefGwOnDhcpForLocalhost);

	PackAddInt(p, "SecureNAT_MaxTcpSessionsPerIp", h->Option->SecureNAT_MaxTcpSessionsPerIp);
	PackAddInt(p, "SecureNAT_MaxTcpSynSentPerIp", h->Option->SecureNAT_MaxTcpSynSentPerIp);
	PackAddInt(p, "SecureNAT_MaxUdpSessionsPerIp", h->Option->SecureNAT_MaxUdpSessionsPerIp);
	PackAddInt(p, "SecureNAT_MaxDnsSessionsPerIp", h->Option->SecureNAT_MaxDnsSessionsPerIp);
	PackAddInt(p, "SecureNAT_MaxIcmpSessionsPerIp", h->Option->SecureNAT_MaxIcmpSessionsPerIp);
	PackAddInt(p, "AccessListIncludeFileCacheLifetime", h->Option->AccessListIncludeFileCacheLifetime);
	PackAddBool(p, "DisableKernelModeSecureNAT", h->Option->DisableKernelModeSecureNAT);
	PackAddBool(p, "DisableIpRawModeSecureNAT", h->Option->DisableIpRawModeSecureNAT);
	PackAddBool(p, "DisableUserModeSecureNAT", h->Option->DisableUserModeSecureNAT);
	PackAddBool(p, "DisableCheckMacOnLocalBridge", h->Option->DisableCheckMacOnLocalBridge);
	PackAddBool(p, "DisableCorrectIpOffloadChecksum", h->Option->DisableCorrectIpOffloadChecksum);

	PackAddInt(p, "SavePacketLog", h->LogSetting.SavePacketLog);
	PackAddInt(p, "PacketLogSwitchType", h->LogSetting.PacketLogSwitchType);
	for (i = 0;i < NUM_PACKET_LOG;i++)
	{
		PackAddIntEx(p, "PacketLogConfig", h->LogSetting.PacketLogConfig[i], i, NUM_PACKET_LOG);
	}
	PackAddInt(p, "SaveSecurityLog", h->LogSetting.SaveSecurityLog);
	PackAddInt(p, "SecurityLogSwitchType", h->LogSetting.SecurityLogSwitchType);
	PackAddData(p, "HashedPassword", h->HashedPassword, SHA1_SIZE);
	PackAddData(p, "SecurePassword", h->SecurePassword, SHA1_SIZE);
	PackAddBool(p, "UseHubNameAsDhcpUserClassOption", h->Option->UseHubNameAsDhcpUserClassOption);
	PackAddBool(p, "UseHubNameAsRadiusNasId", h->Option->UseHubNameAsRadiusNasId);
	PackAddBool(p, "AllowEapMatchUserByCert", h->Option->AllowEapMatchUserByCert);

	SiAccessListToPack(p, h->AccessList);

	if (h->EnableSecureNAT)
	{
		PackAddBool(p, "EnableSecureNAT", h->EnableSecureNAT);
		OutVhOption(p, h->SecureNATOption);
	}
}

// Setting of the HUB has been updated
void SiHubUpdateProc(HUB *h)
{
	SERVER *s;
	UINT i;
	// Validate arguments
	if (h == NULL || h->Cedar == NULL || h->Cedar->Server == NULL || h->Cedar->Server->ServerType != SERVER_TYPE_FARM_CONTROLLER)
	{
		return;
	}

	s = h->Cedar->Server;

	if (s->FarmMemberList == NULL)
	{
		return;
	}

	if (h->LastVersion != h->CurrentVersion || h->CurrentVersion == 0)
	{
		LIST *fm_list;
		if (h->CurrentVersion == 0)
		{
			h->CurrentVersion = 1;
		}
		h->LastVersion = h->CurrentVersion;

		Debug("SiHubUpdateProc HUB=%s, Ver=%u, Type=%u, Offline=%u\n", h->Name, h->CurrentVersion,
			h->Type, h->Offline);

		fm_list = NewListFast(NULL);

		LockList(s->FarmMemberList);
		{
			while (true)
			{
				bool escape = true;
				// Update the HUB on all members
				for (i = 0;i < LIST_NUM(s->FarmMemberList);i++)
				{
					FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);

					if (IsInList(fm_list, f) == false)
					{
						Add(fm_list, f);
						escape = false;

						if (f->Me == false)
						{
							SiCallUpdateHub(s, f, h);
						}

						break;
					}
				}

				if (escape)
				{
					break;
				}

				UnlockList(s->FarmMemberList);
				LockList(s->FarmMemberList);
			}
		}
		UnlockList(s->FarmMemberList);

		ReleaseList(fm_list);
	}

	if (h->Offline == false)
	{
		SiHubOnlineProc(h);
	}
}

// HUB turns to online
void SiHubOnlineProc(HUB *h)
{
	SERVER *s;
	UINT i;
	// Validate arguments
	if (h == NULL || h->Cedar->Server == NULL || h->Cedar->Server->ServerType != SERVER_TYPE_FARM_CONTROLLER)
	{
		// Process only on the farm controller
		return;
	}

	s = h->Cedar->Server;

	if (s->FarmMemberList == NULL)
	{
		return;
	}

	LockList(s->FarmMemberList);
	{
		if (h->Type == HUB_TYPE_FARM_STATIC)
		{
			// Static HUB
			// Create the HUB on all members
			for (i = 0;i < LIST_NUM(s->FarmMemberList);i++)
			{
				UINT j;
				bool exists = false;
				FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);

				LockList(f->HubList);
				{
					for (j = 0;j < LIST_NUM(f->HubList);j++)
					{
						HUB_LIST *hh = LIST_DATA(f->HubList, j);
						if (StrCmpi(hh->Name, h->Name) == 0)
						{
							exists = true;
						}
					}
				}
				UnlockList(f->HubList);

				if (exists == false)
				{
					SiCallCreateHub(s, f, h);
				}
			}
		}
	}
	UnlockList(s->FarmMemberList);
}

// HUB turns to offline
void SiHubOfflineProc(HUB *h)
{
	SERVER *s;
	char hubname[MAX_HUBNAME_LEN + 1];
	UINT i;
	LIST *fm_list;
	// Validate arguments
	if (h == NULL || h->Cedar->Server == NULL || h->Cedar->Server->ServerType != SERVER_TYPE_FARM_CONTROLLER)
	{
		// Process only on the farm controller
		return;
	}

	s = h->Cedar->Server;

	if (s->FarmMemberList == NULL)
	{
		return;
	}

	StrCpy(hubname, sizeof(hubname), h->Name);

	fm_list = NewListFast(NULL);

	LockList(s->FarmMemberList);
	{
		while (true)
		{
			bool escape = true;

			// Stop the HUB on all members
			for (i = 0;i < LIST_NUM(s->FarmMemberList);i++)
			{
				FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);

				if (IsInList(fm_list, f) == false)
				{
					Add(fm_list, f);
					escape = false;

					SiCallDeleteHub(s, f, h);

					break;
				}
			}

			if (escape)
			{
				break;
			}

			UnlockList(s->FarmMemberList);
			LockList(s->FarmMemberList);
		}
	}
	UnlockList(s->FarmMemberList);

	ReleaseList(fm_list);
}

// Convert an access to PACK
void SiAccessToPack(PACK *p, ACCESS *a, UINT i, UINT total)
{
	// Validate arguments
	if (p == NULL || a == NULL)
	{
		return;
	}

	PackAddUniStrEx(p, "Note", a->Note, i, total);
	PackAddIntEx(p, "Active", a->Active, i, total);
	PackAddIntEx(p, "Priority", a->Priority, i, total);
	PackAddIntEx(p, "Discard", a->Discard, i, total);
	if (a->IsIPv6)
	{
		PackAddIp32Ex(p, "SrcIpAddress", 0xFDFFFFDF, i, total);
		PackAddIp32Ex(p, "SrcSubnetMask", 0xFFFFFFFF, i, total);
		PackAddIp32Ex(p, "DestIpAddress", 0xFDFFFFDF, i, total);
		PackAddIp32Ex(p, "DestSubnetMask", 0xFFFFFFFF, i, total);
	}
	else
	{
		PackAddIp32Ex(p, "SrcIpAddress", a->SrcIpAddress, i, total);
		PackAddIp32Ex(p, "SrcSubnetMask", a->SrcSubnetMask, i, total);
		PackAddIp32Ex(p, "DestIpAddress", a->DestIpAddress, i, total);
		PackAddIp32Ex(p, "DestSubnetMask", a->DestSubnetMask, i, total);
	}
	PackAddIntEx(p, "Protocol", a->Protocol, i, total);
	PackAddIntEx(p, "SrcPortStart", a->SrcPortStart, i, total);
	PackAddIntEx(p, "SrcPortEnd", a->SrcPortEnd, i, total);
	PackAddIntEx(p, "DestPortStart", a->DestPortStart, i, total);
	PackAddIntEx(p, "DestPortEnd", a->DestPortEnd, i, total);
	PackAddStrEx(p, "SrcUsername", a->SrcUsername, i, total);
	PackAddStrEx(p, "DestUsername", a->DestUsername, i, total);
	PackAddBoolEx(p, "CheckSrcMac", a->CheckSrcMac, i, total);
	PackAddDataEx(p, "SrcMacAddress", a->SrcMacAddress, sizeof(a->SrcMacAddress), i, total);
	PackAddDataEx(p, "SrcMacMask", a->SrcMacMask, sizeof(a->SrcMacMask), i, total);
	PackAddBoolEx(p, "CheckDstMac", a->CheckDstMac, i, total);
	PackAddDataEx(p, "DstMacAddress", a->DstMacAddress, sizeof(a->DstMacAddress), i, total);
	PackAddDataEx(p, "DstMacMask", a->DstMacMask, sizeof(a->DstMacMask), i, total);
	PackAddBoolEx(p, "CheckTcpState", a->CheckTcpState, i, total);
	PackAddBoolEx(p, "Established", a->Established, i, total);
	PackAddIntEx(p, "Delay", a->Delay, i, total);
	PackAddIntEx(p, "Jitter", a->Jitter, i, total);
	PackAddIntEx(p, "Loss", a->Loss, i, total);
	PackAddStrEx(p, "RedirectUrl", a->RedirectUrl, i, total);
	PackAddBoolEx(p, "IsIPv6", a->IsIPv6, i, total);
	if (a->IsIPv6)
	{
		PackAddIp6AddrEx(p, "SrcIpAddress6", &a->SrcIpAddress6, i, total);
		PackAddIp6AddrEx(p, "SrcSubnetMask6", &a->SrcSubnetMask6, i, total);
		PackAddIp6AddrEx(p, "DestIpAddress6", &a->DestIpAddress6, i, total);
		PackAddIp6AddrEx(p, "DestSubnetMask6", &a->DestSubnetMask6, i, total);
	}
	else
	{
		IPV6_ADDR zero;

		Zero(&zero, sizeof(zero));

		PackAddIp6AddrEx(p, "SrcIpAddress6", &zero, i, total);
		PackAddIp6AddrEx(p, "SrcSubnetMask6", &zero, i, total);
		PackAddIp6AddrEx(p, "DestIpAddress6", &zero, i, total);
		PackAddIp6AddrEx(p, "DestSubnetMask6", &zero, i, total);
	}
}

// Get number of access contained in the PACK
UINT SiNumAccessFromPack(PACK *p)
{
	// Validate arguments
	if (p == NULL)
	{
		return 0;
	}

	return PackGetIndexCount(p, "Active");
}

// Convert the PACK to access
ACCESS *SiPackToAccess(PACK *p, UINT i)
{
	ACCESS *a;
	// Validate arguments
	if (p == NULL)
	{
		return NULL;
	}

	a = ZeroMalloc(sizeof(ACCESS));

	PackGetUniStrEx(p, "Note", a->Note, sizeof(a->Note), i);
	a->Active = PackGetIntEx(p, "Active", i);
	a->Priority = PackGetIntEx(p, "Priority", i);
	a->Discard = PackGetIntEx(p, "Discard", i);
	a->SrcIpAddress = PackGetIp32Ex(p, "SrcIpAddress", i);
	a->SrcSubnetMask = PackGetIp32Ex(p, "SrcSubnetMask", i);
	a->DestIpAddress = PackGetIp32Ex(p, "DestIpAddress", i);
	a->DestSubnetMask = PackGetIp32Ex(p, "DestSubnetMask", i);
	a->Protocol = PackGetIntEx(p, "Protocol", i);
	a->SrcPortStart = PackGetIntEx(p, "SrcPortStart", i);
	a->SrcPortEnd = PackGetIntEx(p, "SrcPortEnd", i);
	a->DestPortStart = PackGetIntEx(p, "DestPortStart", i);
	a->DestPortEnd = PackGetIntEx(p, "DestPortEnd", i);
	PackGetStrEx(p, "SrcUsername", a->SrcUsername, sizeof(a->SrcUsername), i);
	PackGetStrEx(p, "DestUsername", a->DestUsername, sizeof(a->DestUsername), i);
	a->CheckSrcMac = PackGetBoolEx(p, "CheckSrcMac", i);
	PackGetDataEx2(p, "SrcMacAddress", a->SrcMacAddress, sizeof(a->SrcMacAddress), i);
	PackGetDataEx2(p, "SrcMacMask", a->SrcMacMask, sizeof(a->SrcMacMask), i);
	a->CheckDstMac = PackGetBoolEx(p, "CheckDstMac", i);
	PackGetDataEx2(p, "DstMacAddress", a->DstMacAddress, sizeof(a->DstMacAddress), i);
	PackGetDataEx2(p, "DstMacMask", a->DstMacMask, sizeof(a->DstMacMask), i);
	a->CheckTcpState = PackGetBoolEx(p, "CheckTcpState", i);
	a->Established = PackGetBoolEx(p, "Established", i);
	a->Delay = PackGetIntEx(p, "Delay", i);
	a->Jitter = PackGetIntEx(p, "Jitter", i);
	a->Loss = PackGetIntEx(p, "Loss", i);
	a->IsIPv6 = PackGetBoolEx(p, "IsIPv6", i);
	PackGetStrEx(p, "RedirectUrl", a->RedirectUrl, sizeof(a->RedirectUrl), i);
	if (a->IsIPv6)
	{
		PackGetIp6AddrEx(p, "SrcIpAddress6", &a->SrcIpAddress6, i);
		PackGetIp6AddrEx(p, "SrcSubnetMask6", &a->SrcSubnetMask6, i);
		PackGetIp6AddrEx(p, "DestIpAddress6", &a->DestIpAddress6, i);
		PackGetIp6AddrEx(p, "DestSubnetMask6", &a->DestSubnetMask6, i);
	}

	return a;
}

// Convert the PACK to an access list
void SiAccessListToPack(PACK *p, LIST *o)
{
	// Validate arguments
	if (p == NULL || o == NULL)
	{
		return;
	}

	LockList(o);
	{
		UINT i;
		for (i = 0;i < LIST_NUM(o);i++)
		{
			ACCESS *a = LIST_DATA(o, i);
			SiAccessToPack(p, a, i, LIST_NUM(o));
		}
	}
	UnlockList(o);
}

// Get the member that is hosting the specified HUB
FARM_MEMBER *SiGetHubHostingMember(SERVER *s, HUB *h, bool admin_mode, CONNECTION *c)
{
	FARM_MEMBER *ret = NULL;
	char name[MAX_SIZE];
	UINT i;
	// Validate arguments
	if (s == NULL || h == NULL || c == NULL)
	{
		return NULL;
	}

	StrCpy(name, sizeof(name), h->Name);

	if (h->Type == HUB_TYPE_FARM_STATIC)
	{
		// It is good to select any member in the case of static HUB
		if (admin_mode == false)
		{
			ret = SiGetNextFarmMember(s, c, h);
		}
		else
		{
			UINT i;
			ret = NULL;

			for (i = 0;i < LIST_NUM(s->FarmMemberList);i++)
			{
				FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);
				if (f->Me)
				{
					ret = f;
					break;
				}
			}
		}
	}
	else
	{
		// Examine whether there is a member that is hosting the HUB already in the case of dynamic HUB
		for (i = 0;i < LIST_NUM(s->FarmMemberList);i++)
		{
			FARM_MEMBER *f = LIST_DATA(s->FarmMemberList, i);
			HUB_LIST *hh, t;
			StrCpy(t.Name, sizeof(t.Name), name);
			LockList(f->HubList);
			{
				hh = Search(f->HubList, &t);
				if (hh != NULL)
				{
					// Found
					ret = f;
				}
			}
			UnlockList(f->HubList);
		}

		if (ret == NULL)
		{
			// Let host the new HUB
			FARM_MEMBER *f;

			// Select the member to host
			ret = SiGetNextFarmMember(s, c, h);

			f = ret;
			if (f != NULL)
			{
				// HUB creation directive
				SiAddHubCreateHistory(s, name);
				SiCallCreateHub(s, f, h);
				SiCallUpdateHub(s, f, h);
			}
		}
	}

	return ret;
}

// Task is called
PACK *SiCalledTask(FARM_CONTROLLER *f, PACK *p, char *taskname)
{
	PACK *ret;
	SERVER *s;
	// Validate arguments
	if (f == NULL || p == NULL || taskname == NULL)
	{
		return NULL;
	}

	ret = NULL;
	s = f->Server;

	if (StrCmpi(taskname, "noop") == 0)
	{
		// NO OPERATION
		ret = NewPack();
	}
	else
	{
		Debug("Task Called: [%s].\n", taskname);
		if (StrCmpi(taskname, "createhub") == 0)
		{
			SiCalledCreateHub(s, p);
			ret = NewPack();
		}
		else if (StrCmpi(taskname, "deletehub") == 0)
		{
			SiCalledDeleteHub(s, p);
			ret = NewPack();
		}
		else if (StrCmpi(taskname, "enumhub") == 0)
		{
			ret = NewPack();
			SiCalledEnumHub(s, ret, p);
		}
		else if (StrCmpi(taskname, "updatehub") == 0)
		{
			SiCalledUpdateHub(s, p);
			ret = NewPack();
		}
		else if (StrCmpi(taskname, "createticket") == 0)
		{
			ret = SiCalledCreateTicket(s, p);
		}
		else if (StrCmpi(taskname, "enumnat") == 0)
		{
			ret = SiCalledEnumNat(s, p);
		}
		else if (StrCmpi(taskname, "enumdhcp") == 0)
		{
			ret = SiCalledEnumDhcp(s, p);
		}
		else if (StrCmpi(taskname, "getnatstatus") == 0)
		{
			ret = SiCalledGetNatStatus(s, p);
		}
		else if (StrCmpi(taskname, "enumsession") == 0)
		{
			ret = SiCalledEnumSession(s, p);
		}
		else if (StrCmpi(taskname, "deletesession") == 0)
		{
			SiCalledDeleteSession(s, p);
			ret = NewPack();
		}
		else if (StrCmpi(taskname, "deletemactable") == 0)
		{
			SiCalledDeleteMacTable(s, p);
			ret = NewPack();
		}
		else if (StrCmpi(taskname, "deleteiptable") == 0)
		{
			SiCalledDeleteIpTable(s, p);
			ret = NewPack();
		}
		else if (StrCmpi(taskname, "enummactable") == 0)
		{
			ret = SiCalledEnumMacTable(s, p);
		}
		else if (StrCmpi(taskname, "enumiptable") == 0)
		{
			ret = SiCalledEnumIpTable(s, p);
		}
		else if (StrCmpi(taskname, "getsessionstatus") == 0)
		{
			ret = SiCalledGetSessionStatus(s, p);
		}
		else if (StrCmpi(taskname, "enumlogfilelist") == 0)
		{
			ret = SiCalledEnumLogFileList(s, p);
		}
		else if (StrCmpi(taskname, "readlogfile") == 0)
		{
			ret = SiCalledReadLogFile(s, p);
		}
	}

	return ret;
}

// Call the task
PACK *SiCallTask(FARM_MEMBER *f, PACK *p, char *taskname)
{
	PACK *ret;
	char tmp[MAX_PATH];
	// Validate arguments
	if (f == NULL || p == NULL || taskname == NULL)
	{
		return NULL;
	}

	PackAddStr(p, "taskname", taskname);

	Debug("Call Task [%s] (%s)\n", taskname, f->hostname);

	Format(tmp, sizeof(tmp), "CLUSTER_CALL: Entering Call [%s] to %s", taskname, f->hostname);
	SiDebugLog(f->Cedar->Server, tmp);

	ret = SiExecTask(f, p);

	Format(tmp, sizeof(tmp), "CLUSTER_CALL: Leaving Call [%s] to %s", taskname, f->hostname);
	SiDebugLog(f->Cedar->Server, tmp);

	return ret;
}

// Task listening procedure (Main Process)
void SiAcceptTasksFromControllerMain(FARM_CONTROLLER *f, SOCK *sock)
{
	PACK *request;
	PACK *response;
	char taskname[MAX_SIZE];
	// Validate arguments
	if (f == NULL || sock == NULL)
	{
		return;
	}

	f->IsConnected = true;

	while (true)
	{
		bool ret;
		// Receive the PACK
		request = HttpClientRecv(sock);
		if (request == NULL)
		{
			// Disconnect
			break;
		}

		response = NULL;

		// Get the name
		if (PackGetStr(request, "taskname", taskname, sizeof(taskname)))
		{
			Lock(f->Server->TasksFromFarmControllerLock);
			{
				response = SiCalledTask(f, request, taskname);
			}
			Unlock(f->Server->TasksFromFarmControllerLock);
		}

		FreePack(request);

		// Return a response
		if (response == NULL)
		{
			response = NewPack();
		}
		else
		{
			PackAddInt(response, "succeed", 1);
		}

		ret = HttpClientSend(sock, response);
		FreePack(response);

		if (ret == false)
		{
			// Disconnect
			break;
		}
	}

	f->IsConnected = false;
}

// Task waiting procedure
void SiAcceptTasksFromController(FARM_CONTROLLER *f, SOCK *sock)
{
	UINT i;
	HUB **hubs;
	UINT num_hubs;
	CEDAR *c;
	SERVER *s;
	// Validate arguments
	if (f == NULL || sock == NULL)
	{
		return;
	}

	s = f->Server;
	c = s->Cedar;

	// Main process
	SiAcceptTasksFromControllerMain(f, sock);

	// Stop all Virtual HUBs since the connection to the controller is disconnected
	LockList(c->HubList);
	{
		hubs = ToArray(c->HubList);
		num_hubs = LIST_NUM(c->HubList);
		for (i = 0;i < num_hubs;i++)
		{
			AddRef(hubs[i]->ref);
		}
	}
	UnlockList(c->HubList);

	for (i = 0;i < num_hubs;i++)
	{
		SetHubOffline(hubs[i]);
		DelHub(c, hubs[i]);
		ReleaseHub(hubs[i]);
	}

	Free(hubs);
}

// Execute the task
PACK *SiExecTask(FARM_MEMBER *f, PACK *p)
{
	FARM_TASK *t;
	// Validate arguments
	if (f == NULL || p == NULL)
	{
		return NULL;
	}

	t = SiFarmServPostTask(f, p);
	if (t == NULL)
	{
		return NULL;
	}

	return SiFarmServWaitTask(t);
}

// Task queuing
FARM_TASK *SiFarmServPostTask(FARM_MEMBER *f, PACK *request)
{
	FARM_TASK *t;
	// Validate arguments
	if (f == NULL || request == NULL)
	{
		return NULL;
	}

	t = ZeroMalloc(sizeof(FARM_TASK));
	t->CompleteEvent = NewEvent();
	t->Request = request;

	LockQueue(f->TaskQueue);
	{
		if (f->Halting)
		{
			// Halting (failure)
			UnlockQueue(f->TaskQueue);
			ReleaseEvent(t->CompleteEvent);
			Free(t);
			return NULL;
		}

		InsertQueue(f->TaskQueue, t);
	}
	UnlockQueue(f->TaskQueue);

	Set(f->TaskPostEvent);

	return t;
}

// Wait for task results
PACK *SiFarmServWaitTask(FARM_TASK *t)
{
	PACK *response;
	// Validate arguments
	if (t == NULL)
	{
		return NULL;
	}

	Wait(t->CompleteEvent, INFINITE);
	ReleaseEvent(t->CompleteEvent);
	FreePack(t->Request);

	response = t->Response;
	Free(t);

	if (PackGetInt(response, "succeed") == 0)
	{
		// Task calling fails for any reason
		FreePack(response);
		return NULL;
	}

	return response;
}

// Server farm processing main
void SiFarmServMain(SERVER *server, SOCK *sock, FARM_MEMBER *f)
{
	UINT wait_time = SERVER_CONTROL_TCP_TIMEOUT / 2;
	bool send_noop = false;
	UINT i;
	CEDAR *c;
	// Validate arguments
	if (server == NULL || sock == NULL || f == NULL)
	{
		Debug("SiFarmServMain Failed.\n");
		return;
	}

	Debug("SiFarmServMain Started.\n");

	c = server->Cedar;

	// Send a directive to create all static HUBs at the stage
	// where the members have been connected to the controller
	LockList(c->HubList);
	{
		for (i = 0;i < LIST_NUM(c->HubList);i++)
		{
			HUB *h = LIST_DATA(c->HubList, i);
			if (h->Offline == false)
			{
				if (h->Type == HUB_TYPE_FARM_STATIC)
				{
					PACK *p;
					HUB_LIST *hh;
					p = NewPack();
					SiPackAddCreateHub(p, h);
					PackAddStr(p, "taskname", "createhub");
					HttpServerSend(sock, p);
					FreePack(p);
					p = HttpServerRecv(sock);
					FreePack(p);

					p = NewPack();
					SiPackAddCreateHub(p, h);
					PackAddStr(p, "taskname", "updatehub");
					HttpServerSend(sock, p);
					FreePack(p);
					p = HttpServerRecv(sock);
					FreePack(p);

					hh = ZeroMalloc(sizeof(HUB_LIST));
					hh->DynamicHub = false;
					hh->FarmMember = f;
					StrCpy(hh->Name, sizeof(hh->Name), h->Name);
					LockList(f->HubList);
					{
						Add(f->HubList, hh);
					}
					UnlockList(f->HubList);
				}
			}
		}
	}
	UnlockList(c->HubList);

	Debug("SiFarmServMain: while (true)\n");

	while (true)
	{
		FARM_TASK *t;
		UINT64 tick;

		do
		{
			// Check whether a new task arrived
			LockQueue(f->TaskQueue);
			{
				t = GetNext(f->TaskQueue);
			}
			UnlockQueue(f->TaskQueue);

			if (t != NULL)
			{
				// Handle this task
				PACK *p = t->Request;
				bool ret;

				// Transmission
				ret = HttpServerSend(sock, p);
				send_noop = false;

				if (ret == false)
				{
					// Disconnected
					// Cancel this task
					Set(t->CompleteEvent);
					goto DISCONNECTED;
				}

				// Receive
				p = HttpServerRecvEx(sock, FIRM_SERV_RECV_PACK_MAX_SIZE);

				t->Response = p;
				Set(t->CompleteEvent);

				if (p == NULL)
				{
					// Avoid infinite loop
					Disconnect(sock);
					goto DISCONNECTED;
				}
			}
		}
		while (t != NULL);

		if (send_noop)
		{
			// Send a NOOP
			PACK *p;
			bool ret;
			p = NewPack();
			PackAddStr(p, "taskname", "noop");

			ret = HttpServerSend(sock, p);
			FreePack(p);

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

			p = HttpServerRecv(sock);
			if (p == NULL)
			{
				goto DISCONNECTED;
			}

			FreePack(p);
		}

		tick = Tick64();

		while (true)
		{
			bool break_flag;
			if ((tick + wait_time) <= Tick64())
			{
				break;
			}

			Wait(f->TaskPostEvent, 250);

			break_flag = false;
			LockQueue(f->TaskQueue);
			{
				if (f->TaskQueue->num_item != 0)
				{
					break_flag = true;
				}
			}
			UnlockQueue(f->TaskQueue);

			if (break_flag || f->Halting || server->Halt)
			{
				break;
			}
		}
		send_noop = true;
	}

DISCONNECTED:

	Debug("SiFarmServMain: DISCONNECTED\n");

	f->Halting = true;
	// Cancel all outstanding tasks
	LockQueue(f->TaskQueue);
	{
		FARM_TASK *t;

		while (t = GetNext(f->TaskQueue))
		{
			Set(t->CompleteEvent);
		}
	}
	UnlockQueue(f->TaskQueue);
}

// Farm server function that handles the connection from farm members
void SiFarmServ(SERVER *server, SOCK *sock, X *cert, UINT ip, UINT num_port, UINT *ports, char *hostname, UINT point, UINT weight, UINT max_sessions)
{
	PACK *p;
	FARM_MEMBER *f;
	UINT i;
	char tmp[MAX_SIZE];
	// Validate arguments
	if (server == NULL || sock == NULL || cert == NULL || num_port == 0 || ports == NULL || hostname == NULL)
	{
		return;
	}

	if (weight == 0)
	{
		weight = FARM_DEFAULT_WEIGHT;
	}

	if (max_sessions == 0)
	{
		max_sessions = SERVER_MAX_SESSIONS;
	}

	if (ip == 0)
	{
		// If the public IP address is not specified, specify the connection
		// source IP address of this farm member server
		ip = IPToUINT(&sock->RemoteIP);
	}

	IPToStr32(tmp, sizeof(tmp), ip);
	SLog(server->Cedar, "LS_FARM_SERV_START", tmp, hostname);

	// Inform the success
	p = NewPack();
	HttpServerSend(sock, p);
	FreePack(p);

	IPToStr32(tmp, sizeof(tmp), ip);
	Debug("Farm Member %s Connected. IP: %s\n", hostname, tmp);

	SetTimeout(sock, SERVER_CONTROL_TCP_TIMEOUT);

	f = ZeroMalloc(sizeof(FARM_MEMBER));
	f->Cedar = server->Cedar;
	f->Ip = ip;
	f->NumPort = num_port;
	f->Ports = ports;
	StrCpy(f->hostname, sizeof(f->hostname), hostname);
	f->ServerCert = cert;
	f->ConnectedTime = SystemTime64();
	f->Weight = weight;
	f->MaxSessions = max_sessions;

	f->HubList = NewList(CompareHubList);
	f->Point = point;

	f->TaskQueue = NewQueue();
	f->TaskPostEvent = NewEvent();

	// Add to the list
	LockList(server->FarmMemberList);
	{
		Add(server->FarmMemberList, f);
	}
	UnlockList(server->FarmMemberList);

	// Main process
	SiFarmServMain(server, sock, f);

	// Remove from the list
	LockList(server->FarmMemberList);
	{
		Delete(server->FarmMemberList, f);
	}
	UnlockList(server->FarmMemberList);

	ReleaseQueue(f->TaskQueue);
	ReleaseEvent(f->TaskPostEvent);

	for (i = 0;i < LIST_NUM(f->HubList);i++)
	{
		HUB_LIST *hh = LIST_DATA(f->HubList, i);
		Free(hh);
	}

	ReleaseList(f->HubList);

	Free(f);

	SLog(server->Cedar, "LS_FARM_SERV_END", hostname);
}

// Search in HUB list
int CompareHubList(void *p1, void *p2)
{
	HUB_LIST *h1, *h2;
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	h1 = *(HUB_LIST **)p1;
	h2 = *(HUB_LIST **)p2;
	if (h1 == NULL || h2 == NULL)
	{
		return 0;
	}
	return StrCmpi(h1->Name, h2->Name);
}

// Search in WireGuard key list
int CompareWgk(void *p1, void *p2)
{
	WGK *wgk_1, *wgk_2;

	if (p1 == NULL || p2 == NULL)
	{
		return (p1 == NULL && p2 == NULL ? 0 : (p1 == NULL ? -1 : 1));
	}

	wgk_1 = *(WGK **)p1;
	wgk_2 = *(WGK **)p2;

	if (wgk_1 == NULL || wgk_2 == NULL)
	{
		return (wgk_1 == NULL && wgk_2 == NULL ? 0 : (wgk_1 == NULL ? -1 : 1));
	}

	return StrCmp(wgk_1->Key, wgk_2->Key);
}

// Connection thread to the controller
void SiConnectToControllerThread(THREAD *thread, void *param)
{
	FARM_CONTROLLER *f;
	SESSION *s;
	CONNECTION *c;
	SERVER *server;
	bool first_failed;
	// Validate arguments
	if (thread == NULL || param == NULL)
	{
		return;
	}

#ifdef	OS_WIN32
	MsSetThreadPriorityRealtime();
#endif	// OS_WIN32

	f = (FARM_CONTROLLER *)param;
	f->Thread = thread;
	AddRef(f->Thread->ref);
	NoticeThreadInit(thread);

	f->StartedTime = SystemTime64();

	server = f->Server;

	SLog(server->Cedar, "LS_FARM_CONNECT_1", server->ControllerName);

	first_failed = true;

	while (true)
	{
		// Attempt to connect
		CLIENT_OPTION o;

		f->LastError = ERR_TRYING_TO_CONNECT;

		Zero(&o, sizeof(CLIENT_OPTION));
		StrCpy(o.Hostname, sizeof(o.Hostname), server->ControllerName);
		o.Port = server->ControllerPort;
		f->NumTry++;

		Debug("Try to Connect %s (Controller).\n", server->ControllerName);

		s = NewRpcSessionEx(server->Cedar, &o, NULL, CEDAR_SERVER_FARM_STR);

		if (s != NULL)
		{
			// Connection success: send the authentication data
			PACK *p = NewPack();
			UCHAR secure_password[SHA1_SIZE];
			BUF *b;

			c = s->Connection;

			Lock(f->lock);
			{
				f->Sock = c->FirstSock;
				AddRef(f->Sock->ref);
				SetTimeout(f->Sock, SERVER_CONTROL_TCP_TIMEOUT);
			}
			Unlock(f->lock);

			// Method
			PackAddStr(p, "method", "farm_connect");
			PackAddClientVersion(p, s->Connection);

			// Password
			SecurePassword(secure_password, server->MemberPassword, s->Connection->Random);
			PackAddData(p, "SecurePassword", secure_password, sizeof(secure_password));

			Lock(server->Cedar->lock);
			{
				b = XToBuf(server->Cedar->ServerX, false);
			}
			Unlock(server->Cedar->lock);

			if (b != NULL)
			{
				char tmp[MAX_SIZE];
				bool ret;
				UINT i;
				// Server certificate
				PackAddBuf(p, "ServerCert", b);
				FreeBuf(b);

				// Maximum number of sessions
				PackAddInt(p, "MaxSessions", GetServerCapsInt(server, "i_max_sessions"));

				// Point
				PackAddInt(p, "Point", SiGetPoint(server));
				PackAddInt(p, "Weight", server->Weight);

				// Host name
				GetMachineName(tmp, sizeof(tmp));
				PackAddStr(p, "HostName", tmp);

				// Public IP
				PackAddIp32(p, "PublicIp", server->PublicIp);

				// Public port
				for (i = 0;i < server->NumPublicPort;i++)
				{
					PackAddIntEx(p, "PublicPort", server->PublicPorts[i], i, server->NumPublicPort);
				}

				ret = HttpClientSend(c->FirstSock, p);

				if (ret)
				{
					PACK *p;
					UINT err = ERR_PROTOCOL_ERROR;

					first_failed = true;
					p = HttpClientRecv(c->FirstSock);
					if (p != NULL && (err = GetErrorFromPack(p)) == 0)
					{
						// Successful connection
						SLog(server->Cedar, "LS_FARM_START");
						f->CurrentConnectedTime = SystemTime64();
						if (f->FirstConnectedTime == 0)
						{
							f->FirstConnectedTime = SystemTime64();
						}
						f->NumConnected++;
						Debug("Connect Succeed.\n");
						f->Online = true;

						// Main process
						SiAcceptTasksFromController(f, c->FirstSock);

						f->Online = false;
					}
					else
					{
						// Error
						f->LastError = err;
						SLog(server->Cedar, "LS_FARM_CONNECT_2", server->ControllerName,
							GetUniErrorStr(err), err);
					}
					FreePack(p);
				}
				else
				{
					f->LastError = ERR_DISCONNECTED;

					if (first_failed)
					{
						SLog(server->Cedar, "LS_FARM_CONNECT_3", server->ControllerName, RETRY_CONNECT_TO_CONTROLLER_INTERVAL / 1000);
						first_failed = false;
					}
				}
			}

			FreePack(p);

			// Disconnect
			Lock(f->lock);
			{
				if (f->Sock != NULL)
				{
					ReleaseSock(f->Sock);
					f->Sock = NULL;
				}
			}
			Unlock(f->lock);

			ReleaseSession(s);
			s = NULL;

			if (f->LastError == ERR_TRYING_TO_CONNECT)
			{
				f->LastError = ERR_DISCONNECTED;
			}
		}
		else
		{
			// Connection failure
			f->LastError = ERR_CONNECT_TO_FARM_CONTROLLER;

			if (first_failed)
			{
				SLog(server->Cedar, "LS_FARM_CONNECT_3", server->ControllerName, RETRY_CONNECT_TO_CONTROLLER_INTERVAL / 1000);
				first_failed = false;
			}
		}

		Debug("Controller Disconnected. ERROR = %S\n", _E(f->LastError));

		f->NumFailed = f->NumTry - f->NumConnected;

		// Wait for event
		Wait(f->HaltEvent, RETRY_CONNECT_TO_CONTROLLER_INTERVAL);

		if (f->Halt)
		{
			// Halting flag
			break;
		}
	}

	SLog(server->Cedar, "LS_FARM_DISCONNECT");
}

// Disconnect the connection to the controller
void SiStopConnectToController(FARM_CONTROLLER *f)
{
	// Validate arguments
	if (f == NULL)
	{
		return;
	}

	f->Halt = true;

	// Stop the connection
	Lock(f->lock);
	{
		Disconnect(f->Sock);
	}
	Unlock(f->lock);

	Set(f->HaltEvent);

	// Wait for the thread termination
	WaitThread(f->Thread, INFINITE);
	ReleaseThread(f->Thread);

	DeleteLock(f->lock);
	ReleaseEvent(f->HaltEvent);

	Free(f);
}

// Start a connection to the controller
FARM_CONTROLLER *SiStartConnectToController(SERVER *s)
{
	FARM_CONTROLLER *f;
	THREAD *t;
	// Validate arguments
	if (s == NULL)
	{
		return NULL;
	}

	f = ZeroMalloc(sizeof(FARM_CONTROLLER));
	f->Server = s;
	f->LastError = ERR_TRYING_TO_CONNECT;
	f->HaltEvent = NewEvent();
	f->lock = NewLock();

	t = NewThread(SiConnectToControllerThread, f);
	WaitThreadInit(t);
	ReleaseThread(t);

	return f;
}

// Get the current version
void SiGetCurrentRegion(CEDAR *c, char *region, UINT region_size)
{
	ClearStr(region, region_size);
	// Validate arguments
	if (c == NULL || region == NULL)
	{
		return;
	}

	Lock(c->CurrentRegionLock);
	{
		StrCpy(region, region_size, c->CurrentRegion);
	}
	Unlock(c->CurrentRegionLock);

	if (IsEmptyStr(region))
	{
		if (GetCurrentLangId() == SE_LANG_JAPANESE)
		{
			StrCpy(region, region_size, "JP");
		}
		else if (GetCurrentLangId() == SE_LANG_CHINESE_ZH)
		{
			StrCpy(region, region_size, "CN");
		}
	}
}

// Check whether some enterprise functions are restricted
// 
// ** Hints by Daiyuu Nobori, written on March 19, 2014 **
// 
// The following 'enterprise functions' are implemented on SoftEther VPN Server
// since March 19, 2014. However, these functions are disabled on
// SoftEther VPN Servers which run in Japan and China.
// 
// - RADIUS / NT Domain user authentication
// - RSA certificate authentication
// - Deep-inspect packet logging
// - Source IP address control list
// - syslog transfer
// 
// The SoftEther VPN Project intentionally disables these functions for users
// in Japan and China. The reason is: Daiyuu Nobori, the chief author of
// SoftEther VPN, has been liable to observe the existing agreements and
// restrictions between him and some companies. The agreements have regulated
// the region-limited restriction to implement and distribute the above
// enterprise functions on the SoftEther VPN open-source program.
// 
// Therefore, the SoftEther VPN Project distributes the binary program and
// the source code with the "SiIsEnterpriseFunctionsRestrictedOnOpenSource"
// function. This function identifies whether the SoftEther VPN Server
// program is running in either Japan or China. If the restricted region is
// detected, then the above enterprise functions will be disabled.
// 
// Please note that the above restriction has been imposed only on the
// original binaries and source codes from the SoftEther VPN Project.
// Anyone, except Daiyuu Nobori, who understands and writes the C language
// program can remove this restriction at his own risk.
// 
bool SiIsEnterpriseFunctionsRestrictedOnOpenSource(CEDAR *c)
{
	char region[128];
	bool ret = false;
	// Validate arguments
	if (c == NULL)
	{
		return false;
	}


	SiGetCurrentRegion(c, region, sizeof(region));

	if (StrCmpi(region, "JP") == 0 || StrCmpi(region, "CN") == 0)
	{
		ret = true;
	}

	return ret;
}

// Update the current region
void SiUpdateCurrentRegion(CEDAR *c, char *region, bool force_update)
{
	bool changed = false;
	// Validate arguments
	if (c == NULL)
	{
		return;
	}

	if (IsEmptyStr(region) == false)
	{
		Lock(c->CurrentRegionLock);
		{
			if (StrCmpi(c->CurrentRegion, region) != 0)
			{
				StrCpy(c->CurrentRegion, sizeof(c->CurrentRegion), region);
				changed = true;
			}
		}
		Unlock(c->CurrentRegionLock);
	}

	if (force_update)
	{
		changed = true;
	}

	if (changed)
	{
		FlushServerCaps(c->Server);
	}
}

// Create a server
SERVER *SiNewServer(bool bridge)
{
	return SiNewServerEx(bridge, false, false);
}
SERVER *SiNewServerEx(bool bridge, bool in_client_inner_server, bool relay_server)
{
	SERVER *s;
	LISTENER *inproc;
	LISTENER *azure;
	LISTENER *rudp;

	DnsThreadNumMaxSet(DNS_THREAD_DEFAULT_NUM_MAX);

	s = ZeroMalloc(sizeof(SERVER));

	SetEraserCheckInterval(0);

	SiInitHubCreateHistory(s);

	InitServerCapsCache(s);

	Rand(s->MyRandomKey, sizeof(s->MyRandomKey));

	s->lock = NewLock();


	s->OpenVpnSstpConfigLock = NewLock();
	s->SaveCfgLock = NewLock();
	s->ref = NewRef();
	s->Cedar = NewCedar(NULL, NULL);
	s->Cedar->Server = s;


#ifdef	OS_WIN32
	s->IsInVm = MsIsInVm();
#else	// OS_WIN32
	s->IsInVm = UnixIsInVm();
#endif	// OS_WIN32

#ifdef	ENABLE_AZURE_SERVER
	if (IsFileExists("@azureserver.config"))
	{
		DisableRDUPServerGlobally();
		s->AzureServer = NewAzureServer(s->Cedar);

		SleepThread(500);
	}
#endif	// ENABLE_AZURE_SERVER

	s->Cedar->CheckExpires = true;
	s->ServerListenerList = NewList(CompareServerListener);
	s->PortsUDP = NewIntList(true);
	s->StartTime = SystemTime64();
	s->TasksFromFarmControllerLock = NewLock();

	if (bridge)
	{
		SetCedarVpnBridge(s->Cedar);
	}

#ifdef OS_WIN32
	if (IsHamMode() == false)
	{
		RegistWindowsFirewallAll();
	}
#endif

	s->Keep = StartKeep();

	// Log related
	MakeDir(bridge == false ? SERVER_LOG_DIR_NAME : BRIDGE_LOG_DIR_NAME);
	s->Logger = NewLog(bridge == false ? SERVER_LOG_DIR_NAME : BRIDGE_LOG_DIR_NAME, SERVER_LOG_PERFIX, LOG_SWITCH_DAY);

	SLog(s->Cedar, "L_LINE");
	SLog(s->Cedar, "LS_START_2", s->Cedar->ServerStr, s->Cedar->VerString);
	SLog(s->Cedar, "LS_START_3", s->Cedar->BuildInfo);
	SLog(s->Cedar, "LS_START_UTF8");
	SLog(s->Cedar, "LS_START_1");



	// Initialize the configuration
	SiInitConfiguration(s);

	s->Syslog = NewSysLog(NULL, 0, &s->Cedar->Server->ListenIP);
	s->SyslogLock = NewLock();

	SetFifoCurrentReallocMemSize(MEM_FIFO_REALLOC_MEM_SIZE);


	// Raise the priority
	if (s->NoHighPriorityProcess == false)
	{
		OSSetHighPriority();
	}

#ifdef	OS_UNIX
	UnixSetHighOomScore();
#endif	// OS_UNIX

	if (s->ServerType == SERVER_TYPE_FARM_MEMBER)
	{
		// Start a connection to the controller
		s->FarmController = SiStartConnectToController(s);
	}
	else if (s->ServerType == SERVER_TYPE_FARM_CONTROLLER)
	{
		FARM_MEMBER *f;
		// Start operating as a controller
		s->FarmMemberList = NewList(NULL);

		f = ZeroMalloc(sizeof(FARM_MEMBER));
		f->Cedar = s->Cedar;
		GetMachineName(f->hostname, sizeof(f->hostname));
		f->Me = true;
		f->HubList = NewList(CompareHubList);
		f->Weight = s->Weight;

		s->Me = f;

		Add(s->FarmMemberList, f);

		SiStartFarmControl(s);

		s->FarmControllerInited = true;
	}

	// Start a in-processlistener 
	inproc = NewListener(s->Cedar, LISTENER_INPROC, 0);
	ReleaseListener(inproc);

	// Start a listener for Azure
	if (s->AzureClient != NULL)
	{
		azure = NewListener(s->Cedar, LISTENER_REVERSE, 0);
		ReleaseListener(azure);
	}

	// Start a R-UDP listener
	if (s->DisableNatTraversal == false && s->Cedar->Bridge == false)
	{
		rudp = NewListenerEx4(s->Cedar, LISTENER_RUDP, 0, TCPAcceptedThread, NULL, false, false,
			&s->NatTGlobalUdpPort, RAND_PORT_ID_SERVER_LISTEN);
		ReleaseListener(rudp);
	}

	// Start a VPN-over-ICMP listener
	s->DynListenerIcmp = NewDynamicListener(s->Cedar, &s->EnableVpnOverIcmp, LISTENER_ICMP, 0);

	// Start a VPN-over-DNS listener
	s->DynListenerDns = NewDynamicListener(s->Cedar, &s->EnableVpnOverDns, LISTENER_DNS, 53);


	SiInitDeadLockCheck(s);

	SiUpdateCurrentRegion(s->Cedar, "", true);

	return s;
}

