// TokDump.cpp : Implementacja CTokDump
#include "stdafx.h"
#include "Tokdumpsrv.h"
#include "TokDump.h"
#include <strstream>
#include <time.h>
#include <aclapi.h>
using namespace std;

const char* const s_pszKeithsShamelessPlug = "<p><i>TokenDump to darmowe narzędzie napisane przez <a href=\"http://www.develop.com/kbrown\">Keith Brown</a></i>";

#ifndef SE_GROUP_USE_FOR_DENY_ONLY
#define SE_GROUP_USE_FOR_DENY_ONLY      (0x00000010L)
#endif // SE_GROUP_USE_FOR_DENY_ONLY

inline bool operator==( const LUID& lhs, const LUID& rhs )
{
	return ( lhs.LowPart == rhs.LowPart ) && ( lhs.HighPart == rhs.HighPart );
}

inline void LookupErrorMsg( char* pszMsg, int cch, DWORD dwError = GetLastError() )
{
    if ( !FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM, 0, dwError, 0, pszMsg, cch, 0 ) )
        wsprintfA(pszMsg, "Nieznany błąd: %x", dwError );
}

int errMsg( ostream& os, const char* pszFcn, DWORD dwErr = GetLastError() )
{
	TCHAR szMsg[512];
	LookupErrorMsg( szMsg, sizeof szMsg / sizeof *szMsg, dwErr );

	TCHAR sz[512];
	wsprintfA( sz, "Niepowodzenie %s: %s", pszFcn, szMsg );
	os << sz << endl;

	return dwErr;
}

ostream& operator<<( ostream& os, const SID& sid )
{
	// Algorytm formatowania został zapożyczony z przykładu mssdk "textsid" autorstwa Scotta Fielda
	os << "S-" << int( sid.Revision ) << "-";
	const SID_IDENTIFIER_AUTHORITY& ia = sid.IdentifierAuthority;
	char szIdentifierAuthority[20];
	if ( !ia.Value[0] && !ia.Value[1] )
	{
		// forma dziesiętna
		wsprintfA(	szIdentifierAuthority,
					"%u", 
                    (ULONG)(ia.Value[5]      )   +
                    (ULONG)(ia.Value[4] <<  8)   +
                    (ULONG)(ia.Value[3] << 16)   +
                    (ULONG)(ia.Value[2] << 24)   );

	}
	else
	{
		// forma szesnastkowa
		wsprintfA(	szIdentifierAuthority,
					"0x%02hx%02hx%02hx%02hx%02hx%02hx", 
                    (USHORT)ia.Value[0],
                    (USHORT)ia.Value[1],
                    (USHORT)ia.Value[2],
                    (USHORT)ia.Value[3],
                    (USHORT)ia.Value[4],
                    (USHORT)ia.Value[5] );
	}

	os << szIdentifierAuthority;

	const int nSubAuthorities = *GetSidSubAuthorityCount( (void*)&sid );
	for ( int i = 0; i < nSubAuthorities; ++i )
		os << "-" << *GetSidSubAuthority( (void*)&sid, i );

	return os;
}

