// Copyright (C) 2002 Microsoft Corporation // // answerfile reader object // // 5 April 2002 sburns #include "headers.hxx" #include "AnswerFile.hpp" #include "resource.h" static const String SECTION_NAME(L"DCInstall"); const String AnswerFile::OPTION_ADMIN_PASSWORD (L"AdministratorPassword"); const String AnswerFile::OPTION_ALLOW_ANON_ACCESS (L"AllowAnonymousAccess"); const String AnswerFile::OPTION_AUTO_CONFIG_DNS (L"AutoConfigDNS"); const String AnswerFile::OPTION_CHILD_NAME (L"ChildName"); const String AnswerFile::OPTION_CRITICAL_REPLICATION_ONLY (L"CriticalReplicationOnly"); const String AnswerFile::OPTION_DATABASE_PATH (L"DatabasePath"); const String AnswerFile::OPTION_DISABLE_CANCEL_ON_DNS_INSTALL (L"DisableCancelForDnsInstall"); const String AnswerFile::OPTION_DNS_ON_NET (L"DNSOnNetwork"); const String AnswerFile::OPTION_GC_CONFIRM (L"ConfirmGc"); const String AnswerFile::OPTION_IS_LAST_DC (L"IsLastDCInDomain"); const String AnswerFile::OPTION_LOG_PATH (L"LogPath"); const String AnswerFile::OPTION_NEW_DOMAIN (L"NewDomain"); const String AnswerFile::OPTION_NEW_DOMAIN_NAME (L"NewDomainDNSName"); const String AnswerFile::OPTION_NEW_DOMAIN_NETBIOS_NAME (L"DomainNetbiosName"); const String AnswerFile::OPTION_PARENT_DOMAIN_NAME (L"ParentDomainDNSName"); const String AnswerFile::OPTION_PASSWORD (L"Password"); const String AnswerFile::OPTION_REBOOT (L"RebootOnSuccess"); const String AnswerFile::OPTION_REMOVE_APP_PARTITIONS (L"RemoveApplicationPartitions"); const String AnswerFile::OPTION_REPLICATION_SOURCE (L"ReplicationSourceDC"); const String AnswerFile::OPTION_REPLICA_DOMAIN_NAME (L"ReplicaDomainDNSName"); const String AnswerFile::OPTION_REPLICA_OR_MEMBER (L"ReplicaOrMember"); const String AnswerFile::OPTION_REPLICA_OR_NEW_DOMAIN (L"ReplicaOrNewDomain"); const String AnswerFile::OPTION_SAFE_MODE_ADMIN_PASSWORD (L"SafeModeAdminPassword"); const String AnswerFile::OPTION_SET_FOREST_VERSION (L"SetForestVersion"); const String AnswerFile::OPTION_SITE_NAME (L"SiteName"); const String AnswerFile::OPTION_SOURCE_PATH (L"ReplicationSourcePath"); const String AnswerFile::OPTION_SYSKEY (L"Syskey"); const String AnswerFile::OPTION_SYSVOL_PATH (L"SYSVOLPath"); const String AnswerFile::OPTION_USERNAME (L"UserName"); const String AnswerFile::OPTION_USER_DOMAIN (L"UserDomain"); const String AnswerFile::VALUE_CHILD (L"Child"); const String AnswerFile::VALUE_DOMAIN (L"Domain"); const String AnswerFile::VALUE_NO (L"No"); const String AnswerFile::VALUE_NO_DONT_PROMPT (L"NoAndNoPromptEither"); const String AnswerFile::VALUE_REPLICA (L"Replica"); const String AnswerFile::VALUE_TREE (L"Tree"); const String AnswerFile::VALUE_YES (L"Yes"); static StringList PASSWORD_OPTIONS_LIST; static bool passwordOptionsListInitialized = false; void GetAllKeys(const String& filename, StringList& resultList) { LOG_FUNCTION(GetAllKeys); ASSERT(FS::IsValidPath(filename)); resultList.clear(); // our first call is with a large buffer, hoping that it will suffice... #ifdef DBG // on chk builds, use a small buffer size so that our growth algorithm // gets exercised unsigned bufSizeInCharacters = 3; #else unsigned bufSizeInCharacters = 1023; #endif PWSTR buffer = 0; do { buffer = new WCHAR[bufSizeInCharacters + 1]; // REVIEWED-2002/02/22-sburns byte count correctly passed in ::ZeroMemory(buffer, (bufSizeInCharacters + 1) * sizeof WCHAR); DWORD result = // REVIEWED-2002/02/22-sburns call correctly passes size in characters. ::GetPrivateProfileString( SECTION_NAME.c_str(), 0, L"default", buffer, bufSizeInCharacters, filename.c_str()); if (!result) { break; } // A value was found. check to see if it was truncated. Since lpKeyName // was null, check result against character count - 2 if (result == bufSizeInCharacters - 2) { // buffer was too small, so the value was truncated. Resize the // buffer and try again. // no need to scribble out the buffer: we're retrieving key names, // not values. delete[] buffer; bufSizeInCharacters *= 2; if (bufSizeInCharacters > USHRT_MAX) // effectively ~32K max { // too big. way too big. We'll make do with the truncated value. ASSERT(false); break; } continue; } // copy out the strings results into list elements PWSTR p = buffer; while (*p) { resultList.push_back(p); // REVIEWED-2002/04/08-sburns wcslen is ok, since we arrange for // the buffer to be null terminated p += wcslen(p) + 1; } break; } //lint -e506 ok that this looks like "loop forever" while (true); delete[] buffer; } AnswerFile::AnswerFile(const String& filename_) : filename(filename_), isSafeModePasswordPresent(false) { LOG_CTOR(AnswerFile); // the caller is expected to have verified this ASSERT(FS::PathExists(filename)); GetAllKeys(filename, keysPresent); isSafeModePasswordPresent = IsKeyPresent(OPTION_SAFE_MODE_ADMIN_PASSWORD); // remove read-only file attribute DWORD attrs = 0; HRESULT hr = Win::GetFileAttributes(filename, attrs); if (SUCCEEDED(hr) && attrs & FILE_ATTRIBUTE_READONLY) { LOG(L"Removing readonly attribute on " + filename); hr = Win::SetFileAttributes(filename, attrs & ~FILE_ATTRIBUTE_READONLY); // if this failed, well, we tried. The user runs the risk of cleartext // passwords left in his file. LOG_HRESULT(hr); } // Read all the password options into the encrypted value map, erasing // them as we go. if (!passwordOptionsListInitialized) { ASSERT(PASSWORD_OPTIONS_LIST.empty()); PASSWORD_OPTIONS_LIST.clear(); PASSWORD_OPTIONS_LIST.push_back(OPTION_PASSWORD); PASSWORD_OPTIONS_LIST.push_back(OPTION_ADMIN_PASSWORD); PASSWORD_OPTIONS_LIST.push_back(OPTION_SYSKEY); PASSWORD_OPTIONS_LIST.push_back(OPTION_SAFE_MODE_ADMIN_PASSWORD); passwordOptionsListInitialized = true; } String empty; for ( StringList::iterator i = PASSWORD_OPTIONS_LIST.begin(); i != PASSWORD_OPTIONS_LIST.end(); ++i) { if (IsKeyPresent(*i)) { ovMap[*i] = EncryptedReadKey(*i); hr = WriteKey(*i, empty); if (FAILED(hr)) { popup.Error( Win::GetDesktopWindow(), hr, String::format( IDS_FAILED_PASSWORD_WRITE_TO_ANSWERFILE, i->c_str(), filename.c_str())); } } } } AnswerFile::~AnswerFile() { LOG_DTOR(AnswerFile); } String AnswerFile::ReadKey(const String& key) const { LOG_FUNCTION2(AnswerFile::ReadKey, key); ASSERT(!key.empty()); String result = // REVIEWED-2002/02/22-sburns no cch/cb issue here. Win::GetPrivateProfileString(SECTION_NAME, key, String(), filename); // Don't log the value, as it may be a password. // LOG(L"value=" + result); return result.strip(String::BOTH); } EncryptedString AnswerFile::EncryptedReadKey(const String& key) const { LOG_FUNCTION2(AnswerFile::EncodedReadKey, key); ASSERT(!key.empty()); EncryptedString retval; #ifdef DBG // on chk builds, use a small buffer size so that our growth algorithm // gets exercised unsigned bufSizeInCharacters = 3; #else unsigned bufSizeInCharacters = 1023; #endif PWSTR buffer = 0; do { // +1 for extra null-termination paranoia buffer = new WCHAR[bufSizeInCharacters + 1]; // REVIEWED-2002/02/22-sburns byte count correctly passed in ::ZeroMemory(buffer, (bufSizeInCharacters + 1) * sizeof WCHAR); DWORD result = // REVIEWED-2002/02/22-sburns call correctly passes size in characters. ::GetPrivateProfileString( SECTION_NAME.c_str(), key.c_str(), L"", buffer, bufSizeInCharacters, filename.c_str()); if (!result) { break; } // A value was found. check to see if it was truncated. neither // lpAppName nor lpKeyName were null, so check result against character // count - 1 if (result == bufSizeInCharacters - 1) { // buffer was too small, so the value was truncated. Resize the // buffer and try again. // Since the buffer may have contained passwords, scribble it // out // REVIEWED-2002/02/22-sburns byte count correctly passed in ::SecureZeroMemory(buffer, (bufSizeInCharacters + 1) * sizeof WCHAR); delete[] buffer; bufSizeInCharacters *= 2; if (bufSizeInCharacters > USHRT_MAX) // effectively ~32K max { // too big. way too big. We'll make do with the truncated value. ASSERT(false); break; } continue; } // don't need to trim whitespace, GetPrivateProfileString does that // for us. retval.Encrypt(buffer); break; } while (true); // Since the buffer may have contained passwords, scribble it // out // REVIEWED-2002/02/22-sburns byte count correctly passed in ::SecureZeroMemory(buffer, (bufSizeInCharacters + 1) * sizeof WCHAR); delete[] buffer; // Don't log the value, as it may be a password. // LOG(L"value=" + result); return retval; } HRESULT AnswerFile::WriteKey(const String& key, const String& value) const { LOG_FUNCTION2(AnswerFile::WriteKey, key); ASSERT(!key.empty()); HRESULT hr = Win::WritePrivateProfileString(SECTION_NAME, key, value, filename); return hr; } bool AnswerFile::IsKeyPresent(const String& key) const { LOG_FUNCTION2(AnswerFile::IsKeyPresent, key); ASSERT(!key.empty()); bool result = false; // If GetAllKeys failed, then we won't find the option in the keys list // and will assume that the option is not specified. This is the best // we can do in the case where we can't read the keys. if ( std::find(keysPresent.begin(), keysPresent.end(), key) != keysPresent.end() ) { result = true; } LOG_BOOL(result); return result; } bool IsPasswordOption(const String& option) { ASSERT(passwordOptionsListInitialized); bool result = false; if ( std::find( PASSWORD_OPTIONS_LIST.begin(), PASSWORD_OPTIONS_LIST.end(), option) != PASSWORD_OPTIONS_LIST.end() ) { result = true; } return result; } String AnswerFile::GetOption(const String& option) const { LOG_FUNCTION2(AnswerFile::GetOption, option); String result = ReadKey(option); if (!IsPasswordOption(option)) { LOG(result); } else { // should use GetEncryptedAnswerFileOption for passwords ASSERT(false); } return result; } EncryptedString AnswerFile::GetEncryptedOption(const String& option) const { LOG_FUNCTION2(AnswerFile::GetEncryptedOption, option); OptionEncryptedValueMap::const_iterator ci = ovMap.find(option); if (ci != ovMap.end()) { return ci->second; } return EncryptedString(); } bool AnswerFile::IsSafeModeAdminPwdOptionPresent() const { LOG_FUNCTION(AnswerFile::IsSafeModeAdminPwdOptionPresent); LOG_BOOL(isSafeModePasswordPresent); return isSafeModePasswordPresent; }