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


// Account.c
// Account Manager

#include "CedarPch.h"

// Policy items
POLICY_ITEM policy_item[] =
{
//  ID,     Value,  Omittable, Min, Max, Default, Unit name
// Ver 2.0
	{0,		false,	false,	0,	0,	0,		NULL},			// Access
	{1,		false,	false,	0,	0,	0,		NULL},			// DHCPFilter
	{2,		false,	false,	0,	0,	0,		NULL},			// DHCPNoServer
	{3,		false,	false,	0,	0,	0,		NULL},			// DHCPForce
	{4,		false,	false,	0,	0,	0,		NULL},			// NoBridge
	{5,		false,	false,	0,	0,	0,		NULL},			// NoRouting
	{6,		false,	false,	0,	0,	0,		NULL},			// CheckMac
	{7,		false,	false,	0,	0,	0,		NULL},			// CheckIP
	{8,		false,	false,	0,	0,	0,		NULL},			// ArpDhcpOnly
	{9,		false,	false,	0,	0,	0,		NULL},			// PrivacyFilter
	{10,	false,	false,	0,	0,	0,		NULL},			// NoServer
	{11,	false,	false,	0,	0,	0,		NULL},			// NoBroadcastLimiter
	{12,	false,	false,	0,	0,	0,		NULL},			// MonitorPort
	{13,	true,	false,	1,	32,	32,		"POL_INT_COUNT"},	// MaxConnection
	{14,	true,	false,	5,	60,	20,		"POL_INT_SEC"},	// TimeOut
	{15,	true,	true,	1,	65535,	0,	"POL_INT_COUNT"},	// MaxMac
	{16,	true,	true,	1,	65535,	0,	"POL_INT_COUNT"},	// MaxIP
	{17,	true,	true,	1,	4294967295UL,	0,	"POL_INT_BPS"},	// MaxUpload
	{18,	true,	true,	1,	4294967295UL,	0,	"POL_INT_BPS"},	// MaxDownload
	{19,	false,	false,	0,	0,	0,		NULL},			// FixPassword
	{20,	true,	true,	1,	65535,	0,	"POL_INT_COUNT"},	// MultiLogins
	{21,	false,	false,	0,	0,	0,		NULL},			// NoQoS
// Ver 3.0
	{22,	false,	false,	0,	0,	0,		NULL},			// RSandRAFilter
	{23,	false,	false,	0,	0,	0,		NULL},			// RAFilter
	{24,	false,	false,	0,	0,	0,		NULL},			// DHCPv6Filter
	{25,	false,	false,	0,	0,	0,		NULL},			// DHCPv6NoServer
	{26,	false,	false,	0,	0,	0,		NULL},			// NoRoutingV6
	{27,	false,	false,	0,	0,	0,		NULL},			// CheckIPv6
	{28,	false,	false,	0,	0,	0,		NULL},			// NoServerV6
	{29,	true,	true,	1,	65535,	0,	"POL_INT_COUNT"},	// MaxIPv6
	{30,	false,	false,	0,	0,	0,		NULL},			// NoSavePassword
	{31,	true,	true,	1,	4294967295UL,	0,	"POL_INT_SEC"},	// AutoDisconnect
	{32,	false,	false,	0,	0,	0,		NULL},			// FilterIPv4
	{33,	false,	false,	0,	0,	0,		NULL},			// FilterIPv6
	{34,	false,	false,	0,	0,	0,		NULL},			// FilterNonIP
	{35,	false,	false,	0,	0,	0,		NULL},			// NoIPv6DefaultRouterInRA
	{36,	false,	false,	0,	0,	0,		NULL},			// NoIPv6DefaultRouterInRAWhenIPv6
	{37,	true,	true,	1,	4095,	0,	"POL_INT_VLAN"},	// VLanId
};

