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.
4780 lines
140 KiB
4780 lines
140 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1996.
|
|
//
|
|
// File: security.cxx
|
|
//
|
|
// Contents:
|
|
//
|
|
// Classes:
|
|
//
|
|
// Functions: None.
|
|
//
|
|
// History: 15-May-96 MarkBl Created
|
|
// 26-Feb-01 JBenton Prefix Bug 160502 - using uninit memory
|
|
// 17-Apr-01 a-JyotiG Fixed Bug 367263 - Should not assign any privilege/right
|
|
// to system account.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "..\pch\headers.hxx"
|
|
#include <modes.h> // found in private\inc\crypto
|
|
#include <ntsecapi.h>
|
|
#include <ntdsapi.h> // DsCrackNames
|
|
#include "resource.h"
|
|
#include "globals.hxx" // BUGBUG 254102
|
|
#include "sch_cls.hxx" // To implement AddAtJobWithHash
|
|
#include "authzi.h" // for auditing
|
|
#include <FolderSecurity.h>
|
|
#include "svc_core.hxx"
|
|
#include "security.hxx"
|
|
#include "auditing.hxx"
|
|
#include "misc.hxx"
|
|
|
|
//
|
|
// some prototypes for functions not in a header
|
|
//
|
|
BOOL IsThreadCallerAnAdmin(
|
|
HANDLE hThreadToken);
|
|
|
|
//
|
|
// global stuff
|
|
//
|
|
WCHAR gwszComputerName[MAX_COMPUTERNAME_LENGTH + 2] = L""; // this buffer must remain this size or it will break old credentials
|
|
LPWSTR gpwszComputerName = NULL;
|
|
DWORD gdwKeyElement = 0;
|
|
DWORD gccComputerName = MAX_COMPUTERNAME_LENGTH + 2;
|
|
POLICY_ACCOUNT_DOMAIN_INFO* gpDomainInfo = NULL;
|
|
DWORD gcbMachineSid = 0;
|
|
PSID gpMachineSid = NULL;
|
|
extern CStaticCritSec gcsSSCritSection;
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Helper function: ValidateRunAs
|
|
//
|
|
// Synopsis: Verify that password entered for Run As account is correct
|
|
// by actually trying to log on using the credentials
|
|
//
|
|
// *** Verification of NULL passwords is handled elsewhere ***
|
|
//
|
|
// Returns: bool
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
bool ValidateRunAs(
|
|
LPCWSTR pwszAccount,
|
|
LPCWSTR pwszDomain,
|
|
LPCWSTR pwszPassword)
|
|
{
|
|
// NOTE - don't zero out the password anywhere in here -- we still need it!
|
|
|
|
//
|
|
// copy to buffers we can manipulate
|
|
//
|
|
WCHAR wszDomain [MAX_DOMAINNAME + 1];
|
|
WCHAR wszAccount [MAX_USERNAME + 1];
|
|
|
|
//
|
|
// if the domain is present in the account name (SAM names), skip over it
|
|
//
|
|
WCHAR* pSlash = (WCHAR *) wcschr(pwszAccount, L'\\');
|
|
if (pSlash)
|
|
StringCchCopy(wszAccount, MAX_USERNAME + 1, pSlash + 1);
|
|
else
|
|
StringCchCopy(wszAccount, MAX_USERNAME + 1, pwszAccount);
|
|
|
|
StringCchCopy(wszDomain, MAX_DOMAINNAME + 1, pwszDomain);
|
|
|
|
//
|
|
// If the name was passed in as a UPN, convert it to a SAM name first.
|
|
// Treat the account name as a UPN if it lacks a \ and has an @.
|
|
// Otherwise, treat it as a SAM name.
|
|
//
|
|
if (wcschr(pwszAccount, L'\\') == NULL && wcschr(pwszAccount, L'@') != NULL)
|
|
{
|
|
LPWSTR pwszSamName;
|
|
DWORD dwErr = SchedUPNToAccountName(pwszAccount, &pwszSamName);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
pSlash = wcschr(pwszSamName, L'\\');
|
|
schAssert(pSlash);
|
|
*pSlash = L'\0';
|
|
StringCchCopy(wszDomain, MAX_DOMAINNAME + 1, pwszSamName);
|
|
StringCchCopy(wszAccount, MAX_USERNAME + 1, pSlash + 1);
|
|
delete pwszSamName;
|
|
}
|
|
}
|
|
|
|
HANDLE hToken = NULL;
|
|
if (LogonUser(wszAccount,
|
|
wszDomain,
|
|
pwszPassword,
|
|
LOGON32_LOGON_NETWORK,
|
|
LOGON32_PROVIDER_DEFAULT,
|
|
&hToken))
|
|
{
|
|
CloseHandle(hToken);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Helper function: NotifyLsaOfPasswordChange
|
|
//
|
|
// Synopsis: Notify LSA if the password has been changed for an account so
|
|
// that it can determine if any user sessions need to be refreshed.
|
|
//
|
|
// This code was stolen and modified from base\cluster\service\nm\setpass.c.
|
|
//
|
|
// Returns: ERROR_SUCCESS if successful, Win32 error code otherwise.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
DWORD NotifyLsaOfPasswordChange(
|
|
LPCWSTR pwszAccount,
|
|
LPCWSTR pwszDomain,
|
|
LPCWSTR pwszPassword)
|
|
{
|
|
DWORD ReturnStatus;
|
|
NTSTATUS Status;
|
|
NTSTATUS SubStatus;
|
|
LSA_STRING LsaStringBuf;
|
|
char* AuthPackage = MSV1_0_PACKAGE_NAME;
|
|
HANDLE LsaHandle = NULL;
|
|
ULONG PackageId;
|
|
|
|
PMSV1_0_CHANGEPASSWORD_REQUEST Request = NULL;
|
|
ULONG RequestSize;
|
|
PBYTE Where;
|
|
PVOID Response = NULL;
|
|
ULONG ResponseSize;
|
|
|
|
//
|
|
// Change password in LSA cache
|
|
//
|
|
Status = LsaConnectUntrusted(&LsaHandle);
|
|
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
ReturnStatus = LsaNtStatusToWinError(Status);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
RtlInitString(&LsaStringBuf, AuthPackage);
|
|
|
|
Status = LsaLookupAuthenticationPackage(
|
|
LsaHandle, // Handle
|
|
&LsaStringBuf, // MSV1_0 authentication package
|
|
&PackageId // output: authentication package identifier
|
|
);
|
|
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
ReturnStatus = LsaNtStatusToWinError(Status);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Prepare to call LsaCallAuthenticationPackage()
|
|
//
|
|
RequestSize = sizeof(MSV1_0_CHANGEPASSWORD_REQUEST) +
|
|
( ( wcslen(pwszAccount) +
|
|
wcslen(pwszDomain) +
|
|
wcslen(pwszPassword) + 3
|
|
) * sizeof(WCHAR)
|
|
);
|
|
|
|
Request = (PMSV1_0_CHANGEPASSWORD_REQUEST)
|
|
HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, RequestSize);
|
|
|
|
if (Request == NULL)
|
|
{
|
|
ReturnStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
ULONG BuffSize = RequestSize;
|
|
|
|
Where = (PBYTE) (Request + 1);
|
|
BuffSize--;
|
|
Request->MessageType = MsV1_0ChangeCachedPassword;
|
|
StringCbCopy((LPWSTR) Where, BuffSize, pwszDomain );
|
|
RtlInitUnicodeString( &Request->DomainName, (wchar_t *) Where );
|
|
Where += Request->DomainName.MaximumLength;
|
|
BuffSize -= Request->DomainName.MaximumLength;
|
|
|
|
StringCbCopy((LPWSTR) Where, BuffSize , pwszAccount );
|
|
RtlInitUnicodeString( &Request->AccountName, (wchar_t *) Where );
|
|
Where += Request->AccountName.MaximumLength;
|
|
BuffSize -= Request->AccountName.MaximumLength;
|
|
|
|
StringCbCopy((LPWSTR) Where, BuffSize , pwszPassword );
|
|
RtlInitUnicodeString( &Request->NewPassword, (wchar_t *) Where );
|
|
Where += Request->NewPassword.MaximumLength;
|
|
|
|
Status = LsaCallAuthenticationPackage(
|
|
LsaHandle,
|
|
PackageId,
|
|
Request, // MSV1_0_CHANGEPASSWORD_REQUEST
|
|
RequestSize,
|
|
&Response,
|
|
&ResponseSize,
|
|
&SubStatus // Receives NSTATUS code indicating the
|
|
// completion status of the authentication
|
|
// package if ERROR_SUCCESS is returned.
|
|
);
|
|
|
|
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
ReturnStatus = LsaNtStatusToWinError(Status);
|
|
goto ErrorExit;
|
|
}
|
|
else if (LsaNtStatusToWinError(SubStatus) != ERROR_SUCCESS)
|
|
{
|
|
ReturnStatus = LsaNtStatusToWinError(SubStatus);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
ReturnStatus = ERROR_SUCCESS;
|
|
|
|
ErrorExit:
|
|
|
|
if (LsaHandle != NULL)
|
|
{
|
|
Status = LsaDeregisterLogonProcess(LsaHandle);
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
// ignore; could possibly log this
|
|
}
|
|
}
|
|
|
|
if (Request != NULL)
|
|
{
|
|
if (!HeapFree(GetProcessHeap(), 0, Request))
|
|
{
|
|
// ignore; could possibly log this
|
|
}
|
|
}
|
|
|
|
if (Response != NULL)
|
|
{
|
|
Status = LsaFreeReturnBuffer(Response);
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
// ignore; could possibly log this
|
|
}
|
|
}
|
|
|
|
return ReturnStatus;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// RPC: SASetAccountInformation
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [Handle] --
|
|
// [pwszJobName] -- Relative job name. eg: MyJob.job.
|
|
// [pwszAccount] --
|
|
// [pwszPassword] --
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
SASetAccountInformation(
|
|
SASEC_HANDLE Handle,
|
|
LPCWSTR pwszJobName,
|
|
LPCWSTR pwszAccount,
|
|
LPCWSTR pwszPassword,
|
|
DWORD dwJobFlags)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// we're going to do the access check in two stages,
|
|
// first make sure that the principal is allowed to
|
|
// do any scheduling whatsoever - later on, we'll
|
|
// check permissions on the specific file in question
|
|
if (FAILED(hr = RPCFolderAccessCheck(g_TasksFolderInfo.ptszPath, FILE_WRITE_DATA, HandleImpersonation)))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Check for invalid params (note that pwszPassword is allowed to be NULL)
|
|
//
|
|
if (pwszJobName == NULL || pwszAccount == NULL)
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
//
|
|
// Disallow files outside the tasks folder
|
|
//
|
|
if (wcschr(pwszJobName, L'\\') || wcschr(pwszJobName, L'/'))
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
//
|
|
// Append the job name to the local Task's folder path.
|
|
//
|
|
schAssert(g_TasksFolderInfo.ptszPath != NULL);
|
|
WCHAR wszJobPath[MAX_PATH + 1];
|
|
if ((wcslen(g_TasksFolderInfo.ptszPath) + 1 + wcslen(pwszJobName) + 1) > (MAX_PATH + 1))
|
|
{
|
|
CHECK_HRESULT(SCHED_E_CANNOT_OPEN_TASK);
|
|
return(SCHED_E_CANNOT_OPEN_TASK);
|
|
}
|
|
StringCchCopy(wszJobPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath);
|
|
StringCchCat(wszJobPath, MAX_PATH + 1, L"\\");
|
|
StringCchCat(wszJobPath, MAX_PATH + 1, pwszJobName);
|
|
|
|
//
|
|
// Get the account's SID and domain
|
|
//
|
|
PSID pAccountSid = NULL;
|
|
DWORD cbAccountSid = MAX_SID_SIZE;
|
|
DWORD ccDomain = MAX_DOMAINNAME + 1;
|
|
BYTE pbAccountSid[MAX_SID_SIZE];
|
|
WCHAR wszDomain[MAX_DOMAINNAME + 1] = L"";
|
|
|
|
HRESULT hrGetAccountSidAndDomain = GetAccountSidAndDomain(pwszAccount, pbAccountSid, cbAccountSid, wszDomain, ccDomain);
|
|
if (FAILED(hrGetAccountSidAndDomain))
|
|
{
|
|
// continue on -- we don't want to return yet on failure, because we don't want to reveal that
|
|
// the "run as" account is invalid if the caller shouldn't even be allowed to make this call;
|
|
}
|
|
else
|
|
{
|
|
pAccountSid = pbAccountSid;
|
|
}
|
|
|
|
//
|
|
// Impersonate the caller, open his token, then end impersonation so we aren't impersonated during Auditing
|
|
//
|
|
DWORD RpcStatus = RpcImpersonateClient(NULL);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(RpcStatus);
|
|
CHECK_HRESULT(hr);
|
|
return hr;
|
|
}
|
|
|
|
HANDLE hToken;
|
|
if (!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_QUERY, // Desired access.
|
|
TRUE, // Open as self.
|
|
&hToken))
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto Clean0;
|
|
}
|
|
|
|
if ((RpcStatus = RpcRevertToSelf()) != RPC_S_OK)
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(RpcStatus);
|
|
CHECK_HRESULT(hr);
|
|
goto Clean1;
|
|
}
|
|
|
|
//
|
|
// Now that we have the thread token, audit the job creation.
|
|
// We do this here regardless of whether the user gets access denied down below.
|
|
// However, we can only do this if we succeeded in looking up the "run as" account,
|
|
// as that information is needed for the audit logging.
|
|
//
|
|
if (SUCCEEDED(hrGetAccountSidAndDomain))
|
|
{
|
|
hr = AuditJob(hToken, pAccountSid, wszJobPath);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR_OUT("SASetAccountInformation: AuditJob", hr);
|
|
|
|
// let's just forget this happened, OK?
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Reimpersonate client
|
|
//
|
|
RpcStatus = RpcImpersonateClient(NULL);
|
|
if (RpcStatus != RPC_S_OK)
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(RpcStatus);
|
|
CHECK_HRESULT(hr);
|
|
goto Clean1;
|
|
}
|
|
|
|
//
|
|
// Check whether caller should even be allowed to make this call
|
|
//
|
|
if (FAILED(hr = FolderAccessCheck(wszJobPath, hToken, FILE_WRITE_DATA)))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto Clean1;
|
|
}
|
|
|
|
if (FAILED(hrGetAccountSidAndDomain))
|
|
{
|
|
//
|
|
// OK, caller passed the above access check, so reveal that the "run as" account is bad
|
|
//
|
|
hr = hrGetAccountSidAndDomain;
|
|
CHECK_HRESULT(hr);
|
|
goto Clean1;
|
|
}
|
|
|
|
//
|
|
// If the password is NULL, this task is meant to be run
|
|
// without prompting the user for credentials
|
|
//
|
|
if (pwszPassword == NULL)
|
|
{
|
|
DWORD dwError = NO_ERROR;
|
|
|
|
do // Not a loop. Error break out.
|
|
{
|
|
//
|
|
// If the caller has a restricted token (e.g., an ActiveX
|
|
// control), it's not allowed to use a NULL password.
|
|
//
|
|
if (IsTokenRestricted(hToken))
|
|
{
|
|
dwError = ERROR_ACCESS_DENIED;
|
|
schDebugOut((DEB_ERROR, "Restricted token tried to set NULL "
|
|
"password for %ws. Denying access.\n", pwszJobName));
|
|
break;
|
|
}
|
|
|
|
//
|
|
// To set credentials for the job, the caller must have write
|
|
// access to the job file.
|
|
//
|
|
HANDLE hFile;
|
|
hr = OpenFileWithRetry(wszJobPath, GENERIC_WRITE, FILE_SHARE_WRITE, &hFile);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR_OUT("SASetAccountInformation: caller's open of task file", hr);
|
|
break;
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
|
|
//
|
|
// Unless the task is being set to run as LocalSystem, a NULL
|
|
// password means that the task must be scheduled to run only
|
|
// if the user is logged on, so make sure that flag is set in
|
|
// that case
|
|
//
|
|
// An account name of "" signifies the local system account.
|
|
//
|
|
BOOL fIsAccountLocalSystem = (pwszAccount[0] == L'\0');
|
|
|
|
if (!fIsAccountLocalSystem
|
|
&&
|
|
!(dwJobFlags & TASK_FLAG_RUN_ONLY_IF_LOGGED_ON))
|
|
{
|
|
schDebugOut((DEB_ERROR, "SetAccountInformation with NULL "
|
|
"password is only supported for LocalSystem "
|
|
"account or for job with "
|
|
"TASK_FLAG_RUN_ONLY_IF_LOGGED_ON\n",
|
|
pwszJobName));
|
|
hr = SCHED_E_UNSUPPORTED_ACCOUNT_OPTION;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The caller must be either LocalSystem, an administrator or
|
|
// the user named in pwszAccount (the latter being the most
|
|
// common case. CODEWORK - rearrange to optimize for that case?)
|
|
//
|
|
|
|
BOOL fIsCallerLocalSystem;
|
|
SID LocalSystemSid = {SID_REVISION,
|
|
1,
|
|
SECURITY_NT_AUTHORITY,
|
|
SECURITY_LOCAL_SYSTEM_RID };
|
|
|
|
if (!CheckTokenMembership(hToken,
|
|
&LocalSystemSid,
|
|
&fIsCallerLocalSystem))
|
|
{
|
|
dwError = GetLastError();
|
|
ERR_OUT("CheckTokenMembership", dwError);
|
|
// translate this to E_UNEXPECTED?
|
|
break;
|
|
}
|
|
|
|
if (fIsCallerLocalSystem || IsThreadCallerAnAdmin(hToken))
|
|
{
|
|
//
|
|
// (success)
|
|
//
|
|
break;
|
|
}
|
|
|
|
if (fIsAccountLocalSystem)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
|
|
schDebugOut((DEB_ERROR, "Non-system, non-admin tried "
|
|
"to schedule task as LocalSystem\n"));
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Compare the caller's token with the account's SID
|
|
//
|
|
BOOL fIsCallerAccount;
|
|
if (!CheckTokenMembership(hToken,
|
|
pAccountSid,
|
|
&fIsCallerAccount))
|
|
{
|
|
dwError = GetLastError();
|
|
ERR_OUT("CheckTokenMembership", dwError);
|
|
// translate this to E_UNEXPECTED?
|
|
break;
|
|
}
|
|
|
|
if (! fIsCallerAccount)
|
|
{
|
|
schDebugOut((DEB_ERROR, "Caller is neither LocalSystem "
|
|
"nor admin nor the named account\n"));
|
|
hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
|
|
}
|
|
|
|
//
|
|
// else success -- the caller is the named account
|
|
//
|
|
} while (0);
|
|
|
|
if (dwError != NO_ERROR)
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(dwError);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
else
|
|
{
|
|
schDebugOut((DEB_TRACE, "Saving NULL password for %ws\n", pwszJobName));
|
|
}
|
|
|
|
// end of NULL password stuff
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Verify that the credentials entered actually work.
|
|
// This prevents someone from scheduling jobs for a valid account with an invalid password
|
|
// and causing the credential database to be updated with the bad password.
|
|
// It also prevents someone from creating lots of bogus jobs.
|
|
//
|
|
if (!ValidateRunAs(pwszAccount, wszDomain, pwszPassword))
|
|
{
|
|
hr = E_ACCESSDENIED;
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
|
|
Clean1:
|
|
//
|
|
// Close the handle to the thread token
|
|
//
|
|
CloseHandle(hToken);
|
|
|
|
Clean0:
|
|
//
|
|
// End impersonation.
|
|
//
|
|
if ((RpcStatus = RpcRevertToSelf()) != RPC_S_OK)
|
|
{
|
|
ERR_OUT("RpcRevertToSelf", RpcStatus);
|
|
schAssert(!"RpcRevertToSelf failed");
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Write the credentials to the database
|
|
// If given a UPN, save "" for the domain and the entire UPN for the user.
|
|
// Treat the account name as a UPN if it lacks a \ and has an @.
|
|
// Otherwise, treat it as a SAM name.
|
|
//
|
|
BOOL fUpn = (wcschr(pwszAccount, L'\\') == NULL && wcschr(pwszAccount, L'@') != NULL);
|
|
|
|
//
|
|
// Retrieve the original creds and compare with the ones we're about to save
|
|
// in order to determine if just the password is being updated. If so, notify LSA.
|
|
// There's no need to do any of this for local system, and we also shouldn't do this
|
|
// if the job is flagged to run only if logged on, as the NULL password supplied in
|
|
// this case is not really the user's password. We can exclude both cases by testing
|
|
// for a non-NULL password as there is no other situation where a NULL password will
|
|
// be allowed. Blank passwords are legit, but they are non-NULL and therefore OK.
|
|
//
|
|
if (pwszPassword)
|
|
{
|
|
JOB_CREDENTIALS jc;
|
|
hr = GetAccountInformation(wszJobPath, &jc);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if ((lstrcmpiW(jc.wszAccount, fUpn ? pwszAccount : SkipDomainName(pwszAccount)) == 0) &&
|
|
(lstrcmpiW(jc.wszPassword, pwszPassword) != 0))
|
|
{
|
|
NotifyLsaOfPasswordChange(fUpn ? pwszAccount : SkipDomainName(pwszAccount),
|
|
fUpn ? L"" : wszDomain,
|
|
pwszPassword);
|
|
}
|
|
ZERO_PASSWORD(jc.wszPassword);
|
|
}
|
|
}
|
|
|
|
hr = SaveJobCredentials(
|
|
wszJobPath,
|
|
fUpn ? pwszAccount : SkipDomainName(pwszAccount),
|
|
fUpn ? L"" : wszDomain,
|
|
pwszPassword,
|
|
pAccountSid
|
|
);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetAccountSidAndDomain
|
|
//
|
|
// Synopsis: Gets the SID and Domain of an account.
|
|
// This was factored out of SASetAccountInformation() above, because this is a
|
|
// task that now needs to be performed in more than one place, and I did not
|
|
// wish to duplicate code.
|
|
//
|
|
// Arguments:
|
|
// IN LPCWSTR pwszAccount -- account to look up
|
|
// IN OUT PSID pAccountSid -- pointer to buffer to receive SID
|
|
// IN DWORD cbAccountSid -- size of buffer
|
|
// IN OUT LPWSTR pwszDomain -- pointer to buffer to receive domain
|
|
// IN DWORD ccDomain -- size of buffer
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
GetAccountSidAndDomain(
|
|
LPCWSTR pwszAccount,
|
|
PSID pAccountSid,
|
|
DWORD cbAccountSid,
|
|
LPWSTR pwszDomain,
|
|
DWORD ccDomain)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (pwszAccount == NULL || pAccountSid == NULL || pwszDomain == NULL)
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
//
|
|
// An account name of "" signifies the local system account.
|
|
//
|
|
BOOL fIsAccountLocalSystem = (pwszAccount[0] == L'\0');
|
|
|
|
//
|
|
// Get the account's SID
|
|
//
|
|
if (fIsAccountLocalSystem)
|
|
{
|
|
SID LocalSystemSid = {SID_REVISION,
|
|
1,
|
|
SECURITY_NT_AUTHORITY,
|
|
SECURITY_LOCAL_SYSTEM_RID };
|
|
|
|
if (!CopySid(cbAccountSid, pAccountSid, &LocalSystemSid))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
return hr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Treat the account name as a UPN if it lacks a \ and has an @.
|
|
// Otherwise, treat it as a SAM name.
|
|
//
|
|
BOOL fUpn = (wcschr(pwszAccount, L'\\') == NULL && wcschr(pwszAccount, L'@') != NULL);
|
|
schDebugOut((DEB_TRACE, "Name '%S' is a %s name\n", pwszAccount, fUpn ? "UPN" : "SAM"));
|
|
|
|
LPWSTR pwszSamName;
|
|
|
|
if (fUpn)
|
|
{
|
|
//
|
|
// Get the SAM name, so we can call LookupAccountName
|
|
//
|
|
DWORD dwErr = SchedUPNToAccountName(pwszAccount, &pwszSamName);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
CHECK_HRESULT(hr);
|
|
return hr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pwszSamName = (LPWSTR) pwszAccount;
|
|
}
|
|
|
|
DWORD ccDomain = MAX_DOMAINNAME + 1;
|
|
WCHAR wszDomain[MAX_DOMAINNAME + 1] = L"";
|
|
SID_NAME_USE snu;
|
|
|
|
if (!LookupAccountNameWrap(NULL,
|
|
pwszSamName,
|
|
pAccountSid,
|
|
&cbAccountSid,
|
|
pwszDomain,
|
|
&ccDomain,
|
|
&snu))
|
|
{
|
|
CHECK_HRESULT(_HRESULT_FROM_WIN32(GetLastError()));
|
|
hr = SCHED_E_ACCOUNT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
if (fUpn)
|
|
{
|
|
delete pwszSamName;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
schAssert(IsValidSid(pAccountSid));
|
|
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetNSAccountSid
|
|
//
|
|
// Synopsis: Gets the SID of the account set to be used with the Net Schedule API (AT command).
|
|
//
|
|
// Arguments:
|
|
// IN OUT PSID pAccountSid -- pointer to buffer to receive SID
|
|
// IN DWORD cbAccountSid -- size of buffer
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT
|
|
GetNSAccountSid(
|
|
PSID pAccountSid,
|
|
DWORD cbAccountSid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (pAccountSid == NULL)
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
//
|
|
// get the name of the AT service account
|
|
//
|
|
DWORD cchAccount = MAX_USERNAME + 1;
|
|
WCHAR wszAccount[MAX_USERNAME + 1];
|
|
hr = SAGetNSAccountInformation(NULL, cchAccount, wszAccount);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
//
|
|
// Get the account's SID
|
|
//
|
|
DWORD ccDomain = MAX_DOMAINNAME + 1;
|
|
WCHAR wszDomain[MAX_DOMAINNAME + 1] = L"";
|
|
hr = GetAccountSidAndDomain(wszAccount, pAccountSid, cbAccountSid, wszDomain, ccDomain);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SaveJobCredentials
|
|
//
|
|
// Synopsis: Writes the job credentials to the credential database
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
SaveJobCredentials(
|
|
LPCWSTR pwszJobPath,
|
|
LPCWSTR pwszAccount,
|
|
LPCWSTR pwszDomain,
|
|
LPCWSTR pwszPassword,
|
|
PSID pAccountSid
|
|
)
|
|
{
|
|
BYTE rgbIdentity[HASH_DATA_SIZE];
|
|
BYTE rgbHashedAccountSid[HASH_DATA_SIZE] = { 0 };
|
|
RC2_KEY_INFO RC2KeyInfo;
|
|
HRESULT hr;
|
|
DWORD cbSAI;
|
|
DWORD cbSAC;
|
|
DWORD cbCredentialNew;
|
|
DWORD cbEncryptedData;
|
|
DWORD CredentialIndexNew, CredentialIndexPrev;
|
|
BYTE * pbEncryptedData;
|
|
BYTE * pbFoundIdentity;
|
|
BYTE * pbIdentitySet;
|
|
BYTE * pbCredentialNew = NULL;
|
|
BYTE * pbSAI = NULL;
|
|
BYTE * pbSAC = NULL;
|
|
HCRYPTPROV hCSP = NULL;
|
|
|
|
//
|
|
// Obtain a provider handle to the CSP (for use with Crypto API).
|
|
//
|
|
|
|
hr = GetCSPHandle(&hCSP);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
return(hr);
|
|
}
|
|
|
|
//
|
|
// Hash the job into a unique identity.
|
|
//
|
|
|
|
hr = HashJobIdentity(hCSP, pwszJobPath, rgbIdentity);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CloseCSPHandle(hCSP);
|
|
return(hr);
|
|
}
|
|
|
|
//
|
|
// Store a NULL password by flipping the last bit of the hash data.
|
|
//
|
|
|
|
if (pwszPassword == NULL)
|
|
{
|
|
LAST_HASH_BYTE(rgbIdentity) ^= 1;
|
|
}
|
|
|
|
//
|
|
// Guard SA security database access.
|
|
//
|
|
|
|
EnterCriticalSection(&gcsSSCritSection);
|
|
|
|
//
|
|
// Generate the encryption key & encrypt the account information passed.
|
|
//
|
|
|
|
hr = ComputeCredentialKey(hCSP, &RC2KeyInfo);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
hr = EncryptCredentials(RC2KeyInfo,
|
|
pwszAccount,
|
|
pwszDomain,
|
|
pwszPassword,
|
|
pAccountSid,
|
|
&cbEncryptedData,
|
|
&pbEncryptedData);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Read SAI & SAC databases.
|
|
//
|
|
|
|
hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Check whether we will be in danger of exceeding the max secret size.
|
|
// We don't know at this time whether we'll be increasing the size of the
|
|
// secret as the data may already be present, but if it does need to be added,
|
|
// the calculation below will show if the size will be over the limit. If so,
|
|
// do a scavenge operation first as a precaution to remove all unused data,
|
|
// then reread the db.
|
|
//
|
|
if ((cbSAI + sizeof(DWORD) + HASH_DATA_SIZE) > MAX_SECRET_SIZE ||
|
|
(cbSAC + sizeof(DWORD) + HASH_DATA_SIZE + cbEncryptedData) > MAX_SECRET_SIZE)
|
|
{
|
|
if (pbSAI != NULL) LocalFree(pbSAI);
|
|
if (pbSAC != NULL) LocalFree(pbSAC);
|
|
pbSAI = pbSAC = NULL;
|
|
|
|
ScavengeSASecurityDBase();
|
|
|
|
hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC);
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if the identity exists in the SAI.
|
|
// (Note, SAIFindIdentity ignores the last bit of the hash data
|
|
// when searching for a match.)
|
|
//
|
|
|
|
hr = SAIFindIdentity(rgbIdentity,
|
|
cbSAI,
|
|
pbSAI,
|
|
&CredentialIndexPrev,
|
|
NULL,
|
|
&pbFoundIdentity,
|
|
NULL,
|
|
&pbIdentitySet);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Check if the caller-specified credentials already exist in the SAC.
|
|
// Ensure also, if the credentials exist, that the caller has access.
|
|
//
|
|
|
|
hr = CredentialLookupAndAccessCheck(hCSP,
|
|
pAccountSid,
|
|
cbSAC,
|
|
pbSAC,
|
|
&CredentialIndexNew,
|
|
rgbHashedAccountSid,
|
|
&cbCredentialNew,
|
|
&pbCredentialNew);
|
|
|
|
if (FAILED(hr) && hr != SCHED_E_ACCOUNT_INFORMATION_NOT_SET)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (pbFoundIdentity == NULL)
|
|
{
|
|
//
|
|
// This job is new to the SAI. That is, there are no credentials
|
|
// associated with this job yet.
|
|
//
|
|
|
|
if (pbCredentialNew != NULL)
|
|
{
|
|
//
|
|
// If the credentials the caller specified already exist in the
|
|
// SAC, use them. Note, we've already established the caller
|
|
// has permission to use them.
|
|
//
|
|
// Insert the job identity into the SAI identity set associated
|
|
// with this credential.
|
|
//
|
|
|
|
hr = SAIIndexIdentity(cbSAI,
|
|
pbSAI,
|
|
CredentialIndexNew,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&pbIdentitySet);
|
|
|
|
if (hr == S_FALSE)
|
|
{
|
|
//
|
|
// The SAC & SAI databases are out of sync.
|
|
// Should *never* occur. Logic on exit handles this.
|
|
//
|
|
|
|
ASSERT_SECURITY_DBASE_CORRUPT();
|
|
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
|
|
goto ErrorExit;
|
|
}
|
|
else if (SUCCEEDED(hr))
|
|
{
|
|
hr = SAIInsertIdentity(rgbIdentity,
|
|
pbIdentitySet,
|
|
&cbSAI,
|
|
&pbSAI);
|
|
CHECK_HRESULT(hr);
|
|
|
|
if (SUCCEEDED(hr) && pwszPassword != NULL)
|
|
{
|
|
//
|
|
// Simply change of existing credentials (password change).
|
|
// If we're setting a NULL password, we're setting it for
|
|
// this job alone, and we don't need to touch the SAC.
|
|
// If we're setting a non-NULL password, we're setting it
|
|
// for all jobs in this account, and we need to update the
|
|
// SAC credential in-place.
|
|
//
|
|
|
|
hr = SACUpdateCredential(cbEncryptedData,
|
|
pbEncryptedData,
|
|
cbCredentialNew,
|
|
pbCredentialNew,
|
|
&cbSAC,
|
|
&pbSAC);
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The credentials didn't exist in the SAC.
|
|
//
|
|
// Append new credentials to the SAC & append the new job
|
|
// identity to the SAI. As a result, the identity will be
|
|
// associated with the new credentials.
|
|
//
|
|
|
|
hr = SACAddCredential(rgbHashedAccountSid,
|
|
cbEncryptedData,
|
|
pbEncryptedData,
|
|
&cbSAC,
|
|
&pbSAC);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
hr = SAIAddIdentity(rgbIdentity, &cbSAI, &pbSAI);
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Account change for an existing job's credentials.
|
|
//
|
|
// Ensure the caller has permission to change account information.
|
|
// Do so by verifying caller access to the existing credentials.
|
|
//
|
|
|
|
DWORD cbCredentialPrev;
|
|
BYTE * pbCredentialPrev;
|
|
|
|
hr = SACIndexCredential(CredentialIndexPrev,
|
|
cbSAC,
|
|
pbSAC,
|
|
&cbCredentialPrev,
|
|
&pbCredentialPrev);
|
|
|
|
if (hr == S_FALSE)
|
|
{
|
|
//
|
|
// Credential not found? The SAC & SAI databases are out of sync.
|
|
// This should *never* occur. Logic on exit handles this.
|
|
//
|
|
|
|
ASSERT_SECURITY_DBASE_CORRUPT();
|
|
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
|
|
goto ErrorExit;
|
|
}
|
|
else if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Only check the credentials if we're dealing with a non-NULL password
|
|
//
|
|
if (pwszPassword != NULL)
|
|
{
|
|
//
|
|
// pbCredentialPrev points to the start of the credential identity.
|
|
//
|
|
|
|
if (!CredentialAccessCheck(hCSP,
|
|
pbCredentialPrev))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
if ((pbCredentialNew != NULL) &&
|
|
(CredentialIndexPrev != CredentialIndexNew))
|
|
{
|
|
//
|
|
// The credentials the caller wishes to use already exist in the
|
|
// SAC, yet it differs from the previous.
|
|
//
|
|
// Remove the job identity from its existing SAI position
|
|
// (associated with the previous credentials) and relocate
|
|
// to be associated with the new credentials.
|
|
//
|
|
// SAIRemoveIdentity could result in removal of the associated
|
|
// credential, if this was the last identity associated with it.
|
|
// Save away the original SAC size to see if we must fix up the
|
|
// new credential index on remove.
|
|
//
|
|
|
|
DWORD cbSACOrg = cbSAC;
|
|
|
|
hr = SAIRemoveIdentity(pbFoundIdentity,
|
|
pbIdentitySet,
|
|
&cbSAI,
|
|
&pbSAI,
|
|
CredentialIndexPrev,
|
|
&cbSAC,
|
|
&pbSAC);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (cbSACOrg != cbSAC)
|
|
{
|
|
//
|
|
// The new credential index must be adjusted.
|
|
//
|
|
|
|
if (CredentialIndexNew > CredentialIndexPrev)
|
|
{
|
|
CredentialIndexNew--;
|
|
}
|
|
}
|
|
|
|
hr = SAIIndexIdentity(cbSAI,
|
|
pbSAI,
|
|
CredentialIndexNew,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&pbIdentitySet); // [out] ptr.
|
|
|
|
if (hr == S_FALSE)
|
|
{
|
|
//
|
|
// The SAC & SAI databases are out of sync. This should
|
|
// *never* occur. Logic on exit handles this.
|
|
//
|
|
|
|
ASSERT_SECURITY_DBASE_CORRUPT();
|
|
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
|
|
goto ErrorExit;
|
|
}
|
|
else if (SUCCEEDED(hr))
|
|
{
|
|
hr = SAIInsertIdentity(rgbIdentity,
|
|
pbIdentitySet,
|
|
&cbSAI,
|
|
&pbSAI);
|
|
CHECK_HRESULT(hr);
|
|
|
|
if (SUCCEEDED(hr) && pwszPassword != NULL)
|
|
{
|
|
//
|
|
// Update the existing credentials if the user has
|
|
// specified a non-NULL password.
|
|
//
|
|
// First, re-index the credential since the remove
|
|
// above may have altered SAC content.
|
|
//
|
|
|
|
hr = SACIndexCredential(CredentialIndexNew,
|
|
cbSAC,
|
|
pbSAC,
|
|
&cbCredentialNew,
|
|
&pbCredentialNew);
|
|
|
|
if (hr == S_FALSE)
|
|
{
|
|
//
|
|
// Something is terribly wrong. This should *never*
|
|
// occur. Logic on exit handles this.
|
|
//
|
|
|
|
ASSERT_SECURITY_DBASE_CORRUPT();
|
|
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
|
|
goto ErrorExit;
|
|
}
|
|
else if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
hr = SACUpdateCredential(cbEncryptedData,
|
|
pbEncryptedData,
|
|
cbCredentialNew,
|
|
pbCredentialNew,
|
|
&cbSAC,
|
|
&pbSAC);
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
else if (pbCredentialNew == NULL)
|
|
{
|
|
//
|
|
// The credentials the caller wishes to use do not exist in the
|
|
// SAC.
|
|
//
|
|
// Remove the job identity from its existing SAI position
|
|
// (associated with the previous credentials), then add both
|
|
// the new credentials and the identity to the SAC & SAI
|
|
// respectively. As a result, the identity will be associated
|
|
// with the new credentials.
|
|
//
|
|
|
|
//
|
|
// NB : This routine also removes the associated credential from
|
|
// the SAC if this was the last identity associated with it.
|
|
// Also, do not reference pbFoundIdentity & pbIdentitySet
|
|
// after this call, as they will be invalid.
|
|
//
|
|
|
|
hr = SAIRemoveIdentity(pbFoundIdentity,
|
|
pbIdentitySet,
|
|
&cbSAI,
|
|
&pbSAI,
|
|
CredentialIndexPrev,
|
|
&cbSAC,
|
|
&pbSAC);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Append the identity and the new credentials to the SAI and
|
|
// SAC respectively.
|
|
//
|
|
|
|
hr = SACAddCredential(rgbHashedAccountSid,
|
|
cbEncryptedData,
|
|
pbEncryptedData,
|
|
&cbSAC,
|
|
&pbSAC);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
hr = SAIAddIdentity(rgbIdentity, &cbSAI, &pbSAI);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Simply change of existing credentials (password change).
|
|
// If we're setting a NULL password, we're setting it for this job
|
|
// alone, and we don't need to touch the SAC. If we're setting a
|
|
// non-NULL password, we're setting it for all jobs in this
|
|
// account, and we need to update the SAC credential in-place.
|
|
//
|
|
|
|
if (pwszPassword != NULL)
|
|
{
|
|
hr = SACUpdateCredential(cbEncryptedData,
|
|
pbEncryptedData,
|
|
cbCredentialPrev,
|
|
pbCredentialPrev,
|
|
&cbSAC,
|
|
&pbSAC);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We also need to rewrite the SAI data, because if the password
|
|
// changed from NULL to non-NULL or vice versa, the last bit of
|
|
// the SAI data will have changed.
|
|
//
|
|
hr = SAIUpdateIdentity(rgbIdentity,
|
|
pbFoundIdentity,
|
|
cbSAI,
|
|
pbSAI);
|
|
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = WriteSecurityDBase(cbSAI, pbSAI, cbSAC, pbSAC);
|
|
CHECK_HRESULT(hr);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Grant the account batch privilege.
|
|
// We could choose to ignore the return code here, since the
|
|
// privilege can still be granted later; but if we ignored it,
|
|
// a caller might never know that the call failed until it was
|
|
// time to run the job, which is not good behavior. (See
|
|
// bug 366582)
|
|
//
|
|
|
|
//Also we should not assign any privilege/right to system account. Refer to bug 367263
|
|
SID LocalSystemSid = { SID_REVISION,
|
|
1,
|
|
SECURITY_NT_AUTHORITY,
|
|
SECURITY_LOCAL_SYSTEM_RID };
|
|
|
|
if(!EqualSid(&LocalSystemSid,pAccountSid)) {
|
|
hr = GrantAccountBatchPrivilege(pAccountSid);
|
|
}
|
|
}
|
|
}
|
|
|
|
ErrorExit:
|
|
if (pbSAI != NULL) LocalFree(pbSAI);
|
|
if (pbSAC != NULL) LocalFree(pbSAC);
|
|
if (hCSP != NULL) CloseCSPHandle(hCSP);
|
|
|
|
//
|
|
// Log an error & rest the SA security dbases SAI & SAC if corruption
|
|
// is detected.
|
|
//
|
|
|
|
if (hr == SCHED_E_ACCOUNT_DBASE_CORRUPT)
|
|
{
|
|
//
|
|
// Log an error.
|
|
//
|
|
|
|
LogServiceError(IERR_SECURITY_DBASE_CORRUPTION, 0,
|
|
IDS_HELP_HINT_DBASE_CORRUPT);
|
|
|
|
//
|
|
// Reset SAI & SAC by writing four bytes of zeros into each.
|
|
// Ignore the return code. No recourse if this fails.
|
|
//
|
|
|
|
DWORD dwZero = 0;
|
|
WriteSecurityDBase(sizeof(dwZero), (BYTE *)&dwZero, sizeof(dwZero),
|
|
(BYTE *)&dwZero);
|
|
}
|
|
|
|
LeaveCriticalSection(&gcsSSCritSection);
|
|
|
|
return(hr);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// RPC: SASetNSAccountInformation
|
|
//
|
|
// Synopsis: Configure the NetSchedule account.
|
|
//
|
|
// Arguments: [Handle] -- Unused.
|
|
// [pwszAccount] -- Account name. If NULL, reset the credential
|
|
// information to zero.
|
|
// [pwszPassword] -- Account password.
|
|
//
|
|
// Returns: S_OK -- Operation successful.
|
|
// HRESULT -- Error.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
SASetNSAccountInformation(
|
|
SASEC_HANDLE Handle,
|
|
LPCWSTR pwszAccount,
|
|
LPCWSTR pwszPassword)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
RPC_STATUS RpcStatus;
|
|
|
|
//
|
|
// If not done so already, initialize the DWORD global data element to be
|
|
// used in generation of the encryption key. It's possible this hasn't
|
|
// been performed yet.
|
|
//
|
|
|
|
if (!gdwKeyElement)
|
|
{
|
|
//
|
|
// NB : This routine enters (and leaves) the gcsSSCritSection
|
|
// critical section.
|
|
//
|
|
|
|
SetMysteryDWORDValue();
|
|
}
|
|
|
|
//
|
|
// The RPC caller must be an administrator to perform this function.
|
|
//
|
|
// Impersonate the caller.
|
|
//
|
|
|
|
if ((RpcStatus = RpcImpersonateClient(NULL)) != RPC_S_OK)
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(RpcStatus);
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
|
|
if (! IsThreadCallerAnAdmin(NULL))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
|
|
}
|
|
|
|
//
|
|
// End impersonation.
|
|
//
|
|
|
|
if ((RpcStatus = RpcRevertToSelf()) != RPC_S_OK)
|
|
{
|
|
//
|
|
// BUGBUG : What to do if the impersonation revert fails?
|
|
//
|
|
|
|
hr = _HRESULT_FROM_WIN32(RpcStatus);
|
|
CHECK_HRESULT(hr);
|
|
schAssert(!"Couldn't revert to self");
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
return(hr);
|
|
}
|
|
|
|
if (pwszPassword && wcslen(pwszPassword) > REAL_PWLEN)
|
|
return _HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
|
|
|
//
|
|
// Privilege level check above succeeded if we've gotten to this point.
|
|
//
|
|
// Retrieve the SID of the account name specified.
|
|
//
|
|
|
|
RC2_KEY_INFO RC2KeyInfo;
|
|
BYTE pbAccountSid[MAX_SID_SIZE];
|
|
PSID pAccountSid = NULL;
|
|
WCHAR wszDomain[MAX_DOMAINNAME + 1] = L"";
|
|
DWORD cbAccountSid = MAX_SID_SIZE;
|
|
DWORD ccDomain = MAX_DOMAINNAME + 1;
|
|
DWORD dwZero = 0;
|
|
DWORD cbEncryptedData = 0;
|
|
BYTE * pbEncryptedData = NULL;
|
|
SID_NAME_USE snu;
|
|
HCRYPTPROV hCSP = NULL;
|
|
|
|
if (pwszAccount != NULL)
|
|
{
|
|
if (!LookupAccountName(NULL,
|
|
pwszAccount,
|
|
pbAccountSid,
|
|
&cbAccountSid,
|
|
wszDomain,
|
|
&ccDomain,
|
|
&snu))
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
return(SCHED_E_ACCOUNT_NAME_NOT_FOUND);
|
|
}
|
|
|
|
pAccountSid = pbAccountSid;
|
|
pwszAccount = SkipDomainName(pwszAccount);
|
|
|
|
//
|
|
// Verify that the credentials entered actually work.
|
|
// Also note that for NetSchedule jobs, there is no TASK_FLAG_RUN_ONLY_IF_LOGGED_ON,
|
|
// so if a NULL password is entered that mean the password really is supposed to be NULL.
|
|
//
|
|
if (!ValidateRunAs(pwszAccount, wszDomain, pwszPassword))
|
|
{
|
|
hr = E_ACCESSDENIED;
|
|
CHECK_HRESULT(hr);
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Retrieve the original creds and compare with the ones we're about to save
|
|
// in order to determine if just the password is being updated. If so, notify LSA.
|
|
//
|
|
JOB_CREDENTIALS jc;
|
|
hr = GetNSAccountInformation(&jc);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if ((lstrcmpiW(jc.wszAccount, pwszAccount) == 0) &&
|
|
(lstrcmpiW(jc.wszPassword, pwszPassword) != 0))
|
|
{
|
|
NotifyLsaOfPasswordChange(pwszAccount, wszDomain, pwszPassword);
|
|
}
|
|
ZERO_PASSWORD(jc.wszPassword);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Guard SA security database access.
|
|
//
|
|
|
|
EnterCriticalSection(&gcsSSCritSection);
|
|
|
|
if (pwszAccount == NULL)
|
|
{
|
|
//
|
|
// zero the cred info out to indicate LocalSystem
|
|
//
|
|
hr = WriteLsaData(sizeof(WSZ_SANSC), WSZ_SANSC, sizeof(dwZero),
|
|
(BYTE *)&dwZero);
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Obtain a provider handle to the CSP (for use with Crypto API).
|
|
//
|
|
|
|
hr = GetCSPHandle(&hCSP);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Generate the encryption key & encrypt the account information
|
|
// passed.
|
|
//
|
|
|
|
hr = ComputeCredentialKey(hCSP, &RC2KeyInfo);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
hr = EncryptCredentials(RC2KeyInfo,
|
|
pwszAccount,
|
|
wszDomain,
|
|
pwszPassword,
|
|
pAccountSid,
|
|
&cbEncryptedData,
|
|
&pbEncryptedData);
|
|
|
|
// Clear key content.
|
|
//
|
|
SecureZeroMemory(&RC2KeyInfo, sizeof(RC2KeyInfo));
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
hr = WriteLsaData(sizeof(WSZ_SANSC), WSZ_SANSC, cbEncryptedData,
|
|
pbEncryptedData);
|
|
|
|
delete [] pbEncryptedData;
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Grant the account batch privilege.
|
|
// We could choose to ignore the return code here, since the
|
|
// privilege can still be granted later; but if we ignored it,
|
|
// a caller might never know that the call failed until it was
|
|
// time to run the job, which is not good behavior. (See
|
|
// bug 366582)
|
|
//
|
|
if (pAccountSid != NULL)
|
|
{
|
|
hr = GrantAccountBatchPrivilege(pAccountSid);
|
|
}
|
|
|
|
ErrorExit:
|
|
LeaveCriticalSection(&gcsSSCritSection);
|
|
|
|
if (hCSP != NULL) CloseCSPHandle(hCSP);
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// RPC: SAGetNSAccountInformation
|
|
//
|
|
// Synopsis: Retrieve the NetSchedule account name.
|
|
//
|
|
// Arguments: [Handle] --
|
|
// [ccBufferSize] --
|
|
// [wszBuffer] --
|
|
//
|
|
// Returns: S_OK -- Operation successful.
|
|
// S_FALSE -- No account specified.
|
|
// HRESULT -- Error.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
SAGetNSAccountInformation(
|
|
SASEC_HANDLE Handle,
|
|
DWORD ccBufferSize,
|
|
WCHAR wszBuffer[])
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Verify that caller has permission before proceeding any further
|
|
//
|
|
schAssert(g_TasksFolderInfo.ptszPath != NULL);
|
|
if (FAILED(hr = RPCFolderAccessCheck(g_TasksFolderInfo.ptszPath, FILE_READ_DATA, HandleImpersonation)))
|
|
return hr;
|
|
|
|
//
|
|
// Check for invalid params
|
|
//
|
|
if (!wszBuffer)
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
//
|
|
// Retrieve the NetSchedule credentials, but return only the account name.
|
|
//
|
|
JOB_CREDENTIALS jc;
|
|
hr = GetNSAccountInformation(&jc);
|
|
|
|
if (SUCCEEDED(hr) && hr != S_FALSE)
|
|
{
|
|
ZERO_PASSWORD(jc.wszPassword); // Not needed; NULL handled.
|
|
|
|
if (ccBufferSize > (jc.ccAccount + 1 + jc.ccDomain))
|
|
{
|
|
StringCchCopy(wszBuffer, ccBufferSize, jc.wszDomain);
|
|
StringCchCat(wszBuffer, ccBufferSize, L"\\");
|
|
StringCchCat(wszBuffer, ccBufferSize, jc.wszAccount);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Should *never* occur.
|
|
//
|
|
|
|
hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Note that LocalSystem accounts will be returned under the S_FALSE condition;
|
|
// set the buffer to the empty string to reflect this
|
|
//
|
|
if (S_FALSE == hr)
|
|
{
|
|
StringCchCopy(wszBuffer, ccBufferSize, L"");
|
|
}
|
|
}
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetNSAccountInformation
|
|
//
|
|
// Synopsis: Retrieve the NetSchedule account credentials.
|
|
//
|
|
// Arguments: [pjc] -- Returned credentials.
|
|
//
|
|
// Returns: S_OK -- Operation successful.
|
|
// S_FALSE -- No account specified.
|
|
// HRESULT -- Error.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
GetNSAccountInformation(
|
|
PJOB_CREDENTIALS pjc)
|
|
{
|
|
RC2_KEY_INFO RC2KeyInfo;
|
|
DWORD cbEncryptedData = 0;
|
|
BYTE * pbEncryptedData = NULL;
|
|
HCRYPTPROV hCSP = NULL;
|
|
HRESULT hr;
|
|
|
|
//
|
|
// If not done so already, initialize the DWORD global data element to be
|
|
// used in generation of the encryption key. It's possible this hasn't
|
|
// been performed yet.
|
|
//
|
|
|
|
if (!gdwKeyElement)
|
|
{
|
|
//
|
|
// NB : This routine enters (and leaves) the gcsSSCritSection
|
|
// critical section.
|
|
//
|
|
|
|
SetMysteryDWORDValue();
|
|
}
|
|
|
|
//
|
|
// Guard SA security database access.
|
|
//
|
|
|
|
EnterCriticalSection(&gcsSSCritSection);
|
|
|
|
//
|
|
// Read SAI & SAC databases.
|
|
//
|
|
|
|
hr = ReadLsaData(sizeof(WSZ_SANSC), WSZ_SANSC, &cbEncryptedData,
|
|
&pbEncryptedData);
|
|
|
|
if (FAILED(hr) || hr == S_FALSE)
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
else if (cbEncryptedData <= sizeof(DWORD))
|
|
{
|
|
//
|
|
// The information was specified previously but has been reset since.
|
|
//
|
|
// NOTE: This will be the case if the value has been reset back to LocalSystem,
|
|
// as it merely stores a dword = 0x00000000 in that case
|
|
//
|
|
hr = S_FALSE;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Obtain a provider handle to the CSP (for use with Crypto API).
|
|
//
|
|
|
|
hr = GetCSPHandle(&hCSP);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Generate key & decrypt the credentials.
|
|
//
|
|
|
|
hr = ComputeCredentialKey(hCSP, &RC2KeyInfo);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// *** Important ***
|
|
//
|
|
// The encrypted credentials passed are decrypted *in-place*.
|
|
// The decrypted data must be zeroed immediately following decryption
|
|
// (even in a failure case).
|
|
//
|
|
|
|
hr = DecryptCredentials(RC2KeyInfo,
|
|
cbEncryptedData,
|
|
pbEncryptedData,
|
|
pjc);
|
|
|
|
// Don't leave the plain-text password on the heap.
|
|
//
|
|
SecureZeroMemory(pbEncryptedData, cbEncryptedData);
|
|
|
|
// Clear key content.
|
|
//
|
|
SecureZeroMemory(&RC2KeyInfo, sizeof(RC2KeyInfo));
|
|
}
|
|
|
|
ErrorExit:
|
|
LeaveCriticalSection(&gcsSSCritSection);
|
|
|
|
if (pbEncryptedData != NULL) LocalFree(pbEncryptedData);
|
|
|
|
if (hCSP != NULL) CloseCSPHandle(hCSP);
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// RPC: SAGetAccountInformation
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [pwszJobName] -- Relative job name. eg: MyJob.job.
|
|
// [ccBufferSize] --
|
|
// [wszBuffer] --
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
SAGetAccountInformation(
|
|
SASEC_HANDLE Handle,
|
|
LPCWSTR pwszJobName,
|
|
DWORD ccBufferSize,
|
|
WCHAR wszBuffer[])
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// we're going to do the access check in two stages,
|
|
// first make sure that the principal is allowed to
|
|
// do any scheduling whatsoever - later on, we'll
|
|
// check permissions on the specific file in question
|
|
if (FAILED(hr = RPCFolderAccessCheck(g_TasksFolderInfo.ptszPath, FILE_READ_DATA, HandleImpersonation)))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Check for invalid params
|
|
//
|
|
if (pwszJobName == NULL || wszBuffer == NULL)
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
//
|
|
// Disallow files outside the tasks folder
|
|
//
|
|
if (wcschr(pwszJobName, L'\\') || wcschr(pwszJobName, L'/'))
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
//
|
|
// Append the job name to the local Task's folder path.
|
|
//
|
|
WCHAR wszJobPath[MAX_PATH + 1];
|
|
schAssert(g_TasksFolderInfo.ptszPath != NULL);
|
|
if ((wcslen(g_TasksFolderInfo.ptszPath) + 1 + wcslen(pwszJobName) + 1) > (MAX_PATH + 1))
|
|
{
|
|
CHECK_HRESULT(SCHED_E_CANNOT_OPEN_TASK);
|
|
return(SCHED_E_CANNOT_OPEN_TASK);
|
|
}
|
|
|
|
StringCchCopy(wszJobPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath);
|
|
StringCchCat(wszJobPath, MAX_PATH + 1, L"\\");
|
|
StringCchCat(wszJobPath, MAX_PATH + 1, pwszJobName);
|
|
|
|
//
|
|
// Verify that caller has permission before proceeding any further
|
|
//
|
|
if (FAILED(hr = RPCFolderAccessCheck(wszJobPath, FILE_READ_DATA, HandleImpersonation)))
|
|
return hr;
|
|
|
|
//
|
|
// Retrieve the job's credentials, but return only the account name.
|
|
//
|
|
JOB_CREDENTIALS jc;
|
|
hr = GetAccountInformation(wszJobPath, &jc);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ZERO_PASSWORD(jc.wszPassword); // Not needed; NULL handled.
|
|
|
|
if (ccBufferSize > (jc.ccAccount + 1 + jc.ccDomain))
|
|
{
|
|
//
|
|
// If the job was scheduled to run in the LocalSystem account,
|
|
// Accountname is the empty string
|
|
//
|
|
if (jc.wszAccount[0] == L'\0')
|
|
{
|
|
wszBuffer[0] = L'\0';
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If the account was supplied as a UPN, DomainName is
|
|
// the empty string
|
|
//
|
|
StringCchCopy(wszBuffer, ccBufferSize, jc.wszDomain);
|
|
if (wszBuffer[0] != L'\0')
|
|
{
|
|
StringCchCat(wszBuffer, ccBufferSize, L"\\");
|
|
}
|
|
StringCchCat(wszBuffer, ccBufferSize, jc.wszAccount);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Should *never* occur.
|
|
//
|
|
|
|
hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
}
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetAccountInformation
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [pwszJobPath] -- Fully qualified job path.
|
|
// eg: D:\NT\Tasks\MyJob.job.
|
|
// [pjc] --
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
GetAccountInformation(
|
|
LPCWSTR pwszJobPath,
|
|
PJOB_CREDENTIALS pjc)
|
|
{
|
|
BYTE rgbIdentity[HASH_DATA_SIZE];
|
|
HCRYPTPROV hCSP = NULL;
|
|
DWORD CredentialIndex;
|
|
DWORD cbSAI;
|
|
DWORD cbSAC;
|
|
DWORD cbCredential;
|
|
BYTE * pbCredential;
|
|
BYTE * pbSAI = NULL;
|
|
BYTE * pbSAC = NULL;
|
|
BOOL fIsPasswordNull = FALSE;
|
|
HRESULT hr;
|
|
|
|
//
|
|
// Obtain a provider handle to the CSP (for use with Crypto API).
|
|
//
|
|
|
|
hr = GetCSPHandle(&hCSP);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
return(hr);
|
|
}
|
|
|
|
//
|
|
// Hash the job into a unique identity.
|
|
// It will be used for credential lookup.
|
|
//
|
|
|
|
hr = HashJobIdentity(hCSP, pwszJobPath, rgbIdentity);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CloseCSPHandle(hCSP);
|
|
return(hr);
|
|
}
|
|
|
|
//
|
|
// Guard SA security database access.
|
|
//
|
|
|
|
EnterCriticalSection(&gcsSSCritSection);
|
|
|
|
//
|
|
// Read SAI & SAC databases.
|
|
//
|
|
|
|
hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Does this identity exist in the LSA?
|
|
//
|
|
|
|
hr = SAIFindIdentity(rgbIdentity,
|
|
cbSAI,
|
|
pbSAI,
|
|
&CredentialIndex,
|
|
&fIsPasswordNull);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
else if (hr == S_OK) // Found it.
|
|
{
|
|
//
|
|
// Index the credential associated with the identity.
|
|
//
|
|
|
|
hr = SACIndexCredential(CredentialIndex,
|
|
cbSAC,
|
|
pbSAC,
|
|
&cbCredential,
|
|
&pbCredential);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
else if (hr == S_FALSE)
|
|
{
|
|
//
|
|
// Credential not found? The SAC & SAI databases are out of sync.
|
|
// This should *never* occur.
|
|
//
|
|
|
|
ASSERT_SECURITY_DBASE_CORRUPT();
|
|
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Generate key & decrypt the credentials.
|
|
//
|
|
|
|
RC2_KEY_INFO RC2KeyInfo;
|
|
|
|
hr = ComputeCredentialKey(hCSP, &RC2KeyInfo);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// *** Important ***
|
|
//
|
|
// The encrypted credentials passed are decrypted
|
|
// *in-place*. Therefore, SAC buffer content has been
|
|
// compromised; plus, the decrypted data must be zeroed
|
|
// immediately following decryption (even in a failure
|
|
// case).
|
|
//
|
|
// NB : The start of the credential refers to the
|
|
// credential identity. Skip over this to refer
|
|
// to the encrypted bits.
|
|
//
|
|
|
|
DWORD cbEncryptedData = cbCredential - HASH_DATA_SIZE;
|
|
BYTE * pbEncryptedData = pbCredential + HASH_DATA_SIZE;
|
|
|
|
hr = DecryptCredentials(RC2KeyInfo,
|
|
cbEncryptedData,
|
|
pbEncryptedData,
|
|
pjc);
|
|
|
|
CHECK_HRESULT(hr);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Don't leave the plain-text password on the heap.
|
|
//
|
|
SecureZeroMemory(pbEncryptedData, cbEncryptedData);
|
|
|
|
//
|
|
// If the SAI said this job has a null password, that
|
|
// overrides the password read from the SAC.
|
|
//
|
|
if (fIsPasswordNull)
|
|
{
|
|
pjc->fIsPasswordNull = TRUE;
|
|
SecureZeroMemory(pjc->wszPassword, sizeof pjc->wszPassword);
|
|
pjc->ccPassword = 0;
|
|
}
|
|
}
|
|
// Clear key content.
|
|
//
|
|
SecureZeroMemory(&RC2KeyInfo, sizeof(RC2KeyInfo));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = SCHED_E_ACCOUNT_INFORMATION_NOT_SET;
|
|
}
|
|
|
|
ErrorExit:
|
|
if (pbSAI != NULL) LocalFree(pbSAI);
|
|
if (pbSAC != NULL) LocalFree(pbSAC);
|
|
|
|
if (hCSP != NULL) CloseCSPHandle(hCSP);
|
|
|
|
//
|
|
// Log an error & rest the SA security dbases SAI & SAC
|
|
// if corruption is detected.
|
|
//
|
|
|
|
if (hr == SCHED_E_ACCOUNT_DBASE_CORRUPT)
|
|
{
|
|
//
|
|
// Log an error.
|
|
//
|
|
|
|
LogServiceError(IERR_SECURITY_DBASE_CORRUPTION, 0,
|
|
IDS_HELP_HINT_DBASE_CORRUPT);
|
|
|
|
//
|
|
// Reset SAI & SAC by writing four bytes of zeros into each.
|
|
// Ignore the return code. No recourse if this fails.
|
|
//
|
|
|
|
DWORD dwZero = 0;
|
|
WriteSecurityDBase(sizeof(dwZero), (BYTE *)&dwZero, sizeof(dwZero),
|
|
(BYTE *)&dwZero);
|
|
}
|
|
|
|
LeaveCriticalSection(&gcsSSCritSection);
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HashJobIdentity
|
|
//
|
|
// Synopsis: calculate a hash from several pieces of data specific to the job file
|
|
// that can help to uniquely identify the job and detect tampering
|
|
//
|
|
// Arguments: [hCSP] -- handle to cryptographic service provider
|
|
// [pwszFileName] -- job file name
|
|
// [rgbHash] -- hashed identity
|
|
// [dwHashMethod -- dword value indicating which hash method to use;
|
|
// Default if not specified is the latest method.
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: 11/09/2002 - it was discovered that the value retrieved for domain name
|
|
// (and possibly account name) may not always be the same case, thus causing
|
|
// different hashes to be produced even though the domain had not changed,
|
|
// and the file had not been touched. Always forcing the names to upper case
|
|
// prior to calculating the hash prevents such a change from affecting the
|
|
// hash. Removing the values from the hash calculation altogether also
|
|
// avoids the problem and prevents localization from having negative affects
|
|
// as well. A new parameter, dwHashMethod, has been introduced to allow different
|
|
// hashing methods to be employed to facilitate conversion of existing data.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
HashJobIdentity(
|
|
HCRYPTPROV hCSP,
|
|
LPCWSTR pwszFileName,
|
|
BYTE rgbHash[],
|
|
DWORD dwHashMethod /* = 1 */)
|
|
{
|
|
WCHAR wszApplication[MAX_PATH + 1] = L"";
|
|
WCHAR wszOwnerName[MAX_USERNAME + 1] = L"";
|
|
WCHAR wszOwnerDomain[MAX_DOMAINNAME + 1] = L"";
|
|
UUID JobID;
|
|
FILETIME ftCreationTime;
|
|
PSECURITY_DESCRIPTOR pOwnerSecDescr = NULL;
|
|
DWORD cbOwnerSid;
|
|
PSID pOwnerSid;
|
|
DWORD dwVolumeSerialNo;
|
|
HRESULT hr;
|
|
|
|
hr = GetFileInformation(pwszFileName,
|
|
&cbOwnerSid,
|
|
&pOwnerSid,
|
|
&pOwnerSecDescr,
|
|
&JobID,
|
|
MAX_USERNAME + 1,
|
|
MAX_DOMAINNAME + 1,
|
|
MAX_PATH + 1,
|
|
wszOwnerName,
|
|
wszOwnerDomain,
|
|
wszApplication,
|
|
&ftCreationTime,
|
|
&dwVolumeSerialNo);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DWORD cbHash = HASH_DATA_SIZE;
|
|
BYTE * pbHash = rgbHash;
|
|
|
|
if (dwHashMethod == 0)
|
|
{
|
|
hr = MarshalData(hCSP,
|
|
NULL,
|
|
HashAndSign,
|
|
&cbHash,
|
|
&pbHash,
|
|
7,
|
|
cbOwnerSid,
|
|
pOwnerSid,
|
|
sizeof(JobID),
|
|
&JobID,
|
|
(wcslen(wszOwnerName) + 1) * sizeof(WCHAR),
|
|
wszOwnerName,
|
|
(wcslen(wszOwnerDomain) + 1) * sizeof(WCHAR),
|
|
wszOwnerDomain,
|
|
(wcslen(wszApplication) + 1) * sizeof(WCHAR),
|
|
wszApplication,
|
|
sizeof(ftCreationTime),
|
|
&ftCreationTime,
|
|
sizeof(dwVolumeSerialNo),
|
|
&dwVolumeSerialNo);
|
|
}
|
|
else /* if (dwHashMethod == 1) */
|
|
{
|
|
hr = MarshalData(hCSP,
|
|
NULL,
|
|
HashAndSign,
|
|
&cbHash,
|
|
&pbHash,
|
|
5,
|
|
cbOwnerSid,
|
|
pOwnerSid,
|
|
sizeof(JobID),
|
|
&JobID,
|
|
(wcslen(wszApplication) + 1) * sizeof(WCHAR),
|
|
wszApplication,
|
|
sizeof(ftCreationTime),
|
|
&ftCreationTime,
|
|
sizeof(dwVolumeSerialNo),
|
|
&dwVolumeSerialNo);
|
|
}
|
|
|
|
schAssert(pbHash == rgbHash);
|
|
}
|
|
|
|
// BUGBUG Is pOwnerSid leaked???
|
|
|
|
delete pOwnerSecDescr;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GrantAccountBatchPrivilege
|
|
//
|
|
// Synopsis: Grant the account batch privilege.
|
|
//
|
|
// Arguments: [pAccountSid] -- Account set.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: HRESULTs
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
GrantAccountBatchPrivilege(PSID pAccountSid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
LSA_OBJECT_ATTRIBUTES ObjectAttributes = {
|
|
sizeof(LSA_OBJECT_ATTRIBUTES),
|
|
NULL,
|
|
NULL,
|
|
0L,
|
|
NULL,
|
|
NULL
|
|
};
|
|
LSA_HANDLE hPolicy;
|
|
|
|
NTSTATUS Status = LsaOpenPolicy(NULL,
|
|
&ObjectAttributes,
|
|
POLICY_CREATE_ACCOUNT,
|
|
&hPolicy);
|
|
if (Status >= 0)
|
|
{
|
|
LSA_UNICODE_STRING PrivilegeString = {
|
|
sizeof(SE_BATCH_LOGON_NAME) - 2,
|
|
sizeof(SE_BATCH_LOGON_NAME),
|
|
SE_BATCH_LOGON_NAME,
|
|
};
|
|
|
|
Status = LsaAddAccountRights(hPolicy, pAccountSid, &PrivilegeString, 1);
|
|
if (Status < 0)
|
|
{
|
|
ERR_OUT("LsaAddAccountRights", Status);
|
|
}
|
|
|
|
LsaClose(hPolicy);
|
|
}
|
|
else
|
|
{
|
|
ERR_OUT("LsaOpenPolicy", Status);
|
|
}
|
|
|
|
if (Status < 0)
|
|
{
|
|
schAssert(!"Grant Batch Privilege failed, shouldn't have");
|
|
DWORD err = RtlNtStatusToDosError(Status);
|
|
hr = HRESULT_FROM_WIN32(err);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: MarshalData
|
|
//
|
|
// Synopsis: [hCSP] --
|
|
// [phHash] --
|
|
// [MarshalFunction] --
|
|
// [pcbSignature] --
|
|
// [ppbSignature] --
|
|
// [cArgs] --
|
|
// [...] --
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
MarshalData(
|
|
HCRYPTPROV hCSP,
|
|
HCRYPTHASH * phHash,
|
|
MARSHAL_FUNCTION MarshalFunction,
|
|
DWORD * pcbSignature,
|
|
BYTE ** ppbSignature,
|
|
DWORD cArgs,
|
|
...)
|
|
{
|
|
#define COPYMEMORY(dest, src, size) { \
|
|
CopyMemory(*dest, src, size); \
|
|
*(BYTE **)dest += size; \
|
|
}
|
|
|
|
HCRYPTHASH hHash = NULL;
|
|
DWORD cbSignature = 0;
|
|
BYTE * pbSignature = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
va_list pvarg;
|
|
|
|
va_start(pvarg, cArgs);
|
|
|
|
DWORD i, cbSize, cbData = 0;
|
|
|
|
for (i = cArgs; i--; )
|
|
{
|
|
cbData += va_arg(pvarg, DWORD);
|
|
va_arg(pvarg, BYTE *);
|
|
}
|
|
|
|
BYTE * pbData, * pb;
|
|
|
|
pbData = pb = new BYTE[cbData];
|
|
|
|
if (pbData == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
va_start(pvarg, cArgs);
|
|
|
|
for (i = cArgs; i--; )
|
|
{
|
|
cbSize = va_arg(pvarg, DWORD);
|
|
COPYMEMORY(&pb, va_arg(pvarg, BYTE *), cbSize);
|
|
}
|
|
|
|
if (MarshalFunction == Marshal)
|
|
{
|
|
//
|
|
// Done. Return marshal data in the signature return args.
|
|
//
|
|
|
|
*pcbSignature = cbData;
|
|
*ppbSignature = pbData;
|
|
va_end(pvarg);
|
|
return(S_OK);
|
|
}
|
|
|
|
//
|
|
// Acquire a handle to an MD5 hashing object. MD5 is the most secure
|
|
// hashing algorithm.
|
|
//
|
|
|
|
schAssert(hCSP != NULL);
|
|
|
|
#if DBG
|
|
//
|
|
// We must not be impersonating while calling the Crypto APIs.
|
|
// If we are, the key data will go in the wrong hives.
|
|
//
|
|
HANDLE hToken;
|
|
schAssert(!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_QUERY, // Desired access.
|
|
TRUE, // Open as self.
|
|
&hToken));
|
|
#endif
|
|
|
|
if (!CryptCreateHash(hCSP,
|
|
CALG_MD5, // Use MD5 hashing.
|
|
0, // MD5 is non-keyed.
|
|
0, // New key container.
|
|
&hHash)) // Returned handle.
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Hash and optionally sign the data. The hash is cached w/in the hash
|
|
// object and returned upon signing.
|
|
//
|
|
|
|
if (!CryptHashData(hHash,
|
|
pbData, // Hash data.
|
|
cbData, // Hash data size.
|
|
0)) // No special flags.
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (MarshalFunction == HashAndSign)
|
|
{
|
|
//
|
|
// First, determine necessary signature buffer size & allocate it.
|
|
//
|
|
|
|
if (!CryptSignHash(hHash,
|
|
AT_SIGNATURE, // Signature private key.
|
|
NULL, // No signature.
|
|
0, // Reserved.
|
|
NULL, // NULL return buffer.
|
|
&cbSignature)) // Returned size.
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Caller can supply a buffer to return the signed data only with
|
|
// the HashAndSign option. This is an optimization to reduce the
|
|
// number of memory allocations with known data sizes such as
|
|
// hashed data.
|
|
//
|
|
|
|
if (*pcbSignature)
|
|
{
|
|
if (*pcbSignature >= cbSignature)
|
|
{
|
|
//
|
|
// Caller supplied a buffer & the signed data will fit in it.
|
|
//
|
|
|
|
pbSignature = *ppbSignature;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Caller supplied buffer insufficient size.
|
|
// This is a developer error only.
|
|
//
|
|
|
|
hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
schAssert(0 && "MarshalData insufficient buffer!");
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pbSignature = new BYTE[cbSignature];
|
|
|
|
if (pbSignature == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Perform the actual signing.
|
|
//
|
|
|
|
if (!CryptSignHash(hHash,
|
|
AT_SIGNATURE, // Signature private key.
|
|
NULL, // No signature.
|
|
0, // Reserved.
|
|
pbSignature, // Signature buffer.
|
|
&cbSignature)) // Buffer size.
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
*pcbSignature = cbSignature;
|
|
*ppbSignature = pbSignature;
|
|
}
|
|
|
|
if (phHash != NULL)
|
|
{
|
|
*phHash = hHash;
|
|
hHash = NULL;
|
|
}
|
|
|
|
ErrorExit:
|
|
delete pbData;
|
|
if (FAILED(hr))
|
|
{
|
|
//
|
|
// Caller may have supplied the signature data buffer in the
|
|
// HashAndSign option. If so, don't delete it.
|
|
//
|
|
|
|
if (pbSignature != *ppbSignature)
|
|
{
|
|
delete pbSignature;
|
|
}
|
|
}
|
|
if (hHash != NULL) CryptDestroyHash(hHash);
|
|
va_end(pvarg);
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HashSid
|
|
//
|
|
// Synopsis: [hCSP] --
|
|
// [pSid] --
|
|
// [rgbHash] --
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
STATIC HRESULT
|
|
HashSid(
|
|
HCRYPTPROV hCSP,
|
|
PSID pSid,
|
|
BYTE rgbHash[])
|
|
{
|
|
DWORD rgdwSubAuthorities[SID_MAX_SUB_AUTHORITIES];
|
|
SID_IDENTIFIER_AUTHORITY * pAuthority;
|
|
|
|
//
|
|
// Validate the sid passed. This is important since the win32
|
|
// documentation for the sid-related api states the returns are
|
|
// undefined if the functions fail.
|
|
//
|
|
|
|
if (!IsValidSid(pSid))
|
|
{
|
|
CHECK_HRESULT(_HRESULT_FROM_WIN32(GetLastError()));
|
|
return(E_UNEXPECTED);
|
|
}
|
|
|
|
//
|
|
// Fetch the sid identifier authority.
|
|
// BUGBUG : I hate this. The doc states if these functions fail, the
|
|
// return value is undefined. How to determine failure?
|
|
//
|
|
|
|
pAuthority = GetSidIdentifierAuthority(pSid);
|
|
|
|
//
|
|
// Fetch all sid subauthorities. Copy them to a temporary buffer in
|
|
// preparation for hashing.
|
|
//
|
|
|
|
PUCHAR pcSubAuthorities = GetSidSubAuthorityCount(pSid);
|
|
|
|
UCHAR cSubAuthoritiesCopied = min(*pcSubAuthorities,
|
|
SID_MAX_SUB_AUTHORITIES);
|
|
|
|
for (UCHAR i = 0; i < cSubAuthoritiesCopied; i++)
|
|
{
|
|
rgdwSubAuthorities[i] = *GetSidSubAuthority(pSid, i);
|
|
}
|
|
|
|
DWORD cbHash = HASH_DATA_SIZE;
|
|
BYTE * pbHash = rgbHash;
|
|
|
|
HRESULT hr = MarshalData(hCSP,
|
|
NULL,
|
|
HashAndSign,
|
|
&cbHash,
|
|
&pbHash,
|
|
2,
|
|
sizeof(SID_IDENTIFIER_AUTHORITY),
|
|
pAuthority,
|
|
cSubAuthoritiesCopied * sizeof(DWORD),
|
|
rgdwSubAuthorities);
|
|
|
|
schAssert(pbHash == rgbHash);
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: InitSS
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
InitSS(void)
|
|
{
|
|
LSA_OBJECT_ATTRIBUTES ObjectAttributes = {
|
|
sizeof(LSA_OBJECT_ATTRIBUTES),
|
|
NULL,
|
|
NULL,
|
|
0L,
|
|
NULL,
|
|
NULL
|
|
};
|
|
NTSTATUS Status;
|
|
HRESULT hr;
|
|
|
|
gccComputerName = sizeof(gwszComputerName) / sizeof(TCHAR);
|
|
|
|
if (!GetComputerName(gwszComputerName, &gccComputerName))
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// gwszComputerName will be munged. Save an unmunged copy in
|
|
// gpwszComputerName.
|
|
//
|
|
gpwszComputerName = new WCHAR[gccComputerName + 1];
|
|
if (gpwszComputerName == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
StringCchCopy(gpwszComputerName, gccComputerName + 1, gwszComputerName);
|
|
|
|
//
|
|
// gwszComputerName is used only for credential encryption. The
|
|
// computer might have been renamed since the credential database was
|
|
// created, so the credential database might have been encrypted using
|
|
// a different computer name than the present one. If a computer name
|
|
// is stored in the registry, use that one rather than the present name.
|
|
// If no name is stored in the registry, store the present one.
|
|
//
|
|
|
|
{
|
|
//
|
|
// Open the schedule agent key
|
|
//
|
|
HKEY hSchedKey;
|
|
long lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_AGENT_KEY, 0,
|
|
KEY_QUERY_VALUE | KEY_SET_VALUE, &hSchedKey);
|
|
if (lErr != ERROR_SUCCESS)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(lErr);
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Get the saved computer name
|
|
//
|
|
WCHAR wszOldName[MAX_COMPUTERNAME_LENGTH + 2];
|
|
DWORD dwType;
|
|
DWORD cb = sizeof(wszOldName);
|
|
lErr = RegQueryValueEx(hSchedKey, SCH_OLDNAME_VALUE, NULL, &dwType,
|
|
(LPBYTE)wszOldName, &cb);
|
|
|
|
if (lErr != ERROR_SUCCESS || dwType != REG_SZ)
|
|
{
|
|
schDebugOut((DEB_ERROR, "InitSS: Couldn't read OldName: err %u, "
|
|
"type %u. Writing '%ws'\n",
|
|
lErr, dwType, gwszComputerName));
|
|
//
|
|
// Write the present computer name
|
|
//
|
|
lErr = RegSetValueEx(hSchedKey, SCH_OLDNAME_VALUE, NULL, REG_SZ,
|
|
(LPBYTE) gwszComputerName,
|
|
(gccComputerName + 1) * sizeof(WCHAR));
|
|
if (lErr != ERROR_SUCCESS)
|
|
{
|
|
schDebugOut((DEB_ERROR, "InitSS: Couldn't write OldName: err %u\n",
|
|
lErr));
|
|
}
|
|
}
|
|
else if (lstrcmpi(gwszComputerName, wszOldName) != 0)
|
|
{
|
|
//
|
|
// Use the stored name instead of the present name
|
|
//
|
|
schDebugOut((DEB_ERROR, "InitSS: Using OldName '%ws'\n", wszOldName));
|
|
StringCchCopy(gwszComputerName, MAX_COMPUTERNAME_LENGTH + 2, wszOldName);
|
|
gccComputerName = (cb / sizeof(WCHAR)) - 1;
|
|
}
|
|
|
|
//
|
|
// Close the key
|
|
//
|
|
RegCloseKey(hSchedKey);
|
|
}
|
|
|
|
|
|
LSA_HANDLE hPolicy;
|
|
|
|
if (!(LsaOpenPolicy(NULL,
|
|
&ObjectAttributes,
|
|
POLICY_VIEW_LOCAL_INFORMATION,
|
|
&hPolicy) >= 0))
|
|
{
|
|
hr = E_UNEXPECTED;
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Status = LsaQueryInformationPolicy(hPolicy,
|
|
PolicyAccountDomainInformation,
|
|
(void **)&gpDomainInfo);
|
|
|
|
LsaClose(hPolicy);
|
|
|
|
if (!(Status >= 0))
|
|
{
|
|
hr = E_UNEXPECTED;
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
MungeComputerName(gccComputerName);
|
|
|
|
gpMachineSid = gpDomainInfo->DomainSid;
|
|
gcbMachineSid = GetLengthSid(gpDomainInfo->DomainSid);
|
|
|
|
DWORD dwRet = StartupAuditing();
|
|
return _HRESULT_FROM_WIN32(dwRet);
|
|
|
|
ErrorExit:
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: UninitSS
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
UninitSS(void)
|
|
{
|
|
ShutdownAuditing();
|
|
|
|
if (gpDomainInfo != NULL)
|
|
{
|
|
LsaFreeMemory(gpDomainInfo);
|
|
gpDomainInfo = NULL;
|
|
}
|
|
|
|
if (gpwszComputerName != NULL)
|
|
{
|
|
delete gpwszComputerName;
|
|
gpwszComputerName = NULL;
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: MungeComputerName
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [psidUser] --
|
|
// [ccAccountName] --
|
|
// [wszAccountName] --
|
|
// [wszAccountNameSize] --
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
STATIC void
|
|
MungeComputerName(DWORD ccComputerName)
|
|
{
|
|
WCHAR * pwszStart = gwszComputerName;
|
|
|
|
while (*pwszStart) pwszStart++;
|
|
|
|
gwszComputerName[MAX_COMPUTERNAME_LENGTH + 1] = L'\0';
|
|
|
|
//
|
|
// Set the character following the computername to a '+' or '-' depending
|
|
// on the value of ccAccountName (if the 2nd bit is set).
|
|
//
|
|
|
|
if ((ccComputerName - 1) & 0x00000001)
|
|
{
|
|
gwszComputerName[MAX_COMPUTERNAME_LENGTH] = L'+';
|
|
}
|
|
else
|
|
{
|
|
gwszComputerName[MAX_COMPUTERNAME_LENGTH] = L'-';
|
|
}
|
|
|
|
//
|
|
// Fill any intermediary buffer space with space characters. Note, no
|
|
// portion of the computername is overwritten.
|
|
//
|
|
// NB : The astute reader will notice the subtle difference in behavior
|
|
// if the computername should be of maximum length. In this case,
|
|
// the '+' or '-' character written above will be overwritten with
|
|
// a space.
|
|
//
|
|
|
|
WCHAR * pwszEnd = &gwszComputerName[MAX_COMPUTERNAME_LENGTH - 1];
|
|
|
|
if (pwszEnd > pwszStart)
|
|
{
|
|
while (pwszEnd != pwszStart)
|
|
{
|
|
*pwszEnd-- = L' ';
|
|
}
|
|
}
|
|
|
|
*pwszStart = L' ';
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetCSPHandle
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
GetCSPHandle(HCRYPTPROV * phCSP)
|
|
{
|
|
#if DBG
|
|
//
|
|
// We must not be impersonating while calling the Crypto APIs.
|
|
// If we are, the key data will go in the wrong hives.
|
|
//
|
|
HANDLE hToken;
|
|
schAssert(!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_QUERY, // Desired access.
|
|
TRUE, // Open as self.
|
|
&hToken));
|
|
#endif
|
|
|
|
HRESULT hr;
|
|
|
|
if (!CryptAcquireContext(phCSP, // Returned CSP handle.
|
|
g_tszSrvcName, // Default Key container.
|
|
// MSFT RSA Base Provider.
|
|
NULL, // Default user provider.
|
|
PROV_RSA_FULL, // Default provider type.
|
|
0)) // No special flags.
|
|
{
|
|
DWORD Status = GetLastError();
|
|
|
|
if (Status == NTE_KEYSET_ENTRY_BAD || Status == NTE_BAD_KEYSET)
|
|
{
|
|
//
|
|
// Delete the keyset and try again.
|
|
// Ignore this return code.
|
|
//
|
|
|
|
if (!CryptAcquireContext(phCSP,
|
|
g_tszSrvcName,
|
|
NULL,
|
|
PROV_RSA_FULL,
|
|
CRYPT_DELETEKEYSET))
|
|
{
|
|
ERR_OUT("CryptAcquireContext(delete)", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
LogServiceError(IERR_SECURITY_KEYSET_CORRUPT, 0, IDS_HELP_HINT_DBASE_CORRUPT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Print the error in debug builds, but otherwise ignore it.
|
|
//
|
|
ERR_OUT("CryptAcquireContext(open)", Status);
|
|
}
|
|
|
|
//
|
|
// Assume this is the first time this code has been run on this
|
|
// particular machine. Must create a new keyset & key.
|
|
//
|
|
|
|
if (!CryptAcquireContext(phCSP,
|
|
g_tszSrvcName,
|
|
NULL,
|
|
PROV_RSA_FULL,
|
|
CRYPT_NEWKEYSET)) // New keyset.
|
|
{
|
|
Status = GetLastError();
|
|
if (Status == NTE_EXISTS)
|
|
{
|
|
//
|
|
// Our assumption was wrong!
|
|
// Delete the keyset and try again.
|
|
// Ignore this return code.
|
|
//
|
|
if (!CryptAcquireContext(phCSP,
|
|
g_tszSrvcName,
|
|
NULL,
|
|
PROV_RSA_FULL,
|
|
CRYPT_DELETEKEYSET))
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
else
|
|
{
|
|
LogServiceError(IERR_SECURITY_KEYSET_CORRUPT, 0, IDS_HELP_HINT_DBASE_CORRUPT);
|
|
}
|
|
|
|
//
|
|
// Must now create a new keyset & key.
|
|
//
|
|
if (!CryptAcquireContext(phCSP,
|
|
g_tszSrvcName,
|
|
NULL,
|
|
PROV_RSA_FULL,
|
|
CRYPT_NEWKEYSET)) // New keyset.
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(Status);
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
}
|
|
|
|
HCRYPTKEY hKey;
|
|
|
|
//
|
|
// The upper 16 bits of the 3rd parm to CryptGenKey specify the key
|
|
// size in bits. The size of the signature from CryptSignHash will
|
|
// be equal to the size of this key. Since we rely on the signature
|
|
// being a specific size, we must explicitly specify the key size.
|
|
//
|
|
if (!CryptGenKey(*phCSP,
|
|
AT_SIGNATURE, // Digital signature.
|
|
(HASH_DATA_SIZE * 8) << 16, // see above
|
|
&hKey ))
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
CryptDestroyKey(hKey); // No further use for
|
|
// the key.
|
|
}
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CloseCSPHandle
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CloseCSPHandle(HCRYPTPROV hCSP)
|
|
{
|
|
CryptReleaseContext(hCSP, 0);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ComputeCredentialKey
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [hCSP] --
|
|
// [pRC2KeyInfo] --
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
ComputeCredentialKey(HCRYPTPROV hCSP, RC2_KEY_INFO * pRC2KeyInfo)
|
|
{
|
|
BYTE rgbHash[HASH_DATA_SIZE];
|
|
HCRYPTHASH hHash = NULL;
|
|
DWORD cbHash = 0;
|
|
BYTE * pbHash = NULL;
|
|
HRESULT hr = S_OK;
|
|
DWORD i;
|
|
|
|
//
|
|
// Hash misc. global data.
|
|
//
|
|
// NB : MarshalData actually does nothing with the 3rd & 4th arguments
|
|
// with the Hash option.
|
|
//
|
|
|
|
hr = MarshalData(hCSP,
|
|
&hHash,
|
|
Hash,
|
|
&cbHash,
|
|
&pbHash,
|
|
2,
|
|
(gccComputerName & 0x00000001 ?
|
|
(MAX_COMPUTERNAME_LENGTH + 2) * sizeof(WCHAR) :
|
|
sizeof(DWORD)),
|
|
(gccComputerName & 0x00000001 ?
|
|
(BYTE *)gwszComputerName : (BYTE *)&gdwKeyElement),
|
|
gcbMachineSid,
|
|
gpMachineSid);
|
|
|
|
//
|
|
// Generate the key.
|
|
//
|
|
// NB : In place of CryptDeriveKey, statically generate the key. This
|
|
// is done to work around Crypto restrictions in France.
|
|
//
|
|
// Old:
|
|
//
|
|
// CryptDeriveKey(ghCSP, CALG_RC2, hHash, 0, &hKey);
|
|
//
|
|
// New:
|
|
//
|
|
|
|
cbHash = sizeof(rgbHash);
|
|
|
|
if (!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Clear RC2KeyInfo content.
|
|
//
|
|
schAssert(pRC2KeyInfo != NULL);
|
|
SecureZeroMemory(pRC2KeyInfo, sizeof(*pRC2KeyInfo));
|
|
|
|
//
|
|
// Set the upper eleven bytes to 0x00 because Derive key by default
|
|
// uses 11 bytes of 0x00 salt
|
|
//
|
|
SecureZeroMemory(rgbHash + 5, 11);
|
|
|
|
//
|
|
// Use the 5 bytes (40 bits) of the hash as a key.
|
|
//
|
|
RC2KeyEx(pRC2KeyInfo->rgwKeyTable, rgbHash, 16, 40);
|
|
|
|
ErrorExit:
|
|
if (hHash != NULL) CryptDestroyHash(hHash);
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: EncryptCredentials
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [RC2KeyInfo] --
|
|
// [pwszAccount] --
|
|
// [pwszDomain] --
|
|
// [pwszPassword] --
|
|
// [pSid] --
|
|
// [pcbEncryptedData] --
|
|
// [ppbEncryptedData] --
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
EncryptCredentials(
|
|
const RC2_KEY_INFO & RC2KeyInfo,
|
|
LPCWSTR pwszAccount,
|
|
LPCWSTR pwszDomain,
|
|
LPCWSTR pwszPassword,
|
|
PSID pSid,
|
|
DWORD * pcbEncryptedData,
|
|
BYTE ** ppbEncryptedData)
|
|
{
|
|
BYTE rgbBuf[RC2_BLOCKLEN];
|
|
WCHAR * pwszPasswordLocal;
|
|
DWORD cbAccount;
|
|
DWORD cbDomain;
|
|
DWORD cbPassword;
|
|
DWORD cbData = 0;
|
|
DWORD cbEncryptedData = 0;
|
|
DWORD cbPartial;
|
|
DWORD dwPadVal;
|
|
BYTE * pbData = NULL;
|
|
BYTE * pbEncryptedData = NULL;
|
|
HRESULT hr;
|
|
|
|
*pcbEncryptedData = 0;
|
|
*ppbEncryptedData = NULL;
|
|
|
|
if (pwszAccount == NULL || pwszDomain == NULL)
|
|
{
|
|
CHECK_HRESULT(E_INVALIDARG);
|
|
return(E_INVALIDARG);
|
|
}
|
|
|
|
if (pwszPassword == NULL)
|
|
{
|
|
//
|
|
// In the SAC, a NULL password is stored the same as a "" password.
|
|
// (The distinction is made per-job, in the SAI.)
|
|
//
|
|
pwszPasswordLocal = L"";
|
|
}
|
|
else
|
|
{
|
|
pwszPasswordLocal = (WCHAR *)pwszPassword;
|
|
}
|
|
|
|
cbAccount = wcslen(pwszAccount) * sizeof(WCHAR);
|
|
cbDomain = wcslen(pwszDomain) * sizeof(WCHAR);
|
|
cbPassword = wcslen(pwszPasswordLocal) * sizeof(WCHAR);
|
|
|
|
hr = MarshalData(NULL,
|
|
NULL,
|
|
Marshal,
|
|
&cbData,
|
|
&pbData,
|
|
6,
|
|
sizeof(cbAccount),
|
|
&cbAccount,
|
|
cbAccount,
|
|
pwszAccount,
|
|
sizeof(cbDomain),
|
|
&cbDomain,
|
|
cbDomain,
|
|
pwszDomain,
|
|
sizeof(cbPassword),
|
|
&cbPassword,
|
|
cbPassword,
|
|
pwszPasswordLocal);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// NB : This code exists in place of a call to CryptEncrypt to
|
|
// work around France's Crypto API restrictions. Since
|
|
// CryptEncrypt cannot be called directly, the code from
|
|
// the API to accomplish cypher block encryption is duplicated
|
|
// here.
|
|
//
|
|
|
|
//
|
|
// Calculate the number of pad bytes necessary (must be a multiple)
|
|
// of RC2_BLOCKLEN). If already a multiple of blocklen, do a full
|
|
// block of pad.
|
|
//
|
|
|
|
cbPartial = (cbData % RC2_BLOCKLEN);
|
|
|
|
dwPadVal = RC2_BLOCKLEN - cbPartial;
|
|
|
|
cbEncryptedData = cbData + dwPadVal;
|
|
|
|
//
|
|
// Allocate a buffer for the encrypted data.
|
|
//
|
|
|
|
pbEncryptedData = new BYTE[cbEncryptedData];
|
|
|
|
if (pbEncryptedData == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
CopyMemory(pbEncryptedData, pbData, cbData);
|
|
|
|
if (dwPadVal)
|
|
{
|
|
//
|
|
// Fill the pad with a value equal to the length of the padding,
|
|
// so decrypt will know the length of the original data and as
|
|
// a simple integrity check.
|
|
//
|
|
|
|
memset(pbEncryptedData + cbData, (INT)dwPadVal, (size_t)dwPadVal);
|
|
}
|
|
|
|
//
|
|
// Perform the encryption - cypher block.
|
|
//
|
|
|
|
*pcbEncryptedData = cbEncryptedData;
|
|
*ppbEncryptedData = pbEncryptedData;
|
|
|
|
while (cbEncryptedData)
|
|
{
|
|
//
|
|
// Put the plaintext into a temporary buffer, then encrypt the
|
|
// data back into the allocated buffer.
|
|
//
|
|
|
|
CopyMemory(rgbBuf, pbEncryptedData, RC2_BLOCKLEN);
|
|
|
|
CBC(RC2,
|
|
RC2_BLOCKLEN,
|
|
pbEncryptedData,
|
|
rgbBuf,
|
|
(void *)RC2KeyInfo.rgwKeyTable,
|
|
ENCRYPT,
|
|
(BYTE *)RC2KeyInfo.rgbIV);
|
|
|
|
pbEncryptedData += RC2_BLOCKLEN;
|
|
cbEncryptedData -= RC2_BLOCKLEN;
|
|
}
|
|
}
|
|
|
|
pbEncryptedData = NULL; // For delete below.
|
|
|
|
ErrorExit:
|
|
delete pbData;
|
|
delete pbEncryptedData;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SkipDomainName
|
|
//
|
|
// Synopsis: Return the relative username if the username passed is in
|
|
// distinguished form. eg: return 'Joe' from 'DogFood\Joe'.
|
|
//
|
|
// Arguments: [pwszUserName] -- User name.
|
|
//
|
|
// Returns: Pointer index to/into pwszUserName.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LPWSTR
|
|
SkipDomainName(LPCWSTR pwszUserName)
|
|
{
|
|
LPWSTR pwsz = (LPWSTR)pwszUserName;
|
|
|
|
while (*pwsz && *pwsz != '\\')
|
|
{
|
|
pwsz++;
|
|
}
|
|
|
|
if (*pwsz == L'\\')
|
|
{
|
|
return(++pwsz);
|
|
}
|
|
|
|
return((LPWSTR)pwszUserName);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: DecryptCredentials
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [RC2KeyInfo] --
|
|
// [cbEncryptedData] --
|
|
// [pbEncryptedData] --
|
|
// [pjc] --
|
|
// [fDecryptInPlace] --
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
DecryptCredentials(
|
|
const RC2_KEY_INFO & RC2KeyInfo,
|
|
DWORD cbEncryptedData,
|
|
BYTE * pbEncryptedData,
|
|
PJOB_CREDENTIALS pjc,
|
|
BOOL fDecryptInPlace)
|
|
{
|
|
BYTE rgbBuf[RC2_BLOCKLEN];
|
|
DWORD cbDecryptedData = cbEncryptedData;
|
|
BYTE * pbDecryptedData;
|
|
DWORD BytePos;
|
|
DWORD dwPadVal;
|
|
DWORD i;
|
|
DWORD cbAccount, cbDomain, cbPassword;
|
|
BYTE * pbAccount, * pbDomain, * pbPassword;
|
|
BOOL fIsPasswordNull = FALSE;
|
|
BYTE * pb;
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// The encrypted data length *must* be a multiple of RC2_BLOCKLEN.
|
|
//
|
|
|
|
if (cbEncryptedData % RC2_BLOCKLEN)
|
|
{
|
|
CHECK_HRESULT(E_UNEXPECTED);
|
|
return(E_UNEXPECTED);
|
|
}
|
|
|
|
//
|
|
// Decrypt overwrites the encrypted data with the decrypted data.
|
|
// If fDecryptInPlace is FALSE, allocate an additional buffer for
|
|
// the decrypted bits, so the encrypted data buffer will not be
|
|
// overwritten.
|
|
//
|
|
|
|
if (!fDecryptInPlace)
|
|
{
|
|
pbDecryptedData = new BYTE[cbEncryptedData];
|
|
|
|
if (pbDecryptedData == NULL)
|
|
{
|
|
CHECK_HRESULT(E_OUTOFMEMORY);
|
|
return(E_OUTOFMEMORY);
|
|
}
|
|
CopyMemory(pbDecryptedData, pbEncryptedData, cbEncryptedData);
|
|
}
|
|
else
|
|
{
|
|
pbDecryptedData = pbEncryptedData;
|
|
}
|
|
|
|
//
|
|
// NB : This code exists in place of a call to CryptDencrypt to
|
|
// work around France's Crypto API restrictions. Since
|
|
// CryptDecrypt cannot be called directly, the code from
|
|
// the API to accomplish cypher block decryption is duplicated
|
|
// here.
|
|
//
|
|
|
|
for (BytePos = 0; (BytePos + RC2_BLOCKLEN) <= cbEncryptedData;
|
|
BytePos += RC2_BLOCKLEN)
|
|
{
|
|
//
|
|
// Use a temporary buffer to store the encrypted data.
|
|
//
|
|
|
|
CopyMemory(rgbBuf, pbDecryptedData + BytePos, RC2_BLOCKLEN);
|
|
|
|
CBC(RC2,
|
|
RC2_BLOCKLEN,
|
|
pbDecryptedData + BytePos,
|
|
rgbBuf,
|
|
(void *)RC2KeyInfo.rgwKeyTable,
|
|
DECRYPT,
|
|
(BYTE *)RC2KeyInfo.rgbIV);
|
|
}
|
|
|
|
//
|
|
// Verify the padding and remove the pad size from the data length.
|
|
// NOTE: The padding is filled with a value equal to the length
|
|
// of the padding and we are guaranteed >= 1 byte of pad.
|
|
//
|
|
// NB : If the pad is wrong, the user's buffer is hosed, because
|
|
// we've decrypted into the user's buffer -- can we re-encrypt it?
|
|
//
|
|
|
|
dwPadVal = (DWORD)*(pbDecryptedData + cbEncryptedData - 1);
|
|
|
|
if (dwPadVal == 0 || dwPadVal > (DWORD) RC2_BLOCKLEN)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Make sure all the (rest of the) pad bytes are correct.
|
|
//
|
|
|
|
for (i = 1; i < dwPadVal; i++)
|
|
{
|
|
if (pbDecryptedData[cbEncryptedData - (i + 1)] != dwPadVal)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
pb = pbDecryptedData;
|
|
|
|
//
|
|
// Have to do the following incantation since otherwise we'd likely
|
|
// fault on an unaligned fetch.
|
|
//
|
|
// Cache account name size & position.
|
|
//
|
|
|
|
CopyMemory(&cbAccount, pb, sizeof(cbAccount));
|
|
pbAccount = pb + sizeof(cbAccount);
|
|
pb = pbAccount + cbAccount;
|
|
|
|
if (((DWORD)(pb - pbDecryptedData) > cbDecryptedData) || // Check size.
|
|
(cbAccount > (MAX_USERNAME * sizeof(WCHAR))))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Cache domain name size & position.
|
|
//
|
|
|
|
CopyMemory(&cbDomain, pb, sizeof(cbDomain));
|
|
pbDomain = pb + sizeof(cbDomain);
|
|
pb = pbDomain + cbDomain;
|
|
|
|
if (((DWORD)(pb - pbDecryptedData) > cbDecryptedData) || // Check size.
|
|
(cbDomain > (MAX_DOMAINNAME * sizeof(WCHAR))))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Cache password size & position.
|
|
//
|
|
|
|
CopyMemory(&cbPassword, pb, sizeof(cbPassword));
|
|
pbPassword = pb + sizeof(cbPassword);
|
|
// In the IE 5 release of the Task Scheduler, a NULL password was denoted
|
|
// by a size of 0xFFFFFFFF in the SAC. The following check lets us read
|
|
// databases created by the IE 5 TS.
|
|
if (cbPassword == NULL_PASSWORD_SIZE)
|
|
{
|
|
fIsPasswordNull = TRUE;
|
|
cbPassword = 0;
|
|
}
|
|
pb = pbPassword + cbPassword;
|
|
|
|
if (((DWORD)(pb - pbDecryptedData) > cbDecryptedData) || // Check size.
|
|
(cbPassword > (MAX_PASSWORD * sizeof(WCHAR))))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Finally, copy the return data.
|
|
//
|
|
|
|
CopyMemory(pjc->wszAccount, pbAccount, cbAccount);
|
|
*(WCHAR *)(((BYTE *)pjc->wszAccount) + cbAccount) = L'\0';
|
|
pjc->ccAccount = cbAccount / sizeof(WCHAR);
|
|
|
|
CopyMemory(pjc->wszDomain, pbDomain, cbDomain);
|
|
*(WCHAR *)(((BYTE *)pjc->wszDomain) + cbDomain) = L'\0';
|
|
pjc->ccDomain = cbDomain / sizeof(WCHAR);
|
|
|
|
CopyMemory(pjc->wszPassword, pbPassword, cbPassword);
|
|
*(WCHAR *)(((BYTE *)pjc->wszPassword) + cbPassword) = L'\0';
|
|
pjc->ccPassword = cbPassword / sizeof(WCHAR);
|
|
|
|
pjc->fIsPasswordNull = fIsPasswordNull;
|
|
|
|
ErrorExit:
|
|
if (!fDecryptInPlace) delete pbDecryptedData;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CredentialLookupAndAccessCheck
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [hCSP] --
|
|
// [pSid] --
|
|
// [cbSAC] --
|
|
// [pbSAC] --
|
|
// [pCredentialIndex] --
|
|
// [rgbHashedSid] --
|
|
// [pcbCredential] --
|
|
// [ppbCredential] --
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
STATIC HRESULT
|
|
CredentialLookupAndAccessCheck(
|
|
HCRYPTPROV hCSP,
|
|
PSID pSid,
|
|
DWORD cbSAC,
|
|
BYTE * pbSAC,
|
|
DWORD * pCredentialIndex,
|
|
BYTE rgbHashedSid[],
|
|
DWORD * pcbCredential,
|
|
BYTE ** ppbCredential)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Either pSid or rgbHashedSid must be specified.
|
|
//
|
|
schAssert(rgbHashedSid != NULL && (pSid != NULL || *rgbHashedSid));
|
|
|
|
if (pSid != NULL)
|
|
{
|
|
if (!IsValidSid(pSid))
|
|
{
|
|
CHECK_HRESULT(E_UNEXPECTED);
|
|
return(E_UNEXPECTED);
|
|
}
|
|
|
|
hr = HashSid(hCSP, pSid, rgbHashedSid);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
return(hr);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Find the credential in the SAC associated with the account sid. The
|
|
// hashed account sid is utilized as a SAC database key.
|
|
//
|
|
|
|
DWORD cbEncryptedData;
|
|
BYTE * pbEncryptedData;
|
|
|
|
hr = SACFindCredential(rgbHashedSid,
|
|
cbSAC,
|
|
pbSAC,
|
|
pCredentialIndex,
|
|
&cbEncryptedData,
|
|
&pbEncryptedData);
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// Found it. Does the caller have access to this credential?
|
|
//
|
|
|
|
BYTE * pbCredential = pbEncryptedData - HASH_DATA_SIZE;
|
|
|
|
if (CredentialAccessCheck(hCSP, pbCredential))
|
|
{
|
|
// Update out ptrs.
|
|
//
|
|
*ppbCredential = pbCredential;
|
|
CopyMemory(pcbCredential,
|
|
*ppbCredential - sizeof(*pcbCredential),
|
|
sizeof(*pcbCredential));
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
|
|
}
|
|
}
|
|
else if (hr == S_FALSE)
|
|
{
|
|
//
|
|
// Didn't find the credential.
|
|
//
|
|
|
|
hr = SCHED_E_ACCOUNT_INFORMATION_NOT_SET;
|
|
}
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CredentialAccessCheck
|
|
//
|
|
// Synopsis: Determine if the RPC client has access to the credential
|
|
// indicated.
|
|
//
|
|
// Arguments: [hCSP] -- CSP provider handle (for use with
|
|
// Crypto API).
|
|
// [pbCredentialIdentity] -- Credential identity.
|
|
//
|
|
// Returns: TRUE -- RPC client has permission to access this credential.
|
|
// FALSE -- RPC client doesn't have credential access or an
|
|
// unexpected error occurred.
|
|
//
|
|
// Notes: ** Important **
|
|
//
|
|
// Thread impersonation is performed in this routine via
|
|
// RpcImpersonateClient; therefore, it is assumed only RPC
|
|
// threads enter it.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
STATIC BOOL
|
|
CredentialAccessCheck(
|
|
HCRYPTPROV hCSP,
|
|
BYTE * pbCredentialIdentity)
|
|
{
|
|
RPC_STATUS RpcStatus;
|
|
|
|
//
|
|
// Impersonate the caller.
|
|
//
|
|
|
|
if ((RpcStatus = RpcImpersonateClient(NULL)) != RPC_S_OK)
|
|
{
|
|
CHECK_HRESULT(RpcStatus);
|
|
return(FALSE);
|
|
}
|
|
|
|
HANDLE hToken;
|
|
BOOL bRet;
|
|
|
|
if (!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_QUERY, // Desired access.
|
|
TRUE, // Open as self.
|
|
&hToken))
|
|
{
|
|
CHECK_HRESULT(_HRESULT_FROM_WIN32(GetLastError()));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// End impersonation, but don't close the token yet.
|
|
// (We must not be impersonating when we call HashSid, which is
|
|
// called by MatchThreadCallerAgainstCredential.)
|
|
//
|
|
if ((RpcStatus = RpcRevertToSelf()) != RPC_S_OK)
|
|
{
|
|
ERR_OUT("RpcRevertToSelf", RpcStatus);
|
|
schAssert(!"RpcRevertToSelf failed");
|
|
}
|
|
|
|
//
|
|
// Does the thread caller's hashed SID match the credential identity.
|
|
// If so, the caller's account is the same as that specified in the
|
|
// credentials.
|
|
//
|
|
|
|
if (!(bRet = MatchThreadCallerAgainstCredential(hCSP,
|
|
hToken,
|
|
pbCredentialIdentity)))
|
|
{
|
|
//
|
|
// Nope. Thread caller account/credential account mismatch.
|
|
// Is the caller an administrator?
|
|
//
|
|
|
|
bRet = IsThreadCallerAnAdmin(hToken);
|
|
}
|
|
|
|
CloseHandle(hToken);
|
|
|
|
return(bRet);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: MatchThreadCallerAgainstCredential
|
|
//
|
|
// Synopsis: Hash the user SID of the thread indicated and compare it
|
|
// against the credential identity passed. A credential identity
|
|
// is the hashed SID of the associated account.
|
|
//
|
|
// Arguments: [hCSP] -- CSP provider handle (for use with
|
|
// Cryto API).
|
|
// [hThreadToken] -- Obtain the user SID from this
|
|
// thread.
|
|
// [pbCredentialIdentity] -- Matched credential identity.
|
|
//
|
|
// Returns: TRUE -- Match
|
|
// FALSE -- No match or an error occurred.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
STATIC BOOL
|
|
MatchThreadCallerAgainstCredential(
|
|
HCRYPTPROV hCSP,
|
|
HANDLE hThreadToken,
|
|
BYTE * pbCredentialIdentity)
|
|
{
|
|
BYTE rgbTokenInformation[USER_TOKEN_STACK_BUFFER_SIZE];
|
|
TOKEN_USER * pTokenUser = (TOKEN_USER *)rgbTokenInformation;
|
|
DWORD cbReturnLength;
|
|
DWORD Status = ERROR_SUCCESS;
|
|
|
|
if (!GetTokenInformation(hThreadToken,
|
|
TokenUser,
|
|
pTokenUser,
|
|
USER_TOKEN_STACK_BUFFER_SIZE,
|
|
&cbReturnLength))
|
|
{
|
|
//
|
|
// Buffer space should have been sufficient. Check if we goofed.
|
|
//
|
|
|
|
schAssert(GetLastError() != ERROR_INSUFFICIENT_BUFFER);
|
|
CHECK_HRESULT(_HRESULT_FROM_WIN32(GetLastError()));
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Hash the user's SID.
|
|
//
|
|
|
|
BYTE rgbHashedSid[HASH_DATA_SIZE] = { 0 };
|
|
|
|
if (SUCCEEDED(HashSid(hCSP, pTokenUser->User.Sid, rgbHashedSid)))
|
|
{
|
|
if (memcmp(pbCredentialIdentity, rgbHashedSid, HASH_DATA_SIZE) == 0)
|
|
{
|
|
return(TRUE);
|
|
}
|
|
else
|
|
{
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED));
|
|
}
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ScavengeSASecurityDBase
|
|
//
|
|
// Synopsis: Enumerate the jobs folder and remove identities in the SAI
|
|
// for which no current jobs hash to. Note, SAC credentials
|
|
// are also removed if the removed identity was the last to be
|
|
// associated with it.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: Should read of any job fail, for any reason, the scavenge
|
|
// task is abandoned. Reason is, if the removal process was
|
|
// to continue anyway, credentials might be removed for existent
|
|
// jobs.
|
|
//
|
|
// The service state is checked periodically as this could
|
|
// potentially be a lengthy routine time-wise. Bail as soon
|
|
// as service stop or service stop pending is detected.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
ScavengeSASecurityDBase(void)
|
|
{
|
|
TCHAR tszSearchPath[MAX_PATH + 1];
|
|
BYTE rgbIdentity[HASH_DATA_SIZE];
|
|
WIN32_FIND_DATA fd;
|
|
JOB_IDENTITY_SET * rgIdentitySet = NULL;
|
|
HRESULT hr = S_OK;
|
|
HANDLE hFileEnum;
|
|
DWORD dwZero = 0;
|
|
DWORD i, j;
|
|
DWORD iConcatenation;
|
|
DWORD dwRet;
|
|
DWORD dwSetCount = 0;
|
|
DWORD dwSetSubCount;
|
|
DWORD cbIdentitySetArraySize;
|
|
BYTE * pbSet;
|
|
BOOL fDirty = FALSE;
|
|
|
|
//
|
|
// Build the enumeration search path.
|
|
//
|
|
|
|
StringCchCopy(tszSearchPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath);
|
|
StringCchCat(tszSearchPath, MAX_PATH + 1, EXTENSION_WILDCARD TSZ_JOB);
|
|
|
|
//
|
|
// Initialize the enumeration.
|
|
//
|
|
|
|
if ((hFileEnum = FindFirstFile(tszSearchPath,
|
|
&fd)) == INVALID_HANDLE_VALUE)
|
|
{
|
|
//
|
|
// Either no jobs, or an error occurred.
|
|
//
|
|
|
|
dwRet = GetLastError();
|
|
|
|
if (dwRet == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
EnterCriticalSection(&gcsSSCritSection);
|
|
|
|
//
|
|
// No files found. Reset SAI & SAC by writing four bytes of
|
|
// zeros into each.
|
|
//
|
|
hr = WriteSecurityDBase(sizeof(dwZero), (BYTE *)&dwZero,
|
|
sizeof(dwZero), (BYTE *)&dwZero);
|
|
CHECK_HRESULT(hr);
|
|
|
|
LeaveCriticalSection(&gcsSSCritSection);
|
|
}
|
|
else
|
|
{
|
|
CHECK_HRESULT(_HRESULT_FROM_WIN32(dwRet));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD cbSAI;
|
|
DWORD cbSAC;
|
|
BYTE * pbSAI = NULL;
|
|
BYTE * pbSAC = NULL;
|
|
BYTE * pbSAIEnd;
|
|
BYTE * pb;
|
|
HCRYPTPROV hCSP = NULL;
|
|
|
|
//
|
|
// Check if the service is stopping.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnterCriticalSection(&gcsSSCritSection);
|
|
|
|
hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (cbSAI <= SAI_HEADER_SIZE)
|
|
{
|
|
//
|
|
// Database empty.
|
|
//
|
|
|
|
hr = S_OK;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Some background first. The SAI consists of an array of arrays. The
|
|
// first dimension represents the set of job identities per credential
|
|
// in the SAC. SAI/SAC indices are associative in this case. The set of
|
|
// job identities at SAI row[n] correspond to the credential at SAC
|
|
// row[n].
|
|
//
|
|
// We need to construct an SAI pending deletion data structure. It will
|
|
// consist of an array of JOB_IDENTITY_SET structures, in which each
|
|
// structure refers to an array of pointers to job identities in the
|
|
// SAI (literally indexing the SAI).
|
|
//
|
|
// Once the data structure is built and initialized, we'll enumerate the
|
|
// jobs in the local tasks folder. For each job found, the corresponding
|
|
// job identity pointer in the job identity set array will be set to NULL.
|
|
// Upon completion of the enumeration, the non-NULL job identity ptr
|
|
// entries within the job identity set array refer to non-existent jobs.
|
|
// The job identitites these entries refer to are removed from the SAI,
|
|
// and the associated credential in the SAC, if there are no longer
|
|
// entries in the SAI associated with it.
|
|
//
|
|
// First, allocate the array.
|
|
//
|
|
|
|
pb = pbSAI + USN_SIZE;
|
|
|
|
CopyMemory(&dwSetCount, pb, sizeof(dwSetCount));
|
|
pb += sizeof(dwSetCount);
|
|
|
|
cbIdentitySetArraySize = dwSetCount * sizeof(JOB_IDENTITY_SET);
|
|
|
|
rgIdentitySet = (JOB_IDENTITY_SET *)LocalAlloc(LMEM_FIXED,
|
|
cbIdentitySetArraySize);
|
|
|
|
if (rgIdentitySet == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
SecureZeroMemory(rgIdentitySet, cbIdentitySetArraySize);
|
|
|
|
pb = pbSAI + SAI_HEADER_SIZE;
|
|
pbSAIEnd = pbSAI + cbSAI;
|
|
|
|
//
|
|
// Check if the service is stopping.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
hr = S_OK;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Now allocate, intialize individual identity sets.
|
|
//
|
|
|
|
for (i = 0; i < dwSetCount; i++)
|
|
{
|
|
//
|
|
// Check boundary.
|
|
//
|
|
|
|
if ((pb + sizeof(dwSetSubCount)) > pbSAIEnd)
|
|
{
|
|
ASSERT_SECURITY_DBASE_CORRUPT();
|
|
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
CopyMemory(&dwSetSubCount, pb, sizeof(dwSetSubCount));
|
|
pb += sizeof(dwSetSubCount);
|
|
|
|
BYTE ** rgpbIdentity = (BYTE **)LocalAlloc(
|
|
LMEM_FIXED,
|
|
sizeof(BYTE *) * dwSetSubCount);
|
|
|
|
if (rgpbIdentity == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
rgIdentitySet[i].pbSetStart = pb;
|
|
rgIdentitySet[i].dwSetSubCount = dwSetSubCount;
|
|
rgIdentitySet[i].rgpbIdentity = rgpbIdentity;
|
|
|
|
for (j = 0; j < dwSetSubCount; j++)
|
|
{
|
|
rgpbIdentity[j] = pb;
|
|
pb += HASH_DATA_SIZE;
|
|
|
|
if (pb > pbSAIEnd)
|
|
{
|
|
ASSERT_SECURITY_DBASE_CORRUPT();
|
|
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if the service is stopping.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
hr = S_OK;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Enumerate job objects in the task's folder directory. Set
|
|
// corresponding job identity ptrs in the job identity set array to
|
|
// NULL for existent jobs.
|
|
//
|
|
|
|
//
|
|
// First, obtain a provider handle to the CSP (for use with Crypto API).
|
|
//
|
|
|
|
hr = GetCSPHandle(&hCSP);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Must concatenate the filename returned from the enumeration onto
|
|
// the folder path.
|
|
//
|
|
|
|
StringCchCopy(tszSearchPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath);
|
|
iConcatenation = lstrlenW(g_TasksFolderInfo.ptszPath);
|
|
tszSearchPath[iConcatenation++] = L'\\';
|
|
|
|
for (;;)
|
|
{
|
|
//
|
|
// Append the filename to the folder path.
|
|
//
|
|
|
|
tszSearchPath[iConcatenation] = L'\0';
|
|
StringCchCat(tszSearchPath, MAX_PATH + 1, fd.cFileName);
|
|
|
|
//
|
|
// Hash the job into a unique identity.
|
|
//
|
|
|
|
hr = HashJobIdentity(hCSP, tszSearchPath, rgbIdentity);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
//
|
|
// Must bail if the hash fails. If this is ignored, one, or more,
|
|
// identities may be removed for existent jobs - not good.
|
|
//
|
|
// TBD : Log error.
|
|
//
|
|
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Does an identity exist in the SAI for this job? If so, NULL out
|
|
// the corresponding entry in the job identity set array.
|
|
//
|
|
|
|
DWORD CredentialIndex;
|
|
BYTE * pbIdentity;
|
|
|
|
hr = SAIFindIdentity(rgbIdentity,
|
|
cbSAI,
|
|
pbSAI,
|
|
&CredentialIndex,
|
|
NULL,
|
|
&pbIdentity,
|
|
NULL,
|
|
&pbSet);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (pbIdentity != NULL)
|
|
{
|
|
for (i = 0; i < dwSetCount; i++)
|
|
{
|
|
for (j = 0; j < rgIdentitySet[i].dwSetSubCount; j++)
|
|
{
|
|
if (pbIdentity == rgIdentitySet[i].rgpbIdentity[j])
|
|
{
|
|
rgIdentitySet[i].rgpbIdentity[j] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!FindNextFile(hFileEnum, &fd))
|
|
{
|
|
dwRet = GetLastError();
|
|
|
|
if (dwRet == ERROR_NO_MORE_FILES)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
hr = _HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if the service is stopping.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
hr = S_OK;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Non-NULL entries in the identity set array refer to job identities in
|
|
// the SAI to be removed. Mark them for removal.
|
|
//
|
|
|
|
for (i = 0; i < dwSetCount; i++)
|
|
{
|
|
if (rgIdentitySet[i].rgpbIdentity != NULL)
|
|
{
|
|
dwSetSubCount = rgIdentitySet[i].dwSetSubCount;
|
|
|
|
for (j = 0; j < dwSetSubCount; j++)
|
|
{
|
|
if (rgIdentitySet[i].rgpbIdentity[j] != NULL)
|
|
{
|
|
MARK_DELETED_ENTRY(rgIdentitySet[i].rgpbIdentity[j]);
|
|
rgIdentitySet[i].dwSetSubCount--;
|
|
fDirty = TRUE;
|
|
|
|
if (rgIdentitySet[i].dwSetSubCount == 0)
|
|
{
|
|
//
|
|
// Last identity in set. Mark associated SAC
|
|
// credential for removal also.
|
|
//
|
|
|
|
DWORD cbCredential;
|
|
BYTE * pbCredential;
|
|
|
|
hr = SACIndexCredential(i,
|
|
cbSAC,
|
|
pbSAC,
|
|
&cbCredential,
|
|
&pbCredential);
|
|
|
|
if (hr == S_FALSE)
|
|
{
|
|
//
|
|
// This should *never* happen. Consider the
|
|
// database corrupt if so.
|
|
//
|
|
|
|
ASSERT_SECURITY_DBASE_CORRUPT();
|
|
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
|
|
goto ErrorExit;
|
|
}
|
|
else if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
goto ErrorExit;
|
|
}
|
|
else
|
|
{
|
|
MARK_DELETED_ENTRY(pbCredential);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if the service is stopping.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
hr = S_OK;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Removed entries marked for deletion.
|
|
//
|
|
|
|
if (fDirty)
|
|
{
|
|
hr = SAICoalesceDeletedEntries(&cbSAI, &pbSAI);
|
|
CHECK_HRESULT(hr);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SACCoalesceDeletedEntries(&cbSAC, &pbSAC);
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Finally, persist the changes made to the SAI & SAC.
|
|
//
|
|
hr = WriteSecurityDBase(cbSAI, pbSAI, cbSAC, pbSAC);
|
|
CHECK_HRESULT(hr);
|
|
}
|
|
|
|
ErrorExit:
|
|
//
|
|
// Deallocate data structures allocated above.
|
|
//
|
|
for (i = 0; i < dwSetCount; i++)
|
|
{
|
|
if (rgIdentitySet[i].rgpbIdentity != NULL)
|
|
{
|
|
LocalFree(rgIdentitySet[i].rgpbIdentity);
|
|
}
|
|
}
|
|
|
|
if (rgIdentitySet != NULL) LocalFree(rgIdentitySet);
|
|
if (pbSAI != NULL) LocalFree(pbSAI);
|
|
if (pbSAC != NULL) LocalFree(pbSAC);
|
|
|
|
if (hFileEnum != INVALID_HANDLE_VALUE) FindClose(hFileEnum);
|
|
|
|
if (hCSP != NULL) CloseCSPHandle(hCSP);
|
|
//
|
|
// Log an error & rest the SA security dbases SAI & SAC if corruption
|
|
// is detected.
|
|
//
|
|
|
|
if (hr == SCHED_E_ACCOUNT_DBASE_CORRUPT)
|
|
{
|
|
//
|
|
// Log an error.
|
|
//
|
|
|
|
LogServiceError(IERR_SECURITY_DBASE_CORRUPTION, 0,
|
|
IDS_HELP_HINT_DBASE_CORRUPT);
|
|
|
|
//
|
|
// Reset SAI & SAC by writing four bytes of zeros into each.
|
|
// Ignore the return code. No recourse if this fails.
|
|
//
|
|
DWORD dwZero = 0;
|
|
WriteSecurityDBase(sizeof(dwZero), (BYTE *)&dwZero, sizeof(dwZero),
|
|
(BYTE *)&dwZero);
|
|
}
|
|
|
|
LeaveCriticalSection(&gcsSSCritSection);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SchedUPNToAccountName
|
|
//
|
|
// Synopsis: Converts a UPN to an Account Name
|
|
//
|
|
// Arguments: lpUPN - The UPN
|
|
// ppAccountName - Pointer to the location to create/copy the account name
|
|
//
|
|
// Returns: NO_ERROR - Success (ppAccountName contains the converted UPN)
|
|
// Any other Win32 error - error at some stage of conversion
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
DWORD
|
|
SchedUPNToAccountName(
|
|
IN LPCWSTR lpUPN,
|
|
OUT LPWSTR *ppAccountName
|
|
)
|
|
{
|
|
DWORD dwError;
|
|
HANDLE hDS;
|
|
PDS_NAME_RESULT pdsResult;
|
|
|
|
schAssert(ppAccountName != NULL);
|
|
|
|
schDebugOut((DEB_TRACE, "SchedUPNToAccountName: Converting \"%ws\"\n", lpUPN));
|
|
|
|
//
|
|
// Get a binding handle to the DS
|
|
//
|
|
dwError = DsBind(NULL, NULL, &hDS);
|
|
|
|
if (dwError != NO_ERROR)
|
|
{
|
|
schDebugOut((DEB_ERROR, "SchedUPNToAccountName: DsBind failed %d\n", dwError));
|
|
return dwError;
|
|
}
|
|
|
|
dwError = DsCrackNames(hDS, // Handle to the DS
|
|
DS_NAME_NO_FLAGS, // No parsing flags
|
|
DS_USER_PRINCIPAL_NAME, // We have a UPN
|
|
DS_NT4_ACCOUNT_NAME, // We want Domain\User
|
|
1, // Number of names to crack
|
|
&lpUPN, // Array of name(s)
|
|
&pdsResult); // Filled in by API
|
|
|
|
if (dwError != NO_ERROR)
|
|
{
|
|
schDebugOut((DEB_ERROR, "SchedUPNToAccountName: DsCrackNames failed %d\n", dwError));
|
|
|
|
DsUnBind(&hDS);
|
|
return dwError;
|
|
}
|
|
|
|
schAssert(pdsResult->cItems == 1);
|
|
schAssert(pdsResult->rItems != NULL);
|
|
|
|
if (pdsResult->rItems[0].status == DS_NAME_ERROR_DOMAIN_ONLY)
|
|
{
|
|
//
|
|
// Couldn't crack the name but we got the name of
|
|
// the domain where it is -- let's try it
|
|
//
|
|
DsUnBind(&hDS);
|
|
|
|
schAssert(pdsResult->rItems[0].pDomain != NULL);
|
|
|
|
schDebugOut((DEB_TRACE, "Retrying DsBind on domain %ws\n", pdsResult->rItems[0].pDomain));
|
|
|
|
dwError = DsBind(NULL, pdsResult->rItems[0].pDomain, &hDS);
|
|
|
|
//
|
|
// Free up the structure holding the old info
|
|
//
|
|
DsFreeNameResult(pdsResult);
|
|
|
|
if (dwError != NO_ERROR)
|
|
{
|
|
schDebugOut((DEB_ERROR, "SchedUPNToAccountName: DsBind #2 failed %d\n", dwError));
|
|
return dwError;
|
|
}
|
|
|
|
dwError = DsCrackNames(hDS, // Handle to the DS
|
|
DS_NAME_NO_FLAGS, // No parsing flags
|
|
DS_USER_PRINCIPAL_NAME, // We have a UPN
|
|
DS_NT4_ACCOUNT_NAME, // We want Domain\User
|
|
1, // Number of names to crack
|
|
&lpUPN, // Array of name(s)
|
|
&pdsResult); // Filled in by API
|
|
|
|
if (dwError != NO_ERROR)
|
|
{
|
|
schDebugOut((DEB_ERROR, "SchedUPNToAccountName: DsCrackNames #2 failed %d\n", dwError));
|
|
|
|
DsUnBind(&hDS);
|
|
return dwError;
|
|
}
|
|
|
|
schAssert(pdsResult->cItems == 1);
|
|
schAssert(pdsResult->rItems != NULL);
|
|
}
|
|
|
|
if (pdsResult->rItems[0].status != DS_NAME_NO_ERROR)
|
|
{
|
|
schDebugOut((DEB_ERROR, "SchedUPNToAccountName: DsCrackNames failure (status %#x)\n", pdsResult->rItems[0].status));
|
|
|
|
//
|
|
// DS errors don't map to Win32 errors -- this is the best we can do
|
|
//
|
|
dwError = SCHED_E_ACCOUNT_NAME_NOT_FOUND;
|
|
}
|
|
else
|
|
{
|
|
schDebugOut((DEB_TRACE, "SchedUPNToAccountName: Got \"%ws\"\n",
|
|
pdsResult->rItems[0].pName));
|
|
|
|
size_t cchBuff = wcslen(pdsResult->rItems[0].pName) + 1;
|
|
*ppAccountName = new WCHAR[cchBuff];
|
|
|
|
if (*ppAccountName != NULL)
|
|
{
|
|
StringCchCopy(*ppAccountName, cchBuff, pdsResult->rItems[0].pName);
|
|
}
|
|
else
|
|
{
|
|
dwError = GetLastError();
|
|
schDebugOut((DEB_ERROR, "SchedUPNToAccountName: LocalAlloc failed %d\n", dwError));
|
|
}
|
|
}
|
|
|
|
DsUnBind(&hDS);
|
|
DsFreeNameResult(pdsResult);
|
|
return dwError;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LookupAccountNameWrap
|
|
//
|
|
// Synopsis: BUGBUG This is a workaround for bug 254102 - LookupAccountName
|
|
// doesn't work when the DC can't be reached, even for the
|
|
// currently logged-on user, and even though LookupAccountSid
|
|
// does work. Remove this function when that bug is fixed.
|
|
//
|
|
// Arguments: Same as LookupAccountName - but cbSid and cbReferencedDomainName
|
|
// are assumed to be large enough, and peUse is ignored.
|
|
//
|
|
// Returns: Same as LookupAccountName.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL
|
|
LookupAccountNameWrap(
|
|
LPCTSTR lpSystemName, // address of string for system name
|
|
LPCTSTR lpAccountName, // address of string for account name
|
|
PSID Sid, // address of security identifier
|
|
LPDWORD cbSid, // address of size of security identifier
|
|
LPTSTR ReferencedDomainName,
|
|
// address of string for referenced domain
|
|
LPDWORD cbReferencedDomainName,
|
|
// address of size of domain string
|
|
PSID_NAME_USE peUse // address of SID-type indicator
|
|
)
|
|
{
|
|
//
|
|
// See if the account name matches the account name we cached
|
|
//
|
|
|
|
EnterCriticalSection(gUserLogonInfo.CritSection);
|
|
|
|
if (gUserLogonInfo.DomainUserName != NULL &&
|
|
lstrcmpi(gUserLogonInfo.DomainUserName, lpAccountName) == 0)
|
|
{
|
|
//
|
|
// The names match. Return the cached SID.
|
|
//
|
|
schDebugOut((DEB_TRACE, "Using cached SID for user \"%ws\"\n", lpAccountName));
|
|
if (CopySid(*cbSid, Sid, gUserLogonInfo.Sid))
|
|
{
|
|
LeaveCriticalSection(gUserLogonInfo.CritSection);
|
|
|
|
//
|
|
// Copy the ReferencedDomainName from the account name
|
|
//
|
|
PCWCH pchSlash = wcschr(lpAccountName, L'\\');
|
|
schAssert(pchSlash != NULL);
|
|
DWORD DomainLen = (DWORD)(pchSlash - lpAccountName);
|
|
schAssert(DomainLen+1 <= *cbReferencedDomainName);
|
|
wcsncpy(ReferencedDomainName, lpAccountName, DomainLen);
|
|
ReferencedDomainName[DomainLen] = L'\0';
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
schAssert(0);
|
|
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(gUserLogonInfo.CritSection);
|
|
|
|
return LookupAccountName(
|
|
lpSystemName,
|
|
lpAccountName,
|
|
Sid,
|
|
cbSid,
|
|
ReferencedDomainName,
|
|
cbReferencedDomainName,
|
|
peUse
|
|
);
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: ComputeJobSignature
|
|
//
|
|
// Synopsis: Creates a signature for the job file
|
|
//
|
|
// Arguments: [pwszFileName] - name of job file
|
|
// [pSignature] - block in which to store the signature. Must
|
|
// be at least SIGNATURE_SIZE bytes long.
|
|
// [dwHashMethod - dword value indicating which hash method to use;
|
|
// Default if not specified is the latest method.
|
|
//
|
|
// Returns: HRESULT
|
|
//
|
|
// Notes: The job must have been saved to disk before calling this
|
|
// function.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT
|
|
ComputeJobSignature(
|
|
LPCWSTR pwszFileName,
|
|
LPBYTE pbSignature,
|
|
DWORD dwHashMethod /* = 1 */
|
|
)
|
|
{
|
|
HCRYPTPROV hCSP;
|
|
|
|
HRESULT hr = GetCSPHandle(&hCSP);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = HashJobIdentity(hCSP, pwszFileName, pbSignature, dwHashMethod);
|
|
CloseCSPHandle(hCSP);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: CJob::Sign
|
|
//
|
|
// Synopsis: Computes and sets the job's signature
|
|
//
|
|
// Arguments: None
|
|
//
|
|
// Notes: The job must have been written to disk before calling this method
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT
|
|
CJob::Sign(
|
|
VOID
|
|
)
|
|
{
|
|
BYTE rgbSignature[SIGNATURE_SIZE];
|
|
HRESULT hr = ComputeJobSignature(m_ptszFileName, rgbSignature);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
return hr;
|
|
}
|
|
|
|
hr = _SetSignature(rgbSignature);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: CJob::VerifySignature
|
|
//
|
|
// Synopsis: Compares the job file's hash to the one stored in the file
|
|
//
|
|
// Arguments: None
|
|
//
|
|
// Notes: The job must have been written to disk before calling this method
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
BOOL
|
|
CJob::VerifySignature(
|
|
DWORD dwHashMethod /* = 1 */
|
|
) const
|
|
{
|
|
if (m_pbSignature == NULL)
|
|
{
|
|
CHECK_HRESULT(SCHED_E_ACCOUNT_INFORMATION_NOT_SET);
|
|
return FALSE;
|
|
}
|
|
|
|
BYTE rgbSignature[SIGNATURE_SIZE];
|
|
HRESULT hr = ComputeJobSignature(m_ptszFileName, rgbSignature, dwHashMethod);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_HRESULT(hr);
|
|
return FALSE;
|
|
}
|
|
|
|
if (memcmp(m_pbSignature, rgbSignature, SIGNATURE_SIZE) != 0)
|
|
{
|
|
CHECK_HRESULT(E_ACCESSDENIED);
|
|
return(FALSE);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: CSchedule::AddAtJobWithHash
|
|
//
|
|
// Synopsis: create a downlevel job
|
|
//
|
|
// Arguments: [At] - reference to an AT_INFO struct
|
|
// [pID] - returns the new ID (optional, can be NULL)
|
|
//
|
|
// Returns: HRESULTS
|
|
//
|
|
// Notes: This method is not exposed to external clients, thus it is not
|
|
// part of a public interface.
|
|
//-----------------------------------------------------------------------------
|
|
STDMETHODIMP
|
|
CSchedule::AddAtJobWithHash(const AT_INFO &At, DWORD * pID)
|
|
{
|
|
TRACE(CSchedule, AddAtJob);
|
|
HRESULT hr = S_OK;
|
|
CJob *pJob;
|
|
WCHAR wszName[MAX_PATH + 1];
|
|
WCHAR wszID[SCH_SMBUF_LEN];
|
|
|
|
hr = AddAtJobCommon(At, pID, &pJob, wszName, MAX_PATH + 1, wszID);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ERR_OUT("AddAtJobWithHash: AddAtJobCommon", hr);
|
|
return hr;
|
|
}
|
|
|
|
hr = AuditATJob(At, wszName);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR_OUT("AddAtJobWithHash: AuditATJob", hr);
|
|
}
|
|
|
|
//
|
|
// Now get a signature for the job file and add it to the job object
|
|
//
|
|
hr = pJob->Sign();
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ERR_OUT("AddAtJobWithHash: Sign", hr);
|
|
pJob->Release();
|
|
return hr;
|
|
}
|
|
|
|
hr = pJob->SaveWithRetry(pJob->GetFileName(),
|
|
FALSE,
|
|
SAVEP_VARIABLE_LENGTH_DATA |
|
|
SAVEP_PRESERVE_NET_SCHEDULE);
|
|
|
|
//
|
|
// Free the job object.
|
|
//
|
|
pJob->Release();
|
|
|
|
//
|
|
// Return the new job's ID and increment the ID counter
|
|
//
|
|
if (pID != NULL)
|
|
{
|
|
*pID = m_dwNextID;
|
|
}
|
|
|
|
hr = IncrementAndSaveID();
|
|
|
|
return hr;
|
|
}
|