ostream& operator<<( ostream& os, const TOKEN_GROUPS& tg )
{
	os << "<ul>" << endl;
	for ( int i = 0; i < tg.GroupCount; ++i )
	{
		const SID_AND_ATTRIBUTES& saa = tg.Groups[i];

		os << "<li>Nazwa: ";
		TCHAR szName[128];
		DWORD cchName = sizeof szName / sizeof *szName;
		TCHAR szDomain[128];
		DWORD cchDomain = sizeof szDomain / sizeof *szDomain;
		SID_NAME_USE snu;
		if ( saa.Attributes & SE_GROUP_LOGON_ID )
		{
			os << "<i>konto logowania (bez nazwy)</i>";
		}
		else if ( LookupAccountSid(	0, saa.Sid,
								szName, &cchName,
								szDomain, &cchDomain,
								&snu ) )
		{
			if ( cchDomain )
				os << szDomain << "\\";
			os << szName << endl;
		}
		else
		{
			char sz[256];
			LookupErrorMsg( sz, sizeof sz / sizeof *sz );
			os << "<font color=red>Działanie LookupAccountSid nie powiodło się: " << sz << "</font>" << endl;
		}

		os << "<br>SID: " << (*(SID*)saa.Sid) << "<br>" << endl;
		os << "Atrybuty: ";
		if ( saa.Attributes & SE_GROUP_MANDATORY )
			os << " obowiązkowy";
		if ( saa.Attributes & SE_GROUP_ENABLED )
			 os << " włączony";
		else os << " wyłączony";
		if ( saa.Attributes & SE_GROUP_ENABLED_BY_DEFAULT )
			os << " domyślny";
		if ( saa.Attributes & SE_GROUP_OWNER )
			os << " może być właścicielem";
		if ( saa.Attributes & SE_GROUP_LOGON_ID )
			os << " logon_SID";
		os << "<br>" << endl;

		if ( 0 == strcmpi( "brak", szName ) )
		{
			int c = *GetSidSubAuthorityCount( saa.Sid );
			if ( DOMAIN_GROUP_RID_USERS == *GetSidSubAuthority( saa.Sid, c - 1 ) )
			{
				// this is the "Domain Users" group scoped under a local authority
				os << endl;
				os << "<blockquote><i>(tJest to grupa </i><b>Domain Users</b><i> zależna "
					  "od lokalnej tożsamości, przez co nigdy nie pokazuje się w grupie "
					  "edytorów ACL; nie będzie to przydatne dla użytkowników końcowych. "
					  "Ta informacja jest jednak wykorzystywana jako grupa Primary Group, "
					  "przez co znalazła się tutaj.)</i></blockquote>" << endl;
			}
		}

		os << "<p>" << endl;
	}
	os << "</ul>";
	return os;
}

ostream& operator<<( ostream& os, const TOKEN_PRIVILEGES& tp )
{
	os << "<ul>" << endl;
	for ( int i = 0; i < tp.PrivilegeCount; ++i )
	{
		const LUID_AND_ATTRIBUTES& laa = tp.Privileges[i];

		os << "<li>Nazwa: ";
		TCHAR szName[128];
		DWORD cchName = sizeof szName / sizeof *szName;
		if ( LookupPrivilegeName( 0, const_cast<LUID*>(&laa.Luid), szName, &cchName ) )
		{
			os << szName << "<br>" << endl;

			os << "Atrybuty: ";
			if ( laa.Attributes & SE_PRIVILEGE_ENABLED )
				 os << "włączony";
			else os << "wyłączony";
			
			if ( laa.Attributes & SE_PRIVILEGE_ENABLED_BY_DEFAULT )
				os << ", wyłączny domyślnie";

			if ( laa.Attributes & SE_GROUP_USE_FOR_DENY_ONLY )
				os << ", tylko odrzucany";

			os << "<br>" << endl;

			os << "Opis: ";
			TCHAR szDesc[1024];
			DWORD cchDesc = sizeof szDesc / sizeof *szDesc;
			DWORD langid = 0;
			if ( LookupPrivilegeDisplayName( 0, szName, szDesc, &cchDesc, &langid ) )
				 os << szDesc << "<br>" << endl;
			else os << "???" << "<br>" << endl;
		}
		else os << "???" << "<br>" << endl;
		os << "<p>" << endl;
	}
	os << "</ul>";
	return os;
}

ostream& operator<<( ostream& os, const EXPLICIT_ACCESS& ea )
{
	if ( ea.grfAccessMode & SET_ACCESS )
		 os << "(dozwolony ";
	else if ( ea.grfAccessMode & DENY_ACCESS )
		 os << "(odrzucony  ";
	else os << "(???   ";

	char buf[20];
	wsprintf( buf, "0x%08x", ea.grfAccessPermissions );

	os << buf << ", Dziedziczenie: ";

	wsprintf( buf, "0x%01x", ea.grfInheritance );
	os << buf << ") ";

	if ( TRUSTEE_IS_NAME == ea.Trustee.TrusteeForm )
		 os << ea.Trustee.ptstrName;
	else os << (*(SID*)ea.Trustee.ptstrName);

	return os;
}