// Format policy value
void FormatPolicyValue(wchar_t *str, UINT size, UINT id, UINT value)
{
	POLICY_ITEM *p;
	// Validate arguments
	if (str == NULL)
	{
		return;
	}

	p = GetPolicyItem(id);

	if (p->TypeInt == false)
	{
		// bool type
		if (value == 0)
		{
			UniStrCpy(str, size, L"No");
		}
		else
		{
			UniStrCpy(str, size, L"Yes");
		}
	}
	else
	{
		// int type
		if (value == 0 && p->AllowZero)
		{
			UniStrCpy(str, size, _UU("CMD_NO_SETTINGS"));
		}
		else
		{
			UniFormat(str, size, _UU(p->FormatStr), value);
		}
	}
}

// Get description string for range of the policy value
void GetPolicyValueRangeStr(wchar_t *str, UINT size, UINT id)
{
	POLICY_ITEM *p;
	// Validate arguments
	if (str == NULL)
	{
		return;
	}

	p = GetPolicyItem(id);

	if (p->TypeInt == false)
	{
		// bool type
		UniStrCpy(str, size, _UU("CMD_PolicyList_Range_Bool"));
	}
	else
	{
		wchar_t *tag;
		wchar_t tmp1[256], tmp2[256];

		// int type
		if (p->AllowZero)
		{
			tag = _UU("CMD_PolicyList_Range_Int_2");
		}
		else
		{
			tag = _UU("CMD_PolicyList_Range_Int_1");
		}

		UniFormat(tmp1, sizeof(tmp1), _UU(p->FormatStr), p->MinValue);
		UniFormat(tmp2, sizeof(tmp2), _UU(p->FormatStr), p->MaxValue);

		UniFormat(str, size, tag, tmp1, tmp2);
	}
}

// Get a policy item for id
POLICY_ITEM *GetPolicyItem(UINT id)
{
	return &policy_item[id];
}

// Does cascade connection support the specified policy?
bool PolicyIsSupportedForCascade(UINT i)
{
	if (i == 0 || i == 4 || i == 5 || i == 12 || i == 13 ||
		i == 14 || i == 19 || i == 20 || i == 21 || i == 26 || i == 30 || i == 31 || i == 36)
	{
		// These items are not supported by cascade connection.
		return false;
	}

	return true;
}

// Get policy name
char *PolicyIdToStr(UINT i)
{
	switch (i)
	{
	// Ver 2.0
	case 0:		return "Access";
	case 1:		return "DHCPFilter";
	case 2:		return "DHCPNoServer";
	case 3:		return "DHCPForce";
	case 4:		return "NoBridge";
	case 5:		return "NoRouting";
	case 6:		return "CheckMac";
	case 7:		return "CheckIP";
	case 8:		return "ArpDhcpOnly";
	case 9:		return "PrivacyFilter";
	case 10:	return "NoServer";
	case 11:	return "NoBroadcastLimiter";
	case 12:	return "MonitorPort";
	case 13:	return "MaxConnection";
	case 14:	return "TimeOut";
	case 15:	return "MaxMac";
	case 16:	return "MaxIP";
	case 17:	return "MaxUpload";
	case 18:	return "MaxDownload";
	case 19:	return "FixPassword";
	case 20:	return "MultiLogins";
	case 21:	return "NoQoS";

	// Ver 3.0
	case 22:	return "RSandRAFilter";
	case 23:	return "RAFilter";
	case 24:	return "DHCPv6Filter";
	case 25:	return "DHCPv6NoServer";
	case 26:	return "NoRoutingV6";
	case 27:	return "CheckIPv6";
	case 28:	return "NoServerV6";
	case 29:	return "MaxIPv6";
	case 30:	return "NoSavePassword";
	case 31:	return "AutoDisconnect";
	case 32:	return "FilterIPv4";
	case 33:	return "FilterIPv6";
	case 34:	return "FilterNonIP";
	case 35:	return "NoIPv6DefaultRouterInRA";
	case 36:	return "NoIPv6DefaultRouterInRAWhenIPv6";
	case 37:	return "VLanId";
	}

	return NULL;
}

