/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011-2016 Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Affero General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Affero General Public License for more details.
*
*    You should have received a copy of the GNU Affero General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#include <Windows.h>
#include <LM.h>
#include <Sddl.h>
#include "../Interface/Server.h"
#include "../stringtools.h"
#include "../urbackupcommon/os_functions.h"
#include "database.h"
#include "clientdao.h"
#include "Aclapi.h"
#include "tokens.h"
#include "../common/data.h"
#include <assert.h>
#include "../Interface/Types.h"

#pragma comment(lib, "netapi32.lib")

namespace tokens
{

	const DWORD max_dc_user_groups = 100;
	const DWORD max_dc_users = 100;
	const DWORD max_dc_groups = 100;

struct Token
{
	int64 id;
	bool is_user;
};

struct TokenCacheInt
{
	std::map<std::vector<char>, Token> tokens;
};


TokenCache::TokenCache( const TokenCache& other )
{
	assert(false);
}

TokenCache::TokenCache()
{

}


TokenCache::~TokenCache()
{

}


void TokenCache::operator=(const TokenCache& other )
{
	assert(false);
}

TokenCacheInt* TokenCache::get()
{
	return token_cache.get();
}

void TokenCache::reset(TokenCacheInt* cache)
{
	token_cache.reset(cache);
}

std::wstring get_dc_name()
{
	LPBYTE dc_name;
	if (NetGetDCName(NULL, NULL, &dc_name) != NERR_Success)
	{
		return std::wstring();
	}
	
	std::wstring ret(reinterpret_cast<wchar_t*>(dc_name));
	NetApiBufferFree(dc_name);

	return ret;
}

std::string get_domain_account(const wchar_t* hostname, const wchar_t* accountname)
{
	std::vector<char> sid_buffer;
	sid_buffer.resize(sizeof(SID));
	DWORD account_sid_size = sizeof(SID);
	SID_NAME_USE sid_name_use;
	std::wstring referenced_domain;
	referenced_domain.resize(1);
	DWORD referenced_domain_size = 1;

	BOOL b = LookupAccountNameW(hostname,
		accountname,
		&sid_buffer[0], &account_sid_size, &referenced_domain[0],
		&referenced_domain_size, &sid_name_use);

	if (!b && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
	{
		referenced_domain.resize(referenced_domain_size);
		sid_buffer.resize(account_sid_size);
		b = LookupAccountNameW(hostname,
			accountname,
			&sid_buffer[0], &account_sid_size, &referenced_domain[0],
			&referenced_domain_size, &sid_name_use);
	}

	if (referenced_domain.size() != referenced_domain_size)
	{
		referenced_domain.resize(referenced_domain_size);
	}

	std::string pub_accountname = Server->ConvertFromWchar(accountname);
	if (referenced_domain_size>0)
	{
		pub_accountname = Server->ConvertFromWchar(referenced_domain) + "\\" + pub_accountname;
	}

	return pub_accountname;
}

std::vector<std::string> get_dc_users()
{
	std::wstring dc_name = get_dc_name();
	if (dc_name.empty())
	{
		return std::vector<std::string>();
	}

	LPUSER_INFO_0 buf;
	DWORD prefmaxlen = max_dc_users;
	DWORD entriesread = 0;
	DWORD totalentries = 0;
	DWORD resume_handle = 0;
	NET_API_STATUS status;
	std::vector<std::string> ret;

	do
	{
		status = NetUserEnum(dc_name.c_str(), 0, 0,
			(LPBYTE*)&buf, prefmaxlen, &entriesread,
			&totalentries, &resume_handle);

		if (status == NERR_Success || status == ERROR_MORE_DATA)
		{
			for (DWORD i = 0; i<entriesread; ++i)
			{
				LPUSER_INFO_0 user_info = (buf + i);
				ret.push_back(get_domain_account(dc_name.c_str(), user_info->usri0_name));
			}
		}
		else
		{
			Server->Log("Error while enumerating DC users: " + convert((int)status), LL_ERROR);
		}

		if (status == ERROR_MORE_DATA
			&& ret.size() >= max_dc_users)
		{
			Server->Log("Limiting number of DC users to " + convert(ret.size()), LL_WARNING);
			status = NERR_Success;
		}

		if (buf != NULL)
		{
			NetApiBufferFree(buf);
		}
	} while (status == ERROR_MORE_DATA);

	return ret;
}

std::vector<std::string> get_users()
{
	LPUSER_INFO_0 buf;
	DWORD prefmaxlen = MAX_PREFERRED_LENGTH;
	DWORD entriesread = 0;
	DWORD totalentries = 0;
	DWORD resume_handle = 0;
	NET_API_STATUS status;
	std::vector<std::string> ret;

	do 
	{
		 status = NetUserEnum(NULL, 0, 0,
			(LPBYTE*)&buf, prefmaxlen, &entriesread,
			&totalentries, &resume_handle);
		 
		 if (status == NERR_Success || status == ERROR_MORE_DATA)
		 {
			 for(DWORD i=0;i<entriesread;++i)
			 {
				 LPUSER_INFO_0 user_info = (buf+i);
				 ret.push_back(Server->ConvertFromWchar(user_info->usri0_name));
			 }
		 }
		 else
		 {
			 Server->Log("Error while enumerating users: "+ convert((int)status), LL_ERROR);
		 }

		 if(buf!=NULL)
		 {
			 NetApiBufferFree(buf);
		 }
	}
	while (status == ERROR_MORE_DATA);

	std::vector<std::string> dc_users = get_dc_users();
	ret.insert(ret.end(), dc_users.begin(), dc_users.end());

	return ret;
}


std::vector<std::string> get_dc_user_groups(std::string username)
{
	std::wstring dc_name = get_dc_name();
	if (dc_name.empty())
	{
		return std::vector<std::string>();
	}

	if (username.find("\\") != std::string::npos)
	{
		username = getafter("\\", username);
	}

	LPGROUP_USERS_INFO_0 buf;
	DWORD prefmaxlen = max_dc_user_groups;
	DWORD entriesread = 0;
	DWORD totalentries = 0;
	DWORD resume_handle = 0;
	NET_API_STATUS status;
	std::vector<std::string> ret;

	status = NetUserGetGroups(dc_name.c_str(), Server->ConvertToWchar(username).c_str(), 0,
		(LPBYTE*)&buf, prefmaxlen, &entriesread,
		&totalentries);

	if (status == NERR_Success)
	{
		for (DWORD i = 0; i<entriesread; ++i)
		{
			LPGROUP_USERS_INFO_0 user_info = (buf + i);
			ret.push_back(get_domain_account(dc_name.c_str(), user_info->grui0_name));
		}
	}
	else
	{
		Server->Log("Error while enumerating DC user groups: " + convert((int)status), LL_ERROR);
	}

	if (buf != NULL)
	{
		NetApiBufferFree(buf);
	}

	return ret;
}

std::vector<std::string> get_user_groups(const std::string& username)
{
	LPLOCALGROUP_USERS_INFO_0 buf;
	DWORD prefmaxlen = MAX_PREFERRED_LENGTH;
	DWORD entriesread = 0;
	DWORD totalentries = 0;
	DWORD resume_handle = 0;
	NET_API_STATUS status;
	std::vector<std::string> ret;

	status = NetUserGetLocalGroups(NULL, Server->ConvertToWchar(username).c_str(), 0, LG_INCLUDE_INDIRECT,
		(LPBYTE*)&buf, prefmaxlen, &entriesread,
		&totalentries);

	if (status == NERR_Success)
	{
		for(DWORD i=0;i<entriesread;++i)
		{
			LPLOCALGROUP_USERS_INFO_0 user_info = (buf+i);
			ret.push_back(Server->ConvertFromWchar(user_info->lgrui0_name));
		}
	}
	else
	{
		Server->Log("Error while enumerating user \""+username+"\" groups: "+ convert((int)status), LL_ERROR);
	}

	if(buf!=NULL)
	{
		NetApiBufferFree(buf);
	}

	if (username.find("\\") != std::string::npos)
	{
		std::vector<std::string> dc_user_groups = get_dc_user_groups(username);
		ret.insert(ret.end(), dc_user_groups.begin(), dc_user_groups.end());
	}

	return ret;
}

std::vector<std::string> get_dc_groups()
{
	std::vector<std::string> ret;
	std::wstring dc_name = get_dc_name();
	if (!dc_name.empty())
	{
		std::string dc_prefix = Server->ConvertFromWchar(dc_name);
		if (next(dc_prefix, 0, "\\\\"))
		{
			dc_prefix = dc_prefix.substr(2);
		}

		DWORD prefmaxlen = max_dc_groups;
		DWORD entriesread = 0;
		DWORD totalentries = 0;
		NET_API_STATUS status;
		DWORD_PTR resume_handle = 0;
		LPGROUP_INFO_0 buf2;
		do
		{
			status = NetGroupEnum(dc_name.c_str(), 0,
				(LPBYTE*)&buf2, prefmaxlen, &entriesread,
				&totalentries, &resume_handle);

			if (status == NERR_Success || status == ERROR_MORE_DATA)
			{
				for (DWORD i = 0; i < entriesread; ++i)
				{
					LPGROUP_INFO_0 group_info = (buf2 + i);
					ret.push_back(get_domain_account(dc_name.c_str(), group_info->grpi0_name));
				}
			}
			else
			{
				Server->Log("Error while enumerating groups: " + convert((int)status), LL_ERROR);
			}

			if (status == ERROR_MORE_DATA
				&& ret.size() >= max_dc_groups)
			{
				Server->Log("Limiting number of DC groups to "+convert(ret.size()), LL_WARNING);
				status = NERR_Success;
			}

			if (buf2 != NULL)
			{
				NetApiBufferFree(buf2);
			}
		} while (status == ERROR_MORE_DATA);
	}

	return ret;
}

std::vector<std::string> get_groups()
{
	LPLOCALGROUP_INFO_0 buf;
	DWORD prefmaxlen = MAX_PREFERRED_LENGTH;
	DWORD entriesread = 0;
	DWORD totalentries = 0;
	NET_API_STATUS status;
	std::vector<std::string> ret;
	DWORD_PTR resume_handle = 0;
	do
	{
		status = NetLocalGroupEnum(NULL, 0,
			(LPBYTE*)&buf, prefmaxlen, &entriesread,
			&totalentries, &resume_handle);

		if (status == NERR_Success || status == ERROR_MORE_DATA)
		{
			for (DWORD i = 0; i<entriesread; ++i)
			{
				LPLOCALGROUP_INFO_0 group_info = (buf + i);
				ret.push_back(Server->ConvertFromWchar(group_info->lgrpi0_name));
			}
		}
		else
		{
			Server->Log("Error while enumerating groups: " + convert((int)status), LL_ERROR);
		}

		if (buf != NULL)
		{
			NetApiBufferFree(buf);
		}
	} while (status == ERROR_MORE_DATA);

	std::vector<std::string> dc_groups = get_dc_groups();
	ret.insert(ret.end(), dc_groups.begin(), dc_groups.end());

	return ret;
}

bool sid_has_profile(const std::string& account_sid)
{
	HKEY res;
	if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
		Server->ConvertToWchar("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\" + account_sid).c_str(),
		0, KEY_READ, &res) == ERROR_SUCCESS)
	{
		RegCloseKey(res);
		return true;
	}
	return false;
}

bool write_token( std::string hostname, bool is_user, std::string accountname, const std::string &token_fn, ClientDAO &dao, const std::string& ext_token)
{
	std::vector<char> sid_buffer;
	sid_buffer.resize(sizeof(SID));

	DWORD account_sid_size = sizeof(SID);
	SID_NAME_USE sid_name_use;
	std::wstring referenced_domain;
	referenced_domain.resize(1);
	DWORD referenced_domain_size = 1;

	BOOL b = false;

	bool lookup_user = is_user;
	while (!b)
	{
		std::wstring local_username;
		if (accountname.find("\\") != std::string::npos
			|| !lookup_user)
		{
			local_username = Server->ConvertToWchar(accountname);
		}
		else
		{
			local_username = Server->ConvertToWchar(hostname + "\\" + accountname);
		}

		b = LookupAccountNameW(NULL,
			local_username.c_str(),
			&sid_buffer[0], &account_sid_size, &referenced_domain[0],
			&referenced_domain_size, &sid_name_use);

		if (!b && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			referenced_domain.resize(referenced_domain_size);
			sid_buffer.resize(account_sid_size);
			b = LookupAccountNameW(NULL,
				local_username.c_str(),
				&sid_buffer[0], &account_sid_size, &referenced_domain[0],
				&referenced_domain_size, &sid_name_use);
		}

		if (!b && lookup_user)
		{
			lookup_user = false;
			sid_buffer.resize(sizeof(SID));
			account_sid_size = sizeof(SID);
		}
		else
		{
			break;
		}
	}

	if(!b)
	{
		std::string errmsg;
		int64 err = os_last_error(errmsg);
		Server->Log("Error getting account SID of user "+accountname + ". Code: " + convert(err) + " - " + errmsg, LL_ERROR);
		return false;
	}

	if (referenced_domain.size() != referenced_domain_size)
	{
		referenced_domain.resize(referenced_domain_size);
	}

	SID* account_sid = reinterpret_cast<SID*>(&sid_buffer[0]);

	LPWSTR str_account_sid;
	b = ConvertSidToStringSidW(account_sid, &str_account_sid);
	if(!b)
	{
		Server->Log("Error converting SID to string SID. Errorcode: "+convert((int)GetLastError()), LL_ERROR);
		return false;
	}

	std::wstring dacl = std::wstring(L"D:(A;OICI;GA;;;") + str_account_sid + L")"
		+ L"(A;OICI;GA;;;BA)";

	std::string local_account_sid = Server->ConvertFromWchar(str_account_sid);

	LocalFree(str_account_sid);

	SECURITY_ATTRIBUTES  sa;      
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.bInheritHandle = FALSE;


	b=ConvertStringSecurityDescriptorToSecurityDescriptor(
		dacl.c_str(),
		SDDL_REVISION_1,
		&(sa.lpSecurityDescriptor),
		NULL);

	if(!b)
	{
		Server->Log("Error creating security descriptor. Errorcode: "+convert((int)GetLastError()), LL_ERROR);
		return false;
	}

	HANDLE file = CreateFileW(Server->ConvertToWchar(token_fn).c_str(),
		GENERIC_READ | GENERIC_WRITE, 0, &sa, CREATE_ALWAYS, 0, NULL);

	if(file==INVALID_HANDLE_VALUE)
	{
		Server->Log("Error opening file. Errorcode: "+convert((int)GetLastError()), LL_ERROR);
		LocalFree(sa.lpSecurityDescriptor);
		return false;
	}

	std::string token;
	if (ext_token.empty())
	{
		token = Server->secureRandomString(20);
	}
	else
	{
		token = ext_token;
	}

	int i_is_user;
	if (is_user)
	{
		if (sid_has_profile(local_account_sid))
		{
			i_is_user = ClientDAO::c_is_user;
		}
		else
		{
			i_is_user = ClientDAO::c_is_system_user;
		}		
	}
	else
	{
		i_is_user = ClientDAO::c_is_group;
	}

	dao.updateFileAccessToken(accountname_normalize(accountname), token, i_is_user);

	DWORD written=0;
	while(written<token.size())
	{
		b = WriteFile(file, token.data()+written, static_cast<DWORD>(token.size())-written, &written, NULL);
		if(!b)
		{
			Server->Log("Error writing to token file.  Errorcode: "+convert((int)GetLastError()), LL_ERROR);
			CloseHandle(file);
			LocalFree(sa.lpSecurityDescriptor);
			return true;
		}
	}

	CloseHandle(file);
	LocalFree(sa.lpSecurityDescriptor);

	return true;
}

std::string permissions_allow_all()
{
	CWData token_info;
	//allow to all
	token_info.addChar(ID_GRANT_ACCESS);
	token_info.addVarInt(0);

	return std::string(token_info.getDataPtr(), token_info.getDataSize());
}

std::string get_hostname_noslash()
{
	char hostname_c[MAX_PATH];
	hostname_c[0] = 0;
	gethostname(hostname_c, MAX_PATH);
	
	return hostname_c;
}

std::string get_hostname()
{
	std::string hostname = get_hostname_noslash();
	if(!hostname.empty())
		hostname+="\\";

	return hostname;
}

std::vector<std::string> get_local_users()
{
	std::string hostname = get_hostname_noslash();

	HKEY hKey;
	std::vector<std::string> ret;
	if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
		Server->ConvertToWchar("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList").c_str(),
		0, KEY_READ, &hKey) == ERROR_SUCCESS)
	{
		DWORD subkeys;
		if (RegQueryInfoKeyW(hKey, NULL, NULL, NULL, &subkeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
			== ERROR_SUCCESS)
		{
			for (DWORD i = 0; i < subkeys; ++i)
			{
				DWORD key_length = 512;
				wchar_t key_name[512];
				*key_name = 0;
				if (RegEnumKeyExW(hKey, i, key_name, &key_length, NULL, NULL, NULL, NULL)
					== ERROR_SUCCESS)
				{
					PSID sid;
					if (ConvertStringSidToSidW(key_name, &sid))
					{
						DWORD name_length = 0;
						DWORD domain_length = 0;
						SID_NAME_USE name_use;
						std::wstring name;
						std::wstring domain;
						BOOL b = LookupAccountSidW(NULL, sid, NULL, &name_length, NULL, &domain_length, &name_use);
						if (!b && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
						{
							name.resize(name_length);
							domain.resize(domain_length);

							b = LookupAccountSidW(NULL, sid, &name[0], &name_length, &domain[0], &domain_length, &name_use);
						}

						if (b)
						{
							if (name_use == SidTypeUser
								|| name_use == SidTypeComputer)
							{
								std::string curr_domain = Server->ConvertFromWchar(domain.c_str());
								if (!curr_domain.empty()
									&& curr_domain != hostname )
								{
									ret.push_back(curr_domain + "\\" + Server->ConvertFromWchar(name.c_str()));
								}
								else
								{
									ret.push_back(Server->ConvertFromWchar(name.c_str()));
								}
							}
						}

						LocalFree(sid);
					}
				}
			}
		}

		RegCloseKey(hKey);
	}

	return ret;
}

bool read_account_sid( std::vector<char>& sid, std::string hostname, std::string accountname, bool is_user )
{
	sid.resize(sizeof(SID));
	DWORD account_sid_size = sizeof(SID);
	SID_NAME_USE sid_name_use;
	std::wstring referenced_domain;
	referenced_domain.resize(1);
	DWORD referenced_domain_size = 1;

	BOOL b = FALSE;

	std::string local_username = accountname;
	if (local_username.find("\\") == std::string::npos
		&& is_user )
	{
		local_username = hostname + accountname;
	}

	while (!b)
	{
		b = LookupAccountNameW(NULL, Server->ConvertToWchar(local_username).c_str(),
			&sid[0], &account_sid_size, &referenced_domain[0],
			&referenced_domain_size, &sid_name_use);

		if (!b && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			referenced_domain.resize(referenced_domain_size);
			sid.resize(account_sid_size);
			b = LookupAccountNameW(NULL, Server->ConvertToWchar(local_username).c_str(),
				&sid[0], &account_sid_size, &referenced_domain[0],
				&referenced_domain_size, &sid_name_use);
		}

		if (!b && is_user)
		{
			is_user = false;
			sid.resize(sizeof(SID));
			account_sid_size = sizeof(SID);
		}
		else
		{
			break;
		}
	}

	if(!b)
	{
		sid.clear();
		return false;
	}

	return true;
}


void read_all_tokens(ClientDAO* dao, TokenCache& token_cache)
{
	TokenCacheInt* cache = new TokenCacheInt;
	token_cache.reset(cache);

	std::vector<std::string> users = get_users();

	char hostname_c[MAX_PATH];
	hostname_c[0]=0;
	gethostname(hostname_c, MAX_PATH);

	std::string hostname = hostname_c;
	if(!hostname.empty())
		hostname+="\\";

	for(size_t i=0;i<users.size();++i)
	{
		Token new_token;

		std::vector<char> sid;
		if(!read_account_sid(sid, hostname, users[i], true))
		{
			std::string errmsg;
			int64 err = os_last_error(errmsg);
			Server->Log("Cannot get account SID for user "+users[i]+" code: "+convert(err)+" - "+errmsg, LL_ERROR);
			continue;
		}

		ClientDAO::CondInt64 token_id = dao->getFileAccessTokenId2Alts(accountname_normalize(users[i]), ClientDAO::c_is_user, ClientDAO::c_is_system_user);

		if(!token_id.exists)
		{
			Server->Log("Token id for user not found", LL_ERROR);
			continue;
		}

		new_token.id=token_id.value;
		new_token.is_user=true;

		cache->tokens[sid]=new_token;
	}

	std::vector<std::string> groups = get_groups();

	for(size_t i=0;i<groups.size();++i)
	{
		Token new_token;
		
		std::vector<char> sid;
		if(!read_account_sid(sid, hostname, groups[i], false))
		{
			std::string errmsg;
			int64 err = os_last_error(errmsg);
			Server->Log("Cannot get account SID for group "+groups[i] + " code: " + convert(err) + " - " + errmsg, LL_ERROR);
			continue;
		}

		ClientDAO::CondInt64 token_id = dao->getFileAccessTokenId(accountname_normalize(groups[i]), ClientDAO::c_is_group);

		if(!token_id.exists)
		{
			Server->Log("Token id for user not found", LL_ERROR);
			continue;
		}

		new_token.id=token_id.value;
		new_token.is_user=false;

		cache->tokens[sid]=new_token;
	}
}

void read_token_lazy_cache(TokenCache& token_cache, ClientDAO* dao, std::vector<char>& sid)
{
	PSID psid = reinterpret_cast<PSID>(sid.data());
	DWORD name_length = 0;
	DWORD domain_length = 0;
	SID_NAME_USE name_use;
	std::wstring name;
	std::wstring domain;
	BOOL b = LookupAccountSidW(NULL, psid, NULL, &name_length, NULL, &domain_length, &name_use);
	if (!b && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
	{
		name.resize(name_length);
		domain.resize(domain_length);

		b = LookupAccountSidW(NULL, psid, &name[0], &name_length, &domain[0], &domain_length, &name_use);
	}

	if (!b)
	{
		Token empty = { 0, true };
		token_cache.get()->tokens[sid] = empty;
		return;
	}

	
	std::string curr_domain = Server->ConvertFromWchar(domain.c_str());
	bool local_user = curr_domain == get_hostname_noslash();
	std::string name_full;
	if (!curr_domain.empty()
		&& !local_user)
	{
		name_full = curr_domain +"\\" + Server->ConvertFromWchar(name.c_str());
	}
	else
	{
		name_full = Server->ConvertFromWchar(name.c_str());
	}

	std::string name_norm = accountname_normalize(name_full);

	ClientDAO::CondInt64 token_id;

	bool is_user;
	if (name_use == SidTypeUser
		|| name_use == SidTypeComputer)
	{
		is_user = true;
		token_id = dao->getFileAccessTokenId2Alts(name_norm,
			ClientDAO::c_is_user, ClientDAO::c_is_system_user);
	}
	else
	{
		is_user = false;
		token_id = dao->getFileAccessTokenId(name_norm, ClientDAO::c_is_group);
	}		

	if (!token_id.exists)
	{
		std::string hostname;
		if (curr_domain.empty())
		{
			hostname = get_hostname();
		}
		else if (local_user)
		{
			hostname = curr_domain;
		}

		std::string user_fn = (is_user ? "user_" : "group_") + bytesToHex(name_norm);
		std::string token_fn = tokens_path + os_file_sep() + user_fn;
		if (!write_token(hostname, is_user, name_full, token_fn, *dao))
		{
			Token empty = { 0, true };
			token_cache.get()->tokens[sid] = empty;
			return;
		}

		if(is_user)
			token_id = dao->getFileAccessTokenId2Alts(name_norm,
				ClientDAO::c_is_user, ClientDAO::c_is_system_user);
		else
			token_id = dao->getFileAccessTokenId(name_norm, ClientDAO::c_is_group);
	}

	if (!token_id.exists)
	{
		Token empty = { 0, true };
		token_cache.get()->tokens[sid] = empty;
		return;
	}

	Token new_token = { token_id.value, is_user };
	token_cache.get()->tokens[sid] = new_token;
}

std::string get_file_tokens( const std::string& fn, ClientDAO* dao, ETokenRight right, TokenCache& token_cache)
{
	if(token_cache.get()==NULL)
	{
		token_cache.reset(new TokenCacheInt);
	}

	PACL dacl=NULL;
	PSECURITY_DESCRIPTOR sec_desc;

	DWORD rc = GetNamedSecurityInfoW(Server->ConvertToWchar(fn).c_str(), SE_FILE_OBJECT,
		DACL_SECURITY_INFORMATION|PROTECTED_DACL_SECURITY_INFORMATION|UNPROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, &dacl,
		NULL, &sec_desc);

	if(rc!=ERROR_SUCCESS)
	{
		Server->Log("Error getting DACL of file \""+fn+"\". Errorcode: "+convert((int)rc), LL_ERROR);
		return std::string();
	}

	CWData token_info;

	if(dacl==NULL || dacl->AceCount==0)
	{
		//allow to all
		token_info.addChar(ID_GRANT_ACCESS);
		token_info.addVarInt(0);
	}

	if(dacl!=NULL)
	{
		char* ptr=reinterpret_cast<char*>(dacl+1);
		for(WORD i=0;i<dacl->AceCount;++i)
		{
			ACE_HEADER* header = reinterpret_cast<ACE_HEADER*>(ptr);
			std::vector<char> sid;
			bool allow;
			bool known;
			ACCESS_MASK mask;
			switch(header->AceType)
			{
			case ACCESS_ALLOWED_ACE_TYPE:
				{
					ACCESS_ALLOWED_ACE* allowed_ace = reinterpret_cast<ACCESS_ALLOWED_ACE*>(ptr);

					size_t sid_size = header->AceSize+sizeof(DWORD)-sizeof(ACCESS_ALLOWED_ACE);
					sid.resize(sid_size);
					memcpy(&sid[0], &allowed_ace->SidStart, sid_size);

					allow=true;
					known=true;
					mask = allowed_ace->Mask;
				}
				break;
			case ACCESS_DENIED_ACE_TYPE:
				{
					ACCESS_DENIED_ACE* denied_ace = reinterpret_cast<ACCESS_DENIED_ACE*>(ptr);

					size_t sid_size = header->AceSize+sizeof(DWORD)-sizeof(ACCESS_DENIED_ACE);
					sid.resize(sid_size);
					memcpy(&sid[0], &denied_ace->SidStart, sid_size);

					allow=false;
					known=true;
					mask = denied_ace->Mask;
				}			
				break;
			default:
				known=false;
				break;
			}

			if(known)
			{		
				bool has_access = false;

				if (right == ETokenRight_Read)
				{
					has_access = mask & GENERIC_READ || mask & GENERIC_ALL
						|| mask & FILE_GENERIC_READ || mask & FILE_ALL_ACCESS;
				}
				else if (right == ETokenRight_Write)
				{
					has_access = mask & GENERIC_WRITE || mask & GENERIC_ALL
						|| mask & FILE_GENERIC_WRITE || mask & FILE_ALL_ACCESS;
				}
				else if(right==ETokenRight_Delete)
				{
					has_access = mask & DELETE || mask & GENERIC_ALL
						|| mask & FILE_ALL_ACCESS;
				}
				else if (right == ETokenRight_DeleteFromDir)
				{
					has_access = (mask & GENERIC_ALL)>0;
				}
				else
				{
					assert(false);
				}
				
				if (has_access)
				{
					std::map<std::vector<char>, Token>::iterator token_it =
						token_cache.get()->tokens.find(sid);

					if (token_it == token_cache.get()->tokens.end())
					{
						read_token_lazy_cache(token_cache, dao, sid);
						token_it = token_cache.get()->tokens.find(sid);
					}

					if (token_it->second.id != 0)
					{
						if (allow)
						{
							token_info.addChar(ID_GRANT_ACCESS);
							token_info.addVarInt(token_it->second.id);
						}
						else
						{
							token_info.addChar(ID_DENY_ACCESS);
							token_info.addVarInt(token_it->second.id);
						}
					}
					else if (!allow)
					{
						Server->Log("Error looking up SID of ACE entry of file \"" + fn + "\"", LL_ERROR);
						LocalFree(sec_desc);
						return std::string();
					}
				}
			}

			ptr+=header->AceSize;
		}
	}

	LocalFree(sec_desc);

	return std::string(token_info.getDataPtr(), token_info.getDataSize());
}

void free_tokencache( TokenCacheInt* cache )
{
	delete cache;
}

std::string accountname_normalize(const std::string& accountname)
{
	return strlower(accountname);
}

} //namespace tokens
