//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 2002. // // File: conversion.cxx // // Contents: data conversion functions // // History: 11-Nov-02 Created // //---------------------------------------------------------------------------- #include "..\pch\headers.hxx" #include "common.hxx" #include "security.hxx" #include "statsync.hxx" extern CStaticCritSec gcsSSCritSection; #define SCH_DATA_VERSION L"DataVersion" //+--------------------------------------------------------------------------- // // Function: CheckDataVersion // // Synopsis: Check for registry key that will indicate // the version of the scheduler data. // // Arguments: None // // Returns: DWORD - indicates data version // //---------------------------------------------------------------------------- DWORD CheckDataVersion(void) { DWORD dwDataVersion = 0; HKEY hSchedKey = NULL; long lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_AGENT_KEY, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &hSchedKey); if (lErr == ERROR_SUCCESS) { DWORD dwType; DWORD cb = sizeof(dwDataVersion); lErr = RegQueryValueEx(hSchedKey, SCH_DATA_VERSION, NULL, &dwType, (LPBYTE) &dwDataVersion, &cb); if (lErr != ERROR_SUCCESS || dwType != REG_DWORD) { dwDataVersion = 0; } } if (hSchedKey != NULL) { RegCloseKey(hSchedKey); } return dwDataVersion; } //+--------------------------------------------------------------------------- // // Function: RecordDataVersion // // Synopsis: Update registry key to record current data version // // Arguments: dwDataVersion -- version value to store // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT RecordDataVersion(DWORD dwDataVersion) { HRESULT hr = S_OK; HKEY hSchedKey = NULL; long lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_AGENT_KEY, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &hSchedKey); if (lErr == ERROR_SUCCESS) { DWORD cb = sizeof(dwDataVersion); RegSetValueEx(hSchedKey, SCH_DATA_VERSION, NULL, REG_DWORD, (CONST BYTE *)&dwDataVersion, cb); if (lErr != ERROR_SUCCESS) { schDebugOut((DEB_ERROR, "RegSetValueEx of RecordDataVersion value failed %ld\n", lErr)); hr = E_FAIL; } } if (hSchedKey != NULL) { RegCloseKey(hSchedKey); } return hr; } //+--------------------------------------------------------------------------- // // Function: ConvertJobIdentityHashMethod // // Synopsis: Create new identity entries for each job file in the SAI // utilizing the new hash method. // // Arguments: None // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT ConvertJobIdentityHashMethod(void) { HCRYPTPROV hCSP = NULL; WCHAR* pwszTasksFolder = NULL; HANDLE hFileEnum = INVALID_HANDLE_VALUE; DWORD cbSAI = 0; DWORD cbSAC = 0; BYTE* pbSAI = NULL; BYTE* pbSAC = NULL; BYTE* pbIdentitySet = NULL; // // Obtain a provider handle to the CSP (for use with Crypto API). // HRESULT hr = GetCSPHandle(&hCSP); if (FAILED(hr)) { return(hr); } // // Guard SA security database access. // EnterCriticalSection(&gcsSSCritSection); // // Read the security database so we can do the lookups // hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC); if (SUCCEEDED(hr)) { if (cbSAI <= SAI_HEADER_SIZE) { // // Database empty, nothing to do. // } else { // // Enumerate job objects in the task's folder directory. // hr = GetTasksFolder(&pwszTasksFolder); if (SUCCEEDED(hr)) { WCHAR wszSearchPath[MAX_PATH + 1]; hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder); if (SUCCEEDED(hr)) { hr = StringCchCat(wszSearchPath, MAX_PATH + 1, EXTENSION_WILDCARD TSZ_JOB); if (SUCCEEDED(hr)) { WIN32_FIND_DATA fd; ZeroMemory(&fd, sizeof(fd)); DWORD dwRet = 0; if ((hFileEnum = FindFirstFile(wszSearchPath, &fd)) == INVALID_HANDLE_VALUE) { // // Either no jobs (this is OK), or an error occurred. // dwRet = GetLastError(); if (dwRet != ERROR_FILE_NOT_FOUND) { hr = _HRESULT_FROM_WIN32(dwRet); } } else { // // Must concatenate the filename returned from the enumeration onto the folder path // before computing the hash. Prepare for doing that repeatedly by taking the path, // adding a slash, and remembering the next character position. // hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder); if (SUCCEEDED(hr)) { DWORD iConcatenation = lstrlenW(pwszTasksFolder); wszSearchPath[iConcatenation++] = L'\\'; // // Process each found file // BYTE rgbIdentity[HASH_DATA_SIZE]; DWORD dwCredentialIndex = 0; BOOL bIsPasswordNull = FALSE; BYTE* pbFoundIdentity = NULL; DWORD cbFileName = 0; while (dwRet != ERROR_NO_MORE_FILES) { // // Truncate the existing name after the folder path, // then concatenate the new filename onto it. // wszSearchPath[iConcatenation] = L'\0'; if (SUCCEEDED(StringCchCat(wszSearchPath, MAX_PATH + 1, fd.cFileName))) { // // Hash the job into a unique identity using the old method // if (SUCCEEDED(HashJobIdentity(hCSP, wszSearchPath, rgbIdentity, 0))) { // // Find the identity in the SAI for this job // hr = SAIFindIdentity(rgbIdentity, cbSAI, pbSAI, &dwCredentialIndex, &bIsPasswordNull, &pbFoundIdentity, NULL, NULL); // // S_OK means the identity was found; S_FALSE means it wasn't // Other codes are errors. We process only the S_OKs, of course. // if (S_OK == hr) { // // Hash the job into a unique identity using the new method // if (SUCCEEDED(HashJobIdentity(hCSP, wszSearchPath, rgbIdentity))) { // // Store a NULL password by flipping the last bit of the hash data. // if (bIsPasswordNull) { LAST_HASH_BYTE(rgbIdentity) ^= 1; } // // Update the old identity in place with the new one // hr = SAIUpdateIdentity(rgbIdentity, pbFoundIdentity, cbSAI, pbSAI); if (FAILED(hr)) { break; } } else { // // we failed to produce a new hash for this file; // while unexpected, it shouldn't be fatal to the conversion process; // we just won't be able to convert this job; go on to the next job // } } else { // // we failed to find the original job identity; // perhaps its credentials have already been lost; // there's nothing we can do; go on to the next job // hr = S_OK; } } } if (!FindNextFile(hFileEnum, &fd)) { dwRet = GetLastError(); if (dwRet != ERROR_NO_MORE_FILES) { hr = _HRESULT_FROM_WIN32(dwRet); } break; } } if (SUCCEEDED(hr)) { // // Update security database with the new identities // hr = WriteSecurityDBase(cbSAI, pbSAI, cbSAC, pbSAC); } } } } } } } } if (hFileEnum != INVALID_HANDLE_VALUE) { FindClose(hFileEnum); } if (pwszTasksFolder) { delete [] pwszTasksFolder; } if (pbSAI) { LocalFree(pbSAI); } if (pbSAC) { LocalFree(pbSAC); } if (hCSP) { CloseCSPHandle(hCSP); } LeaveCriticalSection(&gcsSSCritSection); return hr; } //+--------------------------------------------------------------------------- // // Function: ConvertNetScheduleJobSignatures // // Synopsis: Netschedule jobs (AT jobs) are signed internally with a hash so // that the service can validate their authenticity at run time. // Since we are changing how the hash is created, in order to be // runnable again, the AT jobs must be signed again with the new hash. // Prior to re-signing, we will verify the jobs using the old hash. // // Arguments: None // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT ConvertNetScheduleJobSignatures(void) { HRESULT hr = S_OK; HANDLE hFileEnum = INVALID_HANDLE_VALUE; WCHAR wszSearchPath[MAX_PATH + 1]; hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath); if (SUCCEEDED(hr)) { hr = StringCchCat(wszSearchPath, MAX_PATH + 1, L"\\" TSZ_AT_JOB_PREFIX L"*" TSZ_DOTJOB); if (SUCCEEDED(hr)) { WIN32_FIND_DATA fd; ZeroMemory(&fd, sizeof(fd)); DWORD dwRet = 0; if ((hFileEnum = FindFirstFile(wszSearchPath, &fd)) == INVALID_HANDLE_VALUE) { // // Either no jobs (this is OK), or an error occurred. // dwRet = GetLastError(); if (dwRet != ERROR_FILE_NOT_FOUND) { hr = _HRESULT_FROM_WIN32(dwRet); } } else { // // Must concatenate the filename returned from the enumeration onto the folder path // before loading the job. Prepare for doing that repeatedly by taking the path, // adding a slash, and remembering the next character position. // hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath); if (SUCCEEDED(hr)) { DWORD iConcatenation = lstrlenW(g_TasksFolderInfo.ptszPath); wszSearchPath[iConcatenation++] = L'\\'; // // Process each found file // while (dwRet != ERROR_NO_MORE_FILES) { // // Truncate the existing name after the folder path, // then concatenate the new filename onto it. // wszSearchPath[iConcatenation] = L'\0'; if (SUCCEEDED(StringCchCat(wszSearchPath, MAX_PATH + 1, fd.cFileName))) { // // Load, verify, sign, save, and release the job // CJob* pJob = CJob::Create(); if (!pJob) { hr = E_OUTOFMEMORY; break; } if (SUCCEEDED(pJob->Load(wszSearchPath, 0))) { // // verify using the old hash // if (pJob->VerifySignature(0)) { // // sign using the new hash // if (SUCCEEDED(pJob->Sign())) { pJob->SaveWithRetry(pJob->GetFileName(), FALSE, SAVEP_VARIABLE_LENGTH_DATA | SAVEP_PRESERVE_NET_SCHEDULE); } } } pJob->Release(); } if (!FindNextFile(hFileEnum, &fd)) { dwRet = GetLastError(); if (dwRet != ERROR_NO_MORE_FILES) { hr = _HRESULT_FROM_WIN32(dwRet); } break; } } } } if (hFileEnum != INVALID_HANDLE_VALUE) { FindClose(hFileEnum); } } } return hr; } //+--------------------------------------------------------------------------- // // Function: PerformDataConversions // // Synopsis: Perform any data conversions that may be required as a result // of changes to task scheduler. This function is intended to // allow potential future conversions to be added easily, and to // allow conversions of data that may have already had earlier // conversions applied. // // Arguments: None // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT PerformDataConversions(void) { HRESULT hr = S_OK; switch (CheckDataVersion()) { case 0: hr = ConvertJobIdentityHashMethod(); if (SUCCEEDED(hr)) { hr = RecordDataVersion(1); } // fall through to handle next higher conversion case 1: if (SUCCEEDED(hr)) { hr = ConvertNetScheduleJobSignatures(); if (SUCCEEDED(hr)) { hr = RecordDataVersion(2); } } // fall through to handle next higher conversion case 2: // nothing to do for now // version 2 is currently the highest and already has all changes applied break; default: // if the value is not listed above, then someone has altered the registry; // the version value is meaningless to us, so it is safest to not do anything, // but reflect the unusual situation in the error code in case we want to log it hr = S_FALSE; break; } return hr; }