// Get policy id for name
UINT PolicyStrToId(char *name)
{
	UINT i;
	// Validate arguments
	if (name == NULL)
	{
		return INFINITE;
	}

	for (i = 0;i < NUM_POLICY_ITEM;i++)
	{
		if (StartWith(PolicyIdToStr(i), name))
		{
			return i;
		}
	}

	return INFINITE;
}

// Get number of policies
UINT PolicyNum()
{
	return NUM_POLICY_ITEM;
}

// Check the name is valid for account name
bool IsUserName(char *name)
{
	UINT i, len;
	char tmp[MAX_SIZE];
	// Validate arguments
	if (name == NULL)
	{
		return false;
	}

	StrCpy(tmp, sizeof(tmp), name);
	name = tmp;

	Trim(name);

	len = StrLen(name);
	if (len == 0)
	{
		return false;
	}

	if (StrCmpi(name, "*") == 0)
	{
		return true;
	}

	for (i = 0; i < len; i++)
	{
		if (IsSafeChar(name[i]) == false && name[i] != '@')
		{
			return false;
		}
	}

	if (StrCmpi(name, LINK_USER_NAME) == 0)
	{
		return false;
	}

	if (StartWith(name, L3_USERNAME))
	{
		return false;
	}

	if (StrCmpi(name, LINK_USER_NAME_PRINT) == 0)
	{
		return false;
	}

	if (StrCmpi(name, SNAT_USER_NAME) == 0)
	{
		return false;
	}

	if (StrCmpi(name, SNAT_USER_NAME_PRINT) == 0)
	{
		return false;
	}

	if (StrCmpi(name, BRIDGE_USER_NAME) == 0)
	{
		return false;
	}

	if (StrCmpi(name, BRIDGE_USER_NAME_PRINT) == 0)
	{
		return false;
	}

	if (StrCmpi(name, ADMINISTRATOR_USERNAME) == 0)
	{
		return false;
	}

	return true;
}

// Get policy title
wchar_t *GetPolicyTitle(UINT id)
{
	char tmp[MAX_SIZE];
	Format(tmp, sizeof(tmp), "POL_%u", id);

	return _UU(tmp);
}

// Get policy description
wchar_t *GetPolicyDescription(UINT id)
{
	char tmp[MAX_SIZE];
	Format(tmp, sizeof(tmp), "POL_EX_%u", id);

	return _UU(tmp);
}

// Clone the policy value
POLICY *ClonePolicy(POLICY *policy)
{
	POLICY *ret;
	// Validate arguments
	if (policy == NULL)
	{
		return NULL;
	}

	ret = ZeroMalloc(sizeof(POLICY));
	Copy(ret, policy, sizeof(POLICY));

	return ret;
}

// Overwrite policy value (If old version data overwrites new version, leave new version value as it is.)
void OverwritePolicy(POLICY **target, POLICY *p)
{
	// Validate arguments
	if (target == NULL)
	{
		return;
	}

	if (p == NULL)
	{
		// Erase policy
		if (*target != NULL)
		{
			Free(*target);
			*target = NULL;
		}
	}
	else
	{
		if (p->Ver3)
		{
			// Ver 3
			if (*target != NULL)
			{
				Free(*target);
				*target = NULL;
			}

			*target = ClonePolicy(p);
		}
		else
		{
			// Ver 2
			if (*target == NULL)
			{
				*target = ClonePolicy(p);
			}
			else
			{
				Copy(*target, p, NUM_POLICY_ITEM_FOR_VER2 * sizeof(UINT));
			}
		}
	}
}

// Set user policy
void SetUserPolicy(USER *u, POLICY *policy)
{
	// Validate arguments
	if (u == NULL)
	{
		return;
	}

	Lock(u->lock);
	{
		OverwritePolicy(&u->Policy, policy);
	}
	Unlock(u->lock);
}

// Set group policy
void SetGroupPolicy(USERGROUP *g, POLICY *policy)
{
	// Validate arguments
	if (g == NULL)
	{
		return;
	}

	Lock(g->lock);
	{
		OverwritePolicy(&g->Policy, policy);
	}
	Unlock(g->lock);
}