void dumpSidWithName( ostream& os, const SID& sid )
{
	os << sid << " (";

	// zrzucenie nazwy konta
	TCHAR szName[128];
	DWORD cchName = sizeof szName / sizeof *szName;
	TCHAR szDomain[128];
	DWORD cchDomain = sizeof szDomain / sizeof *szDomain;
	SID_NAME_USE snu;
	if ( LookupAccountSid(	0, const_cast<SID*>( &sid ),
							szName, &cchName,
							szDomain, &cchDomain,
							&snu ) )
	{
		if ( cchDomain )
			os << szDomain << "\\";
		os << szName;
	}
	else os << "???";

	os << ")";
}

ostream& operator<<( ostream& os, const ACL& acl )
{
	for ( int i = 0; i < acl.AceCount; ++i )
	{
		ACE_HEADER* pAceHeader = 0;
		if ( !GetAce( const_cast<ACL*>( &acl ), i, reinterpret_cast<void**>( &pAceHeader ) ) )
			errMsg( os, "GetAce" );

		if ( ACCESS_ALLOWED_ACE_TYPE == pAceHeader->AceType )
			 os << "przyznanie ";
		else if ( ACCESS_DENIED_ACE_TYPE == pAceHeader->AceType )
			 os << "odrzucenie  ";
		else os << "???   ";

		switch ( pAceHeader->AceType )
		{
			case ACCESS_ALLOWED_ACE_TYPE:
			case ACCESS_DENIED_ACE_TYPE:
			{	
				ACCESS_ALLOWED_ACE* pAce = reinterpret_cast<ACCESS_ALLOWED_ACE*>( pAceHeader );
				SID* pSID = reinterpret_cast<SID*>(&pAce->SidStart);

				char buf[20];
				wsprintf( buf, "0x%08x (inh: %x)", pAce->Mask, pAceHeader->AceFlags );
				os << buf << " to ";
				dumpSidWithName( os, *pSID );
				os << "<br>" << endl;
				break;
			}
		}
		os << endl;
	}


	return os;
}

void dumpTokenGroups( HANDLE hToken, ostream& os )
{
	os << "<h2>Grupy</h2>" << endl;
	DWORD cbInfo = 0;
	if ( !GetTokenInformation( hToken, TokenGroups, 0, 0, &cbInfo ) &&
		 ERROR_INSUFFICIENT_BUFFER == GetLastError() )
	{
		TOKEN_GROUPS* ptg = (TOKEN_GROUPS*) malloc( cbInfo );

		if ( GetTokenInformation( hToken, TokenGroups, ptg, cbInfo, &cbInfo ) )
		{
			os << *ptg;
			os << endl;
		}

		free( ptg );
	}
}

TOKEN_PRIVILEGES* getPrivs( HANDLE hToken )
{
	DWORD cbInfo = 0;
	if ( !GetTokenInformation( hToken, TokenPrivileges, 0, 0, &cbInfo ) &&
		 ERROR_INSUFFICIENT_BUFFER == GetLastError() )
	{
		 TOKEN_PRIVILEGES* ptp = (TOKEN_PRIVILEGES*) malloc( cbInfo );

		if ( GetTokenInformation( hToken, TokenPrivileges, ptp, cbInfo, &cbInfo ) )
			 return ptp;
		else free( ptp );
	}
	return 0;
}

void dumpPrivs( HANDLE hToken, ostream& os )
{
	os << "<h2>Przywileje</h2>" << endl;

	TOKEN_PRIVILEGES* ptp = getPrivs( hToken );
	if ( ptp )
	{
		os << *ptp;
		os << endl;
		free( ptp );
	}
	else
	{
		char sz[256];
		LookupErrorMsg( sz, sizeof sz / sizeof *sz );
		os << "Działanie GetTokenInformation nie powiodło się: " << sz << endl;
	}
}

void dumpOwner( HANDLE hToken, ostream& os )
{
	os << "<h2>Domyślny właściciel (dla nowych obiektów)</h2>" << endl;
	DWORD cbInfo = 0;
	if ( !GetTokenInformation( hToken, TokenOwner, 0, 0, &cbInfo ) &&
		 ERROR_INSUFFICIENT_BUFFER == GetLastError() )
	{
		TOKEN_OWNER* pto = (TOKEN_OWNER*) malloc( cbInfo );

		if ( GetTokenInformation( hToken, TokenOwner, pto, cbInfo, &cbInfo ) )
		{
			dumpSidWithName( os, *(SID*)pto->Owner );
			os << endl;
		}
		else errMsg( os, "GetTokenInformation" );

		free( pto );
	}
}

