/*
Copyright 2004 by Stefan Kuhr
*/
#include <windows.h>
#include <tchar.h>
#include <ntsecapi.h>
#include <psapi.h>
#include "logonsessiondata.h"
#include "hlpfuncs.h"
/// local prototypes:
static DWORD SafeNo_ERROR_MORE_DATA_Error(DWORD dwLastErr);
#define GET_STRING_OR_BAIL_OUT(sz, szLSA) {sz = GetStringFromLsaUnicodeString(szLSA); if (!sz) return FALSE;}
CLogonSessionData::CLogonSessionData(void) : m_szUserName(NULL), m_szLogonDomain(NULL), m_szAuthenticationPackage(NULL),
m_ulLogonType(0L), m_ulSession(0L), m_szUserSID(NULL), m_dwFlags(CLogonSessionData::VOID_STATE)
{
m_liLogonTime.QuadPart = 0;
m_Luid.HighPart = m_Luid.LowPart = 0;
}
CLogonSessionData::~CLogonSessionData(void)
{
if (m_szUserName)
LocalFree(m_szUserName);
if (m_szLogonDomain)
LocalFree(m_szLogonDomain);
if (m_szAuthenticationPackage)
LocalFree(m_szAuthenticationPackage);
if (m_szUserSID)
LocalFree(m_szUserSID);
}
CLogonSessionData::CLogonSessionData(const CLogonSessionData &me)
{
//copy ctor
*this = me;
}
LPWSTR CopyString (LPCWSTR sz)
{
LPWSTR szResult = NULL;
if (sz && (szResult = (LPWSTR)LocalAlloc(LPTR, (wcslen(sz)+1)*sizeof(wchar_t)))!=NULL)
wcscpy(szResult, sz);
return szResult;
}
CLogonSessionData & CLogonSessionData::operator = (const CLogonSessionData &me)
{
if (this!=&me) // prevent self-assignment
{
m_szUserName = CopyString(me.m_szUserName);
m_szLogonDomain = CopyString(me.m_szLogonDomain);
m_szAuthenticationPackage = CopyString(me.m_szAuthenticationPackage);
m_szUserSID = CopyString(me.m_szUserSID);
m_ulLogonType = me.m_ulLogonType;
m_ulSession = me.m_ulSession;
m_liLogonTime = me.m_liLogonTime;
m_dwFlags = me.m_dwFlags;
m_Luid = me.m_Luid;
}
return *this;
}
class CCloseUserSid
{
PSID m_pSid;
public:
CCloseUserSid(PSID psid): m_pSid(psid){}
~CCloseUserSid(void)
{
if (m_pSid)
FreeUserSid(m_pSid);
};
};
BOOL CLogonSessionData::Initialize(HANDLE hToken, PLUID pLogonId, PWTS_PROCESS_INFO pWtsInfo)
{
PSID pUserSid = NULL;
if (!hToken || !pLogonId)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if (!ObtainUserSid(hToken, &pUserSid)|| !pUserSid)
return FALSE;
if (pWtsInfo && !EqualSid(pUserSid, pWtsInfo->pUserSid))
{
/*
Duh!?! SIDs from WTSAPI32 and from opening the process with the
PID from PSAPI do not match? THis can only mean, that a PID has been
reused for a new process with th same process name but under a
different user account. This is probably a very rare suituation, but
anyway we have to deal with this and must not continue.
We have to fail because the result from psapi's EnumProcesses
and wtsapi32's EnumerateProcesses don't match. We fake a GLE of
ERROR_CAN_NOT_COMPLETE in the hope, this is meaningful to the caller.
*/
SetLastError(ERROR_CAN_NOT_COMPLETE);
return FALSE;
}
CCloseUserSid sdc(pUserSid);
DWORD dwNameSize = 0L, dwDomainSize = 0L;
SID_NAME_USE snu;
if (!LookupAccountSid(NULL, pUserSid, NULL, &dwNameSize, NULL, &dwDomainSize, &snu)
&& ERROR_INSUFFICIENT_BUFFER==GetLastError())
{
m_szUserName = (LPWSTR)LocalAlloc(LPTR, dwNameSize*sizeof(TCHAR));
if (!m_szUserName)
return FALSE;
m_szLogonDomain = (LPWSTR)LocalAlloc(LPTR, dwDomainSize*sizeof(TCHAR));
if (!m_szLogonDomain)
return FALSE;
if (!LookupAccountSid(NULL, pUserSid, m_szUserName, &dwNameSize, m_szLogonDomain, &dwDomainSize, &snu))
return FALSE;
}
else
return FALSE;
DWORD dwSidBufSize = 0L;
if (!GetTextualSid(pUserSid, NULL, &dwSidBufSize) && GetLastError()==ERROR_INSUFFICIENT_BUFFER)
{
// okay, a valid SID:
m_szUserSID = (LPWSTR) LocalAlloc(LPTR, dwSidBufSize);
if (m_szUserSID)
GetTextualSid(pUserSid, m_szUserSID, &dwSidBufSize);
else
return FALSE;
}
else
return FALSE;
m_Luid = *pLogonId;
m_ulLogonType = Interactive;
m_ulSession = pWtsInfo?pWtsInfo->SessionId:0L;
SetFlags(INITIALIZED);
return TRUE;
}
BOOL CLogonSessionData::Initialize(const PSECURITY_LOGON_SESSION_DATA pLSData)
{
if (!pLSData)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
GET_STRING_OR_BAIL_OUT(m_szUserName, &pLSData->UserName);
GET_STRING_OR_BAIL_OUT(m_szLogonDomain, &pLSData->LogonDomain);
GET_STRING_OR_BAIL_OUT(m_szAuthenticationPackage, &pLSData->AuthenticationPackage);
DWORD dwSidBufSize = 0L;
if (!GetTextualSid(pLSData->Sid, NULL, &dwSidBufSize) && GetLastError()==ERROR_INSUFFICIENT_BUFFER)
{
// okay, a valid SID:
m_szUserSID = (LPWSTR) LocalAlloc(LPTR, dwSidBufSize);
if (m_szUserSID)
GetTextualSid(pLSData->Sid, m_szUserSID, &dwSidBufSize);
else
return FALSE;
}
m_ulLogonType = pLSData->LogonType;
m_ulSession = pLSData->Session ;
m_liLogonTime = pLSData->LogonTime;
m_Luid = pLSData->LogonId;
SetFlags(INITIALIZED);
return TRUE;
}
static DWORD GetLUIDsFromProcesses(PLUID *ppLuid)
{
DWORD dwSize, dwSize2, dwIndex ;
LPDWORD lpdwPIDs ;
DWORD dwLastError = ERROR_SUCCESS;
if (!ppLuid)
{
SetLastError(ERROR_INVALID_PARAMETER);
return 0L;
}
// Call the PSAPI function EnumProcesses to get all of the
// ProcID's currently in the system.
// NOTE: In the documentation, the third parameter of
// EnumProcesses is named cbNeeded, which implies that you
// can call the function once to find out how much space to
// allocate for a buffer and again to fill the buffer.
// This is not the case. The cbNeeded parameter returns
// the number of PIDs returned, so if your buffer size is
// zero cbNeeded returns zero.
// NOTE: The "HeapAlloc" loop here ensures that we
// actually allocate a buffer large enough for all the
// PIDs in the system.
dwSize2 = 256 * sizeof( DWORD ) ;
lpdwPIDs = NULL ;
do
{
if( lpdwPIDs )
{
HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
dwSize2 *= 2 ;
}
lpdwPIDs = (unsigned long *)HeapAlloc( GetProcessHeap(), 0, dwSize2 );
if( lpdwPIDs == NULL )
return 0L; // Last error will be that of HeapAlloc
if( !EnumProcesses( lpdwPIDs, dwSize2, &dwSize ) )
{
DWORD dw = GetLastError();
HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
SetLastError(dw);
return 0L;
}
}
while( dwSize == dwSize2 ) ;
///
/// at this point we have an array of the PIDs at the
/// time of the last EnumProcesses invocation. We will
/// allocate an array of LUIDs passed back via the out
/// param ppLuid of exactly the number of PIDs. We will
/// only fill the first n values of this array, with n
/// being the number of unique LUIDs found in these PIDs
///
// How many ProcIDs did we get?
dwSize /= sizeof( DWORD ) ;
dwSize2 = 0L; /// our return value of found luids
*ppLuid = (LUID *)LocalAlloc(LPTR, dwSize*sizeof(LUID));
if (!(*ppLuid))
{
dwLastError = GetLastError();
goto CLEANUP;
}
for( dwIndex = 0 ; dwIndex < dwSize ; dwIndex++ )
{
(*ppLuid)[dwIndex].LowPart =0L;
(*ppLuid)[dwIndex].HighPart=0;
// Open the process (if we can... security does not
// permit every process in the system).
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,FALSE, lpdwPIDs[ dwIndex ] ) ;
if( hProcess != NULL )
{
HANDLE hAccessToken;
if (OpenProcessToken( hProcess, TOKEN_QUERY, &hAccessToken))
{
TOKEN_STATISTICS ts;
DWORD dwSize;
if (GetTokenInformation(hAccessToken, TokenStatistics, &ts, sizeof ts, &dwSize))
{
DWORD dwTmp = 0L;
BOOL bFound = FALSE;
for (;dwTmp<dwSize2 && !bFound;dwTmp++)
bFound = (*ppLuid)[dwTmp].HighPart == ts.AuthenticationId.HighPart &&
(*ppLuid)[dwTmp].LowPart == ts.AuthenticationId.LowPart;
if (!bFound)
(*ppLuid)[dwSize2++] = ts.AuthenticationId;
}
CloseHandle(hAccessToken) ;
}
else
{
#ifdef _DEBUG
TCHAR sz[100]; /// duh! magic # is just okay for Debug build...
_stprintf(sz, _T("Could not open process token for PID %ld\n"), lpdwPIDs[dwIndex]);
OutputDebugString(sz);
#endif
}
CloseHandle( hProcess ) ;
}
else
{
#ifdef _DEBUG
TCHAR sz[100]; /// duh! magic # is just okay for Debug build...
_stprintf(sz, _T("Could not open process with PID %ld\n"), lpdwPIDs[dwIndex]);
OutputDebugString(sz);
#endif
}
/// we don't really care if OpenProcess or OpenProcessToken fail or succeed, because
/// there are quite a number of system processes we cannot open anyway, not even as SYSTEM.
}
CLEANUP:
if (lpdwPIDs)
HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
if (ERROR_SUCCESS !=dwLastError)
SetLastError(dwLastError);
return dwSize2;
}
#define INTERACTIVE_SID_FLAG 0x1
#define LOCAL_SID_FLAG 0x2
#define LOGON_SID_FLAG 0x4
#define IS_INTERACTIVE (LOCAL_SID_FLAG|INTERACTIVE_SID_FLAG|LOGON_SID_FLAG)
/*
pLocalSid and pInterActiveSidhave been computed by the caller because this is much more efficient and leads
to reduced complexity in this function and fewer points of failure:
*/
static BOOL InteractiveTokenExamination(HANDLE hToken, PBOOL pbInteractive, PSID pLocalSid, PSID pInterActiveSid)
{
BOOL bSuccess = FALSE; // assume function will
// fail
DWORD dwIndex;
DWORD dwLength = 0;
TOKEN_INFORMATION_CLASS tic = TokenGroups;
PTOKEN_GROUPS ptg = NULL;
DWORD dwLastError = ERROR_SUCCESS;
DWORD dwSIDFlags = 0L;
if (!pbInteractive || !hToken)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
__try
{
//
// determine the size of the buffer
//
if (!GetTokenInformation(hToken, tic, (LPVOID)ptg, 0, &dwLength))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,dwLength);
if (ptg == NULL)
{
dwLastError = GetLastError();
__leave;
}
}
else
{
dwLastError = GetLastError();
__leave;
}
}
//
// obtain the groups the access token belongs to
//
if (!GetTokenInformation(hToken,tic,(LPVOID)ptg,dwLength,&dwLength))
{
dwLastError = GetLastError();
__leave;
}
//
// iterate over the groups and collect the flags:
//
for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++)
{
if (EqualSid (ptg->Groups[dwIndex].Sid, pInterActiveSid))
dwSIDFlags |= INTERACTIVE_SID_FLAG;
if (EqualSid (ptg->Groups[dwIndex].Sid, pLocalSid))
dwSIDFlags |= LOCAL_SID_FLAG;
if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
dwSIDFlags |= LOGON_SID_FLAG;
}
//
// indicate success
//
bSuccess = TRUE;
*pbInteractive = (dwSIDFlags & IS_INTERACTIVE)==IS_INTERACTIVE;
}
__finally
{
//
// free the buffer for the token group
//
if (ptg != NULL)
HeapFree(GetProcessHeap(), 0, (LPVOID)ptg);
}
if (!bSuccess)
SetLastError(dwLastError);
return bSuccess;
}
static PWTS_PROCESS_INFO GetWtsProcessInfo(const PWTS_PROCESS_INFO pwtsPI, DWORD dwPID, DWORD dwCount, HANDLE hProcess)
{
DWORD dwIndex = 0L;
DWORD dwModCount;
HMODULE hm;
TCHAR szModname[_MAX_PATH];
///
/// Iterate over the pwtsPI array until dwPID is found
///
for (;dwIndex<dwCount;dwIndex++)
{
if (pwtsPI[dwIndex].ProcessId == dwPID)
{
if (EnumProcessModules(hProcess, &hm, sizeof(HMODULE), &dwModCount))
{
if(GetModuleBaseName(hProcess, hm, szModname, _MAX_PATH) && !_tcsicmp(szModname, pwtsPI[dwIndex].pProcessName))
return &pwtsPI[dwIndex];
}
}
}
return NULL;
}
BOOL EnumNT4StyleInteractiveSessions(CLogonSessionData *pLogonSessionData, PULONG lpulSessions)
{
DWORD dwSize, dwSize2, dwIndex ;
LPDWORD lpdwPIDs ;
DWORD dwLastError = ERROR_SUCCESS;
BOOL bResult = TRUE; // assume success
if (!lpulSessions || (*lpulSessions) && !pLogonSessionData)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
ULONG ulSessionsPassed = *lpulSessions;
*lpulSessions = 0L;
PSID pLocalSid = NULL;
PSID pInterActiveSid = NULL;
SID_IDENTIFIER_AUTHORITY sia = SECURITY_LOCAL_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY siant = SECURITY_NT_AUTHORITY;
PWTS_PROCESS_INFO pwtsPI = NULL;
DWORD dwWTSPCount = 0L;
HMODULE hWTS = NULL;
dwSize2 = 256 * sizeof( DWORD ) ;
lpdwPIDs = NULL ;
do
{
if( lpdwPIDs )
{
HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
dwSize2 *= 2 ;
}
lpdwPIDs = (unsigned long *)HeapAlloc( GetProcessHeap(), 0, dwSize2 );
if( lpdwPIDs == NULL )
return 0L; // Last error will be that of HeapAlloc
if( !EnumProcesses( lpdwPIDs, dwSize2, &dwSize ) )
{
DWORD dw = GetLastError();
HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
SetLastError(dw);
return 0L;
}
}
while( dwSize == dwSize2 ) ;
hWTS = LoadLibrary (_T("wtsapi32.dll"));
if (hWTS)
{
if (GetProcAddress(hWTS, "WTSEnumerateProcessesW"))
{
if (!WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0L, 1L, &pwtsPI, &dwWTSPCount) && GetLastError()!=ERROR_APP_WRONG_OS)
{
pwtsPI =NULL;
dwLastError = SafeNo_ERROR_MORE_DATA_Error(GetLastError());
goto CLEANUP;
}
}
}
///
/// if hWTS is NULL we simply do nothing. After all,
/// NT4 Wksta and Server don't have this DLL.
///
// well-known local SID: S-1-2-0
if (!AllocateAndInitializeSid( &sia, 1, 0, 0, 0, 0, 0, 0, 0, 0, &pLocalSid ))
{
dwLastError = SafeNo_ERROR_MORE_DATA_Error(GetLastError());
goto CLEANUP;
}
// well-known interactive SID: S-1-5-4
if (!AllocateAndInitializeSid( &siant, 1, 4, 0, 0, 0, 0, 0, 0, 0, &pInterActiveSid ))
{
dwLastError = SafeNo_ERROR_MORE_DATA_Error(GetLastError());
goto CLEANUP;
}
///
/// at this point we have an array of the PIDs at the
/// time of the last EnumProcesses invocation.
///
// How many ProcIDs did we get?
dwSize /= sizeof( DWORD ) ;
for (dwIndex = 0L;
bResult && dwIndex<dwSize;
dwIndex++)
{
///
/// enumerate each process and look at its SIDs:
///
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, lpdwPIDs[dwIndex]);
if (hProcess)
{
HANDLE hToken;
if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
{
BOOL bInteractive = FALSE;
bResult &= InteractiveTokenExamination(hToken, &bInteractive, pLocalSid, pInterActiveSid);
if (bResult)
{
if (bInteractive)
{
TOKEN_STATISTICS ts;
DWORD dwSize;
if (!GetTokenInformation(hToken, TokenStatistics, &ts, sizeof ts, &dwSize))
{
dwLastError = SafeNo_ERROR_MORE_DATA_Error(GetLastError());
bResult = FALSE;
}
else
{
///
/// Test, if we already have recorded this logon session:
///
BOOL bFound = FALSE;
for (DWORD dwLSIndex = 0L;pLogonSessionData && dwLSIndex<(*lpulSessions);dwLSIndex++)
{
LUID ld = pLogonSessionData[dwLSIndex].GetLuid();
if (ld.HighPart == ts.AuthenticationId.HighPart &&
ld.LowPart == ts.AuthenticationId.LowPart )
{
bFound = TRUE;
break;
}
}
if (!bFound)
{
(*lpulSessions)++;
if ((*lpulSessions) <= ulSessionsPassed)
{
PWTS_PROCESS_INFO pi = NULL;
if (pwtsPI)
{
pi = GetWtsProcessInfo(pwtsPI, lpdwPIDs[dwIndex], dwWTSPCount, hProcess);
if (!pi)
{
/*
Duh!?! We are running on a TS capable machine but either the PID since taking the
process snapshot with WTSEnumerateProcesses has been reused or the PID did not
exist at that time. We have to fail because the result from psapi's EnumProcesses
and wtsapi32's EnumerateProcesses don't match. We fake a GLE of
ERROR_CAN_NOT_COMPLETE in the hope, this is meaningful to the caller.
*/
dwLastError = ERROR_CAN_NOT_COMPLETE;
bResult = FALSE;
}
}
if (bResult && pLogonSessionData && !pLogonSessionData[(*lpulSessions)-1].Initialize(hToken, &ts.AuthenticationId, pi))
{
dwLastError = SafeNo_ERROR_MORE_DATA_Error(GetLastError());
bResult = FALSE; // initializazion of a CLogonSessionData object failed,
// probably not 'nuff memory !?! Bail out!
}
}
else
dwLastError = ERROR_MORE_DATA; // don't set bResult to FALSE *now* in order to continue the
// enumeration so we get an accurate estimation of the # of
// required CLogonSessionData objects to be passed.
}
}
}
}
CloseHandle(hToken);
}
CloseHandle(hProcess);
}
}
if (bResult && ERROR_MORE_DATA == dwLastError)
bResult = FALSE; // indicate to the caller that not enough objects have been passed.
CLEANUP:
if (lpdwPIDs)
HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
if (pInterActiveSid)
FreeSid(pInterActiveSid);
if (pLocalSid)
FreeSid(pLocalSid);
if (pwtsPI)
WTSFreeMemory(pwtsPI);
if (hWTS)
FreeLibrary(hWTS);
if (!bResult)
SetLastError(dwLastError);
return bResult;
}
BOOL EnumLogonSessions(CLogonSessionData *pLogonSessionData, PULONG lpulSessions)
{
BOOL bRet = FALSE;
DWORD dwLastError = NO_ERROR;
PLUID sessions = NULL;
ULONG ulActualSessions = 0;
NTSTATUS ntRet = 0;
PLUID pLuid = NULL;
DWORD dwNumOfProcLUIDs = 0L;
/*
We bail out if on NT4 or even older NT. Those don't even
necessarily have secur32.dll, which must be delayloaded
if a binary using this module needs to run on those
versions.
*/
OSVERSIONINFO osvi;
memset(&osvi,0,sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
if (osvi.dwMajorVersion<5L)
{
dwLastError = ERROR_CALL_NOT_IMPLEMENTED;
goto CLEANUP;
}
if (!lpulSessions)
{
dwLastError = ERROR_INVALID_PARAMETER;
goto CLEANUP;
}
ntRet = LsaEnumerateLogonSessions(&ulActualSessions, &sessions);
if (ntRet != STATUS_SUCCESS)
{
dwLastError = SafeNo_ERROR_MORE_DATA_Error(LsaNtStatusToWinError(ntRet));
sessions = NULL;
goto CLEANUP;
}
if (ulActualSessions> *lpulSessions)
{
dwLastError = ERROR_MORE_DATA;
*lpulSessions = ulActualSessions;
goto CLEANUP;
}
dwNumOfProcLUIDs = GetLUIDsFromProcesses(&pLuid);
if (!dwNumOfProcLUIDs)
{
dwLastError = SafeNo_ERROR_MORE_DATA_Error(GetLastError());
goto CLEANUP;
}
for (ulActualSessions = 0;ulActualSessions< *lpulSessions;ulActualSessions++)
{
PSECURITY_LOGON_SESSION_DATA sessionData = NULL;
// Get the session information.
ntRet = LsaGetLogonSessionData (&sessions[ulActualSessions], &sessionData);
if (ntRet != STATUS_SUCCESS)
{
// An error occurred. Tell the world.
dwLastError = SafeNo_ERROR_MORE_DATA_Error(LsaNtStatusToWinError(ntRet));
// If session information was returned, free it.
if (sessionData)
LsaFreeReturnBuffer(sessionData);
goto CLEANUP;
}
BOOL bFoundInLUIDs = FALSE;
for (DWORD dwIndex = 0L;dwIndex<dwNumOfProcLUIDs;dwIndex++)
{
if (pLuid[dwIndex].HighPart == sessionData->LogonId.HighPart &&
pLuid[dwIndex].LowPart == sessionData->LogonId.LowPart )
{
bFoundInLUIDs = TRUE;
break;
}
}
if (!pLogonSessionData[ulActualSessions].Initialize(sessionData))
{
dwLastError = SafeNo_ERROR_MORE_DATA_Error(GetLastError());
goto CLEANUP;
}
if (!bFoundInLUIDs)
pLogonSessionData[ulActualSessions].SetFlags(CLogonSessionData::STALE);
LsaFreeReturnBuffer(sessionData);
}
// if we get here, everything is okay:
bRet = TRUE;
CLEANUP:
// Free the array of session LUIDs allocated by the LSA.
if (sessions)
LsaFreeReturnBuffer(sessions);
if (pLuid)
LocalFree (pLuid);
SetLastError(dwLastError);
return bRet;
}
/// With both EnumNT4StyleInteractiveSessions and EnumLogonSessions we want
/// to indicate to the caller a too small argument array by returning FALSE
/// and setting the last error to ERROR_MORE_DATA. Conversely, this means:
/// We mustn't return ERROR_MORE_DATA in any other case of failure. So if
/// there is any other reason for failure from one of the functions that
/// are called from within these two functions, we have to convert a possibly
/// returned GetLastError into some other, more general error. One nice,
/// innocent looking and general last error is ERROR_CAN_NOT_COMPLETE. With
/// the following function we convert ERROR_MORE_DATA into
/// ERROR_CAN_NOT_COMPLETE, if necessary:
static DWORD SafeNo_ERROR_MORE_DATA_Error(DWORD dwLastErr)
{
return dwLastErr==ERROR_MORE_DATA?ERROR_CAN_NOT_COMPLETE:dwLastErr;
}