// Get group policy
POLICY *GetGroupPolicy(USERGROUP *g)
{
	POLICY *ret;
	// Validate arguments
	if (g == NULL)
	{
		return NULL;
	}

	Lock(g->lock);
	{
		if (g->Policy == NULL)
		{
			ret = NULL;
		}
		else
		{
			ret = ClonePolicy(g->Policy);
		}
	}
	Unlock(g->lock);

	return ret;
}

// Get default policy template
POLICY *GetDefaultPolicy()
{
	static POLICY def_policy =
	{
		true,
		false,
		false,
		false,
		false,
		false,
		false,
		false,
		false,
		false,
		false,
		false,
		false,
		32,
		20,
		0,
		0,
		0,
		0,
		false,
		0,
		false,
		false,
		false,
		false,
		false,
		false,
		false,
		false,
		0,
		false,
		0,
		false,
		false,
		false,
		false,
		false,
	};

	return &def_policy;
}

// Create a NT authentication data
void *NewNTAuthData(wchar_t *username)
{
	AUTHNT *a;
	// Validate arguments
	a = ZeroMallocEx(sizeof(AUTHNT), true);
	a->NtUsername = CopyUniStr(username);

	return a;
}

// Create a Radius authentication data
void *NewRadiusAuthData(wchar_t *username)
{
	AUTHRADIUS *a;
	// Validate arguments
	a = ZeroMallocEx(sizeof(AUTHRADIUS), true);
	a->RadiusUsername = CopyUniStr(username);

	return a;
}

// Create a root certification authentication data
void *NewRootCertAuthData(X_SERIAL *serial, wchar_t *common_name)
{
	AUTHROOTCERT *a;

	a = ZeroMallocEx(sizeof(AUTHROOTCERT), true);
	if (common_name != NULL && UniIsEmptyStr(common_name) == false)
	{
		a->CommonName = CopyUniStr(common_name);
	}
	if (serial != NULL && serial->size >= 1)
	{
		a->Serial = CloneXSerial(serial);
	}

	return a;
}

// Create an authentication data for user certification
void *NewUserCertAuthData(X *x)
{
	AUTHUSERCERT *a;

	a = ZeroMalloc(sizeof(AUTHUSERCERT));
	a->UserX = CloneX(x);

	return a;
}

// Hash the password
void HashPassword(void *dst, char *username, char *password)
{
	BUF *b;
	char *username_upper;
	// Validate arguments
	if (dst == NULL || username == NULL || password == NULL)
	{
		return;
	}

	b = NewBuf();
	username_upper = CopyStr(username);
	StrUpper(username_upper);
	WriteBuf(b, password, StrLen(password));
	WriteBuf(b, username_upper, StrLen(username_upper));
	Sha0(dst, b->Buf, b->Size);

	FreeBuf(b);
	Free(username_upper);
}

// Create a password authentication data
void *NewPasswordAuthData(char *username, char *password)
{
	AUTHPASSWORD *pw;
	// Validate arguments
	if (username == NULL || password == NULL)
	{
		return NULL;
	}

	pw = ZeroMalloc(sizeof(AUTHPASSWORD));
	HashPassword(pw->HashedKey, username, password);
	GenerateNtPasswordHash(pw->NtLmSecureHash, password);

	return pw;
}

// Create a password authentication data for the hashed password
void *NewPasswordAuthDataRaw(UCHAR *hashed_password, UCHAR *ntlm_secure_hash)
{
	AUTHPASSWORD *pw;
	// Validate arguments
	if (hashed_password == NULL)
	{
		return NULL;
	}

	pw = ZeroMalloc(sizeof(AUTHPASSWORD));
	Copy(pw->HashedKey, hashed_password, SHA1_SIZE);

	if (ntlm_secure_hash != NULL)
	{
		Copy(pw->NtLmSecureHash, ntlm_secure_hash, MD5_SIZE);
	}

	return pw;
}