void dumpPrimaryGroup( HANDLE hToken, ostream& os )
{
	os << "<h2>Domyślna grupa Primary (dla nowych obiektów)</h2>" << endl;
	DWORD cbInfo = 0;
	if ( !GetTokenInformation( hToken, TokenPrimaryGroup, 0, 0, &cbInfo ) &&
		 ERROR_INSUFFICIENT_BUFFER == GetLastError() )
	{
		TOKEN_PRIMARY_GROUP* ptpg = (TOKEN_PRIMARY_GROUP*) malloc( cbInfo );

		if ( GetTokenInformation( hToken, TokenPrimaryGroup, ptpg, cbInfo, &cbInfo ) )
		{
			dumpSidWithName( os, *(SID*)ptpg->PrimaryGroup );
			os << endl;
		}
		else errMsg( os, "GetTokenInformation" );

		free( ptpg );
	}
}

void dumpDefDacl( HANDLE hToken, ostream& os )
{
	os << "<h2>Domyślny DACL (dla nowych obiektów)</h2>" << endl;
	DWORD cbInfo = 0;
	if ( !GetTokenInformation( hToken, TokenDefaultDacl, 0, 0, &cbInfo ) &&
		 ERROR_INSUFFICIENT_BUFFER == GetLastError() )
	{
		TOKEN_DEFAULT_DACL* pdacl = (TOKEN_DEFAULT_DACL*) malloc( cbInfo );

        if ( GetTokenInformation( hToken, TokenDefaultDacl, pdacl, cbInfo, &cbInfo ) )
        {
            if (pdacl->DefaultDacl)
			     os << *pdacl->DefaultDacl;
            else os << "PUSTY DACL<br>" << endl;
        }
		else errMsg( os, "GetTokenInformation" );

		free( pdacl );
	}
}

void dumpTokenDacl( HANDLE hToken, ostream& os )
{
	os << "<h2>Żeton DACL</h2>" << endl;
	DWORD cbInfo = 0;
	void* psd = 0;
	ACL* pdacl = 0;
	DWORD err = GetSecurityInfo( hToken, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
								 0, 0, &pdacl, 0, &psd );
    if (err && ERROR_ACCESS_DENIED == err) {
        // Ten uchwyt nie otrzymał autoryzacji do odczytu SD;
        // należy uzyskać uchwyt, które jest do tego autoryzowany.
        HANDLE hToken2;
        if (DuplicateHandle(GetCurrentProcess(), hToken,
                            GetCurrentProcess(), &hToken2,
                            READ_CONTROL, FALSE, 0)) {
            err = GetSecurityInfo(hToken2, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,
								 0, 0, &pdacl, 0, &psd);
            CloseHandle(hToken2);
        }
    }

	if ( !err )
	{
		if ( pdacl )
			 os << *pdacl;
		else os << "PUSTY DACL" << "<br>" << endl;
		LocalFree( psd );
	}
	else errMsg( os, "GetSecurityInfo( hToken )", err );
}

typedef BOOL (WINAPI* ITR_PROC)( HANDLE );

void dumpRestrictedStatus( HANDLE htok, ostream& os )
{
	OSVERSIONINFO vi = { sizeof vi };
	if ( GetVersionEx( &vi ) && vi.dwMajorVersion >= 5 )
	{
		HMODULE h = LoadLibrary( "advapi32.dll" );
		if ( h )
		{
			ITR_PROC isTokenRestricted = reinterpret_cast<ITR_PROC>( GetProcAddress( h, "IsTokenRestricted" ) );
			if ( isTokenRestricted )
			{
				if ( isTokenRestricted( htok ) )
					os << "<h3><font color=red><i>Restricted Token</i></font></h3>" << endl;
			}
			FreeLibrary( h );
		}
	}
}

