Leaked source code of windows server 2003
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

//+---------------------------------------------------------------------------
//
// 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;
}