// Clone authentication data
void *CopyAuthData(void *authdata, UINT authtype)
{
	AUTHPASSWORD *pw = (AUTHPASSWORD *)authdata;
	AUTHUSERCERT *usercert = (AUTHUSERCERT *)authdata;
	AUTHROOTCERT *rootcert = (AUTHROOTCERT *)authdata;
	AUTHRADIUS *radius = (AUTHRADIUS *)authdata;
	AUTHNT *nt = (AUTHNT *)authdata;
	// Validate arguments
	if (authdata == NULL || authtype == AUTHTYPE_ANONYMOUS)
	{
		return NULL;
	}

	switch (authtype)
	{
	case AUTHTYPE_PASSWORD:
		{
			AUTHPASSWORD *ret = ZeroMalloc(sizeof(AUTHPASSWORD));
			Copy(ret, pw, sizeof(AUTHPASSWORD));
			return ret;
		}
		break;

	case AUTHTYPE_USERCERT:
		{
			AUTHUSERCERT *ret = ZeroMalloc(sizeof(AUTHUSERCERT));
			ret->UserX = CloneX(usercert->UserX);
			return ret;
		}
		break;

	case AUTHTYPE_ROOTCERT:
		{
			AUTHROOTCERT *ret = ZeroMalloc(sizeof(AUTHROOTCERT));
			ret->CommonName = CopyUniStr(rootcert->CommonName);
			ret->Serial = CloneXSerial(rootcert->Serial);
			return ret;
		}
		break;

	case AUTHTYPE_RADIUS:
		{
			AUTHRADIUS *ret = ZeroMalloc(sizeof(AUTHRADIUS));
			ret->RadiusUsername = UniCopyStr(radius->RadiusUsername);
			return ret;
		}
		break;

	case AUTHTYPE_NT:
		{
			AUTHNT *ret = ZeroMalloc(sizeof(AUTHNT));
			ret->NtUsername = UniCopyStr(nt->NtUsername);
			return ret;
		}
		break;
	}

	return NULL;
}

// Set authentication data to the user
void SetUserAuthData(USER *u, UINT authtype, void *authdata)
{
	// Validate arguments
	if (u == NULL)
	{
		return;
	}
	if (authtype != AUTHTYPE_ANONYMOUS && authdata == NULL)
	{
		return;
	}

	Lock(u->lock);
	{
		if (u->AuthType != AUTHTYPE_ANONYMOUS)
		{
			if (u->AuthType == AUTHTYPE_PASSWORD && authtype == AUTHTYPE_PASSWORD)
			{
				AUTHPASSWORD *pw_old = (AUTHPASSWORD *)u->AuthData;
				AUTHPASSWORD *pw_new = (AUTHPASSWORD *)authdata;

				// Copy NTLM hash for new password from old data, if the password is not changed 
				// and management tool don't send NTLM hash.

				if (Cmp(pw_old->HashedKey, pw_new->HashedKey, SHA1_SIZE) == 0)
				{
					if (IsZero(pw_new->NtLmSecureHash, MD5_SIZE))
					{
						Copy(pw_new->NtLmSecureHash, pw_old->NtLmSecureHash, MD5_SIZE);
					}
				}
			}

			// Free current authentication data
			FreeAuthData(u->AuthType, u->AuthData);
		}
		// Set new authentication data
		u->AuthType = authtype;
		u->AuthData = authdata;
	}
	Unlock(u->lock);
}

// Set traffic data for group
void SetGroupTraffic(USERGROUP *g, TRAFFIC *t)
{
	// Validate arguments
	if (g == NULL)
	{
		return;
	}

	Lock(g->lock);
	{
		if (t != NULL)
		{
			Copy(g->Traffic, t, sizeof(TRAFFIC));
		}
		else
		{
			Zero(g->Traffic, sizeof(TRAFFIC));
		}
	}
	Unlock(g->lock);
}

// Set traffic data for user
void SetUserTraffic(USER *u, TRAFFIC *t)
{
	// Validate arguments
	if (u == NULL)
	{
		return;
	}

	Lock(u->lock);
	{
		if (t != NULL)
		{
			Copy(u->Traffic, t, sizeof(TRAFFIC));
		}
		else
		{
			Zero(u->Traffic, sizeof(TRAFFIC));
		}
	}
	Unlock(u->lock);
}

