You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2321 lines
77 KiB
2321 lines
77 KiB
/*+
|
|
*
|
|
* Microsoft Windows
|
|
* Copyright (C) Microsoft Corporation, 1997 - 1998.
|
|
*
|
|
* Name : seclogon.cxx
|
|
* Author:Jeffrey Richter (v-jeffrr)
|
|
*
|
|
* Abstract:
|
|
* This is the service DLL for Secondary Logon Service
|
|
* This service supports the CreateProcessWithLogon API implemented
|
|
* in advapi32.dll
|
|
*
|
|
* Revision History:
|
|
* PraeritG 10/8/97 To integrate this in to services.exe
|
|
*
|
|
-*/
|
|
|
|
|
|
#define STRICT
|
|
|
|
// Disable some warnings we don't care about:
|
|
#pragma warning(disable:4115) // '': named type definition in parentheses
|
|
#pragma warning(disable:4201) // nonstandard extension used : nameless struct/union
|
|
#pragma warning(disable:4204) // nonstandard extension used : non-constant aggregate initializer
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <ntlsa.h>
|
|
|
|
#include <Windows.h>
|
|
#define SECURITY_WIN32
|
|
#define SECURITY_KERBEROS
|
|
#include <security.h>
|
|
#include <secint.h>
|
|
#include <winsafer.h>
|
|
#include <shellapi.h>
|
|
#include <svcs.h>
|
|
#include <userenv.h>
|
|
#include <sddl.h>
|
|
#include <rpcdcep.h>
|
|
#include <crypt.h>
|
|
#include <lm.h>
|
|
#include <strsafe.h>
|
|
#include "seclogon.h"
|
|
#include <stdio.h>
|
|
#include "stringid.h"
|
|
#include "dbgdef.h"
|
|
|
|
|
|
//
|
|
// must move to winbase.h soon!
|
|
#define LOGON_WITH_PROFILE 0x00000001
|
|
#define LOGON_NETCREDENTIALS_ONLY 0x00000002
|
|
|
|
#define MAXIMUM_SECLOGON_PROCESSES MAXIMUM_WAIT_OBJECTS*4
|
|
|
|
struct SECL_STATE {
|
|
SERVICE_STATUS serviceStatus;
|
|
SERVICE_STATUS_HANDLE hServiceStatus;
|
|
HANDLE hLSA;
|
|
ULONG hMSVPackage;
|
|
ULONG hKerbPackage;
|
|
BOOL fRPCServerActive;
|
|
LIST_ENTRY JobListHead;
|
|
} g_state;
|
|
|
|
typedef struct _SECONDARYLOGONINFOW {
|
|
// First fields should all be quad-word types to avoid alignment errors:
|
|
LPSTARTUPINFO lpStartupInfo;
|
|
LPWSTR lpUsername;
|
|
LPWSTR lpDomain;
|
|
LPWSTR lpApplicationName;
|
|
LPWSTR lpCommandLine;
|
|
LPVOID lpEnvironment;
|
|
LPCWSTR lpCurrentDirectory;
|
|
|
|
UNICODE_STRING uszPassword; // use UNICODE_STRING for the passwd (easier to work with Rtl(De/En)cryptMemory())
|
|
|
|
// Next group of fields are double-word types:
|
|
DWORD dwProcessId;
|
|
ULONG LogonIdLowPart;
|
|
LONG LogonIdHighPart;
|
|
DWORD dwLogonFlags;
|
|
DWORD dwCreationFlags;
|
|
|
|
DWORD dwSeclogonFlags;
|
|
// Insert smaller types below:
|
|
HANDLE hToken; // client access token handle, for CreateProcessWithToken
|
|
} SECONDARYLOGONINFOW, *PSECONDARYLOGONINFOW;
|
|
|
|
|
|
typedef struct _SECONDARYLOGONRETINFO {
|
|
PROCESS_INFORMATION pi;
|
|
DWORD dwErrorCode;
|
|
} SECONDARYLOGONRETINFO, *PSECONDARYLOGONRETINFO;
|
|
|
|
typedef struct _SECONDARYLOGINWATCHINFO {
|
|
HANDLE hProcess;
|
|
HANDLE hToken;
|
|
HANDLE hProfile;
|
|
LUID LogonId;
|
|
DWORD dwClientSessionId;
|
|
PSECONDARYLOGONINFOW psli;
|
|
} SECONDARYLOGONWATCHINFO, *PSECONDARYLOGONWATCHINFO;
|
|
|
|
//
|
|
// The SecondaryLogonJob structure contains all of the information needed to cleanup when
|
|
//
|
|
// a) a secondary logon job group terminates (non-TS case)
|
|
// OR b) a secondary logon process terminates (TS case)
|
|
//
|
|
typedef struct _SecondaryLogonJob {
|
|
HANDLE hJob; // The Job to watch for (case 'a')
|
|
HANDLE hProcess; // The Process to watch for (case 'b')
|
|
HANDLE hRegisteredProcessTerminated; // Used to deregister wait on hProcess (case 'b')
|
|
HANDLE hToken; // Used to unload profile on cleanup
|
|
HANDLE hProfile; // Used to unload profile on cleanup
|
|
LUID RootLogonId; // The root logon session which this process/job is associated with
|
|
// BUGBUG: psli no longer has to be preserved until cleanup. However, there's no reason to
|
|
// destabilze the codebase by fixing it now.
|
|
PSECONDARYLOGONINFOW psli; // More data to free when the process/job goes away
|
|
LIST_ENTRY list; // Links this struct to other active jobs
|
|
BOOL fHeapAllocated; // TRUE if this job was HeapAlloc'd (must be freed on cleanup).
|
|
} SecondaryLogonJob;
|
|
|
|
#define _JumpCondition(condition, label) \
|
|
if (condition) \
|
|
{ \
|
|
goto label; \
|
|
} \
|
|
else { }
|
|
|
|
#define _JumpConditionWithExpr(condition, label, expr) \
|
|
if (condition) \
|
|
{ \
|
|
expr; \
|
|
goto label; \
|
|
} \
|
|
else { }
|
|
|
|
#define ARRAYSIZE(array) ((sizeof(array)) / (sizeof(array[0])))
|
|
#define FIELDOFFSET(s,m) ((size_t)(ULONG_PTR)&(((s *)0)->m))
|
|
|
|
CRITICAL_SECTION csForProcessCount;
|
|
BOOL g_fIsCsInitialized = FALSE;
|
|
PSVCHOST_GLOBAL_DATA GlobalData;
|
|
HANDLE g_hIOCP = NULL;
|
|
BOOL g_fCleanupThreadActive = FALSE;
|
|
|
|
//
|
|
// function prototypes
|
|
//
|
|
void Free_SECONDARYLOGONINFOW(PSECONDARYLOGONINFOW psli);
|
|
void FreeGlobalState();
|
|
DWORD InitGlobalState();
|
|
DWORD MySetServiceStatus(DWORD dwCurrentState, DWORD dwCheckPoint, DWORD dwWaitHint, DWORD dwExitCode);
|
|
DWORD MySetServiceStopped(DWORD dwExitCode);
|
|
DWORD SeclStartRpcServer();
|
|
DWORD SeclStopRpcServer();
|
|
void WINAPI TS_SecondaryLogonCleanupProcess(PVOID pvIgnored, BOOLEAN bIgnored);
|
|
VOID SecondaryLogonCleanupJob(LPVOID pvJobIndex, BOOL *pfLastJob);
|
|
BOOL SlpLoadUserProfile(HANDLE hToken, PHANDLE hProfile);
|
|
DWORD To_SECONDARYLOGONINFOW(PSECL_SLI pSeclSli, PSECONDARYLOGONINFOW *ppsli);
|
|
DWORD To_SECL_SLRI(SECONDARYLOGONRETINFO *pslri, PSECL_SLRI pSeclSlri);
|
|
|
|
|
|
void DbgPrintf( DWORD dwSubSysId, LPCSTR pszFormat , ...)
|
|
{
|
|
#if DBG
|
|
va_list args;
|
|
CHAR pszBuffer[1024];
|
|
HRESULT hr;
|
|
|
|
va_start(args, pszFormat);
|
|
hr = StringCchVPrintfA(pszBuffer, 1024, pszFormat, args);
|
|
if (FAILED(hr))
|
|
return;
|
|
va_end(args);
|
|
OutputDebugStringA(pszBuffer);
|
|
#else
|
|
UNREFERENCED_PARAMETER(pszFormat);
|
|
#endif // #if DBG
|
|
UNREFERENCED_PARAMETER(dwSubSysId);
|
|
}
|
|
|
|
BOOL
|
|
IsSystemProcess(
|
|
VOID
|
|
)
|
|
{
|
|
PTOKEN_USER User;
|
|
HANDLE Token;
|
|
DWORD RetLen;
|
|
PSID SystemSid = NULL;
|
|
SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY;
|
|
BYTE Buffer[100];
|
|
|
|
if(AllocateAndInitializeSid(&SidAuthority,1,SECURITY_LOCAL_SYSTEM_RID,
|
|
0,0,0,0,0,0,0,&SystemSid))
|
|
{
|
|
if(OpenThreadToken(GetCurrentThread(), MAXIMUM_ALLOWED, FALSE, &Token))
|
|
{
|
|
if(GetTokenInformation(Token, TokenUser, Buffer, 100, &RetLen))
|
|
{
|
|
User = (PTOKEN_USER)Buffer;
|
|
|
|
CloseHandle(Token);
|
|
|
|
if(EqualSid(User->User.Sid, SystemSid))
|
|
{
|
|
FreeSid(SystemSid);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
CloseHandle(Token);
|
|
}
|
|
FreeSid(SystemSid);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
DWORD SlpGetClientSessionId(HANDLE hProcess, DWORD *pdwSessionId)
|
|
{
|
|
DWORD dwResult;
|
|
DWORD dwReturnLen;
|
|
HANDLE hToken = NULL;
|
|
|
|
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
|
|
goto OpenProcessTokenError;
|
|
}
|
|
|
|
if (!GetTokenInformation (hToken, TokenSessionId, pdwSessionId, sizeof(DWORD), &dwReturnLen)) {
|
|
goto GetTokenInformationError;
|
|
}
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
if (NULL != hToken) {
|
|
CloseHandle(hToken);
|
|
}
|
|
return dwResult;
|
|
|
|
SET_DWRESULT(OpenProcessTokenError, GetLastError());
|
|
SET_DWRESULT(GetTokenInformationError, GetLastError());
|
|
}
|
|
|
|
DWORD
|
|
SlpGetClientLogonId(
|
|
HANDLE Process,
|
|
PLUID LogonId
|
|
)
|
|
|
|
{
|
|
HANDLE Token;
|
|
TOKEN_STATISTICS TokenStats;
|
|
DWORD ReturnLength;
|
|
|
|
//
|
|
// Get handle to the process token.
|
|
//
|
|
if(OpenProcessToken(Process, MAXIMUM_ALLOWED, &Token))
|
|
{
|
|
if(GetTokenInformation (
|
|
Token,
|
|
TokenStatistics,
|
|
(PVOID)&TokenStats,
|
|
sizeof( TOKEN_STATISTICS ),
|
|
&ReturnLength
|
|
))
|
|
{
|
|
|
|
*LogonId = TokenStats.AuthenticationId;
|
|
CloseHandle(Token);
|
|
return ERROR_SUCCESS;
|
|
|
|
}
|
|
CloseHandle(Token);
|
|
}
|
|
return GetLastError();
|
|
}
|
|
|
|
DWORD GetLogonSid(PSID *ppSid)
|
|
{
|
|
DWORD dwIndex;
|
|
DWORD dwResult;
|
|
DWORD dwReturnLen = 0;
|
|
DWORD dwSidLen;
|
|
HANDLE hToken = NULL;
|
|
PSID *pSid = NULL;
|
|
TOKEN_GROUPS *pTokenGroups = NULL;
|
|
|
|
// Get the current thread's token
|
|
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
|
|
goto OpenThreadTokenError;
|
|
|
|
// Compute the size of this token's groups:
|
|
GetTokenInformation(hToken, TokenGroups, NULL, 0, &dwReturnLen);
|
|
if (0 == dwReturnLen)
|
|
goto GetTokenInformationSizeError;
|
|
|
|
pTokenGroups = (TOKEN_GROUPS *)HeapAlloc(GetProcessHeap(), 0, dwReturnLen);
|
|
if (NULL == pTokenGroups)
|
|
goto MemoryError;
|
|
|
|
// Get this token's groups:
|
|
if (!GetTokenInformation(hToken, TokenGroups, pTokenGroups, dwReturnLen, &dwReturnLen))
|
|
goto GetTokenInformationError;
|
|
|
|
// Find the logon sid in this token's groups:
|
|
for (dwIndex = 0; dwIndex < pTokenGroups->GroupCount; dwIndex++) {
|
|
if (SE_GROUP_LOGON_ID & pTokenGroups->Groups[dwIndex].Attributes ) {
|
|
// we've found it. Copy it to a new sid:
|
|
dwSidLen = RtlLengthSid(pTokenGroups->Groups[dwIndex].Sid);
|
|
pSid = (PSID)HeapAlloc(GetProcessHeap(), 0, dwSidLen);
|
|
if (NULL == pSid)
|
|
goto MemoryError;
|
|
|
|
RtlCopySid(dwSidLen, pSid, pTokenGroups->Groups[dwIndex].Sid);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Did we find the logon sid?
|
|
if (NULL == pSid)
|
|
goto NoLogonSidError; // No: this token doesn't have one.
|
|
|
|
*ppSid = pSid;
|
|
pSid = NULL;
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
if (NULL != hToken)
|
|
CloseHandle(hToken);
|
|
if (NULL != pTokenGroups)
|
|
HeapFree(GetProcessHeap(), 0, pTokenGroups);
|
|
if (NULL != pSid)
|
|
HeapFree(GetProcessHeap(), 0, pSid);
|
|
return dwResult;
|
|
|
|
|
|
SET_DWRESULT(GetTokenInformationError, GetLastError());
|
|
SET_DWRESULT(GetTokenInformationSizeError, GetLastError());
|
|
SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY);
|
|
SET_DWRESULT(NoLogonSidError, ERROR_ACCESS_DENIED);
|
|
SET_DWRESULT(OpenThreadTokenError, GetLastError());
|
|
}
|
|
|
|
DWORD DecryptString(UNICODE_STRING usz)
|
|
{
|
|
DWORD dwResult;
|
|
NTSTATUS ntStatus;
|
|
|
|
// We want the MaximumLength to be greater than Length because we'll want to add a NULL
|
|
// termination character to prevent buffer overruns.
|
|
if (usz.Length >= usz.MaximumLength)
|
|
goto InvalidParameterError;
|
|
|
|
// We've got the empty string, nothing to decrypt
|
|
if (0 == usz.Length)
|
|
goto done;
|
|
|
|
// We should've been passed an encrypted string that's a multiple of the block size
|
|
if (0 != ((sizeof(WCHAR)*usz.Length) % RTL_ENCRYPT_MEMORY_SIZE))
|
|
goto InvalidParameterError;
|
|
|
|
// Attempt to decrypt the password
|
|
ntStatus = RtlDecryptMemory(usz.Buffer, sizeof(WCHAR)*usz.Length, RTL_ENCRYPT_OPTION_SAME_LOGON);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
goto RtlDecryptMemoryError;
|
|
|
|
done:
|
|
// Terminate the buffer to prevent overrun attacks (probably already done)
|
|
// This is OK because we've already checked that MaximumLength is > Length
|
|
usz.Buffer[usz.Length] = L'\0';
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
return dwResult;
|
|
|
|
SET_DWRESULT(InvalidParameterError, ERROR_INVALID_PARAMETER);
|
|
SET_DWRESULT(RtlDecryptMemoryError, RtlNtStatusToDosError(ntStatus));
|
|
}
|
|
|
|
void DestroyAuthInfo(BOOL bUseNTLM, PVOID pvAuthInfo)
|
|
{
|
|
UNICODE_STRING *pustrPasswd;
|
|
|
|
if (bUseNTLM)
|
|
{
|
|
pustrPasswd = &(((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->Password);
|
|
} else
|
|
{
|
|
pustrPasswd = &(((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->Password);
|
|
}
|
|
|
|
// this was allocated with HEAP_ZERO_MEMORY, so this check is valid
|
|
if (NULL != pustrPasswd->Buffer)
|
|
{
|
|
// will the compiler optimize this out?
|
|
SecureZeroMemory(pustrPasswd->Buffer, pustrPasswd->Length);
|
|
}
|
|
HeapFree(GetProcessHeap(), 0, pvAuthInfo);
|
|
}
|
|
|
|
DWORD MakeAuthInfo(BOOL bUseNTLM, LPWSTR pwszUserName, LPWSTR pwszDomainName, LPWSTR pwszPasswd, ULONG *pulAuthPackage, PVOID *ppvAuthInfo, ULONG *pulAuthInfoLength)
|
|
{
|
|
DWORD dwResult;
|
|
HRESULT hr;
|
|
LPBYTE pbCurrent;
|
|
LPBYTE pbEnd;
|
|
PVOID pvAuthInfo = NULL;
|
|
ULONG cbAuthInfoStrings;
|
|
ULONG ulAuthInfoLength;
|
|
ULONG ulAuthPackage;
|
|
UNICODE_STRING *pustrUserName;
|
|
UNICODE_STRING *pustrDomainName;
|
|
UNICODE_STRING *pustrPasswd;
|
|
|
|
if (bUseNTLM)
|
|
{
|
|
ulAuthPackage = g_state.hMSVPackage;
|
|
ulAuthInfoLength = sizeof(MSV1_0_INTERACTIVE_LOGON);
|
|
} else
|
|
{
|
|
ulAuthPackage = g_state.hKerbPackage;
|
|
ulAuthInfoLength = sizeof(KERB_INTERACTIVE_LOGON);
|
|
}
|
|
|
|
ulAuthInfoLength += (ULONG)(sizeof(WCHAR) * (wcslen(pwszUserName)));
|
|
ulAuthInfoLength += (ULONG)(sizeof(WCHAR) * (wcslen(pwszDomainName)));
|
|
ulAuthInfoLength += (ULONG)(sizeof(WCHAR) * (wcslen(pwszPasswd)+1));
|
|
|
|
// zero out the allocated memory (checked by DestroyAuthInfo)
|
|
pvAuthInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ulAuthInfoLength);
|
|
if (NULL == pvAuthInfo)
|
|
goto MemoryError;
|
|
|
|
// fill out the auth info structure:
|
|
if (bUseNTLM)
|
|
{
|
|
((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->MessageType = MsV1_0InteractiveLogon;
|
|
|
|
pustrUserName = &(((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->UserName);
|
|
pustrDomainName = &(((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->LogonDomainName);
|
|
pustrPasswd = &(((MSV1_0_INTERACTIVE_LOGON *)pvAuthInfo)->Password);
|
|
pbCurrent = ((LPBYTE)pvAuthInfo) + sizeof(MSV1_0_INTERACTIVE_LOGON);
|
|
cbAuthInfoStrings = ulAuthInfoLength - sizeof(MSV1_0_INTERACTIVE_LOGON);
|
|
} else
|
|
{
|
|
((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->MessageType = KerbInteractiveLogon;
|
|
|
|
pustrUserName = &(((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->UserName);
|
|
pustrDomainName = &(((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->LogonDomainName);
|
|
pustrPasswd = &(((KERB_INTERACTIVE_LOGON *)pvAuthInfo)->Password);
|
|
pbCurrent = ((LPBYTE)pvAuthInfo) + sizeof(KERB_INTERACTIVE_LOGON);
|
|
cbAuthInfoStrings = ulAuthInfoLength - sizeof(KERB_INTERACTIVE_LOGON);
|
|
}
|
|
|
|
// Initialize a pointer to the end of our buffer.
|
|
pbEnd = pbCurrent + cbAuthInfoStrings;
|
|
|
|
// Copy the auth info strings into our allocated buffer
|
|
hr = StringCbCopy((WCHAR *)pbCurrent, pbEnd-pbCurrent, pwszUserName);
|
|
if (FAILED(hr))
|
|
goto StringCchCopyError;
|
|
RtlInitUnicodeString(pustrUserName, (LPWSTR)pbCurrent);
|
|
pbCurrent += sizeof(WCHAR) * (wcslen(pwszUserName));
|
|
|
|
hr = StringCbCopy((WCHAR *)pbCurrent, pbEnd-pbCurrent, pwszDomainName);
|
|
if (FAILED(hr))
|
|
goto StringCchCopyError;
|
|
RtlInitUnicodeString(pustrDomainName, (LPWSTR)pbCurrent);
|
|
pbCurrent += sizeof(WCHAR) * (wcslen(pwszDomainName));
|
|
|
|
hr = StringCbCopy((WCHAR *)pbCurrent, pbEnd-pbCurrent, pwszPasswd);
|
|
if (FAILED(hr))
|
|
goto StringCchCopyError;
|
|
RtlInitUnicodeString(pustrPasswd, (LPWSTR)pbCurrent);
|
|
pbCurrent += sizeof(WCHAR) * (wcslen(pwszPasswd));
|
|
|
|
*pulAuthPackage = ulAuthPackage;
|
|
*ppvAuthInfo = pvAuthInfo;
|
|
pvAuthInfo = NULL;
|
|
*pulAuthInfoLength = ulAuthInfoLength;
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
if (NULL != pvAuthInfo)
|
|
DestroyAuthInfo(bUseNTLM, pvAuthInfo);
|
|
return dwResult;
|
|
|
|
SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY);
|
|
SET_DWRESULT(StringCchCopyError, (DWORD)hr);
|
|
}
|
|
|
|
DWORD LogonUserWrap(LPWSTR pwszUserName,
|
|
LPWSTR pwszDomainName,
|
|
UNICODE_STRING uszPassword,
|
|
DWORD dwLogonType,
|
|
DWORD dwLogonProvider,
|
|
PSID pLogonSid,
|
|
HANDLE *phToken,
|
|
LPWSTR *ppwszProfilePath)
|
|
{
|
|
BOOL bUseNTLM = FALSE;
|
|
DWORD cTokenGroups;
|
|
DWORD dwLengthSid;
|
|
DWORD dwResult;
|
|
HRESULT hr;
|
|
KERB_INTERACTIVE_LOGON kerbLogon;
|
|
LPWSTR pwszProfilePath = NULL;
|
|
MSV1_0_INTERACTIVE_LOGON msv10Logon;
|
|
NTSTATUS ntStatus;
|
|
PSID pLocalSid = NULL;
|
|
PUNICODE_STRING puszProfilePath = NULL;
|
|
SID_IDENTIFIER_AUTHORITY LocalSidAuthority = SECURITY_LOCAL_SID_AUTHORITY;
|
|
|
|
// Declare the parameters to LsaLogonUser:
|
|
LSA_STRING lsastr_OriginName;
|
|
SECURITY_LOGON_TYPE sltLogonType;
|
|
ULONG ulAuthPackage;
|
|
PVOID pvAuthInfo = NULL;
|
|
ULONG ulAuthInfoLength;
|
|
TOKEN_GROUPS *pTokenGroups = NULL;
|
|
TOKEN_SOURCE sourceContext;
|
|
PVOID pvProfileBuffer = NULL;
|
|
ULONG ulProfileBufferLength;
|
|
LUID LogonId;
|
|
QUOTA_LIMITS quotas;
|
|
NTSTATUS ntSubStatus;
|
|
|
|
ZeroMemory(&kerbLogon, sizeof(kerbLogon));
|
|
ZeroMemory(&msv10Logon, sizeof(msv10Logon));
|
|
|
|
// Map NULLs to the empty strings so we don't have to deal with them:
|
|
if (NULL == pwszUserName)
|
|
pwszUserName = L"";
|
|
if (NULL == pwszDomainName)
|
|
pwszDomainName = L"";
|
|
if (NULL == uszPassword.Buffer)
|
|
{
|
|
uszPassword.Buffer = L"";
|
|
uszPassword.Length = 0;
|
|
uszPassword.MaximumLength = 1;
|
|
}
|
|
|
|
// Check if we use NTLM or not:
|
|
if (NULL == pwszDomainName || L'\0' == pwszDomainName[0])
|
|
{
|
|
if (NULL == wcschr(pwszUserName, L'@'))
|
|
{
|
|
bUseNTLM = TRUE;
|
|
}
|
|
}
|
|
|
|
RtlInitString(&lsastr_OriginName, "seclogon");
|
|
sltLogonType = dwLogonType;
|
|
|
|
// The password is current encrypted. Need to decrypt it before we can use it:
|
|
dwResult = DecryptString(uszPassword);
|
|
if (ERROR_SUCCESS != dwResult)
|
|
goto DecryptStringError;
|
|
|
|
// Get the values of the auth-package dependendent LsaLogonUser parameters.
|
|
dwResult = MakeAuthInfo(bUseNTLM, pwszUserName, pwszDomainName, uszPassword.Buffer, &ulAuthPackage, &pvAuthInfo, &ulAuthInfoLength);
|
|
if (ERROR_SUCCESS != dwResult)
|
|
goto GetAuthInfoError;
|
|
|
|
// We don't need the password anymore. Zero it out:
|
|
SecureZeroMemory(uszPassword.Buffer, sizeof(WCHAR)*uszPassword.Length);
|
|
|
|
// If the caller specified a logon sid, cram it into a TOKEN_GROUPS structure
|
|
if (NULL != pLogonSid)
|
|
{
|
|
// BUG 522969: If we specify a token groups to LsaLogonUser, it's not going to add the local sid to the token groups.
|
|
// So, we have to do that ourselves:
|
|
dwLengthSid = RtlLengthRequiredSid(1);
|
|
pLocalSid = (PSID)HeapAlloc(GetProcessHeap(), 0, dwLengthSid);
|
|
if (NULL == pLocalSid)
|
|
goto MemoryError;
|
|
|
|
RtlInitializeSid(pLocalSid, &LocalSidAuthority, 1);
|
|
*(RtlSubAuthoritySid(pLocalSid, 0)) = SECURITY_LOCAL_RID;
|
|
|
|
// Initialize a TOKEN_GROUPS structure
|
|
cTokenGroups = 2; // the local sid, and the logon sid
|
|
pTokenGroups = (TOKEN_GROUPS *)HeapAlloc(GetProcessHeap(), 0, sizeof(TOKEN_GROUPS) + ((cTokenGroups - ANYSIZE_ARRAY) * sizeof(SID_AND_ATTRIBUTES)));
|
|
if (NULL == pTokenGroups)
|
|
goto MemoryError;
|
|
|
|
pTokenGroups->GroupCount = cTokenGroups;
|
|
pTokenGroups->Groups[0].Sid = pLogonSid;
|
|
pTokenGroups->Groups[0].Attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_LOGON_ID;
|
|
pTokenGroups->Groups[1].Sid = pLocalSid;
|
|
pTokenGroups->Groups[1].Attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT;
|
|
}
|
|
|
|
strncpy(sourceContext.SourceName, "seclogon ", sizeof(sourceContext.SourceName));
|
|
|
|
ntStatus = LsaLogonUser
|
|
(g_state.hLSA,
|
|
&lsastr_OriginName,
|
|
sltLogonType,
|
|
ulAuthPackage,
|
|
pvAuthInfo,
|
|
ulAuthInfoLength,
|
|
pTokenGroups,
|
|
&sourceContext,
|
|
&pvProfileBuffer,
|
|
&ulProfileBufferLength,
|
|
&LogonId,
|
|
phToken,
|
|
"as,
|
|
&ntSubStatus);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
goto LsaLogonUserError;
|
|
|
|
if (!NT_SUCCESS(ntSubStatus)) {
|
|
ntStatus = ntSubStatus;
|
|
goto LsaLogonUserError;
|
|
}
|
|
|
|
if (NULL != pvProfileBuffer) {
|
|
puszProfilePath = &(((MSV1_0_INTERACTIVE_PROFILE *)pvProfileBuffer)->ProfilePath);
|
|
if (NULL != puszProfilePath->Buffer) {
|
|
pwszProfilePath = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR)*(puszProfilePath->Length+1));
|
|
if (NULL == pwszProfilePath)
|
|
goto MemoryError;
|
|
hr = StringCchCopy(pwszProfilePath, puszProfilePath->Length+1, puszProfilePath->Buffer);
|
|
if (FAILED(hr))
|
|
goto StringCchCopyError;
|
|
}
|
|
}
|
|
|
|
*ppwszProfilePath = pwszProfilePath;
|
|
pwszProfilePath = NULL;
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
if (NULL != pvAuthInfo)
|
|
DestroyAuthInfo(bUseNTLM, pvAuthInfo);
|
|
if (NULL != pLocalSid)
|
|
HeapFree(GetProcessHeap(), 0, pLocalSid);
|
|
if (NULL != pTokenGroups)
|
|
HeapFree(GetProcessHeap(), 0, pTokenGroups);
|
|
if (NULL != pvProfileBuffer)
|
|
LsaFreeReturnBuffer(pvProfileBuffer);
|
|
if (NULL != pwszProfilePath)
|
|
HeapFree(GetProcessHeap(), 0, pwszProfilePath);
|
|
return dwResult;
|
|
|
|
SET_DWRESULT(DecryptStringError, dwResult);
|
|
SET_DWRESULT(GetAuthInfoError, dwResult);
|
|
SET_DWRESULT(LsaLogonUserError, RtlNtStatusToDosError(ntStatus));
|
|
SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY);
|
|
SET_DWRESULT(StringCchCopyError, (DWORD)hr);
|
|
|
|
UNREFERENCED_PARAMETER(dwLogonProvider);
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
WaitForNextJobTermination(PVOID pvIgnored)
|
|
{
|
|
BOOL fResult;
|
|
DWORD dwNumberOfBytes;
|
|
DWORD dwResult;
|
|
OVERLAPPED *po;
|
|
ULONG_PTR ulptrCompletionKey;
|
|
|
|
for (;;)
|
|
{
|
|
fResult = GetQueuedCompletionStatus(g_hIOCP, &dwNumberOfBytes, &ulptrCompletionKey, &po, INFINITE);
|
|
if (!fResult) {
|
|
// We've encountered an error. Shutdown our cleanup thread -- the next runas will queue another one.
|
|
EnterCriticalSection(&csForProcessCount);
|
|
g_fCleanupThreadActive = FALSE;
|
|
LeaveCriticalSection(&csForProcessCount);
|
|
|
|
goto GetQueuedCompletionStatusError;
|
|
}
|
|
|
|
// When waiting on job objects, the dwNumberOfBytes contains a message ID, indicating
|
|
// the event which just occured.
|
|
switch (dwNumberOfBytes)
|
|
{
|
|
case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
|
|
{
|
|
BOOL fLastJob;
|
|
|
|
// All of our processes have terminated. Call our cleanup function.
|
|
SecondaryLogonCleanupJob((LPVOID)ulptrCompletionKey /*job index*/, &fLastJob);
|
|
if (fLastJob)
|
|
{
|
|
// There are no more jobs -- we're done processing notification.
|
|
goto CommonReturn;
|
|
}
|
|
else
|
|
{
|
|
// More jobs left to clean up. Keep processing...
|
|
}
|
|
}
|
|
default:;
|
|
// some message we don't care about. Try again.
|
|
}
|
|
}
|
|
|
|
CommonReturn:
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
return dwResult;
|
|
|
|
SET_DWRESULT(GetQueuedCompletionStatusError, GetLastError());
|
|
|
|
UNREFERENCED_PARAMETER(pvIgnored);
|
|
}
|
|
|
|
|
|
VOID
|
|
SecondaryLogonCleanupJob(
|
|
LPVOID pvslj,
|
|
BOOL *pfLastJob
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine is a process cleanup handler when one of the secondary
|
|
logon process goes away.
|
|
|
|
Arguments:
|
|
dwProcessIndex -- the actual index to the process, the pointer is cast
|
|
back to dword. THIS IS SAFE IN SUNDOWN.
|
|
fWaitStatus -- status of the wait done by one of services.exe threads.
|
|
|
|
Return Value:
|
|
always 0.
|
|
|
|
--*/
|
|
{
|
|
SecondaryLogonJob *pslj = (SecondaryLogonJob *)pvslj;
|
|
|
|
EnterCriticalSection(&csForProcessCount);
|
|
|
|
if (NULL != pslj->hJob) {
|
|
CloseHandle(pslj->hJob);
|
|
}
|
|
|
|
if (NULL != pslj->hRegisteredProcessTerminated) {
|
|
UnregisterWaitEx(pslj->hRegisteredProcessTerminated, 0 /*don't wait*/);
|
|
}
|
|
|
|
if (NULL != pslj->hProcess) {
|
|
CloseHandle(pslj->hProcess);
|
|
}
|
|
|
|
if (NULL != pslj->hProfile) {
|
|
UnloadUserProfile(pslj->hToken, pslj->hProfile);
|
|
}
|
|
|
|
if (NULL != pslj->hToken) {
|
|
CloseHandle(pslj->hToken);
|
|
}
|
|
|
|
if (NULL != pslj->psli) {
|
|
Free_SECONDARYLOGONINFOW(pslj->psli);
|
|
}
|
|
|
|
// Unlink us from the list of jobs
|
|
RemoveEntryList(&pslj->list);
|
|
|
|
// Free the list element, if it was allocated.
|
|
if (pslj->fHeapAllocated) {
|
|
HeapFree(GetProcessHeap(), 0, pslj);
|
|
}
|
|
|
|
// If the list is empty, we don't need the cleanup thread anymore.
|
|
*pfLastJob = IsListEmpty(&g_state.JobListHead);
|
|
|
|
// If it's the last job, the cleanup thread terminates:
|
|
g_fCleanupThreadActive = !(*pfLastJob) && g_fCleanupThreadActive;
|
|
|
|
// Update the service status to reflect whether there is a runas'd process alive.
|
|
MySetServiceStatus(SERVICE_RUNNING, 0, 0, 0);
|
|
|
|
LeaveCriticalSection(&csForProcessCount);
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
WINAPI
|
|
TS_SecondaryLogonCleanupProcess(PVOID pvIndex, BOOLEAN bIgnored)
|
|
{
|
|
BOOL bIgnored2;
|
|
SecondaryLogonCleanupJob(pvIndex, &bIgnored2);
|
|
|
|
UNREFERENCED_PARAMETER(bIgnored);
|
|
}
|
|
|
|
DWORD
|
|
APIENTRY
|
|
SecondaryLogonProcessWatchdogNewProcess(
|
|
PSECONDARYLOGONWATCHINFO dwParam
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine puts the secondary logon process created on the wait queue
|
|
such that cleanup can be done after the process dies.
|
|
|
|
Arguments:
|
|
dwParam -- the pointer to the process information.
|
|
|
|
Return Value:
|
|
none.
|
|
|
|
--*/
|
|
{
|
|
BOOL fFailedAllocation = FALSE;
|
|
BOOL fEnteredCriticalSection = FALSE;
|
|
DWORD dwResult;
|
|
JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp;
|
|
LUID ProcessLogonId;
|
|
PLIST_ENTRY ple;
|
|
SecondaryLogonJob *psljNew = NULL;
|
|
SecondaryLogonJob sljDummy;
|
|
|
|
ZeroMemory(&sljDummy, sizeof(sljDummy));
|
|
|
|
__try {
|
|
if (dwParam != NULL) {
|
|
PSECONDARYLOGONWATCHINFO pslwi = (PSECONDARYLOGONWATCHINFO) dwParam;
|
|
EnterCriticalSection(&csForProcessCount);
|
|
fEnteredCriticalSection = TRUE;
|
|
|
|
psljNew = (SecondaryLogonJob *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SecondaryLogonJob));
|
|
if (NULL == psljNew) {
|
|
// Couldn't allocate a new SecondaryLogonJob object in which to hold cleanup info. Use a dummy
|
|
// stack object and perform the cleanup immediately.
|
|
fFailedAllocation = TRUE;
|
|
psljNew = &sljDummy;
|
|
}
|
|
|
|
// Insert the new SecondaryLogonJob into the list of jobs to clean up. If we fail to fully register
|
|
// for cleanup, we'll remove it from the list ourselves
|
|
InsertTailList(&g_state.JobListHead, &psljNew->list);
|
|
|
|
psljNew->hProcess = pslwi->hProcess;
|
|
psljNew->hToken = pslwi->hToken;
|
|
psljNew->hProfile = pslwi->hProfile;
|
|
psljNew->psli = pslwi->psli;
|
|
psljNew->fHeapAllocated = !fFailedAllocation;
|
|
|
|
if (fFailedAllocation) {
|
|
goto MemoryError;
|
|
}
|
|
|
|
// Initialize this job with the logon ID of the client process.
|
|
// If this is a recursive runas, we'll override this value in the following loop
|
|
psljNew->RootLogonId.LowPart = pslwi->LogonId.LowPart;
|
|
psljNew->RootLogonId.HighPart = pslwi->LogonId.HighPart;
|
|
|
|
// Search this list of active jobs and determine which logon session the new process should be associated with.
|
|
for (ple = g_state.JobListHead.Flink; ple != &g_state.JobListHead; ple = ple->Flink)
|
|
{
|
|
SecondaryLogonJob *pslj = CONTAINING_RECORD(ple, SecondaryLogonJob, list);
|
|
|
|
SlpGetClientLogonId(pslj->hProcess, &ProcessLogonId);
|
|
if(ProcessLogonId.LowPart == pslwi->LogonId.LowPart && ProcessLogonId.HighPart == pslwi->LogonId.HighPart)
|
|
{
|
|
psljNew->RootLogonId.LowPart = pslj->RootLogonId.LowPart;
|
|
psljNew->RootLogonId.HighPart = pslj->RootLogonId.HighPart;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now determine what kind of cleanup we're going to do. In the non-TS (session 0) case, we'll
|
|
// add the process to a job and cleanup when the process count goes to 0. In the TS case,
|
|
// we'll wait on the process handle and cleanup when it terminates. This is the best we can do,
|
|
// as there is no support for cross-session jobs.
|
|
if (0 == pslwi->dwClientSessionId) {
|
|
// The console login case
|
|
psljNew->hJob = CreateJobObject(NULL, NULL);
|
|
if (NULL == psljNew->hJob)
|
|
goto CreateJobObjectError;
|
|
|
|
if (!AssignProcessToJobObject(psljNew->hJob, psljNew->hProcess))
|
|
goto AssignProcessToJobObjectError;
|
|
|
|
// Register our IO completion port to wait for events from this job:
|
|
joacp.CompletionKey = (LPVOID)psljNew;
|
|
joacp.CompletionPort = g_hIOCP;
|
|
|
|
if (!SetInformationJobObject(psljNew->hJob, JobObjectAssociateCompletionPortInformation, &joacp, sizeof(joacp)))
|
|
goto SetInformationJobObjectError;
|
|
|
|
// If we don't already have a cleanup thread running, start one now:
|
|
if (!g_fCleanupThreadActive)
|
|
{
|
|
// NOTE: it's acceptable for this to fail -- we'll just cleanup on the next runas
|
|
g_fCleanupThreadActive = QueueUserWorkItem(WaitForNextJobTermination, NULL, WT_EXECUTELONGFUNCTION);
|
|
}
|
|
} else {
|
|
// The TS case
|
|
// We can't perform cleanup if we can't add the process to a job. The best we can do
|
|
// for now is to hope that this is a termsrv client (highly likely), and that csrss will
|
|
// do the cleanup for us.
|
|
//
|
|
// csrss won't unload user profiles, so we'll still have to do that. Wait until the
|
|
// process terminates and unload then. We might unload profiles when an application doesn't
|
|
// want it unloaded, but they can prevent this by holding open a key in the hive.
|
|
//
|
|
// This must be doc'd in CPWL documentation.
|
|
//
|
|
if (!RegisterWaitForSingleObject
|
|
(&psljNew->hRegisteredProcessTerminated,
|
|
psljNew->hProcess,
|
|
TS_SecondaryLogonCleanupProcess,
|
|
(PVOID)psljNew,
|
|
INFINITE,
|
|
WT_EXECUTEONLYONCE)) {
|
|
goto RegisterWaitForSingleObjectError;
|
|
}
|
|
}
|
|
|
|
// we've registered cleanup for this, we don't need to free it here anymore
|
|
psljNew = NULL;
|
|
|
|
// Update the service status to reflect that there is a runas'd process
|
|
// This prevents the service from receiving SERVICE_STOP controls
|
|
// while runas'd processes are alive.
|
|
// NOTE: this will only be correct if called *AFTER* InsertTailList. It bases its
|
|
// decision as to whether to allow a SERVICE_STOP control on whether there are any active
|
|
// processes in this list.
|
|
MySetServiceStatus(SERVICE_RUNNING, 0, 0, 0);
|
|
|
|
} else {
|
|
//
|
|
// We were just awakened in order to terminate the service (nothing to do)
|
|
//
|
|
}
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
dwResult = ERROR_INVALID_DATA; // don't give an attacker exception codes
|
|
goto ExceptionError;
|
|
}
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
if (NULL != psljNew) {
|
|
BOOL bIgnored;
|
|
|
|
// An error has occured. Terminate the process and cleanup after ourselves.
|
|
TerminateProcess(psljNew->hProcess, dwResult);
|
|
|
|
// NOTE: we want to make this call while holding the critsec, in case winlogon tries to shut us down while we're
|
|
// performing this cleanup.
|
|
SecondaryLogonCleanupJob(psljNew, &bIgnored);
|
|
}
|
|
if (fEnteredCriticalSection) {
|
|
LeaveCriticalSection(&csForProcessCount);
|
|
}
|
|
return dwResult;
|
|
|
|
|
|
SET_DWRESULT(AssignProcessToJobObjectError, GetLastError());
|
|
SET_DWRESULT(CreateJobObjectError, GetLastError());
|
|
SET_DWRESULT(ExceptionError, dwResult);
|
|
SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY);
|
|
SET_DWRESULT(RegisterWaitForSingleObjectError, GetLastError());
|
|
SET_DWRESULT(SetInformationJobObjectError, GetLastError());
|
|
}
|
|
|
|
DWORD ServiceStop(BOOL fShutdown, DWORD dwExitCode)
|
|
{
|
|
DWORD dwCheckPoint = 0;
|
|
DWORD dwResult;
|
|
|
|
// Don't want the process count to change while we're shutting down the service!
|
|
EnterCriticalSection(&csForProcessCount);
|
|
|
|
// Only stop if we have no runas'd processes, or if we're shutting down
|
|
if (fShutdown || IsListEmpty(&g_state.JobListHead)) {
|
|
dwResult = MySetServiceStatus(SERVICE_STOP_PENDING, dwCheckPoint++, 0, 0);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult && !fShutdown, MySetServiceStatusError);
|
|
|
|
// We shouldn't hold the critical section while we're shutting down the RPC server,
|
|
// because RPC threads may be trying to acquire it.
|
|
LeaveCriticalSection(&csForProcessCount);
|
|
|
|
dwResult = SeclStopRpcServer();
|
|
_JumpCondition(ERROR_SUCCESS != dwResult && !fShutdown, SeclStopRpcServerError);
|
|
|
|
dwResult = MySetServiceStatus(SERVICE_STOP_PENDING, dwCheckPoint++, 0, 0);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult && !fShutdown, MySetServiceStatusError);
|
|
|
|
if (g_fIsCsInitialized)
|
|
{
|
|
DeleteCriticalSection(&csForProcessCount);
|
|
g_fIsCsInitialized = FALSE;
|
|
}
|
|
|
|
if (NULL != g_hIOCP)
|
|
{
|
|
CloseHandle(g_hIOCP);
|
|
g_hIOCP = NULL;
|
|
}
|
|
|
|
// Unlike MySetServiceStatus, this routine doesn't access any
|
|
// global state which could have been freed:
|
|
dwResult = MySetServiceStopped(dwExitCode);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult && !fShutdown, MySetServiceStopped);
|
|
}
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
return dwResult;
|
|
|
|
SET_DWRESULT(MySetServiceStatusError, dwResult);
|
|
SET_DWRESULT(MySetServiceStopped, dwResult);
|
|
SET_DWRESULT(SeclStopRpcServerError, dwResult);
|
|
}
|
|
|
|
void
|
|
WINAPI
|
|
ServiceHandler(
|
|
DWORD fdwControl
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Service handler which wakes up the main service thread when ever
|
|
service controller needs to send a message.
|
|
|
|
Arguments:
|
|
|
|
fdwControl -- the control from the service controller.
|
|
|
|
Return Value:
|
|
none.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwNextState = g_state.serviceStatus.dwCurrentState;
|
|
DWORD dwResult;
|
|
|
|
switch (fdwControl)
|
|
{
|
|
case SERVICE_CONTROL_CONTINUE:
|
|
dwResult = MySetServiceStatus(SERVICE_CONTINUE_PENDING, 0, 0, 0);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, MySetServiceStatusError);
|
|
dwResult = SeclStartRpcServer();
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, StartRpcServerError);
|
|
dwNextState = SERVICE_RUNNING;
|
|
break;
|
|
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
break;
|
|
|
|
case SERVICE_CONTROL_PAUSE:
|
|
dwResult = MySetServiceStatus(SERVICE_PAUSE_PENDING, 0, 0, 0);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, MySetServiceStatusError);
|
|
dwResult = SeclStopRpcServer();
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, StopRpcServerError);
|
|
dwNextState = SERVICE_PAUSED;
|
|
break;
|
|
|
|
case SERVICE_CONTROL_STOP:
|
|
dwResult = ServiceStop(FALSE /*fShutdown*/, ERROR_SUCCESS);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, ServiceStopError);
|
|
return ; // All global state has been freed, just exit.
|
|
|
|
case SERVICE_CONTROL_SHUTDOWN:
|
|
dwResult = ServiceStop(TRUE /*fShutdown*/, ERROR_SUCCESS);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, ServiceStopError);
|
|
return ; // All global state has been freed, just exit.
|
|
|
|
default:
|
|
// Unhandled service control!
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
CommonReturn:
|
|
// Restore the original state on error, set the new state on success.
|
|
dwResult = MySetServiceStatus(dwNextState, 0, 0, 0);
|
|
return;
|
|
|
|
ErrorReturn:
|
|
goto CommonReturn;
|
|
|
|
SET_ERROR(MySetServiceStatusError, dwResult);
|
|
SET_ERROR(ServiceStopError, dwResult);
|
|
SET_ERROR(StartRpcServerError, dwResult);
|
|
SET_ERROR(StopRpcServerError, dwResult);
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
SlrCreateProcessWithLogon
|
|
(IN RPC_BINDING_HANDLE hRPCBinding,
|
|
IN PSECONDARYLOGONINFOW *ppsli,
|
|
OUT PSECONDARYLOGONRETINFO pslri)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
The core routine -- it handles a client request to start a secondary
|
|
logon process.
|
|
|
|
Arguments:
|
|
psli -- the input structure with client request information
|
|
pslri -- the output structure with response back to the client.
|
|
|
|
Return Value:
|
|
none.
|
|
|
|
--*/
|
|
{
|
|
HANDLE hCurrentThreadToken = NULL;
|
|
HANDLE hToken = NULL;
|
|
HANDLE hProfile = NULL;
|
|
HANDLE hProcessClient = NULL;
|
|
BOOL fCreatedEnvironmentBlock = FALSE;
|
|
BOOL fIsImpersonatingRpcClient = FALSE;
|
|
BOOL fIsImpersonatingClient = FALSE;
|
|
BOOL fInheritHandles = FALSE;
|
|
BOOL fOpenedSTDIN = FALSE;
|
|
BOOL fOpenedSTDOUT = FALSE;
|
|
BOOL fOpenedSTDERR = FALSE;
|
|
SECURITY_ATTRIBUTES sa;
|
|
PSECONDARYLOGONINFOW psli = *ppsli;
|
|
SECONDARYLOGONWATCHINFO slwi;
|
|
DWORD dwResult = ERROR_INVALID_PARAMETER; // Correct error code if we fail immediately
|
|
DWORD SessionId;
|
|
DWORD dwLogonProvider;
|
|
SECURITY_LOGON_TYPE LogonType;
|
|
PSID pLogonSid = NULL;
|
|
LPWSTR pwszProfilePath = NULL;
|
|
PROFILEINFO pi;
|
|
WCHAR szTemp [ UNLEN + 1 ];
|
|
LPWSTR pszUserName = NULL;
|
|
HANDLE hClientProcessToken ;
|
|
PRIVILEGE_SET ImpersonatePrivilege ;
|
|
BOOL PrivilegeTest ;
|
|
|
|
ZeroMemory(&pi, sizeof(pi));
|
|
|
|
__try {
|
|
|
|
//
|
|
// Do some security checks:
|
|
|
|
//
|
|
// 1) We should impersonate the client and then try to open
|
|
// the process so that we are assured that they didn't
|
|
// give us some fake id.
|
|
//
|
|
dwResult = RpcImpersonateClient(hRPCBinding);
|
|
_JumpCondition(RPC_S_OK != dwResult, leave_with_last_error);
|
|
fIsImpersonatingRpcClient = TRUE;
|
|
|
|
hProcessClient = OpenProcess(PROCESS_ALL_ACCESS, FALSE, psli->dwProcessId);
|
|
_JumpCondition(hProcessClient == NULL, leave_with_last_error);
|
|
|
|
#if 0
|
|
//
|
|
// 2) Check that the client is not running from a restricted account.
|
|
//
|
|
hCurrentThread = GetCurrentThread(); // Doesn't need to be freed with CloseHandle().
|
|
_JumpCondition(NULL == hCurrentThread, leave_with_last_error);
|
|
|
|
_JumpCondition(FALSE == OpenThreadToken(hCurrentThread,
|
|
TOKEN_QUERY | TOKEN_DUPLICATE,
|
|
TRUE,
|
|
&hCurrentThreadToken),
|
|
leave_with_last_error);
|
|
|
|
#endif
|
|
dwResult = RpcRevertToSelfEx(hRPCBinding);
|
|
if (RPC_S_OK != dwResult)
|
|
{
|
|
__leave;
|
|
}
|
|
fIsImpersonatingRpcClient = FALSE;
|
|
|
|
#if 0
|
|
if (TRUE == IsTokenUntrusted(hCurrentThreadToken))
|
|
{
|
|
dwResult = ERROR_ACCESS_DENIED;
|
|
__leave;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// We should get the session id from process id
|
|
// we will set this up in the token so that create process
|
|
// happens on the correct session.
|
|
//
|
|
_JumpCondition(!ProcessIdToSessionId(psli->dwProcessId, &SessionId), leave_with_last_error);
|
|
|
|
//
|
|
// Get the unique logonId.
|
|
// we will use this to cleanup any running processes
|
|
// when the logoff happens.
|
|
//
|
|
dwResult = SlpGetClientLogonId(hProcessClient, &slwi.LogonId);
|
|
if(dwResult != ERROR_SUCCESS)
|
|
{
|
|
__leave;
|
|
}
|
|
|
|
dwResult = SlpGetClientSessionId(hProcessClient, &slwi.dwClientSessionId);
|
|
if (dwResult != ERROR_SUCCESS)
|
|
{
|
|
__leave;
|
|
}
|
|
|
|
if ((psli->lpStartupInfo->dwFlags & STARTF_USESTDHANDLES) != 0)
|
|
{
|
|
_JumpCondition(!DuplicateHandle
|
|
(hProcessClient,
|
|
psli->lpStartupInfo->hStdInput,
|
|
GetCurrentProcess(),
|
|
&psli->lpStartupInfo->hStdInput,
|
|
0,
|
|
TRUE, DUPLICATE_SAME_ACCESS),
|
|
leave_with_last_error);
|
|
fOpenedSTDIN = TRUE;
|
|
|
|
_JumpCondition(!DuplicateHandle
|
|
(hProcessClient,
|
|
psli->lpStartupInfo->hStdOutput,
|
|
GetCurrentProcess(),
|
|
&psli->lpStartupInfo->hStdOutput,
|
|
0,
|
|
TRUE,
|
|
DUPLICATE_SAME_ACCESS),
|
|
leave_with_last_error);
|
|
fOpenedSTDOUT = TRUE;
|
|
|
|
_JumpCondition(!DuplicateHandle
|
|
(hProcessClient,
|
|
psli->lpStartupInfo->hStdError,
|
|
GetCurrentProcess(),
|
|
&psli->lpStartupInfo->hStdError,
|
|
0,
|
|
TRUE,
|
|
DUPLICATE_SAME_ACCESS),
|
|
leave_with_last_error);
|
|
fOpenedSTDERR = TRUE;
|
|
|
|
fInheritHandles = TRUE;
|
|
}
|
|
else
|
|
{
|
|
psli->lpStartupInfo->hStdInput = INVALID_HANDLE_VALUE;
|
|
psli->lpStartupInfo->hStdOutput = INVALID_HANDLE_VALUE;
|
|
psli->lpStartupInfo->hStdError = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if(psli->dwLogonFlags & LOGON_NETCREDENTIALS_ONLY)
|
|
{
|
|
LogonType = (SECURITY_LOGON_TYPE)LOGON32_LOGON_NEW_CREDENTIALS;
|
|
dwLogonProvider = LOGON32_PROVIDER_WINNT50;
|
|
}
|
|
else
|
|
{
|
|
LogonType = (SECURITY_LOGON_TYPE) LOGON32_LOGON_INTERACTIVE;
|
|
dwLogonProvider = LOGON32_PROVIDER_DEFAULT;
|
|
}
|
|
|
|
// LogonUser does not return profile information, we need to grab
|
|
// that out of band after the logon has completed.
|
|
//
|
|
dwResult = RpcImpersonateClient(hRPCBinding);
|
|
_JumpCondition(RPC_S_OK != dwResult, leave_with_last_error);
|
|
fIsImpersonatingRpcClient = TRUE;
|
|
|
|
if (0 == (SECLOGON_CALLER_SPECIFIED_DESKTOP & psli->dwSeclogonFlags))
|
|
{
|
|
// BUG 477613:
|
|
// If the caller did not specify their own desktop, it is our responsibility
|
|
// to grant the user access to the default desktop. We'll do this by
|
|
// adding the logon sid
|
|
dwResult = GetLogonSid(&pLogonSid);
|
|
if (ERROR_SUCCESS != dwResult)
|
|
__leave;
|
|
}
|
|
|
|
|
|
if( psli->hToken != NULL )
|
|
{
|
|
//
|
|
// Caller has supplied a token. Verify that the caller can impersonate
|
|
// before proceeding
|
|
//
|
|
if ( OpenProcessToken(hProcessClient, TOKEN_QUERY | TOKEN_IMPERSONATE, &hClientProcessToken ) )
|
|
{
|
|
ImpersonatePrivilege.PrivilegeCount = 1;
|
|
ImpersonatePrivilege.Privilege[ 0 ].Luid.HighPart = 0;
|
|
ImpersonatePrivilege.Privilege[ 0 ].Luid.LowPart = SE_IMPERSONATE_PRIVILEGE ;
|
|
|
|
if ( !PrivilegeCheck( hClientProcessToken,&ImpersonatePrivilege,&PrivilegeTest ) )
|
|
{
|
|
PrivilegeTest = FALSE ;
|
|
|
|
}
|
|
|
|
CloseHandle( hClientProcessToken );
|
|
|
|
if ( !PrivilegeTest )
|
|
{
|
|
dwResult = ERROR_PRIVILEGE_NOT_HELD ;
|
|
__leave ;
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
dwResult = GetLastError() ;
|
|
__leave ;
|
|
}
|
|
|
|
|
|
//
|
|
// duplicate the token from the caller, and use that.
|
|
//
|
|
|
|
if(!DuplicateHandle(
|
|
hProcessClient,
|
|
psli->hToken,
|
|
GetCurrentProcess(),
|
|
&hToken,
|
|
0,
|
|
FALSE,
|
|
DUPLICATE_SAME_ACCESS
|
|
))
|
|
{
|
|
dwResult = GetLastError();
|
|
__leave;
|
|
}
|
|
|
|
dwResult = RpcRevertToSelfEx(hRPCBinding);
|
|
if (RPC_S_OK != dwResult)
|
|
{
|
|
__leave;
|
|
}
|
|
|
|
fIsImpersonatingRpcClient = FALSE;
|
|
|
|
|
|
if(psli->dwLogonFlags & LOGON_WITH_PROFILE)
|
|
{
|
|
DWORD cchUserName = sizeof(szTemp) / sizeof(WCHAR);
|
|
|
|
pszUserName = szTemp;
|
|
|
|
//
|
|
// impersonate the token to get the username, for the profile path.
|
|
//
|
|
|
|
if(!ImpersonateLoggedOnUser( hToken ))
|
|
{
|
|
dwResult = GetLastError();
|
|
__leave;
|
|
}
|
|
|
|
|
|
if(!GetUserNameW(szTemp, &cchUserName))
|
|
{
|
|
dwResult = GetLastError();
|
|
}
|
|
|
|
RevertToSelf();
|
|
|
|
|
|
if( ERROR_SUCCESS != dwResult )
|
|
{
|
|
__leave;
|
|
}
|
|
|
|
|
|
//
|
|
// TODO: pwszProfilePath ?
|
|
//
|
|
}
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
|
|
} else {
|
|
pszUserName = psli->lpUsername;
|
|
|
|
dwResult = LogonUserWrap(
|
|
psli->lpUsername,
|
|
psli->lpDomain,
|
|
psli->uszPassword,
|
|
LogonType,
|
|
dwLogonProvider,
|
|
pLogonSid,
|
|
&hToken,
|
|
&pwszProfilePath);
|
|
|
|
if (ERROR_SUCCESS != dwResult)
|
|
__leave;
|
|
|
|
dwResult = RpcRevertToSelfEx(hRPCBinding);
|
|
if (RPC_S_OK != dwResult)
|
|
{
|
|
__leave;
|
|
}
|
|
|
|
fIsImpersonatingRpcClient = FALSE;
|
|
}
|
|
|
|
|
|
if(psli->dwLogonFlags & LOGON_WITH_PROFILE)
|
|
{
|
|
|
|
// Load the user's profile:
|
|
pi.dwSize = sizeof(pi);
|
|
pi.lpUserName = pszUserName;
|
|
pi.lpProfilePath = pwszProfilePath;
|
|
if (!LoadUserProfile(hToken, &pi))
|
|
goto leave_with_last_error;
|
|
|
|
// Save the profile handle so we can unload it later
|
|
hProfile = pi.hProfile;
|
|
}
|
|
|
|
// Let us set the SessionId in the Token.
|
|
_JumpCondition(!SetTokenInformation(hToken, TokenSessionId, &SessionId, sizeof(DWORD)),
|
|
leave_with_last_error);
|
|
|
|
// we should now impersonate the user.
|
|
//
|
|
_JumpCondition(!ImpersonateLoggedOnUser(hToken), leave_with_last_error);
|
|
fIsImpersonatingClient = TRUE;
|
|
|
|
// Query Default Owner/ACL from token. Make SD with this stuff, pass for
|
|
sa.nLength = sizeof(sa);
|
|
sa.bInheritHandle = FALSE;
|
|
sa.lpSecurityDescriptor = NULL;
|
|
|
|
//
|
|
// We should set the console control handler so CtrlC is correctly
|
|
// handled by the new process.
|
|
//
|
|
|
|
// SetConsoleCtrlHandler(NULL, FALSE);
|
|
|
|
//
|
|
// if lpEnvironment is NULL, we create new one for this user
|
|
// using CreateEnvironmentBlock
|
|
//
|
|
if(NULL == (psli->lpEnvironment))
|
|
{
|
|
if(FALSE == CreateEnvironmentBlock( &(psli->lpEnvironment), hToken, FALSE ))
|
|
{
|
|
psli->lpEnvironment = NULL;
|
|
}
|
|
else
|
|
{
|
|
// Successfully created environment block.
|
|
fCreatedEnvironmentBlock = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
// Create process.
|
|
// NOTE: we want the primary thread to be suspended until we
|
|
// register the thread for cleanup. In case the caller didn't specify it,
|
|
// mask CREATE_SUSPENDED in.
|
|
_JumpCondition(!CreateProcessAsUser(hToken,
|
|
psli->lpApplicationName,
|
|
psli->lpCommandLine,
|
|
&sa,
|
|
&sa,
|
|
fInheritHandles,
|
|
psli->dwCreationFlags | (fCreatedEnvironmentBlock ? CREATE_UNICODE_ENVIRONMENT : 0) | CREATE_SUSPENDED,
|
|
psli->lpEnvironment,
|
|
psli->lpCurrentDirectory,
|
|
psli->lpStartupInfo,
|
|
&pslri->pi),
|
|
leave_with_last_error);
|
|
|
|
SetLastError(NO_ERROR);
|
|
|
|
leave_with_last_error:
|
|
dwResult = GetLastError();
|
|
__leave;
|
|
|
|
}
|
|
__finally {
|
|
pslri->dwErrorCode = dwResult;
|
|
|
|
if (fCreatedEnvironmentBlock) { DestroyEnvironmentBlock(psli->lpEnvironment); }
|
|
if (fIsImpersonatingClient) { RevertToSelf(); /* Ignore retval: nothing we can do on failure! */ }
|
|
if (fIsImpersonatingRpcClient) { RpcRevertToSelfEx(hRPCBinding); /* Ignore retval: nothing we can do on failure! */ }
|
|
if (fOpenedSTDIN) { CloseHandle(psli->lpStartupInfo->hStdInput); }
|
|
if (fOpenedSTDOUT) { CloseHandle(psli->lpStartupInfo->hStdOutput); }
|
|
if (fOpenedSTDERR) { CloseHandle(psli->lpStartupInfo->hStdError); }
|
|
if (NULL != pLogonSid) { HeapFree(GetProcessHeap(), 0, pLogonSid); }
|
|
if (NULL != pwszProfilePath) { HeapFree(GetProcessHeap(), 0, pwszProfilePath); }
|
|
|
|
if(pslri->dwErrorCode != NO_ERROR)
|
|
{
|
|
if (NULL != hProfile) { UnloadUserProfile(hToken, hProfile); }
|
|
if (NULL != hToken) { CloseHandle(hToken); }
|
|
}
|
|
else
|
|
{
|
|
// Start the watchdog process last so it won't delete psli before we're done with it.
|
|
slwi.hProcess = pslri->pi.hProcess;
|
|
slwi.hToken = hToken;
|
|
slwi.hProfile = hProfile;
|
|
// LogonId was already filled up.. right at the begining.
|
|
slwi.psli = psli;
|
|
|
|
// Register for cleanup: SecondaryLogonProcessWatchdogNewProcess MUST BE IN an EH!
|
|
// The cleanup method will cleanup all process info, and terminate the process, on failure.
|
|
dwResult = SecondaryLogonProcessWatchdogNewProcess(&slwi);
|
|
if (ERROR_SUCCESS == dwResult)
|
|
{
|
|
if (0 == (psli->dwCreationFlags & CREATE_SUSPENDED))
|
|
{
|
|
// The caller doesn't want to create the process suspended. Resume the primary thread:
|
|
ResumeThread(pslri->pi.hThread);
|
|
}
|
|
|
|
// SetConsoleCtrlHandler(NULL, TRUE);
|
|
//
|
|
// Have the watchdog watch this newly added process so that
|
|
// cleanup will occur correctly when the process terminates.
|
|
//
|
|
|
|
// Set up the windowstation and desktop for the process
|
|
|
|
DuplicateHandle(GetCurrentProcess(), pslri->pi.hProcess,
|
|
hProcessClient, &pslri->pi.hProcess, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS);
|
|
|
|
DuplicateHandle(GetCurrentProcess(), pslri->pi.hThread, hProcessClient,
|
|
&pslri->pi.hThread, 0, FALSE,
|
|
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
|
|
}
|
|
else
|
|
{
|
|
pslri->dwErrorCode = dwResult; // return the error to the caller
|
|
}
|
|
*ppsli = NULL; // cleanup method will clean this up now.
|
|
}
|
|
|
|
if (NULL != hProcessClient) { CloseHandle(hProcessClient); }
|
|
if (NULL != hCurrentThreadToken) { CloseHandle(hCurrentThreadToken); }
|
|
}
|
|
}
|
|
|
|
void
|
|
WINAPI
|
|
ServiceMain
|
|
(IN DWORD dwArgc,
|
|
IN WCHAR ** lpszArgv)
|
|
/*++
|
|
|
|
Routine Description:
|
|
The main service handler thread routine.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
none.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwResult;
|
|
|
|
__try {
|
|
InitializeCriticalSection(&csForProcessCount);
|
|
g_fIsCsInitialized = TRUE;
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
return; // We can't do anything if we can't initialize this critsec
|
|
}
|
|
|
|
g_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0,0);
|
|
_JumpCondition(NULL == g_hIOCP, CreateIoCompletionPortError);
|
|
|
|
dwResult = InitGlobalState();
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, InitGlobalStateError);
|
|
|
|
// NOTE: hSS does not have to be closed.
|
|
g_state.hServiceStatus = RegisterServiceCtrlHandler(wszSvcName, ServiceHandler);
|
|
_JumpCondition(NULL == g_state.hServiceStatus, RegisterServiceCtrlHandlerError);
|
|
|
|
dwResult = SeclStartRpcServer();
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, StartRpcServerError);
|
|
|
|
// Tell the SCM we're up and running:
|
|
dwResult = MySetServiceStatus(SERVICE_RUNNING, 0, 0, ERROR_SUCCESS);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, MySetServiceStatusError);
|
|
|
|
SetLastError(ERROR_SUCCESS);
|
|
ErrorReturn:
|
|
// Shut down the service if we couldn't fully start:
|
|
if (ERROR_SUCCESS != GetLastError()) {
|
|
ServiceStop(TRUE /*fShutdown*/, GetLastError());
|
|
}
|
|
return;
|
|
|
|
SET_ERROR(InitGlobalStateError, dwResult)
|
|
SET_ERROR(MySetServiceStatusError, dwResult);
|
|
SET_ERROR(RegisterServiceCtrlHandlerError, dwResult);
|
|
SET_ERROR(StartRpcServerError, dwResult);
|
|
TRACE_ERROR(CreateIoCompletionPortError);
|
|
|
|
UNREFERENCED_PARAMETER(dwArgc);
|
|
UNREFERENCED_PARAMETER(lpszArgv);
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
InstallService()
|
|
/*++
|
|
|
|
Routine Description:
|
|
It installs the service with service controller, basically creating
|
|
the service object.
|
|
|
|
Arguments:
|
|
none.
|
|
|
|
Return Value:
|
|
several - as returned by the service controller.
|
|
|
|
--*/
|
|
{
|
|
// TCHAR *szModulePathname;
|
|
TCHAR AppName[MAX_PATH];
|
|
LPTSTR ptszAppName = NULL;
|
|
SC_HANDLE hService;
|
|
DWORD dw;
|
|
HANDLE hMod;
|
|
|
|
//
|
|
// Open the SCM on this machine.
|
|
//
|
|
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
|
if(hSCM == NULL) {
|
|
dw = GetLastError();
|
|
return dw;
|
|
}
|
|
|
|
//
|
|
// Let us give the service a useful description
|
|
// This is not earth shattering... if it works fine, if it
|
|
// doesn't it is just too bad :-)
|
|
//
|
|
hMod = GetModuleHandle(L"seclogon.dll");
|
|
|
|
//
|
|
// we'll try to get the localized name for the service,
|
|
// if it fails, we'll just put an english string...
|
|
//
|
|
if(hMod != NULL)
|
|
{
|
|
LoadString(hMod,
|
|
SECLOGON_STRING_NAME,
|
|
AppName,
|
|
MAX_PATH
|
|
);
|
|
|
|
ptszAppName = AppName;
|
|
}
|
|
else
|
|
ptszAppName = L"RunAs Service";
|
|
|
|
|
|
//
|
|
// Add this service to the SCM's database.
|
|
//
|
|
hService = CreateService
|
|
(hSCM,
|
|
wszSvcName,
|
|
ptszAppName,
|
|
SERVICE_ALL_ACCESS,
|
|
SERVICE_WIN32_SHARE_PROCESS,
|
|
SERVICE_AUTO_START,
|
|
SERVICE_ERROR_IGNORE,
|
|
L"%SystemRoot%\\system32\\svchost.exe -k netsvcs",
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
if(hService == NULL) {
|
|
dw = GetLastError();
|
|
CloseServiceHandle(hSCM);
|
|
return dw;
|
|
}
|
|
|
|
if(hMod != NULL)
|
|
{
|
|
WCHAR DescString[500];
|
|
SERVICE_DESCRIPTION SvcDesc;
|
|
|
|
LoadString( hMod,
|
|
SECLOGON_STRING_DESCRIPTION,
|
|
DescString,
|
|
500
|
|
);
|
|
|
|
SvcDesc.lpDescription = DescString;
|
|
ChangeServiceConfig2( hService,
|
|
SERVICE_CONFIG_DESCRIPTION,
|
|
&SvcDesc
|
|
);
|
|
|
|
}
|
|
|
|
//
|
|
// Close the service and the SCM
|
|
//
|
|
CloseServiceHandle(hService);
|
|
CloseServiceHandle(hSCM);
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
RemoveService()
|
|
/*++
|
|
|
|
Routine Description:
|
|
deinstalls the service.
|
|
|
|
Arguments:
|
|
none.
|
|
|
|
Return Value:
|
|
as returned by service controller apis.
|
|
|
|
--*/
|
|
{
|
|
DWORD dw;
|
|
SC_HANDLE hService;
|
|
//
|
|
// Open the SCM on this machine.
|
|
//
|
|
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
|
|
if(hSCM == NULL) {
|
|
dw = GetLastError();
|
|
return dw;
|
|
}
|
|
|
|
//
|
|
// Open this service for DELETE access
|
|
//
|
|
hService = OpenService(hSCM, wszSvcName, DELETE);
|
|
if(hService == NULL) {
|
|
dw = GetLastError();
|
|
CloseServiceHandle(hSCM);
|
|
return dw;
|
|
}
|
|
|
|
//
|
|
// Remove this service from the SCM's database.
|
|
//
|
|
DeleteService(hService);
|
|
|
|
//
|
|
// Close the service and the SCM
|
|
//
|
|
CloseServiceHandle(hService);
|
|
CloseServiceHandle(hSCM);
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
void SvchostPushServiceGlobals(PSVCHOST_GLOBAL_DATA pGlobalData) {
|
|
// this entry point is called by svchost.exe
|
|
GlobalData=pGlobalData;
|
|
}
|
|
|
|
void SvcEntry_Seclogon
|
|
(IN DWORD argc,
|
|
IN WCHAR **argv)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Entry point for the service dll when running in svchost.exe
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
ServiceMain(0,NULL);
|
|
|
|
UNREFERENCED_PARAMETER(argc);
|
|
UNREFERENCED_PARAMETER(argv);
|
|
}
|
|
|
|
|
|
|
|
STDAPI
|
|
DllRegisterServer(void)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
return InstallService();
|
|
}
|
|
|
|
STDAPI
|
|
DllUnregisterServer(void)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
return RemoveService();
|
|
}
|
|
|
|
DWORD InitGlobalState() {
|
|
BOOLEAN bWasEnabled;
|
|
DWORD dwResult;
|
|
LSA_STRING lsastr_LogonProcessName;
|
|
LSA_STRING lsastr_PackageName;
|
|
NTSTATUS ntStatus;
|
|
ULONG ulSecurityMode;
|
|
|
|
ZeroMemory(&g_state, sizeof(g_state));
|
|
|
|
// LsaRegisterLogonProcess() requires TCB privilege
|
|
RtlAdjustPrivilege(SE_TCB_PRIVILEGE, TRUE, FALSE, &bWasEnabled);
|
|
RtlInitString(&lsastr_LogonProcessName, "Secondary Logon Service");
|
|
ntStatus = LsaRegisterLogonProcess(&lsastr_LogonProcessName, &g_state.hLSA, &ulSecurityMode);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
goto LsaRegisterLogonProcessError;
|
|
RtlAdjustPrivilege(SE_TCB_PRIVILEGE, bWasEnabled, FALSE, &bWasEnabled);
|
|
|
|
RtlInitString(&lsastr_PackageName, "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0");
|
|
ntStatus = LsaLookupAuthenticationPackage(g_state.hLSA, &lsastr_PackageName, &g_state.hMSVPackage);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
goto LsaLookupAuthenticationPackageError;
|
|
|
|
RtlInitString(&lsastr_PackageName, NEGOSSP_NAME_A);
|
|
ntStatus = LsaLookupAuthenticationPackage(g_state.hLSA, &lsastr_PackageName, &g_state.hKerbPackage);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
goto LsaLookupAuthenticationPackageError;
|
|
|
|
// Initialize the list of seclogon processes:
|
|
InitializeListHead(&g_state.JobListHead);
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
return dwResult;
|
|
|
|
SET_DWRESULT(LsaLookupAuthenticationPackageError, RtlNtStatusToDosError(ntStatus));
|
|
SET_DWRESULT(LsaRegisterLogonProcessError, RtlNtStatusToDosError(ntStatus));
|
|
}
|
|
|
|
|
|
DWORD MySetServiceStatus(DWORD dwCurrentState, DWORD dwCheckPoint, DWORD dwWaitHint, DWORD dwExitCode) {
|
|
BOOL fResult;
|
|
DWORD dwResult;
|
|
DWORD dwAcceptStop;
|
|
|
|
EnterCriticalSection(&csForProcessCount);
|
|
dwAcceptStop = IsListEmpty(&g_state.JobListHead) ? SERVICE_ACCEPT_STOP : 0;
|
|
|
|
g_state.serviceStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
|
|
g_state.serviceStatus.dwCurrentState = dwCurrentState;
|
|
|
|
switch (dwCurrentState)
|
|
{
|
|
case SERVICE_STOPPED:
|
|
case SERVICE_STOP_PENDING:
|
|
g_state.serviceStatus.dwControlsAccepted = 0;
|
|
break;
|
|
case SERVICE_RUNNING:
|
|
case SERVICE_PAUSED:
|
|
g_state.serviceStatus.dwControlsAccepted =
|
|
// SERVICE_ACCEPT_SHUTDOWN
|
|
SERVICE_ACCEPT_PAUSE_CONTINUE
|
|
| dwAcceptStop;
|
|
break;
|
|
case SERVICE_START_PENDING:
|
|
case SERVICE_CONTINUE_PENDING:
|
|
case SERVICE_PAUSE_PENDING:
|
|
g_state.serviceStatus.dwControlsAccepted =
|
|
// SERVICE_ACCEPT_SHUTDOWN
|
|
dwAcceptStop;
|
|
break;
|
|
}
|
|
g_state.serviceStatus.dwWin32ExitCode = dwExitCode;
|
|
g_state.serviceStatus.dwCheckPoint = dwCheckPoint;
|
|
g_state.serviceStatus.dwWaitHint = dwWaitHint;
|
|
|
|
fResult = SetServiceStatus(g_state.hServiceStatus, &g_state.serviceStatus);
|
|
_JumpCondition(FALSE == fResult, SetServiceStatusError);
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
CommonReturn:
|
|
LeaveCriticalSection(&csForProcessCount);
|
|
return dwResult;
|
|
|
|
ErrorReturn:
|
|
goto CommonReturn;
|
|
|
|
SET_DWRESULT(SetServiceStatusError, GetLastError());
|
|
}
|
|
|
|
DWORD MySetServiceStopped(DWORD dwExitCode) {
|
|
BOOL fResult;
|
|
DWORD dwResult;
|
|
|
|
g_state.serviceStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
|
|
g_state.serviceStatus.dwCurrentState = SERVICE_STOPPED;
|
|
g_state.serviceStatus.dwControlsAccepted = 0;
|
|
g_state.serviceStatus.dwWin32ExitCode = dwExitCode;
|
|
g_state.serviceStatus.dwCheckPoint = 0;
|
|
g_state.serviceStatus.dwWaitHint = 0;
|
|
|
|
fResult = SetServiceStatus(g_state.hServiceStatus, &g_state.serviceStatus);
|
|
_JumpCondition(FALSE == fResult, SetServiceStatusError);
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
CommonReturn:
|
|
return dwResult;
|
|
|
|
ErrorReturn:
|
|
goto CommonReturn;
|
|
|
|
SET_DWRESULT(SetServiceStatusError, GetLastError());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Implementation of RPC interface:
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Make sure that the caller is local (we don't want to allow remote machines to call us!)
|
|
// This should be done as soon as possible (so we don't waste resources on hackers).
|
|
// Therefore, we put this call in a security callback -- if the callback fails, the RPC
|
|
// runtime won't allocate memory for the hacker's parameters.
|
|
//
|
|
long __stdcall SeclSecurityCallback(void *Interface, void *Context)
|
|
{
|
|
RPC_STATUS rpcStatus;
|
|
unsigned int fClientIsLocal;
|
|
|
|
rpcStatus = I_RpcBindingIsClientLocal(NULL, &fClientIsLocal);
|
|
if (RPC_S_OK != rpcStatus)
|
|
goto error;
|
|
|
|
if (!fClientIsLocal) {
|
|
rpcStatus = RPC_S_ACCESS_DENIED;
|
|
goto error;
|
|
}
|
|
|
|
rpcStatus = RPC_S_OK;
|
|
error:
|
|
return rpcStatus;
|
|
|
|
UNREFERENCED_PARAMETER(Interface);
|
|
UNREFERENCED_PARAMETER(Context);
|
|
}
|
|
|
|
|
|
void WINAPI SeclCreateProcessWithLogonW
|
|
(IN handle_t hRPCBinding,
|
|
IN SECL_SLI *pSeclSli,
|
|
OUT SECL_SLRI *pSeclSlri)
|
|
{
|
|
BOOL fEnteredCriticalSection = FALSE;
|
|
BOOL fIsImpersonatingClient = FALSE;
|
|
DWORD dwResult;
|
|
HANDLE hHeap = NULL;
|
|
PLIST_ENTRY ple;
|
|
PSECONDARYLOGONINFOW psli = NULL;
|
|
SECL_SLRI SeclSlri;
|
|
SECONDARYLOGONRETINFO slri;
|
|
|
|
ZeroMemory(&SeclSlri, sizeof(SeclSlri));
|
|
ZeroMemory(&slri, sizeof(slri));
|
|
|
|
// We don't want the service to be stopped while we're creating a process.
|
|
EnterCriticalSection(&csForProcessCount);
|
|
fEnteredCriticalSection = TRUE;
|
|
// Service isn't running anymore ... don't create the process.
|
|
_JumpCondition(SERVICE_RUNNING != g_state.serviceStatus.dwCurrentState, ServiceStoppedError);
|
|
|
|
hHeap = GetProcessHeap();
|
|
_JumpCondition(NULL == hHeap, MemoryError);
|
|
|
|
__try {
|
|
dwResult = To_SECONDARYLOGONINFOW(pSeclSli, &psli);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, To_SECONDARYLOGONINFOW_Error);
|
|
|
|
if (psli->LogonIdHighPart != 0 || psli->LogonIdLowPart != 0)
|
|
{
|
|
// This is probably a notification from winlogon.exe that
|
|
// a client is logging off. If so, we must clean up all processes
|
|
// they've left running.
|
|
|
|
LUID LogonId;
|
|
|
|
//
|
|
// We should impersonate the client,
|
|
// check it is LocalSystem and only then proceed.
|
|
//
|
|
fIsImpersonatingClient = RPC_S_OK == RpcImpersonateClient((RPC_BINDING_HANDLE)hRPCBinding);
|
|
if(FALSE == fIsImpersonatingClient || FALSE == IsSystemProcess())
|
|
{
|
|
slri.dwErrorCode = ERROR_INVALID_PARAMETER;
|
|
ZeroMemory(&slri.pi, sizeof(slri.pi));
|
|
}
|
|
else
|
|
{
|
|
LogonId.HighPart = psli->LogonIdHighPart;
|
|
LogonId.LowPart = psli->LogonIdLowPart;
|
|
|
|
// Loop over the list of active jobs and find the ones associated with the terminating logon session.
|
|
// NOTE: there could be more than one.
|
|
for (ple = g_state.JobListHead.Flink; ple != &g_state.JobListHead; ple = ple->Flink)
|
|
{
|
|
SecondaryLogonJob *pslj = CONTAINING_RECORD(ple, SecondaryLogonJob, list);
|
|
|
|
if(pslj->RootLogonId.HighPart == LogonId.HighPart && pslj->RootLogonId.LowPart == LogonId.LowPart)
|
|
{
|
|
// This will be NULL in the terminal services case (we don't use job objects in this case).
|
|
// That's OK though, as csrss will clean things up for us.
|
|
if (NULL != pslj->hJob)
|
|
{
|
|
TerminateJobObject(pslj->hJob, 0);
|
|
}
|
|
}
|
|
}
|
|
slri.dwErrorCode = ERROR_SUCCESS;
|
|
ZeroMemory(&slri.pi, sizeof(slri.pi));
|
|
|
|
}
|
|
|
|
if (fIsImpersonatingClient)
|
|
{
|
|
// Ignore error: nothing we can do on failure!
|
|
if (RPC_S_OK == RpcRevertToSelfEx((RPC_BINDING_HANDLE)hRPCBinding))
|
|
{
|
|
fIsImpersonatingClient = FALSE;
|
|
}
|
|
}
|
|
|
|
if (NULL != psli)
|
|
{
|
|
Free_SECONDARYLOGONINFOW(psli);
|
|
psli = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Ok, this isn't notification from winlogon, it's really a user
|
|
// trying to use the service. Create a process for them.
|
|
//
|
|
SlrCreateProcessWithLogon((RPC_BINDING_HANDLE)hRPCBinding, &psli, &slri);
|
|
}
|
|
|
|
// If we've errored out, jump to the error handler.
|
|
_JumpCondition(NO_ERROR != slri.dwErrorCode, UnspecifiedSeclogonError);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
// If anything goes wrong, return the exception code to the client
|
|
dwResult = GetExceptionCode();
|
|
goto ExceptionError;
|
|
}
|
|
|
|
CommonReturn:
|
|
// Do not free slri: this will be freed by the watchdog!
|
|
SeclSlri.hProcess = (unsigned __int64)slri.pi.hProcess;
|
|
SeclSlri.hThread = (unsigned __int64)slri.pi.hThread;
|
|
SeclSlri.ulProcessId = slri.pi.dwProcessId;
|
|
SeclSlri.ulThreadId = slri.pi.dwThreadId;
|
|
SeclSlri.ulErrorCode = slri.dwErrorCode;
|
|
|
|
if (fEnteredCriticalSection)
|
|
LeaveCriticalSection(&csForProcessCount);
|
|
|
|
// Assign the OUT parameter:
|
|
*pSeclSlri = SeclSlri;
|
|
return;
|
|
|
|
ErrorReturn:
|
|
ZeroMemory(&slri.pi, sizeof(slri.pi));
|
|
if (NULL != psli) { Free_SECONDARYLOGONINFOW(psli); }
|
|
|
|
slri.dwErrorCode = dwResult;
|
|
goto CommonReturn;
|
|
|
|
SET_DWRESULT(ExceptionError, dwResult);
|
|
SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY);
|
|
SET_DWRESULT(ServiceStoppedError, ERROR_SERVICE_NOT_ACTIVE);
|
|
SET_DWRESULT(To_SECONDARYLOGONINFOW_Error, dwResult);
|
|
SET_DWRESULT(UnspecifiedSeclogonError, slri.dwErrorCode);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// RPC Utility methods:
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
DWORD SeclStartRpcServer() {
|
|
DWORD dwResult;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
EnterCriticalSection(&csForProcessCount);
|
|
|
|
if (!g_state.fRPCServerActive) {
|
|
RpcStatus = RpcServerUseProtseqEpW(L"ncalrpc", RPC_C_PROTSEQ_MAX_REQS_DEFAULT, wszSeclogonSharedProcEndpointName, NULL);
|
|
if (RPC_S_DUPLICATE_ENDPOINT == RpcStatus)
|
|
RpcStatus = RPC_S_OK;
|
|
if (RPC_S_OK != RpcStatus)
|
|
goto RpcServerUseProtseqEpWError;
|
|
|
|
RpcStatus = RpcServerRegisterIfEx(ISeclogon_v1_0_s_ifspec, NULL, NULL, RPC_IF_AUTOLISTEN | RPC_IF_ALLOW_SECURE_ONLY, RPC_C_PROTSEQ_MAX_REQS_DEFAULT, SeclSecurityCallback);
|
|
if (RPC_S_OK != RpcStatus)
|
|
goto StartRpcServerError;
|
|
|
|
g_state.fRPCServerActive = TRUE;
|
|
}
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
CommonReturn:
|
|
LeaveCriticalSection(&csForProcessCount);
|
|
return dwResult;
|
|
|
|
ErrorReturn:
|
|
goto CommonReturn;
|
|
|
|
SET_DWRESULT(RpcServerUseProtseqEpWError, RpcStatus);
|
|
SET_DWRESULT(StartRpcServerError, RpcStatus);
|
|
}
|
|
|
|
DWORD SeclStopRpcServer() {
|
|
DWORD dwResult;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
EnterCriticalSection(&csForProcessCount);
|
|
|
|
if (g_state.fRPCServerActive) {
|
|
RpcStatus = RpcServerUnregisterIf(ISeclogon_v1_0_s_ifspec, 0, 0);
|
|
if (RPC_S_OK != RpcStatus)
|
|
goto RpcServerUnregisterIfError;
|
|
g_state.fRPCServerActive = FALSE;
|
|
}
|
|
|
|
dwResult = ERROR_SUCCESS;
|
|
CommonReturn:
|
|
LeaveCriticalSection(&csForProcessCount);
|
|
return dwResult;
|
|
|
|
ErrorReturn:
|
|
goto CommonReturn;
|
|
|
|
SET_DWRESULT(RpcServerUnregisterIfError, RpcStatus);
|
|
}
|
|
|
|
|
|
void Free_SECONDARYLOGONINFOW(IN PSECONDARYLOGONINFOW psli) {
|
|
HANDLE hHeap = GetProcessHeap();
|
|
|
|
if (NULL == hHeap)
|
|
return;
|
|
|
|
if (NULL != psli) {
|
|
if (NULL != psli->lpStartupInfo) {
|
|
HeapFree(hHeap, 0, psli->lpStartupInfo);
|
|
}
|
|
HeapFree(hHeap, 0, psli);
|
|
}
|
|
}
|
|
|
|
DWORD To_LPWSTR(IN SECL_STRING *pss,
|
|
OUT LPWSTR *ppwsz)
|
|
{
|
|
DWORD dwResult;
|
|
|
|
if (NULL != pss->pwsz) {
|
|
// Ensure that the string is NULL-terminated where the caller says it is:
|
|
if (pss->ccSize <= pss->ccLength) {
|
|
goto InvalidParameterError;
|
|
}
|
|
// NULL-terminate the string ourself
|
|
pss->pwsz[pss->ccLength] = L'\0';
|
|
}
|
|
|
|
*ppwsz = pss->pwsz;
|
|
dwResult = ERROR_SUCCESS;
|
|
ErrorReturn:
|
|
return dwResult;
|
|
|
|
SET_DWRESULT(InvalidParameterError, ERROR_INVALID_PARAMETER);
|
|
}
|
|
|
|
DWORD To_SECONDARYLOGONINFOW(IN PSECL_SLI pSeclSli,
|
|
OUT PSECONDARYLOGONINFOW *ppsli)
|
|
{
|
|
DWORD dwAllocFlags = HEAP_ZERO_MEMORY;
|
|
DWORD dwIndex;
|
|
DWORD dwResult;
|
|
HANDLE hHeap = NULL;
|
|
PSECONDARYLOGONINFOW psli = NULL;
|
|
|
|
hHeap = GetProcessHeap();
|
|
_JumpCondition(NULL == hHeap, GetProcessHeapError);
|
|
|
|
psli = (PSECONDARYLOGONINFOW)HeapAlloc(hHeap, dwAllocFlags, sizeof(SECONDARYLOGONINFOW));
|
|
_JumpCondition(NULL == psli, MemoryError);
|
|
|
|
psli->lpStartupInfo = (LPSTARTUPINFO)HeapAlloc(hHeap, dwAllocFlags, sizeof(STARTUPINFO));
|
|
_JumpCondition(NULL == psli->lpStartupInfo, MemoryError);
|
|
|
|
__try {
|
|
{
|
|
struct {
|
|
SECL_STRING *pss;
|
|
LPWSTR *ppwsz;
|
|
} rg_StringsToMap[] = {
|
|
{ &(pSeclSli->ssDesktop), /* Is mapped to ----> */ &(psli->lpStartupInfo->lpDesktop) },
|
|
{ &(pSeclSli->ssTitle), /* Is mapped to ----> */ &(psli->lpStartupInfo->lpTitle) },
|
|
{ &(pSeclSli->ssUsername), /* Is mapped to ----> */ &(psli->lpUsername) },
|
|
{ &(pSeclSli->ssDomain), /* Is mapped to ----> */ &(psli->lpDomain) },
|
|
{ &(pSeclSli->ssApplicationName), /* Is mapped to ----> */ &(psli->lpApplicationName) },
|
|
{ &(pSeclSli->ssCommandLine), /* Is mapped to ----> */ &(psli->lpCommandLine) },
|
|
{ &(pSeclSli->ssCurrentDirectory), /* Is mapped to ----> */ (LPWSTR *)&(psli->lpCurrentDirectory) }
|
|
};
|
|
|
|
for (dwIndex = 0; dwIndex < ARRAYSIZE(rg_StringsToMap); dwIndex++) {
|
|
dwResult = To_LPWSTR(rg_StringsToMap[dwIndex].pss, rg_StringsToMap[dwIndex].ppwsz);
|
|
_JumpCondition(ERROR_SUCCESS != dwResult, To_LPWSTR_Error);
|
|
}
|
|
}
|
|
|
|
// Get the (possibly encrypted) passwd:
|
|
psli->uszPassword.Buffer = pSeclSli->ssPassword.pwsz;
|
|
psli->uszPassword.Length = pSeclSli->ssPassword.ccLength;
|
|
psli->uszPassword.MaximumLength = pSeclSli->ssPassword.ccSize;
|
|
if (NULL != psli->uszPassword.Buffer && psli->uszPassword.MaximumLength > 0)
|
|
{
|
|
psli->uszPassword.Buffer[psli->uszPassword.MaximumLength-1] = L'\0';
|
|
}
|
|
// Grab the environment from the SECL_SLI block:
|
|
psli->lpEnvironment = pSeclSli->sbEnvironment.pb;
|
|
|
|
// Ensure that our environment parameter is NULL-terminated:
|
|
if (NULL != psli->lpEnvironment)
|
|
{
|
|
// The environment block is terminated by 2 NULL chars.
|
|
DWORD cbTerm = (CREATE_UNICODE_ENVIRONMENT & pSeclSli->ulCreationFlags) ? sizeof(WCHAR)*2 : sizeof(CHAR)*2;
|
|
|
|
// ensure we have a large enough buffer to contain the NULL termination chars:
|
|
if (pSeclSli->sbEnvironment.cb < cbTerm)
|
|
goto InvalidParameterError;
|
|
|
|
// add terminated NULLs to the end of the environment block
|
|
for (; cbTerm > 0; cbTerm--)
|
|
{
|
|
pSeclSli->sbEnvironment.pb[pSeclSli->sbEnvironment.cb - cbTerm] = 0;
|
|
}
|
|
}
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
// Don't give the caller too much information: the input was bad, that's all they need to know.
|
|
dwResult = ERROR_INVALID_PARAMETER;
|
|
goto ExceptionError;
|
|
}
|
|
|
|
psli->dwProcessId = pSeclSli->ulProcessId;
|
|
psli->LogonIdLowPart = pSeclSli->ulLogonIdLowPart;
|
|
psli->LogonIdHighPart = pSeclSli->lLogonIdHighPart;
|
|
psli->dwLogonFlags = pSeclSli->ulLogonFlags;
|
|
psli->dwCreationFlags = pSeclSli->ulCreationFlags;
|
|
psli->dwSeclogonFlags = pSeclSli->ulSeclogonFlags;
|
|
psli->hToken = (HANDLE)((ULONG_PTR)pSeclSli->hToken);
|
|
|
|
*ppsli = psli;
|
|
dwResult = ERROR_SUCCESS;
|
|
CommonReturn:
|
|
return dwResult;
|
|
|
|
ErrorReturn:
|
|
Free_SECONDARYLOGONINFOW(psli);
|
|
goto CommonReturn;
|
|
|
|
SET_DWRESULT(ExceptionError, dwResult);
|
|
SET_DWRESULT(GetProcessHeapError, GetLastError());
|
|
SET_DWRESULT(InvalidParameterError, ERROR_INVALID_PARAMETER);
|
|
SET_DWRESULT(MemoryError, ERROR_NOT_ENOUGH_MEMORY);
|
|
SET_DWRESULT(To_LPWSTR_Error, dwResult);
|
|
}
//////////////////////////////// End Of File /////////////////////////////////
|