void dumpPrincipal( ostream& os, const SID& sid )
{
    os << "<h2>Principal: <font color=blue>";
    dumpSidWithName(os, sid);
    os << "</font></h2>" << endl;
}

void dumpLogonSessionID(ostream& os, HANDLE htok) {
    TOKEN_STATISTICS ts;
    DWORD cb = sizeof ts;
    if (GetTokenInformation(htok, TokenStatistics, &ts, cb, &cb)) {
        char sz[40];
        wsprintf(sz, "Identyfikator sesji logowania: <tt>0x%X-%X</tt>", ts.AuthenticationId.HighPart, ts.AuthenticationId.LowPart);
        os << sz << endl;
    }
	else errMsg( os, "GetTokenInformation" );
}

void dumpTokenImpLevel(HANDLE htok, ostream& os) {
    TOKEN_STATISTICS ts;
    DWORD cb = sizeof ts;
    if (GetTokenInformation(htok, TokenStatistics, &ts, cb, &cb)) {
        os << "<p>Typ żetonu: ";
        switch (ts.TokenType) {
            case TokenPrimary:       os << "Główny";       break;
            case TokenImpersonation: os << "Podszywanie"; break;
            default:                 os << "&lt;nieznany (" << long(ts.TokenType) << ")&gt;"; break;
        }
        os << "<br>Poziom podszywania: ";
        switch (ts.ImpersonationLevel) {
            case SecurityAnonymous:      os << "Anonimowy";      break;
            case SecurityIdentification: os << "Identyfikacja"; break;
            case SecurityImpersonation:  os << "Podszywanie";  break;
            case SecurityDelegation:     os << "Delegacja";     break;
            default:                     os << "&lt;nieznany (" << long(ts.ImpersonationLevel) << ")&gt;"; break;
        }
        os << endl;
    }
	else errMsg( os, "GetTokenInformation" );
}

void performDump( HANDLE htok, DWORD grfOptions, ostream& os )
{
    dumpLogonSessionID(os, htok);

	if ( TDO_PRINCIPAL & grfOptions )
	{
        BYTE tu[256];
		DWORD cb = sizeof tu;
		GetTokenInformation( htok, TokenUser, tu, cb, &cb );
		dumpPrincipal( os, *reinterpret_cast<SID*>(reinterpret_cast<TOKEN_USER*>(tu)->User.Sid) );
	}
	dumpRestrictedStatus( htok, os );
	if ( TDO_GROUPS & grfOptions )
		dumpTokenGroups( htok, os );
	if ( TDO_PRIVILEGES & grfOptions )
		dumpPrivs( htok, os );
	if ( TDO_DEF_OWNER & grfOptions )
		dumpOwner( htok, os );
	if ( TDO_DEF_PRIGROUP & grfOptions )
		dumpPrimaryGroup( htok, os );
	if ( TDO_DEF_DACL & grfOptions )
		dumpDefDacl( htok, os );
	if ( TDO_TOKEN_DACL & grfOptions )
		dumpTokenDacl( htok, os );
	dumpTokenImpLevel( htok, os );
}

void dumpUserObjectName(ostream& os, HANDLE h) {
    char sz[128];
    DWORD cb = sizeof sz;
    if (GetUserObjectInformationA(h, UOI_NAME, sz, cb, &cb))
         os << sz;
    else errMsg( os, "GetUserObjectInformationA" );
}

void dumpCurrentWinstaAndDesktop(ostream& os) {
    HWINSTA hws   = GetProcessWindowStation();
    HDESK   hdesk = GetThreadDesktop(GetCurrentThreadId());
    os << "WindowStation: <tt>";
    dumpUserObjectName(os, hws);
    os << "</tt><br>Pulpit: <tt>";
    dumpUserObjectName(os, hdesk);
    os << "</tt><p>" << endl;
    CloseDesktop(hdesk);
    CloseWindowStation(hws);
}