// Join the user to the group
void JoinUserToGroup(USER *u, USERGROUP *g)
{
	// Validate arguments
	if (u == NULL)
	{
		return;
	}

	if (g != NULL)
	{
		// Join
		Lock(u->lock);
		{
			Lock(g->lock);
			{
				if (u->Group != NULL)
				{
					// Remove the user from current group first
					// 
					ReleaseGroup(u->Group);
					u->Group = NULL;
					Free(u->GroupName);
					u->GroupName = NULL;
				}
				// Join the user to the group
				u->GroupName = CopyStr(g->Name);
				u->Group = g;
				AddRef(g->ref);
			}
			Unlock(g->lock);
		}
		Unlock(u->lock);
	}
	else
	{
		// Withdrawal
		Lock(u->lock);
		{
			if (u->Group != NULL)
			{
				// Remove the user from current group
				ReleaseGroup(u->Group);
				u->Group = NULL;
				Free(u->GroupName);
				u->GroupName = NULL;
			}
		}
		Unlock(u->lock);
	}
}

// Validate group name
bool AcIsGroup(HUB *h, char *name)
{
	USERGROUP *g;
	// Validate arguments
	if (h == NULL || name == NULL || NO_ACCOUNT_DB(h))
	{
		return false;
	}

	g = AcGetGroup(h, name);
	if (g == NULL)
	{
		return false;
	}
	ReleaseGroup(g);

	return true;
}

// Validate user name
bool AcIsUser(HUB *h, char *name)
{
	USER *u;
	// Validate arguments
	if (h == NULL || name == NULL || NO_ACCOUNT_DB(h))
	{
		return false;
	}

	u = AcGetUser(h, name);
	if (u == NULL)
	{
		return false;
	}
	ReleaseUser(u);

	return true;
}

// Get group object
USERGROUP *AcGetGroup(HUB *h, char *name)
{
	USERGROUP *g, t;
	// Validate arguments
	if (h == NULL || name == NULL || NO_ACCOUNT_DB(h))
	{
		return NULL;
	}

	t.Name = name;
	g = Search(h->HubDb->GroupList, &t);
	if (g == NULL)
	{
		return NULL;
	}
	AddRef(g->ref);

	return g;
}

// Get user object
USER *AcGetUser(HUB *h, char *name)
{
	USER *u, t;
	// Validate arguments
	if (h == NULL || name == NULL || NO_ACCOUNT_DB(h))
	{
		return NULL;
	}

	t.Name = name;
	u = Search(h->HubDb->UserList, &t);
	if (u == NULL)
	{
		return NULL;
	}
	AddRef(u->ref);

	return u;
}

// Delete the user
bool AcDeleteUser(HUB *h, char *name)
{
	USER *u;
	// Validate arguments
	if (h == NULL || name == NULL)
	{
		return false;
	}

	u = AcGetUser(h, name);
	if (u == NULL)
	{
		return false;
	}

	if (Delete(h->HubDb->UserList, u))
	{
		ReleaseUser(u);
	}

	ReleaseUser(u);

	return true;
}

// Delete the group
bool AcDeleteGroup(HUB *h, char *name)
{
	USERGROUP *g;
	UINT i;
	// Validate arguments
	if (h == NULL || name == NULL)
	{
		return false;
	}

	g = AcGetGroup(h, name);
	if (g == NULL)
	{
		return false;
	}

	if (Delete(h->HubDb->GroupList, g))
	{
		ReleaseGroup(g);
	}

	for (i = 0;i < LIST_NUM(h->HubDb->UserList);i++)
	{
		USER *u = LIST_DATA(h->HubDb->UserList, i);
		Lock(u->lock);
		{
			if (u->Group == g)
			{
				JoinUserToGroup(u, NULL);
			}
		}
		Unlock(u->lock);
	}

	ReleaseGroup(g);

	return true;
}

