//+--------------------------------------------------------------------------- // // 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 // found in private\inc\crypto #include #include // DsCrackNames #include "resource.h" #include "globals.hxx" // BUGBUG 254102 #include "sch_cls.hxx" // To implement AddAtJobWithHash #include "authzi.h" // for auditing #include #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; }