/////////////////////////////////////////////////////////////////////////////
// CTokDump
STDMETHODIMP CTokDump::TokenDump(DWORD grfOptions, BSTR *pVal)
{
	HRESULT hr = S_OK;
	try
	{
		ostrstream os;

		// Wyświetlenie daty i godziny w celach diagnostycznych
		time_t t = time( 0 );
		os << "Zrzut żetonu wykonano " << ctime( &t ) << "<p>" << endl;

        // Wyświetlenie window station i pulpitu
        dumpCurrentWinstaAndDesktop(os);

		DWORD grfAccess = TOKEN_QUERY;
		if ( TDO_TOKEN_DACL & grfOptions )
			grfAccess |= READ_CONTROL;

		// Zrzut żetonu wątku (jeśli istnieje)
		HANDLE hImpTok = 0;
		BOOL bUsedProcessTokenToOpenThreadToken = FALSE;
		if ( !(bUsedProcessTokenToOpenThreadToken = OpenThreadToken( GetCurrentThread(), grfAccess, TRUE, &hImpTok )) &&
		     !OpenThreadToken( GetCurrentThread(), grfAccess | TOKEN_IMPERSONATE, FALSE, &hImpTok ) )
			hImpTok = 0;

		if ( hImpTok )
		{
			os << "<h2>Żeton wątku:</h2><blockquote>" << endl;
		
			if ( !bUsedProcessTokenToOpenThreadToken )
				os << "<font color=red>(należy użyć żetonu wątku do otwarcia żetonu wątku)</font><br></endl>" << endl;

			performDump( hImpTok, grfOptions, os );

			os << "</blockquote><hr>" << endl << endl;
		}
		else
		{
			if ( ERROR_NO_TOKEN != GetLastError() )
				errMsg( os, "OpenThreadToken" );
			hImpTok = 0;
		}

		grfAccess = TOKEN_QUERY;
		if ( TDO_TOKEN_DACL & grfOptions )
			grfAccess |= READ_CONTROL;

		// Otwarcie żetonu procesu
		if (hImpTok) {
			if (!SetThreadToken(0, 0))
				errMsg(os, "SetThreadToken (próba usunięcia żetonu imp)");
		}

		// Zrzut żetonu procesu
		HANDLE htok = 0;
		if ( OpenProcessToken( GetCurrentProcess(), grfAccess, &htok ) )
		{
			os << "<h2>Żeton procesu:</h2><blockquote>" << endl;
			performDump( htok, grfOptions, os );
			os << "</blockquote>" << endl;
			CloseHandle( htok );
		}
		else errMsg( os, "OpenProcessToken" );

		// Przywrócenie żetonu wątku (jeśli istniał)
		if (hImpTok) {
			if (!SetThreadToken(0, hImpTok))
				errMsg(os, "SetThreadToken (próba przywrócenia żetonu imp)");
		}

        os << s_pszKeithsShamelessPlug << endl;

		const char* psz = os.str();
		const int cch = os.rdbuf()->pcount();
		*pVal = SysAllocStringLen( 0, cch );
		if ( !*pVal )
			return E_OUTOFMEMORY;
		mbstowcs( *pVal, psz, cch );
		os.freeze( false );
	}
	catch ( ... )
	{
		hr = E_FAIL;
		*pVal = 0;
	}
	return hr;
}

STDMETHODIMP CTokDump::DumpThisToken(long htok, /*[in]*/ DWORD grfOptions, /*[out, retval]*/ BSTR* pVal) {
	HRESULT hr = S_OK;
	try
	{
		ostrstream os;

		// Wyświetlenie daty i godziny w celach diagnostycznych
		time_t t = time( 0 );
		os << "Zrzut żetonu wykonano " << ctime( &t ) << "<p>" << endl;

		os << "<h2>Żeton:</h2><blockquote>" << endl;
		
		performDump( reinterpret_cast<HANDLE>(htok), grfOptions, os );

		os << "</blockquote><hr>" << endl << endl;
        os << s_pszKeithsShamelessPlug << endl;

		const char* psz = os.str();
		const int cch = os.rdbuf()->pcount();
		*pVal = SysAllocStringLen( 0, cch );
		if ( !*pVal )
			return E_OUTOFMEMORY;
		mbstowcs( *pVal, psz, cch );
		os.freeze( false );
	}
	catch ( ... )
	{
		hr = E_FAIL;
		*pVal = 0;
	}
	return hr;
}