// Add new group to the hub
bool AcAddGroup(HUB *h, USERGROUP *g)
{
	// Validate arguments
	if (h == NULL || g == NULL || NO_ACCOUNT_DB(h))
	{
		return false;
	}

	if (LIST_NUM(h->HubDb->GroupList) >= MAX_GROUPS)
	{
		return false;
	}

	if (AcIsGroup(h, g->Name) != false)
	{
		return false;
	}

	Insert(h->HubDb->GroupList, g);
	AddRef(g->ref);

	return true;
}

// Add new user in the hub
bool AcAddUser(HUB *h, USER *u)
{
	// Validate arguments
	if (h == NULL || u == NULL || NO_ACCOUNT_DB(h))
	{
		return false;
	}

	if (LIST_NUM(h->HubDb->UserList) >= MAX_USERS)
	{
		return false;
	}

	if (AcIsUser(h, u->Name) != false)
	{
		return false;
	}

	Insert(h->HubDb->UserList, u);
	AddRef(u->ref);

	return true;
}

// Release user object (decrease reference counter)
void ReleaseUser(USER *u)
{
	// Validate arguments
	if (u == NULL)
	{
		return;
	}

	if (Release(u->ref) == 0)
	{
		CleanupUser(u);
	}
}

// Cleanup the user object 
void CleanupUser(USER *u)
{
	// Validate arguments
	if (u == NULL)
	{
		return;
	}

	DeleteLock(u->lock);
	Free(u->Name);
	Free(u->RealName);
	Free(u->Note);
	Free(u->GroupName);
	if (u->Group != NULL)
	{
		ReleaseGroup(u->Group);
	}

	// Free authentication data
	FreeAuthData(u->AuthType, u->AuthData);

	if (u->Policy)
	{
		// Free policy data
		Free(u->Policy);
	}

	FreeTraffic(u->Traffic);

	Free(u);
}

// Free authentication data
void FreeAuthData(UINT authtype, void *authdata)
{
	AUTHPASSWORD *pw = (AUTHPASSWORD *)authdata;
	AUTHUSERCERT *uc = (AUTHUSERCERT *)authdata;
	AUTHROOTCERT *rc = (AUTHROOTCERT *)authdata;
	AUTHRADIUS *rd = (AUTHRADIUS *)authdata;
	AUTHNT *nt = (AUTHNT *)authdata;
	// Validate arguments
	if (authtype == AUTHTYPE_ANONYMOUS || authdata == NULL)
	{
		return;
	}

	switch (authtype)
	{
	case AUTHTYPE_PASSWORD:
		// Password authentication
		// Nothing to free
		break;

	case AUTHTYPE_USERCERT:
		// User certification
		FreeX(uc->UserX);
		break;

	case AUTHTYPE_ROOTCERT:
		// Root certification
		if (rc->Serial != NULL)
		{
			FreeXSerial(rc->Serial);
		}
		if (rc->CommonName != NULL)
		{
			Free(rc->CommonName);
		}
		break;

	case AUTHTYPE_RADIUS:
		// Radius authentication
		Free(rd->RadiusUsername);
		break;

	case AUTHTYPE_NT:
		// Windows NT authentication
		Free(nt->NtUsername);
		break;
	}

	Free(authdata);
}

// Create new user object
USER *NewUser(char *name, wchar_t *realname, wchar_t *note, UINT authtype, void *authdata)
{
	USER *u;
	// Validate arguments
	if (name == NULL || realname == NULL || note == NULL)
	{
		return NULL;
	}
	if (authtype != AUTHTYPE_ANONYMOUS && authdata == NULL)
	{
		return NULL;
	}

	u = ZeroMalloc(sizeof(USER));
	u->lock = NewLock();
	u->ref = NewRef();
	u->Name = CopyStr(name);
	u->RealName = CopyUniStr(realname);
	u->Note = CopyUniStr(note);
	u->GroupName = NULL;
	u->Group = NULL;
	u->AuthType = authtype;
	u->AuthData = authdata;
	u->CreatedTime = SystemTime64();
	u->UpdatedTime = SystemTime64();

	u->Policy = NULL;
	u->Traffic = NewTraffic();

	return u;
}

// Release group object (decrease reference counter)
void ReleaseGroup(USERGROUP *g)
{
	// Validate arguments
	if (g == NULL)
	{
		return;
	}

	if (Release(g->ref) == 0)
	{
		CleanupGroup(g);
	}
}

// Cleanup the group object
void CleanupGroup(USERGROUP *g)
{
	// Validate arguments
	if (g == NULL)
	{
		return;
	}

	Free(g->Name);
	Free(g->RealName);
	Free(g->Note);

	if (g->Policy)
	{
		// Free policy data
		Free(g->Policy);
	}


	FreeTraffic(g->Traffic);

	DeleteLock(g->lock);
	Free(g);
}

// Create new group object
USERGROUP *NewGroup(char *name, wchar_t *realname, wchar_t *note)
{
	USERGROUP *g;
	// Validate arguments
	if (name == NULL || realname == NULL || note == NULL)
	{
		return NULL;
	}

	g = ZeroMalloc(sizeof(USERGROUP));
	g->lock = NewLock();
	g->ref = NewRef();
	g->Name = CopyStr(name);
	g->RealName = CopyUniStr(realname);
	g->Note = CopyUniStr(note);
	g->Policy = NULL;
	g->Traffic = NewTraffic();

	return g;
}

// Lock the account database for the hub
void AcLock(HUB *h)
{
	// Validate arguments
	if (h == NULL)
	{
		return;
	}
	if (NO_ACCOUNT_DB(h))
	{
		return;
	}

	// Lock group list and user list
	LockList(h->HubDb->GroupList);
	LockList(h->HubDb->UserList);
}

// Unlock the account database for the hub
void AcUnlock(HUB *h)
{
	// Validate arguments
	if (h == NULL)
	{
		return;
	}
	if (NO_ACCOUNT_DB(h))
	{
		return;
	}

	// Unlock group list and user list
	UnlockList(h->HubDb->UserList);
	UnlockList(h->HubDb->GroupList);
}

// Compare group names (for sort)
int CompareGroupName(void *p1, void *p2)
{
	USERGROUP *g1, *g2;
	// Validate arguments
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	g1 = *(USERGROUP **)p1;
	g2 = *(USERGROUP **)p2;
	if (g1 == NULL || g2 == NULL)
	{
		return 0;
	}

	return StrCmpi(g1->Name, g2->Name);
}

// Compare user names (for sort)
int CompareUserName(void *p1, void *p2)
{
	USER *u1, *u2;
	// Validate arguments
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	u1 = *(USER **)p1;
	u2 = *(USER **)p2;
	if (u1 == NULL || u2 == NULL)
	{
		return 0;
	}

	return StrCmpi(u1->Name, u2->Name);
}

// Get the MAC address from the user's note string
bool GetUserMacAddressFromUserNote(UCHAR *mac, wchar_t *note)
{
	bool ret = false;
	UINT i;

	Zero(mac, 6);
	if (mac == NULL || note == NULL)
	{
		return false;
	}

	i = UniSearchStrEx(note, USER_MAC_STR_PREFIX, 0, false);
	if (i != INFINITE)
	{
		wchar_t *macstr_start = &note[i + UniStrLen(USER_MAC_STR_PREFIX)];
		wchar_t macstr2[MAX_SIZE];
		UNI_TOKEN_LIST *tokens;

		UniStrCpy(macstr2, sizeof(macstr2), macstr_start);

		UniTrim(macstr2);

		tokens = UniParseToken(macstr2, L" ,/()[].");
		if (tokens != NULL)
		{
			if (tokens->NumTokens >= 1)
			{
				wchar_t *macstr = tokens->Token[0];

				if (UniIsEmptyStr(macstr) == false)
				{
					char macstr_a[MAX_SIZE];

					UniToStr(macstr_a, sizeof(macstr_a), macstr);

					ret = StrToMac(mac, macstr_a);
				}
			}

			UniFreeToken(tokens);
		}
	}

	return ret;
}
